Building CLI Tools
Write tools! Why?
This year, I’m trying to spend a little more time building tools. That’s what sets humans apart, right? Why do we keep typing the same set of things into a computer to manually do something, then forgetting how to do it. Or moving to another computer and having to re-google to try and find that one forum post that describes how to fix your particular linux distro, network manager, and hardware combination. Ugh.
So, instead of that I am diligently (hah) recording all the stupid things I have to do, and consolidating the ones I repeat into small tools that I execute from the command line, where I spend most of my time. Some of these tools are not intended for widespread usage, they fix the problems I have, like having to repeat the same information in a standard git workflow: branch name, commit name, push, pr name. Making these small tools really does free up some brainpower - I want to spend my time concentrating on solving new problems, not on how I solved the previous one.
We should all try and write tools, and not be too caught up in making things open source or accessible to others as a first step. Solve the problems you have, not the problems you think others have.
Given this desire to automate more this year, and that I work on a team where we build tools for the rest of our developer team to use, I thought I’d share some quick thoughts on how to get started, conventions I like, and examples of good and bad tooling.
This is as much a reference guide for me as anything else - I’ll try and update as I go.
Really doesn’t matter about language too much. Almost all of them have appropriate tooling around creating CLI applications. The main thing is to just pick something and do it. However, there are a few things I think about when picking a language:
- Pick something that you know, that you can JFDI and finish the project.
- Pick something that’s easy to install for your common users. E.g. if you’re writing a tool for a team that uses Ruby already, that’s a fine choice.
- Pick something that others you work with can contribute to.
- Pick something that you can push updates to easily and distribute. Do you want people to have to check out the source code and
bundleit (and have all the system dependencies set up already), or is a binary that just works better?
- If it’s just for personal use, it’s a good chance to learn something new.
Pretty much the only time it really matters is if you need concurrency, in that case don’t pick something where that sucks.
A quick library to set up command line flags and arguments is really useful. Sure, you can roll your own and do some undifferentiated heavy lifting, or you can just pick up a small library and get on with it.
Some good ones I personally like:
Go: kingpin (simpler), or cobra (more complete)
Ruby: thor, I also like highline to do i/o in Ruby.
JS: commander There’s a bunch of others that provide in-depth terminal manipulation, but for the most part they are not required.
Every tool should provide a
--help flag that prints usage instructions. This should also be printed when the tool is invoked incorrectly (like missing a required argument). The above libraries handle this for you.
$toolname --version vs
At least provide the first of these (passing version as a flag), if not both. It’s annoying typing the wrong one all the time. Don’t get me started on Java’s single-hyphen
Provide a flag for verbose mode. Keep useless information about ongoing work only printing to terminal if that flag is used.
Don’t provide useless information
Corollary to the verbose flag, keep operation quiet, don’t print useless information. Also keep in mind when printing results to discard useless data. If I ask a tool to tell me what subsystems are running, it shouldn’t reply with a list of subsystem names along with their “running” status next to them. I know all the ones in the list are the running ones, that is what I asked! Having that extra piece of information is just something I have to strip out when piping results.
Follow standard unix conventions
Print data to stdout, print errors to stderr. Output proper error codes for success/failure. If your usual output is formatted for human readability (multi-column, for example), then make sure that you detect when your output is being piped somewhere and change that so the standard unix pipeline approaches work well.
ls does this already - check out the output of a plain
ls | cat.
$toolname upgrade vs
Homebrew is an example of an application that provides both an upgrade and an update command. One of these upgrades brew, one updates something installed via brew. I don’t know which is which off the top of my head. Provide just one! Make it self-update with no arguments, or find another name.