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:
1
|
|
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.
The function, 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 if
or 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 args
array.
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.
Here’s fetchReminders
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
The 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 while
loop.
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.