Skip to content

Help

Clip generates an help message for every commands, respecting the known conventions used since decades by many CLI applications, as we already seen it in the previous sections.

The #help method

When the module Clip::Mapper is included, three methods are generated:

  • #initialize
  • #parse
  • and #help

The #help method returns the help message:

require "clip"

module Myapplication
  VERSION = "0.1.0"

  struct Command
    include Clip::Mapper

    getter repeat = 1
    getter name : String
  end

  def self.run
    puts Command.help
  end
end

Myapplication.run
$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication
Usage: ./bin/myapplication [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --repeat INTEGER  [default: 1]
  --help            Show this message and exit.

This method accepts one parameter: the program's name. The default value is PROGRAM_NAME. You can set it to nil, which is useful for non CLI appliaction.

require "clip"

module Myapplication
  VERSION = "0.1.0"

  struct Command
    include Clip::Mapper

    getter repeat = 1
    getter name : String
  end

  def self.run
    puts Command.help("tutorialapp")
    puts "---"
    puts Command.help(nil)
  end
end

Myapplication.run
$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication
Usage: tutorialapp [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --repeat INTEGER  [default: 1]
  --help            Show this message and exit.
---
Usage: [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --repeat INTEGER  [default: 1]
  --help            Show this message and exit.

The Help object

The #help method is fine, but it does not work with nested commands. As it is called on the command itself, it has no context about the command being nested or not.

See this example:

require "clip"

module Myapplication
  VERSION = "0.1.0"

  abstract struct Command
    include Clip::Mapper

    Clip.add_commands({"hello" => HelloCommand})

    getter repeat = 1
  end

  struct HelloCommand < Command
    include Clip::Mapper

    getter name : String
  end

  def self.run
    puts HelloCommand.help
  end
end

Myapplication.run

$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication
Usage: ./bin/myapplication [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --repeat INTEGER  [default: 1]
  --help            Show this message and exit.

Here the usage shown is wrong. If you try this to excute ./bin/myapplication Alice it will actually complains that Alice is an unknown command.

Futhermore, you probably want to show the help when the user ask for it. And you can try, but setting an --help option yourself would require to define all other options and arguments as optional, as you probably don't want that ./bin/myapplication --help complains about NAME not being set. And setting everything optional defeat the purpose of having a library validating required options an arguments for you.

So to fix all that Clip provides a special mechanism for the help. We have used it already.

When using #parse, Clip checks before the parsing if the user input contains --help (or help if the command has nested commands). If it is the case, it returns a special object Help. The parsing is not done, so missing required options or arguments don't raise an error.

The Help object has two properties:

  • it inherits from Clip::Mapper::Help
  • it exposes a method #help

The first property is useful when using nested commands. The Help type is generated for each command, so without this property you would have to check if the returned value from #parse is an Help object for each available command. Instead you can just check if the type is Clip::Mapper::Help.

The second property is also useful when using nested commands. Help#help behaves like the #help method generated on your command, excepts it has context about nested commands.

Let's fix our previous example with a nested command:

require "clip"

module Myapplication
  VERSION = "0.1.0"

  abstract struct Command
    include Clip::Mapper

    Clip.add_commands({"hello" => HelloCommand})

    getter repeat = 1
  end

  struct HelloCommand < Command
    include Clip::Mapper

    getter name : String
  end

  def self.run
    begin
      command = Command.parse
    rescue ex : Clip::Error
      puts ex
      return
    end

    if command.is_a?(Clip::Mapper::Help)
      # command is actually an HelloCommand::Help instance
      puts command.help
    end
  end
end

Myapplication.run

The help is now correct:

$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication hello --help
Usage: ./bin/myapplication hello [OPTIONS] NAME

Arguments:
  NAME  [required]

Options:
  --repeat INTEGER  [default: 1]
  --help            Show this message and exit.