summaryrefslogtreecommitdiff
path: root/lib/optparse.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/optparse.rb')
-rw-r--r--lib/optparse.rb341
1 files changed, 270 insertions, 71 deletions
diff --git a/lib/optparse.rb b/lib/optparse.rb
index 060b13428a..069c3e436e 100644
--- a/lib/optparse.rb
+++ b/lib/optparse.rb
@@ -8,7 +8,6 @@
# See OptionParser for documentation.
#
-
#--
# == Developer Documentation (not for RDoc output)
#
@@ -48,9 +47,9 @@
#
# == OptionParser
#
-# === New to \OptionParser?
+# === New to +OptionParser+?
#
-# See the {Tutorial}[./doc/optparse/tutorial_rdoc.html].
+# See the {Tutorial}[optparse/tutorial.rdoc].
#
# === Introduction
#
@@ -152,14 +151,14 @@
# OptionParser supports the ability to coerce command line arguments
# into objects for us.
#
-# OptionParser comes with a few ready-to-use kinds of type
+# OptionParser comes with a few ready-to-use kinds of type
# coercion. They are:
#
-# - Date -- Anything accepted by +Date.parse+
-# - DateTime -- Anything accepted by +DateTime.parse+
-# - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+
-# - URI -- Anything accepted by +URI.parse+
-# - Shellwords -- Anything accepted by +Shellwords.shellwords+
+# - 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)
@@ -420,12 +419,13 @@
# === Further documentation
#
# The above examples, along with the accompanying
-# {Tutorial}[./doc/optparse/tutorial_rdoc.html],
+# {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
- OptionParser::Version = "0.1.1"
+ # The version string
+ OptionParser::Version = "0.5.0"
# :stopdoc:
NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
@@ -438,6 +438,8 @@ class OptionParser
# and resolved against a list of acceptable values.
#
module Completion
+ # :nodoc:
+
def self.regexp(key, icase)
Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase)
end
@@ -459,7 +461,7 @@ class OptionParser
candidates
end
- def candidate(key, icase = false, pat = nil)
+ def candidate(key, icase = false, pat = nil, &_)
Completion.candidate(key, icase, pat, &method(:each))
end
@@ -510,6 +512,8 @@ class OptionParser
# RequiredArgument, etc.
#
class Switch
+ # :nodoc:
+
attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block
#
@@ -674,6 +678,34 @@ class OptionParser
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.
#
@@ -687,12 +719,16 @@ class OptionParser
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
#
@@ -703,13 +739,17 @@ class OptionParser
#
# Raises an exception if argument is not present.
#
- def parse(arg, argv)
+ def parse(arg, argv, &_)
unless arg
raise MissingArgument if argv.empty?
arg = argv.shift
end
conv_arg(*parse_arg(arg, &method(:raise)))
end
+
+ def pretty_head # :nodoc:
+ "Required"
+ end
end
#
@@ -724,32 +764,41 @@ 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 '-'.
+ # 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 '-'.
+ # 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
@@ -759,6 +808,8 @@ class OptionParser
# 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
@@ -781,13 +832,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 = /.*/m, &block)
if pat
pat.respond_to?(:match) or
- raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2))
+ raise TypeError, "has no 'match'", ParseError.filter_backtrace(caller(2))
else
pat = t if t.respond_to?(:match)
end
@@ -983,11 +1045,31 @@ XXX
to << "#compdef #{name}\n"
to << COMPSYS_HEADER
visit(:compsys, {}, {}) {|o, d|
- to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n]
+ 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 || less.empty? ? '-' : 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
+
#
# Default options for ARGV, which never appear in option summary.
#
@@ -999,8 +1081,7 @@ XXX
#
Officious['help'] = proc do |parser|
Switch::NoArgument.new do |arg|
- puts parser.help
- exit
+ parser.help_exit
end
end
@@ -1079,6 +1160,10 @@ XXX
default.to_i + 1
end
end
+
+ #
+ # See self.inc
+ #
def inc(*args)
self.class.inc(*args)
end
@@ -1098,6 +1183,7 @@ XXX
@summary_indent = indent
@default_argv = ARGV
@require_exact = false
+ @raise_unknown = true
add_officious
yield self if block_given?
end
@@ -1116,11 +1202,19 @@ XXX
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
#
@@ -1141,9 +1235,9 @@ XXX
#
# Directs to reject specified class argument.
#
- # +t+:: Argument class specifier, any object including Class.
+ # +type+:: Argument class specifier, any object including Class.
#
- # reject(t)
+ # reject(type)
#
def reject(*args, &blk) top.reject(*args, &blk) end
#
@@ -1175,6 +1269,9 @@ XXX
# abbreviated long option as short option).
attr_accessor :require_exact
+ # Whether to raise at unknown option.
+ attr_accessor :raise_unknown
+
#
# Heading banner preceding summary.
#
@@ -1230,10 +1327,24 @@ XXX
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
@@ -1255,6 +1366,9 @@ XXX
#
# 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?
@@ -1293,6 +1407,29 @@ XXX
def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end
alias to_s help
+ 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.
#
@@ -1429,7 +1566,7 @@ XXX
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
@@ -1455,6 +1592,12 @@ XXX
nolong
end
+ # ----
+ # Option definition phase methods
+ #
+ # These methods are used to define options, or to construct an
+ # OptionParser instance in other words.
+
# :call-seq:
# define(*params, &block)
#
@@ -1530,6 +1673,13 @@ XXX
top.append(string, nil, nil)
end
+ # ----
+ # 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
@@ -1539,21 +1689,21 @@ XXX
#
# Returns the rest of +argv+ left unparsed.
#
- def order(*argv, into: nil, &nonopt)
+ def order(*argv, **keywords, &nonopt)
argv = argv[0].dup if argv.size == 1 and Array === argv[0]
- order!(argv, into: into, &nonopt)
+ 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, &nonopt)
+ def order!(argv = default_argv, into: nil, **keywords, &nonopt)
setter = ->(name, val) {into[name.to_sym] = val} if into
- parse_in_order(argv, setter, &nonopt)
+ parse_in_order(argv, setter, **keywords, &nonopt)
end
- def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
+ 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) {
@@ -1564,17 +1714,24 @@ XXX
opt, rest = $1, $2
opt.tr!('_', '-')
begin
- sw, = complete(:long, opt, true)
- if require_exact && !sw.long.include?(arg)
- raise InvalidOption, arg
+ 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, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
- val = cb.call(val) if cb
- setter.call(sw.switch_name, val) if setter
+ 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
@@ -1592,7 +1749,7 @@ XXX
val = arg.delete_prefix('-')
has_arg = true
rescue InvalidOption
- raise if require_exact
+ raise if exact
# if no short options match, try completion with long
# options.
sw, = complete(:long, opt)
@@ -1600,10 +1757,11 @@ XXX
end
end
rescue ParseError
+ throw :terminate, arg unless raise_unknown
raise $!.set_option(arg, true)
end
begin
- opt, cb, 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
@@ -1611,8 +1769,8 @@ XXX
end
begin
argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
- val = cb.call(val) if cb
- setter.call(sw.switch_name, val) if setter
+ 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
@@ -1638,6 +1796,17 @@ XXX
end
private :parse_in_order
+ # Calls callback with _val_.
+ def callback!(cb, max_arity, *args) # :nodoc:
+ 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
+ private :callback!
+
#
# Parses command line arguments +argv+ in permutation mode and returns
# list of non-option arguments. When optional +into+ keyword
@@ -1645,18 +1814,18 @@ XXX
# <code>[]=</code> method (so it can be Hash, or OpenStruct, or other
# similar object).
#
- def permute(*argv, into: nil)
+ def permute(*argv, **keywords)
argv = argv[0].dup if argv.size == 1 and Array === argv[0]
- permute!(argv, into: into)
+ permute!(argv, **keywords)
end
#
# Same as #permute, but removes switches destructively.
# Non-option arguments remain in +argv+.
#
- def permute!(argv = default_argv, into: nil)
+ def permute!(argv = default_argv, **keywords)
nonopts = []
- order!(argv, into: into, &nonopts.method(:<<))
+ order!(argv, **keywords, &nonopts.method(:<<))
argv[0, 0] = nonopts
argv
end
@@ -1668,20 +1837,20 @@ XXX
# values are stored there via <code>[]=</code> method (so it can be Hash,
# or OpenStruct, or other similar object).
#
- def parse(*argv, into: nil)
+ def parse(*argv, **keywords)
argv = argv[0].dup if argv.size == 1 and Array === argv[0]
- parse!(argv, into: into)
+ parse!(argv, **keywords)
end
#
# Same as #parse, but removes switches destructively.
# Non-option arguments remain in +argv+.
#
- def parse!(argv = default_argv, into: nil)
+ def parse!(argv = default_argv, **keywords)
if ENV.include?('POSIXLY_CORRECT')
- order!(argv, into: into)
+ order!(argv, **keywords)
else
- permute!(argv, into: into)
+ permute!(argv, **keywords)
end
end
@@ -1695,7 +1864,16 @@ XXX
# # params["bar"] = "x" # --bar x
# # params["zot"] = "z" # --zot Z
#
- def getopts(*args)
+ # 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
@@ -1723,15 +1901,15 @@ XXX
end
end
- parse_in_order(argv, result.method(:[]=))
- result
+ parse_in_order(argv, result.method(:[]=), **keywords)
+ symbolize_names ? result.transform_keys(&:to_sym) : result
end
#
# See #getopts.
#
- def self.getopts(*args)
- new.getopts(*args)
+ def self.getopts(*args, symbolize_names: false)
+ new.getopts(*args, symbolize_names: symbolize_names)
end
#
@@ -1792,6 +1970,9 @@ XXX
DidYouMean.formatter.message_for(all_candidates & checker.correct(opt))
end
+ #
+ # Return candidates for +word+.
+ #
def candidate(word)
list = []
case word
@@ -1830,10 +2011,13 @@ XXX
# directory ~/.options, then the basename with '.options' suffix
# under XDG and Haiku standard places.
#
- def load(filename = nil)
+ # 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(basename, '~/.options')) rescue nil
+ return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil
basename << ".options"
return [
# XDG
@@ -1845,11 +2029,11 @@ XXX
'~/config/settings',
].any? {|dir|
next if !dir or dir.empty?
- load(File.expand_path(basename, dir)) rescue nil
+ load(File.expand_path(basename, dir), **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
@@ -1862,10 +2046,10 @@ XXX
#
# +env+ defaults to the basename of the program.
#
- def environment(env = File.basename($0, '.*'))
+ def environment(env = File.basename($0, '.*'), **keywords)
env = ENV[env] || ENV[env.upcase] or return
require 'shellwords'
- parse(*Shellwords.shellwords(env))
+ parse(*Shellwords.shellwords(env), **keywords)
end
#
@@ -2001,10 +2185,23 @@ XXX
f |= Regexp::IGNORECASE if /i/ =~ o
f |= Regexp::MULTILINE if /m/ =~ o
f |= Regexp::EXTENDED if /x/ =~ o
- k = o.delete("imx")
- k = nil if k.empty?
+ 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 || all, f, k)
+ Regexp.new(s, f)
end
#
@@ -2018,6 +2215,7 @@ XXX
# Reason which caused the error.
Reason = 'parse error'
+ # :nodoc:
def initialize(*args, additional: nil)
@additional = additional
@arg0, = args
@@ -2168,19 +2366,19 @@ XXX
# Parses +self+ destructively in order and returns +self+ containing the
# rest arguments left unparsed.
#
- def order!(&blk) options.order!(self, &blk) end
+ 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!() options.permute!(self) end
+ def permute!(**keywords) options.permute!(self, **keywords) end
#
# Parses +self+ destructively and returns +self+ containing the
# rest arguments left unparsed.
#
- def parse!() options.parse!(self) end
+ def parse!(**keywords) options.parse!(self, **keywords) end
#
# Substitution of getopts is possible as follows. Also see
@@ -2193,8 +2391,8 @@ XXX
# rescue OptionParser::ParseError
# end
#
- def getopts(*args)
- options.getopts(self, *args)
+ def getopts(*args, symbolize_names: false, **keywords)
+ options.getopts(self, *args, symbolize_names: symbolize_names, **keywords)
end
#
@@ -2204,7 +2402,8 @@ XXX
super
obj.instance_eval {@optparse = nil}
end
- def initialize(*args)
+
+ def initialize(*args) # :nodoc:
super
@optparse = nil
end