# Como
#
# = Introduction
#
# Como provides low manifest command line option parsing and
# deployment. The command line options are described in compact table
# format and option values are stored to conveniently named
# properties. Como builds command usage information based on the
# option table (+ generic program info) and displays it automatically
# if necessary. Como supports also subcommands and checking for option
# combinations using a simple DSL.
#
#
#
# = Usage Examples
#
# Two simple examples are presented in this section. First one
# includes a straight forward command definition and the second is a
# bit more complicated with subcommand feature in use.
#
# == Simple example
#
# Below is a small example program ("como_simple") that demonstrates
# typical usage.
#
# === Program listing
#
#  require "como"
#  include Como
#
#  # Define command line arguments:
#  Spec.command( "como_simple", "Programmer", "2013",
#    [
#      [ :single, "file",  "-f", "File argument." ],
#      [ :switch, "debug", "-d", "Enable debugging." ],
#    ] )
#
#  puts "  File option: #{Opt['file'].value}"
#  puts "  Debugging selected!" if Opt['debug'].given
#
#
# First Como is required and Como module is included.
#
# {Spec.command} method takes 4 arguments:
# [progname] Name of the program (or command).
# [author] Author of the program.
# [year] Year (or any date) for the program.
# [option table] Description of the command options.
#
# Each option table entry (row/sub-array) includes 4 fields and
# specifies one option:
#   [ type, name, mnemonic, doc ]
#
# Two different types are present in the example:
# [:single] Single means that the option requires one argument (and
#           only one).
# [:switch] Switch is an optional flag (default value is false).
#
# Option name is used to reference the option value that user has
# given.  The command line option values are stored automatically. For
# example the file option value is returned by:
#   Opt['file'].value
# The option name also doubles as long option format, i.e. one could
# use "--file <filename>" on the command line.
#
# Existence of optional options can be tested using the {Opt#given}
# method. For example
#   Opt['debug'].given
# would return "true" if "-d" was given on the command line.
#
# Mnemonic is the short form option specification e.g. "-f". If short
# form is replaced with "nil", the long option format is only
# available.
#
# Doc includes documentation for the option. It is displayed when
# "help" ("-h") option is given. Help option is added to the command
# automatically as default behavior.
#
# === Simple example executions
#
# Normal behavior would be achieved by executing:
#   shell> como_simple -f example -d
#
# The program would execute with the following output:
#    File option: example
#    Debugging selected!
#
# Same output would be achieved with:
#   shell> como_simple --file example --debug
#
# Since option name doubles as long option.
#
# Como includes certain "extra" behavior out-of-box. Required
# arguments are checked for existence and error is displayed if
# arguments are not given.
#
# For example given the command:
#   shell> como_simple
#
# The following is displayed on the screen:
#
#  como_simple error: Option "-f" missing for "como_simple"...
#
#    como_simple -f <file> [-d]
#
#    -f          File argument.
#    -d          Enable debugging.
#
#
#    Copyright (c) 2013 by Programmer
#
#
# Missing option error is displayed since "file" is a mandatory
# option. The error message is followed by "usage" display (Usage
# Help). Documentation string is taken from the option specification to
# "usage" display.
#
# Given the command:
#   shell> como_simple -h
#
# would display the same "usage" screen except without the error
# line.
#
# == Subcommand example
#
# Subcmd example includes a program which has subcommands. Subcommands
# can have their own command line switches and options.
#
# === Program listing
#
#  require "como"
#  include Como
#
#  Spec.program( "Programmer", "2013" ) do
#
#      command( "como_subcmd", [
#         [ :subcmd,      "add",       nil,  "Add file." ],
#         [ :subcmd,      "rm",        nil,  "Remove file." ],
#        ], )
#
#      subcmd( "add", [
#         [ :switch,      "force",     "-fo", "Force operation." ],
#         [ :opt_single,  "password",  "-p",  "User password." ],
#         [ :opt_single,  "username",  "-u",  "Username." ],
#         [ :single,      "file",      "-f",  "File." ],
#        ] )
#
#      check do
#          one(
#              '-fo',
#              all( 'password', 'username' )
#              )
#      end
#
#      subcmd( "rm", [
#         [ :single,  "file",      "-f",  "File." ],
#        ] )
#
#  end
#
#  subcmd = Opt.main.givenSubcmd
#
#  case subcmd.name
#  when 'add'; puts "  Adding file \"#{subcmd['file'].value}\"..."
#  when 'rm';  puts "  Removing file \"#{subcmd['file'].value}\"..."
#  end
#
# {Spec.program} method defines a program (command) with
# subcommands. The author and date are provided as parameters, and the
# program and subcommand options are defined in block.
#
# The first {Spec#command} (or {Spec#subcmd}) method call defines the main command
# ({Opt.main}) which represents the program. It has two "subcmd"
# options ("add" and "rm").
#
# The rest of the "subcmd" methods define subcommands for the parent
# command. This example includes one subcommand level, but multiple
# levels are allowed.
#
# The "check" (or "checkRule") method defines option combination
# ({RuleCheck}) for the previous subcommand definition. In this case the
# definition allows "add" to have either the "-fo" option defined or
# "password" and "username" in combination.
#
# Main (root) commands can be referenced through
#   Opt.main
# or alternatively
#   Opt['como_subcmd']
#
# The subcommands can be referenced through {Opt.main} (etc.)
#   Opt.main['add']
#   Opt['como_subcmd']['add']
#
# or directly from {Opt} if subcommand names do not collide:
#   Opt['add']
#
# The given subcommand can be accessed with {Opt#givenSubcmd} method from
# each parent command.
#
# === Subcommand example executions
#
# Normal behavior would be achieved by executing:
#   shell> como_subcmd add -fo -f example
#
# The program would execute with the following output:
#    Adding file "example"...
#
# The option combinations for "add" subcommand are automatically
# checked. Thus executing:
#   shell> como_subcmd add -f example
#
# Would result to:
#  como_subcmd error: Option combination mismatch!
#
#    Subcommand "add" usage:
#      como_subcmd add [-fo] [-p <password>] [-u <username>] -f <file>
#
#    -fo         Force operation.
#    -p          User password.
#    -u          Username.
#    -f          File.
#
#
#    Option Combinations:
#      |--# One of:
#      |     |--<-fo>
#      |     |--# All of:
#      |     |     |--<password>
#      |     |     |--<username>
#
# since the combination rule requires either "-fo", or both "password"
# and "username".
#
# Help is automatically provided on each command level, thus these are
# both valid.
#   shell> como_subcmd -h
# and
#   shell> como_subcmd rm -h
#
#
#
# = Option specification
#
# == Overview
#
# Option specification includes the minimum set of information
# required for command line parsing. It is used to:
# * Parse the command line.
# * Check for wrong options and report.
# * Check for mandatory arguments and report.
# * Set the options given/non-given state.
# * Set the options value. Array/String for all except true/false for
#   switches.
# * Generate Usage Help printout.
#
# == Option types
#
# The following types can be defined for the command line options:
# [:subcmd] Subcmd option. Subcmd specific options are provided
#           separately.
# [:switch] Single switch option (no arguments).
# [:single] Mandatory single argument option.
# [:multi] Mandatory multiple argument option (one or many). Option
#          values in array.
# [:opt_single] Optional single argument option. Value is nil when
#               option is not given.
# [:opt_multi] Optional multiple argument option (one or many). Option
#              values in array.
# [:opt_any] Optional multiple argument option (also none
#            accepted). Option values in array.
# [:default] Default option (no switch associated). Name and option
#            String values can be left out, since only the document
#            string is used. Default option is referred with
#            ":default" or "nil".
# [:exclusive] Option that does not co-exist with other
#              options. :exclusive can have arguments as with
#              :opt_any, however :exclusive is documented like
#              :switch.
# [:silent] Option that does not coexist with other options and is not
#           displayed as an option in Usage Help display. In effect a
#           sub-option of :exclusive.
#
# Options use typically all the 4 option fields:
#   [ type, name, mnemonic, doc ]
#
# "type" field is mandatory for all options.
#
# "name" field is also mandatory for all options. "mnemonic" can be
# left out, but then option accepts only long option format.
#
# ":default" uses only "doc" and ":subcmd" doesn't use the "mnemonic"
# field.
#
# ":multi", ":opt_multi", and ":opt_any" option arguments are
# terminated only when an option specifier is found. This can be a
# problem if ":default" option follows. The recommended solution is to
# use a ":silent" option that can be used to terminate the argument
# list. For example:
#   [ :silent, "terminator", "-", "The terminator." ],
#
#
# == Option specification method configuration
#
# Option behavior can be controlled with several configuration options.
#
# The configuration options are provided in a Hash. These are the
# passed as the last regular parameter for both {Spec.command} and
# {Spec.program} methods. Setting the configuration at {Spec.program}
# will propagate the config options to all the subcommands as
# well. Configuration can be given to each subcommand separately to
# override the inherited config values. Subcommand settings are not
# inherited, but apply only in the subcommand.
#
# The usable configuration Hash keys:
# [:autohelp] Add help option automatically (default: true). Custom
#             help option can be provided and it can be made also
#             visible to user.
# [:rulehelp] Include RuleCheck help to Usage Help (default: false).
# [:header] Header lines before standard usage printout.
# [:footer] Footer lines after standard usage printout.
# [:subcheck] Automatically check that a subcommand is provided
#             (default: true).
# [:check_missing] Check for missing arguments (default: true).
# [:check_invalid] Error for unknown options (default: true).
# [:tab] Tab stop column for option documentation (default: 12).
# [:help_exit] Exit program if help displayed (default: true).
# [:copyright] Display copyright/author in usage printout (default: true).
#
#
#
# = Option referencing
#
# == Existence and values
#
# Opt class includes the parsed option values. All options can be
# tested whether they are specified on the command line using:
#   Opt['name'].given
#
# The {Opt#given} method takes optionally a block argument. When block
# argument is used, the block is supplied with option value and the
# block is executed if the option has been set (See: {Opt#given}).
#
# Provided value is returned by:
#   Opt['name'].value
#
# For ":switch" type it is true/false value and for the other types a
# String or an Array of Strings.
#
# If an option takes multiple arguments, the value for the option is
# an Array. The values can be iterated simply by:
#  Opt['files'].value.each do |val|
#      puts val
#  end
#
# Short syntax for value referencing is performed with unary operator
# "~". Thus
#   ~Opt['files']
# is equal to
#   Opt['files'].value
#
# With ":opt_any" type, the user should first check if the option was given:
#   Opt['many_files_or_none'].given
# Then check how many arguments where given:
#   Opt['many_files_or_none'].value.length
# And finally decide what to do.
#
# == Options including parameters
#
# Sometimes it is convenient for the program to use an option to
# include multiple parameter settings. These settings can be parsed
# and mapped to a Hash. Como performs automatic conversion to numeric
# values if possible. For example with option:
#   --set rounds=10 length=5
# Como can be used extract the parameter values with the "params" method:
#   Opt['set'].params
# And a Hash is returned:
#   { 'rounds' => 10, 'length' => 5 }
#
# == Subcommand options
#
# The given subcommand for the parent command is return by
# {Opt#givenSubcmd}. Commonly the program creator should just check
# directly which subcommand has been selected and check for any
# subcommand options set. For example:
#   if Opt['como_subcmd']['add'].given
#      ...
#
# == Program external options
#
# If the user gives the "--" option (double-dash), the arguments after
# that option are returned as an Array with {Opt.external}.
#
#
#
# = Option combination checks
#
# Como provides a facility to create relations between options using
# RuleCheck DSL. This is needed since sometimes options have to be
# used in combination to make sense for the program. Also options
# might be mutually exclusive.
#
# The following rules can be used (in combination):
# [all] All options in the list.
# [one] One and only one from the list.
# [any] At least one of the list is given.
# [none] No options are required.
# [inv] Logical negation for existence.
# [incr] Incremental options in order i.e. have to have previous to
#        have later.
# [follow] Incremental options in order i.e. have to have all later if
#          had first.
# [meh] Dont care, always succeeds.
#
# Examples can be found above.
#
#
# = Customization
#
# A Como user specific customization file can be referenced through
# the "COMO" environment variable. If environment variable "COMO" is
# defined, the referenced file is read in as Ruby file as a last phase
# when Como is loaded from the program (require). Proposed naming
# convention for the customization is:
#
#   $HOME/.como
#
# User can define a pre and a post action hook in the file.
#
# The pre-hook can be used for example to change the Como config
# defaults. It is run before the body of {Spec.command} or
# {Spec.program} is executed. It is passed all the parameters that has
# been passed to {Spec.command} or {Spec.program}, only collected into
# a Hash. The Hash keys are method parameter names as symbols.
#
# Example:
#  # Define pre parser hook for Como.
#  Como.preHook do |args|
#
#      # Get default config for options.
#      config = Como::Opt.configGet
#
#      # Disable 'copyright' lines from usage.
#      config[ :copyright ] = false
#
#      # Test if "Spec.command" is the entry method (it has arg named "prog").
#      if args[ :prog ]
#          # Place a custom header for all programs.
#          config[ :header ] = "\nProgram \"#{args[ :prog ]}\" is ...\n\n"
#      end
#  end
#
# There is no predefined use cases for post-hook. Post-hook is passed
# the {Opt.main} as parameter.
#
# {Spec.program} and {Spec.command} both process and check options in
# one pass. Como user can separate the definition and checking
# phase. Definition phase is performed by executing
# {Spec.defineProgram} or {Spec.defineCommand}. After definition phase
# the user can for example programmatically add new subcommands or
# options, in addition to existing options. When the subcommands and
# options are complete, {Spec.execute} should be called to perform
# options checking.
#
# If the provided customization facilities are not satisfactory,
# changes can be implemented simply by overloading the existing
# functions. Some knowledge of the internal workings of Como is
# required though.
#
# Como version is returned with:
#  Como.version

