Skip to content

Instantly share code, notes, and snippets.

@mntone
Last active December 27, 2021 01:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mntone/36d161acce82bf7b2dc04ca0b902c5f9 to your computer and use it in GitHub Desktop.
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.
import Foundation
enum EmphasizableToken {
case plain(String)
case toggleEmphasize
}
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...])
}
}
@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)
}
}
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)
}
}
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