= Executable
Require Executable library.
require 'executable'
== No Subcommmands
This example demonstrates using Executable::Command to create a simple command line interface without subcommands. (Note the Executable mixin could be used just as well).
class NoSubCommandCLI < Executable::Command
attr :result
def o?
@o
end
def o=(flag)
@o = flag
end
def call
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 Executable that -o
is an option flag, not taking
any parameters.
== Multiple Subcommmands
Setup an example CLI subclass.
class MyCLI < Executable::Command
attr :result
def initialize
@result = []
end
def g=(value)
@result << "g" if value
end
def g?
@result.include?("g")
end
#
class C1 < self
def call
@result << "c1"
end
def o1=(value)
@result << "c1_o1 #{value}"
end
def o2=(value)
@result << "c1_o2 #{value}"
end
end
#
class C2 < Executable::Command
attr :result
def initialize
@result = []
end
def call
@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 NotImplementedError do
cli = MyCLI.run('q')
cli.result.assert == ['q']
end
How about an option only.
expect NotImplementedError do
cli = MyCLI.run('-g')
cli.result.assert == ['-g']
end
How about a non-existant options.
expect Executable::NoOptionError do
MyCLI.run('c1 --foo')
end
== Command Help
Executable Commands can generate help output. It does this by extracting
the commenst associated with the option methods. A description of the
command itself is taken from the comment on the #call
method. Only
the first line of a comment is used, so the reset of the comment can
still be catered to documention tools such as YARD and RDoc.
Let's setup an example CLI subclass to demonstrate this.
class MyCLI < Executable::Command
# This is global option -g.
# Yadda yadda yadda...
def g=(bool)
@g = bool
end
def g?; @g; end
# Subcommand `c1`.
class C1 < self
# This does c1.
def call(*args)
end
# This is option --o1 for c1.
def o1=(value)
end
# This is option --o2 for c1.
def o2=(value)
end
end
# Subcommand `c2`.
class C2 < self
# This does c2.
def call(*args)
end
# This is option --o1 for c2.
def o1=(value)
end
# This is option --o2 for c2.
def o2=(value)
end
end
end
=== Plain Text
The help output,
@out = MyCLI::C1.help.to_s
should be clearly laid out as follows:
Usage: mycli-c1 [options...] [subcommand]
This does c1.
OPTIONS
-g This is global option -g.
--o1=VALUE This is option --o1 for c1.
--o2=VALUE This is option --o2 for c1.
Copyright (c) 2012
=== Markdown
The help feature can also output ronn-style markdown,
@out = MyCLI::C1.help.markdown
should be clearly laid out as follows:
mycli-c1(1) - This does c1.
===========================
## SYNOPSIS
`mycli-c1` [options...] [subcommand]
## DESCRIPTION
This does c1.
## OPTIONS
* `-g`:
This is global option -g.
* `--o1=VALUE`:
This is option --o1 for c1.
* `--o2=VALUE`:
This is option --o2 for c1.
## COPYRIGHT
Copyright (c) 2012
== Manpage
If a man page is available for a given command using the #show_help method will automatically find the manpage and display it.
sample = File.dirname(__FILE__) + '/samples'
load(sample + '/bin/hello')
manpage = Hello.cli.manpage
manpage.assert == sample + '/man/hello.1'
= Subclass Example
Lets say we have a class that we would like to work with on the command line, but want to keep the class itself unchanaged without mixin.
class Hello
attr_accessor :name
def initialize(name="World")
@name = name
end
def hello
@output = "Hello, #{name}!"
end
def output
@output
end
end
Rather then including Exectuable in the class directly, we can create a subclass and use it instead.
class HelloCommand < Hello
include Executable
def call(*args)
hello
end
end
Now we can execute the command perfectly well.
cmd = HelloCommand.execute(['hello', '--name=Fred'])
cmd.output.assert == "Hello, Fred!"
And the original class remains undisturbed.
= README Example
This is the example used in the documentation.
class Example
include Executable
attr_switch :quiet
def bread(*args)
["bread", quiet?, *args]
end
def butter(*args)
["butter", quiet?, *args]
end
# Route call to methods.
def call(name, *args)
meth = public_method(name)
meth.call(*args)
end
end
Use a subcommand and an argument.
c, a = Example.parse(['butter', 'yum'])
r = c.call(*a)
r.assert == ["butter", nil, "yum"]
A subcommand and a boolean option.
c, a = Example.parse(['bread', '--quiet'])
r = c.call(*a)
r.assert == ["bread", true]
== Legacy/Dispath
The Dispatch mixin, which is also called Legacy b/c this is how older
version of Executable worked, provides Executable with a #call
method
that automatically routes the to a method given by the first argument.
class DispatchExample < Executable::Command include Legacy
attr :result
def foo
@result = :foo
end
def
@result = :bar
end
end
Now when we invoke the command, the
eg = DispatchExample.run('foo') eg.result.assert == :foo
eg = DispatchExample.run('bar') eg.result.assert == :bar
== OptionParser Example
This example mimics the one given in optparse.rb documentation.
require 'ostruct'
require 'time'
class ExampleCLI < Executable::Command
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
# Require the LIBRARY before executing your script
def require=(lib)
.library << lib
end
alias :r= :require=
# Edit ARGV files in place (make backup if EXTENSION supplied)
def inplace=(ext)
.inplace = true
.extension = ext
.extension.sub!(/\A\.?(?=.)/, ".") # ensure extension begins with dot.
end
alias :i= :inplace=
# Delay N seconds before executing
# Cast 'delay' argument to a Float.
def delay=(n)
.delay = n.to_float
end
# Begin execution at given time
# Cast 'time' argument to a Time object.
def time=(time)
.time = Time.parse(time)
end
alias :t= :time=
# Specify record separator (default \\0)
# Cast to octal integer.
def irs=(octal)
.record_separator = octal.to_i(8)
end
alias :F= :irs=
# Example 'list' of arguments
# List of arguments.
def list=(args)
.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.option(:code, "Select encoding (#{CODE_LIST})")
# Select encoding
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
.encoding = codes.first
end
# Select transfer type (text, binary, auto)
# Optional argument with keyword completion.
def type=(type)
raise ArgumentError unless %w{text binary auto}.include(type.downcase)
.transfer_type = type.downcase
end
# Run verbosely
# Boolean switch.
def verbose=(bool)
.verbose = bool
end
def verbose?
@options.verbose
end
alias :v= :verbose=
alias :v? :verbose?
# Show this message
# No argument, shows at tail. This will print an options summary.
def help!
puts help_text
exit
end
alias :h! :help!
# Show version
# Another typical switch to print the version.
def version?
puts Executor::VERSION
exit
end
#
def call
# ... 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