module Como


    # IO stream options for Como classes.
    class ComoCommon

        # Default value for display output.
        @@io = STDOUT

        # Hooks.
        @@hook = {}

        # Set @@io.
        def ComoCommon.setIo( io )
            @@io = io
        end

        # Get @@io.
        def ComoCommon.getIo
            @@io
        end

        # Set hook content.
        #
        # @param name [String] Hook name.
        # @yield code Hook code.
        def ComoCommon.setHook( name, &code )
            @@hook[ name ] = code
        end

        # Run hook.
        #
        # @param name [String] Hook name.
        # @yield code Hook arguments.
        def ComoCommon.runHook( name, args )
            @@hook[ name ].yield( args ) if @@hook[ name ]
        end

    end


    # Set "preHook" routine.
    def Como.preHook( &code )
        ComoCommon.setHook( :preHook, &code )
    end


    # Set "postHook" routine.
    def Como.postHook( &code )
        ComoCommon.setHook( :postHook, &code )
    end


    # User interface for Como.
    class Spec < ComoCommon


        # Create specification for program with subcmds.
        #
        # @param author [String] Program author.
        # @param year [String] Year (or dates) for program.
        # @yield [] Subcmd definitions.
        def Spec.program( author, year, config = nil, &defs )
            Spec.defineProgram( author, year, config, &defs )
            Spec.execute
        end


        # The primary entry point to Como. Defines the command
        # switches and parses the command line. Performs "usage"
        # display if "help" was selected.
        #
        # @param prog [String] Program (i.e. command) name.
        # @param author [String] Author of the program.
        # @param year [String] Year (or dates) for program.
        # @param defs [Array<Array>] Option definitions.
        # @param config [Hash] Option definition's behavioral config
        #                      (changes @@config defaults).
        def Spec.command( prog, author, year, defs = [], config = {} )
            Spec.defineCommand( prog, author, year, defs, config )
            Spec.execute
        end


        # Define options specification for program. User should
        # perform {Spec.execute} separately.
        #
        # @param author [String] Program author.
        # @param year [String] Year (or dates) for program.
        # @yield [] Subcmd definitions.
        def Spec.defineProgram( author, year, config = nil, &defs )
            preHookArgs = {
                :author => author,
                :year => year,
                :config => config,
                :defs => defs,
            }

            ComoCommon.runHook( :preHook, preHookArgs )

            if config
                Opt.configOverlay( config )
            end

            spec = Spec.new( author, year )
            spec.instance_eval( &defs )
        end


        # Define options specification for command. User should
        # perform {Spec.execute} separately.
        #
        # @param prog [String] Program (i.e. command) name.
        # @param author [String] Author of the program.
        # @param year [String] Year (or dates) for program.
        # @param defs [Array<Array>] Option definitions.
        # @param config [Hash] Option definition's behavioral config
        #                      (changes @@config defaults).
        def Spec.defineCommand( prog, author, year, defs, config = {} )

            preHookArgs = {
                :prog => prog,
                :author => author,
                :year => year,
                :defs => defs,
                :config => config,
            }

            ComoCommon.runHook( :preHook, preHookArgs )

            spec = Spec.new( author, year )
            spec.subcmd( prog, defs, config )
        end


        # Alias for {Spec.command}.
        #
        # NOTE: This method is deprecated and will be removed in
        # future releases.
        def Spec.defineCheckHelp( prog, author, year, defs, config = {} )
            Spec.command( prog, author, year, defs, config )
        end


        # Perform command line options checking.
        def Spec.execute
            Opt.main.check( ArgsParseState.new( @@argv ) )
            ComoCommon.runHook( :postHook, Opt.main )
        end


        # Create Spec object that can handle subcmd definitions.
        #
        # @param author [String] Program author.
        # @param year [String] Year (or dates) for program.
        def initialize( author, year )
            @author = author
            @year = year

            Spec.ArgCheck( author.class == String, "Author name is not a String" )
            Spec.ArgCheck( year.class == String, "Year is not a String" )
        end


        # Define subcommand options.
        #
        # @param cmd [String] Subcmd name.
        # @param defs [Array<Array>] Option definition table.
        # @param config [] Configuration options.
        def subcmd( cmd, defs = [], config = {} )

            unless Opt.main

                main = MainOpt.new( @author, @year,
                    cmd, nil, :subcmd, nil )
                Opt.setMain( main )
                subcmd = main

            else

                subcmd = Opt.findOpt( cmd )

                Opt.setSubcmd( subcmd )

                Spec.ArgCheck( false, "Subcommand \"#{cmd}\" not defined." ) unless subcmd

            end

            # Overlay user config on top of default.
            subcmd.applyConfig( config )

            if subcmd.config[ :autohelp ]
                # Automatically add the help option.
                defs.insert( 0, [ :silent, "help", "-h", "Display usage info." ] )
            end

            Spec.specify( subcmd, defs )

        end


        # Alias to subcmd to highlight the main command.
        alias command subcmd


        # Check/fix options specs and create option objects for the
        # whole table.
        #
        # @param subcmd [Opt] Subcommand target.
        # @param table [Array<Array>] Option definition table for subcommand.
        def Spec.specify( subcmd, table )

            options = {}
            subcmds = {}

            # Type checks for valid user input.
            Spec.ArgCheck( table.class == Array, "Option table is not an Array" )

            table.each do |e|
                os = Spec.specifyOptOrSub( e )
                case os.type
                when :subcmd; subcmds[ os.name ] = os
                else options[ os.name ] = os
                end
            end

            subcmd.setOptionSubcmd( options.values, subcmds.values )
            subcmd
        end



        # Check/fix options specs and create option objects for the
        # whole table.
        #
        # @param opt_or_sub [Array<Array>] Option definition table.
        def Spec.specifyOptOrSub( opt_or_sub )

            # Fix the table entries if needed.

            Spec.ArgCheck( opt_or_sub.class == Array, "Option table entry is not an Array" )

            if opt_or_sub[0] == :default && opt_or_sub.length == 2

                # Add 2 dummy entries for :default type if needed.
                opt_or_sub = [ opt_or_sub[0], nil, nil, opt_or_sub[1] ]

            elsif opt_or_sub[0] == :subcmd && opt_or_sub.length == 3

                # Add 1 dummy entry for :subcmd type if needed.
                opt_or_sub = [ opt_or_sub[0], opt_or_sub[1], nil, opt_or_sub[2] ]

            end

            Spec.ArgCheck( opt_or_sub.length == 4,
                "Option table entry length not 4" )

            if opt_or_sub[0] == :subcmd

                Opt.subcmd( opt_or_sub[1], opt_or_sub[3] )

            else

                case opt_or_sub[0]

                when :switch, :exclusive, :silent, :single, :multi,
                    :opt_single, :opt_multi, :opt_any
                    Opt.full( opt_or_sub[1], opt_or_sub[2], opt_or_sub[0], opt_or_sub[3] )

                when :default
                    Opt.defaultOpt( opt_or_sub[3] )

                else
                    raise "Unknown option type: \"#{opt_or_sub[0]}\"..."

                end

            end

        end


        # Command line options source.
        @@argv = ARGV

        # Set command line options source, i.e. @@argv (default: ARGV).
        def Spec.setArgv( newArgv )
            @@argv = newArgv
        end

        # Display program usage (and optionally exit).
        def Spec.usage
            Opt.main.usage
        end

        # Set optional header for "usage".
        def Spec.setUsageHeader( str )
            Opt.main.setUsageHeader( str )
        end


        # Set optional footer for "usage".
        def Spec.setUsageFooter( str )
            Opt.main.setUsageFooter( str )
        end


        # Check option combination rules.
        #
        # @param opt [String] Opt name to which rules are set. If not
        #                     given, Opt.current is used.
        # @param rule [Proc] Rules to check.
        def Spec.checkRule( opt = nil, &rule )
            if opt
                opt = Opt[ opt ]
            else
                opt = Opt.current
            end
            opt.setRuleCheck( &rule )
            opt.checkRule
        end


        # Alias for Spec.checkRule.
        def Spec.check( opt = nil, &rule ) Spec.checkRule( opt = nil, &rule ) end

        # Check option combination rules.
        #
        # @param opt [String] Opt name to which rules are set. If not
        #                     given, Opt.current is used.
        # @param rule [Proc] Rules to check.
        def checkRule( opt = nil, &rule )
            if opt
                opt = Opt[ opt ]
            else
                opt = Opt.current
            end
            opt.setRuleCheck( &rule )
        end


        # Alias for checkRule
        alias check checkRule


        # Additional option check.
        #
        # @param opt [String] Option name.
        # @param error [String] Error string for false return values (from check).
        # @param check [Proc] Checker proc run for the option. Either
        # @return false or generate an exception when errors found.
        def Spec.checkAlso( opt, error, &check )
            Opt.main.checkAlso( opt, error, &check )
        end


        private

        # Argument checking assertion.
        def Spec.ArgCheck( cond, str )
            raise( ArgumentError, str ) unless cond
        end


    end




    # Opt includes all options spec information and parsed options
    # and their values. Option instance is accessed with
    # "Opt['name']". The option status (Opt instance) can be
    # queried with for example "given" and "value" methods.
    class Opt < ComoCommon


        # Create exception with capability to pass arbitrary data.
        class ErrorWithData < StandardError

            # Exception data.
            attr_reader :data

            # Create error exception.
            def initialize( message = nil, data = nil )
                super( message )
                @data = data
            end
        end


        # Missing argument exception.
        class MissingArgument  < ErrorWithData; end

        # Invalid (non-existing) option exception.
        class InvalidOption    < ErrorWithData; end



        # ------------------------------------------------------------
        # Option specification:


        # Program i.e. highest level subcommand.
        @@main = nil

        # List of parsed option specs and option values.
        @@opts = []

        # Current subcommand recorded.
        @@subcmd = nil


        # Set of default configs for printout.
        @@config = {
            :autohelp      => true,
            :rulehelp      => false,
            :header        => nil,
            :footer        => nil,
            :subcheck      => true,
            :check_missing => true,
            :check_invalid => true,
            :tab           => 12,
            :help_exit     => true,
            :copyright     => true,
        }


        # Set main option.
        def Opt.setMain( main )
            @@main = main
            Opt.setSubcmd( main )
        end

        # Get main option.
        def Opt.main
            @@main
        end


        # Add option to options list.
        def Opt.addOpt( opt )
            @@opts.push opt
        end


        # Set current subcmd.
        def Opt.setSubcmd( opt )
            @@subcmd = opt
        end


        # Current subcmd processed.
        def Opt.current
            @@subcmd
        end


        # Find option by name.
        def Opt.findOpt( name )
            idx = @@opts.index do |i| i.name == name end
            if idx
                @@opts[ idx ]
            else
                nil
            end
        end


        # Reset "dynamic" class members.
        def Opt.reset
            @@opts = []
        end


        # Select option object by name. Main is searched first and
        # then the flattened list of all options.
        def Opt.[](str)

            # Search Main first.
            ret = Opt.main.argByName( str )

            unless ret
                ret = Opt.findOpt( str )
                unless ret
                    raise RuntimeError, "Option \"#{str}\" does not exist..."
                end
            end

            ret
        end


        # Return program name.
        def Opt.progname
            @@main.name
        end


        # Return program year.
        def Opt.year
            @@main.year
        end


        # Return author.
        def Opt.author
            @@main.author
        end


        # Return arguments (options) that are specified as command
        # external (i.e. after '--').
        def Opt.external
            Opt.main.external
        end


        # Return arguments (options) that have no switch.
        def Opt.default
            Opt.main.default
        end


        # Create option spec.
        def Opt.full( name, opt, type, doc = "No doc." )
            new( name, opt, type, doc )
        end

        # Create sub-command option spec.
        def Opt.subcmd( name, doc = "No doc." )
            new( name, nil, :subcmd, doc, false )
        end

        # Create default option spec, no switch.
        def Opt.defaultOpt( doc = "No doc." )
            new( "<default>", "<default>", :default, doc, [] )
        end


        # Options iterator for all options.
        def Opt.each( &blk )
            Opt.main.each &blk
        end


        # Options iterator for given options.
        def Opt.each_given( &blk )
            Opt.main.each_given( &blk )
        end


        # Overlay Opt default configuration options.
        def Opt.configOverlay( config )
            @@config.merge!( config )
        end


        # Return default config for Como options.
        def Opt.configGet
            @@config
        end


        # Set default config for Como options. User can manipulate the
        # defaults with "preHook".
        def Opt.configSet( config )
            @@config = config
        end


        # Issue non-fatal user error. See {#error}.
        def Opt.error( str, nl = false )
            Opt.main.error( str, nl )
        end


        # Issue fatal user error. See {#fatal}.
        def Opt.fatal( str )
            Opt.main.fatal( str )
        end


        # Issue user warning. See {#warn}.
        def Opt.warn( str, nl = false )
            Opt.main.warn( str, nl )
        end


        # ------------------------------------------------------------
        # Opt properties:


        # Subcommand parent (i.e. host).
        attr_accessor :parent


        # Option name.
        attr_accessor :name

        # Short option string.
        attr_accessor :shortOpt

        # Long option string.
        attr_accessor :longOpt

        # Option type.
        attr_accessor :type

        # Option value.
        attr_accessor :value

        # Option documentation string.
        attr_accessor :doc

        # Is option specified?
        attr_writer :given

        # List of suboptions.
        attr_reader :subopt

        # List of subcommands.
        attr_reader :subcmd

        # Opt configuration.
        attr_reader :config

        # Opt rules.
        attr_reader :rules


        # Create Opt object.
        # @param name [String] Option name.
        # @param opt [String] Switch.
        # @param type [Symbol] Option type. One of:
        #        * :switch
        #        * :single
        #        * :multi
        #        * :opt_single
        #        * :opt_multi
        #        * :opt_any
        #        * :default
        #        * :exclusive
        #        * :silent
        # @param doc [String] Option documentation.
        # @param value [Object] Default value.
        def initialize( name, opt, type, doc, value = nil )
            @parent = nil
            @name = name
            @shortOpt = opt
            @longOpt = "--#{name}"
            @type = type
            @value = value

            if hasMany && value == nil
                @value = []
            end

            @doc = doc
            # Whether option was set or not.
            @given = false
            @subopt = []
            @subcmd = []
            @rules = nil

            @config = @@config.dup

            Opt.addOpt( self )
        end


        # Set command (subcommand) suboptions and subcmds.
        #
        # @param opts [Array<Opt>]
        def setOptionSubcmd( opts, subs )
            opts.each do |i|
                addOption( i )
            end

            subs.each do |i|
                addSubcmd( i )
            end
        end


        # Add subcommand option.
        #
        # @param opt [Option] Option to add.
        def addOption( opt )
            opt.parent = self
            @subopt.push opt
        end


        # Add subcommand subcmd.
        #
        # @param cmd [Option] Subcmd to add.
        def addSubcmd( cmd )
            cmd.parent = self
            @subcmd.push cmd
        end


        # Merge config to base config.
        #
        # @param config [Hash] Configuration Hash to merge.
        def applyConfig( config )
            @config.merge!( config )
        end


        # Set rule checks for the option.
        #
        # @param rule [Proc] Rule to check after command line parsing.
        def setRuleCheck( &rule )
            @rules = rule
        end


        # ------------------------------------------------------------
        # Command line parsing and checking:

        # Check provided args.
        def check( argsState )

            # Start at top.
            top = self

            parse = Proc.new do
                # Parse and check for invalid arguments.
                begin
                    top = top.parse( argsState, top.config[ :check_invalid ] )
                end while( top )

                # Check for any missing valid arguments.
                checkMissing
            end

            error = Proc.new do |err|
                errornl( err.to_s )

                # Display subcmd specific usage info.
                err.data.usage

                exit( 1 )
            end

            begin
                parse.call
            rescue Opt::MissingArgument, Opt::InvalidOption => err
                error.call( err )
            end

            # Revert back to top after hierarchy travelsal.
            usageIfHelp

            # Check rules.
            cur = self
            while cur
                cur.checkRule
                cur = cur.givenSubcmd
            end

            self
        end



        # Parse cmdline options from args.
        def parse( args, checkInvalids = true )

            while args.get

                #puts "Opt.parse (#{@name}): #{args.get}"

                if args.isOptTerm

                    # Rest of the args do not belong to this program.
                    args.next
                    Opt.main.external = args.rest
                    break

                elsif args.isOpt

                    o = findOpt( args.get )

                    if !o
                        if checkInvalids
                            raise \
                            InvalidOption.new( "Unknown option \"#{args.get}\"...",
                                self )
                        else
                            o = findOpt( nil )
                            if !o
                                raise \
                                InvalidOption.new(
                                    "No default option specified to allow \"#{args.get}\"...",
                                   self )
                            else
                                # Default option.
                                o.value.push args.toValue
                                args.next
                            end
                        end

                    elsif o && o.hasArg

                        args.next

                        if ( !args.get || args.isOpt ) &&
                                o.type != :opt_any && o.type != :exclusive

                            raise MissingArgument.new(
                                "No argument given for \"#{o.opt}\"...",
                                self )

                        else

                            if o.hasMany

                                # Get all argument for multi-option.
                                o.value = [] if !o.given
                                while args.get && !args.isOpt
                                    o.value.push args.toValue
                                    args.next
                                end

                            else

                                # Get one argument for single-option.

                                if o.given
                                    raise \
                                    InvalidOption.new(
                                        "Too many arguments for option (\"#{o.name}\")...",
                                        self )
                                else
                                    o.value = args.toValue
                                end
                                args.next
                            end
                        end

                        o.given = true

                    else

                        if !o
                            raise InvalidOption.new( "No valid options specified...",
                                self )
                        else
                            o.given = true
                            args.next
                        end
                    end

                else

                    # Subcmd or default. Check for Subcmd first.

                    # Search for Subcmd.
                    o = findOpt( args.get )

                    if !o

                        # Search for default option.
                        o = findOpt( nil )
                        if !o
                            raise \
                            InvalidOption.new(
                                "No default option specified to allow \"#{args.get}\"...",
                                self )
                        else
                            # Default option.
                            o.given = true
                            o.value.push args.toValue
                            args.next
                        end

                    else

                        # Subcmd.
                        o.given = true
                        args.next
                        return o

                    end

                end
            end

            nil
        end


        # Check for any non-given required arguments recursively
        # through hierarchy of subcommands. MissingArgument Exception
        # is generated if argument is missing.
        def checkMissing

            return unless config[ :check_missing ]

            # Full cmd name.
            cmd = ( getParents.map do |i| i.name end ).join( ' ' )

            # Check for any exclusive args first.
            @subopt.each do |o|
                if o.isExclusive && o.given
                    return
                end
            end


            # Check for required arguments for this level before
            # subcmds.
            @subopt.each do |o|
                if o.isRequired
                    unless o.given
                        raise MissingArgument.new(
                            "Option \"#{o.opt}\" missing for \"#{cmd}\"...",
                            self )
                    end
                end
            end

            if hasSubcmd
                if @config[ :subcheck ]
                    # Compulsory Subcommand checking enabled.
                    subcmdMissing = true
                else
                    subcmdMissing = false
                end
            else
                subcmdMissing = false
            end

            # Check for any subcmd args.
            sub = givenSubcmd
            if sub
                subcmdMissing = false
                # Use recursion to examine the next level.
                return sub.checkMissing
            end

            # If no subcmds are given, issue error.
            raise MissingArgument.new(
                "Subcommand required for \"#{cmd}\"...",
                self ) if subcmdMissing

        end



        # Check option combination rules.
        def checkRule

            return unless @rules

            begin
                raise Opt::InvalidOption.new( "Option combination mismatch!", self ) unless
                    RuleCheck.check( self, &@rules )

            rescue Opt::MissingArgument, Opt::InvalidOption => err
                @@io.puts
                errornl( err.to_s )

                usage( nil, true )

            end
        end


        # Additional option check.
        #
        # @param opt [String] Option name.
        # @param error [String] Error string for false return values (from check).
        # @param check [Proc] Checker proc run for the option. Either
        # @return false or generate an exception when errors found.
        def checkAlso( opt, error, &check )
            begin
                if self[opt].evalCheck( &check ) != true
                    raise Opt::InvalidOption.new( error, self )
                end
            rescue Opt::MissingArgument, Opt::InvalidOption => err
                @@io.puts
                errornl( err.to_s )
                err.data.usage
                exit( 1 )
            end
        end




        # ------------------------------------------------------------
        # Opt query user interface:

        # Select option object by name operator.
        def []( str )
            ret = argByName( str )
            unless ret
                raise RuntimeError, "Subopt \"#{str}\" does not exist for \"#{@name}\"!"
            end
            ret
        end


        # Option's opt id. Short if exists otherwise long.
        def opt
            @shortOpt ? @shortOpt : @longOpt
        end


        # All subcommand options, options and subcommands.
        def suball
            @subopt + @subcmd
        end


        # Options list iterator.
        def each( &blk )
            suball.each do |o|
                yield o
            end
        end


        # Options iterator for given options.
        def each_given( &blk )
            suball.each do |o|
                yield o if o.given
            end
        end


        # Number of given options.
        def givenCount
            cnt = 0
            each_given do |i|
                cnt += 1
            end
            cnt
        end


        # Short syntax for value reference. Example: "~Opt['file']".
        def ~()
            @value
        end


        # Return option value if given otherwise the default.
        # Example usage: fileName = Opt["file"].apply( "no_name.txt" )
        def apply( default = nil )
            if given
                if @type == :switch
                    true
                else
                    value
                end
            else
                default
            end
        end


        # Returns true if option is given, and block is not
        # present. When block is present, the block is executed (with
        # value as parameter) if option has been given.
        #
        # @param optArg [Boolean] Pass Opt to block instead of its
        #   value.
        def given( optArg = false, &prog )
            if block_given?
                if @given
                    if optArg
                        yield( self )
                    else
                        yield( @value )
                    end
                else
                    false
                end
            else
                @given
            end
        end


        # Alias for given.
        alias given? given


        # Return the selected subcommand.
        def givenSubcmd
            ( @subcmd.select do |o| o.given end )[0]
        end


        # Returns Hash of option value parameters. Example command
        # line content:
        #   -p rounds=10 length=5
        # Option value content in this case would be (Array of param
        # settings):
        #  [ "rounds=10", "length=5" ]
        # @return [Hash] Parameter settings included in the option.
        def params
            map = {}
            @value.each do |i|
                name, value = i.split('=')
                value = str_to_num( value )
                map[ name ] = value
            end
            map
        end


        # Return default options.
        def default
            argByName( nil )
        end


        # Select option object by name.
        def argByName( str )
            if str == nil || str == :default
                @subopt.each do |o|
                    if o.type == :default
                        return o
                    end
                end
                nil
            else
                suball.each do |o|
                    if str == o.name
                        return o
                    end
                end
                nil
            end
        end


        # Select option object by name/opt/longOpt.
        def argById( str )
            if str == nil || str == :default
                @subopt.each do |o|
                    if o.type == :default
                        return o
                    end
                end
                nil
            else
                suball.each do |o|
                    if str == o.name || str == o.opt || str == o.longOpt
                        return o
                    end
                end
                nil
            end
        end


        # Option requires argument?
        def hasArg
            case @type
            when :single, :multi, :opt_single, :opt_multi, :opt_any, :exclusive; true
            else false
            end
        end


        # Option requires many arguments?
        def hasMany
            case @type
            when :multi, :opt_multi, :opt_any, :exclusive, :default; true
            else false
            end
        end


        # Is mandatory argument?
        def isRequired
            case @type
            when :single, :multi; true
            else false
            end
        end


        # Test if option is silent.
        def silent?
            @type == :silent
        end


        # Test if option is exclusive. In addition :exclusive also
        # :silent is exclusive.
        def isExclusive
            case @type
            when :exclusive, :silent; true
            else false
            end
        end


        # Test if option is of switch type.
        def isSwitch
            case @type
            when :switch, :exclusive, :default; true
            else false
            end
        end


        # ------------------------------------------------------------
        # Opt "Usage Help" user interface:

        # Set optional header for "usage".
        def setUsageHeader( str )
            @config[ :header ] = str
        end


        # Set optional footer for "usage".
        def setUsageFooter( str )
            @config[ :footer ] = str
        end


        # Display program usage (and optionally exit).
        #
        # @param doExit [Boolean] Exit program after help
        #                         display. Default to help_exit config
        #                         if nil.
        # @param ruleHelp [Boolean] Include rule help to help
        #                           display. Default to rulehelp config
        #                           if nil.
        def usage( doExit = nil, ruleHelp = nil )

            doExit = @config[ :help_exit ] if doExit == nil
            ruleHelp = @config[ :rulehelp ] if ruleHelp == nil

            @@io.puts usageNormal

            if ruleHelp
                @@io.puts "\n  Option Combinations:"
                @@io.puts RuleDisplay.print( &@rules )
            end

            exit( 1 ) if doExit
        end


        # Display program usage (and optionally exit).
        def usageIfHelp
            if self.argByName( 'help' ) && self['help'].given
                usage
            elsif hasSubcmd && givenSubcmd
                givenSubcmd.usageIfHelp
            end
        end


        # Usage printout for command.
        def usageCommand
            str = ""
            str += "\
  Subcommand \"#{@name}\" usage:
    #{fullCommand} #{cmdline.join(" ")}
