Crystal CLI

Yet another Crystal library for building command-line interface applications.

Build Status

Installation

Add this to your application's shard.yml:

dependencies:
  cli:
    github: mosop/cli

Features

Option Parser

class Command < Cli::Command
  class Options
    string "--hello"
  end

  def run
    puts "Hello, #{options.hello}!"
  end
end

Command.run %w(--hello world) # prints "Hello, world!"

For more detail, see Parsing Options.

Exit

class Open < Cli::Command
  class Options
    arg "word"
  end

  def valid?
    args.word == "sesame"
  end

  def run
    if valid?
      exit! "Opened!"
    else
      error! "Not opened!"
    end
  end
end

Open.run %w(sesame) # => prints "Opened!" and returns 0 as an exit code
Open.run %w(paprika) # => prints "Not opened!" into STDERR and returns 1 as an exit code

For more detail, see Handling Exit.

Subcommand

class Polygon < Cli::Supercommand
  command "triangle", default: true
  command "square"
  command "hexagon"

  module Commands
    class Triangle < Cli::Command
      def run
        puts 3
      end
    end

    class Square < Cli::Command
      def run
        puts 4
      end
    end

    class Hexagon < Cli::Command
      def run
        puts 6
      end
    end
  end
end

Polygon.run %w(triangle) # prints "3"
Polygon.run %w(square)   # prints "4"
Polygon.run %w(hexagon)  # prints "6"
Polygon.run %w()         # prints "3"

For more detail, see Defining Subcommands.

Aliasing

class Command < Cli::Supercommand
  command "loooooooooong"
  command "l", aliased: "loooooooooong"

  module Commands
    class Loooooooooong < Cli::Command
      def run
        sleep 1000
      end
    end
  end
end

Command.run %w(l) # sleeps

Inheritance

class Role < Cli::Command
  class Options
    string "--name"
  end
end

class Chase < Cli::Supercommand
  command "mouse"
  command "cat"

  module Commands
    class Mouse < Role
      def run
        puts "#{options.name} runs away."
      end
    end

    class Cat < Role
      def run
        puts "#{options.name} runs into a wall."
      end
    end
  end
end

Chase.run %w(mouse --name Jerry) # prints "Jerry runs away."
Chase.run %w(cat --name Tom)     # prints "Tom runs into a wall."

Help

class Call < Cli::Command
  class Help
    header "Receives an ancient message."
    footer "(C) 20XX mosop"
  end

  class Options
    arg "message", desc: "your message to call them", required: true
    bool "-w", not: "-W", desc: "wait for response", default: true
    help
  end
end

Call.run %w(--help)
# call [OPTIONS] MESSAGE
#
# Receives an ancient message.
#
# Arguments:
#   MESSAGE (required)  your message to call them
#
# Options:
#   -w          wait for response
#               (default: true)
#   -W          disable -w
#   -h, --help  show this help
#
# (C) 20XX mosop

For more detail, see Generating Help.

Versioning

class Command < Cli::Supercommand
  version "1.0.0"

  class Options
    version
  end
end

Command.run %w(-v) # prints 1.0.0

For more detail, see Versioning.

Usage

require "cli"

and see Features.

Fundamentals

Crystal CLI provides 4 fundamental classes: Command, Supercommand, Options and Help.

Both Command and Supercommand inherit the CommandBase class that has several features commonly used.

Once you make a class inherit Command or Supercommand, then Options and Help is automatically defined into the class.

class YourCommand < Cli::Command
end

This code seems that it simply defines the YourCommand class. But, actually, it also makes YourCommand::Options and YourCommand::Help defined internally.

Parsing Options

The Options class is used to define command-line options and arguments.

For example:

class PlaySong < Cli::Command
  class Options
    arg "title", required: true
    bool "--repeat", not: "--Repeat", default: true
    array "--genre"
  end
end

Options inherits the Optarg::Model class provided from the optarg parser library. For more information about optarg, see the README.

Crystal CLI provides the proxy methods calling the corresponding optarg's API methods for accessing options and arguments. The following table shows how the methods are mapped.

| Crystal CLI | optarg | | :-- | :-- | | Cli::CommandBase#options | Optarg::Model#options | Cli::CommandBase#args | Optarg::Model#args | Cli::CommandBase#named_args | Optarg::Model#named_args | Cli::CommandBase#nameless_args | Optarg::Model#nameless_args | Cli::CommandBase#parsed_args | Optarg::Model#parsed_args | Cli::CommandBase#unparsed_args | Optarg::Model#unparsed_args

