From 5a92e4fddbcb356f482144535ef7c8d9d3035dc7 Mon Sep 17 00:00:00 2001 From: Ben Bowers Date: Thu, 4 Jul 2019 09:12:53 -0400 Subject: [PATCH 1/8] Add exact_match to settings, defaulting to inexact matching --- lib/optimist.rb | 45 +++++++++-- test/optimist/parser_test.rb | 143 ++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 8 deletions(-) diff --git a/lib/optimist.rb b/lib/optimist.rb index 07a11a4..3ad9dcf 100644 --- a/lib/optimist.rb +++ b/lib/optimist.rb @@ -84,8 +84,8 @@ def self.registry_getopttype(type) ## ignore options that it does not recognize. attr_accessor :ignore_invalid_options - DEFAULT_SETTINGS = { suggestions: true } - + DEFAULT_SETTINGS = { suggestions: true, exact_match: false } + ## Initializes the parser, and instance-evaluates any block given. def initialize(*a, &b) @version = nil @@ -246,12 +246,27 @@ def educate_on_error @educate_on_error = true end + ## Match long variables with inexact match. + ## If we hit a complete match, then use that, otherwise see how many long-options partially match. + ## If only one partially matches, then we can safely use that. + ## Otherwise, we raise an error that the partially given option was ambiguous. + def perform_inexact_match(arg, partial_match) # :nodoc: + return @long[partial_match] if @long.has_key?(partial_match) + partially_matched_keys = @long.keys.grep(/^#{partial_match}/) + case partially_matched_keys.size + when 0 ; nil + when 1 ; @long[partially_matched_keys.first] + else ; raise CommandlineError, "ambiguous option '#{arg}' matched keys (#{partially_matched_keys.join(',')})" + end + end + private :perform_inexact_match + def handle_unknown_argument(arg, candidates, suggestions) errstring = "unknown argument '#{arg}'" if (suggestions && - Module::const_defined?("DidYouMean") && - Module::const_defined?("DidYouMean::JaroWinkler") && - Module::const_defined?("DidYouMean::Levenshtein")) + Module::const_defined?("DidYouMean") && + Module::const_defined?("DidYouMean::JaroWinkler") && + Module::const_defined?("DidYouMean::Levenshtein")) input = arg.sub(/^[-]*/,'') # Code borrowed from did_you_mean gem @@ -318,8 +333,12 @@ def parse(cmdline = ARGV) sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments + ## Support inexact matching of long-arguments like perl's Getopt::Long + if !sym && !@settings[:exact_match] && arg.match(/^--(\S*)$/) + sym = perform_inexact_match(arg, $1) + end + next nil if ignore_invalid_options && !sym - handle_unknown_argument(arg, @long.keys, @settings[:suggestions]) unless sym if given_args.include?(sym) && !@specs[sym].multi? @@ -996,6 +1015,20 @@ def multi_arg? ; true ; end ## ## if called with --monkey ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true} ## +## Settings: +## Optimist::options and Optimist::Parser.new accept +settings+ to control how +## options are interpreted. These settings are given as hash arguments, e.g: +## +## opts = Optimist::options(ARGV, exact_match: false) do +## opt :foobar, 'messed up' +## opt :forget, 'forget it' +## end +## +## +settings+ include: +## * :exact_match : (default=false) Allow minimum unambigous number of characters to match a long option +## * :suggestions : (default=true) Enables suggestions when unknown arguments are given and DidYouMean is installed. DidYouMean comes standard with Ruby 2.3+ +## Because Optimist::options uses a default argument for +args+, you must pass that argument when using the settings feature. +## ## See more examples at https://www.manageiq.org/optimist def options(args = ARGV, *a, &b) @last_parser = Parser.new(*a, &b) diff --git a/test/optimist/parser_test.rb b/test/optimist/parser_test.rb index d492a79..a29930a 100644 --- a/test/optimist/parser_test.rb +++ b/test/optimist/parser_test.rb @@ -45,10 +45,57 @@ def test_synopsis def test_unknown_arguments - assert_raises(CommandlineError) { @p.parse(%w(--arg)) } + err = assert_raises(CommandlineError) { @p.parse(%w(--arg)) } + assert_match(/unknown argument '--arg'$/, err.message) @p.opt "arg" @p.parse(%w(--arg)) - assert_raises(CommandlineError) { @p.parse(%w(--arg2)) } + err = assert_raises(CommandlineError) { @p.parse(%w(--arg2)) } + assert_match(/unknown argument '--arg2'$/, err.message) + end + + def test_unknown_arguments_with_suggestions + sugp = Parser.new(:suggestions => true) + err = assert_raises(CommandlineError) { sugp.parse(%w(--bone)) } + assert_match(/unknown argument '--bone'$/, err.message) + + if (Module::const_defined?("DidYouMean") && + Module::const_defined?("DidYouMean::JaroWinkler") && + Module::const_defined?("DidYouMean::Levenshtein")) + sugp.opt "cone" + sugp.parse(%w(--cone)) + + # single letter mismatch + err = assert_raises(CommandlineError) { sugp.parse(%w(--bone)) } + assert_match(/unknown argument '--bone'. Did you mean: \[--cone\] \?$/, err.message) + + # transposition + err = assert_raises(CommandlineError) { sugp.parse(%w(--ocne)) } + assert_match(/unknown argument '--ocne'. Did you mean: \[--cone\] \?$/, err.message) + + # extra letter at end + err = assert_raises(CommandlineError) { sugp.parse(%w(--cones)) } + assert_match(/unknown argument '--cones'. Did you mean: \[--cone\] \?$/, err.message) + + # too big of a mismatch to suggest (extra letters in front) + err = assert_raises(CommandlineError) { sugp.parse(%w(--snowcone)) } + assert_match(/unknown argument '--snowcone'$/, err.message) + + # too big of a mismatch to suggest (nothing close) + err = assert_raises(CommandlineError) { sugp.parse(%w(--clown-nose)) } + assert_match(/unknown argument '--clown-nose'$/, err.message) + + sugp.opt "zippy" + sugp.opt "zapzy" + # single letter mismatch, matches two + err = assert_raises(CommandlineError) { sugp.parse(%w(--zipzy)) } + assert_match(/unknown argument '--zipzy'. Did you mean: \[--zippy, --zapzy\] \?$/, err.message) + + sugp.opt "big_bug" + # suggest common case of dash versus underscore in argnames + err = assert_raises(CommandlineError) { sugp.parse(%w(--big_bug)) } + assert_match(/unknown argument '--big_bug'. Did you mean: \[--big-bug\] \?$/, err.message) + end + end def test_unknown_arguments_with_suggestions @@ -778,6 +825,20 @@ def test_arguments_passed_through_block end assert_equal @goat, boat end + + ## test-only access reader method so that we dont have to + ## expose settings in the public API. + class Optimist::Parser + def get_settings_for_testing ; return @settings ;end + end + + def test_two_arguments_passed_through_block + newp = Parser.new(:abcd => 123, :efgh => "other" ) do |i| + end + assert_equal newp.get_settings_for_testing[:abcd], 123 + assert_equal newp.get_settings_for_testing[:efgh], "other" + end + def test_version_and_help_override_errors @p.opt :asdf, "desc", :type => String @@ -1161,6 +1222,54 @@ def test_default_shorts_assigned_only_after_user_shorts assert opts[:ccd] end + def test_inexact_match + newp = Parser.new() + newp.opt :liberation, "liberate something", :type => :int + newp.opt :evaluate, "evaluate something", :type => :string + opts = newp.parse %w(--lib 5 --ev bar) + assert_equal 5, opts[:liberation] + assert_equal 'bar', opts[:evaluate] + assert_equal nil, opts[:eval] + end + + def test_exact_match + newp = Parser.new(exact_match: true) + newp.opt :liberation, "liberate something", :type => :int + newp.opt :evaluate, "evaluate something", :type => :string + assert_raises(CommandlineError, /unknown argument '--lib'/) do + newp.parse %w(--lib 5) + end + assert_raises_errmatch(CommandlineError, /unknown argument '--ev'/) do + newp.parse %w(--ev bar) + end + end + + def test_inexact_collision + newp = Parser.new() + newp.opt :bookname, "name of a book", :type => :string + newp.opt :bookcost, "cost of the book", :type => :string + opts = newp.parse %w(--bookn hairy_potsworth --bookc 10) + assert_equal 'hairy_potsworth', opts[:bookname] + assert_equal '10', opts[:bookcost] + assert_raises(CommandlineError) do + newp.parse %w(--book 5) # ambiguous + end + ## partial match causes 'specified multiple times' error + assert_raises(CommandlineError, /specified multiple times/) do + newp.parse %w(--bookc 17 --bookcost 22) + end + end + + def test_inexact_collision_with_exact + newp = Parser.new() + newp.opt :book, "name of a book", :type => :string, :default => "ABC" + newp.opt :bookcost, "cost of the book", :type => :int, :default => 5 + opts = newp.parse %w(--book warthog --bookc 3) + assert_equal 'warthog', opts[:book] + assert_equal 3, opts[:bookcost] + + end + def test_accepts_arguments_with_spaces @p.opt :arg1, "arg", :type => String @p.opt :arg2, "arg2", :type => String @@ -1316,6 +1425,36 @@ def test_ignore_invalid_options_stop_on_unknown_partial_mid_short assert opts[:arg1] assert_equal %w{-bu potato}, @p.leftovers end + + # Due to strangeness in how the cloaker works, there were + # cases where Optimist.parse would work, but Optimist.options + # did not, depending on arguments given to the function. + # These serve to validate different args given to Optimist.options + def test_options_takes_hashy_settings + passargs_copy = [] + settings_copy = [] + ::Optimist.options(%w(--wig --pig), :fizz=>:buzz, :bear=>:cat) do |*passargs| + opt :wig + opt :pig + passargs_copy = passargs.dup + settings_copy = @settings + end + assert_equal [], passargs_copy + assert_equal({:fizz=>:buzz, :bear=>:cat}, settings_copy) + end + + def test_options_takes_some_other_data + passargs_copy = [] + settings_copy = [] + ::Optimist.options(%w(--nose --close), 1, 2, 3) do |*passargs| + opt :nose + opt :close + passargs_copy = passargs.dup + settings_copy = @settings + end + assert_equal [1,2,3], passargs_copy + assert_equal({}, settings_copy) + end end end From a3e34f5bf823f901b4d94c55285716e0dd08bb75 Mon Sep 17 00:00:00 2001 From: Ben Bowers Date: Fri, 3 May 2024 09:38:43 -0400 Subject: [PATCH 2/8] implementation of inexact match feature --- lib/optimist.rb | 16 ++++----- test/optimist/parser_test.rb | 62 +++++----------------------------- test/support/assert_helpers.rb | 6 ++++ 3 files changed, 23 insertions(+), 61 deletions(-) diff --git a/lib/optimist.rb b/lib/optimist.rb index 3ad9dcf..21cfb0e 100644 --- a/lib/optimist.rb +++ b/lib/optimist.rb @@ -85,7 +85,7 @@ def self.registry_getopttype(type) attr_accessor :ignore_invalid_options DEFAULT_SETTINGS = { suggestions: true, exact_match: false } - + ## Initializes the parser, and instance-evaluates any block given. def initialize(*a, &b) @version = nil @@ -264,9 +264,9 @@ def perform_inexact_match(arg, partial_match) # :nodoc: def handle_unknown_argument(arg, candidates, suggestions) errstring = "unknown argument '#{arg}'" if (suggestions && - Module::const_defined?("DidYouMean") && - Module::const_defined?("DidYouMean::JaroWinkler") && - Module::const_defined?("DidYouMean::Levenshtein")) + Module::const_defined?("DidYouMean") && + Module::const_defined?("DidYouMean::JaroWinkler") && + Module::const_defined?("DidYouMean::Levenshtein")) input = arg.sub(/^[-]*/,'') # Code borrowed from did_you_mean gem @@ -331,13 +331,13 @@ def parse(cmdline = ARGV) else raise CommandlineError, "invalid argument syntax: '#{arg}'" end - sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments - + if arg =~ /--no-/ # explicitly invalidate --no-no- arguments + sym = nil ## Support inexact matching of long-arguments like perl's Getopt::Long - if !sym && !@settings[:exact_match] && arg.match(/^--(\S*)$/) + elsif !sym && !@settings[:exact_match] && arg.match(/^--(\S*)$/) sym = perform_inexact_match(arg, $1) end - + next nil if ignore_invalid_options && !sym handle_unknown_argument(arg, @long.keys, @settings[:suggestions]) unless sym diff --git a/test/optimist/parser_test.rb b/test/optimist/parser_test.rb index a29930a..50043a6 100644 --- a/test/optimist/parser_test.rb +++ b/test/optimist/parser_test.rb @@ -46,56 +46,11 @@ def test_synopsis def test_unknown_arguments err = assert_raises(CommandlineError) { @p.parse(%w(--arg)) } - assert_match(/unknown argument '--arg'$/, err.message) + assert_match(/unknown argument '--arg'/, err.message) @p.opt "arg" @p.parse(%w(--arg)) err = assert_raises(CommandlineError) { @p.parse(%w(--arg2)) } - assert_match(/unknown argument '--arg2'$/, err.message) - end - - def test_unknown_arguments_with_suggestions - sugp = Parser.new(:suggestions => true) - err = assert_raises(CommandlineError) { sugp.parse(%w(--bone)) } - assert_match(/unknown argument '--bone'$/, err.message) - - if (Module::const_defined?("DidYouMean") && - Module::const_defined?("DidYouMean::JaroWinkler") && - Module::const_defined?("DidYouMean::Levenshtein")) - sugp.opt "cone" - sugp.parse(%w(--cone)) - - # single letter mismatch - err = assert_raises(CommandlineError) { sugp.parse(%w(--bone)) } - assert_match(/unknown argument '--bone'. Did you mean: \[--cone\] \?$/, err.message) - - # transposition - err = assert_raises(CommandlineError) { sugp.parse(%w(--ocne)) } - assert_match(/unknown argument '--ocne'. Did you mean: \[--cone\] \?$/, err.message) - - # extra letter at end - err = assert_raises(CommandlineError) { sugp.parse(%w(--cones)) } - assert_match(/unknown argument '--cones'. Did you mean: \[--cone\] \?$/, err.message) - - # too big of a mismatch to suggest (extra letters in front) - err = assert_raises(CommandlineError) { sugp.parse(%w(--snowcone)) } - assert_match(/unknown argument '--snowcone'$/, err.message) - - # too big of a mismatch to suggest (nothing close) - err = assert_raises(CommandlineError) { sugp.parse(%w(--clown-nose)) } - assert_match(/unknown argument '--clown-nose'$/, err.message) - - sugp.opt "zippy" - sugp.opt "zapzy" - # single letter mismatch, matches two - err = assert_raises(CommandlineError) { sugp.parse(%w(--zipzy)) } - assert_match(/unknown argument '--zipzy'. Did you mean: \[--zippy, --zapzy\] \?$/, err.message) - - sugp.opt "big_bug" - # suggest common case of dash versus underscore in argnames - err = assert_raises(CommandlineError) { sugp.parse(%w(--big_bug)) } - assert_match(/unknown argument '--big_bug'. Did you mean: \[--big-bug\] \?$/, err.message) - end - + assert_match(/unknown argument '--arg2'/, err.message) end def test_unknown_arguments_with_suggestions @@ -825,13 +780,13 @@ def test_arguments_passed_through_block end assert_equal @goat, boat end - + ## test-only access reader method so that we dont have to ## expose settings in the public API. class Optimist::Parser def get_settings_for_testing ; return @settings ;end end - + def test_two_arguments_passed_through_block newp = Parser.new(:abcd => 123, :efgh => "other" ) do |i| end @@ -1229,7 +1184,7 @@ def test_inexact_match opts = newp.parse %w(--lib 5 --ev bar) assert_equal 5, opts[:liberation] assert_equal 'bar', opts[:evaluate] - assert_equal nil, opts[:eval] + assert_nil opts[:eval] end def test_exact_match @@ -1440,9 +1395,10 @@ def test_options_takes_hashy_settings settings_copy = @settings end assert_equal [], passargs_copy - assert_equal({:fizz=>:buzz, :bear=>:cat}, settings_copy) + assert_equal settings_copy[:fizz], :buzz + assert_equal settings_copy[:bear], :cat end - + def test_options_takes_some_other_data passargs_copy = [] settings_copy = [] @@ -1453,7 +1409,7 @@ def test_options_takes_some_other_data settings_copy = @settings end assert_equal [1,2,3], passargs_copy - assert_equal({}, settings_copy) + assert_equal(Optimist::Parser::DEFAULT_SETTINGS, settings_copy) end end diff --git a/test/support/assert_helpers.rb b/test/support/assert_helpers.rb index 558e701..e17c67a 100644 --- a/test/support/assert_helpers.rb +++ b/test/support/assert_helpers.rb @@ -42,5 +42,11 @@ def assert_system_exit *exp end flunk "#{msg}#{mu_pp(exp)} SystemExit expected but nothing was raised." end + + # wrapper around common assertion checking pattern + def assert_raises_errmatch(err_klass, err_regexp, &b) + err = assert_raises(err_klass, &b) + assert_match(err_regexp, err.message) + end end From 7514de5d0e51cecfa3a975326d5ebe322202dfac Mon Sep 17 00:00:00 2001 From: nanobowers Date: Fri, 3 May 2024 12:46:53 -0400 Subject: [PATCH 3/8] fix argument match regex Co-authored-by: Jason Frey --- lib/optimist.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimist.rb b/lib/optimist.rb index 21cfb0e..9d3fe2d 100644 --- a/lib/optimist.rb +++ b/lib/optimist.rb @@ -334,7 +334,7 @@ def parse(cmdline = ARGV) if arg =~ /--no-/ # explicitly invalidate --no-no- arguments sym = nil ## Support inexact matching of long-arguments like perl's Getopt::Long - elsif !sym && !@settings[:exact_match] && arg.match(/^--(\S*)$/) + elsif !sym && !@settings[:exact_match] && arg.match(/^--(\S+)$/) sym = perform_inexact_match(arg, $1) end From 245afb0175c93828d6defa7cb6b89e3e25b02714 Mon Sep 17 00:00:00 2001 From: nanobowers Date: Fri, 3 May 2024 12:47:19 -0400 Subject: [PATCH 4/8] fix exact match default to true Co-authored-by: Jason Frey --- lib/optimist.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimist.rb b/lib/optimist.rb index 9d3fe2d..c6d8942 100644 --- a/lib/optimist.rb +++ b/lib/optimist.rb @@ -84,7 +84,7 @@ def self.registry_getopttype(type) ## ignore options that it does not recognize. attr_accessor :ignore_invalid_options - DEFAULT_SETTINGS = { suggestions: true, exact_match: false } + DEFAULT_SETTINGS = { suggestions: true, exact_match: true } ## Initializes the parser, and instance-evaluates any block given. def initialize(*a, &b) From 657bf8b4c86b399416247c82bcc123e7d54fe637 Mon Sep 17 00:00:00 2001 From: nanobowers Date: Fri, 3 May 2024 12:50:22 -0400 Subject: [PATCH 5/8] fix exact_match default in documentation Co-authored-by: Jason Frey --- lib/optimist.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimist.rb b/lib/optimist.rb index c6d8942..67695a9 100644 --- a/lib/optimist.rb +++ b/lib/optimist.rb @@ -1025,7 +1025,7 @@ def multi_arg? ; true ; end ## end ## ## +settings+ include: -## * :exact_match : (default=false) Allow minimum unambigous number of characters to match a long option +## * :exact_match : (default=true) Allow minimum unambigous number of characters to match a long option ## * :suggestions : (default=true) Enables suggestions when unknown arguments are given and DidYouMean is installed. DidYouMean comes standard with Ruby 2.3+ ## Because Optimist::options uses a default argument for +args+, you must pass that argument when using the settings feature. ## From 6c15fd38579895fa7f40c66c616b174f8e8df3b2 Mon Sep 17 00:00:00 2001 From: Ben Bowers Date: Fri, 3 May 2024 13:07:14 -0400 Subject: [PATCH 6/8] fixed tests for new default value of exact_match (true) --- test/optimist/parser_test.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/optimist/parser_test.rb b/test/optimist/parser_test.rb index 50043a6..c07adda 100644 --- a/test/optimist/parser_test.rb +++ b/test/optimist/parser_test.rb @@ -1178,7 +1178,7 @@ def test_default_shorts_assigned_only_after_user_shorts end def test_inexact_match - newp = Parser.new() + newp = Parser.new(exact_match: false) newp.opt :liberation, "liberate something", :type => :int newp.opt :evaluate, "evaluate something", :type => :string opts = newp.parse %w(--lib 5 --ev bar) @@ -1188,7 +1188,7 @@ def test_inexact_match end def test_exact_match - newp = Parser.new(exact_match: true) + newp = Parser.new() newp.opt :liberation, "liberate something", :type => :int newp.opt :evaluate, "evaluate something", :type => :string assert_raises(CommandlineError, /unknown argument '--lib'/) do @@ -1200,7 +1200,7 @@ def test_exact_match end def test_inexact_collision - newp = Parser.new() + newp = Parser.new(exact_match: false) newp.opt :bookname, "name of a book", :type => :string newp.opt :bookcost, "cost of the book", :type => :string opts = newp.parse %w(--bookn hairy_potsworth --bookc 10) @@ -1216,13 +1216,12 @@ def test_inexact_collision end def test_inexact_collision_with_exact - newp = Parser.new() + newp = Parser.new(exact_match: false) newp.opt :book, "name of a book", :type => :string, :default => "ABC" newp.opt :bookcost, "cost of the book", :type => :int, :default => 5 opts = newp.parse %w(--book warthog --bookc 3) assert_equal 'warthog', opts[:book] assert_equal 3, opts[:bookcost] - end def test_accepts_arguments_with_spaces From d21d7185834c624f805cb34b3df36b236f11b8eb Mon Sep 17 00:00:00 2001 From: Ben Bowers Date: Fri, 3 May 2024 13:14:39 -0400 Subject: [PATCH 7/8] replacing regexp with String#start_with? --- lib/optimist.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/optimist.rb b/lib/optimist.rb index 67695a9..ca310e6 100644 --- a/lib/optimist.rb +++ b/lib/optimist.rb @@ -252,7 +252,7 @@ def educate_on_error ## Otherwise, we raise an error that the partially given option was ambiguous. def perform_inexact_match(arg, partial_match) # :nodoc: return @long[partial_match] if @long.has_key?(partial_match) - partially_matched_keys = @long.keys.grep(/^#{partial_match}/) + partially_matched_keys = @long.keys.select { |opt| opt.start_with?(partial_match) } case partially_matched_keys.size when 0 ; nil when 1 ; @long[partially_matched_keys.first] @@ -331,7 +331,7 @@ def parse(cmdline = ARGV) else raise CommandlineError, "invalid argument syntax: '#{arg}'" end - if arg =~ /--no-/ # explicitly invalidate --no-no- arguments + if arg.start_with?("--no-") # explicitly invalidate --no-no- arguments sym = nil ## Support inexact matching of long-arguments like perl's Getopt::Long elsif !sym && !@settings[:exact_match] && arg.match(/^--(\S+)$/) @@ -532,7 +532,7 @@ def each_arg(args) until i >= args.length return remains += args[i..-1] if @stop_words.member? args[i] case args[i] - when /^--$/ # arg terminator + when "--" # arg terminator return remains += args[(i + 1)..-1] when /^--(\S+?)=(.*)$/ # long argument with equals num_params_taken = yield "--#{$1}", [$2] @@ -617,7 +617,7 @@ def resolve_default_short_options! opts = @specs[name] next if type != :opt || opts.short - c = opts.long.split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) } + c = opts.long.chars.find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) } if c # found a character to use opts.short = c @short[c] = name @@ -856,7 +856,7 @@ def initialize end def flag? ; true ; end def parse(_paramlist, neg_given) - return(self.name.to_s =~ /^no_/ ? neg_given : !neg_given) + return(self.name.to_s.start_with("^no_") ? neg_given : !neg_given) end end From 1bc055f8776117037a29a52199356982c8961804 Mon Sep 17 00:00:00 2001 From: Ben Bowers Date: Fri, 3 May 2024 13:17:53 -0400 Subject: [PATCH 8/8] fixed subtle logic bug --- lib/optimist.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimist.rb b/lib/optimist.rb index ca310e6..4f617a9 100644 --- a/lib/optimist.rb +++ b/lib/optimist.rb @@ -856,7 +856,7 @@ def initialize end def flag? ; true ; end def parse(_paramlist, neg_given) - return(self.name.to_s.start_with("^no_") ? neg_given : !neg_given) + return(self.name.to_s =~ /^no_/ ? neg_given : !neg_given) end end