diff options
Diffstat (limited to 'Sources/NorgKit/Models/DetachedModifier.swift')
| -rw-r--r-- | Sources/NorgKit/Models/DetachedModifier.swift | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/Sources/NorgKit/Models/DetachedModifier.swift b/Sources/NorgKit/Models/DetachedModifier.swift new file mode 100644 index 0000000..1f28c36 --- /dev/null +++ b/Sources/NorgKit/Models/DetachedModifier.swift @@ -0,0 +1,61 @@ +/// A detached modifier with its marker, nestling level, task status (if set), +/// and content. +struct DetachedModifier { + let marker: Character + let level: Int + let status: TaskStatus? + let statusIndex: String.Index? + let content: Substring + + /// Finds and parses a detached modifier if present. The accepting markers + /// is used to reduce the markers being parsed. + static func parse(_ line: Substring, accepting markers: ASCIIByteSet) -> DetachedModifier? { + + let utf8 = line.utf8 + let end = utf8.endIndex + var i = utf8.startIndex + + while i < end, ASCIIHelper.isWhitespace(utf8[i]) { i = utf8.index(after: i) } + guard i < end else { return nil } + + let markerByte = utf8[i] + guard markers.contains(markerByte) else { return nil } + + var level = 0 + var run = i + while run < end, utf8[run] == markerByte { + level += 1 + run = utf8.index(after: run) + } + + guard run < end, ASCIIHelper.isWhitespace(utf8[run]) else { return nil } + + var rest = run + while rest < end, ASCIIHelper.isWhitespace(utf8[rest]) { rest = utf8.index(after: rest) } + + var status: TaskStatus? + var statusIndex: String.Index? + var contentStart = rest + if rest < end, utf8[rest] == UInt8(ascii: "(") { + let markerPosition = utf8.index(after: rest) + if markerPosition < end { + let closePosition = utf8.index(after: markerPosition) + let statusByte = utf8[markerPosition] + if closePosition < end, utf8[closePosition] == UInt8(ascii: ")"), + let parsed = TaskStatus(markerByte: statusByte) { + status = parsed + statusIndex = markerPosition + contentStart = utf8.index(after: closePosition) + } + } + } + + return DetachedModifier( + marker: Character(Unicode.Scalar(markerByte)), + level: level, + status: status, + statusIndex: statusIndex, + content: TextHelper.whitespaceTrimmed(line[contentStart...]) + ) + } +} |