Building a Command Line Interface for the MinuteDock API with Ruby

Intro

In this guide we'll go through the steps to create a really simple Command Line Interface (CLI) to your MinuteDock acccount, via the MinuteDock API.

By the end, you'll be able to run:

$ ruby md.rb status

Inside Terminal.app and get a summary of your currently active time entry.

An example of the MinuteDock CLI we'll build

This is a useful start if you want to quickly log time without leaving your Terminal, or if you want to make it quick and easy for you or your team to execute a number of complex operations.

What you'll need

  • Basic to Intermediate knowledge of the Ruby Programming Language
  • Ruby Version 2.0 or greater installed (ruby -v will show you your installed version)
  • A working Ruby Gems installation
  • A code editor, such as Sublime Text
  • Around 25 free minutes!

Step 1: Installing Thor and HTTParty

The Thor gem, aside from having a cool name, is one of the best ways to create a simple CLI in Ruby. We'll can install it using Ruby Gems buy running the following command in Terminal.app:

$ gem install thor

You should see a message like "1 gem installed". Congrats, you're ready to use Thor!

We also need the HTTParty gem - it's a really easy way to make HTTP requests:

$ gem install httparty

You should get a similar "gem installed" message.

We're ready to create the skeleton of our CLI. Save this as md.rb.

require "thor"
require "httparty"

class MinuteDockCLI < Thor
end

MinuteDockCLI.start(ARGV)

You can test this out by running:

$ ruby md.rb

You should get some automatic output from Thor listing the available commands. For the moment this will just be the help command that it creates for us automatically.

Step 2: API Keys

In order to access our MinuteDock account, we'll need to provide it with our MinuteDock API Key. You can find your API Key on your MinuteDock Profile Page.

The location of your API Key inside MinuteDock

In order to make your key available to Thor, we're going to set it in a constant at the top of our md.rb file:

require "thor"
require "httparty"

API_KEY = "YOUR_API_KEY"

class MinuteDockCLI < Thor
end

MinuteDockCLI.start(ARGV)

Note: If you'd like to share your file with other people later, be sure to remove your key. You can also modify your program to read the key from a .minutedock file in the user's home directory, rather than hard-coding it in the file.

Step 3: Writing a Status Command

Alright, now we're ready to write our first CLI command. Each method inside the MinuteDockCLI class will become a Thor command. So we just need to add a status method, which we can describe using Thor's describe:

class MinuteDockCLI < Thor
  include HTTParty
  base_uri "https://minutedock.com/api/v1/"

  desc "status", "Returns the current entry status from MinuteDock"
  def status
    data = self.class.get("/entries/current.json", query: { api_key: API_KEY })
    puts data.parsed_response
  end
end

MinuteDockCLI.start(ARGV)

We've also configured HTTParty here, and told it that the MinuteDock API is at https://minutedock.com/api/v1. HTTParty provides the "get" class method, which will perform an HTTP GET reqest to the base URI plus the first parameter (in this case, the full URL will be https://minutedock.com/api/v1/current.json. We also tell HTTParty to include our API Key as a query parameter so MinuteDock can authenticate us.

You can now run this with:

$ ruby md.rb status

It should print out some JSON representing your current entry.

If you get an "Access Denied" message here, double check your API Key.

That's nice, but it's not incredibly user-friendly. In an ideal world we'd get something like the following:

Your timer is currently active for @some_client

Step 4: Nice Output

The first thing we'll need to do to get nicely formatted output is make another request to the Contact Resource and retrieve the name of the contact for our current entry.

To do that, change your status method to look like this:

  def status
    response = self.class.get("/entries/current.json", query: { api_key: API_KEY })
    entry    = response.parsed_response

    if entry["contact_id"]
      response = self.class.get("/contacts/#{entry["contact_id"]}.json", query: { api_key: API_KEY })
      contact  = response.parsed_response
    end

    puts %(Your timer is currently #{(entry["timer_active"]) ? "active" : "paused"}, for #{(contact) ? "@" + contact["short_code"] : "no contact"})
  end

First we're getting the entry data out from the HTTParty response (because MinuteDock sends the response with an application/json Content Type header, HTTParty knows to parse the JSON into a Ruby Hash for us).

Then we're checking if the current entry has a contact ID, and if it does, we're making another HTTP GET request to get the details for that contact.

Finally, we output our message!

You should now be able to run:

$ ruby md.rb status

And get a nice summary of your current timer status.

Step 5: Writing a Log Command

Sometimes it would be nice to quickly log our current MinuteDock entry. With our Thor CLI, that's really easy to do. Just add another method to the MinuteDockCLI class that performs an HTTP POST request (using HTTParty) to the Log Entry Action.

  desc "log", "Logs the current entry"
  def log
    response = self.class.post("/entries/current/log.json", query: { api_key: API_KEY })
    if response.success? && response.parsed_response["logged"] == true
      puts "Okay, we've logged that!"
    end
  end

This method is very similar to status, except this time we're checking the HTTP Response code of the POST request using .success? to ensure we've successfully logged an entry.

If you run this using:

$ ruby md.rb log

You'll notice your current entry has been logged.

We're done!

We've built a basic CLI for your MinuteDock account, and you're now set up to extend it if you want - just add more methods to the MinuteDockCLI class.

Congratulations!

Extra for Experts

If you'd like, you can extend this slightly so that you can run:

$ ./md status

First, you need to add a "shebang" line to the top of your md.rb file that tells your shell where to find Ruby. Usually this line is something like:

#!/usr/bin/env ruby

But you might need to change it, depending on the location of your Ruby executable (check which ruby).

Then you can rename md.rb and mark it executable:

$ mv md.rb mv
$ chmod +x md.rb

Now your commands can be really snappy:

$ ./md status
Your timer is currently paused for no contact.