I want a command-line tool to access Reminders.app data. I was inspired by the command-line password manager, pass. Based on the man page,
pass uses the following parameters:
This seems like a good model to follow. I’m going to name this command
rem, short for reminders. It also reminds me of my old BASIC days, where
REM started a comment line.
Here’s the functionality I want for the first iteration:
rem [ls] [calendar]list reminders
rem rm <calendar> <reminder_id>remove a reminder
rem cat <calendar> <reminder_id>show a reminder details
rem done <calendar> <reminder_id>mark a reminder as completed
I’m not including functionality to add or modify a reminder (yet). I just don’t need this functionality right now, as I usually add reminders via my iPhone.
Start with the OSX Command Line Tool Application template, with default options of using Foundation and ARC. Add
EventKit.framework to the
rem target and away we go.
Parsing the Command-line Arguments
I need to process the application arguments to know what I want to do. Before doing that, some set up. I
#define an array of command strings, and an enumerated type that maps to each element of the command string array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
The three global variables hold the parsed values from the command arguments. Some consider it bad form to use a global like this, but it’s just easier to keep it global rather than passing it around.
parseArguments, handles the argument parsing and is called from
main. I won’t go over the entire implementation, but want to make note of the of a few things.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
I use NSProcessInfo to get our command-line arguments. The first element will be the application, so I pop that off.
You can see where how the
COMMAND string array and
CommandType enumeration have to lineup. I can search the
COMMAND array for the matching string, and the index with be the CommandType enumeration value. Better than making a giant
switch statement. If the command argument isn’t found the command string array, then I print an error message and spit out the usage.
The rest of the function reads the reminder list (calendar) name and reminder id from the
Loading and Sorting Reminders
Two functions to handle reminders.
fetchReminders will load the reminders from the event store.
sortReminders will organize the fetched reminders into a dictionary keyed on reminder list (calendar) name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
EKEventStore instance is a global that’s instantiated at application start. I use it create a predicate to fetch all the reminders across all calendars. Recall that in Part 1, I noted that the event store can be expensive to create and release. For a GUI (Cocoa or iOS), I’d create the event store in the application delegate and have it persist for the lifetime of the app.
I have some funky coding going on here. What’s up with the
while loop after the call to
fetchRemindersMatchingPredicate:completion:? I have a boolean flag,
fetching, that gets flipped inside the completion handler block. Until it gets flipped, the application’s runloop will run for 1/100th of a second. During this 1/100th of second, the completion handler block may get invoked. If so, the
fetching flag will be flipped, and the app will leave the
fetchRemindersMatchingPredicate:completion: is an asynchronous call. It gets sent to a another thread to run in the background. The problem is that the completion handler block gets invoked back on the main thread which can cause problems. Ideally, I would try to use Grand Central Dispatch like this:
1 2 3 4 5 6
Since the completion handler block gets invoked on the main thread, the semaphone blocks, and the signal is never sent. So looping over the run loop seems like the best bet.
Note: This is also the reason I’m loading all the reminders, then sorting them. If I had access to a synchronous reminder fetch, my first instinct would be to loop over each reminder list. However, loading all at once, then sorting is probably faster.
Now that I have an array of reminders, I sort them into a dictionary, keyed on calendar names. I ignore the completed reminders.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Handling the Command
I’ve got my command-line arguments, and I’ve got my (sorted) reminders. Now we need to do something.
handleCommand is a function around a switch statement for command-specific functions. We’ll just stub out those functions right now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
At this point, I can build
rem and run it. Each command I defined goes to the function that should handle it. But we’re not printing anything out yet. I’ll get to that in Part 3.