Benry-CmdApp

($Release: 0.1.0 $)

What's This?

Benry-CmdApp is a framework to create command-line application. If you want create command-line application which takes sub-commands like git, docker, or npm, Benry-CmdApp is the solution.

Base idea:

  • Sub-command (= action) is defined as a method in Ruby.
  • Commnad-line arguments are passed to action method as positional arguments.
  • Command-line options are passed to action method as keyword arguments.

For example:

  • <command> foo in command-line invokes action method foo() in Ruby.
  • <command> foo arg1 arg2 invokes foo("arg1", "arg2").
  • <command> foo arg --opt=val invokes foo("arg", opt: "val").

Links:

Benry-CmdApp requires Ruby >= 2.3.

Table of Contents

Install

$ gem install benry-cmdapp

Usage

Action

  • Inherit action class and define action methods in it.
  • An action class can have several action methods.
  • It is ok to define multiple action classes.
  • Command-line arguments are passed to action method as positional arguments.

File: ex01.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

## action
class MyAction < Benry::CmdApp::Action    # !!!!

  @action.("print greeting message")      # !!!!
  def hello(user="world")                 # !!!!
    puts "Hello, #{user}!"
  end

end

## configuration
config = Benry::CmdApp::Config.new("sample app", "1.0.0")
config.default_help = true

## run application
app = Benry::CmdApp::Application.new(config)
status_code = app.main()
exit status_code

Output:

[bash]$ ruby ex01.rb hello           # action
Hello, world!

[bash]$ ruby ex01.rb hello Alice     # action + argument
Hello, Alice!

Help message of command:

[bash]$ ruby ex01.rb -h     # or `--help`
ex01.rb (1.0.0) -- sample app

Usage:
  $ ex01.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)
  -V, --version      : print version

Actions:
  hello              : print greeting message

Help message of action:

[bash]$ ruby ex01.rb -h hello
ex01.rb hello -- print greeting message

Usage:
  $ ex01.rb hello [<user>]

Method Name and Action Name

  • Method name print_ results in action name print. This is useful to define actions which name is same as Ruby keyword or popular functions.
  • Method name foo_bar_baz results in action name foo-bar-baz.
  • Method name foo__bar__baz results in action name foo:bar:baz.

File: ex02.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  ## 'print_' => 'print'
  @action.("sample #1")
  def print_()                 # !!!!
    puts __method__
  end

  ## 'foo_bar_baz' => 'foo-bar-baz'
  @action.("sample #2")
  def foo_bar_baz()            # !!!!
    puts __method__
  end

  ## 'foo__bar__baz' => 'foo:bar:baz'
  @action.("sample #3")
  def foo__bar__baz()          # !!!!
    puts __method__
  end

end

