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.