General purpose Command Line Interface (CLI) framework for Ruby.
thor gem replacement), NOT the implementation of the hanami CLI commands
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).callPlease 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.
A command is a subclass of Hanami::CLI::Command and it MUST respond to #call(*).
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).callAn 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"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(**options)
puts "Performing a request (mode: #{options.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"Add this line to your application's Gemfile:
gem "hanami-cli"And then execute:
$ bundle
Or install it yourself as:
$ gem install hanami-cli
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(**options)
puts "stopped - graceful: #{options.fetch(:graceful)}"
end
end
module Generate
class Configuration < Hanami::CLI::Command
desc "Generate configuration"
def call(*)
puts "generated configuration"
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 "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).callLet's have a look at the command line usage.
% foo
Commands:
foo echo [INPUT] # Print input
foo generate [SUBCOMMAND]
foo start ROOT # Start Foo machinery
foo stop # Stop Foo machinery
foo version # Print version% 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'% foo echo
wuh?
% foo echo hello
hello% foo start .
started - root: .% foo start
ERROR: "foo start" was called with no arguments
Usage: "foo start ROOT"% 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"% foo stop
stopped - graceful: true% foo stop --no-graceful
stopped - graceful: false% foo generate
Commands:
foo generate config # Generate configuration
foo generate test # Generate tests% foo version
1.0.0% foo v
1.0.0% foo -v
1.0.0% foo --version
1.0.0% foo g config
generated configurationThird 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.callThe 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, AntonAfter 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.
Bug reports and pull requests are welcome on GitHub at https://github.com/hanami/cli.
Copyright © 2017 Luca Guidi – Released under MIT License