DEV Community

Cover image for dry-cli v0.6. Highlighting main features

dry-cli v0.6. Highlighting main features

Ivan Shamatov
Saint P Ruby community lead
・3 min read

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

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 # do not forget to execute `call` after configuring your CLI

Singular command app

You can think of 2 types of CLI applications:

  1. Single command, like ls, cat, cd
  2. Multitool, like git or heroku.

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)


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!'

and you still can make a separate class and run it:

require 'sinatra/base'

class App < Sinatra::Base
  get '/' do
    'Hello world!'

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)

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 (
  • Add support for subcommands with valid parent command (

Discussion (2)

megatux profile image
Cristian Molina

Maybe I'm wrong but I think you have to add a "source" line in the gemfile block:

gemfile do
  source ''
  gem 'dry-cli', require: 'dry/cli/inline'
wuarmin profile image

@ivanshamatov Great post, great improvements! Thanks