No Subcommmands
This example demonstrates using Executioner to create a simple command line interface without subcommands.
require 'executioner'
class NoSubCommandCLI < Executioner
attr :result
def o?
@o
end
def o=(flag)
@o = flag
end
def main
if o?
@result = "with"
else
@result = "without"
end
end
end
Execute the CLI on an example command line.
cli = NoSubCommandCLI.run('')
cli.result.assert == 'without'
Execute the CLI on an example command line.
cli = NoSubCommandCLI.run('-o')
cli.result.assert == 'with'
There are two important things to notices heres. Frist, that #main is being called in each case. It is the method called with no other subcommands are defined. And second, the fact the a `o?` method is defined to compliment the `o=` writer, informs Executioner that `-o` is an option flag, not taking any parameters.
Multiple Subcommmands
Require Executioner class.
require 'executioner'
Setup an example CLI subclass.
class MyCLI < Executioner
attr :result
def initialize
@result = []
end
def g=(value)
@result << "g" if value
end
def g?
@result.include?("g")
end
class C1 < self
def main
@result << "c1"
end
def o1=(value)
@result << "c1_o1 #{value}"
end
def o2=(value)
@result << "c1_o2 #{value}"
end
end
class C2 < Executioner
attr :result
def initialize
@result = []
end
def main
@result << "c2"
end
def o1=(value)
@result << "c2_o1 #{value}"
end
def o2=(value)
@result << "c2_o2" if value
end
def o2?
@result.include?("c2_o2")
end
end
end
Instantiate and run the class on an example command line.
Just a command.
cli = MyCLI.run('c1')
cli.result.assert == ['c1']
Command with global option.
cli = MyCLI.run('c1 -g')
cli.result.assert == ['g', 'c1']
Command with an option.
cli = MyCLI.run('c1 --o1 A')
cli.result.assert == ['c1_o1 A', 'c1']
Command with two options.
cli = MyCLI.run('c1 --o1 A --o2 B')
cli.result.assert == ['c1_o1 A', 'c1_o2 B', 'c1']
Try out the second command.
cli = MyCLI.run('c2')
cli.result.assert == ['c2']
Seoncd command with an option.
cli = MyCLI.run('c2 --o1 A')
cli.result.assert == ['c2_o1 A', 'c2']
Second command with two options.
cli = MyCLI.run('c2 --o1 A --o2')
cli.result.assert == ['c2_o1 A', 'c2_o2', 'c2']
Since C1#main takes not arguments, if we try to issue a command that will have left over arguments, then an ArgumentError will be raised.
expect ArgumentError do
cli = MyCLI.run('c1 a')
end
How about a non-existenct subcommand.
expect Executioner::NoCommandError do
cli = MyCLI.run('q')
cli.result.assert == ['q']
end
How about an option only.
expect Executioner::NoCommandError do
cli = MyCLI.run('-g')
cli.result.assert == ['-g']
end
How about a non-existant options.
expect Executioner::NoOptionError do
MyCLI.run('c1 --foo')
end
RDoc Example
This example mimics the one given in optparse.rb documentation.
require 'executioner'
require 'ostruct'
class ExampleCLI < Executioner
CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
attr :options
def initialize
super
reset
end
def reset
@options = OpenStruct.new
@options.library = []
@options.inplace = false
@options.encoding = "utf8"
@options.transfer_type = :auto
@options.verbose = false
end
help "Require the LIBRARY before executing your script"
def require=(lib)
options.library << lib
end
alias :r= :require=
help "Edit ARGV files in place (make backup if EXTENSION supplied)"
def inplace=(ext)
options.inplace = true
options.extension = ext
options.extension.sub!(/\A\.?(?=.)/, ".") # ensure extension begins with dot.
end
alias :i= :inplace=
help "Delay N seconds before executing"
# Cast 'delay' argument to a Float.
def delay=(n)
options.delay = n.to_float
end
help "Begin execution at given time"
# Cast 'time' argument to a Time object.
def time=(time)
options.time = Time.parse(time)
end
alias :t= :time=
help "Specify record separator (default \\0)"
# Cast to octal integer.
def irs=(octal)
options.record_separator = octal.to_i(8)
end
alias :F= :irs=
help "Example 'list' of arguments"
# List of arguments.
def list=(args)
options.list = list.split(',')
end
# Keyword completion. We are specifying a specific set of arguments (CODES
# and CODE_ALIASES - notice the latter is a Hash), and the user may provide
# the shortest unambiguous text.
CODE_LIST = (CODE_ALIASES.keys + CODES)
help "Select encoding (#{CODE_LIST})"
def code=(code)
codes = CODE_LIST.select{ |x| /^#{code}/ =~ x }
codes = codes.map{ |x| CODE_ALIASES.key?(x) ? CODE_ALIASES[x] : x }.uniq
raise ArgumentError unless codes.size == 1
options.encoding = codes.first
end
help "Select transfer type (text, binary, auto)"
# Optional argument with keyword completion.
def type=(type)
raise ArgumentError unless %w{text binary auto}.include(type.downcase)
options.transfer_type = type.downcase
end
help "Run verbosely"
# Boolean switch.
def verbose=(bool)
options.verbose = bool
end
def verbose?
@options.verbose
end
alias :v= :verbose=
alias :v? :verbose?
help "Show this message"
# No argument, shows at tail. This will print an options summary.
def help!
puts help_text
exit
end
alias :h! :help!
help "Show version"
# Another typical switch to print the version.
def version?
puts Executor::VERSION
exit
end
def main
# ... main procedure here ...
end
end
We will run some scenarios on this example to make sure it works.
cli = ExampleCLI.execute('-r=facets')
cli.options.library.assert == ['facets']
Make sure time option parses.
cli = ExampleCLI.execute('--time=2010-10-10')
cli.options.time.assert == Time.parse('2010-10-10')
Make sure code lookup words and is limted to the selections provided.
cli = ExampleCLI.execute('--code=ji')
cli.options.encoding.assert == 'iso-2022-jp'
expect ArgumentError do
ExampleCLI.execute('--code=xxx')
end
Ensure irs is set to an octal number.
cli = ExampleCLI.execute('-F 32')
cli.options.record_separator.assert == 032
Ensure extension begins with dot and inplace is set to true.
cli = ExampleCLI.execute('--inplace txt')
cli.options.extension.assert == '.txt'
cli.options.inplace.assert == true
Command Help
Require Executor library.
require 'executioner'
Setup an example CLI subclass.
class MyCLI < Executioner
help "This is global option -g."
def g=(val)
end
# help "This does c1."
class C1 < self
header "This does c1."
help "This is option --o1 for c1."
def o1=(value)
end
help "This is option --o2 for c1."
def o2=(value)
end
end
# help "This does c2."
class C2 < self
help "This is option --o1 for c2."
def o1=(value)
end
help "This is option --o2 for c2."
def o2=(bool)
end
end
end
The help output,
@out = MyCLI::C1.to_s
should be clearly laid out as follows:
qed
This does c1.
OPTIONS:
-g This is global option -g.
--o1 This is option --o1 for c1.
--o2 This is option --o2 for c1.