matheusmoreira / acclaim

Command-line option parsing and command interface.
https://rubygems.org/gems/acclaim
6 stars 1 forks source link

Acclaim

Command-line option parsing and command interface.

Installation

Latest version:

gem install acclaim

From source:

git clone git://github.com/matheusmoreira/acclaim.git

Introduction

Acclaim makes it easy to describe commands and options for a command-line application in a structured manner. Commands are classes that inherit from Acclaim::Command:

require 'acclaim'

module App
  class Command < Acclaim::Command
    option :verbose, '-V', '--verbose'

    when_called do |options, args|
      puts 'Hello World!'
      puts args.join ', ' if options.verbose? and args.any?
    end
  end
end

App::Command.run *ARGV

$ app --verbose a b c
Hello World!
a, b, c

Every command has a set of options and block. When the application runs, the options are parsed into an object and passed to the command's block along with the remaining command line arguments. The first argument of the option method is the key used to store the value of the option, the other strings are either switches or a description:

option :verbose, '-V', '--verbose', '--run-verbosely', 'Run verbosely.'

Acclaim provides especial help and version commands that may be added to your program. The help command will automatically generate and print a help page for all commands and options. The version command will print your program's version and exit. To use them:

class App::Command
  help
  version '1.2.3'
end

$ app -h
$ app --help
$ app help

      -v, --verbose, --run-verbosely    Run verbosely.
      -h, --help    Show usage information and exit.
      -v, --version    Show version and exit.

$ app -v
$ app --version
$ app version
1.2.3

Both methods can take a hash as the last parameter. If you don't want the options, or if you want to specify a different set of switches or a different description, you can write:

help options: false
version '1.2.3', switches: %w(--version),
                 description: "Shows this program's version."

Subcommands

Essentially, a command given to another command. Subcommands benefit from all the option processing done by its parents. To create one, you simply inherit from an existing command:

class App::Command::Do < App::Command

  # option is aliased as opt
  opt :what, '--what', 'What to do.', default: 'something', arity: [1, 0]

  # when_called is aliased as action
  action do |options, args|
    puts "Doing #{options.what} with #{args.join ', '}"
  end
end

$ app do x y z
Doing something with x, y, z

$ app do --what x y z
Doing x with y, z

Options may also take an hash as the last parameter. Among the things that can be configured is the default value for the option and its arity. The default value is nil by default and is used if the option is not given. The arity of the option represents the minimum number of arguments it must take and the number of optional arguments it may take. It can be specified as an array in the form [minimum, optional]. Options that take zero arguments, which is the default, are called switches.

class App::Command::Do < App::Command

  # Negative number of optional arguments denote unlimited argument count
  opt :what, '--what', default: 'something', arity: [0, -1]

  action do |options, args|
    what = (options.what.join ', ' rescue options.what)
    subjects = args.join ', '
    puts "Doing #{what} with #{subjects}"
  end
end

$ app do --what x y z
Doing x, y, z with

Now, our option is eating up all the arguments in the command line! Hope is not lost, however. Even though the list of arguments may be unlimited, parsing will still stop if either another switch or an argument separator is encountered. An argument separator is a group of two or more dashes:

$ app do --what w x -- y z
Doing w, x with y, z

An important thing to understand is that options are not parsed all at once; first, the main command's options are parsed, then the remaining arguments are searched for subcommands. If one is found, its options are parsed, the remaining arguments are searched and so on. If a subcommand can't be found, the most specific command found is executed.

Options are deleted from the command line as they are parsed, so the following will not work:

$ app do --what w x --verbose y z
Doing w, x, y, z with

This happens because --verbose is an option of the main command. When do gets its turn it will be parsing the %w(--what w x y z) array.

Option Type Handlers

Arguments given to options are by default strings. To make life easier, you may specify the type of the arguments by passing a class among the arguments:

class App::Command::Do
  opt :when, '--when', 'When to do it.', Date, default: Date.today, arity: [1,0]

  action do |options, args|
    what = (options.what.join ', ' rescue options.what)
    subjects = args.join ', '
    date = options.when
    puts 'Merry Christmas!' if date.month == 12 and date.day == 25
    date = date.strftime '%m/%d/%Y'
    puts "Doing #{what} with #{subjects} on #{date}"
  end
end

$ app do --what w x --when 2011-12-25 y z
Merry Christmas!
Doing w, x with y, z on 12/25/2011

There are type handlers included for Dates, Times, DateTimes, URIs, Symbols and Strings, but if you need more you can always write your own:

class Person
  attr_accessor :first_name, :middle_names, :last_name

  def initialize(first, *others)
    @first_name, @last_name, @middle_names = first, others.pop, others
  end
end

Acclaim::Option::Type.add_handler_for Person do |argument|
  Person.new *argument.split
end

class App::Command::Show < App::Command

  # If no switches are specified, one will be derived from the key
  opt :person, Person, arity: [1,0], required: true

  when_called do |options, args|
    puts "#{options.person.last_name}, #{options.person.first_name}"
  end
end

$ app show --person 'Matheus Afonso Martins Moreira'
Moreira, Matheus

add_handler_for takes a class and a block, which will be called for every argument of that class that must be parsed.


Originally extracted from Safeguard.