So, you can access to options and arguments easily in a command's scope.

class Command < Cli::Command
  class Options
    arg "arg"
    string "-s"
    terminator "--"
  end

  def run
    puts args.arg
    puts options.s
    puts unparsed_args[0]
  end
end

Command.run %w(foo -s bar -- baz)

This prints:

foo
bar
baz

Running a Command

The virtual CommandBase#run method is the entry point for running your command.

Your command's class will be instantiated and its #run method will be invoked after calling the static .run method.

class AncientCommand < Cli::Command
  def run
    puts "We the Earth"
  end
end

AncientCommand.run

This prints:

We the Earth

A command's instance is also accessible with the command method in option parser's scopes.

class AncientCommand < Cli::Command
  class Options
    on("--understand") { command.understand }
  end

  def understand
    puts "We know"
  end

  def run
    puts "We the Earth"
  end
end

AncientCommand.run %w(--understand)

This prints:

We know
We the Earth

Handling Exit

When a command normally ends, it returns 0.

class Command < Cli::Command
  def run
  end
end

Command.run # => 0

When you want to abort your command, you may raise an exception:

class Command < Cli::Command
  def run
    raise "ERROR!"
  end
end

Command.run # => raises error

Or, instead, you can have more control of exit with one of the 3 methods: help!, exit! and error!.

help!

class Command < Cli::Command
  def run
    help!
  end
end

Command.run # => 0

This command just ends after printing its help message. Command.run returns 0.

To print a message to STDERR and exit with an error code, use :error option.

help! error: true

If the :error option is true, run method returns 1. To specify a number, use the :code option.

help! code: 22

You can also let a command exit with an additional message:

help! message: "You passed an illegal option! See help!"

Or simply:

help! "You passed an illegal option! See help!"

Calling help! with the :message argument implies that the :error option is true. To exit normally, set false to :error.

exit! and error!

exit! is more general purpose than help!.

class Command < Cli::Command
  def run
    exit!
  end
end

Command.run # => 0

It just ends and returns 0 without a message.

To print a message:

exit! "bye."

Or more variations:

exit! help: true # equivalent to help!
exit! error: true # returns 1 as an exit code
exit! "message", error: true, help: true # equivalent to help!("message")

error! is similar to exit!, but the :error option is true as default.

error! # ends with 1 as an exit code
error! "message" # equivalent to exit!("message", error: true)
error! code: 22 # specifies exit code
error! help: true # equivalent to help!(error: true)
error! "message", help: true # equivalent to help!("message")

Displaying Help on Parsing Error

If a parsing error occurs, a help message automatically will be displayed.

class Bookmark < Cli::Command
  class Options
    arg "url", required: true, desc: "a URL to be bookmarked"
  end
end

Command.run

This prints:

Parsing Error: The URL argument is required.

bookmark URL

Arguments:
  URL  a URL to be bookmarked

If you want to disable the feature, use the CommandBase.disable_help_on_parsing_error! method.

class Bookmark < Cli::Command
  disable_help_on_parsing_error!

  class Options
    arg "url", required: true, desc: "a URL to be bookmarked"
  end
end

This only prints an error message:

Parsing Error: The URL argument is required.

Defining Subcommands

A subcommand is a child command that is categorized under a specific namespace. For example, the git command has its several subcommands, clone, commit, push, etc.

To define subcommands, you do:

class Git < Cli::Supercommand
  command "clone"
  command "commit"
  command "push"

  module Commands
    class Clone < Cli::Command
     # ...
    end

    class Commit < Cli::Command
      # ...
    end

    class Push < Cli::Command
      # ...
    end
  end
end

Default Subcommand

You can mark one of subcommands as default. The default subcommand can be run without an explicit name in a command line.

class Bundle < Cli::Supercommand
  command "install", default: true
  command "update"
  command "config"
  # ...
end

Bundle.run %w(install)  # explicitly runs install
Bundle.run %w()         # implicitly runs install

Generating Help

To format help messages, use the Help class.

For example:

class Smile < Cli::Command
  class Help
    header "Smiles n times."
    footer "(C) 20XX mosop"
  end

  class Options
    arg "face", required: true, desc: "your face, for example, :), :(, :P"
    string "--times", var: "NUMBER", default: "1", desc: "number of times to display"
    help
  end

  def run
    puts args.face * options.times.to_i
  end
end

Smile.run ARGV

If you run this command with a help option, you see:

$ smile --help
smile [OPTIONS] FACE

Smiles n times.

