# = 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( , year, config = nil, &defs ) if config Opt.configOverlay( config ) end spec = Spec.new( , 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, , year, defs, config = {} ) Spec.defineCheck( prog, , year, defs, config ) Spec.usage if Opt['help'].given end # Alias to Spec.command. def Spec.defineCheckHelp( prog, , year, defs, config = {} ) Spec.command( prog, , year, defs, config ) end # Same as "defineCheckHelp" except without automatic "help" # option processing. def Spec.defineCheck( prog, , year, defs, config = {} ) spec = Spec.new( , 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( , year ) @author = @year = year Spec.ArgCheck( .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 ) = {} 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 [ option.name ] = option end end [ .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.( str ) Opt.master.( 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( = nil, data = nil ) super( ) @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. @@master. 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 ( 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( , year, name, opt, type, doc, value = nil ) @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.} " 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