dry-cli v0.6. Highlighting main features
Tags: ruby, dry, cli, hanami
Published: 2020-03-06 10:57:24

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](#anonymous-registry-syntax)
* [Singular command app](#singular-command-app)
* [Inline syntax for commands](#inline-syntax-for-commands)
* [Stderr added](#stderr-added)

### 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. 

```ruby
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.

```ruby
  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:

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.

```ruby
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.

```ruby
require 'sinatra'

get '/' do
  'Hello world!'
end
```

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

```ruby
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:

```ruby
#!/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)