Exploring the basic anatomy of a CLI

Our friend C01t is riding the elevator to the 15th floor for a meeting with Naomi, one of the system admins in charge of Guava Orchard’s IT infrastructure. After settling in as a member of project Feijoa, C01t has been tasked with building a command line app. Admins will use the tool to configure, maintain and troubleshoot the Feijoa service. He has never built a CLI before. Furthermore, no one on the team has a clear idea of what the requirements for the tool are. Therefore, Kaya suggested he meet with Naomi to get her input on the requirements.

“Why don’t I show you the Tamarillo command line app”, Naomi proposes once they are settled in the conference room. “Sam and I think its easily one of the best CLIs we use”, she continues nodding at the system admin sitting across from her. “I run Linux. Sam runs windows. Yet both of us can use the Tamarillo CLI from our desktops.” Then Naomi demos the intuitive command structure and contextual help feature of the app.

Next Sam opens one of the bash scripts they wrote to provision new users. To his surprise, C01t notices that the script invokes the same app that Naomi just demoed. “Scripting this tool was easy and straight forward. That capability alone simplifies our jobs a ton”, explains Sam.

“How long have you been using this app?”, asks C01t. “When did we deploy Tamarillo? Maybe 15 months ago”, replies Naomi. “That sounds about right”, confirms Sam. “And you know what has been truly amazing? In these 15 months they have released 22 updates to the command line app. That’s roughly a new version every 3 weeks. And not once did they break or regress existing functionality”, Naomi explains. “That is amazing”, agrees C01t. “They must have a huge army of testers.” Naomi gives him a knowing smile. “As a matter of fact 2 developers spend 20% of their time developing the app. That’s it. There is not army of testers. Automated tests validate all functionality”, she explains still smiling.

The anatomy of a CLI

Naomi and Sam introduced C10t to a well written CLI. The features they highlighted provide an excellent road-map for teasing out the characteristics of a good command line app.

Deliver portability

Our primary goal when developing a CLI is to make it available on all platforms that our customers use. Furthermore, we aim to make installation of the CLI as simple as possible. Therefore, go is our preferred language for the task.

With go you can write the code once and run it everywhere. Additionally, all go programs compile into binary executables. Since version 1.5 go has built-in support for cross-compiling. Check out The Go Cookbook for instructions on how to cross compile your code. In addition, you can find the list of supported compile targets in the official docs.

spf13/cobra and urfave/cli are 2 widely used libraries for building command line apps in the vein of the git command. Both provide a declarative way to define the command and sub-commands. Furthermore, both will handle the parsing and extraction of flags and command arguments. And both implement a contextual help system that enable you to build self documenting apps. Overall the libs are very similar. So you can confidently choose either one.

Design for usability

We aim to make the command line app easy to use. Therefore, we need to be mindful of how commands are organized and how complex the user input is.

Command structure

At the core of every CLI app there are 2 concepts: actions and entities those actions apply to. By organizing our command structure around these 2 concepts, we can build an intuitive self documenting application.

Consequently we can chose from 2 different approaches. The first approach is to build an action centric command structure. In this model the primary commands represent actions and the secondary commands are the entities. To illustrate this approach consider what command to start a service for the Tamarillo app would look like:


> tamarillo start service [args ...]

Another option is to build an entity centric command structure. In this case the primary commands all represent entities and the secondary commands are the actions. Now the same command to start a service would look like:


> tamarillo service start [args ...]

So how do we decide which structure to apply? First we consider the symmetry of the actions. Do all or a plurality of the actions apply to a plurality of the entities? Next we consider the cardinality of the 2 concepts. Does our domain have a large number of actions but only a handful of entities? Or vice versa? If the actions are symmetric we make the primary command the actions regardless of cardinality. Otherwise we use the concept with the lower cardinality as the primary command.

The key is consistency. A consistent structure makes it easier for a new customer to explore the commands. Therefore, we must apply the structure we choose consistently to all commands.

Input

To improve ease of use we avoid requiring the user to provide UUIDs as parameters to commands. Because UUIDs are the preferred entity identifiers in most distributed systems, satisfying this principle is more complicated than it seems.

We recommend using config files to load and save the UUID(s) of the “active” entity(s). If the app is similar to git, where commands are executed in the context of a working folder, then we use a config file per working folder. However, if the commands don’t have a folder execution context we can store any config files in the user’s home folder. To save the UUIDs we can implement explicit app commands for setting the active entity.


> tamarillo service set -id a0744c52-36fe-11e8-b467-0ed5f89f718b

However, our preferred way is to implicitly set the “active” entity to the last entity that was explicitly specified. Then all subsequent commands can omit specifying the UUID explicitly.