"
            str += suboptDoc

            str += "\n"
        end

        # Usage info for Opt:s.
        def usageNormal
            str = ""

            if @config[ :header ]
                str += @config[ :header ]
            else
                str += "\n"
            end

            str += usageCommand

            if @config[ :footer ]
                str += @config[ :footer ]
            else
                str += "\n"
            end

            str
        end


        # Return cmdline usage strings for options in an Array.
        def cmdline
            opts = []

            @subopt.each do |o|

                next if o.silent?

                prural = nil
                case o.type
                when :multi, :opt_multi; prural = "+"
                when :opt_any; prural = "*"
                else prural = ""
                end


                if !( o.isSwitch )
                    name = " <#{o.name}>#{prural}"
                else
                    name = ""
                end

                if o.shortOpt == nil
                    opt = o.longOpt
                else
                    opt = o.shortOpt
                end

                if o.isRequired
                    opts.push "#{opt}#{name}"
                else
                    opts.push "[#{opt}#{name}]"
                end
            end


            if hasSubcmd
                opts.push "<<subcommand>>"
            end

            opts

        end


        # Return document strings for options.
        def suboptDoc

            str = ""
            # format = Proc.new do |s,d| ( "  %-#{@config[ :tab ]}s%s\n" % [ s, d ] ) end

            str += "\n"

            str += "  Options:\n" if hasSubcmd && hasVisibleOptions

            @subopt.each do |o|
                next if o.silent?
                str += suboptDocFormat( o.opt, o.doc )
            end

            str += "\n" + suboptDocFormat( "Subcommands:", "" ) if hasSubcmd

            @subcmd.each do |o|
                str += suboptDocFormat( o.name, o.doc )
            end

            str
        end


        # Find option object by option str.
        def findOpt( str )
            if str == nil
                suball.detect { |i| i.type == :default }
            elsif str[0..1] == "--"
                suball.detect { |i| i.longOpt == str }
            elsif str[0..0] != "-"
                suball.detect { |i| i.name == str }
            else
                suball.detect { |i| i.opt == str }
            end
        end


        # Convert to hash representation.
        #
        # Keys are symbols: name, type, given, value, subopt, subcmd.
        def to_hash
            h = {}
            h[ :name ] = @name
            h[ :type ] = @type
            h[ :given ] = @given
            h[ :value ] = @value
            h[ :subopt ] = @subopt.map{|i| i.to_hash }
            h[ :subcmd ] = @subcmd.map{|i| i.to_hash }
            h
        end


        # Como error printout.
        #
        # @param str [String] Error message.
        # @param nl [Boolean] Prepend msg with newline.
        def error( str, nl = false )
            nl = nl ? "\n" : ""
            STDERR.puts( "#{nl}#{Opt.progname} error: #{str}" )
        end


        # Como error printout with pre-newline.
        def errornl( str )
            error( str, true )
        end


        # Como error printout with immediate exit.
        def fatal( str )
            error( str )
            exit( false )
        end


        # Como warning printout.
        #
        # @param str [String] Warning message.
        # @param nl [Boolean] Prepend msg with newline.
        def warn( str, nl = false )
            nl = nl ? "\n" : ""
            STDERR.puts( "#{nl}#{Opt.progname} warning: #{str}" )
        end


        # Custom check for the option. User has to know some Como
        # internals.
        def evalCheck( &check )
            instance_eval &check
        end


        # ------------------------------------------------------------
        # Internal methods:

        private


        # Convert string to number if possible. The order of checks:
        # integer, float, and no-conversion.
        def str_to_num( str )
            begin return Integer( str ); rescue ; end
            begin return Float( str );   rescue ; end
            str
        end


        # Return list of parents.
        def getParents
            list = []
            cur = self
            begin
                list.push cur
                cur = cur.parent
            end while cur

            list.reverse
        end


        # Full command name.
        def fullCommand
            ( getParents.map do |i| i.name end ).join( ' ' )
        end


        # Test if option is of subcmd type.
        def isSubcmd
            @type == :subcmd
        end


        # Test if option is of subcmd type.
        def hasVisibleOptions
            # Count non-silent options.
            ( @subopt.select do |i| !i.silent? end ).length > 0
        end


        # Test if option is of subcmd type.
        def hasSubcmd
            !@subcmd.empty?
        end


        # Format option documentation. If newline is followed by tab,
        # rest of the documentation is aligned with the description of
        # previous line.
        def suboptDocFormat( switch, doc )

            # Format doc nicely to multiple lines if newline is
            # followed by tab stop.

            parts = doc.split( "\n" )
            lines = [ ( "  %-#{@config[ :tab ]}s%s\n" % [ switch, parts[0] ] ) ]

            if parts[1]
                parts[1..-1].each do |p|

                    if p[0] == "\t"
                        lines.push ( "  #{' '*@config[ :tab ]}%s\n" % [  p[1..-1] ] )
                    else
                        lines.push p
                    end

                end
            end

            lines.join
        end

    end



    # Specialized Opt class for program (i.e. highest level
    # subcommand).
    class MainOpt < Opt

        # Program external arguments:
        attr_accessor :external

        # Program author and year (date).
        attr_reader :author, :year


        # Create program main option.
        def initialize( author, year,
                name, opt,
                type, doc,
                value = nil )

            @author = author
            @year = year
            @external = nil
            super( name, opt, type, doc, value = nil )

        end


        # Full command name.
        def fullCommand
            Opt.progname
        end


        # Usage printout for command.
        def usageCommand
            str = "\
  #{fullCommand} #{cmdline.join(" ")}
