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
git
orheroku
.
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.CLI
constructor. - 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-cli
gem. 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