config = Benry::CmdApp::Config.new("test app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex02.rb --help
ex02.rb -- test app

Usage:
  $ ex02.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  foo-bar-baz        : sample #2
  foo:bar:baz        : sample #3
  print              : sample #1

Output:

[bash]$ ruby ex02.rb print            # `print_` method
print_

[bash]$ ruby ex02.rb foo-bar-baz      # `foo_bar_baz` method
foo_bar_baz

[bash]$ ruby ex02.rb foo:bar:baz      # `foo__bar__baz` method
foo__bar__baz

Parameter Name in Help Message of Action

In help message of action, positional parameters of action methods are printed under the name conversion rule.

  • Parameter foo is printed as <foo>.
  • Parameter foo_bar_baz is printed as <foo-bar-baz>.
  • Parameter foo_or_bar_or_baz is printed as <foo|bar|baz>.

In addition, positional parameters are printed in different way according to its kind.

  • If parameter foo is required (= doesn't have default value), it will be printed as <foo>.
  • If parameter foo is optional (= has default value), it will be printed as [<foo>].
  • If parameter foo is variable length (= *foo style), it will be printed as [<foo>...].

File: ex03.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("parameter names test")
  def test1(aaa, bbb_or_ccc, ddd=nil, eee=nil, *fff)  # !!!!
    # ...
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex03.rb -h test1
hoge.rb test1 -- parameter names test

Usage:
  $ ex03.rb test1 <aaa> <bbb|ccc> [<ddd> [<eee> [<fff>...]]]  # !!!!

Options

  • Action can take command-line options.
  • Option values specified in command-line are passed to actio method as keyword arguments.

File: ex04.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

## action
class MyAction < Benry::CmdApp::Action

  @action.("print greeting message")
  @option.(:lang, "-l, --lang=<en|fr|it>", "language")   # !!!!
  def hello(user="world", lang: "en")                    # !!!!
    case lang
    when "en" ; puts "Hello, #{user}!"
    when "fr" ; puts "Bonjour, #{user}!"
    when "it" ; puts "Ciao, #{user}!"
    else
      raise "#{lang}: unknown language."
    end
  end

end

## configuration
config = Benry::CmdApp::Config.new("sample app", "1.0.0")
config.default_help = true

## run application
app = Benry::CmdApp::Application.new(config)
status_code = app.main()
exit status_code

Output:

[bash]$ ruby ex04.rb hello
Hello, world!

[bash]$ ruby ex04.rb hello -l fr            # !!!!
Bonjour, world!

[bash]$ ruby ex04.rb hello --lang=it        # !!!!
Ciao, world!
  • An action can have multiple options.
  • Option format can have indentation spaces, for example ' --help'.

File: ex05.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

## action
class MyAction < Benry::CmdApp::Action

  @action.("print greeting message")
  @option.(:lang  , "-l, --lang=<en|fr|it>", "language")
  @option.(:repeat, "    --repeat=<N>", "repeat <N> times")  # !!!!
  def hello(user="world", lang: "en", repeat: "1")
    #p repeat.class   #=> String                    # !!!!
    repeat.to_i.times do                            # !!!!
      case lang
      when "en" ; puts "Hello, #{user}!"
      when "fr" ; puts "Bonjour, #{user}!"
      when "it" ; puts "Ciao, #{user}!"
      else
        raise "#{lang}: unknown language."
      end
    end
  end

end

## configuration
config = Benry::CmdApp::Config.new("sample app", "1.0.0")
config.default_help = true

## run application
app = Benry::CmdApp::Application.new(config)
status_code = app.main()
exit status_code

Output:

[bash]$ ruby ex05.rb hello Alice -l fr --repeat=3
Bonjour, Alice!
Bonjour, Alice!
Bonjour, Alice!

Help message:

[bash]$ ruby ex05.rb -h hello
ex05.rb hello -- print greeting message

Usage:
  $ ex05.rb hello [<options>] [<user>]

Options:
  -l, --lang=<en|fr|it> : language        # !!!!
      --repeat=<N>   : repeat <N> times   # !!!!

For usability reason, Benry::CmdApp supports --lang=<val> style long option and doesn't support --lang <val> style option. Benry::CmdApp regards --lang <val> as 'long option without argument' and 'argument for command'.

[bash]$ ruby ex05.rb hello --lang fr         # ``--lang fr`` != ``--lang=fr``
[ERROR] --lang: argument required.

Option Definition Format

Option definition format should be one of:

  • (short option) -q : no values.
  • (short option) -f <file> : value required.
  • (short option) -i[<width>] : value is optional.
  • (long option) --quiet : no values.
  • (long option) --file=<file> : value required.
  • (long option) --indent[=<width>] : value is optional.
  • (short & long) -q, --quiet : no values.
  • (short & long) -f, --file=<file> : value required.
  • (short & long) -i, --indent[=<width>] : value is optional.

File: ex06.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  ## short options
  @action.("short options")
  @option.(:quiet  , "-q"        , "quiet mode")     # none
  @option.(:file   , "-f <file>" , "filename")       # required
  @option.(:indent , "-i[<N>]"   , "indent width")   # optional
  def test1(quiet: false, file: nil, indent: nil)
    puts "quiet=#{quiet.inspect}, file=#{file.inspect}, indent=#{indent.inspect}"
  end

  ## long options
  @action.("long options")
  @option.(:quiet  , "--quiet"        , "quiet mode")     # none
  @option.(:file   , "--file=<file>"  , "filename")       # required
  @option.(:indent , "--indent[=<N>]" , "indent width")   # optional
  def test2(quiet: false, file: nil, indent: nil)
    puts "quiet=#{quiet.inspect}, file=#{file.inspect}, indent=#{indent.inspect}"
  end

  ## short and long options
  @action.("short and long options")
  @option.(:quiet  , "-q, --quiet"        , "quiet mode")    # none
  @option.(:file   , "-f, --file=<file>"  , "filename")      # required
  @option.(:indent , "-i, --indent[=<N>]" , "indent width")  # optional
  def test3(quiet: false, file: nil, indent: nil)
    puts "quiet=#{quiet.inspect}, file=#{file.inspect}, indent=#{indent.inspect}"
  end

end

config = Benry::CmdApp::Config.new("test app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex06.rb test1 -q -f readme.txt -i4
quiet=true, file="readme.txt", indent="4"

[bash]$ ruby ex06.rb test2 --quiet --file=readme.txt --indent=4
quiet=true, file="readme.txt", indent="4"

[bash]$ ruby ex06.rb test3 -q -f readme.txt -i4
quiet=true, file="readme.txt", indent="4"
[bash]$ ruby ex06.rb test3 --quiet --file=readme.txt --indent=4
quiet=true, file="readme.txt", indent="4"

Optional argument example:

[bash]$ ruby ex06.rb test1 -i                 # ``-i`` results in ``true``
quiet=false, file=nil, indent=true
[bash]$ ruby ex06.rb test1 -i4                # ``-i4`` results in ``4``
quiet=false, file=nil, indent="4"

[bash]$ ruby ex06.rb test2 --indent           # ``--indent`` results in ``true``
quiet=false, file=nil, indent=true
[bash]$ ruby ex06.rb test2 --indent=4         # ``--indent=4`` results in ``4``
quiet=false, file=nil, indent="4"

Help message:

[bash]$ ruby ex06.rb -h test1
ex06.rb test1 -- short options

Usage:
  $ ex06.rb test1 [<options>]

Options:
  -q                 : quiet mode
  -f <file>          : filename
  -i[<N>]            : indent width

[bash]$ ruby ex06.rb -h test2
ex06.rb test2 -- long options

Usage:
  $ ex06.rb test2 [<options>]

Options:
  --quiet            : quiet mode
  --file=<file>      : filename
  --indent[=<N>]     : indent width

[bash]$ ruby ex06.rb -h test3
ex06.rb test3 -- short and long options

Usage:
  $ ex06.rb test3 [<options>]

Options:
  -q, --quiet        : quiet mode
  -f, --file=<file>  : filename
  -i, --indent[=<N>] : indent width

Option Value Validation

@option.() can validate option value via keyword argument.

  • type: <class> specifies option value class. Currently supports Integer, Float, TrueClass, and Date.
  • rexp: <rexp> specifies regular expression of option value.
  • enum: <array> specifies available values as option value.
  • range: <range> specifies range of option value.

File: ex07.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

## action
class MyAction < Benry::CmdApp::Action

  @action.("print greeting message")
  @option.(:lang  , "-l, --lang=<en|fr|it>", "language",
                  enum: ["en", "fr", "it"],         # !!!!
          rexp: /\A\w\w\z/)                 # !!!!
  @option.(:repeat, "    --repeat=<N>", "repeat <N> times",
                  type: Integer, range: 1..10)      # !!!!
  def hello(user="world", lang: "en", repeat: 1)
    #p repeat.class   #=> Integer
    repeat.times do
      case lang
      when "en" ; puts "Hello, #{user}!"
      when "fr" ; puts "Bonjour, #{user}!"
      when "it" ; puts "Ciao, #{user}!"
      else
        raise "#{lang}: unknown language."
      end
    end
  end

end

## configuration
config = Benry::CmdApp::Config.new("sample app", "1.0.0")
config.default_help = true

## run application
app = Benry::CmdApp::Application.new(config)
status_code = app.main()
exit status_code

Output:

[bash]$ ruby ex07.rb hello -l japan
[ERROR] -l japan: pattern unmatched.

[bash]$ ruby ex07.rb hello -l ja
[ERROR] -l ja: expected one of en/fr/it.

[bash]$ ruby ex07.rb hello --repeat=abc
[ERROR] --repeat=abc: integer expected.

[bash]$ ruby ex07.rb hello --repeat=100
[ERROR] --repeat=100: Too large (max: 10).

Callback for Option Value

@option.() can take a block argument which is a callback for option value. Callback can:

  • Do custom validation of option value.
  • Convert option value into other value.

File: ex08.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

## action
class MyAction < Benry::CmdApp::Action

  @action.("print greeting message")
  @option.(:lang  , "-l, --lang=<en|fr|it>", "language",
                  enum: ["en", "fr", "it", "EN", "FR", "IT"],
          rexp: /\A\w\w\z/) {|v| v.downcase }    # !!!!
  @option.(:repeat, "    --repeat=<N>", "repeat <N> times",
                  type: Integer) {|v|                    # !!!!
            v > 0 or raise "not positive value." # !!!!
                    v                                    # !!!!
                  }                                      # !!!!
  def hello(user="world", lang: "en", repeat: 1)
    repeat.times do
      case lang
      when "en" ; puts "Hello, #{user}!"
      when "fr" ; puts "Bonjour, #{user}!"
      when "it" ; puts "Ciao, #{user}!"
      else
        raise "#{lang}: unknown language."
      end
    end
  end

end

## configuration
config = Benry::CmdApp::Config.new("sample app", "1.0.0")
config.default_help = true

## run application
app = Benry::CmdApp::Application.new(config)
status_code = app.main()
exit status_code

Output:

[bash]$ ruby ex08.rb hello -l FR   # converted into lowercase
Bonjour, world!

[bash]$ ruby ex08.rb hello --repeat=0
[ERROR] --repeat=0: not positive value.

Boolean (On/Off) Option

Benry::CmdApp doesn't support --[no-]foobar style option. Instead, define boolean (on/off) option.

  • Specify type: TrueClass to @option.().
  • Option value true, yes, and on are converted into true.
  • Option value false, no, and off are converted into false.

File: ex09.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("flag test")
  @option.(:verbose, "--verbose[=<on|off>]",  # !!!!
                     "verbose mode",
                     type: TrueClass)         # !!!!
  def flagtest(verbose: false)                # !!!!
    puts "verbose=#{verbose}"
  end

end

config = Benry::CmdApp::Config.new("sample app", "1.0.0")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex09.rb flagtest --verbose=on       # on
verbose=true

[bash]$ ruby ex09.rb flagtest --verbose=off      # off
verbose=false

[bash]$ ruby ex09.rb flagtest --verbose=true     # on
verbose=true

[bash]$ ruby ex09.rb flagtest --verbose=false    # off
verbose=false

[bash]$ ruby ex09.rb flagtest --verbose=yes      # on
verbose=true

[bash]$ ruby ex09.rb flagtest --verbose=no       # off
verbose=false

[bash]$ ruby ex09.rb flagtest --verbose=abc      # error
[ERROR] --verbose=abc: boolean expected.

If you want default value of flag to true, use value: keyword argument.

  • value: keyword argument in @option.() specifies the substitute value instead of true when no option value specified in command-line.

File: ex10.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("flag test")
  @option.(:verbose, "-q, --quiet", "quiet mode",
                     value: false)                 # !!!!
  def flagtest2(verbose: true)                     # !!!!
    puts "verbose=#{verbose.inspect}"
  end

end

config = Benry::CmdApp::Config.new("git helper")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex10.rb flagtest2           # true if '--quiet' NOT specified
verbose=true

[bash]$ ruby ex10.rb flagtest2 --quiet   # false if '--quiet' specified
verbose=false

[bash]$ ruby ex10.rb flagtest2 --quiet=on   # error
[ERROR] --quiet=on: unexpected argument.

In above example, --quiet=on will be error because option is defined as @option.(:verbose, "-q, --quiet", ...) which means that this option takes no arguments. If you want to allow --quiet=on, specify option argument and type: TrueClass.

  ...(snip)...

  @action.("flag test")
  @option.(:verbose, "-q, --quiet[=<on|off]", "quiet mode",  # !!!!
                     type: TrueClass, value: false)          # !!!!
  def flagtest2(verbose: true)
    puts "verbose=#{verbose.inspect}"
  end

  ...(snip)...

Prefix of Action Name

  • prefix: "foo:bar" in action class adds prefix foo:bar: to each action name.
  • Method name def baz__test() with prefix: "foo:bar" results in action name foo:bar:baz:test.

File: ex11.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action
  prefix "foo:bar"            # !!!!

  @action.("test action #1")
  def test1()                 # action name: 'foo:bar:test1'
    puts __method__
  end

  @action.("test action #2")
  def baz__test2()            # action name: 'foo:bar:baz:test2'
    puts __method__
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex11.rb foo:bar:test1
test1

[bash]$ ruby ex11.rb foo:bar:baz:test2
baz__test2

Help message:

[bash]$ ruby ex11.rb -h
ex11.rb -- sample app

Usage:
  $ ex11.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  foo:bar:baz:test2  : test action #2
  foo:bar:test1      : test action #1
  • prefix: "foo:bar", action: :test defines foo:bar action (intead of foo:bar:test) with test() method.

File: ex12.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action
  prefix "foo:bar", action: :test3_      # !!!!
  ## or:
  #prefix "foo:bar", action: "test3"     # !!!!

  @action.("test action #1")
  def test1()                 # action name: 'foo:bar:test1'
    puts __method__
  end

  @action.("test action #3")
  def test3_()                # action name: 'foo:bar'
    puts __method__
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex12.rb foo:bar:test1
test1

[bash]$ ruby ex12.rb foo:bar:test3
[ERROR] foo:bar:test2: unknown action.

[bash]$ ruby ex12.rb foo:bar
test3_

Help message:

[bash]$ ruby ex12.rb -h
ex12.rb -- sample app

Usage:
  $ ex12.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  foo:bar            : test action #3
  foo:bar:test1      : test action #1

Invoke Other Action

  • run_action!() invokes other action.
  • run_action_once() invokes other action only once. This is equivarent to 'prerequisite task' feature in task runner application.

File: ex13.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("create build dir")
  def prepare()
    puts "rm -rf build"
    puts "mkdir build"
  end

  @action.("build something")
  def build()
    run_action_once("prepare")        # !!!!
    run_action_once("prepare")        # skipped because already invoked
    puts "echo 'README' > build/README.txt"
    puts "zip -r build.zip build"
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex13.rb build
rm -rf build                          # invoked only once!!!!
mkdir build                           # invoked only once!!!!
echo 'README' > build/README.txt
zip -r build.zip build
  • When looped action is detected, Benry::CmdApp aborts action.

File: ex14.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class LoopedAction < Benry::CmdApp::Action

  @action.("test #1")
  def test1()
    run_action_once("test2")
  end

  @action.("test #2")
  def test2()
    run_action_once("test3")
  end

  @action.("test #3")
  def test3()
    run_action_once("test1")          # !!!!
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex14.rb test1
[ERROR] test1: looped action detected.

[bash]$ ruby ex14.rb test3
[ERROR] test3: looped action detected.

Action Alias

  • Alias of action provides alternative short name of action.

File: ex15.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action
  prefix "foo:bar"

  @action.("test action #1")
  def test1()                 # action name: 'foo:bar:test1'
    puts __method__
  end

end

Benry::CmdApp.action_alias "test", "foo:bar:test1"   # !!!!

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex15.rb test             # alias name
test1

[bash]$ ruby ex15.rb foo:bar:test1    # original action name
test1

Help message:

[bash]$ ruby ex15.rb -h
ex15.rb -- sample app

Usage:
  $ ex15.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  foo:bar:test1      : test action #1
  test               : alias to 'foo:bar:test1' action
  • Alias can include positional and keyword arguments in definition.

File: ex16.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class MyAction < Benry::CmdApp::Action

  @action.("print greeting message")
  @option.(:lang, "-l, --lang=<lang>", "language", enum: ["en", "fr", "it"])
  def hello(user="world", lang: "en")
    case lang
    when "en" ; puts "Hello, #{user}!"
    when "fr" ; puts "Bonjour, #{user}!"
    when "it" ; puts "Ciao, #{user}!"
    else
      raise "#{lang}: unknown language."
    end
  end

end

Benry::CmdApp.action_alias("bonjour", "hello", "--lang=fr")        # !!!!
Benry::CmdApp.action_alias("ciao"   , "hello", "Bob", "-l", "it")  # !!!!

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex16.rb hello
Hello, world!

[bash]$ ruby ex16.rb bonjour           # !!!!
Bonjour, world!

[bash]$ ruby ex16.rb bonjour Alice     # !!!!
Bonjour, Alice!

[bash]$ ruby ex16.rb ciao              # !!!!
Ciao, Bob!

Default Action

  • config.default = "test1" defines default action. In this case, action test1 will be invoked if action name not specified in command-line.
  • Default action name is shown in help message.

File: ex17.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action #1")
  def test1()
    puts __method__
  end

end

config = Benry::CmdApp::Config.new("sample app")
config.default_action = "test1"     # !!!!
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex17.rb test1
test1

[bash]$ ruby ex17.rb               # no action name!!!!
test1

Help message:

[bash]$ ruby ex17.rb -h
ex17.rb -- sample app

Usage:
  $ ex17.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions: (default: test1)                   # !!!!
  test1              : test action #1

Default Help

  • config.default_help = true prints help message if action not specified in command-line.
  • This is very useful when you don't have proper default action. It's recommended.
  • config.default_action is prior than config.default_help.

File: ex18.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action #1")
  def test1()
    puts __method__
  end

end

config = Benry::CmdApp::Config.new("sample app")
config.default_help = true     # !!!!
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex18.rb            # no action name!!!!
ex18.rb -- sample app

Usage:
  $ ex18.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  test1              : test action #1

Private (Hidden) Action

  • If action method is private, Benry::CmdApp regards that action as private.
  • Private actions are hidden in help message.
  • Private actions are shown when -a or --all option enabled and specified.

File: ex20.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action #1")
  def test1()
    puts __method__
  end

  @action.("test action #2")
  def test2()
    puts __method__
  end
  private :test2               # !!!! private method !!!!

  private                      # !!!! private method !!!!

  @action.("test action #3")
  def test3()
    puts __method__
  end

end

config = Benry::CmdApp::Config.new("sample app")
config.option_all = true       # !!!! enable '-a, --all' option !!!!
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message (without -a nor --all):

[bash]$ ruby ex20.rb -h
ex20.rb -- sample app

Usage:
  $ ex20.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)
  -a, --all          : list all actions/options including private (hidden) ones

Actions:
  test1              : test action #1

Help message (with -a or --all):

[bash]$ ruby ex20.rb -h --all      # !!!!
ex20.rb -- sample app

Usage:
  $ ex20.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)
  -a, --all          : list all actions/options including private (hidden) ones

Actions:
  test1              : test action #1
  test2              : test action #2          # !!!!
  test3              : test action #3          # !!!!

Private (Hidden) Option

  • Options which name stars with _ are treated as private option.
  • Private options are hidden in help message of action.
  • Private options are shown when -a or --all option enabled and specified.

File: ex21.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action")
  @option.(:verbose, "-v", "verbose mode")
  @option.(:_debug , "-D", "debug mode")      # !!!!
  def test1(verbose: false, _debug: false)
    puts "verbose=#{verbose}, _debug=#{_debug}"
  end

end

config = Benry::CmdApp::Config.new("sample app")
config.option_all = true       # !!!! enable '-a, --all' option !!!!
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message (without -a nor --all):

[bash]$ ruby ex21.rb -h test1
ex21.rb test1 -- test action

Usage:
  $ ex21.rb test1 [<options>]

Options:
  -v                 : verbose mode

Help message (with -a or --all)

[bash]$ ruby ex21.rb -h --all test1           # !!!!
ex21.rb test1 -- test action

Usage:
  $ ex21.rb test1 [<options>]

Options:
  -v                 : verbose mode
  -D                 : debug mode             # !!!!

Configuratoin and Customization

Application Configuration

Benry::CmdApp::Config class configures application behaviour.

  • config.app_desc = "..." sets command description which is shown in help message. (required)
  • config.app_version = "1.0.0" enables -V and --version option, and prints version number if -V or --version option specified. (default: nil)
  • config.app_command = "<command>" sets command name which is shown in help message. (default: File.basname($0))
  • config.app_detail = "<text>" sets detailed description of command which is showin in help message. (default: nil)
  • config.default_action = "<action>" sets default action name. (default: nil)
  • config.default_help = true prints help message if no action names specified in command-line. (default: false)
  • config.option_help = true enables -h and --help options. (default: true)
  • config.option_all = true enables -a and --all options which shows private (hidden) actions and options into help message. (default: false)
  • config.option_verbose = true enables -v and --verbose options which sets $QUIET_MODE = false. (default: false)
  • config.option_quiet = true enables -q and --quiet options which sets $QUIET_MODE = true. (default: false)
  • config.option_color = true enables --color[=<on|off>] option which sets $COLOR_MODE = true/false. This affects to help message colorized or not. (default: false)
  • config.option_debug = true enables -D and --debug options which sets $DEBUG_MODE = true. (default: false)
  • config.option_trace = true enables -T and --trace options which sets $TRACE_MODE = true. Entering into and exitting from action are reported when trace mode is on. (default: false)
  • config.help_aliases = true adds Aliases: section in help message. (default: false)
  • config.help_sections = [["<title>", "<text>"], ...] adds section title and text into help message. (default: [])
  • config.help_postamble = "<text>" sets postamble text in help message, such as 'Examples:' or 'Tips:'. (default: nil)
  • config.feat_candidate = true enables feature to list action names starting with 'foo:' when action name specified in command-line is foo:. (default: true)
  • config.format_help = " %-18s : %s" sets format of options and actions in help message. (default: " \e[1m%-18s\e[0m : %s")
  • config.format_usage = " $ %s %s" sets format of usage in help message. (default: " $ \e[1m%s\e[0m %s")
  • config.format_heading = "[%s]" sets format of heading in help message. (default: "\e[34m%s\e[0m")

File: ex22.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

config = Benry::CmdApp::Config.new("sample app", "1.0.0")
#config.default_help = true

config.class.instance_methods(false).each do |name|
  next if name =~ /=$/
  next if ! config.class.method_defined?("#{name}=")
  val = config.__send__(name)
  puts "%-25s = %s" % ["config.#{name}", val.inspect]
end

Output:

[bash]$ ruby ex22.rb
config.app_desc           = "sample app"
config.app_version        = "1.0.0"
config.app_name           = "ex22.rb"
config.app_command        = "ex22.rb"
config.app_detail         = nil
config.default_action     = nil
config.default_help       = false
config.option_help        = true
config.option_all         = false
config.option_verbose     = false
config.option_quiet       = false
config.option_color       = false
config.option_debug       = false
config.option_trace       = false
config.help_aliases       = false
config.help_sections      = []
config.help_postamble     = nil
config.feat_candidate     = true
config.format_help        = "  \e[1m%-18s\e[0m : %s"
config.format_usage       = "  $ \e[1m%s\e[0m %s"
config.format_heading     = "\e[34m%s\e[0m"
config.format_appname     = "\e[1m%s\e[0m"

Customization of Global Options

To add custom global options:

  • (1) Create global option schema object.
  • (2) Add custom options to it.
  • (3) Pass it to Application.new().

File: ex23.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action")
  def test1()
    puts __method__
  end

end

## (1) create global option shema
config = Benry::CmdApp::Config.new("sample app")
schema = Benry::CmdApp::AppOptionSchema.new(config)  # !!!!

## (2) add custom options to it
schema.add(:logging, "--logging", "enable logging")    # !!!!

## (3) pass it to ``Application.new()``
app = Benry::CmdApp::Application.new(config, schema)   # !!!!

exit app.main()

Help message:

[bash]$ ruby ex23.rb -h
ex23.rb -- sample app

Usage:
  $ ex23.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)
  --logging          : enable logging          # !!!!

