Alfred to Day One script (in Swift)

The problem

I like to tweak my workflow. It's always evolving because my context changes over time, so I'm always changing the way I do things and the tools I use. Because of that I'm always on the lookout on how to improve the way I do things.

A big part of any workflow is, of course, keeping tabs on what I have to do. It's always satisfying to check that to-do off the list! I've been noticing recently that, in addition to completing things on my list, I also find things that need to be done that I do on the spot.

When I complete things, I like to have a record of it. This is done by default when you check something off in your to-do list, but not when you did something that wasn't in your system.

Now the idea of finding something that needs to be done and doing it, and then having to put in my to-do app so that I can check it off immediately, just to keep track of what I accomplished simply doesn't work for me. It has to be simpler than that!

The solution

So I created a script and an Alfred workflow to help me do that seamlessly. As I'm using Swift, I wanted to write the script in that language instead of a bash or a Ruby script. The question, then, was where should I log these completed tasks?

I decided to use Day One and a specific journal within it, called simply log, to keep track of those things I do that aren't in one of my various other 'buckets.'

Day One has a command line interface that you can install. With that you can send a bash command that will create an entry in the journal you specify, with optional tags added.

So now, when I want to log something I just did (before I forget it), I simply invoke Alfred (⌘-space) and type do return to call my dolog workflow, then type the task and then return and a new entry is created in Day One, in the log journal, which is formatted completed: <task> with the tags dolog and completed task added.

If I want to add specific tags to that entry, I can use the syntax:

-t tag1 tag_2 @ task

The first characters must be -t then any number of space separated tags (with spaces within tags replaced with '_' which are converted back to spaces by the script), then the 'end of tags marker' @ then the task completed.

The Swift script is actually quite simple. It simply parses the query passed in by Alfred, check for the -t tags option and process tags if present, otherwise it formats a Day One CLI command and passes it to a terminal session.

How it works

Here is the script (as it stands as of this writing, check the github repo for the latest version).

#!/usr/bin/env xcrun swift

import Foundation

/* *********************************
            DOLOG Script
   ********************************* */

/* *********************************
     MODIFY THESE 3 PROPERTIES
             AS NEEDED
********************************* */

// the journal to log to in Day One

let dayOneJournal = "log"

// the default tag(s) to add to all entries. If you don't
// add at least one default tag, you'll have to modify the code below.
// tags *can* have spaces

let defaultTags = ["dolog", "completed tasks"]

// the entry prefix

let entryPrefix = "completed task:"

/* ********************************* */

// requires Swift 3.0
// might work with Swift 2.0 but is untested
// Will not work with Swift 1.0

//-- get parameter input
// `argument` holds the text entered in Alfred by the user
// I initialize it with an example of something the user could enter
// for testing. 

var argument = "-t workflow@Switched to Mailmate as my email client"
#if swift(>=3.0)
    if CommandLine.arguments.count > 1 {
        argument = CommandLine.arguments[1]
    }
#elseif swift(>=2.0)
    if Process.arguments.count > 1 {
        argument = Process.arguments[1]
    }
#elseif swift(>=1.0)
    print("Unsupported version of Swift (<= 2.0) please update to Swift 3.0")
    break
#endif

// MARK: - Properties

// variable 'task' will hold the completed task passed in

var task  = ""

// `outputString` is the result of the script that will be passed to the CLI, 
// we initialize it with the Day One CLI command, setting the default journal
// and the default tags.

var outputString: String = "dayone2 --journal "

// add journal name and default tags

outputString += dayOneJournal + " --tags "

for defaulTag in defaultTags {
    let tag = defaulTag.replacingOccurrences(of: " ", with: "\\ ")
    outputString += tag + " "
}

// MARK: - Process input

//-- Test if tags are present

// weHaveTags is true if there are tags present

let weHaveTags = argument.hasPrefix("-t")

//-- Process tags if present, otherwise just pass the input

if weHaveTags {

    // find the index of the tags separator

    if let endOfTags = argument.characters.index(of: "@") {

        // Map the tags into an array. The first tag (index 0) will be the tag option marker (-t) and will be
        // omitted

        let tags = String(argument.characters.prefix(upTo: endOfTags)).characters.split(separator: " ").map{ String($0) }

        // Now process the task part to remove the end of tags marker

        // get the task part of the input

        let taskSection = String(argument.characters.suffix(from: endOfTags))

        // find the index of the tags separator in this string (different than above)

        let endTagIndex = taskSection.characters.index(of: "@")!

        // The task proper starts after the tags separator

        let tagIndex = taskSection.characters.index(after: endTagIndex)

        // get the task

        task = String(taskSection.characters.suffix(from: tagIndex))

        // Now we have the task, we then process and format the tags
        // Add the tags to the output string separated by spaces
        // skipping the first one which is the `-t` marker

        for i in 1..<tags.count {

            // first we process underscores (_) in tags to replace them with escaped spaces so they're
            // treated as a single tag

            let tag = tags[i].replacingOccurrences(of: "_", with: "\\ ")

            // add this processed tag to the output string

            outputString += tag + " "
        }
    } else {

        // user forgot the '@' separator so just pass the input string (task) as received

        task = argument

    }

} else {

    // no tags, so just pass the input string (task) as received

    task = argument
}

// Add the task to the output string (enclosed in quotes to prevent the CLI to interpret special characters)

outputString += " -- new" + " \"" + entryPrefix + " " + task + "\""

// pass the result of the script, we suppress the newline character in the output

print(outputString, terminator:"")

You can find the complete script as well as the Alfred workflow (and a custom icon) on my github page.

For this to work, you need to have both Alfred and Day One, obviously. You also need a PowerPack license for Alfred, which enables the Workflow feature (Alfred is free for the basics but requires a license for the power features).

For a complete explanation on how to install this please consult the script's readme.

If you modify it, be sure to compile the script. I use CodeRunner to write scripts. It's an excellent editor which supports many languages, it has auto-completion and a full debugger for Swift (as well as Ruby and many others). When you run a script, it compiles it so you get the executable without having to use the terminal to compile it yourself.

If you find issues, be sure to add them on GitHub, and pull requests are welcome.

blog comments powered by Disqus