Sources/OrgCore/Semantic/HeadingInfo.swift
67 lines · 2655 bytes
1import Foundation
2
3public struct Property: Sendable, Equatable {
4 public let key: String
5 public let value: String
6 /// `:KEY+:` appends to the value instead of replacing it.
7 public let additive: Bool
8
9 public init(key: String, value: String, additive: Bool = false) {
10 self.key = key
11 self.value = value
12 self.additive = additive
13 }
14
15 /// A `:KEY: value` or `:KEY+: value` drawer line.
16 init?(line: String) {
17 let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
18 guard trimmed.hasPrefix(":") else { return nil }
19 let rest = trimmed.dropFirst()
20 guard let colon = rest.firstIndex(of: ":") else { return nil }
21 var key = rest[..<colon]
22 guard !key.isEmpty else { return nil }
23 let additive = key.hasSuffix("+")
24 if additive { key = key.dropLast() }
25 let value = rest[rest.index(after: colon)...].trimmingCharacters(in: .whitespaces)
26 self.init(key: String(key), value: value, additive: additive)
27 }
28}
29
30public struct Clock: Sendable, Equatable {
31 public let start: Timestamp.Point
32 /// Nil while the clock is running.
33 public let end: Timestamp.Point?
34 /// From `=> H:MM`, when present.
35 public let minutes: Int?
36}
37
38/// One heading and the facts that belong to it directly, not to its children.
39public struct HeadingInfo: Sendable, Equatable {
40 public internal(set) var sectionRange: Range<Int>
41 public internal(set) var headingRange: Range<Int>
42 public internal(set) var level: Int
43 /// Index of the parent heading in `DocumentModel.headings`.
44 public internal(set) var parent: Int?
45 public internal(set) var todo: String?
46 public internal(set) var isDone: Bool
47 /// The cookie's value (`A`, `10`), when the heading has one.
48 public internal(set) var priority: String?
49 /// The title as written, markup included.
50 public internal(set) var title: String
51 public internal(set) var localTags: [String]
52 /// Property drawer lines in order.
53 public internal(set) var properties: [Property]
54 public internal(set) var scheduled: Timestamp?
55 public internal(set) var deadline: Timestamp?
56 public internal(set) var closed: Timestamp?
57 public internal(set) var clocks: [Clock]
58 /// Timestamps in the title and body, excluding planning and clock lines.
59 public internal(set) var timestamps: [Timestamp]
60 /// Link targets in the title and body.
61 public internal(set) var links: [String]
62
63 /// The heading's own `ID`. Never inherited.
64 public var id: String? {
65 properties.last { $0.key.uppercased() == "ID" && !$0.additive }?.value
66 }
67}