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.