Introducing hanami-cli: a general purpose Command Line Interface (CLI) for Ruby. Learn why Hanami replaced thor in favor of hanami-cli and how to use it to build a CLI application in 5 minutes.

Why not thor?#

For long time we used thor 🔨 to build the Command Line Interface (CLI) of Hanami. But as the time passed, we needed more control on the internals of our implementation.

The Hanami 🌸 command line needs two crucial features: subcommands and extendibility.

Subcommands#

A subcommand is a nested command under the main executable. For instance hanami is the executable and generate action is the subcommand.

thor doesn’t play well with subcommands, which lead to a lot of hacks to make it to work 🙀🙀🙀. We reached a point of code fragility, and immobility. We were afraid to touch it and that made impossible to introduce new features.

Extendibility#

On the other side, to grow an ecosystem around Hanami, we needed third-party gems to extend the basic set of subcommands that we provide out of the box.

Imagine a developer who wants to integrate Webpack with Hanami, they can build a gem to make it happen. This gem may need to generate some configuration, so it can register a subcommand to do so: hanami generate webpack.

This feature was impossible with thor because it wasn’t designed for it. 😭

hanami-cli#

At this point we decided to roll-out our own solution to solve these problems 💪. Good news are: hanami-cli is a general purpose toolkit to build CLIs 🙌.

Following our tradition, we build components that can be used outside of Hanami, and this is the case for hanami-cli too!

To recap: hanami-cli is a thor alternative to build your own CLI outside of Hanami

A quick example#

Let’s build a small CLI utility to convert money from one currency to another.

As first thing we generate a new gem via Bundler:

$ gem install bundler
$ bundler gem curex

Then we create the executable for our gem:

$ mkdir exe
$ vim exe/curex

And now we can edit it:

#!/usr/bin/env ruby

require "bundler/setup"
require "curex"
Curex::CLI.new.call

Don’t forget to give it the right permissions:

$ chmod +x exe/curex

Now we need to setup the curex.gemspec file. The fastest way is:

$ curl https://raw.githubusercontent.com/jodosha/curex/master/curex.gemspec > curex.gemspec

At this point we can try to run the executable:

$ ./exe/curex
./exe/curex:5:in `<main>': uninitialized constant Curex::CLI (NameError)

This returns an error because, we haven’t implemented our code yet. Let’s do it now in lib/curex.rb.

module Curex
  require "curex/version"

  class CLI
    def call(*args)
    end
  end
end

By running the code now, it doesn’t raise an exception anymore:

$ ./exe/curex

Our CLI isn’t really useful at the moment. Let’s build our first command:

require "hanami/cli"

module Curex
  require "curex/version"

  class CLI
    def call(*args)
      Hanami::CLI.new(Commands).call(*args)
    end

    module Commands
      extend Hanami::CLI::Registry

      class Convert < Hanami::CLI::Command
        def call(*)
          puts "converting.."
        end
      end

      register "convert", Convert
    end
  end
end

We can try it again:

$ ./exe/curex
Commands:
  curex convert

This output indicates the available (sub)commands, in our case we have only convert:

$ ./exe/curex convert
converting..

Yay! We have our working CLI, now it needs some logic.

require "hanami/cli"
require "bigdecimal"
require "money"
require "money/bank/google_currency"

Money.use_i18n = false
Money.default_bank = Money::Bank::GoogleCurrency.new

module Curex
  require "curex/version"

  class CLI
    def call(*args)
      Hanami::CLI.new(Commands).call(*args)
    end

    module Commands
      extend Hanami::CLI::Registry

      class Convert < Hanami::CLI::Command
        argument :amount, required: true
        argument :from,   required: true
        argument :to,     required: true

        def call(amount:, from:, to:)
          money = Money.new(amount.to_d * 100, from)
          result = money.exchange_to(to)

          puts "#{amount} #{from} == #{result} #{to}"
        end
      end

      register "convert", Convert
    end
  end
end

Let’s try it again:

$ ./exe/curex convert
ERROR: "curex convert" was called with no arguments
Usage: "curex convert AMOUNT FROM TO"

It now tells us which args are required to run the subcommand:

$ ./exe/curex convert 100 USD EUR
100 USD == 84,76 EUR

It works! 🎉

If you want to expand the command with more hints for your users, please check the curex repository or the hanami-cli docs.

Conclusion#

In 5 minutes we were able to build a Command Line Interface (CLI) app with hanami-cli.

The first hanami-cli (v0.1.0) version will be released with hanami v1.1.0 in October 2017.

Hanami provides both an integrated web framework (the hanami gem) and also a toolkit of general purpose gems like hanami-cli.

Indeed, in the near future, both dry-rb and Trailblazer will adopt hanami-cli to build their own command lines.

We love Ruby 💎❤️ and we build tools for the entire ecosystem.