aboutsummaryrefslogtreecommitdiff
path: root/Sources/NorgKit/Models/DetachedModifier.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Sources/NorgKit/Models/DetachedModifier.swift')
-rw-r--r--Sources/NorgKit/Models/DetachedModifier.swift61
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...])
+ )
+ }
+}