krz/orgstar

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

Sources/OrgApp/AgendaViews.swift

322a87d47526d505d903e05d14e65a5464e1468f
orgstar/Sources/OrgApp/AgendaViews.swift history · blame · raw

95 lines · 3740 bytes

 1import Foundation
 2import OrgCore
 3
 4/// What the agenda window shows.
 5public enum AgendaMode: Hashable, Sendable {
 6    /// `org-agenda-list`; nil span and start use the settings.
 7    case agenda(span: Int?, startOffset: Int?)
 8    /// `org-todo-list`, optionally for `KWD1|KWD2`.
 9    case todo(keywords: String?)
10    /// `org-tags-view`.
11    case match(String, todoOnly: Bool)
12    /// `org-search-view`.
13    case search(String, todoOnly: Bool)
14}
15
16/// A named view: the built-in ones and those in `views.toml`.
17public struct AgendaViewDefinition: Hashable, Sendable, Identifiable {
18    public let name: String
19    public let mode: AgendaMode
20    public var id: String { name }
21
22    public init(name: String, mode: AgendaMode) {
23        self.name = name
24        self.mode = mode
25    }
26}
27
28/// Reads `views.toml`:
29///
30/// ```toml
31/// [[view]]
32/// name = "Work"
33/// type = "match"        # agenda, todo, match, todo-match, search or todo-search
34/// match = "+work-boss"
35/// ```
36///
37/// `agenda` views take `span` and `start` (days from today), `todo` views `keywords`
38/// (`WAIT|HOLD`); a `search` view's `match` is its search string.
39@MainActor
40public enum AgendaViewLoader {
41    public static var userFile: URL { UserConfig.file("views.toml") }
42
43    public static let builtIn = [
44        AgendaViewDefinition(name: "Agenda", mode: .agenda(span: nil, startOffset: nil)),
45        AgendaViewDefinition(name: "TODO List", mode: .todo(keywords: nil)),
46    ]
47
48    /// The built-in views followed by the user's, and the problems found in the file.
49    public static func load(userFile: URL = userFile) -> (views: [AgendaViewDefinition], problems: [String]) {
50        guard let text = try? String(contentsOf: userFile, encoding: .utf8) else { return (builtIn, []) }
51        let tables: [TOML.Table]
52        do {
53            tables = try TOML.parse(text)
54        } catch {
55            return (builtIn, ["views.toml: \(error)"])
56        }
57        var views = builtIn
58        var problems: [String] = []
59        for table in tables where !(table.name.isEmpty && table.values.isEmpty) {
60            guard table.name == "view", table.isArrayElement else {
61                problems.append("views.toml line \(table.line): expected [[view]]")
62                continue
63            }
64            guard let name = table.values["name"]?.string else {
65                problems.append("views.toml line \(table.line): a view needs a name")
66                continue
67            }
68            let match = table.values["match"]?.string
69            let mode: AgendaMode
70            switch table.values["type"]?.string ?? "agenda" {
71            case "agenda":
72                mode = .agenda(span: table.values["span"]?.integer, startOffset: table.values["start"]?.integer)
73            case "todo":
74                mode = .todo(keywords: table.values["keywords"]?.string)
75            case "match", "todo-match":
76                guard let match else {
77                    problems.append("views.toml line \(table.line): \(name) needs a match")
78                    continue
79                }
80                mode = .match(match, todoOnly: table.values["type"]?.string == "todo-match")
81            case "search", "todo-search":
82                guard let match else {
83                    problems.append("views.toml line \(table.line): \(name) needs a match")
84                    continue
85                }
86                mode = .search(match, todoOnly: table.values["type"]?.string == "todo-search")
87            case let other:
88                problems.append("views.toml line \(table.line): unknown type \(other)")
89                continue
90            }
91            views.append(AgendaViewDefinition(name: name, mode: mode))
92        }
93        return (views, problems)
94    }
95}