[ruby/optparse] Add post-check of value

Fix https://github.com/ruby/optparse/pull/80

https://github.com/ruby/optparse/commit/050a87d029
This commit is contained in:
Nobuyoshi Nakada 2025-03-10 13:12:34 +09:00 committed by git
parent b51450f3bd
commit 9e265b583b
2 changed files with 38 additions and 9 deletions

View File

@ -461,6 +461,10 @@ class OptionParser
candidates candidates
end end
def self.completable?(key)
String.try_convert(key) or defined?(key.id2name)
end
def candidate(key, icase = false, pat = nil, &_) def candidate(key, icase = false, pat = nil, &_)
Completion.candidate(key, icase, pat, &method(:each)) Completion.candidate(key, icase, pat, &method(:each))
end end
@ -544,11 +548,11 @@ class OptionParser
def initialize(pattern = nil, conv = nil, def initialize(pattern = nil, conv = nil,
short = nil, long = nil, arg = nil, short = nil, long = nil, arg = nil,
desc = ([] if short or long), block = nil, &_block) desc = ([] if short or long), block = nil, values = nil, &_block)
raise if Array === pattern raise if Array === pattern
block ||= _block block ||= _block
@pattern, @conv, @short, @long, @arg, @desc, @block = @pattern, @conv, @short, @long, @arg, @desc, @block, @values =
pattern, conv, short, long, arg, desc, block pattern, conv, short, long, arg, desc, block, values
end end
# #
@ -581,11 +585,15 @@ class OptionParser
# exception. # exception.
# #
def conv_arg(arg, val = []) # :nodoc: def conv_arg(arg, val = []) # :nodoc:
v, = *val
if conv if conv
val = conv.call(*val) val = conv.call(*val)
else else
val = proc {|v| v}.call(*val) val = proc {|v| v}.call(*val)
end end
if @values
@values.include?(val) or raise InvalidArgument, v
end
return arg, block, val return arg, block, val
end end
private :conv_arg private :conv_arg
@ -780,7 +788,7 @@ class OptionParser
# Returns nil if argument is not present or begins with '-' and is not '-'. # Returns nil if argument is not present or begins with '-' and is not '-'.
# #
def parse(arg, argv, &error) def parse(arg, argv, &error)
if !(val = arg) and !(val = argv[0])&.match?(/\A(?!-.)/) if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0]))
return nil, block return nil, block
end end
opt = (val = parse_arg(val, &error))[1] opt = (val = parse_arg(val, &error))[1]
@ -1464,6 +1472,7 @@ XXX
klass = nil klass = nil
q, a = nil q, a = nil
has_arg = false has_arg = false
values = nil
opts.each do |o| opts.each do |o|
# argument class # argument class
@ -1477,7 +1486,7 @@ XXX
end end
# directly specified pattern(any object possible to match) # directly specified pattern(any object possible to match)
if (!(String === o || Symbol === o)) and o.respond_to?(:match) if !Completion.completable?(o) and o.respond_to?(:match)
pattern = notwice(o, pattern, 'pattern') pattern = notwice(o, pattern, 'pattern')
if pattern.respond_to?(:convert) if pattern.respond_to?(:convert)
conv = pattern.method(:convert).to_proc conv = pattern.method(:convert).to_proc
@ -1492,6 +1501,11 @@ XXX
when Proc, Method when Proc, Method
block = notwice(o, block, 'block') block = notwice(o, block, 'block')
when Array, Hash when Array, Hash
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 case pattern
when CompletingHash when CompletingHash
when nil when nil
@ -1500,7 +1514,9 @@ XXX
else else
raise ArgumentError, "argument pattern given twice" raise ArgumentError, "argument pattern given twice"
end end
o.each {|pat, *v| pattern[pat.to_s] = v.fetch(0) {pat}} o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}}
when Range
values = notwice(o, values, 'values')
when Module when Module
raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4))
when *ArgumentStyle.keys when *ArgumentStyle.keys
@ -1568,12 +1584,18 @@ XXX
end end
default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
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 !(short.empty? and long.empty?)
if has_arg and default_style == Switch::NoArgument if has_arg and default_style == Switch::NoArgument
default_style = Switch::RequiredArgument default_style = Switch::RequiredArgument
end end
s = (style || default_style).new(pattern || default_pattern, s = (style || default_style).new(pattern || default_pattern,
conv, sdesc, ldesc, arg, desc, block) conv, sdesc, ldesc, arg, desc, block, values)
elsif !block elsif !block
if style or pattern if style or pattern
raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
@ -1582,7 +1604,7 @@ XXX
else else
short << pattern short << pattern
s = (style || default_style).new(pattern, s = (style || default_style).new(pattern,
conv, nil, nil, arg, desc, block) conv, nil, nil, arg, desc, block, values)
end end
return s, short, long, return s, short, long,
(not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),

View File

@ -8,7 +8,8 @@ class TestOptionParserPlaceArg < TestOptionParser
@opt.def_option("--option [VAL]") {|x| @flag = x} @opt.def_option("--option [VAL]") {|x| @flag = x}
@opt.def_option("-T [level]", /^[0-4]$/, Integer) {|x| @topt = x} @opt.def_option("-T [level]", /^[0-4]$/, Integer) {|x| @topt = x}
@opt.def_option("--enum [VAL]", [:Alpha, :Bravo, :Charlie]) {|x| @enum = x} @opt.def_option("--enum [VAL]", [:Alpha, :Bravo, :Charlie]) {|x| @enum = x}
@opt.def_option("--integer [VAL]", [1, 2, 3]) {|x| @integer = x} @opt.def_option("--integer [VAL]", Integer, [1, 2, 3]) {|x| @integer = x}
@opt.def_option("--range [VAL]", Integer, 1..3) {|x| @range = x}
@topt = nil @topt = nil
@opt.def_option("-n") {} @opt.def_option("-n") {}
@opt.def_option("--regexp [REGEXP]", Regexp) {|x| @reopt = x} @opt.def_option("--regexp [REGEXP]", Regexp) {|x| @reopt = x}
@ -105,4 +106,10 @@ class TestOptionParserPlaceArg < TestOptionParser
assert_equal([], no_error {@opt.parse!(%w"--integer=1")}) assert_equal([], no_error {@opt.parse!(%w"--integer=1")})
assert_equal(1, @integer) assert_equal(1, @integer)
end end
def test_enum_range
assert_equal([], no_error {@opt.parse!(%w"--range=1")})
assert_equal(1, @range)
assert_raise(OptionParser::InvalidArgument) {@opt.parse!(%w"--range=4")}
end
end end