# = 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 provides the command usage information based on the
# option table (+ generic program info) automatically. Como supports
# also subcommands and checking for option combinations using a simple
# DSL.
#
#
#
# == Usage Examples
#
# === 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
#
# "Spec.defineCheckHelp" 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 in format with 4
#                entries in each sub-array.
# 
# Help is added silenty by default:
#      [ :silent, "help", "-h", "Display usage info." ],
#
# Each option table entry is an Array of 4 values: type, name,
# mnemonic, doc. Three different types are present in the example:
# [:silent] Silent is left out from the "usage" printout (see
#           below). Also "help" is reserved as special option name to
#           designate command line Usage Help.
# [: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 from Opt class.
# The command line option values are stored to Opt class
# automatically. For example the file option value is returned by
# executing "Opt['file'].value". The option name also doubles as
# long option, i.e. one could use "--file <filename>" on the command
# line.
#
# Existence of optional options can be tested using the "given"
# method. For example "Opt['debug'].given" would return "true" if
# "-d" was given on the command line.
#
# ==== 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. For example
# given the command:
#   shell> como_simple
#
# The following is displayed on the screen:
#
#  como_simple error: Option "-f" missing for "como_simple"...
#  
#    Usage:
#      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).
#
# Given the command:
#   shell> como_simple -h
#
# would display the same "usage" screen except without the error
# line. Documentation string is taken from the specification to
# "usage" display. The "help" option is added automatically to the
# specification.
#
#
#
# === Subcmd example
#
#
#
#
#
#
# == Option specification
# 
# === Overview
# 
# 
#
# === Option types
#
# The following types can be defined for the command line options:
# [:switch] Single switch option (no arguments).
# [:single] Mandatory single argument option.
# [:multi] Mandatory multiple argument option. Option values in
# array.
# [:opt_single] Optional single argument option.
# [:opt_multi] Optional multiple argument option. Option values in array.
# [:opt_any] Optional multiple argument option (also none accepted).
#            Option values in array.
# [:default] Default option (no switch associated). Any name and
#            option String value can be supplied to the spec, since
#            only the document string is used. Default option is
#            referred with "nil".
# [:exclusive] Option that does not coexist with other options.
# [:silent] Option that does not coexist with other options and is not
#           displayed as an option in "usage" display. In effect a
#           sub-option of :exclusive.
#
# TODO: multi note about termination.
# TODO: leaving out short option.
#
# === TODO: Change header: Specification method options
# 
# The common method for specifying the options is to use
# "Spec.defineCheckHelp". Method invocation includes definition
# of the options, parsing the command line, checking for missing
# mandatory options, and it will automatically display "usage" if
# "help" option is given.
#
# Automatic "help" option processing can be avoided using
# "Spec.defineCheck" instead.
#
# TODO: How options are inherited, program vs subcmd.
#
# Both methods above accept additional parameters passed in a
# Hash. The usable 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] TODO
# [:check] Check for missing arguments (default: true).
# [:tab] TODO (default: 12).
# [:help_exit]  Exit program if help displayed (default: true).
# [:error_exit]  Exit program if error in options (default: true).
#
#
#
# == Option referencing
# 
# 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 "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.
#
# "Opt['name'].value" returns the provided option 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'].value
# is equal to
#   ~Opt['files']
#
# 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 }
#
# 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.
#
# If the user gives the "--" option (double-dash), the arguments after
# that option is returned as an Array with "Opt.external". TODO:
# silent to terminate multi options.
#
#
#
# == Customization
#
# If the default behavior is not satisfactory, changes can be
# implemented simply by overloading the existing functions. Some
# knowledge of the internal workings of Como is required though.
#
#
# == Additional checks
#
# Sometimes the options have to be used in combination to make sense
# for the program. Como provides a facility to create relations
# between options. Consider the following options spec:
#  Spec.defineCheckHelp( "como_fulltest", "Programmer", "2013",
#    [
#      [ :silent, "help", "-h", "Display usage info." ],
#      [ :single, "file", "-f", "File argument." ],
#      [ :switch, "o1", "-o1", "o1" ],
#      [ :opt_single, "o2", "-o2", "o2" ],
#      [ :opt_single, "o3", "-o3", "o3" ],
#      [ :opt_multi, "o4", "-o4", "o4" ],
#      [ :opt_any, "o5", "-o5", "o5" ],
#      [ :switch, "debug", "-d", "Enable debugging." ],
#    ] )
#  
#  Spec.checkRule do
#      all( 'file',
#           one(
#               all( 'o1', 'o2' ),
#               one( 'o3', 'o4', 'o5' )
#               )
#           )
#  end
#  
# This spec includes multiple optional options ("o?"). The
# "Spec.checkRule" method accepts a block where option rule
# check DSL (Domain Specific Language) is used. The rule specifies
# that the "file" option has to be used in combination with some other
# options. These are "all( 'o1', 'o2' )" or "one( 'o3', 'o4', 'o5' )",
# i.e. either both "o1" and "o2", or one of ["o3","o4","o5]. The
# checker will validate this rule and error if for example the command
# line reads:
#   shell> como_fulltest --file myfile -o3 black -o5
#
# 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.

