class Getopt::Declare
Main Class
Constants
- VERSION
- nocase
Attributes
Public Class Methods
Constructor
# File lib/Getopt/Declare.rb, line 996 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!(/\\\[/,"\255") # 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]+),\s+(--?[\w]+)(\s+.*)?/ specs = ["#$1#$3", "#$2#$3"] specs.each { |sp| arg = Arg.new(sp,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
Public Instance Methods
Operator to easily return cache of Getopt::Declare
# File lib/Getopt/Declare.rb, line 1716 def [](name) if @cache return @cache[name] else return nil end end
Operator to easily create new value in of Getopt::Declare
# File lib/Getopt/Declare.rb, line 1710 def []=(name,val) @cache = {} unless @cache @cache[name] = val end
Main method to generate code to be evaluated for parsing.
# File lib/Getopt/Declare.rb, line 1464 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%'" _FOUND_['% + arg.name + %q%'] = nil @args['% + arg.name + %q%'] = nil _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 { |j| ARGV.push(j) } 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
Iterator for Getopt::Declare
(travels thru all cache keys)
# File lib/Getopt/Declare.rb, line 1705 def each(&t) @cache.each(&t) end
Inspect cache (not the declare object)
# File lib/Getopt/Declare.rb, line 1681 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
Parse the parameter description and in some cases, optionally eval it, too.
# File lib/Getopt/Declare.rb, line 1196 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 = '<STDIN>' 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 = '<ARRAY>' elsif source.length() == 1 && source[0] == "-STDIN" _get_nextline = method(:_get_nextline) _args = $stdin.readline source = '<STDIN>' 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
Operator to return number of flags set
# File lib/Getopt/Declare.rb, line 1725 def size return 0 unless @cache return @cache.keys.size end
# File lib/Getopt/Declare.rb, line 1281 def type(*t) Getopt::Declare::ScalarArg::addtype(t) end
Print out usage information
# File lib/Getopt/Declare.rb, line 1310 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!(/\255/,"[/") # 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]+\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
Return list of used parameters (after parsing)
# File lib/Getopt/Declare.rb, line 1456 def used used = @cache.keys return used.join(' ') end
Print out version information and maybe exit
# File lib/Getopt/Declare.rb, line 1286 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
Private Instance Methods
Handle quote replacements for [ditto] flag
# File lib/Getopt/Declare.rb, line 945 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 exclude conditions
# File lib/Getopt/Declare.rb, line 971 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
Read the next line from stdin
# File lib/Getopt/Declare.rb, line 833 def _get_nextline $stdin.readline end
Check parameter description for special options
# File lib/Getopt/Declare.rb, line 867 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 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
For each file provided and found, read it in
# File lib/Getopt/Declare.rb, line 838 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( '<STDIN>' ) _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 mutex conditions
# File lib/Getopt/Declare.rb, line 960 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
Extract a new type from the description and add it to the list of standard types
# File lib/Getopt/Declare.rb, line 900 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
Given an array or hash, flatten them to a string
# File lib/Getopt/Declare.rb, line 819 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
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.
# File lib/Getopt/Declare.rb, line 798 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
Returns a regex to match a single argument line
# File lib/Getopt/Declare.rb, line 983 def re_argument /\A(.*?\S.*?#{@@separator})(.*?\n)/ end
Returns a regex to keep matching a multi-line description for an argument.
# File lib/Getopt/Declare.rb, line 989 def re_more_desc /\A((?![ \t]*(\{|\n)|.*?\S.*?#{@@separator}.*?\S).*?\S.*\n)/ end