Actions:
  test1              : test action

To customize global options entirely:

  • (1) Create empty AppOptionSchema object.
  • (2) Add global options as you want.
  • (3) Create and execute Application object with it.

File: ex24.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

## (1) Create empty ``AppOptionSchema`` object.
schema = Benry::CmdApp::AppOptionSchema.new(nil)   # !!!!

## (2) Add global options as you want.
schema.add(:help   , "-h, --help"   , "print help message")
schema.add(:version, "-V, --version", "print version")
schema.add(:all    , "-a, --all"    , "list all actions/options")
schema.add(:verbose, "-v, --verbose", "verbose mode")
schema.add(:quiet  , "-q, --quiet"  , "quiet mode")
schema.add(:color  , "--color[=<on|off>]", "enable/disable color", type: TrueClass)
schema.add(:debug  , "-D, --debug"  , "set $DEBUG_MODE to true")
schema.add(:trace  , "-T, --trace"  , "report enter into and exit from action")

## (3) Create and execute Application object with it.
config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config, schema)  # !!!!
exit app.main()

Customization of Global Option Behaviour

  • (1) Define subclass of Application class.
  • (2) Override #do_toggle_global_switches() method.
  • (3) Create and execute subclass object of Application.

File: ex25.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

## (1) Define subclass of ``Application`` class.
class MyApplication < Benry::CmdApp::Application

  ## (2) Override ``#do_toggle_global_switches()`` method.
  def do_toggle_global_switches(_args, global_opts)
    super
    ## here is original behaviour
    #global_opts.each do |key, val|
    #  case key
    #  when :verbose ; $QUIET_MODE = ! val
    #  when :quiet   ; $QUIET_MODE = val
    #  when :color   ; $COLOR_MODE = val
    #  when :debug   ; $DEBUG_MODE = val
    #  when :trace   ; $TRACE_MODE = val
    #  else          ; # do nothing
    #  end
    #end
  end