module Como


    # IO stream options for Como classes.
    class ComoCommon
        
        # Default value for display output.
        @@io = STDOUT

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

        # Get @@io.
        def ComoCommon.getIo
            @@io
        end
    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 defs [] Subcmd definitions.
        def Spec.program( author, year, config = nil, &defs )
            if config
                Opt.configOverlay( config )
            end
            spec = Spec.new( author, year )
            spec.instance_eval( &defs )
            Opt.master.check( ArgsParseState.new( @@argv ) )
        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.defineCheck( prog, author, year, defs, config )
            Spec.usage if Opt['help'].given
        end

        # Alias to Spec.command.
        def Spec.defineCheckHelp( prog, author, year, defs, config = {} )
            Spec.command( prog, author, year, defs, config )
        end

        # Same as "defineCheckHelp" except without automatic "help"
        # option processing.
        def Spec.defineCheck( prog, author, year, defs, config = {} )
            spec = Spec.new( author, year )
            spec.subcmd( prog, defs, config )
            Opt.master.check( ArgsParseState.new( @@argv ) )
        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.master

                master = MasterOpt.new( @author, @year,
                                        cmd, nil, :subcmd, nil )
                Opt.setMaster( master )
                subcmd = master

            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

            subcmd.setSubopt( *Spec.specify( defs ) )
            subcmd

        end


        # Specify and check options spec.
        #
        # @param table [Array<Array>] Option definition table.
        def Spec.specify( table )

            options = {}
            subcmds = {}

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

            table.each_index do |idx|

                i = table[ idx ]
                
                Spec.ArgCheck( i.class == Array, "Option table entry is not an Array" )

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

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

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

                    # Add 1 dummy entry for :subcmd type if needed.
                    table[ idx ] = [ i[0], i[1], nil, i[2] ]
                end

                Spec.ArgCheck( table[ idx ].length == 4, "Option table entry length not 4" )
            end

            
            table.each do |e|

                if e[0] == :subcmd

                    subcmds[ e[1] ] = Opt.subcmd( e[1], e[3] )

                else

                    option = nil

                    case e[0]

                    when :switch
                        option = Opt.switch( e[1], e[2], e[3] )

                    when :exclusive
                        option = Opt.exclusive( e[1], e[2], e[3] )

                    when :silent
                        option = Opt.exclusive( e[1], e[2], e[3], true )

                    when :single, :multi, :opt_single, :opt_multi, :opt_any
                        option = Opt.full( e[1], e[2], e[0], e[3] )

                    when :default
                        option = Opt.defaultOpt( e[3] )

                    else
                        raise "Unknown option type: \"#{e[0]}\"..."
                    end
                    
                    options[ option.name ] = option

                end
                    
            end
            
            [ options.values, subcmds.values ]
        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.master.usage
        end

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


        # Set optional footer for "usage".
        def Spec.setUsageFooter( str )
            Opt.master.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


        # 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

        # 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.master.checkAlso( opt, error, &check )
        end


        private

        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
            attr_reader :data
            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.
        @@master = 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      => true,
            :tab        => 12,
            :help_exit  => true,
            :error_exit => true,
        }


        # Set master option.
        def Opt.setMaster( master )
            @@master = master
            Opt.setSubcmd( master )
        end

        # Get master option.
        def Opt.master
            @@master
        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. Master is searched first and
        # then the flattened list of all options.
        def Opt.[](str)

            # Search Master first.
            ret = Opt.master.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
            @@master.name
        end


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


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


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


        # Return arguments (options) that have no switch.
        def Opt.default
            Opt.master.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 switch option spec.
        def Opt.switch( name, opt, doc = "No doc." )
            new( name, opt, :switch, doc, false )
        end

        # Create exclusive option spec.
        def Opt.exclusive( name, opt, doc = "No doc.", silent = false )
            o = new( name, opt, :exclusive, doc, false )
            o.silent = silent
            o
        end

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

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

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

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


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


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


        # Option name.
        attr_accessor :name

        # Short option string.
        attr_accessor :opt

        # 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

        # Is option hidden (usage).
        attr_accessor :silent

        # 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:
        # [name] Option name string.
        # [opt] Switch string.
        # [type] Option type. One of:
        #        * :switch
        #        * :single
        #        * :multi
        #        * :opt_single
        #        * :opt_multi
        #        * :opt_any
        #        * :default
        #        * :exclusive
        #        * :silent
        # [doc] Option documentation.
        # [value] Default value.

        def initialize( name, opt, type, doc, value = nil )
            @parent = nil
            @name = name
            @opt = opt
            @longOpt = "--#{name}"
            @type = type
            @value = value
            @doc = doc
            @silent = false
            # Whether option was set or not.
            @given = false
            @subopt = nil
            @subcmd = nil
            @rules = nil

            @config = @@config.dup

            Opt.addOpt( self )
        end


        # Set subcommand suboptions.
        #
        # @param opts [Array<Opt>] 
        def setSubopt( opts, subs )
            opts.each do |i|
                i.parent = self
            end

            subs.each do |i|
                i.parent = self
            end

            @subopt = opts
            @subcmd = subs
        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

            begin

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

                # Check for any missing valid arguments.
                checkMissing

            rescue Opt::MissingArgument, Opt::InvalidOption => err

                @@io.puts

                error( err.to_s )

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

                exit( 1 ) if Opt.master.config[ :error_exit ]

            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.master.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 for \"#{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

                            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.value = !o.value if !o.given
                            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 for \"#{args.get}\"...",
                                                  self )
                        else
                            # Default option.
                            o.given = true
                            o.value.push args.toValue
                            args.next
                        end

                    else
                        
                        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

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

            # Check for any exclusive args first.
            @subopt.each do |o|
                if o.type == :exclusive && 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 @config[ :subcheck ] && hasSubcmd
                # Compulsory Subcommand checking enabled.
                subcmdGiven = false
            else
                subcmdGiven = true
            end
            
            # Check for any subcmd args.
            sub = givenSubcmd
            if sub
                subcmdGiven = true
                # 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 ) unless subcmdGiven
            
        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
                error( err.to_s )

                usage( nil, true )

                # Included in 
