Writing Awesome CLI Tools in Ruby: Part II

by

This is Part II of my series on writing awesome CLI (command line interface) tools using ruby (Part I). In the first part I described how to create your project layout, add an executable binary, and get started. In this next part I will cover:

  • How to structure your code to be usable as both a tool and a library
  • Building your CLI frontend to your library.

The Library

Your library lives within lib/. Break up the library into modules and classes. In this example we have a single module, wunder.

Classes

All of your work should be done from classes within your gem. These classes will be configured and executed by the CLI, but can also be reused within other scripts and programs by requiring them.

Be sure to design your classes to be configurable, and to return values rather than simply printing the output. It also helps if any IO is configurable with defaults to stdin/stdout.

You notice that the Wunder::Wunderground class takes a configuration object, provided by the CLI class (or potentially any other object that wants to take advantage of its features).

The command line interface

Within the library, I have a class called “cli” (lib/wunder/cli.rb), which is my command line interface. The command line interface uses the fantastic Thor gem. An example of a thor interface might look like:

[code language=”ruby”]
class CLI < Thor
desc ‘echo ARG’, ‘Echo. Takes an arg, prints it.’
def echo(arg)
puts arg
end
end
[/code]

As you can see right away: it is as simple as extending Thor and defining public methods. The methods are executable as sub-commands when this script is run. Arguments are simply method arguments.

Options are a bit different though…

[code language=”ruby”]
class CLI < Thor
desc ‘take_option’, ‘Option taking class’
option :opt, required: false, default: nil, aliases: [‘o’], desc: ‘An option.’
def take_option
puts "My option: #{options[:opt]}"
end
end
[/code]

The above method can be passed an option (‘opt’) either through --opt=value or -o value.

The CLI portion gets invoked from our ‘binary’ in bin/wunder, which calls Wunder::CLI.start(ARGV) to kick it off.

The methods within the CLI class should configure objects, call methods, and format/print the results.

Conclusion

This post covered how to structure and build your library and commandline interface. Hopefully this will help you create useful and effective tools!

Links/references

Leave a Reply

Your email address will not be published. Required fields are marked