end

## (3) Create and execute subclass object of ``Application``.
config = Benry::CmdApp::Config.new("sample app")
app = MyApplication.new(config)            # !!!!
exit app.main()

Of course, prepending custom module to Application class is also effective way.

File: ex26.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

module MyApplicationMod

  def do_toggle_global_switches(_args, global_opts)
    # ....
  end

end

Benry::CmdApp::Application.prepend(MyApplicationMod)   # !!!!

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Custom Hook of Application

  • (1) Define subclass of Application class.
  • (2) Override callback method.
  • (3) Create and execute custom application object.

File: ex27.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action")
  def test1()
    $logger.info("logging message") if $logger
  end

end

## (1) Define subclass of Application class
class MyApplication < Benry::CmdApp::Application   # !!!!

  ## (2) Override callback method
  def do_callback(args, global_opts)               # !!!!
    #p @config
    #p @schema
    if global_opts[:logging]
      require 'logger'
      $logger = Logger.new(STDOUT)
    end
    ## if return :SKIP, action skipped (not invoked).
    #return :SKIP
  end

  ## or:
  #def do_handle_global_options(args, global_opts)
  #  if global_opts[:logging]
  #    require 'logger'
  #    $logger = Logger.new(STDOUT)
  #  end
  #  super
  #end

