krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgCore/Agenda/Board.swift

3a3dedba062790e1580c66d38606eda4b02cb501
orgstar/Sources/OrgCore/Agenda/Board.swift history · blame · raw

58 lines · 2500 bytes

 1import Foundation
 2
 3/// An entry as the table and kanban views show it.
 4public struct BoardEntry: Sendable, Equatable, Identifiable {
 5    public let path: String
 6    public let headingOffset: Int
 7    /// The heading line, to check the offset still holds before editing.
 8    public let headingLine: String
 9    public let title: String
10    public let todo: String?
11    public let isDone: Bool
12    public let priority: String?
13    public let tags: [String]
14    public let category: String
15    /// Planning timestamps without brackets.
16    public let scheduled: String?
17    public let deadline: String?
18    /// Upper-cased keys of the entry's own drawer.
19    public let properties: [String: String]
20
21    public var id: String { "\(path)#\(headingOffset)" }
22}
23
24public enum Board {
25    /// Entries of `sources` that match `match` (a tags/property/TODO match, as for `m` in the
26    /// agenda), or every entry with a TODO keyword when `match` is empty.
27    public static func entries(_ sources: [AgendaSource], match: String, now: Date = Date(), calendar: Calendar = .current) -> [BoardEntry] {
28        let trimmed = match.trimmingCharacters(in: .whitespaces)
29        let matcher = trimmed.isEmpty ? nil : TagsMatcher(trimmed)
30        var result: [BoardEntry] = []
31        for source in sources {
32            for heading in source.headings where !heading.skipped {
33                if let matcher {
34                    guard matcher.matches(heading, in: source, now: now, calendar: calendar) else { continue }
35                } else if heading.todo == nil {
36                    continue
37                }
38                result.append(BoardEntry(
39                    path: source.path, headingOffset: heading.start, headingLine: heading.line, title: heading.title,
40                    todo: heading.todo, isDone: heading.isDone, priority: heading.priority, tags: heading.tags.map(\.name),
41                    category: heading.category, scheduled: heading.scheduled?.raw, deadline: heading.deadline?.raw,
42                    properties: heading.properties
43                ))
44            }
45        }
46        return result
47    }
48
49    /// The keywords for kanban columns, in sequence order: each file's keywords, first come
50    /// first.
51    public static func keywords(_ sources: [AgendaSource]) -> [String] {
52        var seen: [String] = []
53        for source in sources {
54            for keyword in source.keywords where !seen.contains(keyword) { seen.append(keyword) }
55        }
56        return seen
57    }
58}