# # Getopt::Declare - Declaratively Expressed Command-Line Arguments via Regular Expressions # # Ruby port of Perl's Getopt::Declare, version 1.21, # released May 21, 1999. # # $Release Version: 1.28 $ # $Date: 2007/01/15 10:53:09 $ # by Gonzalo Garramuņo # # For detailed instructions, see Declare.rdoc file # # Ruby Port: # Copyright (c) 2004, Gonzalo Garramuno. All Rights Reserved. # This package is free software. It may be used, redistributed # and/or modified under the terms of the Perl Artistic License # (see http://www.perl.com/perl/misc/Artistic.html) # # Original Perl Implementation: # Damian Conway (damian@conway.org) # Copyright (c) 1997-2000, Damian Conway. All Rights Reserved. # This package is free software. It may be used, redistributed # and/or modified under the terms of the Perl Artistic License # (see http://www.perl.com/perl/misc/Artistic.html) # require File.expand_path(File.join(File.dirname(__FILE__), "DelimScanner")) # Verifies that code is valid Ruby code. returns false if not def valid_syntax?(code, fname = 'parser_code') eval("BEGIN {return true}\n#{code}", nil, fname, 0) rescue Exception false end # An add-on to the String class class String # Expand all tabs to spaces def expand_tabs!( tabstop = 8 ) while self.sub!(/(^|\n)([^\t\n]*)(\t+)/sex) { |f| val = ( tabstop * "#$3".length() - ("#$2".length() % tabstop) ) "#$1#$2" + (" " * val) } end self end # Return new string with all tabs set to spaces def expand_tabs( tabstop = 8 ) h = self.dup while h.sub!(/(^|\n)([^\t\n]*)(\t+)/sex) { |f| val = ( tabstop * "#$3".length() - ("#$2".length() % tabstop) ) "#$1#$2" + (" " * val) } end h end end # Regex for removing bracket directives BracketDirectives = /\[\s*(?:ditto|tight|strict|no\s*case|repeatable|debug|required|mutex:.*|implies:.*|excludes:.*|requires:.*|cluster:.*)\s*\]/ module Getopt # Main Class class Declare VERSION = '1.28' # For debugging, use [debug] and it will output the ruby code as .CODE.rb @@debug = false # Main separator used to distinguish arguments in Getopt/Declare spec. # By default, one or more tabs or 3 spaces or more. @@separator = '(?:\t+| {3}[^<])' # Class used to handle the beginning of options class StartOpt # Returns regex used matching start of options def matcher(g) '(?:()' end # Returns code used def code(*t) '' end # Returns how to cache code in class def cachecode(a,b) '' end # Helps build regex that matches parameters of flags def trailer nil end # Helps build regex that matches parameters of flags def ows(g) g end end # StartOpt # Class used to handle the ending of options class EndOpt < StartOpt # Returns regex used matching end of options def matcher(g) '())?' end end # EndOpt # Class used to handle scalar (ie.non-array) parameters class ScalarArg @@stdtype = {} # (re)set standard types def ScalarArg._reset_stdtype @@stdtype = { ':i' => { :pattern => '(?:(?:%T[+-]?)%D+)' }, ':n' => { :pattern => '(?:(?:%T[+-]?)(?:%D+(?:%T\.%D*)?' + '(?:%T[eE](?:[+-])?%D+)?|%T\.%D+(?:%T[eE](?:[+-])?%D+)?))', }, ':s' => { :pattern => '(?:%T(?:\S|\0))+(?=\s|\0|\z)' }, ':qs' => { :pattern => %q{"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|(?:%T(?:\S|\0))+} }, ':id' => { :pattern => '%T[a-zA-Z_](?:%T\w)*(?=\s|\0|\z)' }, ':d' => { :pattern => '(?:%T(?:\S|\0))+', :action => %q% reject( (_VAL_.nil? || !test(?d, _VAL_) ), "in parameter '#{_PARAM_}' (\"#{_VAL_}\" is not a directory)")% }, ':if' => { :pattern => '%F(?:%T(?:\S|\0))+(?=\s|\0|\z)', :action => %q% reject( (_VAL_.nil? || _VAL_ != "-" && !test(?r, _VAL_) ), "in parameter '#{_PARAM_}' (file \"#{_VAL_}\" is not readable)")% }, ':of' => { :pattern => '%F(?:%T(?:\S|\0))+(?=\s|\0|\z)', :action => %q% reject( (_VAL_.nil? || _VAL_ != "-" && test(?r, _VAL_) && !test(?w, _VAL_)), "in parameter '#{_PARAM_}' (file \"#{_VAL_}\" is not writable)")% }, '' => { :pattern => ':s', :ind => 1 }, ':+i' => { :pattern => ':i', :action => %q%reject( _VAL_ <= 0, "in parameter '#{_PARAM_}' (#{_VAL_} must be an integer greater than zero)")%, :ind => 1 }, ':+n' => { :pattern => ':n', :action => %q%reject( _VAL_ <= 0.0, "in parameter '#{_PARAM_}' (#{_VAL_} must be a number greater than zero)")%, :ind => 1 }, ':0+i' => { :pattern => ':i', :action => %q%reject( _VAL_ < 0, "in parameter '#{_PARAM_}' (#{_VAL_} must be an positive integer)")%, :ind => 1 }, ':0+n' => { :pattern => ':n', :action => %q%reject( _VAL_ < 0, "in parameter '#{_PARAM_}' (#{_VAL_} must be an positive number)")%, :ind => 1 }, } end # _reset_stdtype # Given a standard type name, return the corresponding regex # pattern or nil def ScalarArg.stdtype(name) seen = {} while (!seen[name] && @@stdtype[name] && @@stdtype[name][:ind]) seen[name] = 1; name = @@stdtype[name][:pattern] end return nil if seen[name] || !@@stdtype[name] @@stdtype[name][:pattern] end def stdtype(name) ScalarArg.stdtype(name) end # Given the name of a type, return its corresponding action(s) def ScalarArg.stdactions(name) seen = {} actions = [] while (!seen[name] && @@stdtype[name] && @@stdtype[name][:ind]) seen[name] = 1 if @@stdtype[name][:action] actions.push( @@stdtype[name][:action] ) end name = @@stdtype[name][:pattern] end if @@stdtype[name] && @@stdtype[name][:action] actions.push( @@stdtype[name][:action] ) end return actions end # Add a new (user defined) type to the standard types def ScalarArg.addtype(abbrev, pattern, action, ref) typeid = ":#{abbrev}" unless (pattern =~ /\S/) pattern = ":s" ref = 1 end @@stdtype[typeid] = {} @@stdtype[typeid][:pattern] = "(?:#{pattern})" if pattern && !ref @@stdtype[typeid][:pattern] = ":#{pattern}" if pattern && ref @@stdtype[typeid][:action] = action if action @@stdtype[typeid][:ind] = ref end attr :name attr :type attr :nows # Constructor def initialize(name, type, nows) @name = name @type = type @nows = nows end # Create regexp to match parameter def matcher(g) trailing = g ? '(?!'+Regexp::quote(g)+')' : '' # Find type in list of standard (and user) types stdtype = stdtype(@type) # Handle stdtypes that are specified as regex in parameter if (!stdtype && @type =~ %r"\A:/([^/]+)/\Z" ) stdtype = "#$1" end if stdtype.nil? raise "Error: bad type in Getopt::Declare parameter variable specification near '<#{@name}#{@type}>'\nValid types are:\n" + @@stdtype.keys.inspect end stdtype = stdtype.dup # make a copy, as we'll change it in place stdtype.gsub!(/\%D/,"(?:#{trailing}\\d)") stdtype.gsub!(/\%T/,trailing) unless ( stdtype.sub!("\%F","") ) stdtype = Getopt::Declare::Arg::negflagpat + stdtype end return "(?:#{stdtype})" end # Return string with code to process parameter def code(*t) if t[0] pos1 = t[0].to_s else pos1 = '0' end c = conversion c = "\n _VAL_ = _VAL_#{c} if _VAL_" if c code = <<-EOS _VAR_ = %q|<#{@name}>| _VAL_ = @@m[#{pos1}] _VAL_.tr!("\\0"," ") if _VAL_#{c} EOS actions = Getopt::Declare::ScalarArg::stdactions(@type) for i in actions next if i.nil? # i.sub!(/(\s*\{)/, '\1 module '+t[1]) code << " begin #{i} end " end code << " #{@name} = _VAL_\n" end # Based on parameter type, default conversion to apply def conversion pat = @@stdtype[@type] ? @@stdtype[@type][:pattern] : '' [ @type, pat ].each { |t| case t when /^\:0?(\+)?i$/ return '.to_i' when /^\:0?(\+)?n$/ return '.to_f' end } return nil end # Return string with code to cache argument in Getopt::Declare's cache def cachecode(ownerflag, itemcount) if itemcount > 1 " @cache['#{ownerflag}']['<#{@name}>'] = #{@name}\n" else " @cache['#{ownerflag}'] = #{@name}\n" end end # Helps build regex that matches parameters of flags def trailer nil # MEANS TRAILING PARAMETER VARIABLE (in Perl,was '') end # Helps build regex that matches parameters of flags # Wraps parameter passed for #$1, etc. matching def ows(g) return '[\s|\0]*(' + g + ')' unless @nows '('+ g +')' end end # ScalarArg # Class used to handle array arguments class ArrayArg < ScalarArg # Create regexp to match array def matcher(g) suffix = !g.nil? ? '([\s\0]+)' : '' scalar = super # contains regex to match a scalar element # we match one obligatory element, and one or more optionals ')*' return scalar + '(?:[\s\0]+' + scalar + ')*' + suffix end # Return string with code to process array parameter def code(*t) if t[0] pos1 = t[0].to_s else pos1 = '0' end code = <<-EOS _VAR_ = %q|<#{@name}>| _VAL_ = nil #{@name} = (@@m[#{pos1}]||'').split(' ').map { |i| i.tr("\\0", " ") } EOS # Handle conversion to proper type c = conversion if c code << " #{@name}.map! { |i| i#{c} }\n" end actions = Getopt::Declare::ScalarArg::stdactions(@type) if actions.size > 0 code << " for _VAL_ in #{@name}\n" for i in actions code << " #{i}\n" end code << " end\n\n" end return code end # Return string with code to cache array in Getopt::Declare's cache def cachecode(ownerflag, itemcount) if itemcount > 1 " @cache['#{ownerflag}']['<#{@name}>'] = [] unless @cache['#{ownerflag}']['<#{@name}>'] @cache['#{ownerflag}']['<#{@name}>'] = #{@name}\n" else " @cache['#{ownerflag}'] = #{@name}\n" end end end # ArrayArg # Class used to handle punctuations (., -, etc.) class Punctuator # Constructor def initialize(text, nows) @text = text @nows = nows end # Return regex that matches this punctuation def matcher(g) Arg::negflagpat + Regexp::quote(@text) end # Return string with code to process punctuation def code(*t) if t[0] pos1 = t[0].to_s else pos1 = '0' end " if @@m[#{pos1}] && !@@m[#{pos1}].empty? _PUNCT_['#{@text}'] = @@m[#{pos1}] end " end # Return string with code to cache punctuation in Getopt::Declare's cache def cachecode(ownerflag, itemcount) if itemcount > 1 " @cache['#{ownerflag}']['#{@text}'] = _PUNCT_['#{@text}']\n" else " unless @cache['#{ownerflag}']\n" + " @cache['#{ownerflag}'] = _PUNCT_['#{@text}'] || 1\n" + " end\n" end end # Helps build regex that matches parameters of flags def trailer @text end # Helps build regex that matches parameters of flags # Wraps parameter passed for #$1, etc. matching def ows(g) return '[\s\0]*(' + g + ')' unless @nows '(' + g + ')' end #ows end # Punctuator # Class used to handle other arguments (flags, etc) class Arg @@nextid = 0 Helpcmd = %w( -help --help -Help --Help -HELP --HELP -h -H ) @@helpcmdH = {} for i in Helpcmd; @@helpcmdH[i] = 1; end def Arg.besthelp for i in Helpcmd; return i if @@helpcmdH[i]; end end # Create regex of help flags based on help shortcuts left def Arg.helppat @@helpcmdH.keys.join('|') end Versioncmd = %w( -version --version -Version --Version -VERSION --VERSION -v -V ) @@versioncmdH = {} for i in Versioncmd; @@versioncmdH[i] = 1; end def Arg.bestversion for i in Versioncmd; return i if @@versioncmdH[i]; end end # Create regex of version flags based on help shortcuts left def Arg.versionpat @@versioncmdH.keys.join('|') end @@flags = [] @@posflagpat = nil @@negflagpat = nil def Arg.clear @@flags = [] @@nextid = 0 @@posflagpat = nil @@negflagpath = nil end # Return string with regex that avoids all flags in declaration def Arg.negflagpat(*t) if !@@negflagpat && @@flags @@negflagpat = ( @@flags.map { |i| "(?!" + Regexp::quote(i) + ")" } ).join('') else @@negflagpat end end # Return string with regex that matches any of the flags in declaration def Arg.posflagpat(*t) if !@@posflagpat && @@flags @@posflagpat = '(?:' + ( @@flags.map { |i| Regexp::quote(i) } ).join('|') + ')' else @@posflagpat end end attr_accessor :flag, :args, :actions, :ditto, :nocase attr_accessor :required, :id, :repeatable, :desc attr_accessor :requires # def found_requires expr = @requires.gsub(/((?:&&|\|\|)?\s*(?:[!(]\s*)*)([^ \t\n|&\)]+)/x, '\1_FOUND_[\'\2\']') if !valid_syntax?( expr ) raise "Error: bad condition in [requires: #{original}]\n" end expr end # Constructor def initialize(spec, desc, dittoflag) first = 1 @@nextid += 1 @flag = '' @foundid = nil @args = [] @actions = [] @ditto = dittoflag @required = false @requires = nil @id = @@nextid @desc = spec.dup @items = 0 @nocase = false @desc.sub!(/\A\s*(.*?)\s*\Z/,'\1') while spec && spec != '' begin # OPTIONAL if spec.sub!( /\A(\s*)\[/, '\1' ) @args.push( StartOpt.new ) next elsif spec.sub!(/\A\s*\]/,"") @args.push( EndOpt.new ) next end # ARG se = DelimScanner::new( spec ) tmp = se.scanBracketed('<>') arg = nows = nil arg, spec, nows = tmp[:match], tmp[:suffix], tmp[:prefix] if tmp if arg arg =~ /\A(\s*)(<)([a-zA-Z]\w*)(:[^>]+|)>/ or raise "Error: bad Getopt::Declare parameter variable specification near '#{arg}'\n" # NAME,TYPE,NOW details = [ "#$3", "#$4", !first && !(nows.length>0) ] if spec && spec.sub!( /\A\.\.\./, "") # ARRAY ARG @args.push( ArrayArg.new(*details) ) else # SCALAR ARG @args.push( ScalarArg.new(*details) ) end @items += 1 next # PUNCTUATION elsif spec.sub!( /\A(\s*)((\\.|[^\] \t\n\[<])+)/, '' ) ows, punct = $1, $2 punct.gsub!( /\\(?!\\)(.)/, '\1' ) if first spec =~ /\A(\S+)/ @foundid = "#{punct}#{$1}" @flag = punct @@flags.push( punct ) else @args.push( Punctuator.new(punct, !(ows.size > 0)) ) @items += 1 end else break end # if arg/spec.sub ensure first = nil end end # while @@helpcmdH.delete(@flag) if @@helpcmdH.key?(@flag) @@versioncmdH.delete(@flag) if @@versioncmdH.key?(@flag) end # initialize # Return String with code to parse this argument (ie. flag) def code(*t) owner = t[0] mod = t[1] code = "\n" flag = @flag clump = owner.clump i = 0 nocasei = ((Getopt::Declare::nocase || @nocase) ? 'i' : '') code << " catch(:paramout) do\n while " code += !@repeatable? "!_FOUND_['" + self.foundid + "']" : "true" if (flag && (clump==1 && flag !~ /\A[^a-z0-9]+[a-z0-9]\Z/i || (clump<3 && @args.size > 0 ))) code << ' and !_lastprefix' end code <<' begin catch(:param) do _pos = _nextpos if _args _PUNCT_ = {} ' if flag != '' # This boundary is to handle -- option, so that if user uses # --foo and --foo is not a flag, it does not become # -- and unused: 'foo', but an error saying flag '--foo' not defined. boundary = '' boundary = '(\s+|\Z)' if flag =~ /^(--|-|\+|\+\+)$/ code << ' _args && _pos = gindex( _args, /\G[\s|\0]*' + Regexp::quote(flag) + boundary + '/' + nocasei + ", _pos) or throw(:paramout) unless @_errormsg @_errormsg = %q|incorrect specification of '" + flag + "' parameter| end " elsif ( ScalarArg::stdtype(@args[0].type)||'') !~ /\%F/ code << "\n throw(:paramout) if @_errormsg\n" end code << "\n _PARAM_ = '" + self.name + "'\n" trailer = [] i = @args.size-1 while i > 0 trailer[i-1] = @args[i].trailer trailer[i-1] = trailer[i] unless trailer[i-1] i -= 1 end # while i if @args code << "\n"+' _args && _pos = gindex( _args, /\G' @args.each_with_index { |arg, i| code << arg.ows(arg.matcher(trailer[i])) } code << '/x' + nocasei + ", _pos ) or throw(:paramout)\n" end # if @args @args.each_with_index { |arg, i| code << arg.code(i,mod) #, $flag ???? } if flag mutexlist = owner.mutex[flag] ? ( owner.mutex[flag].map {|i| "'#{i}'"} ).join(',') : '' code << " if _invalid.has_key?('#{flag}') @_errormsg = %q|parameter '#{flag}' not allowed with parameter '| + _invalid['#{flag}'] + %q|'| throw(:paramout) else for i in [#{mutexlist}] _invalid[i] = '#{flag}' end end #if/then " end for action in @actions #action.sub!( /(\s*\{)/, '\1 module '+mod ) # @TODO code << "\n " + action + "\n" end if flag && @items==0 code << "\n @cache['#{flag}'] = '#{flag}'\n" if @ditto code << "\n @cache['#{@ditto.flag}'] = '#{flag}'\n" end end if @items > 1 code << " @cache['#{self.name}'] = {} unless @cache['#{self.name}'].kind_of?(Hash)\n" if @ditto code << "\n @cache['#{@ditto.name}'] = {} unless @cache['#{@ditto.name}'].kind_of?(Hash)\n" end end for subarg in @args code << subarg.cachecode(self.name,@items) if ditto code << subarg.cachecode(@ditto.name,@items) end end if flag =~ /\A([^a-z0-9]+)/i code << ' _lastprefix = "'+ Regexp::quote("#$1") + '"' + "\n" else code << " _lastprefix = nil\n" end code << " _FOUND_['"+ self.foundid + "'] = 1 throw :arg if _pos > 0 _nextpos = _args.size throw :alldone end # catch(:param) end # begin end # while end # catch(:paramout) " code end # Return name of argument, which can be flag's name or variable's name def name return @flag unless @flag.empty? for i in @args return "<#{i.name}>" if i.respond_to?(:name) end raise "Unknown flag name for parameter #{self.desc}" end # Return foundid of argument, which can be flag's name or variable's name def foundid return @foundid || self.name end end # Arg private class << self @nocase = false attr_accessor :nocase end # # This is an additional function added to the class to simulate Perl's # pos() \G behavior and m///g # # It performs a regex match, and returns the last index position of the # match or nil. On successive invocations, it allows doing regex matches # NOT from the beginning of the string easily. # # Class Array @@m stores the list of matches, as #$1 and similar # variables have short lifespan in ruby, unlike perl. # def gindex(str, re, pos) @@m.clear() if pos = str.index( re, pos ) l = $&.size # length of match if l > 0 @@m[0] = "#$1" @@m[1] = "#$2" @@m[2] = "#$3" @@m[3] = "#$4" @@m[4] = "#$5" @@m[5] = "#$6" @@m[6] = "#$7" @@m[7] = "#$8" @@m[8] = "#$9" pos += l end end pos end # Given an array or hash, flatten them to a string def flatten(val, nested = nil) case val when Array return val.map{ |i| flatten(i,1) }.join(" ") when Hash return val.keys.map{ |i| nested || i =~ /^-/ ? [i, flatten(val[i],1)] : [flatten(val[i],1)] }.join(" ") else return val end end # Read the next line from stdin def _get_nextline $stdin.readline end # For each file provided and found, read it in def _load_sources( _get_nextline, files ) text = '' found = [] for i in files begin f = File.open(i,"r") rescue next end if f.tty? found.push( '' ) _get_nextline = method(:_get_nextline) else found.push( i ); t = f.readlines.join(' ') t.tr!('\t\n',' ') text += t end end return nil unless found.size > 0 text = $stdin.readline if text.empty? return [text, found.join(' or ')] end # Check parameter description for special options def _infer(desc, arg, mutex) while desc.sub!(/\[\s*mutex:\s*(.*?)\]/i,"") _mutex(mutex, "#$1".split(' ')) end if desc =~ /\[\s*no\s*case\s*\]/i if arg arg.nocase = true else nocase = true end end if !arg.nil? if desc =~ /.*\[\s*excludes:\s*(.*?)\]/i _exclude(mutex, arg.name, ("#$1".split(' '))) end if desc =~ /.*\[\s*requires:\s*(.*?)\s*\]/i arg.requires = "#$1" end arg.required = ( desc =~ /\[\s*required\s*\]/i ) arg.repeatable = ( desc =~ /\[\s*repeatable\s*\]/i ) end _typedef(desc) while desc.sub!(/.*?\[\s*pvtype:\s*/,"") end # Extract a new type from the description and add it to the list # of standard types def _typedef(desc) se = DelimScanner::new( desc ) tmp = se.scanQuotelike name = nil name, desc = tmp[:delimText], tmp[:suffix] if tmp unless name desc.sub!(/\A\s*([^\] \t\n]+)/,"") and name = "#$1" end raise "Error: bad type directive (missing type name): [pvtype: " + desc[0,desc.index(']')||20] + "....\n" unless name se = DelimScanner::new( desc ) tmp = se.scanQuotelike('\s*:?\s*') # @TODO What is element 2 of extract_quotelike? :trail is a fake here # pat,desc,ind = (extract_quotelike(desc,'\s*:?\s*'))[5,1,2] pat = ind = nil pat, desc, ind = tmp[:match], tmp[:suffix], tmp[:prefix] if tmp pat = pat[1..-2] if pat unless pat desc.sub!(/\A\s*(:?)\s*([^\] \t\n]+)/,"") and pat = "#$2" and ind = "#$1" end pat = '' unless pat se = DelimScanner::new( desc ) action = se.extractCodeblock || '' desc.sub!( Regexp::quote(action).to_re, '' ) action = action[1..-2] raise "Error: bad type directive (expected closing ']' but found " + "'#$1' instead): [pvtype: #{name} " + (pat ? "/#{pat}/" : '') + " action:#{action} #$1#$2....\n" if desc =~ /\A\s*([^\] \t\n])(\S*)/ Getopt::Declare::ScalarArg::addtype(name,pat,action,ind=~/:/) end # Handle quote replacements for [ditto] flag def _ditto(originalflag, originaldesc, extra) if originaldesc =~ /\n.*\n/ originaldesc = "Same as #{originalflag} " else originaldesc.chomp originaldesc.gsub!(/\S/,'"') while originaldesc.gsub!(/"("+)"/,' \1 ') end originaldesc.gsub!(/""/,'" ') end "#{originaldesc}#{extra}\n" end # Check mutex conditions def _mutex(mref, mutexlist) for flag in mutexlist mref[flag] = [] unless mref[flag] for otherflag in mutexlist next if flag == otherflag mref[flag].push( otherflag ) end end end # Check exclude conditions def _exclude(mref, excluded, mutexlist) for flag in mutexlist unless flag == excluded mref[flag] = [] unless mref[flag] mref[excluded] = [] unless mref[excluded] mref[excluded].push( flag ) mref[flag].push( excluded ) end end end # Returns a regex to match a single argument line def re_argument /\A(.*?\S.*?#{@@separator})(.*?\n)/ end # Returns a regex to keep matching a multi-line description # for an argument. def re_more_desc /\A((?![ \t]*(\{|\n)|.*?\S.*?#{@@separator}.*?\S).*?\S.*\n)/ end public # Constructor def initialize(*opts) @cache = nil Getopt::Declare::Arg::clear # HANDLE SHORT-CIRCUITS return if opts.size==2 && (!opts[1] || opts[1] == '-SKIP') grammar, source = opts if grammar.nil? raise "Error: No grammar description provided." end ### REMOVED PREDEF GRAMMAR AS IT WAS NOT DOCUMENTED NOR ### WORKING IN PERL'S Declare.pm VERSION. # PRESERVE ESCAPED '['s grammar.gsub!(/\\\[/,"\29") # MAKE SURE GRAMMAR ENDS WITH A NEWLINE. grammar.sub!(/([^\n])\Z/,'\1'+"\n") @usage = grammar.dup # SET-UP i = grammar _args = [] _mutex = {} _strict = false _all_repeatable = false _lastdesc = nil arg = nil Getopt::Declare::nocase = false Getopt::Declare::ScalarArg::_reset_stdtype # CONSTRUCT GRAMMAR while i.length > 0 # COMMENT: i.sub!(/\A[ \t]*#.*\n/,"") and next # TYPE DIRECTIVE: se = DelimScanner::new( i ) if i =~ /\A\s*\[\s*pvtype:/ _action = se.extractBracketed("[") if _action i.sub!( Regexp::quote( _action ).to_re, "" ) ### @GGA: added i.sub!(/\A[ \t]*\n/,"") ### @GGA: added _action.sub!(/.*?\[\s*pvtype:\s*/,"") _typedef(_action) next end # if end # ACTION codeblockDelimiters = { '{' => '}', } _action = se.extractCodeblock(codeblockDelimiters) if _action i.sub!( Regexp::quote(_action ).to_re, "" ) i.sub!(/\A[ \t]*\n/,"") _action = _action[1..-2] if !valid_syntax?( _action ) raise "Error: bad action in Getopt::Declare specification:" + "\n\n#{_action}\n\n\n" end if _args.length == 0 raise "Error: unattached action in Getopt::Declare specification:\n#{_action}\n" + "\t(did you forget the tab after the preceding parameter specification?)\n" end _args.last.actions.push( _action ) next elsif i =~ /\A(\s*[{].*)/ raise "Error: incomplete action in Getopt::Declare specification:\n$1.....\n" + "\t(did you forget a closing '}'?)\n" end # ARG + DESC: if i.sub!(re_argument,"") spec = "#$1".strip desc = "#$2" _strict ||= desc =~ /\[\s*strict\s*\]/ while i.sub!(re_more_desc,"") desc += "#$1" end ditto = nil if _lastdesc and desc.sub!(/\A\s*\[\s*ditto\s*\]/,_lastdesc) ditto = arg else _lastdesc = desc end # Check for GNU spec line like: -d, --debug arg = nil if spec =~ /(-[\w_\d]+),\s+(--?[\w_\d]+)(\s+.*)?/ specs = ["#$1#$3", "#$2#$3"] specs.each { |spec| arg = Arg.new(spec,desc,ditto) _args.push( arg ) _infer(desc, arg, _mutex) ditto = arg } else arg = Arg.new(spec,desc,ditto) _args.push( arg ) _infer(desc, arg, _mutex) end next end # OTHERWISE: DECORATION i.sub!(/((?:(?!\[\s*pvtype:).)*)(\n|(?=\[\s*pvtype:))/,"") decorator = "#$1" _strict ||= decorator =~ /\[\s*strict\s*\]/ _infer(decorator, nil, _mutex) _all_repeatable = true if decorator =~ /\[\s*repeatable\s*\]/ @@debug = true if decorator =~ /\[\s*debug\s*\]/ end # while i.length _lastactions = nil for i in _args if _lastactions && i.ditto && i.actions.size == 0 i.actions = _lastactions else _lastactions = i.actions end if _all_repeatable i.repeatable = 1 end end # Sort flags based on criteria described in docs # Sadly, this cannot be reduced to sort_by _args = _args.sort() { |a,b| cond1 = ( b.flag.size <=> a.flag.size ) cond2 = ( b.flag == a.flag and ( b.args.size <=> a.args.size ) ) cond3 = ( a.id <=> b.id ) cond1 = nil if cond1 == 0 cond2 = nil if cond2 == 0 cond1 or cond2 or cond3 } # Handle clump clump = (@usage =~ /\[\s*cluster:\s*none\s*\]/i) ? 0 : (@usage =~ /\[\s*cluster:\s*singles?\s*\]/i) ? 1 : (@usage =~ /\[\s*cluster:\s*flags?\s*\]/i) ? 2 : (@usage =~ /\[\s*cluster:\s*any\s*\]/i) ? 3 : (@usage =~ /\[\s*cluster:(.*)\s*\]/i) ? "r" : 3 raise "Error: unknown clustering mode: [cluster:#$1]\n" if clump == "r" # CONSTRUCT OBJECT ITSELF @args = _args @mutex = _mutex @helppat = Arg::helppat() @verspat = Arg::versionpat() @strict = _strict @clump = clump @source = '' @tight = @usage =~ /\[\s*tight\s*\]/i @caller = caller() # VESTIGAL DEBUGGING CODE if @@debug f = File.new(".CODE.rb","w") and f.puts( code() ) and f.close() end # DO THE PARSE (IF APPROPRIATE) if opts.size == 2 return nil unless parse(source) else return nil unless parse() end end # initialize # Parse the parameter description and in some cases, # optionally eval it, too. def parse(*opts) source = opts[0] _args = nil _get_nextline = proc { nil } if source case source when Method _get_nextline = source _args = _get_nextline.call(self) source = '[METHOD]' when Proc _get_nextline = source _args = _get_nextline.call(self) source = '[PROC]' when IO if source.fileno > 0 && source.tty? _get_nextline = method(:_get_nextline) _args = $stdin.readline source = '' else _args = source.readlines.join(' ') _args.tr!('\t\n',' ') end when :build, :skip return 0 when Array if source.length() == 1 && !source[0] || source[0] == "-BUILD" || source[0] == "-SKIP" return 0 elsif source.length() == 2 && source[0] == "-ARGV" if !source[1] or !source[1] === Array raise 'Error: parse(["-ARGV"]) not passed an array as second parameter.' end _args = source[1].map { |i| i.tr( " \t\n", "\0\0\0" ) }.join(' ') source = '' elsif source.length() == 1 && source[0] == "-STDIN" _get_nextline = method(:_get_nextline) _args = $stdin.readline source = '' elsif source.length() == 1 && source[0] == '-CONFIG' progname = "#{$0}rc" progname.sub!(%r#.*/#,'') home = ENV['HOME'] || '' _args, source = _load_sources( _get_nextline, [ home+"/.#{progname}", ".#{progname}" ] ) else # Bunch of files to load passed to parse() _args, source = _load_sources( _get_nextline, source ) end when String # else/case LITERAL STRING TO PARSE _args = source.dup source = source[0,7] + '...' if source && source.length() > 7 source = "\"#{source[0..9]}\"" else raise "Unknown source type for Getopt::Declare::parse" end # case return 0 unless _args source = " (in #{source})" else _args = ARGV.map { |i| i.tr( " \t\n", "\0\0\0" ) }.join(' ') source = '' end @source = source begin err = eval( code(@caller) ) if $@ # oops, something wrong... exit puts "#{$!}: #{$@.inspect}" exit(1) end if !err exit(1) end rescue raise end true end def type(*t) Getopt::Declare::ScalarArg::addtype(t) end # Print out version information and maybe exit def version(*t) prog = "#{$0}" begin filedate = File.stat( prog ).mtime.localtime() rescue filedate = 'Unknown date' end prog.sub!(%r#.*/#,'') r = '' if defined?($VERSION) r << "\n#{prog}: version #{$VERSION} (#{filedate})\n\n" else r << "\n#{prog}: version dated #{filedate}\n\n" end if t.empty? return r else puts r exit t[0] end end # Print out usage information def usage(*opt) t = @usage lastflag = nil lastdesc = nil usage = '' while !t.empty? # COMMENT: t.sub!(/\A[ \t]*#.*\n/,".") and next # TYPE DIRECTIVE: se = DelimScanner::new( t ) if t =~ /\A\s*\[\s*pvtype:/ if action = se.extractBracketed("[") t.sub!(Regexp::quote( action ).to_re,'') t.sub!(/\A[ \t]*\n/,"") next end end # ACTION codeblockDelimiters = { '{' => '}' } se = DelimScanner::new( t ) if action = se.extractCodeblock(codeblockDelimiters) t.sub!(Regexp::quote( action ).to_re,'') t.sub!(/\A[ \t]*\n/,"") decfirst = 0 unless !decfirst.nil? next end # ARG + DESC: if t.sub!(re_argument,"") decfirst = 0 unless !decfirst.nil? spec = "#$1".expand_tabs!() desc = "#$2".expand_tabs!() while t.gsub!(re_more_desc, '') desc += "#$1".expand_tabs! end next if desc =~ /\[\s*undocumented\s*\]/i uoff = 0 spec.gsub!(/(<[a-zA-Z]\w*):([^>]+)>/e) { |i| uoff += 1 + "#$2".length() and "#$1>" } spec.gsub!(/\t/,"=") ditto = desc =~ /\A\s*\[ditto\]/ desc.gsub!(/^\s*\[.*?\]\s*\n/m,"") desc.gsub!(BracketDirectives,'') #desc.gsub!(/\[.*?\]/,"") if ditto desc = (lastdesc ? _ditto(lastflag,lastdesc,desc) : "" ) elsif desc =~ /\A\s*\Z/ next else lastdesc = desc end spec =~ /\A\s*(\S+)/ and lastflag = "#$1" desc.sub!(/\s+\Z/, "\n") usage += spec + ' ' * uoff + desc next end # OTHERWISE, DECORATION if t.sub!(/((?:(?!\[\s*pvtype:).)*)(\n|(?=\[\s*pvtype:))/,"") desc = "#$1"+("#$2"||'') #desc.gsub!(/^(\s*\[.*?\])+\s*\n/m,'') #desc.gsub!(/\[.*?\]/,'') # eliminates anything in brackets if @tight || desc !~ /\A\s*\Z/ desc.gsub!(BracketDirectives,'') next if desc =~ /\A\s*\Z/ end decfirst = 1 unless !decfirst.nil? or desc =~ /\A\s*\Z/ usage += desc end end #while required = '' for arg in @args required += ' ' + arg.desc + ' ' if arg.required end usage.gsub!(Regexp.new("\29"),"[/") # REINSTATE ESCAPED '['s required.gsub!(/<([a-zA-Z]\w*):[^>]+>/,'<\1>') required.rstrip! helpcmd = Getopt::Declare::Arg::besthelp versioncmd = Getopt::Declare::Arg::bestversion header = '' unless @source.nil? header << version() prog = "#{$0}" prog.sub!(%r#.*/#,'') header << "Usage: #{prog} [options]#{required}\n" header << " #{prog} #{helpcmd}\n" if helpcmd header << " #{prog} #{versioncmd}\n" if versioncmd header << "\n" unless decfirst && decfirst == 1 && usage =~ /\A[ \t]*\n/ end header << "Options:\n" unless decfirst && decfirst == 1 usage.sub!(/[\s\n]+\Z/m, '') pager = $stdout #begin # eval('require "IO/Pager";') # pager = IO::Pager.new() #rescue #end if opt.empty? pager.puts "#{header}#{usage}" return 0 ### usage end #usage.sub!(/\A[\s\n]+/m, '') pager.puts "#{header}#{usage}" exit(opt[0]) if opt[0] end attr_accessor :unused # Return list of used parameters (after parsing) def used used = @cache.keys return used.join(' ') end @@m = [] # Main method to generate code to be evaluated for parsing. def code(*t) package = t[0] || '' code = %q% @_deferred = [] @_errormsg = nil @_finished = nil begin begin undef :defer undef :reject undef :finish rescue end def defer(&i) @_deferred.push( i ) end def reject(*i) if !i || i[0] @_errormsg = i[1] if i[1] throw :paramout end end def finish(*i) if i.size @_finished = i else @_finished = true end end @unused = [] @cache = {} _FOUND_ = {} _errors = 0 _invalid = {} _lastprefix = nil _pos = 0 # current position to match from _nextpos = 0 # next position to match from catch(:alldone) do while !@_finished begin catch(:arg) do @_errormsg = nil # This is used for clustering of flags while _lastprefix substr = _args[_nextpos..-1] substr =~ /^(?!\s|\0|\Z)% + Getopt::Declare::Arg::negflagpat() + %q%/ or begin _lastprefix=nil break end "#{_lastprefix}#{substr}" =~ /^(% + Getopt::Declare::Arg::posflagpat() + %q%)/ or begin _lastprefix=nil break end _args = _args[0.._nextpos-1] + _lastprefix + _args[_nextpos..-1] break end # while _lastprefix % + '' + %q% _pos = _nextpos if _args usage(0) if _args && gindex(_args,/\G(% + @helppat + %q%)(\s|\0|\Z)/,_pos) version(0) if _args && _args =~ /\G(% + @verspat + %q%)(\s|\0|\Z)/ % for arg in @args code << arg.code(self,package) end code << %q% if _lastprefix _pos = _nextpos + _lastprefix.length() _lastprefix = nil next end _pos = _nextpos _args && _pos = gindex( _args, /\G[\s|\0]*(\S+)/, _pos ) or throw(:alldone) if @_errormsg $stderr.puts( "Error#{source}: #{@_errormsg}\n" ) else @unused.push( @@m[0] ) end _errors += 1 if @_errormsg end # catch(:arg) ensure # begin _pos = 0 if _pos.nil? _nextpos = _pos if _args if _args and _args.index( /\G(\s|\0)*\Z/, _pos ) _args = _get_nextline.call(self) if !@_finished throw(:alldone) unless _args _pos = _nextpos = 0 _lastprefix = '' end # if end # begin/ensure end # while @_finished end # catch(:alldone) end # begin % ################################ # Check for required arguments # ################################ for arg in @args next unless arg.required code << %q%unless _FOUND_['% + arg.name + %q%'] % if @mutex[arg.name] for m in @mutex[arg.name] code << %q# or _FOUND_['# + m + %q#']# end end code << %q% $stderr.puts "Error#{@source}: required parameter '% + arg.name + %q%' not found." _errors += 1 end % end ######################################## # Check for arguments requiring others # ######################################## for arg in @args next unless arg.requires code << %q% if _FOUND_['% + arg.name + %q%'] && !(% + arg.found_requires + %q%) $stderr.puts "Error#{@source}: parameter '% + arg.name + %q%' can only be specified with '% + arg.requires + %q%'" _errors += 1 end % end code << %q% #################### Add unused arguments if _args && _nextpos > 0 && _args.length() > 0 @unused.replace( @unused + _args[_nextpos..-1].split(' ') ) end for i in @unused i.tr!( "\0", " " ) end % if @strict code << %q% #################### Handle strict flag unless _nextpos < ( _args ? _args.length : 0 ) for i in @unused $stderr.puts "Error#{@source}: unrecognizable argument ('#{i}')" _errors += 1 end end % end code << %q% #################### Print help hint if _errors > 0 && !@source.nil? $stderr.puts "\n(try '#$0 % + Getopt::Declare::Arg::besthelp + %q%' for more information)" end ## cannot just assign unused to ARGV in ruby unless @source != '' ARGV.clear @unused.map { |i| ARGV.push(i) } end unless _errors > 0 for i in @_deferred begin i.call rescue => e STDERR.puts "Action in Getopt::Declare specification produced:\n#{e}" _errors += 1 end end end !(_errors>0) # return true or false (false for errors) % return code end # Inspect cache (not the declare object) def inspect return nil if !@cache t = '' @cache.each { |a,b| t << a + " => " case b when Hash t << "{" i = [] b.each { |c,d| i.push( " '#{c}' => " + d.inspect ) } t << i.join(',') t << " }" else t << b.inspect end t << "\n" } t << "Unused: " + unused.join(', ') end # Iterator for Getopt::Declare (travels thru all cache keys) def each(&t) @cache.each(&t) end # Operator to easily create new value in of Getopt::Declare def []=(name,val) @cache = {} unless @cache @cache[name] = val end # Operator to easily return cache of Getopt::Declare def [](name) if @cache return @cache[name] else return nil end end # Operator to return number of flags set def size return 0 unless @cache return @cache.keys.size end attr :mutex attr :helppat attr :verspat attr :strict attr :clump attr :source end # class Declare end # module Getopt