Arguments:
  FACE  your face, for example, :), :(, :P

Options:
  --times NUMBER  number of times to display
                  (default: 1)
  -h, --help      show this help

(C) 20XX mosop

The help format has the following sections. Each section is aligned in the order.

Titling

By default, the title section is automatically generated. To explicitly specify a title, use the Help.title method.

class Ancient < Cli::Command
  class Help
    title "ancient - calls ancient people"
  end
end

Instead of specifying a whole title, you can only set a command's name. It is convenient when a command name is different from its class name.

class Main < Cli::Command
  command_name "ancient"
end

Note: The command_name method belongs to the CommandBase class, not the Help class.

Unparsed Arguments

You may make a command that has arguments unparsed. For example, "exec" command internally executes another command and passes unparsed arguments to the other command. If you still need to display unparsed arguments in a title, use the Help.unparsed_args method.

class Exec < Cli::Command
  class Options
    arg "command", required: true, stop: true, desc: "command name"
    help
  end

  class Help
    unparsed_args "[ARG1 ARG2 ...]"
  end
end

Command.run %w(-h)

This prints:

exec COMMAND [ARG1 ARG2 ...]

Arguments:
  COMMAND  command name

Options:
  -h, --help  show this help

Header and Footer

The header and footer sections are not automatically defined. They appear only if you define them.

To define those sections, use the methods: Help.header and Help.footer.

class Dependency < Cli::Command
  class Help
    header <<-EOS
      Renders a dependency diagram.

      Supported package managers:
        RubyGems
        Shards
        npm
      EOS
    footer <<-EOS
      (C) 20XX mosop
      Created by mosop (http://mosop.me)
      EOS
  end
end

Subcommands

The subcommands section is appeared only if a command is a supercommand.

You can specify a caption that is displayed beside each subcommand. The caption is a very short description and typically a single phrase.

class Cake < Cli::Supercommand
  command "strawberry"
  command "cheese"
  command "chocolat"

  class Options
    help
  end

  module Commands
    class Strawberry < Cli::Command
      class Help
        caption "made with Sachinoka strawberry"
      end
    end

    class Cheese < Cli::Command
      class Help
        caption "New York-style"
      end
    end

    class Chocolat < Cli::Command
      class Help
        caption "winter only"
      end
    end
  end
end

Cake.run %w(--help)
# cake SUBCOMMAND
#
# Subcommands:
#   cheese      New York-style
#   chocolat    winter only
#   strawberry  made with Toyonoka strawberry
#
# Options:
#   -h, --help  show this help

Arguments and Options

The arguments and options sections are automatically generated from information defined.

You can specify an option's description by the :desc option.

class Friend < Cli::Command
  class Options
    arg "name", required: true, desc: "your friend name"
    string "--years", desc: "how long you've been friends"
    help
  end
end

Friend.run %w(--help)
# friend [OPTIONS] NAME
#
# Arguments:
#   NAME (required)  your friend name
#
# Options:
#   --years  how long you've been friends  

Options.help

The Options.help method adds the -h and --help options to your command. These options can be used to print a help message.

class Command < Cli::Command
  class Options
    help # equivalent to on(%w(-h --help)) { command.help! }
  end
end

You can change the option's name:

class Command < Cli::Command
  class Options
    help "--show-help"
  end
end

Versioning

You can set a command's version with the CommandBase.version method.

class Command < Cli::Command
  version "1.0.0"
end

To access the version string in a running context, use the CommandBase#version method.

class Command < Cli::Command
  version "1.0.0"

  def run
    version # => "1.0.0"
  end
end

Like the CommandBase#help! method, the CommandBase#version! method exits with a version string.

class Command < Cli::Command
  version "1.0.0"

  def run
    version! # prints "1.0.0" and exits
  end
end

Version Inheritance

Without an explicit definition, a subcommand inherits its supercommand's version.

class Command < Cli::Supercommand
  version "1.1.0"

  command "specific"
  command "inherit"

  module Commands
    class Specific < Cli::Command
      version "1.0.0"

      def run
        version # => "1.0.0"
      end
    end

    class Inherit < Cli::Command
      def run
        version # => "1.1.0"
      end
    end
  end
end

Options.version

The Options.version method adds the -v and --version options to your command. These options can be used to print a version string.

class Command < Cli::Command
  class Options
    version # equivalent to on(%w(-v --version)) { command.version! }
  end
end

You can change the option's name:

class Command < Cli::Command
  class Options
    version "--show-version"
  end
end

Want to Do

Releases

Development

[WIP]

Contributing

  1. Fork it ( https://github.com/mosop/cli/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors