Hey folks!
For those who are not familiar with dry-cli gem, let me introduce it to you. Long story short: dry-cli, formerly known as hanami-cli, was moved from hanami repository to dry-rb organization. For almost everyone, hanami-cli sounds like a set of cli utilities/generators/runners for hanami, but in fact it wasn't the case.
So it was decided to rename hanami-cli to dry-cli. hanami-cli 0.3.1 became a dry-cli 0.4, dry-cli 0.5 release removed all the hanami dependencies. And the latest release of dry-cli 0.6 has multiple new features. Do not worry, all backward compatibility is retained.
So let me run through the CHANGELOG and highlight some of the new features.
Anonymous registry syntax
Initial hanami-cli allowed to create any module you want, extend it with Dry::CLI::Registry functionality and use register method to add commands inside.
module Commands
extend Dry::CLI::Registry
register 'version', Version, aliases: %w[v -v --version]
register 'echo', Echo
register 'generate', aliases: ['g'] do |prefix|
prefix.register 'config', Generate::Configuration
end
end
Dry::CLI.new(Commands).call
Please understand me correctly, I love Registry concept, but to decrease entry threshold the new anonymous registry syntax has been introduced. Now you don't have to think of an extra Registry module, it will be prepared for you automatically at runtime.
Dry.CLI do
register 'version', Version, aliases: %w[v -v --version]
register 'echo', Echo
register 'generate', aliases: ['g'] do
register 'config', Generate::Configuration
end
end.call # do not forget to execute `call` after configuring your CLI
Singular command app
You can think of 2 types of CLI applications:
- Single command, like
ls,cat,cd - Multitool, like
gitorheroku.
So when you invoke git you also have to specify which command of git do you need to run: git pull, git checkout, etc. For a long time, hanami-cli (and later dry-cli) was extremely useful for building multitools, but you had no ability to create a single command app.
Now it is fixed. And to run a singular command app you may pass the command-class into Dry.CLI constructor.
class Command < Dry::CLI::Command
def call(**options)
end
end
Dry.CLI(Command).call
Inline syntax for commands
I decided to go further and added a little bit of syntactic sugar. Think of it as something similar to the sinatra simplified syntax.
require 'sinatra'
get '/' do
'Hello world!'
end
and you still can make a separate class and run it:
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
'Hello world!'
end
end
App.run!
Similarly to sinatra, you have the ability to save some keystrokes with dry-cli, while building a very simple CLI app. And with a combination of bundler inline syntax it looks awesome:
#!/usr/bin/env ruby
require 'bundler/inline'
gemfile { gem 'dry-cli', require 'dry/cli/inline' }
desc 'List files in a directory'
argument :path, required: false, desc: '[DIR]'
option :all, aliases: ['a'], type: :boolean
run do |path: '.', **options|
puts options.key?(:all) ? Dir.entries(path) : Dir.children(path)
end
Don't forget to run chmod +x your_cli to be able to execute it.
Stderr added
Nothing to show here, just a small improvement according to the best Unix's practices. All the diagnostic outputs and errors should go straight to the Stderr. The banner which reacts to the -h flag is the result of command execution, so the output is valid and goes straight to the Stdout.
Future plans
Since I've joined the team to work on this gem, I found lots of things to improve.
- First of all, currently all the IO inside the commands are not being delegated to IO you pass into
Dry.CLIconstructor. - I'm looking for a better way to invoke commands from the other commands
- Several file utils have been extracted from hanami to be a part of the
dry-cligem. We need a better support for them. - Generation abilities (https://github.com/dry-rb/dry-cli/pull/38)
- Add support for subcommands with valid parent command (https://github.com/dry-rb/dry-cli/pull/86)
Top comments (2)
Maybe I'm wrong but I think you have to add a "source" line in the gemfile block:
@ivanshamatov Great post, great improvements! Thanks