# = 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 " 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. # # 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"... # # Usage: # como_simple -f [-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. # # === Subccommand 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 # # subcmd( "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." ], # ] ) # # checkRule 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 "subcmd" method call defines the main command # ("Opt.main") which represents the program. It has two "subcmd" # options. # # The rest of the "subcmd" methods define subcommands for the parent # command. This example includes one subcommand level, but multiple # levels are allowed. # # The "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 "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 ] [-u ] -f # # -fo Force operation. # -p User password. # -u Username. # -f File. # # # Option Combinations: # |--# One of: # | |--<-fo> # | |--# All of: # | | |-- # | | |-- # # Since the combination rule requires either "-fo" or both "password" # and "username" in a pair. # # 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. # [: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 coexist with other options. # [: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). # [:tab] Tab stop column for option documentation (default: 12). # [:help_exit] Exit program if help displayed (default: true). # [:error_exit] Exit program if error in options (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 "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 # "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 is 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 # # 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. 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 [] 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.main.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] 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.main.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] 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 subcmd.setSubopt( *Spec.specify( defs ) ) subcmd end # Specify and check options spec. # # @param table [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.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 # 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.main.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. @@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, :tab => 12, :help_exit => true, :error_exit => 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 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( "", "", :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 # ------------------------------------------------------------ # 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] 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_missing ] ) 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.main.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.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 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 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 error( 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 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 || 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; 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 [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['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 += "\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 "<>" 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 += suboptDocFormat( "Options:", "" ) if hasSubcmd && hasVisibleOptions @subopt.each do |o| next if o.silent? str += suboptDocFormat( o.opt ? o.opt : o.longOpt, 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 # 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 # 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 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 = " 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] 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 # 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: # | | |-- # | | |-- # | | |-- # | |-- # | |-- # | |-- # 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 # Dont care. def meh( *args ) [ "Ignoring", *args ] end end end