end

## (3) create and execute custom application object
config = Benry::CmdApp::Config.new("sample app")
schema = Benry::CmdApp::AppOptionSchema.new(config)
schema.add(:logging, "--logging", "enable logging")
app = MyApplication.new(config, schema)             # !!!!
exit app.main()
  • [EXPERIMENTAL] Instead of defining subclass of Application, you can pass callback block to Application object.

File: ex28.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action")
  def test1()
    $logger.info("logging message") if $logger
  end

end

config = Benry::CmdApp::Config.new("sample app")
schema = Benry::CmdApp::AppOptionSchema.new(config)
schema.add(:logging, "--logging", "enable logging")
app = Benry::CmdApp::Application.new(config, schema) do   # !!!!
  |args, global_opts, config|                             # !!!!
  if global_opts[:logging]                                # !!!!
    require 'logger'                                      # !!!!
    $logger = Logger.new(STDOUT)                          # !!!!
  end                                                     # !!!!
  #:SKIP                                                  # !!!!
end                                                       # !!!!
exit app.main()

Customization of Command Help Message

If you want to just add more text into command help message, set config.app_detail, config.help_sections, and/or config.help_postamble.

File: ex29.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action #1")
  def hello(user="world")
    puts "Hello, #{user}!"
  end

end

config = Benry::CmdApp::Config.new("sample app")
config.app_detail = "Document: https://...."      # !!!!
config.help_sections = [                          # !!!!
  ["Example:", "  $ <command> hello Alice"],      # !!!!
]                                                 # !!!!
config.help_postamble = "(Tips: ....)"            # !!!!
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex29.rb -h
ex29.rb -- sample app

