Last active
December 27, 2021 01:26
-
-
Save mntone/36d161acce82bf7b2dc04ca0b902c5f9 to your computer and use it in GitHub Desktop.
Copyright (C) 2021 mntone. All right reserved. This source code is under MIT license.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
enum EmphasizableToken { | |
case plain(String) | |
case toggleEmphasize | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
struct EmphasizableTokenizer: Tokenizer { | |
private let text: String | |
private var index: String.Index | |
init(_ text: String) { | |
self.text = text | |
self.index = text.startIndex | |
} | |
mutating func nextToken() -> EmphasizableToken? { | |
guard let ch = nextCharacter() else { return nil } | |
if ch == "*", isEmphasize() { | |
return .toggleEmphasize | |
} else { | |
return .plain(stringToken()) | |
} | |
} | |
private mutating func nextCharacter() -> String.Element? { | |
guard index != text.endIndex else { return nil } | |
return text[index] | |
} | |
private mutating func isEmphasize(advanced: Bool = true) -> Bool { | |
let nextIndex = text.index(after: index) | |
guard nextIndex != text.endIndex, | |
text[nextIndex] == "*" else { return false } | |
if advanced { | |
index = text.index(after: nextIndex) | |
} | |
return true | |
} | |
private mutating func stringToken() -> String { | |
let startIndex: String.Index = index | |
repeat { | |
if text[index] == "*", isEmphasize(advanced: false) { | |
return String(text[startIndex..<index]) | |
} | |
index = text.index(after: index) | |
} while index != text.endIndex | |
assert(startIndex != text.endIndex) | |
return String(text[startIndex...]) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@testable import EmphasizableToken | |
import XCTest | |
extension EmphasizableToken: Equatable { | |
public static func == (lhs: EmphasizableToken, rhs: EmphasizableToken) -> Bool { | |
switch (lhs, rhs) { | |
case let (.plain(leftText), .plain(rightText)): | |
return leftText == rightText | |
case (.toggleEmphasize, .toggleEmphasize): | |
return true | |
default: | |
return false | |
} | |
} | |
} | |
typealias _EmphasizableTestTuple = (tokenizer: EmphasizableTokenizer, expected: [EmphasizableToken]) | |
private func XCTAssertExecute(_ testsData: [_EmphasizableTestTuple]) { | |
for var testData in testsData { | |
var results: [EmphasizableToken] = [] | |
while let token = testData.tokenizer.nextToken() { | |
results.append(token) | |
} | |
XCTAssertEqual(results, testData.expected) | |
} | |
} | |
class EmphasizableTokenizerTests: XCTestCase { | |
func testPlains() throws { | |
let tests: [_EmphasizableTestTuple] = [ | |
(EmphasizableTokenizer("text"), [.plain("text")]), | |
(EmphasizableTokenizer("t*e*x*t"), [.plain("t*e*x*t")]), | |
] | |
XCTAssertExecute(tests) | |
} | |
func testSimple() throws { | |
let tests: [_EmphasizableTestTuple] = [ | |
(EmphasizableTokenizer("**emphasize**"), [.toggleEmphasize, .plain("emphasize"), .toggleEmphasize]), | |
(EmphasizableTokenizer("**emphasize"), [.toggleEmphasize, .plain("emphasize")]), | |
] | |
XCTAssertExecute(tests) | |
} | |
func testComplex() throws { | |
let tests: [_EmphasizableTestTuple] = [ | |
(EmphasizableTokenizer("prefix**emphasize**"), [.plain("prefix"), .toggleEmphasize, .plain("emphasize"), .toggleEmphasize]), | |
(EmphasizableTokenizer("**emphasize**suffix"), [.toggleEmphasize, .plain("emphasize"), .toggleEmphasize, .plain("suffix")]), | |
(EmphasizableTokenizer("prefix**emphasize**suffix"), [.plain("prefix"), .toggleEmphasize, .plain("emphasize"), .toggleEmphasize, .plain("suffix")]), | |
] | |
XCTAssertExecute(tests) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
extension Text { | |
init(styled text: String) { | |
var tokenizer = EmphasizableTokenizer(text) | |
var isEmphasize: Bool = false | |
var styledTexts: [Text] = [] | |
while let token = tokenizer.nextToken() { | |
switch token { | |
case let .plain(string): | |
let styledText: Text | |
if isEmphasize { | |
// MARK: your emphasized style | |
styledText = Text(verbatim: string).bold().foregroundColor(.primary) | |
} else { | |
styledText = Text(verbatim: string) | |
} | |
styledTexts.append(styledText) | |
case .toggleEmphasize: | |
isEmphasize.toggle() | |
} | |
} | |
switch styledTexts.count { | |
case 0: | |
self = Text("") | |
case 1: | |
self = styledTexts[0] | |
default: | |
self = styledTexts.dropFirst().reduce(styledTexts[0]) { curr, next in | |
curr + next | |
} | |
} | |
} | |
init(styledLocalized key: String) { | |
let localized = NSLocalizedString(key, comment: "") | |
self.init(styled: localized) | |
} | |
init(styledLocalized key: String, param: Int) { | |
let localized = String.localizedStringWithFormat(NSLocalizedString(key, comment: ""), param) | |
self.init(styled: localized) | |
} | |
init(styledLocalized key: String, param: Int32) { | |
let localized = String.localizedStringWithFormat(NSLocalizedString(key, comment: ""), Int(param)) | |
self.init(styled: localized) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
protocol Tokenizer { | |
associatedtype Token | |
init(_ text: String) | |
mutating func nextToken() -> Token? | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment