From 7f722ee1690b33e3431f9695af9d118b0bd0bf11 Mon Sep 17 00:00:00 2001 From: Michele Giugliano <9309421+mgiugliano@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:16:14 +0100 Subject: [PATCH] Added feature: JSON string reminder entry --- README.md | 15 ++++ Sources/RemindersLibrary/CLI.swift | 22 ++++++ .../EKReminder+Encodable.swift | 2 +- .../RemindersLibrary/NaturalLanguage.swift | 2 +- Sources/RemindersLibrary/Reminders.swift | 77 ++++++++++++++++++- 5 files changed, 113 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be777bf..c2ae929 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,21 @@ $ reminders show Soon 3: Something really important (priority: high) ``` +#### Add a reminder via JSON input string + +``` +json_string='{ + "title": "Buy groceries", + "notes": "Milk, eggs, bread, cheese", + "dueDate": "2025-02-15T10:24:00Z", + "priority": "medium", + "listName": "Groceries", + "recurring": "weekly", +}' + +$ reminders add-json $json_string +``` + #### See help for more examples ``` diff --git a/Sources/RemindersLibrary/CLI.swift b/Sources/RemindersLibrary/CLI.swift index 329b88e..d00ba09 100644 --- a/Sources/RemindersLibrary/CLI.swift +++ b/Sources/RemindersLibrary/CLI.swift @@ -157,6 +157,27 @@ private struct Add: ParsableCommand { } } +private struct AddJSON: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Add a reminder as JSON") + + @Argument( + parsing: .remaining, + help: "The JSON string representing the reminder to add") + var JSONstring: [String] + + @Option( + name: .shortAndLong, + help: "format, either of 'plain' or 'json'") + var format: OutputFormat = .plain + + func run() { + reminders.addReminderJSON( + string: self.JSONstring.joined(separator: " "), + outputFormat: format) + } +} + private struct Complete: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Complete a reminder") @@ -282,6 +303,7 @@ public struct CLI: ParsableCommand { abstract: "Interact with macOS Reminders from the command line", subcommands: [ Add.self, + AddJSON.self, Complete.self, Uncomplete.self, Delete.self, diff --git a/Sources/RemindersLibrary/EKReminder+Encodable.swift b/Sources/RemindersLibrary/EKReminder+Encodable.swift index 3821b0c..f9f9fdb 100644 --- a/Sources/RemindersLibrary/EKReminder+Encodable.swift +++ b/Sources/RemindersLibrary/EKReminder+Encodable.swift @@ -1,6 +1,6 @@ import EventKit -extension EKReminder: Encodable { +extension EKReminder: @retroactive Encodable { private enum EncodingKeys: String, CodingKey { case externalId case lastModified diff --git a/Sources/RemindersLibrary/NaturalLanguage.swift b/Sources/RemindersLibrary/NaturalLanguage.swift index e38ab3c..b9f9baf 100644 --- a/Sources/RemindersLibrary/NaturalLanguage.swift +++ b/Sources/RemindersLibrary/NaturalLanguage.swift @@ -44,7 +44,7 @@ private func components(from string: String) -> DateComponents? { } } -extension DateComponents: ExpressibleByArgument { +extension DateComponents: @retroactive ExpressibleByArgument { public init?(argument: String) { if let components = components(from: argument) { self = components diff --git a/Sources/RemindersLibrary/Reminders.swift b/Sources/RemindersLibrary/Reminders.swift index cab7d77..68912ef 100644 --- a/Sources/RemindersLibrary/Reminders.swift +++ b/Sources/RemindersLibrary/Reminders.swift @@ -90,7 +90,7 @@ public final class Reminders { } func showLists(outputFormat: OutputFormat) { - switch (outputFormat) { + switch outputFormat { case .json: print(encodeToJson(data: self.getListNames())) default: @@ -100,8 +100,10 @@ public final class Reminders { } } - func showAllReminders(dueOn dueDate: DateComponents?, - displayOptions: DisplayOptions, outputFormat: OutputFormat) { + func showAllReminders( + dueOn dueDate: DateComponents?, + displayOptions: DisplayOptions, outputFormat: OutputFormat + ) { let semaphore = DispatchSemaphore(value: 0) let calendar = Calendar.current @@ -334,6 +336,75 @@ public final class Reminders { } } + func addReminderJSON(string: String, outputFormat: OutputFormat) + { + // Let's decode the JSON string into a dictionary + guard let jsonData = string.data(using: .utf8) else { + print("Failed to convert JSON string to data") + exit(1) + } + + guard let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else { + print("Failed to parse data as JSON") + exit(1) + } + + let title = json["title"] as? String ?? "" + let name = json["listName"] as? String ?? "" + let notes = json["notes"] as? String ?? "" + + let prior = json["priority"] as? String ?? "none" + let priority = Priority(rawValue: prior) ?? .none + + let recur = json["recurring"] as? String ?? "none" + + let ddate = json["dueDate"] as? String ?? "" + let dueDateComponents = DateComponents(argument: ddate) + + // Let's create a new reminder + let calendar = self.calendar(withName: name) + let reminder = EKReminder(eventStore: Store) + reminder.calendar = calendar + reminder.title = title + reminder.notes = notes + reminder.priority = Int(priority.value.rawValue) + + switch (recur) { // see also, e.g. EKRecurrenceRule(recurrenceWith: .weekly, interval: 1, daysOfTheWeek: [.init(.monday),.init(.tuesday)], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: .none) + case "daily": + reminder.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .daily, interval: 1, end: .none)) + reminder.addAlarm(EKAlarm(relativeOffset: .zero)) + case "weekly": + reminder.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .weekly, interval: 1, end: .none)) + reminder.addAlarm(EKAlarm(relativeOffset: .zero)) + case "monthly": + reminder.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .monthly, interval: 1, end: .none)) + reminder.addAlarm(EKAlarm(relativeOffset: .zero)) + case "yearly": + reminder.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .yearly, interval: 1, end: .none)) + reminder.addAlarm(EKAlarm(relativeOffset: .zero)) + default: + break + } + + reminder.dueDateComponents = dueDateComponents + if let dueDate = dueDateComponents?.date, dueDateComponents?.hour != nil { + reminder.addAlarm(EKAlarm(absoluteDate: dueDate)) + } + + do { + try Store.save(reminder, commit: true) + switch (outputFormat) { + case .json: + print(encodeToJson(data: reminder)) + default: + print("Added '\(reminder.title!)' to '\(calendar.title)'") + } + } catch let error { + print("Failed to save reminder with error: \(error)") + exit(1) + } + } + // MARK: - Private functions private func reminders(