Document: https://....              # !!!! app.detail !!!!

Usage:
  $ ex29.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  hello              : test action #1

Example:                            # !!!! help_sections !!!!
  $ <command> hello Alice           # !!!! help_sections !!!!

(Tips: ....)                        # !!!! help_postamble !!!!

If you want to change behaviour of building command help message:

  • (1) Define subclass of Benry::CmdApp::AppHelpBuilder class.
  • (2) Override methods.
  • (3) Create an instance object of the class.
  • (4) Pass it to Application object.

File: ex30.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("greeting message")
  def hello(user="world")
    puts "Hello, #{user}!"
  end

end

## (1) Define subclass of ``Benry::CmdApp::AppHelpBuilder`` class.
class MyAppHelpBuilder < Benry::CmdApp::AppHelpBuilder

  ## (2) Override methods.
  def build_help_message(all=false, format=nil)
    super
  end
  def build_preamble(all=false)
    super
  end
  def build_usage(all=false)
    super
  end
  def build_options(all=false, format=nil)
    super
  end
  def build_actions(all=false, format=nil)
    super
  end
  def build_postamble(all=false)
    super
  end
  def heading(str)
    super
  end
end

## (3) Create an instance object of the class.
config = Benry::CmdApp::Config.new("sample app")
schema = Benry::CmdApp::AppOptionSchema.new(config)
schema.add(:logging, "--logging", "enable logging")
help_builder = MyAppHelpBuilder.new(config, schema)     # !!!!

## (4) Pass it to Application object.
app = Benry::CmdApp::Application.new(config, schema, help_builder) # !!!!
exit app.main()

More simple way:

  • (1) Create a module and override methods of Benry::CmdApp::AppHelpBuilder class.
  • (2) Prepend it to Benry::CmdApp::AppHelpBuilder class.
  • (3) Create and execute Application object.

File: ex31.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("greeting message")
  def hello(user="world")
    puts "Hello, #{user}!"
  end

end

## (1) Create a module and override methods of ``AppHelpBuilder`` class.
module MyHelpBuilderMod
  def build_help_message(all=false, format=nil)
    super
  end
  def build_preamble(all=false)
    super
  end
  def build_usage(all=false)
    super
  end
  def build_options(all=false, format=nil)
    super
  end
  def build_actions(all=false, format=nil)
    super
  end
  def build_postamble(all=false)
    super
  end
  def heading(str)
    super
  end
end

## (2) Prepend it to ``Benry::CmdApp::AppHelpBuilder`` class.
Benry::CmdApp::AppHelpBuilder.prepend(MyHelpBuilderMod)

## (3) Create and execute Application object.
config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Customization of Action Help Message

If you want to just add more text into action help message, pass detail: and/or postamble: keyword arguments to @action.().

File: ex32.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action #1",
           detail: "Document: https://....",      # !!!!
           postamble: "(Tips: ....)")             # !!!!
  def hello(user="world")
    puts "Hello, #{user}!"
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex32.rb -h
ex32.rb hello -- test action #1