"
            str += suboptDoc

            if @config[ :copyright ]
                str += "

  Copyright (c) #{Opt.year} by #{Opt.author}
"
            end

            str
        end

    end


    # Command argument parsing state.
    class ArgsParseState

        # Create parse state.
        #
        # @param list [Array<String>] List of Command Line Arguments
        #                             (default: ARGV).
        def initialize( list )
            set( list )
            @idx = 0
        end

        # Set list of arguments.
        def set( list )
            @args = list
        end

        # Step to next argument.
        def next
            @idx += 1
        end

        # Step to previous argument.
        def prev
            @idx -= 1
        end

        # Get current argument.
        def get( idx = @idx )
            @args[ idx ]
        end

        # Get last argument.
        def last( idx = @idx )
            idx == ( @args.length-1 )
        end

        # Get rest of the arguments.
        def rest( idx = @idx )
            @args[ idx..-1 ]
        end

        # Parser at argument list end?
        def done?
            @idx >= @list.length
        end


        # Test whether str is an option.
        def isOpt( str = get )
            str[0..0] == "-"
        end

        # Test whether str is an option list terminator.
        def isOptTerm( str = get )
            str == "--"
        end

        # Format value string if escaped.
        def toValue( str = get )
            if str[0..0] == "\\"
                str[1..-1]
            else
                str
            end
        end

    end



    # Set of methods which represent option combination checking.
    # In effect this is a meta language (DSL) for option
    # combinations.
    #
    # Example:
    #  RuleCheck.check( opt ) do
    #      one(
    #          incr( "gcov", "exclude", "refreshed" ),
    #          "manifest",
    #          "pairs",
    #          "files"
    #          )
    #  end
    class RuleCheck

        # Perform rule checking options.
        #
        # @param opt [Object] Options to check.
        # @yield rule Checking conditions.
        def RuleCheck.check( opt, &rule )
            rc = RuleCheck.new( opt )
            rc.instance_eval( &rule )
        end


        # Build Rule checker.
        def initialize( opt, &rule )
            @opt = opt
        end


        # Get given count.
        def getScore( *args )

            score = 0
            args.each do |i|
                if i.class == TrueClass || i.class == FalseClass
                    score += 1 if i
                else
                    score += 1 if @opt.argById(i).given
                end
            end

            score
        end

        # Special condition when no options are given.
        def none
            @opt.givenCount == 0
        end

        # Incremental options in order i.e. have to have previous
        # to have later.
        def incr( *args )

            # Opts given consecutive.
            consecutiveCnt = 0

            # Consecutive flag.
            consecutive = true

            # All given opts.
            givenCnt = 0

            i = 0
            while args[i]
                score = getScore( args[i] )

                # Still consecutive.
                consecutiveCnt += 1 if ( score == 1 ) && consecutive

                # No more consecutives.
                consecutive = false if ( score == 0 )

                # Count all given.
                givenCnt += score

                i += 1
            end

            ( consecutiveCnt == givenCnt ) && ( givenCnt > 0 )
        end

        # Incremental options in order i.e. have to have all later
        # if had first.
        def follow( *args )
            if getScore( args[0] )
                getScore( *args ) == args.length
            else
                false
            end
        end

        # One of list given.
        def one( *args )
            getScore( *args ) == 1
        end

        # At least one is given.
        def any( *args )
            getScore( *args ) > 0
        end

        # All are given.
        def all( *args )
            getScore( *args ) == args.length
        end

        # Logical inversion.
        def inv( *args )
            getScore( *args ) == 0
        end

        # Dont care.
        def meh( *args )
            true
        end
    end



    # Display utility for RuleCheck. Usage model.
    #
    #  RuleDisplay.new.evalAndDisplay( &rule )
    #
    #  Example expansion of options:
    #
    #     |--# One of:
    #     |     |--# Adding in order:
    #     |     |     |--<gcov>
    #     |     |     |--<exclude>
    #     |     |     |--<refreshed>
    #     |     |--<manifest>
    #     |     |--<pairs>
    #     |     |--<files>
    #
    class RuleDisplay < ComoCommon

        # Eval rules to get an nested array and then display it.
        def RuleDisplay.print( prefixStr = "    ", &rule )
            rd = RuleDisplay.new( prefixStr )
            rd.evalAndDisplay( &rule )
        end

        # Create rule displayer.
        def initialize( prefixStr )
            # Prefix string for lines. Rules add/rm from it.
            @prefixStr = prefixStr
        end

        # Display method.
        def evalAndDisplay( &rule )
            printRule( instance_eval( &rule ) )
        end

        # Increase prefix string.
        def addPrefix( str )
            @prefixStr += str
        end

        # Remove from prefix (either str or length ).
        def rmPrefix( item )
            if item.class == String
                cnt = item.length
            else
                cnt = item
            end
            @prefixStr = @prefixStr[0..-(cnt+1)]
        end

        # Print prefix + str.
        def p( str )
            @@io.puts( @prefixStr + str )
        end

        # Recursively go through the nested array of rule items and
        # print out rules.
        def printRule( arr )
            p( "|--# #{arr[0]}:" )
            item = "|     "
            addPrefix( item )

            arr[1..-1].each do |i|
                if i.class == Array
                    printRule( i )
                else
                    p( "|--<#{i}>" )
                end
            end
            rmPrefix( item )
        end

        # Special condition where no arguments are given.
        def none
            [ "NONE" ]
        end

        # Incremental options in order i.e. have to have previous
        # to have later.
        def incr( *args )
            [ "Adding in order", *args ]
        end

        # Incremental options in order i.e. have to have all later
        # if had first.
        def follow( *args )
            [ "If first then rest", *args ]
        end

        # One of list given.
        def one( *args )
            [ "One of", *args ]
        end

        # At least one is given.
        def any( *args )
            [ "One or more of", *args ]
        end

        # All are given.
        def all( *args )
            [ "All of", *args ]
        end

        # Logical inversion.
        def inv( *args )
            [ "Not", *args ]
        end

        # Dont care.
        def meh( *args )
            [ "Ignoring", *args ]
        end

    end


end


# Include version definitions.
require_relative 'version'


# Load user customizations (if any).
load ENV['COMO'] if ENV['COMO']