#                #  Display the possible combination:
#                @@io.puts "\n  Option combinations:\n\n"
#                RuleDisplay.print( &@rules )

            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
                error( 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


        # 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
                value
            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 yieldOpt [Boolean] Pass Opt to block instead of its
        # value.
        def given( yieldOpt = false, &prog )
            if block_given?
                if @given
                    if yieldOpt
                        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
                @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
                @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; true
            else false
            end
        end


        # Option requires many arguments?
        def hasMany
            case @type
            when :multi, :opt_multi, :opt_any; 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?
            @silent
        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 [NilClass,Boolean] Exit program after help
        # display. Default to help_exit config if nil.
        # @param ruleHelp [NilClass,Boolean] Include rule help to help
        # display. Deault 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['help'].given
                usage
            elsif hasSubcmd
                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 += "\n"
                str += @config[ :header ]
                str += "\n"
            end

            str += usageCommand

            if @config[ :footer ]
                str += @config[ :footer ] 
                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.opt == nil
                    opt = o.longOpt
                else
                    opt = o.opt
                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 += format.call( "Options:", "" ) if hasSubcmd && hasVisibleOptions

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

            str += "\n" + format.call( "Subcommands:", "" ) if hasSubcmd

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

            str
        end


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

        # Como error printout.
        def error( str )
            @@io.puts "#{Opt.progname} error: #{str}"
        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


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

    end



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

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

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

#            # Set of default configs for printout.
#            @config = {
#                :header     => nil,
#                :footer     => nil,
#                :subcheck   => true,
#                :check      => true,
#                :tab        => 12,
#                :help_exit  => true,
#                :error_exit => true,
#            }
        end
                

        # Full command name.
        def fullCommand
            Opt.progname
        end


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

"
            str += suboptDoc

            str += "

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

"
        end

    end


    # Command argument parsing state.
    class ArgsParseState

        # @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

        def RuleCheck.check( opt, &rule )
            rc = RuleCheck.new( opt )
            rc.instance_eval( &rule )
        end

        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 )
            had = 0
            add = true
            scoreAll = 0
            i = 0
            while args[i]
                score = getScore( args[i] )
                had += 1 if ( score == 1 ) && add
                add = false if ( score == 0 )
                scoreAll += score
                i += 1
            end

            ( had == scoreAll && had > 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
                true
            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
    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

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

        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

    end


end