Document: https://....                  # !!!!

Usage:
  $ ex32.rb hello [<user>]

(Tips: ....)                            # !!!!

If you want to change behaviour of building action help message:

  • (1) Create a module and override methods of Benry::CmdApp::ActionHelpBuilder class.
  • (2) Prepend it to Benry::CmdApp::ActionHelpBuilder class.
  • (3) Create and execute Application object.

File: ex33.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("greeting message")
  def hello(user="world")
    puts "Hello, #{user}!"
  end

end

## (1) Create a module and override methods of ``ActionHelpBuilder`` class.
module MyActionHelpBuilderMod
  def build_help_message(command, all=false)
    super
  end
  def build_preamble(command, all=false)
    super
  end
  def build_usage(command, all=false)
    super
  end
  def build_options(command, all=false)
    super
  end
  def build_postamble(command, all=false)
    super
  end
  def heading(str)
    super
  end
end

## (2) Prepend it to ``Benry::CmdApp::ActionHelpBuilder`` class.
Benry::CmdApp::ActionHelpBuilder.prepend(MyActionHelpBuilderMod)  # !!!!

## (3) Create and execute Application object.
config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Another way:

  • (1) Define subclass of ActionHelpBuilder class.
  • (2) Set it to ACTION_HELP_BUILDER_CLASS constant value.
  • (3) Create and execute Application object.

File: ex34.rb

#!/usr/bin/env ruby
require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("greeting message")
  def hello(user="world")
    puts "Hello, #{user}!"
  end

end

## (1) Define subclass of ``ActionHelpBuilder`` class.
class MyActionHelpBuilder < Benry::CmdApp::ActionHelpBuilder
  def build_help_message(command, all=false)
    super
  end
  def build_preamble(command, all=false)
    super
  end
  def build_usage(command, all=false)
    super
  end
  def build_options(command, all=false)
    super
  end
  def build_postamble(command, all=false)
    super
  end
  def heading(str)
    super
  end
end

## (2) Set it to ``ACTION_HELP_BUILDER_CLASS`` constant value.
Benry::CmdApp.module_eval do
  remove_const :ACTION_HELP_BUILDER_CLASS
  const_set :ACTION_HELP_BUILDER_CLASS, MyActionHelpBuilder
end

## (3) Create and execute Application object.
config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Q & A

Q: How to Append Some Tasks to Existing Action?

A: (a) Use method alias, or (b) use prepend.

File: ex41.rb

require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action #1")
  def hello(user="world")
    puts "Hello, #{user}!"
  end

  @action.("test action #2")
  def hi(user="world")
    puts "Hi, #{user}!"
  end

end

## (a) use method alias
class SampleAction               # open existing class
  alias __old_hello hello        # alias of existing method
  def hello(user="world")        # override existing method
    puts "---- >8 ---- >8 ----"
    __old_hello(user)            # call original method
    puts "---- 8< ---- 8< ----"
  end
end

## (b) use prepend
module SampleMod                 # define new module
  def hi(user="world")           # override existing method
    puts "~~~~ >8 ~~~~ >8 ~~~~"
    super                        # call original method
    puts "~~~~ 8< ~~~~ 8< ~~~~"
  end
end
SampleAction.prepend(SampleMod)  # prepend it to existing class

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex41.rb hello
---- >8 ---- >8 ----
Hello, world!
---- 8< ---- 8< ----

[bash]$ ruby ex41.rb hi Alice
~~~~ >8 ~~~~ >8 ~~~~
Hi, Alice!
~~~~ 8< ~~~~ 8< ~~~~

Q: How to Re-define Existing Action?

A: Remove existing action at first, and re-define action.

File: ex42.rb

require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("sample action")
  def hello()                               # !!!!
    puts "Hello, world!"
  end

end

Benry::CmdApp.delete_action("hello")        # !!!!

class OtherAction < Benry::CmdApp::Action

  @action.("other action")                  # !!!!
  def hello()                               # !!!!
    puts "Ciao, world!"
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex42.rb -h
ex42.rb -- sample app

Usage:
  $ ex42.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  hello              : other action       # !!!!

Q: How to Delete Existing Action/Alias?

A: Call Benry::CmdApp.delete_action("<action>") or Benry::CmdApp.delete_alias("<alias>").

Q: How to Show Entering Into or Exitting From Action?

A: Set config.option_trace = true and pass -T (or --trace) option.

File: ex43.rb

require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("preparation")
  def prepare()
    puts "... prepare something ..."
  end

  @action.("build")
  def build()
    run_action_once("prepare")
    puts "... build something ..."
  end

end

config = Benry::CmdApp::Config.new("sample app")
config.option_trace = true                          # !!!!
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex43.rb -T build           # !!!!
## enter: build
## enter: prepare
... prepare something ...
## exit:  prepare
... build something ...
## exit:  build

Q: How to Enable/Disable Color Mode?

A: Set config.option_color = true and pass --color=on or --color=off option.

File: ex44.rb

require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("greeting message")
  def hello(user="world")
    puts "Hello, #{user}!"
  end

end

config = Benry::CmdApp::Config.new("sample app")
config.option_color = true                       # !!!!
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex44.rb -h
ex44.rb -- sample app

Usage:
  $ ex44.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)
  --color[=<on|off>] : enable/disable color      # !!!!

Actions:
  hello              : greeting message

[bash]$ ruby ex44.rb -h --color=off              # !!!!

[bash]$ ruby ex44.rb -h --color=on               # !!!!

Q: How to Define Multiple Option, like -I Option of Ruby?

A: Provide block parameter on @option.().

File: ex45.rb

require 'benry/cmdapp'

class TestAction < Benry::CmdApp::Action

  @action.("multiple option test")
  @option.(:path, "-I <path>", "path") {|options, key, val|  # !!!!
    arr = options[key] || []                                 # !!!!
    arr << val                                               # !!!!
    arr                                                      # !!!!
    ## or:                                                   # !!!!
    #(options[key] || []) << val                             # !!!!
  }                                                          # !!!!
  def test(path: [])
    puts "path=#{path.inspect}"     #=> path=["/tmp", "/var/tmp"]
  end

