diff options
Diffstat (limited to 'lib/optparse.rb')
| -rw-r--r-- | lib/optparse.rb | 2538 |
1 files changed, 1586 insertions, 952 deletions
diff --git a/lib/optparse.rb b/lib/optparse.rb index 8fc27f8cd3..97178e284b 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true # # optparse.rb - command-line option analysis with the OptionParser class. -# +# # Author:: Nobu Nakada # Documentation:: Nobu Nakada and Gavin Sinclair. # -# See OptionParser for documentation. +# See OptionParser for documentation. # +require 'set' unless defined?(Set) - -# == Developer Documentation (not for RDoc output) -# +#-- +# == Developer Documentation (not for RDoc output) +# # === Class tree # # - OptionParser:: front end @@ -25,27 +27,31 @@ # # === 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 |<>-----+ +# +--------------+ | +--------+ +# | ,-| Switch | +# on_head -------->+---------------+ / +--------+ +# accept/reject -->| List |<|>- +# | |<|>- +----------+ +# on ------------->+---------------+ `-| argument | +# : : | class | +# +---------------+ |==========| +# on_tail -------->| | |pattern | +# +---------------+ |----------| +# OptionParser.accept ->| DefaultList | |converter | +# reject |(shared between| +----------+ +# | all instances)| +# +---------------+ +# +#++ # # == OptionParser # +# === New to +OptionParser+? +# +# See the {Tutorial}[optparse/tutorial.rdoc]. +# # === Introduction # # OptionParser is a class for command-line option analysis. It is much more @@ -53,18 +59,210 @@ # solution. # # === Features -# -# 1. The argument specification and the code to handle it are written in the same -# place. +# +# 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. +# All of these features are demonstrated in the examples below. See +# #make_switch for full documentation. +# +# === Minimal example +# +# require 'optparse' +# +# options = {} +# OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" +# +# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| +# options[:verbose] = v +# end +# end.parse! +# +# p options +# p ARGV +# +# === Generating Help +# +# OptionParser can be used to automatically generate help for the commands you +# write: +# +# require 'optparse' +# +# Options = Struct.new(:name) +# +# class Parser +# def self.parse(options) +# args = Options.new("world") +# +# opt_parser = OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" +# +# parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| +# args.name = n +# end +# +# parser.on("-h", "--help", "Prints this help") do +# puts parser +# exit +# end +# end +# +# opt_parser.parse!(options) +# return args +# end +# end +# options = Parser.parse %w[--help] +# +# #=> +# # Usage: example.rb [options] +# # -n, --name=NAME Name to say hello to +# # -h, --help Prints this help +# +# === Required Arguments +# +# For options that require an argument, option specification strings may include an +# option name in all caps. If an option is used without the required argument, +# an exception will be raised. +# +# require 'optparse' +# +# options = {} +# OptionParser.new do |parser| +# parser.on("-r", "--require LIBRARY", +# "Require the LIBRARY before executing your script") do |lib| +# puts "You required #{lib}!" +# end +# end.parse! +# +# Used: # -# === Example +# $ ruby optparse-test.rb -r +# optparse-test.rb:9:in '<main>': missing argument: -r (OptionParser::MissingArgument) +# $ ruby optparse-test.rb -r my-library +# You required my-library! +# +# === Type Coercion +# +# OptionParser supports the ability to coerce command line arguments +# into objects for us. +# +# OptionParser comes with a few ready-to-use kinds of type +# coercion. They are: +# +# - Date -- Anything accepted by +Date.parse+ (need to require +optparse/date+) +# - DateTime -- Anything accepted by +DateTime.parse+ (need to require +optparse/date+) +# - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+ (need to require +optparse/time+) +# - URI -- Anything accepted by +URI.parse+ (need to require +optparse/uri+) +# - Shellwords -- Anything accepted by +Shellwords.shellwords+ (need to require +optparse/shellwords+) +# - String -- Any non-empty string +# - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040) +# - Float -- Any float. (e.g. 10, 3.14, -100E+13) +# - Numeric -- Any integer, float, or rational (1, 3.4, 1/3) +# - DecimalInteger -- Like +Integer+, but no octal format. +# - OctalInteger -- Like +Integer+, but no decimal format. +# - DecimalNumeric -- Decimal integer or float. +# - TrueClass -- Accepts '+, yes, true, -, no, false' and +# defaults as +true+ +# - FalseClass -- Same as +TrueClass+, but defaults to +false+ +# - Array -- Strings separated by ',' (e.g. 1,2,3) +# - Regexp -- Regular expressions. Also includes options. +# +# We can also add our own coercions, which we will cover below. +# +# ==== Using Built-in Conversions +# +# As an example, the built-in +Time+ conversion is used. The other built-in +# conversions behave in the same way. +# OptionParser will attempt to parse the argument +# as a +Time+. If it succeeds, that time will be passed to the +# handler block. Otherwise, an exception will be raised. +# +# require 'optparse' +# require 'optparse/time' +# OptionParser.new do |parser| +# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| +# p time +# end +# end.parse! +# +# Used: +# +# $ ruby optparse-test.rb -t nonsense +# ... invalid argument: -t nonsense (OptionParser::InvalidArgument) +# $ ruby optparse-test.rb -t 10-11-12 +# 2010-11-12 00:00:00 -0500 +# $ ruby optparse-test.rb -t 9:30 +# 2014-08-13 09:30:00 -0400 +# +# ==== Creating Custom Conversions +# +# The +accept+ method on OptionParser may be used to create converters. +# It specifies which conversion block to call whenever a class is specified. +# The example below uses it to fetch a +User+ object before the +on+ handler receives it. +# +# require 'optparse' +# +# User = Struct.new(:id, :name) +# +# def find_user id +# not_found = ->{ raise "No User Found for id #{id}" } +# [ User.new(1, "Sam"), +# User.new(2, "Gandalf") ].find(not_found) do |u| +# u.id == id +# end +# end +# +# op = OptionParser.new +# op.accept(User) do |user_id| +# find_user user_id.to_i +# end +# +# op.on("--user ID", User) do |user| +# puts user +# end +# +# op.parse! +# +# Used: +# +# $ ruby optparse-test.rb --user 1 +# #<struct User id=1, name="Sam"> +# $ ruby optparse-test.rb --user 2 +# #<struct User id=2, name="Gandalf"> +# $ ruby optparse-test.rb --user 3 +# optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError) +# +# === Store options to a Hash +# +# The +into+ option of +order+, +parse+ and so on methods stores command line options into a Hash. +# +# require 'optparse' +# +# options = {} +# OptionParser.new do |parser| +# parser.on('-a') +# parser.on('-b NUM', Integer) +# parser.on('-v', '--verbose') +# end.parse!(into: options) +# +# p options +# +# Used: +# +# $ ruby optparse-test.rb -a +# {:a=>true} +# $ ruby optparse-test.rb -a -v +# {:a=>true, :verbose=>true} +# $ ruby optparse-test.rb -a -b 100 +# {:a=>true, :b=>100} +# +# === Complete 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 @@ -74,129 +272,165 @@ # require 'optparse/time' # require 'ostruct' # require 'pp' -# +# # class OptparseExample -# +# Version = '1.0.0' +# # 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 +# +# class ScriptOptions +# attr_accessor :library, :inplace, :encoding, :transfer_type, +# :verbose, :extension, :delay, :time, :record_separator, +# :list +# +# def initialize +# self.library = [] +# self.inplace = false +# self.encoding = "utf8" +# self.transfer_type = :auto +# self.verbose = false +# end +# +# def define_options(parser) +# parser.banner = "Usage: example.rb [options]" +# parser.separator "" +# parser.separator "Specific options:" +# +# # add additional options +# perform_inplace_option(parser) +# delay_execution_option(parser) +# execute_at_time_option(parser) +# specify_record_separator_option(parser) +# list_example_option(parser) +# specify_encoding_option(parser) +# optional_option_argument_with_keyword_completion_option(parser) +# boolean_verbose_option(parser) +# +# parser.separator "" +# parser.separator "Common options:" +# # No argument, shows at tail. This will print an options summary. +# # Try it and see! +# parser.on_tail("-h", "--help", "Show this message") do +# puts parser +# exit # 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. +# # Another typical switch to print the version. +# parser.on_tail("--version", "Show version") do +# puts Version +# exit # end -# +# end +# +# def perform_inplace_option(parser) +# # Specifies an optional option argument +# parser.on("-i", "--inplace [EXTENSION]", +# "Edit ARGV files in place", +# "(make backup if EXTENSION supplied)") do |ext| +# self.inplace = true +# self.extension = ext || '' +# self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot. +# end +# end +# +# def delay_execution_option(parser) # # Cast 'delay' argument to a Float. -# opts.on("--delay N", Float, "Delay N seconds before executing") do |n| -# options.delay = n +# parser.on("--delay N", Float, "Delay N seconds before executing") do |n| +# self.delay = n # end -# +# end +# +# def execute_at_time_option(parser) # # Cast 'time' argument to a Time object. -# opts.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| -# options.time = time +# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| +# self.time = time # end -# +# end +# +# def specify_record_separator_option(parser) # # Cast to octal integer. -# opts.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger, -# "Specify record separator (default \\0)") do |rs| -# options.record_separator = rs +# parser.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger, +# "Specify record separator (default \\0)") do |rs| +# self.record_separator = rs # end -# +# end +# +# def list_example_option(parser) # # List of arguments. -# opts.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| -# options.list = list +# parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| +# self.list = list # end -# +# end +# +# def specify_encoding_option(parser) # # 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 +# code_list = (CODE_ALIASES.keys + CODES).join(', ') +# parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding", +# "(#{code_list})") do |encoding| +# self.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 +# +# def optional_option_argument_with_keyword_completion_option(parser) +# # Optional '--type' option argument with keyword completion. +# parser.on("--type [TYPE]", [:text, :binary, :auto], +# "Select transfer type (text, binary, auto)") do |t| +# self.transfer_type = t # end -# +# end +# +# def boolean_verbose_option(parser) # # 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 +# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| +# self.verbose = v # end # end -# -# opts.parse!(args) -# options -# end # parse() -# +# end +# +# # +# # Return a structure describing the options. +# # +# def parse(args) +# # The options specified on the command line will be collected in +# # *options*. +# +# @options = ScriptOptions.new +# @args = OptionParser.new do |parser| +# @options.define_options(parser) +# parser.parse!(args) +# end +# @options +# end +# +# attr_reader :parser, :options # 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. +# example = OptparseExample.new +# options = example.parse(ARGV) +# pp options # example.options +# pp ARGV +# +# === Shell Completion +# +# For modern shells (e.g. bash, zsh, etc.), you can use shell +# completion for command line options. # # === 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. +# The above examples, along with the accompanying +# {Tutorial}[optparse/tutorial.rdoc], +# should be enough to learn how to use this class. +# If you have any questions, file a ticket at http://bugs.ruby-lang.org. # 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] + # The version string + VERSION = "0.8.1" + # An alias for compatibility + Version = VERSION + # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze @@ -207,14 +441,18 @@ class OptionParser # 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 + # :nodoc: + + def self.regexp(key, icase) + Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) + end + + def self.candidate(key, icase = false, pat = nil, &block) + pat ||= Completion.regexp(key, icase) candidates = [] - each do |k, *v| + block.call do |k, *v| (if Regexp === k - kn = nil + kn = "" k === key else kn = defined?(k.id2name) ? k.id2name : k @@ -223,7 +461,19 @@ class OptionParser v << k if v.empty? candidates << [k, v, kn] end - candidates = candidates.sort_by {|k, v, kn| kn.size} + candidates + end + + def self.completable?(key) + String.try_convert(key) or defined?(key.id2name) + end + + def candidate(key, icase = false, pat = nil, &_) + Completion.candidate(key, icase, pat, &method(:each)) + end + + def complete(key, icase = false, pat = nil) + candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} if candidates.size == 1 canon, sw, * = candidates[0] elsif candidates.size > 1 @@ -252,7 +502,6 @@ class OptionParser end end - # # Map from option/keyword string to object with completion. # @@ -260,14 +509,15 @@ class OptionParser include Completion end - # # Individual switch class. Not important to the user. # # Defined within Switch are several Switch-derived classes: NoArgument, - # RequiredArgument, etc. + # RequiredArgument, etc. # class Switch + # :nodoc: + attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block # @@ -290,7 +540,8 @@ class OptionParser end def self.incompatible_argument_styles(arg, t) - raise ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}" + raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}", + ParseError.filter_backtrace(caller(2))) end def self.pattern @@ -299,28 +550,22 @@ class OptionParser def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, - desc = ([] if short or long), block = Proc.new) + desc = ([] if short or long), block = nil, values = nil, &_block) raise if Array === pattern - @pattern, @conv, @short, @long, @arg, @desc, @block = - pattern, conv, short, long, arg, desc, block + block ||= _block + @pattern, @conv, @short, @long, @arg, @desc, @block, @values = + pattern, conv, short, long, arg, desc, block, values end # - # OptionParser::Switch#parse_arg(arg) {non-serious error handler} + # Parses +arg+ and returns rest of +arg+ and matched portion to the + # argument pattern. Yields when the pattern doesn't match substring. # - # 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 + private def parse_arg(arg) # :nodoc: + pattern or return nil, [arg] unless m = pattern.match(arg) yield(InvalidArgument, arg) - return arg, nil + return arg, [] end if String === m m = [s = m] @@ -334,59 +579,39 @@ class OptionParser 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. + # Parses argument, converts and returns +arg+, +block+ and result of + # conversion. Yields at semi-error condition instead of raising an + # exception. # - def conv_arg(arg, val = nil) - if block - if conv - val = conv.call(*val) - else - val = *val - end - return arg, block, val + private def conv_arg(arg, val = []) # :nodoc: + v, = *val + if conv + val = conv.call(*val) else - return arg, nil + val = proc {|v| v}.call(*val) + end + if @values + @values.include?(val) or raise InvalidArgument, v end + return arg, block, val 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 + + # + # Produces the summary text. Each line of the summary is yielded to the + # block (without newline). + # + # +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 words, the right + # side (description part) starts after +width+ columns. + # +max+:: Maximum width of left side -> the options are filled within + # +max+ columns. + # +indent+:: Prefix string indents all summarized lines. + # + def summarize(sdone = {}, ldone = {}, width = 1, max = width - 1, indent = "") + sopts, lopts = [], [], 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 @@ -397,18 +622,24 @@ class OptionParser 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 + l < max or sopts.empty? or left << +'' + left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s end - left[0] << arg if arg - mlen = left.collect {|s| s.length}.max.to_i + if arg + left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg) + end + mlen = left.collect {|ss| ss.length}.max.to_i while mlen > width and l = left.shift - mlen = left.collect {|s| s.length}.max.to_i if l.length == mlen + mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen + if l.length < width and (r = right[0]) and !r.empty? + l = l.to_s.ljust(width) + ' ' + r + right.shift + end yield(indent + l) end - while (l = left.shift; r = right.shift; l or r) + 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 @@ -416,37 +647,114 @@ class OptionParser self end + def add_banner(to) # :nodoc: + unless @short or @long + s = desc.join + to << " [" + s + "]..." unless s.empty? + end + to + end + + def match_nonswitch?(str) # :nodoc: + @pattern =~ str unless @short or @long + end + + # + # Main name of the switch. + # + def switch_name + (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '') + end + + def compsys(sdone, ldone) # :nodoc: + sopts, lopts = [], [] + @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 + + (sopts+lopts).each do |opt| + # "(-x -c -r)-l[left justify]" + if /\A--\[no-\](.+)$/ =~ opt + o = $1 + yield("--#{o}", desc.join("")) + yield("--no-#{o}", desc.join("")) + else + yield("#{opt}", desc.join("")) + end + end + end + + def pretty_print_contents(q) # :nodoc: + if @block + q.text ":" + @block.source_location.join(":") + ":" + first = false + else + first = true + end + [@short, @long].each do |list| + list.each do |opt| + if first + q.text ":" + first = false + end + q.breakable + q.text opt + end + end + end + + def pretty_print(q) # :nodoc: + q.object_group(self) {pretty_print_contents(q)} + end + + def omitted_argument(val) # :nodoc: + val.pop if val.size == 3 and val.last.nil? + val + end + # # Switch that takes no arguments. # class NoArgument < self + # # Raises an exception if any arguments given. # - def parse(arg, argv, &error) + def parse(arg, argv) yield(NeedlessArgument, arg) if arg conv_arg(arg) end - def self.incompatible_argument_styles(*) + + def self.incompatible_argument_styles(*) # :nodoc: end - def self.pattern + + def self.pattern # :nodoc: Object end + + def pretty_head # :nodoc: + "NoArgument" + end end # # Switch that takes an argument. # class RequiredArgument < self + # # Raises an exception if argument is not present. # - def parse(arg, argv, &error) + def parse(arg, argv, &_) unless arg raise MissingArgument if argv.empty? arg = argv.shift end - conv_arg(*parse_arg(arg, &error)) + conv_arg(*parse_arg(arg, &method(:raise))) + end + + def pretty_head # :nodoc: + "Required" end end @@ -454,6 +762,7 @@ class OptionParser # Switch that can omit argument. # class OptionalArgument < self + # # Parses argument if given, or uses default value. # @@ -461,50 +770,61 @@ class OptionParser if arg conv_arg(*parse_arg(arg, &error)) else - conv_arg(arg) + omitted_argument conv_arg(arg) end end + + def pretty_head # :nodoc: + "Optional" + end end # - # ? + # Switch that takes an argument, which does not begin with '-' or is '-'. # class PlacedArgument < self + # - # ? + # Returns nil if argument is not present or begins with '-' and is not '-'. # def parse(arg, argv, &error) - if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0])) - return nil, block, nil + if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0])) + return nil, block end opt = (val = parse_arg(val, &error))[1] val = conv_arg(*val) if opt and !arg argv.shift else + omitted_argument val val[0] = nil end val end + + def pretty_head # :nodoc: + "Placed" + end end end # # Simple option list providing mapping from short and/or long option - # string to ((<OptionParser::Switch>)), and mapping from acceptable - # argument to matching pattern and converter pair. Also provides - # summary feature. + # string to OptionParser::Switch and mapping from acceptable argument to + # matching pattern and converter pair. Also provides summary feature. # class List + # :nodoc: + # 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 @@ -518,12 +838,24 @@ class OptionParser @list = [] end + def pretty_print(q) # :nodoc: + q.group(1, "(", ")") do + @list.each do |sw| + next unless Switch === sw + q.group(1, "(" + sw.pretty_head, ")") do + sw.pretty_print_contents(q) + end + end + end + end + # # See OptionParser.accept. # - def accept(t, pat = /.*/, &block) + def accept(t, pat = /.*/m, &block) if pat - pat.respond_to?(:match) or raise TypeError, "has no `match'" + pat.respond_to?(:match) or + raise TypeError, "has no 'match'", ParseError.filter_backtrace(caller(2)) else pat = t if t.respond_to?(:match) end @@ -541,158 +873,156 @@ class OptionParser end # - # OptionParser::List#update(sw, sopts, lopts, nlopts = nil) + # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+. # - # Adds ((|sw|)) according to ((|sopts|)), ((|lopts|)) and - # ((|nlopts|)). - # :Parameters: - # : ((|sw|)) - # ((<OptionParser::Switch>)) instance to be added. - # : ((|sopts|)) - # short style options list. - # : ((|lopts|)) - # long style options list. - # : ((|nlopts|)) - # negated long style options list. + # +sw+:: OptionParser::Switch instance to be added. + # +sopts+:: Short style option list. + # +lopts+:: Long style option list. + # +nlopts+:: Negated long style options list. # - def update(sw, sopts, lopts, nsw = nil, nlopts = nil) - o = nil + private def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: 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 the head of the list, and associates short, long + # and negated long options. Arguments are: + # + # +switch+:: OptionParser::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. + # + # 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 the tail of the list, and associates short, long + # and negated long options. Arguments are: + # + # +switch+:: OptionParser::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. + # + # 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|)) - # ((<OptionParser::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. + # Searches +key+ in +id+ list. The result is returned or yielded if a + # block is given. If it isn't found, nil is returned. # def search(id, key) if list = __send__(id) val = list.fetch(key) {return nil} - return val unless block_given? - yield(val) + block_given? ? yield(val) : 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. + # Searches list +id+ for +opt+ and the optional patterns for completion + # +pat+. If +icase+ is true, the search is case insensitive. The result + # is returned or yielded if a block is given. If it isn't found, nil is + # returned. # def complete(id, opt, icase = false, *pat, &block) __send__(id).complete(opt, icase, *pat, &block) end + def get_candidates(id) + yield __send__(id).keys + end + # - # OptionParser::List#summarize(*args) {...} + # Iterates over each option, passing the option to the +block+. # - # 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 each_option(&block) + list.each(&block) + end + + # + # Creates the summary table, passing each line to the +block+ (without + # newline). The arguments +args+ are passed along to the summarize + # method which is called on every option. # def summarize(*args, &block) - list.each do |opt| + sum = [] + list.reverse_each do |opt| if opt.respond_to?(:summarize) # perhaps OptionParser::Switch - opt.summarize(*args, &block) - elsif opt.empty? - yield("") + s = [] + opt.summarize(*args) {|l| s << l} + sum.concat(s.reverse) + elsif !opt or opt.empty? + sum << "" + elsif opt.respond_to?(:each_line) + sum.concat([*opt.each_line].reverse) else - opt.each(&block) + sum.concat([*opt.each].reverse) + end + end + sum.reverse_each(&block) + end + + def add_banner(to) # :nodoc: + list.each do |opt| + if opt.respond_to?(:add_banner) + opt.add_banner(to) + end + end + to + end + + def compsys(*args, &block) # :nodoc: + list.each do |opt| + if opt.respond_to?(:compsys) + opt.compsys(*args, &block) end end end end # - # Hash with completion search feature. See Completion module. + # Hash with completion search feature. See OptionParser::Completion. # class CompletingHash < Hash include Completion # - # OptionParser::CompletingHash#match(key) - # # Completion for hash key. # def match(key) - return key, *fetch(key) { + *values = fetch(key) { raise AmbiguousArgument, catch(:ambiguous) {return complete(key)} } + return key, *values end end + # :stopdoc: + # - # 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}))) + # Enumeration of acceptable argument styles. Possible values are: # - # Use like (({--switch=argument}))(long style) or - # (({-Xargument}))(short style). For short style, only portion - # matched to ((<argument pattern>)) is dealed as argument. + # NO_ARGUMENT:: The switch takes no arguments. (:NONE) + # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED) + # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL) + # + # Use like --switch=argument (long style) or -Xargument (short style). For + # short style, only portion matched to argument pattern is treated as + # argument. # - - # :stopdoc: ArgumentStyle = {} NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument} RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument} @@ -700,38 +1030,96 @@ class OptionParser 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} + COMPSYS_HEADER = <<'XXX' # :nodoc: + +typeset -A opt_args +local context state line + +_arguments -s -S \ +XXX + + def compsys(to, name = File.basename($0)) # :nodoc: + to << "#compdef #{name}\n" + to << COMPSYS_HEADER + visit(:compsys, {}, {}) {|o, d| + to << %Q[ "#{o}[#{d.gsub(/[\\\"\[\]]/, '\\\\\&')}]" \\\n] + } + to << " '*:file:_files' && return 0\n" + end + + def help_exit + if $stdout.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) + less = ENV["LESS"] + args = [{"LESS" => "#{less} -Fe"}, pager, "w"] + print = proc do |f| + f.puts help + rescue Errno::EPIPE + # pager terminated + end + if Process.respond_to?(:fork) and false + IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)} + # unreachable + end + IO.popen(*args, &print) + else + puts help + end + exit + end + # - # OptionParser::Officious # Default options for ARGV, which never appear in option summary. # Officious = {} - # --help - # Shows option summary. + # + # --help + # Shows option summary. + # Officious['help'] = proc do |parser| - Switch::NoArgument.new do - puts parser.help + Switch::NoArgument.new do |arg| + parser.help_exit + end + end + + # + # --*-completion-bash=WORD + # Shows candidates for command line completion. + # + Officious['*-completion-bash'] = proc do |parser| + Switch::RequiredArgument.new do |arg| + puts parser.candidate(arg) + exit + end + end + + # + # --*-completion-zsh[=NAME:FILE] + # Creates zsh completion file. + # + Officious['*-completion-zsh'] = proc do |parser| + Switch::OptionalArgument.new do |arg| + parser.compsys($stdout, arg) exit end end - # --version - # Shows version string if (({::Version})) is defined. + # + # --version + # Shows version string if Version is defined. + # Officious['version'] = proc do |parser| Switch::OptionalArgument.new do |pkg| if pkg begin - require 'optparse/version' + require_relative 'optparse/version' rescue LoadError else show_version(*pkg.split(/,/)) or @@ -751,34 +1139,23 @@ class OptionParser # 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. ((<OptionParser.new>)) - :Parameters: - : ((|banner|)) - banner message. - : ((|width|)) - summary width. - : ((|indent|)) - summary indent. - : (({block})) - to be evaluated in the new instance context. -=end #'#"#`# + # + # Initializes a new instance and evaluates the optional block in context + # of the instance. Arguments +args+ are passed to #new, see there for + # description of parameters. + # + # This method is *deprecated*, its behavior corresponds to the older #new + # method. + # 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 + # + # Returns an incremented value of +default+ according to +arg+. + # def self.inc(arg, default = nil) case arg when Integer @@ -787,23 +1164,21 @@ class OptionParser default.to_i + 1 end end + + # + # See self.inc + # 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 #'#"#`# + # + # Initializes the instance and yields itself if called with a block. + # + # +banner+:: Banner message. + # +width+:: Summary width. + # +indent+:: Summary indent. + # def initialize(banner = nil, width = 32, indent = ' ' * 4) @stack = [DefaultList, List.new, List.new] @program_name = nil @@ -811,160 +1186,201 @@ class OptionParser @summary_width = width @summary_indent = indent @default_argv = ARGV + @require_exact = false + @raise_unknown = true add_officious yield self if block_given? end - # :nodoc: - def add_officious + def add_officious # :nodoc: list = base() - Officious.each_pair do |opt, block| + Officious.each do |opt, block| list.long[opt] ||= block.call(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 #'#"#`# + # + # Terminates option parsing. Optional parameter +arg+ is a string pushed + # back to be the first non-option argument. + # def terminate(arg = nil) self.class.terminate(arg) end + # + # See #terminate. + # def self.terminate(arg = nil) throw :terminate, arg end @stack = [DefaultList] + # + # Returns the global top option list. + # + # Do not use directly. + # 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 #'#"#`# + # + # Directs to accept specified class +t+. The argument string is passed to + # the block in which it should be converted to the desired class. + # + # +t+:: Argument class specifier, any object including Class. + # +pat+:: Pattern for argument, defaults to +t+ if it responds to match. + # + # accept(t, pat, &block) + # def accept(*args, &blk) top.accept(*args, &blk) end + # + # See #accept. + # 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 #'#"#`# + # + # Directs to reject specified class argument. + # + # +type+:: Argument class specifier, any object including Class. + # + # reject(type) + # def reject(*args, &blk) top.reject(*args, &blk) end + # + # See #reject. + # 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 + # + # Instance methods + # + + # Heading banner preceding summary. + attr_writer :banner + + # Program name to be emitted in error message and default banner, + # defaults to $0. + attr_writer :program_name + + # Width for option list portion of summary. Must be Numeric. + attr_accessor :summary_width + + # Indentation for summary. Must be String (or have + String method). + attr_accessor :summary_indent + + # Strings to be parsed in default. attr_accessor :default_argv + # Whether to require that options match exactly (disallows providing + # abbreviated long option as short option). + attr_accessor :require_exact + + # Whether to raise at unknown option. + attr_accessor :raise_unknown + + # + # Heading banner preceding summary. + # def banner - @banner ||= "Usage: #{program_name} [options]" + unless @banner + @banner = +"Usage: #{program_name} [options]" + visit(:add_banner, @banner) + end + @banner end + # + # Program name to be emitted in error message and default banner, defaults + # to $0. + # def program_name - @program_name || File.basename($0, '.*') + @program_name || strip_ext(File.basename($0)) + end + + private def strip_ext(name) # :nodoc: + exts = /#{ + require "rbconfig" + Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) + }\z/o + name.sub(exts, "") end -# for experimental cascading :-) + # 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 ((<program_name>)), (({version})) and - (({release})). -=end #'#"#`# - attr_writer :version, :release + # Version + attr_writer :version + # Release code + attr_writer :release + # + # Version + # def version - @version || (defined?(::Version) && ::Version) + (defined?(@version) && @version) || (defined?(::Version) && ::Version) end + # + # Release code + # def release - @release || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE) + (defined?(@release) && @release) || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE) end + # + # Returns version string from program_name, version and release. + # def ver if v = version - str = "#{program_name} #{[v].join('.')}" + str = +"#{program_name} #{[v].join('.')}" str << " (#{v})" if v = release str end end + # + # Shows warning message with the program name + # + # +mesg+:: Message, defaulted to +$!+. + # + # See Kernel#warn. + # def warn(mesg = $!) super("#{program_name}: #{mesg}") end + # + # Shows message with the program name then aborts. + # + # +mesg+:: Message, defaulted to +$!+. + # + # See Kernel#abort. + # def abort(mesg = $!) super("#{program_name}: #{mesg}") end -=begin ---- OptionParser#top - Subject of ((<on>))/((<on_head>)), ((<accept>))/((<reject>)). -=end #'#"#`# + # + # Subject of #on / #on_head, #accept / #reject + # def top @stack[-1] end -=begin ---- OptionParser#base - Subject of ((<on_tail>)). -=end #'#"#`# + # + # Subject of #on_tail. + # def base @stack[1] end -=begin ---- OptionParser#new - Pushes a new (({List})). -=end #'#"#`# + # + # Pushes a new List. + # + # If a block is given, yields +self+ and returns the result of the + # block, otherwise returns +self+. + # def new @stack.push(List.new) if block_given? @@ -974,120 +1390,95 @@ class OptionParser end end -=begin ---- OptionParser#remove - Removes the last (({List})). -=end #'#"#`# + # + # Removes the last List. + # 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 #'#"#`# + # + # Puts option summary into +to+ and returns +to+. Yields each line if + # a block is given. + # + # +to+:: Output destination, which must have method <<. Defaults to []. + # +width+:: Width of left side, defaults to @summary_width. + # +max+:: Maximum length allowed for left side, defaults to +width+ - 1. + # +indent+:: Indentation, defaults to @summary_indent. + # def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) - visit(:summarize, {}, {}, width, max, indent, &(blk || proc {|l| to << l + $/})) + nl = "\n" + blk ||= proc {|l| to << (l.index(nl, -1) ? l : l + nl)} + visit(:summarize, {}, {}, width, max, indent, &blk) to end -=begin ---- OptionParser#help ---- OptionParser#to_s - Returns option summary string. -=end #'#"#`# - def help; summarize(banner.to_s.sub(/\n?\z/, "\n")) end + # + # Returns option summary string. + # + def help; summarize("#{banner}".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 ((<OptionParser::Switch>)). - :Parameters: - : ((|*opts|)) - option definition: - : argument style - see ((<OptionParser::ArgumentStyle>)) - : argument pattern - acceptable option argument format, must pre-defined with - ((<OptionParser.accept>)) or ((<OptionParser#accept>)), or - (({Regexp})). This can appear once or assigned as (({String})) - if not present, otherwise causes exception (({ArgumentError})). - - cf. ((<Acceptable argument classes>)). - : 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 + def pretty_print(q) # :nodoc: + q.object_group(self) do + first = true + if @stack.size > 2 + @stack.each_with_index do |s, i| + next if i < 2 + next if s.list.empty? + if first + first = false + q.text ":" + end + q.breakable + s.pretty_print(q) + end end end + end + + def inspect # :nodoc: + require 'pp' + pretty_print_inspect + end + + # + # Returns option summary list. + # + def to_a; summarize("#{banner}".split(/^/)) end + + # + # Checks if an argument is given twice, in which case an ArgumentError is + # raised. Called from OptionParser#switch only. + # + # +obj+:: New argument. + # +prv+:: Previously specified argument. + # +msg+:: Exception message. + # + private def notwice(obj, prv, msg) # :nodoc: + unless !prv or prv == obj + raise(ArgumentError, "argument #{msg} given twice: #{obj}", + ParseError.filter_backtrace(caller(2))) + end obj end - private :notwice - def make_switch(*opts, &block) + SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: + + # :call-seq: + # make_switch(params, block = nil) + # + # :include: ../doc/optparse/creates_option.rdoc + # + def make_switch(opts, block = nil) 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 + q, a = nil + has_arg = false + values = nil opts.each do |o| # argument class @@ -1101,9 +1492,13 @@ class OptionParser end # directly specified pattern(any object possible to match) - if !(String === o) and o.respond_to?(:match) + if !Completion.completable?(o) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') - conv = (pattern.method(:convert).to_proc if pattern.respond_to?(:convert)) + if pattern.respond_to?(:convert) + conv = pattern.method(:convert).to_proc + else + conv = SPLAT_PROC + end next end @@ -1111,21 +1506,28 @@ class OptionParser case o when Proc, Method block = notwice(o, block, 'block') - when Array, Hash + when Array, Hash, Set + if Array === o + o, v = o.partition {|v,| Completion.completable?(v)} + values = notwice(v, values, 'values') unless v.empty? + next if o.empty? + end case pattern when CompletingHash when nil pattern = CompletingHash.new - conv = (pattern.method(:convert).to_proc if pattern.respond_to?(:convert)) + 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}} + o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} + when Range + values = notwice(o, values, 'values') when Module - raise ArgumentError, "unsupported argument type: #{o}" + raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') - when /^--no-([^\[\]=\s]*)(.+)?/ + when /\A--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style @@ -1133,9 +1535,10 @@ class OptionParser default_style = Switch::NoArgument default_pattern, conv = search(:atype, FalseClass) unless default_pattern ldesc << "--no-#{q}" - long << 'no-' + (q = q.downcase) + (q = q.downcase).tr!('_', '-') + long << "no-#{q}" nolong << q - when /^--\[no-\]([^\[\]=\s]*)(.+)?/ + when /\A--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a @@ -1143,11 +1546,12 @@ class OptionParser default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--[no-]#{q}" - long << (o = q.downcase) + (o = q.downcase).tr!('_', '-') + long << o not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument - nolong << 'no-' + o - when /^--([^\[\]=\s]*)(.+)?/ + nolong << "no-#{o}" + when /\A--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1155,17 +1559,20 @@ class OptionParser default_pattern, conv = search(:atype, o) unless default_pattern end ldesc << "--#{q}" - long << (o = q.downcase) - when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ + (o = q.downcase).tr!('_', '-') + long << o + when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ 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 + else + has_arg = true end sdesc << "-#{q}" short << Regexp.new(q) - when /^-(.)(.+)?/ + when /\A-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1174,129 +1581,200 @@ class OptionParser end sdesc << "-#{q}" short << q - when /^=/ + when /\A=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else - desc.push(o) + desc.push(o) if o && !o.empty? end end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern - s = if short.empty? and long.empty? - raise ArgumentError, "no switch given" if style or pattern or block - desc - else - (style || default_style).new(pattern || default_pattern, - conv, sdesc, ldesc, arg, desc, block) - end + if Range === values and klass + unless (!values.begin or klass === values.begin) and + (!values.end or klass === values.end) + raise ArgumentError, "range does not match class" + end + end + if !(short.empty? and long.empty?) + if has_arg and default_style == Switch::NoArgument + default_style = Switch::RequiredArgument + end + s = (style || default_style).new(pattern || default_pattern, + conv, sdesc, ldesc, arg, desc, block, values) + elsif !block + if style or pattern + raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) + end + s = desc + else + short << pattern + s = (style || default_style).new(pattern, + conv, nil, nil, arg, desc, block, values) + 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. + # ---- + # Option definition phase methods + # + # These methods are used to define options, or to construct an + # OptionParser instance in other words. - cf. ((<OptionParser#switch>)). -=end #'#"#`# + # :call-seq: + # define(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # def define(*opts, &block) - top.append(*(sw = make_switch(*opts, &block))) + top.append(*(sw = make_switch(opts, block))) sw[0] end + + # :call-seq: + # on(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # def on(*opts, &block) define(*opts, &block) self end alias def_option define + # :call-seq: + # define_head(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # def define_head(*opts, &block) - top.prepend(*(sw = make_switch(*opts, &block))) + top.prepend(*(sw = make_switch(opts, block))) sw[0] end + + # :call-seq: + # on_head(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + # The new option is added at the head of the summary. + # def on_head(*opts, &block) define_head(*opts, &block) self end alias def_head_option define_head + # :call-seq: + # define_tail(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # def define_tail(*opts, &block) - base.append(*(sw = make_switch(*opts, &block))) + base.append(*(sw = make_switch(opts, block))) sw[0] end + + # + # :call-seq: + # on_tail(*params, &block) + # + # :include: ../doc/optparse/creates_option.rdoc + # + # The new option is added at the tail of the summary. + # def on_tail(*opts, &block) define_tail(*opts, &block) self end alias def_tail_option define_tail + # + # Add separator in summary. + # 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) + # ---- + # Arguments parse phase methods + # + # These methods parse +argv+, convert, and store the results by + # calling handlers. As these methods do not modify +self+, +self+ + # can be frozen. + + # + # Parses command line arguments +argv+ in order. When a block is given, + # each non-option argument is yielded. When optional +into+ keyword + # argument is provided, the parsed option values are stored there via + # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other + # similar object). + # + # Returns the rest of +argv+ left unparsed. + # + def order(*argv, **keywords, &nonopt) argv = argv[0].dup if argv.size == 1 and Array === argv[0] - order!(argv, &block) + order!(argv, **keywords, &nonopt) + end + + # + # Same as #order, but removes switches destructively. + # Non-option arguments remain in +argv+. + # + def order!(argv = default_argv, into: nil, **keywords, &nonopt) + setter = ->(name, val) {into[name.to_sym] = val} if into + parse_in_order(argv, setter, **keywords, &nonopt) end - def order!(argv = default_argv, &nonopt) - opt, arg, sw, val, rest = nil - nonopt ||= proc {|arg| throw :terminate, arg} + private def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: + opt, arg, val, rest = nil + nonopt ||= proc {|a| throw :terminate, a} argv.unshift(arg) if arg = catch(:terminate) { while arg = argv.shift case arg # long option - when /\A--([^=]*)(?:=(.*))?/ + when /\A--([^=]*)(?:=(.*))?/m opt, rest = $1, $2 + opt.tr!('_', '-') begin - sw, = complete(:long, opt, true) + if exact + sw, = search(:long, opt) + else + sw, = complete(:long, opt, true) + end rescue ParseError + throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) + else + unless sw + throw :terminate, arg unless raise_unknown + raise InvalidOption, arg + end end begin - opt, sw, val = sw.parse(rest, argv) {|*exc| raise(*exc)} - sw.call(val) if sw + opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} + val = callback!(cb, 1, val) if cb + callback!(setter, 2, sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, rest) end # short option - when /\A-(.)((=).*|.+)?/ - opt, has_arg, eq, val, rest = $1, $3, $3, $2, $2 + when /\A-(.)((=).*|.+)?/m + eq, rest, opt = $3, $2, $1 + has_arg, val = eq, rest begin - unless sw = search(:short, opt) + sw, = search(:short, opt) + unless sw begin sw, = complete(:short, opt) # short option matched. - val = arg.sub(/\A-/, '') + val = arg.delete_prefix('-') has_arg = true rescue InvalidOption + raise if exact # if no short options match, try completion with long # options. sw, = complete(:long, opt) @@ -1304,337 +1782,504 @@ class OptionParser end end rescue ParseError + throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) end begin - opt, sw, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} + opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} + rescue ParseError + raise $!.set_option(arg, arg.length > 2) + else raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" - argv.unshift(opt) if opt and (opt = opt.sub(/\A-*/, '-')) != '-' - sw.call(val) if sw + end + begin + argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') + val = callback!(cb, 1, val) if cb + callback!(setter, 2, sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, arg.length > 2) end # non-option argument else - nonopt.call(arg) + catch(:prune) do + visit(:each_option) do |sw0| + sw = sw0 + sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg) + end + nonopt.call(arg) + end end end nil } + visit(:search, :short, nil) {|sw| sw.block.call(*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) + # Calls callback with _val_. + private def callback!(cb, max_arity, *args) # :nodoc: + args.compact! + + if (size = args.size) < max_arity and cb.to_proc.lambda? + (arity = cb.arity) < 0 and arity = (1-arity) + arity = max_arity if arity > max_arity + args[arity - 1] = nil if arity > size + end + cb.call(*args) + end + + # + # Parses command line arguments +argv+ in permutation mode and returns + # list of non-option arguments. When optional +into+ keyword + # argument is provided, the parsed option values are stored there via + # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other + # similar object). + # + def permute(*argv, **keywords) argv = argv[0].dup if argv.size == 1 and Array === argv[0] - permute!(argv) + permute!(argv, **keywords) end - def permute!(argv = default_argv) + # + # Same as #permute, but removes switches destructively. + # Non-option arguments remain in +argv+. + # + def permute!(argv = default_argv, **keywords) nonopts = [] - arg = nil - order!(argv) {|arg| nonopts << arg} + order!(argv, **keywords) {|nonopt| nonopts << nonopt} 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) + # + # Parses command line arguments +argv+ in order when environment variable + # POSIXLY_CORRECT is set, and in permutation mode otherwise. + # When optional +into+ keyword argument is provided, the parsed option + # values are stored there via <code>[]=</code> method (so it can be Hash, + # or OpenStruct, or other similar object). + # + def parse(*argv, **keywords) argv = argv[0].dup if argv.size == 1 and Array === argv[0] - parse!(argv) + parse!(argv, **keywords) end - def parse!(argv = default_argv) + # + # Same as #parse, but removes switches destructively. + # Non-option arguments remain in +argv+. + # + def parse!(argv = default_argv, **keywords) if ENV.include?('POSIXLY_CORRECT') - order!(argv) + order!(argv, **keywords) 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 + permute!(argv, **keywords) + end + end + + # + # Wrapper method for getopts.rb. + # + # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option") + # # params["a"] = true # -a + # # params["b"] = "1" # -b1 + # # params["foo"] = "1" # --foo + # # params["bar"] = "x" # --bar x + # # params["zot"] = "z" # --zot Z + # + # Option +symbolize_names+ (boolean) specifies whether returned Hash keys should be Symbols; defaults to +false+ (use Strings). + # + # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option", symbolize_names: true) + # # params[:a] = true # -a + # # params[:b] = "1" # -b1 + # # params[:foo] = "1" # --foo + # # params[:bar] = "x" # --bar x + # # params[:zot] = "z" # --zot Z + # + def getopts(*args, symbolize_names: false, **keywords) + argv = Array === args.first ? args.shift : default_argv + single_options, *long_options = *args + + result = {} + setter = (symbolize_names ? + ->(name, val) {result[name.to_sym] = val} + : ->(name, val) {result[name] = val}) + + single_options.scan(/(.)(:)?/) do |opt, val| + if val + setter[opt, nil] + define("-#{opt} VAL") + else + setter[opt, false] + define("-#{opt}") + end + end if single_options + + long_options.each do |arg| + arg, desc = arg.split(';', 2) + opt, val = arg.split(':', 2) + if val + setter[opt, (val unless val.empty?)] + define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) + else + setter[opt, false] + define("--#{opt}", *[desc].compact) + end + end + + parse_in_order(argv, setter, **keywords) + result + end + + # + # See #getopts. + # + def self.getopts(*args, symbolize_names: false) + new.getopts(*args, symbolize_names: symbolize_names) + end + + # + # Traverses @stack, sending each element method +id+ with +args+ and + # +block+. + # + private def visit(id, *args, &block) # :nodoc: @stack.reverse_each do |el| - el.send(id, *args, &block) + 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, k) - visit(:search, id, k) 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((<OptionParser::Switch>)). - :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) + + # + # Searches +key+ in @stack for +id+ hash and returns or yields the result. + # + private def search(id, key) # :nodoc: + block_given = block_given? + visit(:search, id, key) do |k| + return block_given ? yield(k) : k + end + end + + # + # Completes shortened long style option switch and returns pair of + # canonical switch and switch descriptor OptionParser::Switch. + # + # +typ+:: Searching table. + # +opt+:: Searching key. + # +icase+:: Search case insensitive if true. + # +pat+:: Optional pattern for completion. + # + private def complete(typ, opt, icase = false, *pat) # :nodoc: 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 + ambiguous = catch(:ambiguous) { + visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } + exc = ambiguous ? AmbiguousOption : InvalidOption + raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) 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 + + # + # Returns additional info. + # + def additional_message(typ, opt) + return unless typ and opt and defined?(DidYouMean::SpellChecker) + all_candidates = [] + visit(:get_candidates, typ) do |candidates| + all_candidates.concat(candidates) + end + all_candidates.select! {|cand| cand.is_a?(String) } + checker = DidYouMean::SpellChecker.new(dictionary: all_candidates) + DidYouMean.formatter.message_for(all_candidates & checker.correct(opt)) + end + + # + # Return candidates for +word+. + # + def candidate(word) + list = [] + case word + when '-' + long = short = true + when /\A--/ + word, arg = word.split(/=/, 2) + argpat = Completion.regexp(arg, false) if arg and !arg.empty? + long = true + when /\A-/ + short = true + end + pat = Completion.regexp(word, long) + visit(:each_option) do |opt| + next unless Switch === opt + opts = (long ? opt.long : []) + (short ? opt.short : []) + opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat + if /\A=/ =~ opt.arg + opts.map! {|sw| sw + "="} + if arg and CompletingHash === opt.pattern + if opts = opt.pattern.candidate(arg, false, argpat) + opts.map!(&:last) + end + end + end + list.concat(opts) + end + list + end + + # + # Loads options from file names as +filename+. Does nothing when the file + # is not present. Returns whether successfully loaded. + # + # +filename+ defaults to basename of the program without suffix in a + # directory ~/.options, then the basename with '.options' suffix + # under XDG and Haiku standard places. + # + # The optional +into+ keyword argument works exactly like that accepted in + # method #parse. + # + def load(filename = nil, **keywords) + unless filename + basename = File.basename($0, '.*') + return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil + basename << ".options" + if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty? + # https://specifications.freedesktop.org/basedir-spec/latest/#variables + # + # If $XDG_CONFIG_HOME is either not set or empty, a default + # equal to $HOME/.config should be used. + xdg = ['~/.config', true] + end + return [ + xdg, + + *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), + + # Haiku + ['~/config/settings', true], + ].any? {|dir, expand| + next if !dir or dir.empty? + filename = File.join(dir, basename) + filename = File.expand_path(filename) if expand + load(filename, **keywords) rescue nil + } end begin - parse(*IO.readlines(filename).each {|s| s.chomp!}) + parse(*File.readlines(filename, chomp: true), **keywords) 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, '.*')) + # + # Parses environment variable +env+ or its uppercase with splitting like a + # shell. + # + # +env+ defaults to the basename of the program. + # + def environment(env = File.basename($0, '.*'), **keywords) env = ENV[env] || ENV[env.upcase] or return - parse(*Shellwords.shellwords(env)) + require 'shellwords' + parse(*Shellwords.shellwords(env), **keywords) end - -=begin -= Acceptable argument classes -=end #'#"#`# + # + # Acceptable argument classes + # -=begin -: Object - any string, and no conversion. this is fall-back. -=end #'#"#`# + # + # Any string and no conversion. This is fall-back. + # accept(Object) {|s,|s or s.nil?} accept(NilClass) {|s,|s} -=begin -: String - any none-empty string, and no conversion. -=end #'#"#`# - accept(String, /.+/) {|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 #'#"#`# + # + # Any non-empty string, and no conversion. + # + accept(String, /.+/m) {|s,*|s} + + # + # 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. + # decimal = '\d+(?:_\d+)*' binary = 'b[01]+(?:_[01]+)*' hex = 'x[\da-f]+(?:_[\da-f]+)*' - octal = "0(?:[0-7]*(?:_[0-7]+)*|#{binary}|#{hex})" + 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(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,| + begin + Integer(s) + rescue ArgumentError + raise OptionParser::InvalidArgument, s + end if s + } + + # + # Float number format, and converts to Float. + # + float = "(?:#{decimal}(?=(.)?)(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?" + floatpat = %r"\A[-+]?#{float}\z"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 #'#"#`# + # + # Generic numeric format, converts to Integer for integer format, Float + # for float format, and Rational for rational format. + # + real = "[-+]?(?:#{octal}|#{float})" + accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, f, n,| + if n + Rational(d, n) + elsif f + Float(s) + else + Integer(s) + end + } + + # + # Decimal integer format, to be converted to Integer. + # + DecimalInteger = /\A[-+]?#{decimal}\z/io + accept(DecimalInteger, DecimalInteger) {|s,| + begin + Integer(s, 10) + rescue ArgumentError + raise OptionParser::InvalidArgument, s + end if s + } + + # + # Ruby/C like octal/hexadecimal/binary integer format, to be converted to + # Integer. + # + OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io + accept(OctalInteger, OctalInteger) {|s,| + begin + Integer(s, 8) + rescue ArgumentError + raise OptionParser::InvalidArgument, s + end if s + } + + # + # Decimal integer/float number format, to be converted to Integer for + # integer format, Float for float format. + # 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 ((<TrueClass>)), but defaulted to (({false})). -=end #'#"#`# + accept(DecimalNumeric, floatpat) {|s, f| + begin + if f + Float(s) + else + Integer(s) + end + rescue ArgumentError + raise OptionParser::InvalidArgument, s + end if s + } + + # + # 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/+/-. + # 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? + yesno['nil'] = false # should be nil? accept(TrueClass, yesno) {|arg, val| val == nil or val} + # + # Similar to TrueClass, but defaults to false. + # accept(FalseClass, yesno) {|arg, val| val != nil and val} -=begin -: Array - List of strings separated by "," -=end #'#"#`# - accept(Array) do |s,| + # + # List of strings separated by ",". + # + accept(Array) do |s, | if s - s = s.split(',').collect {|s| s unless s.empty?} + s = s.split(',').collect {|ss| ss unless ss.empty?} end s end -=begin -: Regexp - Regular expression with option. -=end + # + # Regular expression with options. + # 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 ((<OptionParser>)) -=== 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 ---- OptionParser::ParseError#to_str - Default stringizing method to emit standard error message. -=end #'#"#`# + case o = o.delete("imx") + when "" + when "u" + s = s.encode(Encoding::UTF_8) + when "e" + s = s.encode(Encoding::EUC_JP) + when "s" + s = s.encode(Encoding::SJIS) + when "n" + f |= Regexp::NOENCODING + else + raise OptionParser::InvalidArgument, "unknown regexp option - #{o}" + end + else + s ||= all + end + Regexp.new(s, f) + end + + # + # Exceptions + # + + # + # Base class of exceptions from OptionParser. + # class ParseError < RuntimeError - Reason = 'parse error'.freeze + # Reason which caused the error. + Reason = 'parse error' - def initialize(*args) + # :nodoc: + def initialize(*args, additional: nil) + @additional = additional + @arg0, = args @args = args @reason = nil end attr_reader :args attr_writer :reason + attr_accessor :additional + # + # Pushes back erred argument(s) to +argv+. + # def recover(argv) argv[0, 0] = @args argv end + DIR = File.join(__dir__, '') + def self.filter_backtrace(array) + unless $DEBUG + array.delete_if {|bt| bt.start_with?(DIR)} + end + array + end + + def set_backtrace(array) + super(self.class.filter_backtrace(array)) + end + def set_option(opt, eq) if eq @args[0] = opt @@ -1644,101 +2289,84 @@ Base class of exceptions from ((<OptionParser>)) self end + # + # Returns error reason. Override this for I18N. + # def reason @reason || self.class::Reason end def inspect - "#<#{self.class.to_s}: #{args.join(' ')}>" + "#<#{self.class}: #{args.join(' ')}>" end + # + # Default stringizing method to emit standard error message. + # def message - reason + ': ' + args.join(' ') + "#{reason}: #{args.join(' ')}#{additional[@arg0] if additional}" end alias to_s message - alias to_str message end -=begin -== ((:OptionParser::AmbiguousOption:)) -Raises when encountered ambiguously completable string. -=== Superclass -((<OptionParser::ParseError>)) -=end #'#"#`# + # + # Raises when ambiguously completable string is encountered. + # class AmbiguousOption < ParseError - const_set(:Reason, 'ambiguous option'.freeze) + Reason = 'ambiguous option' # :nodoc: end -=begin -== ((:OptionParser::NeedlessArgument:)) -Raises when encountered argument for switch defined as which takes no -argument. -=== Superclass -((<OptionParser::ParseError>)) -=end #'#"#`# + # + # Raises when there is an argument for a switch which takes no argument. + # class NeedlessArgument < ParseError - const_set(:Reason, 'needless argument'.freeze) + Reason = 'needless argument' # :nodoc: end -=begin -== ((:OptionParser::MissingArgument:)) -Raises when no argument found for switch defined as which needs -argument. -=== Superclass -((<OptionParser::ParseError>)) -=end #'#"#`# + # + # Raises when a switch with mandatory argument has no argument. + # class MissingArgument < ParseError - const_set(:Reason, 'missing argument'.freeze) + Reason = 'missing argument' # :nodoc: end -=begin -== ((:OptionParser::InvalidOption:)) -Raises when undefined switch. -=== Superclass -((<OptionParser::ParseError>)) -=end #'#"#`# + # + # Raises when switch is undefined. + # class InvalidOption < ParseError - const_set(:Reason, 'invalid option'.freeze) + Reason = 'invalid option' # :nodoc: end -=begin -== ((:OptionParser::InvalidArgument:)) -Raises when the given argument does not match required format. -=== Superclass -((<OptionParser::ParseError>)) -=end #'#"#`# + # + # Raises when the given argument does not match required format. + # class InvalidArgument < ParseError - const_set(:Reason, 'invalid argument'.freeze) + Reason = 'invalid argument' # :nodoc: end -=begin -== ((:OptionParser::AmbiguousArgument:)) -Raises when the given argument word can't completed uniquely. -=== Superclass -((<OptionParser::InvalidArgument>)) -=end #'#"#`# + # + # Raises when the given argument word can't be completed uniquely. + # class AmbiguousArgument < InvalidArgument - const_set(:Reason, 'ambiguous argument'.freeze) + Reason = 'ambiguous argument' # :nodoc: end - -=begin -= Miscellaneous -=end #'#"#`# -=begin -== ((:OptionParser::Arguable:)) -Extends command line arguments array to parse itself. -=end #'#"#`# + # + # Miscellaneous + # + + # + # Extends command line arguments array (ARGV) to parse itself. + # module Arguable -=begin ---- OptionParser::Arguable#options=(opt) - Sets ((<OptionParser>)) object, when ((|opt|)) is (({false})) or - (({nil})), methods ((<OptionParser::Arguable#options>)) and - ((<OptionParser::Arguable#options=>)) are undefined. Thus, there - is no ways to access the ((<OptionParser>)) object via the - receiver object. -=end #'#"#`# + + # + # Sets OptionParser object, when +opt+ is +false+ or +nil+, methods + # OptionParser::Arguable#options and OptionParser::Arguable#options= are + # undefined. Thus, there is no ways to access the OptionParser object + # via the receiver object. + # def options=(opt) unless @optparse = opt class << self @@ -1748,21 +2376,14 @@ Extends command line arguments array to parse itself. end end -=begin ---- OptionParser::Arguable#options - Actual ((<OptionParser>)) object, automatically created if not - yet. - - If called as iterator, yields with the ((<OptionParser>)) object - and returns the result of the block. In this case, rescues any - ((<OptionParser::ParseError>)) exceptions in the block, just emits - error message to ((<STDERR>)) and returns (({nil})). - - :Parameters: - : (({block})) - Yielded with the ((<OptionParser>)) instance. - -=end #'#"#`# + # + # Actual OptionParser object, automatically created if nonexistent. + # + # If called with a block, yields the OptionParser object and returns the + # result of the block. If an OptionParser::ParseError exception occurs + # in the block, it is rescued, a error message printed to STDERR and + # +nil+ returned. + # def options @optparse ||= OptionParser.new @optparse.default_argv = self @@ -1775,55 +2396,68 @@ Extends command line arguments array to parse itself. 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 #'#"#`# + # + # Parses +self+ destructively in order and returns +self+ containing the + # rest arguments left unparsed. + # + def order!(**keywords, &blk) options.order!(self, **keywords, &blk) end + + # + # Parses +self+ destructively in permutation mode and returns +self+ + # containing the rest arguments left unparsed. + # + def permute!(**keywords) options.permute!(self, **keywords) end + + # + # Parses +self+ destructively and returns +self+ containing the + # rest arguments left unparsed. + # + def parse!(**keywords) options.parse!(self, **keywords) end + + # + # Substitution of getopts is possible as follows. Also see + # OptionParser#getopts. + # + # def getopts(*args) + # ($OPT = ARGV.getopts(*args)).each do |opt, val| + # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val" + # end + # rescue OptionParser::ParseError + # end + # + def getopts(*args, symbolize_names: false, **keywords) + options.getopts(self, *args, symbolize_names: symbolize_names, **keywords) + end + + # + # Initializes instance variable. + # def self.extend_object(obj) super obj.instance_eval {@optparse = nil} end - def initialize(*args) + + def initialize(*args) # :nodoc: super @optparse = nil end end -=begin -== OptionParser::Acceptables -Acceptable argument classes. Now contains (({DecimalInteger})), -(({OctalInteger})) and (({DecimalNumeric})). -see ((<Acceptable argument classes>)). -=end #'#"#`# + # + # Acceptable argument classes. Now contains DecimalInteger, OctalInteger + # and DecimalNumeric. See Acceptable argument classes (in source code). + # module Acceptables - const_set(:DecimalInteger, OptionParser::DecimalInteger) - const_set(:OctalInteger, OptionParser::OctalInteger) - const_set(:DecimalNumeric, OptionParser::DecimalNumeric) + # :stopdoc: + DecimalInteger = OptionParser::DecimalInteger + OctalInteger = OptionParser::OctalInteger + DecimalNumeric = OptionParser::DecimalNumeric + # :startdoc: 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 #'#"#`# +# An alias for OptionParser. +OptParse = OptionParser # :nodoc: |
