Hanami::CLI
General purpose Command Line Interface (CLI) framework for Ruby.
:warning: This is a general framework for Ruby (aka thor
gem replacement), NOT the implementation of the hanami
CLI commands :warning:
Status
Contact
- Home page: http://hanamirb.org
- Mailing List: http://hanamirb.org/mailing-list
- API Doc: http://rdoc.info/gems/hanami-cli
- Bugs/Issues: https://github.com/hanami/cli/issues
- Support: http://stackoverflow.com/questions/tagged/hanami
- Chat: http://chat.hanamirb.org
Rubies
Hanami::CLI supports Ruby (MRI) 2.3+, JRuby 9.1.5.0+
Installation
Add this line to your application's Gemfile:
gem 'hanami-cli'
And then execute:
$ bundle
Or install it yourself as:
$ gem install hanami-cli
Table of Contents
Features
Registration
For a given command name, you can register a corresponding command object (aka command).
Example: for foo hi
command name there is the corresponding Foo::CLI::Hello
command object.
#!/usr/bin/env ruby
require "bundler/setup"
require "hanami/cli"
module Foo
module CLI
module Commands
extend Hanami::CLI::Registry
class Hello < Hanami::CLI::Command
def call(*)
end
end
end
end
end
class Version < Hanami::CLI::Command
def call(*)
end
end
Foo::CLI::Commands.register "hi", Foo::CLI::Commands::Hello
Foo::CLI::Commands.register "v", Version
Hanami::CLI.new(Foo::CLI::Commands).call
Please note: there is NOT a convention between the command name and the command object class. The manual registration assigns a command object to a command name.
Commands as objects
A command is a subclass of Hanami::CLI::Command
and it MUST respond to #call(*)
.
Subcommands
There is nothing special in subcommands: they are just command objects registered under a nested command name.
#!/usr/bin/env ruby
require "bundler/setup"
require "hanami/cli"
module Foo
module CLI
module Commands
extend Hanami::CLI::Registry
module Generate
class Configuration < Hanami::CLI::Command
def call(*)
end
end
end
end
end
end
Foo::CLI::Commands.register "generate configuration", Foo::CLI::Commands::Generate::Configuration
Hanami::CLI.new(Foo::CLI::Commands).call
Arguments
An argument is a token passed after the command name.
For instance, given the foo greet
command, when an user types foo greet Luca
, then Luca
is considered an argument.
A command can accept none or many arguments.
An argument can be declared as required.
#!/usr/bin/env ruby
require "bundler/setup"
require "hanami/cli"
module Foo
module CLI
module Commands
extend Hanami::CLI::Registry
class Greet < Hanami::CLI::Command
argument :name, required: true, desc: "The name of the person to greet"
argument :age, desc: "The age of the person to greet"
def call(name:, age: nil, **)
result = "Hello, #{name}."
result = "#{result} You are #{age} years old." unless age.nil?
puts result
end
end
register "greet", Greet
end
end
end
Hanami::CLI.new(Foo::CLI::Commands).call
% foo greet Luca
Hello, Luca.
% foo greet Luca 35
Hello, Luca. You are 35 years old.
% foo greet
ERROR: "foo greet" was called with no arguments
Usage: "foo greet NAME"
Option
An option is a named argument that is passed after the command name and the arguments.
For instance, given the foo request
command, when an user types foo request --mode=http2
, then --mode=http2
is considered an option.
A command can accept none or many options.
#!/usr/bin/env ruby
require "bundler/setup"
require "hanami/cli"
module Foo
module CLI
module Commands
extend Hanami::CLI::Registry
class Request < Hanami::CLI::Command
option :mode, default: "http", values: %w[http http2], desc: "The request mode"
def call(**)
puts "Performing a request (mode: #{.fetch(:mode)})"
end
end
register "request", Request
end
end
end
Hanami::CLI.new(Foo::CLI::Commands).call
% foo request
Performing a request (mode: http)
% foo request --mode=http2
Performing a request (mode: http2)
% foo request --mode=unknown
Error: "request" was called with arguments "--mode=unknown"
Variadic arguments
Sometimes we need extra arguments because those will be forwarded to a sub-command like ssh
, docker
or cat
.
By using --
(double dash, aka hypen), the user indicates the end of the arguments and options belonging to the main command, and the beginning of the variadic arguments that can be forwarded to the sub-command.
These extra arguments are included as :args
in the keyword arguments available for each command.
#!/usr/bin/env ruby
require "bundler/setup"
require "hanami/cli"
module Foo
module CLI
module Commands
extend Hanami::CLI::Registry
class Runner < Hanami::CLI::Command
argument :image, required: true, desc: "Docker image"
def call(image:, args: [], **)
puts `docker run -it --rm #{image} #{args.join(" ")}`
end
end
register "run", Runner
end
end
end
Hanami::CLI.new(Foo::CLI::Commands).call
% foo run ruby:latest -- ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
The user separates via --
the arguments for foo
and the command has to be run by the Docker container.
In this specific case, ruby:latest
corresponds to the image
mandatory argument for foo
, whereas ruby -v
is the variadic argument that is passed to Docker via args
.
Installation
Add this line to your application's Gemfile:
gem "hanami-cli"
And then execute:
$ bundle
Or install it yourself as:
$ gem install hanami-cli
Usage
Imagine to build a CLI executable foo
for your Ruby project.
#!/usr/bin/env ruby
require "bundler/setup"
require "hanami/cli"
module Foo
module CLI
module Commands
extend Hanami::CLI::Registry
class Version < Hanami::CLI::Command
desc "Print version"
def call(*)
puts "1.0.0"
end
end
class Echo < Hanami::CLI::Command
desc "Print input"
argument :input, desc: "Input to print"
example [
" # Prints 'wuh?'",
"hello, folks # Prints 'hello, folks'"
]
def call(input: nil, **)
if input.nil?
puts "wuh?"
else
puts input
end
end
end
class Start < Hanami::CLI::Command
desc "Start Foo machinery"
argument :root, required: true, desc: "Root directory"
example [
"path/to/root # Start Foo at root directory"
]
def call(root:, **)
puts "started - root: #{root}"
end
end
class Stop < Hanami::CLI::Command
desc "Stop Foo machinery"
option :graceful, type: :boolean, default: true, desc: "Graceful stop"
def call(**)
puts "stopped - graceful: #{.fetch(:graceful)}"
end
end
class Exec < Hanami::CLI::Command
desc "Execute a task"
argument :task, type: :string, required: true, desc: "Task to be executed"
argument :dirs, type: :array, required: false, desc: "Optional directories"
def call(task:, dirs: [], **)
puts "exec - task: #{task}, dirs: #{dirs.inspect}"
end
end
module Generate
class Configuration < Hanami::CLI::Command
desc "Generate configuration"
option :apps, type: :array, default: [], desc: "Generate configuration for specific apps"
def call(apps:, **)
puts "generated configuration for apps: #{apps.inspect}"
end
end
class Test < Hanami::CLI::Command
desc "Generate tests"
option :framework, default: "minitest", values: %w[minitest rspec]
def call(framework:, **)
puts "generated tests - framework: #{framework}"
end
end
end
register "version", Version, aliases: ["v", "-v", "--version"]
register "echo", Echo
register "start", Start
register "stop", Stop
register "exec", Exec
register "generate", aliases: ["g"] do |prefix|
prefix.register "config", Generate::Configuration
prefix.register "test", Generate::Test
end
end
end
end
Hanami::CLI.new(Foo::CLI::Commands).call
Let's have a look at the command line usage.
Available commands
% foo
Commands:
foo echo [INPUT] # Print input
foo exec TASK [DIRS] # Execute a task
foo generate [SUBCOMMAND]
foo start ROOT # Start Foo machinery
foo stop # Stop Foo machinery
foo version # Print version
Help
% foo echo --help
Command:
foo echo
Usage:
foo echo [INPUT]
Description:
Print input
Arguments:
INPUT # Input to print
Options:
--help, -h # Print this help
Examples:
foo echo # Prints 'wuh?'
foo echo hello, folks # Prints 'hello, folks'
Optional arguments
% foo echo
wuh?
% foo echo hello
hello
Required arguments
% foo start .
started - root: .
% foo start
ERROR: "foo start" was called with no arguments
Usage: "foo start ROOT"
Array arguments
Captures all the remaining arguments in a single array.
Please note that array
argument must be used as last argument as it works as a "catch-all".
% foo exec test
exec - task: test, dirs: []
% foo exec test spec/bookshelf/entities spec/bookshelf/repositories
exec - task: test, dirs: ["spec/bookshelf/entities", "spec/bookshelf/repositories"]
Options
% foo generate test
generated tests - framework: minitest
% foo generate test --framework=rspec
generated tests - framework: rspec
% foo generate test --framework=unknown
Error: "test" was called with arguments "--framework=unknown"
Boolean options
% foo stop
stopped - graceful: true
% foo stop --no-graceful
stopped - graceful: false
Array options
% foo generate config --apps=web,api
generated configuration for apps: ["web", "api"]
Subcommands
% foo generate
Commands:
foo generate config # Generate configuration
foo generate test # Generate tests
Aliases
% foo version
1.0.0
% foo v
1.0.0
% foo -v
1.0.0
% foo --version
1.0.0
Subcommand aliases
% foo g config
generated configuration for apps: []
Callbacks
Third party gems can register before and after callbacks to enhance a command.
From the foo
gem we have a command hello
.
#!/usr/bin/env ruby
require "hanami/cli"
module Foo
module CLI
module Commands
extend Hanami::CLI::Registry
class Hello < Hanami::CLI::Command
argument :name, required: true
def call(name:, **)
puts "hello #{name}"
end
end
end
end
end
Foo::CLI::Commands.register "hello", Foo::CLI::Commands::Hello
cli = Hanami::CLI.new(Foo::CLI::Commands)
cli.call
The foo-bar
gem enhances hello
command with callbacks:
Foo::CLI::Commands.before("hello") { |args| puts "debug: #{args.inspect}" } # syntax 1
Foo::CLI::Commands.after "hello", &->(args) { puts "bye, #{args.fetch(:name)}" } # syntax 2
% foo hello Anton
debug: {:name=>"Anton"}
hello Anton
bye, Anton
Development
After checking out the repo, run bin/setup
to install dependencies. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/hanami/cli.
Alternatives
Copyright
Copyright © 2017 Luca Guidi – Released under MIT License