# Start the service
> tamarillo service start -id a0744c52-36fe-11e8-b467-0ed5f89f718b

# Query for the status of the serice
> tamarillo service status

# Restart the service
> tamarillo service restart

Another option is to accept entity names instead of unique identifiers. However, for this to work the service has to support entity names.

Output

There is no output harder to read than rows of data where the columns are misaligned. So we align tabular output produced by the app. We use the tablewriter library to format tabular output.

Another priciple is to make information stand out by leveraging colorful text. However, we must be judicious with the use of color. Too much color can actually impair the readability of your output. In a number of our applications we have used the color library to generate colorized output.

Build for versatility

Probably the most consequential omission during the initial design of a CLI is scriptability. Following a few simple patterns we can implement a command line app that is capable of producing JSON output in addition to the human readable one. And as a result one can now easily parse the output using jq in any shell script.

Here are some simple rules of thumb to follow:

  • Output a single JSON object. Even displaying multiple JSON objects with clear delimiters, e.g. “\n”, makes the output considerably harder to parse.
  • Suppress all other output. If we do not any additional output will interfere with parsing of the JSON output.
  • Display errors as valid JSON objects when the command fails.

Structure for testability

Finally, you want to write suite of automated tests to guard against regressions. To accomplish this goal we write both integration tests and unit tests.

We write the integration tests to exercise the app against real services/environments. Here we leverage all the work we did to make the CLI scriptable. Because we can emit JSON output from all commands, we can build robust test cases that validate app behavior. Furthermore, we are able to reliably implement complex test scenarios that requires us to pass parameters between commands.

On the other hand, we use unit-tests to ensure all execution paths are covered, especially failure paths. However, to write unit tests we have to carefully structure the app. First of all, we use client abstractions to interact with external services. During unit-test execution we replace the clients with mocks. Then we make the command action function easy to unit-test by following some simple rules such as: do not access global variables; return detailed results that can be asserted on; propagate errors through return values – never use os.Exit or panic; and pass in an io.Writer to receive all output.

218 Replies to “Exploring the basic anatomy of a CLI”

  1. I oversee a vape store directory site and we have had a listing from a vape store in the USA that likewise offers CBD items. A Calendar month later on, PayPal has contacted use to claim that our account has been limited and have asked us to get rid of PayPal as a payment method from our vape store website directory. We do not offer CBD products such as CBD oil. We merely offer marketing professional services to CBD firms. I have taken a look at Holland & Barrett– the UK’s Top Health Store and if you take a close look, you will witness that they promote a comparatively comprehensive series of CBD products, particularly CBD oil and they also happen to take PayPal as a settlement method. It appears that PayPal is administering double standards to different firms. As a result of this constraint, I can no longer accept PayPal on my CBD-related internet site. This has limited my payment choices and right now, I am intensely contingent on Cryptocurrency payments and straightforward bank transfers. I have checked with a solicitor from a Magic Circle law practice in The city of london and they explained that what PayPal is undertaking is entirely against the law and discriminatory as it ought to be applying a consistent criterion to all companies. I am yet to get in touch with one more legal representative from a US law firm in The city of london to see what PayPal’s legal position is in the United States. Meanwhile, I would be very appreciative if anyone here at targetdomain could provide me with different payment processors/merchants that deal with CBD companies.

  2. With havin so much content and articles do you ever run into any problems of plagorism or
    copyright infringement? My site has a lot of completely unique content I’ve either created myself or outsourced but it appears
    a lot of it is popping it up all over the web without my permission. Do you
    know any techniques to help protect against content from
    being stolen? I’d truly appreciate it.

  3. I know this if off topic but I’m looking into starting my own weblog and was curious what all is required to get setup?
    I’m assuming having a blog like yours would cost a pretty penny?

    I’m not very internet savvy so I’m not 100% sure. Any suggestions or
    advice would be greatly appreciated. Cheers

    my homepage … drug crime

  4. Hi there! This post couldn’t be written any better!
    Reading this post reminds me of my old room mate!
    He always kept talking about this. I will forward
    this article to him. Pretty sure he will have a good read.
    Thanks for sharing!

    Feel free to surf to my webpage low carb diet

  5. I was just looking for this information for a while. After 6 hours of continuous Googleing, at last I got it
    in your web site. I wonder what’s the lack of Google strategy that do not rank this kind of
    informative websites in top of the list. Normally
    the top sites are full of garbage.

    My web page; forum.vkmoravia.cz

  6. Hello terrific blog! Does running a blog such
    as this require a large amount of work? I’ve virtually no expertise in computer programming however I had been hoping to start my own blog soon. Anyways,
    if you have any suggestions or techniques tips for weight loss new blog owners please
    share. I understand this is off subject but
    I just wanted to ask. Cheers!

Comments are closed.