# # optparse.rb - command-line option analysis with the OptionParser class. # # Author:: Nobu Nakada # Documentation:: Nobu Nakada and Gavin Sinclair. # # See OptionParser for documentation. # # == Developer Documentation (not for RDoc output) # # === Class tree # # - OptionParser:: front end # - OptionParser::Switch:: each switches # - OptionParser::List:: options list # - OptionParser::ParseError:: errors on parsing # - OptionParser::AmbiguousOption # - OptionParser::NeedlessArgument # - OptionParser::MissingArgument # - OptionParser::InvalidOption # - OptionParser::InvalidArgument # - OptionParser::AmbiguousArgument # # === Object relationship diagram # # +--------------+ # | OptionParser |<>-----+ # +--------------+ | +--------+ # | ,-| Switch | # on_head -------->+---------------+ / +--------+ # accept/reject -->| List |<|>- # | |<|>- +----------+ # on ------------->+---------------+ `-| argument | # : : | class | # +---------------+ |==========| # on_tail -------->| | |pattern | # +---------------+ |----------| # OptionParser.accept ->| DefaultList | |converter | # reject |(shared between| +----------+ # | all instances)| # +---------------+ # # == OptionParser # # === Introduction # # OptionParser is a class for command-line option analysis. It is much more # advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented # solution. # # === Features # # 1. The argument specification and the code to handle it are written in the same # place. # 2. It can output an option summary; you don't need to maintain this string # separately. # 3. Optional and mandatory arguments are specified very gracefully. # 4. Arguments can be automatically converted to a specified class. # 5. Arguments can be restricted to a certain set. # # All of these features are demonstrated in the example below. # # === Example # # The following example is a complete Ruby program. You can run it and see the # effect of specifying various options. This is probably the best way to learn # the features of +optparse+. # # require 'optparse' # require 'optparse/time' # require 'ostruct' # require 'pp' # # class OptparseExample # # CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] # CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } # # # # # Return a structure describing the options. # # # def self.parse(args) # # The options specified on the command line will be collected in *options*. # # We set default values here. # options = OpenStruct.new # options.library = [] # options.inplace = false # options.encoding = "utf8" # options.transfer_type = :auto # options.verbose = false # # opts = OptionParser.new do |opts| # opts.banner = "Usage: example.rb [options]" # # opts.separator "" # opts.separator "Specific options:" # # # Mandatory argument. # opts.on("-r", "--require LIBRARY", # "Require the LIBRARY before executing your script") do |lib| # options.library << lib # end # # # Optional argument; multi-line description. # opts.on("-i", "--inplace [EXTENSION]", # "Edit ARGV files in place", # " (make backup if EXTENSION supplied)") do |ext| # options.inplace = true # options.extension = ext || '' # options.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot. # end # # # Cast 'delay' argument to a Float. # opts.on("--delay N", Float, "Delay N seconds before executing") do |n| # options.delay = n # end # # # Cast 'time' argument to a Time object. # opts.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| # options.time = time # end # # # Cast to octal integer. # opts.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger, # "Specify record separator (default \\0)") do |rs| # options.record_separator = rs # end # # # List of arguments. # opts.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| # options.list = list # end # # # Keyword completion. We are specifying a specific set of arguments (CODES # # and CODE_ALIASES - notice the latter is a Hash), and the user may provide # # the shortest unambiguous text. # code_list = (CODE_ALIASES.keys + CODES).join(',') # opts.on("--code CODE", CODES, CODE_ALIASES, "Select encoding", # " (#{code_list})") do |encoding| # options.encoding = encoding # end # # # Optional argument with keyword completion. # opts.on("--type [TYPE]", [:text, :binary, :auto], # "Select transfer type (text, binary, auto)") do |t| # options.transfer_type = t # end # # # Boolean switch. # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| # options.verbose = v # end # # opts.separator "" # opts.separator "Common options:" # # # No argument, shows at tail. This will print an options summary. # # Try it and see! # opts.on_tail("-h", "--help", "Show this message") do # puts opts # exit # end # # # Another typical switch to print the version. # opts.on_tail("--version", "Show version") do # puts OptionParser::Version.join('.') # exit # end # end # # opts.parse!(args) # options # end # parse() # # end # class OptparseExample # # options = OptparseExample.parse(ARGV) # pp options # # Note: some bugs were fixed between 1.8.0 and 1.8.1. If you experience trouble # with the above code, keep this in mind. # # === Further documentation # # The methods are not individually documented at this stage. The above example # should be enough to learn how to use this class. If you have any questions, # email me (gsinclair@soyabean.com.au) and I will update this document. # class OptionParser # :stopdoc: RCSID = %w$Id$[1..-1].each {|s| s.freeze}.freeze Version = (RCSID[1].split('.').collect {|s| s.to_i}.extend(Comparable).freeze if RCSID[1]) LastModified = (Time.gm(*RCSID[2, 2].join('-').scan(/\d+/).collect {|s| s.to_i}) if RCSID[2]) Release = RCSID[2] NoArgument = [NO_ARGUMENT = :NONE, nil].freeze RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze # :startdoc: # # Keyword completion module. This allows partial arguments to be specified # and resolved against a list of acceptable values. # module Completion def complete(key, icase = false, pat = nil) pat ||= Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) canon, sw, k, v, cn = nil candidates = [] each do |k, *v| (if Regexp === k kn = nil k === key else kn = defined?(k.id2name) ? k.id2name : k pat === kn end) or next v << k if v.empty? candidates << [k, v, kn] end candidates = candidates.sort_by {|k, v, kn| kn.size} if candidates.size == 1 canon, sw, * = candidates[0] elsif candidates.size > 1 canon, sw, cn = candidates.shift candidates.each do |k, v, kn| next if sw == v if String === cn and String === kn if cn.rindex(kn, 0) canon, sw, cn = k, v, kn next elsif kn.rindex(cn, 0) next end end throw :ambiguous, key end end if canon block_given? or return key, *sw yield(key, *sw) end end def convert(opt = nil, val = nil, *) val end end # # Map from option/keyword string to object with completion. # class OptionMap < Hash include Completion end # # Individual switch class. Not important to the user. # # Defined within Switch are several Switch-derived classes: NoArgument, # RequiredArgument, etc. # class Switch attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block # # Guesses argument style from +arg+. Returns corresponding # OptionParser::Switch class (OptionalArgument, etc.). # def self.guess(arg) case arg when "" t = self when /\A=?\[/ t = Switch::OptionalArgument when /\A\s+\[/ t = Switch::PlacedArgument else t = Switch::RequiredArgument end self >= t or incompatible_argument_styles(arg, t) t end def self.incompatible_argument_styles(arg, t) raise ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}" end def self.pattern NilClass end def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, desc = ([] if short or long), block = Proc.new) raise if Array === pattern @pattern, @conv, @short, @long, @arg, @desc, @block = pattern, conv, short, long, arg, desc, block end # # OptionParser::Switch#parse_arg(arg) {non-serious error handler} # # Parses argument and returns rest of ((|arg|)), and matched portion # to the argument pattern. # :Parameters: # : ((|arg|)) # option argument to be parsed. # : (({block})) # yields when the pattern doesn't match sub-string. # def parse_arg(arg) pattern or return nil, arg unless m = pattern.match(arg) yield(InvalidArgument, arg) return arg, nil end if String === m m = [s = m] else m = m.to_a s = m[0] return nil, m unless String === s end raise InvalidArgument, arg unless arg.rindex(s, 0) return nil, m if s.length == arg.length yield(InvalidArgument, arg) # didn't match whole arg return arg[s.length..-1], m end private :parse_arg # # OptionParser::Switch#conv_arg(arg, val) {semi-error handler} # # Parses argument, convert and returns ((|arg|)), ((|block|)) and # result of conversion. # : Arguments to ((|@conv|)) # substrings matched to ((|@pattern|)), ((|$&|)), ((|$1|)), # ((|$2|)) and so on. # :Parameters: # : ((|arg|)) # argument string follows the switch. # : ((|val|)) # following argument. # : (({block})) # (({yields})) at semi-error condition, instead of raises exception. # def conv_arg(arg, val = []) if block if conv val = conv.yield(*val) else val = val[0] end return arg, block, val else return arg, nil end end private :conv_arg # # OptionParser::Switch#summarize(sdone, ldone, width, max, indent) # # Makes summary strings. # :Parameters: # : ((|sdone|)) # already summarized short style options keyed hash. # : ((|ldone|)) # already summarized long style options keyed hash. # : ((|width|)) # width of left side, option part. in other word, right side, # description part strings start at ((|width|)) column. # : ((|max|)) # maximum width of left side, options are filled within ((|max|)) columns. # : ((|indent|)) # prefix string indents each summarized lines. # : (({block})) # to be passed each lines(without newline). # def summarize(sdone = [], ldone = [], width = 1, max = width - 1, indent = "") sopts, lopts, s = [], [], nil @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long return if sopts.empty? and lopts.empty? # completely hidden left = [sopts.join(', ')] right = desc.dup while s = lopts.shift l = left[-1].length + s.length l += arg.length if left.size == 1 && arg l < max or left << '' left[-1] << if left[-1].empty? then ' ' * 4 else ', ' end << s end left[0] << arg if arg mlen = left.collect {|s| s.length}.max.to_i while mlen > width and l = left.shift mlen = left.collect {|s| s.length}.max.to_i if l.length == mlen yield(indent + l) end while begin l = left.shift; r = right.shift; l or r end l = l.to_s.ljust(width) + ' ' + r if r and !r.empty? yield(indent + l) end self end def add_banner(to) unless @short or @long s = desc.join to << " [" + s + "]..." unless s.empty? end to end def match_nonswitch?(str) @pattern =~ str unless @short or @long end # # Switch that takes no arguments. # class NoArgument < self # # Raises an exception if any arguments given. # def parse(arg, argv, &error) yield(NeedlessArgument, arg) if arg conv_arg(arg) end def self.incompatible_argument_styles(*) end def self.pattern Object end end # # Switch that takes an argument. # class RequiredArgument < self # # Raises an exception if argument is not present. # def parse(arg, argv) unless arg raise MissingArgument if argv.empty? arg = argv.shift end conv_arg(*parse_arg(arg) {|*exc| raise(*exc)}) end end # # Switch that can omit argument. # class OptionalArgument < self # # Parses argument if given, or uses default value. # def parse(arg, argv, &error) if arg conv_arg(*parse_arg(arg, &error)) else conv_arg(arg) end end end # # Switch that takes an argument, which does not begin with '-'. # class PlacedArgument < self # # Returns nil if argument is not present or begins with '-'. # def parse(arg, argv, &error) if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0])) return nil, block, nil end opt = (val = parse_arg(val, &error))[1] val = conv_arg(*val) if opt and !arg argv.shift else val[0] = nil end val end end end # # Simple option list providing mapping from short and/or long option # string to (()), and mapping from acceptable # argument to matching pattern and converter pair. Also provides # summary feature. # class List # Map from acceptable argument types to pattern and converter pairs. attr_reader :atype # Map from short style option switches to actual switch objects. attr_reader :short # Map from long style option switches to actual switch objects. attr_reader :long # List of all switches and summary string. attr_reader :list # # Just initializes all instance variables. # def initialize @atype = {} @short = OptionMap.new @long = OptionMap.new @list = [] end # # See OptionParser.accept. # def accept(t, pat = /.*/nm, &block) if pat pat.respond_to?(:match) or raise TypeError, "has no `match'" else pat = t if t.respond_to?(:match) end unless block block = pat.method(:convert).to_proc if pat.respond_to?(:convert) end @atype[t] = [pat, block] end # # See OptionParser.reject. # def reject(t) @atype.delete(t) end # # OptionParser::List#update(sw, sopts, lopts, nlopts = nil) # # Adds ((|sw|)) according to ((|sopts|)), ((|lopts|)) and # ((|nlopts|)). # :Parameters: # : ((|sw|)) # (()) instance to be added. # : ((|sopts|)) # short style options list. # : ((|lopts|)) # long style options list. # : ((|nlopts|)) # negated long style options list. # def update(sw, sopts, lopts, nsw = nil, nlopts = nil) o = nil sopts.each {|o| @short[o] = sw} if sopts lopts.each {|o| @long[o] = sw} if lopts nlopts.each {|o| @long[o] = nsw} if nsw and nlopts used = @short.invert.update(@long.invert) @list.delete_if {|o| Switch === o and !used[o]} end private :update # # OptionParser::List#prepend(switch, short_opts, long_opts, nolong_opts) # # Inserts ((|switch|)) at head of the list, and associates short, # long and negated long options. def prepend(*args) update(*args) @list.unshift(args[0]) end # # OptionParser::List#append(switch, short_opts, long_opts, nolong_opts) # # Appends ((|switch|)) at tail of the list, and associates short, # long and negated long options. # :Parameters: # : ((|switch|)) # (()) instance to be inserted. # : ((|short_opts|)) # list of short style options. # : ((|long_opts|)) # list of long style options. # : ((|nolong_opts|)) # list of long style options with (({"no-"})) prefix. def append(*args) update(*args) @list.push(args[0]) end # # OptionParser::List#search(id, key) [{block}] # # Searches ((|key|)) in ((|id|)) list. # :Parameters: # : ((|id|)) # searching list. # : ((|k|)) # searching key. # : (({block})) # yielded with the found value when succeeded. # def search(id, key) if list = __send__(id) val = list.fetch(key) {return nil} return val unless block_given? yield(*val) end end # # OptionParser::List#complete(id, opt, *pat, &block) # # Searches list ((|id|)) for ((|opt|)) and ((|*pat|)). # :Parameters: # : ((|id|)) # searching list. # : ((|opt|)) # searching key. # : ((|icase|)) # search case insensitive if true. # : ((|*pat|)) # optional pattern for completion. # : (({block})) # yielded with the found value when succeeded. # def complete(id, opt, icase = false, *pat, &block) __send__(id).complete(opt, icase, *pat, &block) end # # OptionParser::List#each_option # # Iterates for each options. # def each_option(&block) list.each(&block) end # # OptionParser::List#summarize(*args) {...} # # Making summary table, yields the (({block})) with each lines. # Each elements of (({@list})) should be able to (({summarize})). # :Parameters: # : ((|args|)) # passed to elements#summarize through. # : (({block})) # to be passed each lines(without newline). # def summarize(*args, &block) list.each do |opt| if opt.respond_to?(:summarize) # perhaps OptionParser::Switch opt.summarize(*args, &block) elsif opt.empty? yield("") else opt.each(&block) end end end def add_banner(to) list.each do |opt| if opt.respond_to?(:add_banner) opt.add_banner(to) end end to end end # # Hash with completion search feature. See Completion module. # class CompletingHash < Hash include Completion # # OptionParser::CompletingHash#match(key) # # Completion for hash key. # def match(key) return key, fetch(key) { raise AmbiguousArgument, catch(:ambiguous) {return complete(key)} } end end # # OptionParser::ArgumentStyle # Enumeration of acceptable argument styles; possible values are: # : OptionParser::NO_ARGUMENT # the switch takes no arguments. ((({:NONE}))) # : OptionParser::REQUIRED_ARGUMENT # the switch requires an argument. ((({:REQUIRED}))) # : OptionParser::OPTIONAL_ARGUMENT # the switch requires an optional argument, that is, may take or # not. ((({:OPTIONAL}))) # # Use like (({--switch=argument}))(long style) or # (({-Xargument}))(short style). For short style, only portion # matched to (()) is dealed as argument. # # :stopdoc: ArgumentStyle = {} NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument} RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument} OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument} ArgumentStyle.freeze # # OptionParser::DefaultList # # Switches common used such as '--', and also provides default # argument classes # DefaultList = List.new DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} # # OptionParser::Officious # Default options for ARGV, which never appear in option summary. # Officious = {} # --help # Shows option summary. Officious['help'] = proc do |parser| Switch::NoArgument.new do puts parser.help exit end end # --version # Shows version string if (({::Version})) is defined. Officious['version'] = proc do |parser| Switch::OptionalArgument.new do |pkg| if pkg begin require 'optparse/version' rescue LoadError else show_version(*pkg.split(/,/)) or abort("#{parser.program_name}: no version found in package #{pkg}") exit end end v = parser.ver or abort("#{parser.program_name}: version unknown") puts v exit end end # :startdoc: # # Class methods # =begin --- OptionParser.with([banner[, width[, indent]]]) [{...}] Initializes new instance, and evaluates the block in context of the instance if called as iterator. This behavior is equivalent to older (({new})). This is ((*deprecated*)) method. cf. (()) :Parameters: : ((|banner|)) banner message. : ((|width|)) summary width. : ((|indent|)) summary indent. : (({block})) to be evaluated in the new instance context. =end #'#"#`# def self.with(*args, &block) opts = new(*args) opts.instance_eval(&block) opts end =begin --- OptionParser.inc(arg[, default]) --- OptionParser#inc(arg[, default]) Returns incremented value of ((|default|)) according to ((|arg|)). =end def self.inc(arg, default = nil) case arg when Integer arg.nonzero? when nil default.to_i + 1 end end def inc(*args) self.class.inc(*args) end =begin --- OptionParser.new([banner[, width[, indent]]]) [{...}] Initializes the instance, and yields itself if called as iterator. :Parameters: : ((|banner|)) banner message. : ((|width|)) summary width. : ((|indent|)) summary indent. : (({block})) to be evaluated in the new instance context. =end #'#"#`# def initialize(banner = nil, width = 32, indent = ' ' * 4) @stack = [DefaultList, List.new, List.new] @program_name = nil @banner = banner @summary_width = width @summary_indent = indent @default_argv = ARGV add_officious yield self if block_given? end # :nodoc: def add_officious list = base() Officious.each do |opt, block| list.long[opt] ||= block.yield(self) end end =begin --- OptionParser.terminate([arg]) Terminates option parsing. Optional parameter ((|arg|)) would be pushed back if given. :Parameters: : ((|arg|)) string pushed back to be first non-option argument =end #'#"#`# def terminate(arg = nil) self.class.terminate(arg) end def self.terminate(arg = nil) throw :terminate, arg end @stack = [DefaultList] def self.top() DefaultList end =begin --- OptionParser.accept(t, [pat]) {...} --- OptionParser#accept(t, [pat]) {...} Directs to accept specified class argument. :Parameters: : ((|t|)) argument class specifier, any object including Class. : ((|pat|)) pattern for argument, defaulted to ((|t|)) if it respond to (({match})). : (({block})) receives argument string and should be convert to desired class. =end #'#"#`# def accept(*args, &blk) top.accept(*args, &blk) end def self.accept(*args, &blk) top.accept(*args, &blk) end =begin --- OptionParser.reject(t) --- OptionParser#reject(t) Directs to reject specified class argument. :Parameters: : ((|t|)) argument class specifier, any object including Class. =end #'#"#`# def reject(*args, &blk) top.reject(*args, &blk) end def self.reject(*args, &blk) top.reject(*args, &blk) end =begin === Instance methods =end #'#"#`# =begin --- OptionParser#banner --- OptionParser#banner=(heading) Heading banner preceding summary. --- OptionParser#summary_width --- OptionParser#summary_width=(width) Width for option list portion of summary. Must be (({Numeric})). --- OptionParser#summary_indent --- OptionParser#summary_indent=(indent) Indentation for summary. Must be (({String})) (or have (({+ String}))). --- OptionParser#program_name --- OptionParser#program_name=(name) Program name to be emitted in error message and default banner, defaulted to (({$0})). --- OptionParser#default_argv --- OptionParser#default_argv=(argv) Strings to be parsed in default. =end #'#"#`# attr_writer :banner, :program_name attr_accessor :summary_width, :summary_indent attr_accessor :default_argv def banner unless @banner @banner = "Usage: #{program_name} [options]" @stack.reverse_each {|el|el.add_banner(@banner)} end @banner end def program_name @program_name || File.basename($0, '.*') end # for experimental cascading :-) alias set_banner banner= alias set_program_name program_name= alias set_summary_width summary_width= alias set_summary_indent summary_indent= =begin --- OptionParser#version --- OptionParser#version=(ver) Version. --- OptionParser#release --- OptionParser#release=(rel) Release code. --- OptionParser#ver Returns version string from (()), (({version})) and (({release})). =end #'#"#`# attr_writer :version, :release def version @version || (defined?(::Version) && ::Version) end def release @release || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE) end def ver if v = version str = "#{program_name} #{[v].join('.')}" str << " (#{v})" if v = release str end end def warn(mesg = $!) super("#{program_name}: #{mesg}") end def abort(mesg = $!) super("#{program_name}: #{mesg}") end =begin --- OptionParser#top Subject of (())/(()), (())/(()). =end #'#"#`# def top @stack[-1] end =begin --- OptionParser#base Subject of (()). =end #'#"#`# def base @stack[1] end =begin --- OptionParser#new Pushes a new (({List})). =end #'#"#`# def new @stack.push(List.new) if block_given? yield self else self end end =begin --- OptionParser#remove Removes the last (({List})). =end #'#"#`# def remove @stack.pop end =begin --- OptionParser#summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent) Puts option summary into ((|to|)), and returns ((|to|)). :Parameters: : ((|to|)) output destination, which must have method ((|<<|)). Defaulted to (({[]})). : ((|width|)) width of left side. Defaulted to ((|@summary_width|)) : ((|max|)) maximum length allowed for left side. Defaulted to (({((|width|)) - 1})) : ((|indent|)) indentation. Defaulted to ((|@summary_indent|)) : (({block})) yields with each line if called as iterator. =end #'#"#`# def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) visit(:summarize, {}, {}, width, max, indent, &(blk || proc {|l| to << l + $/})) to end =begin --- OptionParser#help --- OptionParser#to_s Returns option summary string. =end #'#"#`# def help; summarize(banner.to_s.sub(/\n?\z/, "\n")) end alias to_s help =begin --- OptionParser#to_a Returns option summary list. =end #'#"#`# def to_a; summarize(banner.to_a.dup) end =begin --- OptionParser#switch Creates (()). :Parameters: : ((|*opts|)) option definition: : argument style see (()) : argument pattern acceptable option argument format, must pre-defined with (()) or (()), or (({Regexp})). This can appear once or assigned as (({String})) if not present, otherwise causes exception (({ArgumentError})). cf. (()). : Hash : Array possible argument values. : Proc : Method alternative way to give the ((*handler*)). : "--switch=MANDATORY", "--switch[=OPTIONAL]", "--switch" specifies long style switch that takes ((*mandatory*)), ((*optional*)) and ((*no*)) argument, respectively. : "-xMANDATORY", "-x[OPTIONAL]", "-x" specifies short style switch that takes ((*mandatory*)), ((*optional*)) and ((*no*)) argument, respectively. : "-[a-z]MANDATORY", "-[a-z][OPTIONAL]", "-[a-z]" special form short style switch that matches character range(not fullset of regular expression). : "=MANDATORY", "=[OPTIONAL]" argument style and description. : "description", ... ((*description*)) for this option. : (({block})) ((*handler*)) to convert option argument to arbitrary (({Class})). =end #'#"#`# =begin private --- OptionParser#notwice(obj, prv, msg) Checks never given twice an argument. ((*Called from OptionParser#switch only*)) :Parameters: : ((|obj|)) new argument. : ((|prv|)) previously specified argument. : ((|msg|)) exception message =end #'#"#`# def notwice(obj, prv, msg) unless !prv or prv == obj begin raise ArgumentError, "argument #{msg} given twice: #{obj}" rescue $@[0, 2] = nil raise end end obj end private :notwice def make_switch(*opts, &block) short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], [] ldesc, sdesc, desc, arg = [], [], [] default_style = Switch::NoArgument default_pattern = nil klass = nil o = nil n, q, a = nil opts.each do |o| # argument class next if search(:atype, o) do |pat, c| klass = notwice(o, klass, 'type') if not_style and not_style != Switch::NoArgument not_pattern, not_conv = pat, c else default_pattern, conv = pat, c end end # directly specified pattern(any object possible to match) if !(String === o) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') conv = (pattern.method(:convert).to_proc if pattern.respond_to?(:convert)) next end # anything others case o when Proc, Method block = notwice(o, block, 'block') when Array, Hash case pattern when CompletingHash when nil pattern = CompletingHash.new conv = (pattern.method(:convert).to_proc if pattern.respond_to?(:convert)) else raise ArgumentError, "argument pattern given twice" end o.each {|(o, *v)| pattern[o] = v.fetch(0) {o}} when Module raise ArgumentError, "unsupported argument type: #{o}" when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') when /^--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style not_style = (not_style || default_style).guess(arg = a) if a default_style = Switch::NoArgument default_pattern, conv = search(:atype, FalseClass) unless default_pattern ldesc << "--no-#{q}" long << 'no-' + (q = q.downcase) nolong << q when /^--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--[no-]#{q}" long << (o = q.downcase) not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << 'no-' + o when /^--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--#{q}" long << (o = q.downcase) when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end sdesc << "-#{q}" short << Regexp.new(q) when /^-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end sdesc << "-#{q}" short << q when /^=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else desc.push(o) end end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern if !(short.empty? and long.empty?) s = (style || default_style).new(pattern || default_pattern, conv, sdesc, ldesc, arg, desc, block) elsif !block_given? raise ArgumentError, "no switch given" if style or pattern s = desc else short << pattern s = (style || default_style).new(pattern, conv, nil, nil, arg, desc, block) end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), nolong end =begin --- OptionParser#on(*opts) [{...}] --- OptionParser#def_option(*opts) [{...}] --- OptionParser#on_head(*opts) [{...}] --- OptionParser#def_head_option(*opts) [{...}] --- OptionParser#on_tail(*opts) [{...}] --- OptionParser#def_tail_option(*opts) [{...}] Defines option switch and handler. (({on_head})), (({def_head_option})) and (({on_tail})), (({def_tail_option})) put the switch at head and tail of summary, respectively. cf. (()). =end #'#"#`# def define(*opts, &block) top.append(*(sw = make_switch(*opts, &block))) sw[0] end def on(*opts, &block) define(*opts, &block) self end alias def_option define def define_head(*opts, &block) top.prepend(*(sw = make_switch(*opts, &block))) sw[0] end def on_head(*opts, &block) define_head(*opts, &block) self end alias def_head_option define_head def define_tail(*opts, &block) base.append(*(sw = make_switch(*opts, &block))) sw[0] end def on_tail(*opts, &block) define_tail(*opts, &block) self end alias def_tail_option define_tail def separator(string) top.append(string, nil, nil) end =begin --- OptionParser#order(*argv) [{...}] --- OptionParser#order!([argv = ARGV]) [{...}] Parses ((|argv|)) in order. When non-option argument encountered, yields it if called as iterator, otherwise terminates the parse process. Returns rest of ((|argv|)) left unparsed. (({order!})) takes argument array itself, and removes switches destructively. Defaults to parse ((|ARGV|)). :Parameters: : ((|argv|)) command line arguments to be parsed. : (({block})) called with each non-option argument. =end #'#"#`# def order(*argv, &block) argv = argv[0].dup if argv.size == 1 and Array === argv[0] order!(argv, &block) end def order!(argv = default_argv, &nonopt) opt, arg, sw, val, rest = nil nonopt ||= proc {|arg| throw :terminate, arg} argv.unshift(arg) if arg = catch(:terminate) { while arg = argv.shift case arg # long option when /\A--([^=]*)(?:=(.*))?/nm opt, rest = $1, $2 begin sw, = complete(:long, opt, true) rescue ParseError raise $!.set_option(arg, true) end begin opt, sw, val = sw.parse(rest, argv) {|*exc| raise(*exc)} sw.yield(*val) if sw rescue ParseError raise $!.set_option(arg, rest) end # short option when /\A-(.)((=).*|.+)?/nm opt, has_arg, eq, val, rest = $1, $3, $3, $2, $2 begin sw, = search(:short, opt) unless sw begin sw, = complete(:short, opt) # short option matched. val = arg.sub(/\A-/, '') has_arg = true rescue InvalidOption # if no short options match, try completion with long # options. sw, = complete(:long, opt) eq ||= !rest end end rescue ParseError raise $!.set_option(arg, true) end begin opt, sw, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" argv.unshift(opt) if opt and (opt = opt.sub(/\A-*/, '-')) != '-' sw.yield(val) if sw rescue ParseError raise $!.set_option(arg, arg.length > 2) end # non-option argument else catch(:prune) do visit(:each_option) do |sw| sw.block.yield(arg) if Switch === sw and sw.match_nonswitch?(arg) end nonopt.yield(arg) end end end nil } visit(:search, :short, nil) {|sw| sw.block.yield(argv) if !sw.pattern} argv end =begin --- OptionParser#permute(*argv) --- OptionParser#permute!([argv = ARGV]) Parses ((|argv|)) in permutation mode, and returns list of non-option arguments. (({permute!})) takes argument array itself, and removes switches destructively. Defaults to parse ((|ARGV|)). :Parameters: : ((|argv|)) command line arguments to be parsed. =end #'#"#`# def permute(*argv) argv = argv[0].dup if argv.size == 1 and Array === argv[0] permute!(argv) end def permute!(argv = default_argv) nonopts = [] arg = nil order!(argv) {|arg| nonopts << arg} argv[0, 0] = nonopts argv end =begin --- OptionParser#parse(*argv) --- OptionParser#parse!([argv = ARGV]) Parses ((|argv|)) in order when environment variable (({POSIXLY_CORRECT})) is set, otherwise permutation mode (({parse!})) takes argument array itself, and removes switches destructively. Defaults to parse ((|ARGV|)). :Parameters: : ((|argv|)) command line arguments to be parsed. =end #'#"#`# def parse(*argv) argv = argv[0].dup if argv.size == 1 and Array === argv[0] parse!(argv) end def parse!(argv = default_argv) if ENV.include?('POSIXLY_CORRECT') order!(argv) else permute!(argv) end end =begin private --- OptionParser#visit(id, *args) {block} Traverses (({stack}))s calling method ((|id|)) with ((|*args|)). :Parameters: : ((|id|)) called method in each elements of (({stack}))s. : ((|*args|)) passed to ((|id|)). : (({block})) passed to ((|id|)). =end #'#"#`# def visit(id, *args, &block) el = nil @stack.reverse_each do |el| el.send(id, *args, &block) end nil end private :visit =begin private --- OptionParser#search(id, k) Searches ((|k|)) in stack for ((|id|)) hash, and returns it or yielded value if called as iterator. :Parameters: : ((|id|)) searching table. : ((|k|)) searching key. : (({block})) yielded with the found value when succeeded. =end #'#"#`# def search(id, key) visit(:search, id, key) do |*k| return k unless block_given? return yield(*k) end end private :search =begin private --- OptionParser#complete(typ, opt, *etc) Completes shortened long style option switch, and returns pair of canonical switch and switch descriptor(()). :Parameters: : ((|id|)) searching table. : ((|opt|)) searching key. : ((|icase|)) search case insensitive if true. : ((|*pat|)) optional pattern for completion. : (({block})) yielded with the found value when succeeded. =end #'#"#`# def complete(typ, opt, icase = false, *pat) if pat.empty? search(typ, opt) {|sw| return [sw, opt]} # exact match or... end raise AmbiguousOption, catch(:ambiguous) { visit(:complete, typ, opt, icase, *pat) {|opt, *sw| return sw} raise InvalidOption, opt } end private :complete =begin undocumented --- OptionParser#load([filename]) Loads options from file named as ((|filename|)). Does nothing when the file is not present. Returns whether successfuly loaded. :Parameters: : ((|filename|)) option file name. defaulted to basename of the program without suffix in a directory ((%~/.options%)). =end #'#"#`# def load(filename = nil) begin filename ||= File.expand_path(File.basename($0, '.*'), '~/.options') rescue return false end begin parse(*IO.readlines(filename).each {|s| s.chomp!}) true rescue Errno::ENOENT, Errno::ENOTDIR false end end =begin undocumented --- OptionParser#environment([env]) Parses environment variable ((|env|)) or its uppercase with spliting like as shell. :Parameters: : ((|env|)) defaulted to basename of the program. =end #'#"#`# def environment(env = File.basename($0, '.*')) env = ENV[env] || ENV[env.upcase] or return parse(*Shellwords.shellwords(env)) end =begin = Acceptable argument classes =end #'#"#`# =begin : Object any string, and no conversion. this is fall-back. =end #'#"#`# accept(Object) {|s,|s or s.nil?} accept(NilClass) {|s,|s} =begin : String any none-empty string, and no conversion. =end #'#"#`# accept(String, /.+/nm) {|s,*|s} =begin : Integer Ruby/C-like integer, octal for (({0-7})) sequence, binary for (({0b})), hexadecimal for (({0x})), and decimal for others; with optional sign prefix. Converts to (({Integer})). =end #'#"#`# decimal = '\d+(?:_\d+)*' binary = 'b[01]+(?:_[01]+)*' hex = 'x[\da-f]+(?:_[\da-f]+)*' octal = "0(?:[0-7]*(?:_[0-7]+)*|#{binary}|#{hex})" integer = "#{octal}|#{decimal}" accept(Integer, %r"\A[-+]?(?:#{integer})"io) {|s,| Integer(s) if s} =begin : Float Float number format, and converts to (({Float})). =end #'#"#`# float = "(?:#{decimal}(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?" floatpat = %r"\A[-+]?#{float}"io accept(Float, floatpat) {|s,| s.to_f if s} =begin : Numeric Generic numeric format, and converts to (({Integer})) for integer format, (({Float})) for float format. =end #'#"#`# accept(Numeric, %r"\A[-+]?(?:#{octal}|#{float})"io) {|s,| eval(s) if s} =begin : OptionParser::DecimalInteger Decimal integer format, to be converted to (({Integer})). =end #'#"#`# DecimalInteger = /\A[-+]?#{decimal}/io accept(DecimalInteger) {|s,| s.to_i if s} =begin : OptionParser::OctalInteger Ruby/C like octal/hexadecimal/binary integer format, to be converted to (({Integer})). =end #'#"#`# OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))/io accept(OctalInteger) {|s,| s.oct if s} =begin : OptionParser::DecimalNumeric Decimal integer/float number format, to be converted to (({Integer})) for integer format, (({Float})) for float format. =end #'#"#`# DecimalNumeric = floatpat # decimal integer is allowed as float also. accept(DecimalNumeric) {|s,| eval(s) if s} =begin : TrueClass Boolean switch, which means whether it is present or not, whether it is absent or not with prefix (({no-})), or it takes an argument (({yes/no/true/false/+/-})). : FalseClass Similar to (()), but defaulted to (({false})). =end #'#"#`# yesno = CompletingHash.new %w[- no false].each {|el| yesno[el] = false} %w[+ yes true].each {|el| yesno[el] = true} yesno['nil'] = false # shoud be nil? accept(TrueClass, yesno) {|arg, val| val == nil or val} accept(FalseClass, yesno) {|arg, val| val != nil and val} =begin : Array List of strings separated by "," =end #'#"#`# accept(Array) do |s,| if s s = s.split(',').collect {|s| s unless s.empty?} end s end =begin : Regexp Regular expression with option. =end accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o| f = 0 if o f |= Regexp::IGNORECASE if /i/ =~ o f |= Regexp::MULTILINE if /m/ =~ o f |= Regexp::EXTENDED if /x/ =~ o k = o.delete("^imx") end Regexp.new(s || all, f, k) end =begin = Exceptions =end #'#"#`# =begin == ((:OptionParser::ParseError:)) Base class of exceptions from (()) === Superclass (({RuntimeError})) === Constants : OptionParser::ParseError::Reason Reason caused error. === Instance methods --- OptionParser::ParseError#recover(argv) Push backs erred argument(s) to ((|argv|)). --- OptionParser::ParseError#reason Returns error reason. Override this to I18N. --- OptionParser::ParseError#inspect Returns inspection string. --- OptionParser::ParseError#message --- OptionParser::ParseError#to_s Default stringizing method to emit standard error message. =end #'#"#`# class ParseError < RuntimeError Reason = 'parse error'.freeze def initialize(*args) @args = args @reason = nil end attr_reader :args attr_writer :reason def recover(argv) argv[0, 0] = @args argv end def set_option(opt, eq) if eq @args[0] = opt else @args.unshift(opt) end self end def reason @reason || self.class::Reason end def inspect "#<#{self.class.to_s}: #{args.join(' ')}>" end def message reason + ': ' + args.join(' ') end alias to_s message end =begin == ((:OptionParser::AmbiguousOption:)) Raises when encountered ambiguously completable string. === Superclass (()) =end #'#"#`# class AmbiguousOption < ParseError const_set(:Reason, 'ambiguous option'.freeze) end =begin == ((:OptionParser::NeedlessArgument:)) Raises when encountered argument for switch defined as which takes no argument. === Superclass (()) =end #'#"#`# class NeedlessArgument < ParseError const_set(:Reason, 'needless argument'.freeze) end =begin == ((:OptionParser::MissingArgument:)) Raises when no argument found for switch defined as which needs argument. === Superclass (()) =end #'#"#`# class MissingArgument < ParseError const_set(:Reason, 'missing argument'.freeze) end =begin == ((:OptionParser::InvalidOption:)) Raises when undefined switch. === Superclass (()) =end #'#"#`# class InvalidOption < ParseError const_set(:Reason, 'invalid option'.freeze) end =begin == ((:OptionParser::InvalidArgument:)) Raises when the given argument does not match required format. === Superclass (()) =end #'#"#`# class InvalidArgument < ParseError const_set(:Reason, 'invalid argument'.freeze) end =begin == ((:OptionParser::AmbiguousArgument:)) Raises when the given argument word can't completed uniquely. === Superclass (()) =end #'#"#`# class AmbiguousArgument < InvalidArgument const_set(:Reason, 'ambiguous argument'.freeze) end =begin = Miscellaneous =end #'#"#`# =begin == ((:OptionParser::Arguable:)) Extends command line arguments array to parse itself. =end #'#"#`# module Arguable =begin --- OptionParser::Arguable#options=(opt) Sets (()) object, when ((|opt|)) is (({false})) or (({nil})), methods (()) and (()) are undefined. Thus, there is no ways to access the (()) object via the receiver object. =end #'#"#`# def options=(opt) unless @optparse = opt class << self undef_method(:options) undef_method(:options=) end end end =begin --- OptionParser::Arguable#options Actual (()) object, automatically created if not yet. If called as iterator, yields with the (()) object and returns the result of the block. In this case, rescues any (()) exceptions in the block, just emits error message to (()) and returns (({nil})). :Parameters: : (({block})) Yielded with the (()) instance. =end #'#"#`# def options @optparse ||= OptionParser.new @optparse.default_argv = self block_given? or return @optparse begin yield @optparse rescue ParseError @optparse.warn $! nil end end =begin --- OptionParser::Arguable#order! --- OptionParser::Arguable#permute! --- OptionParser::Arguable#parse! Parses ((|self|)) destructively, and returns ((|self|)) just contains rest arguments left without parsed. =end #'#"#`# def order!(&blk) options.order!(self, &blk) end def permute!() options.permute!(self) end def parse!() options.parse!(self) end =begin private Initializes instance variable. =end #'#"#`# def self.extend_object(obj) super obj.instance_eval {@optparse = nil} end def initialize(*args) super @optparse = nil end end =begin == OptionParser::Acceptables Acceptable argument classes. Now contains (({DecimalInteger})), (({OctalInteger})) and (({DecimalNumeric})). see (()). =end #'#"#`# module Acceptables const_set(:DecimalInteger, OptionParser::DecimalInteger) const_set(:OctalInteger, OptionParser::OctalInteger) const_set(:DecimalNumeric, OptionParser::DecimalNumeric) end end # ARGV is arguable by OptionParser ARGV.extend(OptionParser::Arguable) if $0 == __FILE__ Version = OptionParser::Version ARGV.options {|q| q.parse!.empty? or puts "what's #{ARGV.join(' ')}?" } or exit 1 end __END__ =begin example = Example <<< opttest.rb =end #'#"#`#