end

config = Benry::CmdApp::Config.new("test app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Output:

[bash]$ ruby ex45.rb test -I /tmp -I /var/tmp     # !!!!
path=["/tmp", "/var/tmp"]                         # !!!!

Q: How to Specify Detailed Description of Option?

A: Add detail: keyword argument to @option.().

File: ex46.rb

require 'benry/cmdapp'

class TestAction < Benry::CmdApp::Action

  @action.("detailed description test")
  @option.(:mode, "-m <mode>", "output mode", detail: <<"END")
    v, verbose: print many output
    q, quiet:   print litte output
    c, compact: print summary output
END
  def test(mode: nil)
    puts "mode=#{mode.inspect}"
  end

end

config = Benry::CmdApp::Config.new("test app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex46.rb -h test
ex46.rb test -- detailed description test

Usage:
  $ ex46.rb test [<options>]

Options:
  -m <mode>          : output mode
                           v, verbose: print many output
                           q, quiet:   print litte output
                           c, compact: print summary output

Q: How to Copy All Options from Other Action?

A: Use @copy_options.().

File: ex47.rb

require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("test action #1")
  @option.(:verbose, "-v, --verbose", "verbose mode")
  @option.(:file, "-f, --file=<file>", "filename")
  @option.(:indent, "-i, --indent[=<N>]", "indent")
  def test1(verbose: false, file: nil, indent: nil)
    puts "verbose=#{verbose}, file=#{file}, indent=#{indent}"
  end

  @action.("test action #2")
  @copy_options.("test1")         # !!!! copy options from test1 !!!!
  @option.(:debug, "-D, --debug", "debug mode")
  def test2(verbose: false, file: nil, indent: nil, debug: false)
    puts "verbose=#{verbose}, file=#{file}, indent=#{indent}, debug=#{debug}"
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message of test2 action:

[bash]$ ruby ex47.rb -h test2
ex47.rb test2 -- test action #2

Usage:
  $ ex47.rb test2 [<options>]

Options:
  -v, --verbose      : verbose mode     # copied!!
  -f, --file=<file>  : filename         # copied!!
  -i, --indent[=<N>] : indent           # copied!!
  -D, --debug        : debug mode

Q: What is the Difference Between prefix(alias_of:) and prefix(action:)?

A: The former defines an alias, and the latter doesn't.

File: ex48.rb

require 'benry/cmdapp'

class AaaAction < Benry::CmdApp::Action
  prefix "aaa", alias_of: :print_        # (or) alias_of: "print"

  @action.("test #1")
  def print_()
    puts "test"
  end

end

class BbbAction < Benry::CmdApp::Action
  prefix "bbb", action: :print_         # (or) action: "print"

  @action.("test #2")
  def print_()
    puts "test"
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex48.rb
ex48.rb -- sample app

Usage:
  $ ex48.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  aaa                : alias of 'aaa:print' action    # !!!!
  aaa:print          : test #1
  bbb                : test #2                        # !!!!

In the above example, alias aaa is defined due to prefix(alias_of:), and action bbb is not an alias due to prefix(action:).

Q: How to Change Order of Options in Help Message?

A: Call AppOptionSchema#sort_options_in_this_order().

File: ex49.rb

require 'benry/cmdapp'

config = Benry::CmdApp::Config.new("sample app", "1.0.0",
  option_all:       true,
  option_quiet:     true,
  option_color:     true,
)
schema = Benry::CmdApp::AppOptionSchema.new(config)
keys = [:all, :quiet, :color, :help, :version]         # !!!!
schema.sort_options_in_this_order(*keys)               # !!!!
app = Benry::CmdApp::Application.new(config, schema)
## or:
#app = Benry::CmdApp::Application.new(config)
#app.schema.sort_options_in_this_order(*keys)          # !!!!
exit app.main()

Help message:

[bash]$ ruby ex49.rb -h
ex49.rb (1.0.0) -- sample app

Usage:
  $ ex49.rb [<options>] [<action> [<arguments>...]]

Options:
  -a, --all          : list all actions/options including private (hidden) ones
  -q, --quiet        : quiet mode
  --color[=<on|off>] : enable/disable color
  -h, --help         : print help message (of action if action specified)
  -V, --version      : print version

Actions:

Q: Is It Possible to Make Action Names Emphasised or Weaken?

A: Yes. When you pass important: true to @action.(), that action will be printed with unerline in help message. When you pass important: false, that action will be printed in gray color.

File: ex50.rb

require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("empasized", important: true)   # !!!!
  def test1()
  end

  @action.("weaken", important: false)   # !!!!
  def test2()
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Help message:

[bash]$ ruby ex50.rb -h
ex50.rb -- sample app

Usage:
  $ ex50.rb [<options>] [<action> [<arguments>...]]

Options:
  -h, --help         : print help message (of action if action specified)

Actions:
  test1              : empasized     # !!!! printed with underline !!!!
  test2              : weaken        # !!!! printed in gray color !!!!

Q: Is It Possible to Add Metadata to Action or Option?

A: Yes. Pass tag: keyword argument to @action.() or @option.().

  • tag: keyword argument accept any type of value such as symbol, string, array, and so on.
  • Currenty, Benry::CmdApp doesn't provide the good way to use it effectively. This feature may be used by command-line application or framework based on Benry::CmdApp.

File: ex51.rb

require 'benry/cmdapp'

class SampleAction < Benry::CmdApp::Action

  @action.("print greeting message", tag: :important)            # !!!!
  @option.(:repeat, "-r <N>", "repeat N times", tag: :important) # !!!!
  def hello(user="world", repeat: nil)
    (repeat || 1).times do
      puts "Hello, #{user}!"
    end
  end

end

config = Benry::CmdApp::Config.new("sample app")
app = Benry::CmdApp::Application.new(config)
exit app.main()

Q: How to Make Error Messages I18Ned?

A: Currently not supported. May be supported in the future release.