lib/como.rb in como-0.1.1 vs lib/como.rb in como-0.1.2

- old
+ new

@@ -44,11 +44,11 @@ # "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: @@ -98,21 +98,21 @@ # 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). Documentation string is taken from the option specification to # "usage" display. # @@ -120,49 +120,49 @@ # shell> como_simple -h # # would display the same "usage" screen except without the error # line. # -# === Subccommand example +# === Subcommand example # # Subcmd example includes a program which has subcommands. Subcommands # can have their own command line switches and options. # # ==== Program listing # # require "como" # include Como -# +# # Spec.program( "Programmer", "2013" ) do -# +# # 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 # @@ -210,27 +210,27 @@ # checked. Thus executing: # shell> como_subcmd add -f example # # Would result to: # como_subcmd error: Option combination mismatch! -# +# # Subcommand "add" usage: # como_subcmd add [-fo] [-p <password>] [-u <username>] -f <file> -# +# # -fo Force operation. # -p User password. # -u Username. # -f File. -# -# +# +# # Option Combinations: # |--# One of: # | |--<-fo> # | |--# All of: # | | |--<password> # | | |--<username> -# +# # Since the combination rule requires either "-fo" or both "password" # and "username" in a pair. # # Help is automatically provided on each command level, thus these are # both valid. @@ -239,13 +239,13 @@ # 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. @@ -270,11 +270,14 @@ # 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. +# [:exclusive] Option that does not coexist with other +# options. :exclusive can have arguments as with +# :opt_any, however :exclusive is documented like +# :switch. # [:silent] Option that does not coexist with other options and is not # displayed as an option in Usage Help display. In effect a # sub-option of :exclusive. # # Options use typically all the 4 option fields: @@ -295,11 +298,11 @@ # 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" @@ -316,18 +319,18 @@ # [:header] Header lines before standard usage printout. # [:footer] Footer lines after standard usage printout. # [:subcheck] Automatically check that a subcommand is provided # (default: true). # [:check_missing] Check for missing arguments (default: true). +# [:check_invalid] Error for unknown options (default: true). # [:tab] Tab stop column for option documentation (default: 12). # [:help_exit] Exit program if help displayed (default: true). -# [: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 @@ -392,11 +395,11 @@ # # 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. @@ -419,11 +422,11 @@ module Como # IO stream options for Como classes. class ComoCommon - + # Default value for display output. @@io = STDOUT # Set @@io. def ComoCommon.setIo( io ) @@ -465,11 +468,11 @@ # @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 + # 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 ) @@ -505,11 +508,11 @@ def subcmd( cmd, defs, config = {} ) unless Opt.main main = MainOpt.new( @author, @year, - cmd, nil, :subcmd, nil ) + cmd, nil, :subcmd, nil ) Opt.setMain( main ) subcmd = main else @@ -547,11 +550,11 @@ 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. @@ -561,14 +564,15 @@ # 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" ) + 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] ) @@ -577,35 +581,27 @@ 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 + when :switch, :exclusive, :silent, :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. @@ -724,19 +720,19 @@ @@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, + :autohelp => true, + :rulehelp => false, + :header => nil, + :footer => nil, + :subcheck => true, + :check_missing => true, + :check_invalid => true, + :tab => 12, + :help_exit => true, } # Set main option. def Opt.setMain( main ) @@ -753,17 +749,17 @@ # 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 @@ -842,37 +838,28 @@ # 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>", "<args>", :default, doc, [] ) + new( "<default>", "<default>", :default, doc, [] ) end + # Options iterator for all options. def Opt.each( &blk ) Opt.main.each &blk end + # Options iterator for given options. def Opt.each_given( &blk ) Opt.main.each_given( &blk ) end + # Overlay Opt default configuration options. def Opt.configOverlay( config ) @@config.merge!( config ) end @@ -887,11 +874,11 @@ # Option name. attr_accessor :name # Short option string. - attr_accessor :opt + attr_accessor :shortOpt # Long option string. attr_accessor :longOpt # Option type. @@ -904,13 +891,10 @@ 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 @@ -939,16 +923,15 @@ # [value] Default value. def initialize( name, opt, type, doc, value = nil ) @parent = nil @name = name - @opt = opt + @shortOpt = 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 @@ -959,11 +942,11 @@ end # Set subcommand suboptions. # - # @param opts [Array<Opt>] + # @param opts [Array<Opt>] def setSubopt( opts, subs ) opts.each do |i| i.parent = self end @@ -1003,29 +986,26 @@ begin # Parse and check for invalid arguments. begin - top = top.parse( argsState, top.config[ :check_missing ] ) + top = top.parse( argsState, top.config[ :check_invalid ] ) 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 ] - + exit( 1 ) end - + # Revert back to top after hierarchy travelsal. usageIfHelp # Check rules. cur = self @@ -1059,17 +1039,17 @@ if !o if checkInvalids raise \ InvalidOption.new( "Unknown option \"#{args.get}\"...", - self ) + self ) else o = findOpt( nil ) if !o raise \ InvalidOption.new( - "No default option specified for \"#{args.get}\"...", + "No default option specified to allow \"#{args.get}\"...", self ) else # Default option. o.value.push args.toValue args.next @@ -1079,20 +1059,20 @@ elsif o && o.hasArg args.next if ( !args.get || args.isOpt ) && - o.type != :opt_any + o.type != :opt_any && o.type != :exclusive raise MissingArgument.new( - "No argument given for \"#{o.opt}\"...", - self ) + "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 @@ -1103,12 +1083,12 @@ # Get one argument for single-option. if o.given raise \ InvalidOption.new( - "Too many arguments for option (\"#{o.name}\")...", - self ) + "Too many arguments for option (\"#{o.name}\")...", + self ) else o.value = args.toValue end args.next end @@ -1118,13 +1098,12 @@ else if !o raise InvalidOption.new( "No valid options specified...", - self ) + self ) else - o.value = !o.value if !o.given o.given = true args.next end end @@ -1140,25 +1119,26 @@ # Search for default option. o = findOpt( nil ) if !o raise \ InvalidOption.new( - "No default option specified for \"#{args.get}\"...", - self ) + "No default option specified to allow \"#{args.get}\"...", + self ) else # Default option. o.given = true o.value.push args.toValue args.next end else - + + # Subcmd. o.given = true args.next return o - + end end end @@ -1169,16 +1149,18 @@ # Check for any non-given required arguments recursively # through hierarchy of subcommands. MissingArgument Exception # is generated if argument is missing. def checkMissing + return unless config[ :check_missing ] + # Full cmd name. cmd = ( getParents.map do |i| i.name end ).join( ' ' ) # Check for any exclusive args first. @subopt.each do |o| - if o.type == :exclusive && o.given + if o.isExclusive && o.given return end end @@ -1186,12 +1168,12 @@ # subcmds. @subopt.each do |o| if o.isRequired unless o.given raise MissingArgument.new( - "Option \"#{o.opt}\" missing for \"#{cmd}\"...", - self ) + "Option \"#{o.opt}\" missing for \"#{cmd}\"...", + self ) end end end if hasSubcmd @@ -1213,13 +1195,13 @@ return sub.checkMissing end # If no subcmds are given, issue error. raise MissingArgument.new( - "Subcommand required for \"#{cmd}\"...", - self ) if subcmdMissing - + "Subcommand required for \"#{cmd}\"...", + self ) if subcmdMissing + end # Check option combination rules. @@ -1273,10 +1255,16 @@ end ret end + # Option's opt id. Short if exists otherwise long. + def opt + @shortOpt ? @shortOpt : @longOpt + end + + # All subcommand options, options and subcommands. def suball @subopt + @subcmd end @@ -1422,20 +1410,20 @@ # Option requires argument? def hasArg case @type - when :single, :multi, :opt_single, :opt_multi, :opt_any; true + when :single, :multi, :opt_single, :opt_multi, :opt_any, :exclusive; true else false end end # Option requires many arguments? def hasMany case @type - when :multi, :opt_multi, :opt_any; true + when :multi, :opt_multi, :opt_any, :exclusive; true else false end end @@ -1448,14 +1436,24 @@ end # Test if option is silent. def silent? - @silent + @type == :silent end + # Test if option is exclusive. In addition :exclusive also + # :silent is exclusive. + def isExclusive + case @type + when :exclusive, :silent; true + else false + end + end + + # Test if option is of switch type. def isSwitch case @type when :switch, :exclusive, :default; true else false @@ -1485,11 +1483,11 @@ # 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 @@ -1513,22 +1511,22 @@ # Usage printout for command. def usageCommand str = "" - str += " + str += "\ Subcommand \"#{@name}\" usage: #{fullCommand} #{cmdline.join(" ")} " str += suboptDoc str += "\n" end # Usage info for Opt:s. - def usageNormal + def usageNormalOld str = "" if @config[ :header ] str += "\n" str += @config[ :header ] @@ -1536,18 +1534,39 @@ end str += usageCommand if @config[ :footer ] - str += @config[ :footer ] + str += @config[ :footer ] str += "\n" end str end + # Usage info for Opt:s. + def usageNormal + str = "" + if @config[ :header ] + str += @config[ :header ] + else + str += "\n" + end + + str += usageCommand + + if @config[ :footer ] + str += @config[ :footer ] + else + str += "\n" + end + + str + end + + # Return cmdline usage strings for options in an Array. def cmdline opts = [] @subopt.each do |o| @@ -1566,24 +1585,24 @@ name = " <#{o.name}>#{prural}" else name = "" end - if o.opt == nil + if o.shortOpt == nil opt = o.longOpt else - opt = o.opt + opt = o.shortOpt end - + if o.isRequired opts.push "#{opt}#{name}" else opts.push "[#{opt}#{name}]" end end - + if hasSubcmd opts.push "<<subcommand>>" end opts @@ -1595,15 +1614,15 @@ def suboptDoc str = "" # format = Proc.new do |s,d| ( " %-#{@config[ :tab ]}s%s\n" % [ s, d ] ) end - str += suboptDocFormat( "Options:", "" ) if hasSubcmd && hasVisibleOptions + str += " Options:\n" if hasSubcmd && hasVisibleOptions @subopt.each do |o| next if o.silent? - str += suboptDocFormat( o.opt ? o.opt : o.longOpt, o.doc ) + str += suboptDocFormat( o.opt, o.doc ) end str += "\n" + suboptDocFormat( "Subcommands:", "" ) if hasSubcmd @subcmd.each do |o| @@ -1624,20 +1643,20 @@ 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}" + STDERR.puts "\n#{Opt.progname} error: #{str}" end - + # ------------------------------------------------------------ # Internal methods: private @@ -1703,20 +1722,20 @@ # 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 @@ -1729,33 +1748,33 @@ # 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 + def usageCommandOld str = " Usage: #{fullCommand} #{cmdline.join(" ")} " @@ -1766,10 +1785,24 @@ Copyright (c) #{Opt.year} by #{Opt.author} " end + # Usage printout for command. + def usageCommand + str = "\ + #{fullCommand} #{cmdline.join(" ")} + +" + str += suboptDoc + + str += " + + Copyright (c) #{Opt.year} by #{Opt.author} +" + end + end # Command argument parsing state. class ArgsParseState @@ -1886,10 +1919,10 @@ end # Incremental options in order i.e. have to have previous # to have later. def incr( *args ) - + # Opts given consecutive. consecutiveCnt = 0 # Consecutive flag. consecutive = true