From 1c895efc39ed2290c185dcb80523fee381dfe74e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 18 Dec 2023 12:15:31 +0900 Subject: [PATCH 01/64] [DOC] Fix tutorial link Fixes #51 --- doc/optparse/option_params.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/optparse/option_params.rdoc b/doc/optparse/option_params.rdoc index 55f9b53..35db8b5 100644 --- a/doc/optparse/option_params.rdoc +++ b/doc/optparse/option_params.rdoc @@ -31,7 +31,7 @@ Contents: - {Long Names with Optional Arguments}[#label-Long+Names+with+Optional+Arguments] - {Long Names with Negation}[#label-Long+Names+with+Negation] - {Mixed Names}[#label-Mixed+Names] -- {Argument Styles}[#label-Argument+Styles] +- {Argument Strings}[#label-Argument+Strings] - {Argument Values}[#label-Argument+Values] - {Explicit Argument Values}[#label-Explicit+Argument+Values] - {Explicit Values in Array}[#label-Explicit+Values+in+Array] From 667ab35f593f81b7850009385453d20ca8023bba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 18 Dec 2023 12:24:46 +0900 Subject: [PATCH 02/64] [DOC] Add missing secition Fixes #51 --- doc/optparse/ruby/argument_abbreviation.rb | 9 +++++++++ doc/optparse/tutorial.rdoc | 23 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 doc/optparse/ruby/argument_abbreviation.rb diff --git a/doc/optparse/ruby/argument_abbreviation.rb b/doc/optparse/ruby/argument_abbreviation.rb new file mode 100644 index 0000000..49007eb --- /dev/null +++ b/doc/optparse/ruby/argument_abbreviation.rb @@ -0,0 +1,9 @@ +require 'optparse' +parser = OptionParser.new +parser.on('-x', '--xxx=VALUE', %w[ABC def], 'Argument abbreviations') do |value| + p ['--xxx', value] +end +parser.on('-y', '--yyy=VALUE', {"abc"=>"XYZ", def: "FOO"}, 'Argument abbreviations') do |value| + p ['--yyy', value] +end +parser.parse! diff --git a/doc/optparse/tutorial.rdoc b/doc/optparse/tutorial.rdoc index b104379..6f56bbf 100644 --- a/doc/optparse/tutorial.rdoc +++ b/doc/optparse/tutorial.rdoc @@ -351,6 +351,29 @@ Executions: Omitting an optional argument does not raise an error. +==== Argument Abbreviations + +Specify an argument list as an Array or a Hash. + + :include: ruby/argument_abbreviation.rb + +When an argument is abbreviated, the expanded argument yielded. + +Executions: + + $ ruby argument_abbreviation.rb --help + Usage: argument_abbreviation [options] + Usage: argument_abbreviation [options] + -x, --xxx=VALUE Argument abbreviations + -y, --yyy=VALUE Argument abbreviations + $ ruby argument_abbreviation.rb --xxx A + ["--xxx", "ABC"] + $ ruby argument_abbreviation.rb --xxx c + argument_abbreviation.rb:9:in `
': invalid argument: --xxx c (OptionParser::InvalidArgument) + $ ruby argument_abbreviation.rb --yyy a --yyy d + ["--yyy", "XYZ"] + ["--yyy", "FOO"] + === Argument Values Permissible argument values may be restricted From 324ff21f042769444cde41949fc0321f6c6ccf63 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 18 Dec 2023 12:45:44 +0900 Subject: [PATCH 03/64] [DOC] Add missing documents --- lib/optparse.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/optparse.rb b/lib/optparse.rb index 832b928..ace5795 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1129,6 +1129,10 @@ def self.inc(arg, default = nil) default.to_i + 1 end end + + # + # See self.inc + # def inc(*args) self.class.inc(*args) end @@ -1167,6 +1171,9 @@ def add_officious # :nodoc: def terminate(arg = nil) self.class.terminate(arg) end + # + # See #terminate. + # def self.terminate(arg = nil) throw :terminate, arg end @@ -1881,6 +1888,9 @@ def additional_message(typ, opt) DidYouMean.formatter.message_for(all_candidates & checker.correct(opt)) end + # + # Return candidates for +word+. + # def candidate(word) list = [] case word @@ -2123,6 +2133,7 @@ class ParseError < RuntimeError # Reason which caused the error. Reason = 'parse error' + # :nodoc: def initialize(*args, additional: nil) @additional = additional @arg0, = args From 294c6e1ed4b170a8a34ab95c84453829c170223c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 25 Dec 2023 15:08:05 +0900 Subject: [PATCH 04/64] Build packages only on the latest release --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c2decb..2986de0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,7 +51,11 @@ jobs: env: RUNNING_OS: ${{matrix.os}} shell: bash - if: ${{ matrix.os != 'windows-latest' }} + if: >- + ${{ + matrix.os != 'windows-latest' && + matrix.ruby == needs.ruby-versions.outputs.latest + }} - name: Upload package uses: actions/upload-artifact@v3 with: From 48385d3eaf37cd10140274453eeadc4496581fc2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Feb 2024 12:01:44 +0900 Subject: [PATCH 05/64] Create codeql.yml --- .github/workflows/codeql.yml | 84 ++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..ee28f6b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,84 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '35 1 * * 0' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: [ 'ruby' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From 434e92fc62ff9571124335a2eedff56df83dae48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 03:43:27 +0000 Subject: [PATCH 06/64] Bump actions/upload-artifact from 3 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2986de0..6b3d44a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: matrix.ruby == needs.ruby-versions.outputs.latest }} - name: Upload package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: pkg/*.gem name: ${{steps.build.outputs.pkg}} From b14c2c644d18cce226e49ccde8a23b5b991ea8cf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Feb 2024 12:17:31 +0900 Subject: [PATCH 07/64] Escape backslashes --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ace5795..ecf6adc 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1033,7 +1033,7 @@ 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 << %Q[ "#{o}[#{d.gsub(/[\\\"\[\]]/, '\\\\\&')}]" \\\n] } to << " '*:file:_files' && return 0\n" end From 4e346ad337f400dbff855798e859f17455698c2a Mon Sep 17 00:00:00 2001 From: fatkodima Date: Sat, 14 Oct 2023 21:52:01 +0300 Subject: [PATCH 08/64] Fix `require_exact` to work with options defined as `--[no]-something` --- lib/optparse.rb | 6 +++--- test/optparse/test_optparse.rb | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ecf6adc..ccfea6d 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1048,7 +1048,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: # Shows option summary. # Officious['help'] = proc do |parser| - Switch::NoArgument.new do |arg| + Switch::NoArgument.new(nil, nil, ["-h"], ["--help"]) do |arg| puts parser.help exit end @@ -1473,7 +1473,7 @@ def make_switch(opts, block = nil) default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end - ldesc << "--[no-]#{q}" + ldesc << "--#{q}" << "--no-#{q}" (o = q.downcase).tr!('_', '-') long << o not_pattern, not_conv = search(:atype, FalseClass) unless not_style @@ -1649,7 +1649,7 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: opt.tr!('_', '-') begin sw, = complete(:long, opt, true) - if require_exact && !sw.long.include?(arg) + if require_exact && !sw.long.include?("--#{opt}") throw :terminate, arg unless raise_unknown raise InvalidOption, arg end diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index bfa705a..be9bcb8 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -88,9 +88,9 @@ def test_require_exact end @opt.require_exact = true - %w(--zrs -F -Ffoo).each do |arg| + [%w(--zrs foo), %w(--zrs=foo), %w(-F foo), %w(-Ffoo)].each do |args| result = {} - @opt.parse([arg, 'foo'], into: result) + @opt.parse(args, into: result) assert_equal({zrs: 'foo'}, result) end @@ -99,6 +99,14 @@ def test_require_exact assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zrs foo))} assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zr foo))} assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-z foo))} + + @opt.def_option('-f', '--[no-]foo', 'foo') {|arg| @foo = arg} + @opt.parse(%w[-f]) + assert_equal(true, @foo) + @opt.parse(%w[--foo]) + assert_equal(true, @foo) + @opt.parse(%w[--no-foo]) + assert_equal(false, @foo) end def test_raise_unknown From 9d53e74aa4421e0c4e2a805a0074ece5aae3773f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Feb 2024 12:59:43 +0900 Subject: [PATCH 09/64] Respect default values in block parameters Fix #55 --- lib/optparse.rb | 24 +++++++++++++++--------- test/optparse/test_acceptable.rb | 5 +++++ test/optparse/test_optarg.rb | 8 ++++++++ test/optparse/test_optparse.rb | 7 +++++++ test/optparse/test_placearg.rb | 10 ++++++++++ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ccfea6d..d363b91 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -697,6 +697,11 @@ 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. # @@ -755,7 +760,7 @@ def parse(arg, argv, &error) if arg conv_arg(*parse_arg(arg, &error)) else - conv_arg(arg) + omitted_argument conv_arg(arg) end end @@ -774,13 +779,14 @@ class PlacedArgument < self # def parse(arg, argv, &error) if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0])) - return nil, block, nil + 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 @@ -1633,7 +1639,7 @@ def order(*argv, into: nil, &nonopt) # Non-option arguments remain in +argv+. # def order!(argv = default_argv, into: nil, &nonopt) - setter = ->(name, val) {into[name.to_sym] = val} if into + setter = ->(name, val = nil) {into[name.to_sym] = val} if into parse_in_order(argv, setter, &nonopt) end @@ -1658,9 +1664,9 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: raise $!.set_option(arg, true) 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 = cb.call(*val) if cb + setter.call(sw.switch_name, *val) if setter rescue ParseError raise $!.set_option(arg, rest) end @@ -1690,7 +1696,7 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 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 @@ -1698,8 +1704,8 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 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 = cb.call(*val) if cb + setter.call(sw.switch_name, *val) if setter rescue ParseError raise $!.set_option(arg, arg.length > 2) end diff --git a/test/optparse/test_acceptable.rb b/test/optparse/test_acceptable.rb index 12f5322..c7ea215 100644 --- a/test/optparse/test_acceptable.rb +++ b/test/optparse/test_acceptable.rb @@ -8,6 +8,7 @@ def setup @opt.def_option("--integer VAL", Integer) { |v| @integer = v } @opt.def_option("--float VAL", Float) { |v| @float = v } @opt.def_option("--numeric VAL", Numeric) { |v| @numeric = v } + @opt.def_option("--array VAL", Array) { |v| @array = v } @opt.def_option("--decimal-integer VAL", OptionParser::DecimalInteger) { |i| @decimal_integer = i } @@ -195,4 +196,8 @@ def test_decimal_numeric end end + def test_array + assert_equal(%w"", no_error {@opt.parse!(%w"--array a,b,c")}) + assert_equal(%w"a b c", @array) + end end diff --git a/test/optparse/test_optarg.rb b/test/optparse/test_optarg.rb index 81127a8..f4882b0 100644 --- a/test/optparse/test_optarg.rb +++ b/test/optparse/test_optarg.rb @@ -9,6 +9,7 @@ def setup @opt.def_option("--regexp[=REGEXP]", Regexp) {|x| @reopt = x} @opt.def_option "--with_underscore[=VAL]" do |x| @flag = x end @opt.def_option "--with-hyphen[=VAL]" do |x| @flag = x end + @opt.def_option("--fallback[=VAL]") do |x = "fallback"| @flag = x end @reopt = nil end @@ -57,4 +58,11 @@ def test_hyphenize assert_equal(%w"", no_error {@opt.parse!(%w"--with_hyphen=foo4")}) assert_equal("foo4", @flag) end + + def test_default_argument + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback=val1")}) + assert_equal("val1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) + assert_equal("fallback", @flag) + end end diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index be9bcb8..3b9ccc7 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -73,10 +73,17 @@ def test_into @opt.def_option "-p", "--port=PORT", "port", Integer @opt.def_option "-v", "--verbose" do @verbose = true end @opt.def_option "-q", "--quiet" do @quiet = true end + @opt.def_option "-o", "--option [OPT]" do |opt| @option = opt end result = {} @opt.parse %w(--host localhost --port 8000 -v), into: result assert_equal({host: "localhost", port: 8000, verbose: true}, result) assert_equal(true, @verbose) + result = {} + @opt.parse %w(--option -q), into: result + assert_equal({quiet: true, option: nil}, result) + result = {} + @opt.parse %w(--option OPTION -v), into: result + assert_equal({verbose: true, option: "OPTION"}, result) end def test_require_exact diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index ed0e4d3..56b641b 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -13,6 +13,7 @@ def setup @reopt = nil @opt.def_option "--with_underscore=VAL" do |x| @flag = x end @opt.def_option "--with-hyphen=VAL" do |x| @flag = x end + @opt.def_option("--fallback [VAL]") do |x = "fallback"| @flag = x end end def test_short @@ -73,4 +74,13 @@ def test_conv assert_equal(%w"te.rb", no_error('[ruby-dev:38333]') {@opt.parse!(%w"-T1 te.rb")}) assert_equal(1, @topt) end + + def test_default_argument + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback=val1")}) + assert_equal("val1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback val2")}) + assert_equal("val2", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) + assert_equal("fallback", @flag) + end end From 213cb03b5978ddacfbcc8f71ec837d9a6fd4c56d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 9 Feb 2024 19:03:20 +0900 Subject: [PATCH 10/64] Adjust arguments for lambda-callbacks Rake uses [lambda] as callbacks. Calling it without omitted argument raises an `ArgumentError`. lambda: https://github.com/ruby/rake/blob/master/lib/rake/application.rb#L543 --- lib/optparse.rb | 21 ++++++++++++++++----- test/optparse/test_optarg.rb | 8 ++++++++ test/optparse/test_placearg.rb | 10 ++++++++++ test/optparse/test_reqarg.rb | 6 ++++++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index d363b91..fbcd7f9 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1639,7 +1639,7 @@ def order(*argv, into: nil, &nonopt) # Non-option arguments remain in +argv+. # def order!(argv = default_argv, into: nil, &nonopt) - setter = ->(name, val = nil) {into[name.to_sym] = val} if into + setter = ->(name, val) {into[name.to_sym] = val} if into parse_in_order(argv, setter, &nonopt) end @@ -1665,8 +1665,8 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 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 + val = callback!(cb, 1, *val) if cb + callback!(setter, 2, sw.switch_name, *val) if setter rescue ParseError raise $!.set_option(arg, rest) end @@ -1704,8 +1704,8 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 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 @@ -1731,6 +1731,17 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 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 diff --git a/test/optparse/test_optarg.rb b/test/optparse/test_optarg.rb index f4882b0..f944605 100644 --- a/test/optparse/test_optarg.rb +++ b/test/optparse/test_optarg.rb @@ -10,6 +10,7 @@ def setup @opt.def_option "--with_underscore[=VAL]" do |x| @flag = x end @opt.def_option "--with-hyphen[=VAL]" do |x| @flag = x end @opt.def_option("--fallback[=VAL]") do |x = "fallback"| @flag = x end + @opt.def_option("--lambda[=VAL]", &->(x) {@flag = x}) @reopt = nil end @@ -65,4 +66,11 @@ def test_default_argument assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) assert_equal("fallback", @flag) end + + def test_lambda + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda=lambda1")}) + assert_equal("lambda1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda")}) + assert_equal(nil, @flag) + end end diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index 56b641b..a8a11e6 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -14,6 +14,7 @@ def setup @opt.def_option "--with_underscore=VAL" do |x| @flag = x end @opt.def_option "--with-hyphen=VAL" do |x| @flag = x end @opt.def_option("--fallback [VAL]") do |x = "fallback"| @flag = x end + @opt.def_option("--lambda [VAL]", &->(x) {@flag = x}) end def test_short @@ -83,4 +84,13 @@ def test_default_argument assert_equal(%w"", no_error {@opt.parse!(%w"--fallback")}) assert_equal("fallback", @flag) end + + def test_lambda + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda=lambda1")}) + assert_equal("lambda1", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda lambda2")}) + assert_equal("lambda2", @flag) + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda")}) + assert_equal(nil, @flag) + end end diff --git a/test/optparse/test_reqarg.rb b/test/optparse/test_reqarg.rb index d5686d1..31d4fef 100644 --- a/test/optparse/test_reqarg.rb +++ b/test/optparse/test_reqarg.rb @@ -6,6 +6,7 @@ def setup super @opt.def_option "--with_underscore=VAL" do |x| @flag = x end @opt.def_option "--with-hyphen=VAL" do |x| @flag = x end + @opt.def_option("--lambda=VAL", &->(x) {@flag = x}) end class Def1 < TestOptionParser @@ -81,6 +82,11 @@ def test_hyphenize assert_equal("foo4", @flag) end + def test_lambda + assert_equal(%w"", no_error {@opt.parse!(%w"--lambda=lambda1")}) + assert_equal("lambda1", @flag) + end + class TestOptionParser::WithPattern < TestOptionParser def test_pattern pat = num = nil From 78afdab30751e1b7cf80bb57d6696b10879f90fe Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 10 Feb 2024 22:56:32 +0900 Subject: [PATCH 11/64] Search exactly when `require_exact` To work with options defined as `--[no]-something`. Fix https://bugs.ruby-lang.org/issues/20252 Fix #60 --- lib/optparse.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index fbcd7f9..8382364 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1054,7 +1054,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: # Shows option summary. # Officious['help'] = proc do |parser| - Switch::NoArgument.new(nil, nil, ["-h"], ["--help"]) do |arg| + Switch::NoArgument.new do |arg| puts parser.help exit end @@ -1479,7 +1479,7 @@ def make_switch(opts, block = nil) default_style = default_style.guess(arg = a) default_pattern, conv = search(:atype, o) unless default_pattern end - ldesc << "--#{q}" << "--no-#{q}" + ldesc << "--[no-]#{q}" (o = q.downcase).tr!('_', '-') long << o not_pattern, not_conv = search(:atype, FalseClass) unless not_style @@ -1654,14 +1654,19 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: opt, rest = $1, $2 opt.tr!('_', '-') begin - sw, = complete(:long, opt, true) - if require_exact && !sw.long.include?("--#{opt}") - throw :terminate, arg unless raise_unknown - raise InvalidOption, arg + if require_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)} From 33956ce93f56320978cedfcebb8b6cf18bf96c53 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Feb 2024 01:07:05 +0900 Subject: [PATCH 12/64] [DOC] Add missing documents --- lib/optparse.rb | 34 ++++++++++++++++++++++++++++++---- lib/optparse/ac.rb | 16 ++++++++++++++++ lib/optparse/version.rb | 9 +++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 8382364..438a09f 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -8,7 +8,6 @@ # See OptionParser for documentation. # - #-- # == Developer Documentation (not for RDoc output) # @@ -425,6 +424,7 @@ # If you have any questions, file a ticket at http://bugs.ruby-lang.org. # class OptionParser + # The version string OptionParser::Version = "0.4.0" # :stopdoc: @@ -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 @@ -510,6 +512,8 @@ class OptionMap < Hash # RequiredArgument, etc. # class Switch + # :nodoc: + attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block # @@ -715,10 +719,10 @@ def parse(arg, argv) 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 @@ -804,6 +808,8 @@ def pretty_head # :nodoc: # 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 @@ -1185,6 +1191,11 @@ def self.terminate(arg = nil) end @stack = [DefaultList] + # + # Returns the global top option list. + # + # Do not use directly. + # def self.top() DefaultList end # @@ -1297,10 +1308,24 @@ def ver 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 @@ -2342,7 +2367,8 @@ def self.extend_object(obj) super obj.instance_eval {@optparse = nil} end - def initialize(*args) + + def initialize(*args) # :nodoc: super @optparse = nil end diff --git a/lib/optparse/ac.rb b/lib/optparse/ac.rb index 0953725..23fc740 100644 --- a/lib/optparse/ac.rb +++ b/lib/optparse/ac.rb @@ -1,7 +1,11 @@ # frozen_string_literal: false require_relative '../optparse' +# +# autoconf-like options. +# class OptionParser::AC < OptionParser + # :stopdoc: private def _check_ac_args(name, block) @@ -14,6 +18,7 @@ def _check_ac_args(name, block) end ARG_CONV = proc {|val| val.nil? ? true : val} + private_constant :ARG_CONV def _ac_arg_enable(prefix, name, help_string, block) _check_ac_args(name, block) @@ -29,16 +34,27 @@ def _ac_arg_enable(prefix, name, help_string, block) enable end + # :startdoc: + public + # Define --enable / --disable style option + # + # Appears as --enable-name in help message. def ac_arg_enable(name, help_string, &block) _ac_arg_enable("enable", name, help_string, block) end + # Define --enable / --disable style option + # + # Appears as --disable-name in help message. def ac_arg_disable(name, help_string, &block) _ac_arg_enable("disable", name, help_string, block) end + # Define --with / --without style option + # + # Appears as --with-name in help message. def ac_arg_with(name, help_string, &block) _check_ac_args(name, block) diff --git a/lib/optparse/version.rb b/lib/optparse/version.rb index b869d8f..b5ed695 100644 --- a/lib/optparse/version.rb +++ b/lib/optparse/version.rb @@ -2,6 +2,11 @@ # OptionParser internal utility class << OptionParser + # + # Shows version string in packages if Version is defined. + # + # +pkgs+:: package list + # def show_version(*pkgs) progname = ARGV.options.program_name result = false @@ -47,6 +52,8 @@ def show_version(*pkgs) result end + # :stopdoc: + def each_const(path, base = ::Object) path.split(/::|\//).inject(base) do |klass, name| raise NameError, path unless Module === klass @@ -68,4 +75,6 @@ def search_const(klass, name) end end end + + # :startdoc: end From 451dea51a0e5d062fb4cb75b1073a2c2b8609bef Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 12 Feb 2024 01:30:58 +0900 Subject: [PATCH 13/64] [DOC] Add description of OptionParser#define_by_keywords --- lib/optparse/kwargs.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/optparse/kwargs.rb b/lib/optparse/kwargs.rb index 992aca4..59a2f61 100644 --- a/lib/optparse/kwargs.rb +++ b/lib/optparse/kwargs.rb @@ -7,12 +7,17 @@ class OptionParser # # :include: ../../doc/optparse/creates_option.rdoc # - def define_by_keywords(options, meth, **opts) - meth.parameters.each do |type, name| + # Defines options which set in to _options_ for keyword parameters + # of _method_. + # + # Parameters for each keywords are given as elements of _params_. + # + def define_by_keywords(options, method, **params) + method.parameters.each do |type, name| case type when :key, :keyreq op, cl = *(type == :key ? %w"[ ]" : ["", ""]) - define("--#{name}=#{op}#{name.upcase}#{cl}", *opts[name]) do |o| + define("--#{name}=#{op}#{name.upcase}#{cl}", *params[name]) do |o| options[name] = o end end From bbec64d0c7f198429b62468b64b27692efa18dc2 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 19 Jan 2024 16:03:38 +0900 Subject: [PATCH 14/64] Do not include a backtick in error messages and backtraces [Feature #16495] --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 438a09f..2ee5929 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -849,7 +849,7 @@ def pretty_print(q) # :nodoc: 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 From 07e83673a8e99b5b7985608a69f713abde3344f6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 21 Feb 2024 13:42:11 +0900 Subject: [PATCH 15/64] Add `exact:` keyword argument --- lib/optparse.rb | 58 +++++++++++++++++----------------- test/optparse/test_optparse.rb | 29 +++++++++++++++++ 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 2ee5929..08abca2 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1654,21 +1654,21 @@ def separator(string) # # 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) { @@ -1679,7 +1679,7 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: opt, rest = $1, $2 opt.tr!('_', '-') begin - if require_exact + if exact sw, = search(:long, opt) else sw, = complete(:long, opt, true) @@ -1714,7 +1714,7 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 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) @@ -1779,18 +1779,18 @@ def callback!(cb, max_arity, *args) # :nodoc: # []= 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 @@ -1802,20 +1802,20 @@ def permute!(argv = default_argv, into: nil) # values are stored there via []= 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 @@ -1838,7 +1838,7 @@ def parse!(argv = default_argv, into: nil) # # params[:bar] = "x" # --bar x # # params[:zot] = "z" # --zot Z # - def getopts(*args, symbolize_names: false) + def getopts(*args, symbolize_names: false, **keywords) argv = Array === args.first ? args.shift : default_argv single_options, *long_options = *args @@ -1866,7 +1866,7 @@ def getopts(*args, symbolize_names: false) end end - parse_in_order(argv, result.method(:[]=)) + parse_in_order(argv, result.method(:[]=), **keywords) symbolize_names ? result.transform_keys(&:to_sym) : result end @@ -1979,10 +1979,10 @@ def candidate(word) # The optional +into+ keyword argument works exactly like that accepted in # method #parse. # - def load(filename = nil, into: nil) + def load(filename = nil, **keywords) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options'), into: into) rescue nil + return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil basename << ".options" return [ # XDG @@ -1994,11 +1994,11 @@ def load(filename = nil, into: nil) '~/config/settings', ].any? {|dir| next if !dir or dir.empty? - load(File.expand_path(basename, dir), into: into) rescue nil + load(File.expand_path(basename, dir), **keywords) rescue nil } end begin - parse(*File.readlines(filename, chomp: true), into: into) + parse(*File.readlines(filename, chomp: true), **keywords) true rescue Errno::ENOENT, Errno::ENOTDIR false @@ -2011,10 +2011,10 @@ def load(filename = nil, into: nil) # # +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 # @@ -2331,19 +2331,19 @@ def options # 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 @@ -2356,8 +2356,8 @@ def parse!() options.parse!(self) end # rescue OptionParser::ParseError # end # - def getopts(*args, symbolize_names: false) - options.getopts(self, *args, symbolize_names: symbolize_names) + def getopts(*args, symbolize_names: false, **keywords) + options.getopts(self, *args, symbolize_names: symbolize_names, **keywords) end # diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index 3b9ccc7..393b033 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -116,6 +116,35 @@ def test_require_exact assert_equal(false, @foo) end + def test_exact_option + @opt.def_option('-F', '--zrs=IRS', 'zrs') + %w(--zrs --zr --z -zfoo -z -F -Ffoo).each do |arg| + result = {} + @opt.parse([arg, 'foo'], into: result) + assert_equal({zrs: 'foo'}, result) + end + + [%w(--zrs foo), %w(--zrs=foo), %w(-F foo), %w(-Ffoo)].each do |args| + result = {} + @opt.parse(args, into: result, exact: true) + assert_equal({zrs: 'foo'}, result) + end + + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(--zr foo), exact: true)} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(--z foo), exact: true)} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zrs foo), exact: true)} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-zr foo), exact: true)} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-z foo), exact: true)} + + @opt.def_option('-f', '--[no-]foo', 'foo') {|arg| @foo = arg} + @opt.parse(%w[-f], exact: true) + assert_equal(true, @foo) + @opt.parse(%w[--foo], exact: true) + assert_equal(true, @foo) + @opt.parse(%w[--no-foo], exact: true) + assert_equal(false, @foo) + end + def test_raise_unknown @opt.def_option('--my-foo [ARG]') {|arg| @foo = arg} assert @opt.raise_unknown From 59b9fd7ddca3e795c235aa397655b51de2e4c949 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 23 Feb 2024 21:49:57 +0900 Subject: [PATCH 16/64] [DOC] About return value of OptionParser#new --- lib/optparse.rb | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 08abca2..51dbfd0 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1216,9 +1216,9 @@ def self.accept(*args, &blk) top.accept(*args, &blk) end # # 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 # @@ -1347,6 +1347,9 @@ def base # # 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? @@ -1570,6 +1573,12 @@ def make_switch(opts, block = nil) 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) # @@ -1645,6 +1654,13 @@ def separator(string) 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 From 77dccce37cb9fd1b8fbc52160d4103d1c4579669 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 1 Mar 2024 01:24:05 +0900 Subject: [PATCH 17/64] Invoke pager for `--help` --- lib/optparse.rb | 23 +++++++++++++++++-- test/optparse/test_optparse.rb | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 51dbfd0..76ff38a 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1050,6 +1050,26 @@ def compsys(to, name = File.basename($0)) # :nodoc: 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. # @@ -1061,8 +1081,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: # Officious['help'] = proc do |parser| Switch::NoArgument.new do |arg| - puts parser.help - exit + parser.help_exit end end diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index 393b033..8d09e0f 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -164,4 +164,44 @@ def test_nonopt_pattern e = assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-t))} assert_equal(["-t"], e.args) end + + def test_help_pager + require 'tmpdir' + Dir.mktmpdir do |dir| + File.open(File.join(dir, "options.rb"), "w") do |f| + f.puts "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + stdout = STDOUT.dup + def stdout.tty?; true; end + Object.__send__(:remove_const, :STDOUT) + STDOUT = stdout + ARGV.options do |opt| + end; + 100.times {|i| f.puts " opt.on('--opt-#{i}') {}"} + f.puts "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + opt.parse! + end + end; + end + + optparse = $".find {|path| path.end_with?("/optparse.rb")} + args = ["-r#{optparse}", "options.rb", "--help"] + cmd = File.join(dir, "pager.cmd") + if RbConfig::CONFIG["EXECUTABLE_EXTS"]&.include?(".cmd") + command = "@echo off" + else # if File.executable?("/bin/sh") + # TruffleRuby just calls `posix_spawnp` and no fallback to `/bin/sh`. + command = "#!/bin/sh\n" + end + + [ + [{"RUBY_PAGER"=>cmd, "PAGER"=>"echo ng"}, "Executing RUBY_PAGER"], + [{"RUBY_PAGER"=>nil, "PAGER"=>cmd}, "Executing PAGER"], + ].each do |env, expected| + File.write(cmd, "#{command}\n" "echo #{expected}\n", perm: 0o700) + assert_in_out_err([env, *args], "", [expected], chdir: dir) + end + end + end end From ece4bb2673c7fa9f53133ba91bbffd423ebadc90 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Wed, 27 Mar 2024 07:29:38 +0900 Subject: [PATCH 18/64] show warning for unused block With verbopse mode (-w), the interpreter shows a warning if a block is passed to a method which does not use the given block. Warning on: * the invoked method is not written in C * the invoked method is not `initialize` * not invoked with `super` * the first time on the call-site with the invoked method (`obj.foo{}` will be warned once if `foo` is same method) [Feature #15554] `Primitive.attr! :use_block` is introduced to declare that primitive functions (written in C) will use passed block. For minitest, test needs some tweak, so use https://github.com/minitest/minitest/commit/ea9caafc0754b1d6236a490d59e624b53209734a for `test-bundled-gems`. ruby/ruby@9180e33ca3a5886fec3f9e0a2f48072b55914e65 --- lib/optparse.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 76ff38a..1dec3fa 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -461,7 +461,7 @@ def self.candidate(key, icase = false, pat = nil, &block) candidates end - def candidate(key, icase = false, pat = nil) + def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end @@ -739,7 +739,7 @@ class RequiredArgument < self # # 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 From f5018a8b1ce82f7ac7458d00d099a454c3c03ea1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 14:05:43 +0900 Subject: [PATCH 19/64] bump up to 0.5.0 --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 1dec3fa..069c3e4 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -425,7 +425,7 @@ # class OptionParser # The version string - OptionParser::Version = "0.4.0" + OptionParser::Version = "0.5.0" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze From b49cb996afdd68e4eb373ba602c95b52c963d160 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 14:24:19 +0900 Subject: [PATCH 20/64] [DOC] Package files for RDoc --- optparse.gemspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/optparse.gemspec b/optparse.gemspec index a4287dd..9427646 100644 --- a/optparse.gemspec +++ b/optparse.gemspec @@ -22,7 +22,8 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir["{doc,lib,misc}/**/*"] + %w[README.md ChangeLog COPYING] + spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + + %w[README.md ChangeLog COPYING .document .rdoc_options"] spec.rdoc_options = ["--main=README.md", "--op=rdoc", "--page-dir=doc"] spec.bindir = "exe" spec.executables = [] From 0aec9adfc58223f8520464841f24dcea48f6e477 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 14:25:48 +0900 Subject: [PATCH 21/64] Fix typo [ci skip] --- optparse.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optparse.gemspec b/optparse.gemspec index 9427646..1aa54aa 100644 --- a/optparse.gemspec +++ b/optparse.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + - %w[README.md ChangeLog COPYING .document .rdoc_options"] + %w[README.md ChangeLog COPYING .document .rdoc_options] spec.rdoc_options = ["--main=README.md", "--op=rdoc", "--page-dir=doc"] spec.bindir = "exe" spec.executables = [] From b7382c02f1092f2c0ef0f4096d3b8428e2adf3fa Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 14:49:59 +0900 Subject: [PATCH 22/64] [DOC] Exclude CI-skipped commits from changelogs --- rakelib/changelogs.rake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rakelib/changelogs.rake b/rakelib/changelogs.rake index df72f9d..016e78b 100644 --- a/rakelib/changelogs.rake +++ b/rakelib/changelogs.rake @@ -3,7 +3,10 @@ task "build" => "changelogs" changelog = proc do |output, ver = nil, prev = nil| ver &&= Gem::Version.new(ver) range = [[prev], [ver, "HEAD"]].map {|ver, branch| ver ? "v#{ver.to_s}" : branch}.compact.join("..") - IO.popen(%W[git log --format=fuller --topo-order --no-merges #{range}]) do |log| + cmd = %W[git log --format=fuller --topo-order --no-merges + --invert-grep --fixed-strings --grep=#{'[ci skip]'} + #{range} --] + IO.popen(cmd) do |log| line = log.gets FileUtils.mkpath(File.dirname(output)) File.open(output, "wb") do |f| From 3a2dcd72d1fdabf63218cd13c8c7953cb3c28f6b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 14:55:14 +0900 Subject: [PATCH 23/64] [DOC] Make ChangeLog non-empty When the latest version is tagged at the head, there is no commit between the tag and the head. Make from the previous tag. --- rakelib/changelogs.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/rakelib/changelogs.rake b/rakelib/changelogs.rake index 016e78b..46cdeb6 100644 --- a/rakelib/changelogs.rake +++ b/rakelib/changelogs.rake @@ -22,6 +22,7 @@ end tags = IO.popen(%w[git tag -l v[0-9]*]).grep(/v(.*)/) {$1} tags.sort_by! {|tag| tag.scan(/\d+/).map(&:to_i)} +tags.pop if IO.popen(%W[git log --format=%H v#{tags.last}..HEAD --], &:read).empty? tags.inject(nil) do |prev, tag| task("logs/ChangeLog-#{tag}") {|t| changelog[t.name, tag, prev]} tag From 436a5c516b67f8aec900f2361567c6fc163ade05 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 15:08:19 +0900 Subject: [PATCH 24/64] [DOC] Make timestamps in a ISO 8601-like format --- rakelib/changelogs.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rakelib/changelogs.rake b/rakelib/changelogs.rake index 46cdeb6..ea5b97d 100644 --- a/rakelib/changelogs.rake +++ b/rakelib/changelogs.rake @@ -3,7 +3,7 @@ task "build" => "changelogs" changelog = proc do |output, ver = nil, prev = nil| ver &&= Gem::Version.new(ver) range = [[prev], [ver, "HEAD"]].map {|ver, branch| ver ? "v#{ver.to_s}" : branch}.compact.join("..") - cmd = %W[git log --format=fuller --topo-order --no-merges + cmd = %W[git log --date=iso --format=fuller --topo-order --no-merges --invert-grep --fixed-strings --grep=#{'[ci skip]'} #{range} --] IO.popen(cmd) do |log| From add085df63d79d878a8901c42f2cfc3eec56307f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 15:57:09 +0900 Subject: [PATCH 25/64] Fetch upto the previous tag for changelogs --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b3d44a..347641f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,14 @@ jobs: - id: build run: | + latest=$(git tag --list --contains HEAD) + case "$latest" in + '') prev=;; + *.0.0) prev=${latest%.0.0}; prev=$((prev-1)).0.0;; + *.0) prev=${latest%.0}; x=${prev##*.}; prev=${prev%.*}.$((x-1)).0;; + *) x=${prev##*.}; prev=${prev%.*}.$((x-1));; + esac + ${prev:+git fetch --unshallow-exclude=$prev origin} rake build ls -l pkg/*.gem shasum -a 256 pkg/*.gem From 7f79bc820ff0e04a05204ec63c5fc3e9c85e14ad Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 18:08:57 +0900 Subject: [PATCH 26/64] [DOC] Fix error when no tag found --- rakelib/changelogs.rake | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rakelib/changelogs.rake b/rakelib/changelogs.rake index ea5b97d..5e00c98 100644 --- a/rakelib/changelogs.rake +++ b/rakelib/changelogs.rake @@ -21,11 +21,13 @@ changelog = proc do |output, ver = nil, prev = nil| end tags = IO.popen(%w[git tag -l v[0-9]*]).grep(/v(.*)/) {$1} -tags.sort_by! {|tag| tag.scan(/\d+/).map(&:to_i)} -tags.pop if IO.popen(%W[git log --format=%H v#{tags.last}..HEAD --], &:read).empty? -tags.inject(nil) do |prev, tag| - task("logs/ChangeLog-#{tag}") {|t| changelog[t.name, tag, prev]} - tag +unless tags.empty? + tags.sort_by! {|tag| tag.scan(/\d+/).map(&:to_i)} + tags.pop if IO.popen(%W[git log --format=%H v#{tags.last}..HEAD --], &:read).empty? + tags.inject(nil) do |prev, tag| + task("logs/ChangeLog-#{tag}") {|t| changelog[t.name, tag, prev]} + tag + end end desc "Make ChangeLog" From ca883bd3a1ac13957774fcb93e8bccd80b74c9d1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 18:21:31 +0900 Subject: [PATCH 27/64] [DOC] git on Github Actions is old It does not support `--unshallow-exclude` option. --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 347641f..bbc8a2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,7 @@ jobs: - id: build run: | + : fetch change logs latest=$(git tag --list --contains HEAD) case "$latest" in '') prev=;; @@ -51,7 +52,10 @@ jobs: *.0) prev=${latest%.0}; x=${prev##*.}; prev=${prev%.*}.$((x-1)).0;; *) x=${prev##*.}; prev=${prev%.*}.$((x-1));; esac - ${prev:+git fetch --unshallow-exclude=$prev origin} + : ${prev:+git fetch --unshallow-exclude=$prev origin} + until git log -1 --oneline $prev; do + git fetch --deepen=100 + done rake build ls -l pkg/*.gem shasum -a 256 pkg/*.gem From 979bd76281d28044b3def3eadba233a87e62415a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 15 Apr 2024 18:42:47 +0900 Subject: [PATCH 28/64] [DOC] Split fetching commits and building package --- .github/workflows/test.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bbc8a2d..d862b11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,9 +42,10 @@ jobs: - name: Run test run: rake test - - id: build + - id: fetch run: | - : fetch change logs + : Fetch deeper for changelogs + set -x latest=$(git tag --list --contains HEAD) case "$latest" in '') prev=;; @@ -56,18 +57,24 @@ jobs: until git log -1 --oneline $prev; do git fetch --deepen=100 done - rake build - ls -l pkg/*.gem - shasum -a 256 pkg/*.gem - echo "pkg=${GITHUB_REPOSITORY#*/}-${RUNNING_OS%-*}" >> $GITHUB_OUTPUT - env: - RUNNING_OS: ${{matrix.os}} + set +x shell: bash if: >- ${{ matrix.os != 'windows-latest' && matrix.ruby == needs.ruby-versions.outputs.latest }} + + - id: build + run: | + rake build + ls -l pkg/*.gem + shasum -a 256 pkg/*.gem + echo "pkg=${GITHUB_REPOSITORY#*/}-${RUNNING_OS%-*}" >> $GITHUB_OUTPUT + env: + RUNNING_OS: ${{matrix.os}} + if: ${{ steps.fetch.outcome == 'success' }} + - name: Upload package uses: actions/upload-artifact@v4 with: From ef9bf03d9eed8f8717ffa66d571c1e04aff4e2eb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Apr 2024 10:19:26 +0900 Subject: [PATCH 29/64] [DOC] Simplify fetching logs --- .github/workflows/test.yml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d862b11..2512fc1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,19 +45,9 @@ jobs: - id: fetch run: | : Fetch deeper for changelogs - set -x - latest=$(git tag --list --contains HEAD) - case "$latest" in - '') prev=;; - *.0.0) prev=${latest%.0.0}; prev=$((prev-1)).0.0;; - *.0) prev=${latest%.0}; x=${prev##*.}; prev=${prev%.*}.$((x-1)).0;; - *) x=${prev##*.}; prev=${prev%.*}.$((x-1));; - esac - : ${prev:+git fetch --unshallow-exclude=$prev origin} - until git log -1 --oneline $prev; do - git fetch --deepen=100 - done - set +x + git fetch --force --no-tags origin 'refs/tags/v*:refs/tags/v*' + prev="$(git tag --list --no-contains HEAD --sort version:refname | tail -1)" + git fetch --shallow-exclude=$prev origin shell: bash if: >- ${{ From 566a2c35550e243ac9791e08bab6f63482d0559b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Apr 2024 12:42:11 +0900 Subject: [PATCH 30/64] Building packages --- .github/workflows/test.yml | 40 ++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2512fc1..7ada1aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,11 +27,6 @@ jobs: - { os: windows-latest, ruby: jruby } runs-on: ${{ matrix.os }} steps: - - name: git config - run: | - git config --global core.autocrlf false - git config --global core.eol lf - git config --global advice.detachedHead 0 - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -42,28 +37,35 @@ jobs: - name: Run test run: rake test - - id: fetch + - id: build + name: Build package run: | + if command -v shasum > /dev/null; then + sha256sum=(shasum -a 256) + else + sha256sum=(ruby -rdigest -e 'ARGV.each{|f| print Digest::SHA256.file(f).hexdigest, " "; puts f}') + fi + PS4='##[command]' + set -x : Fetch deeper for changelogs git fetch --force --no-tags origin 'refs/tags/v*:refs/tags/v*' prev="$(git tag --list --no-contains HEAD --sort version:refname | tail -1)" - git fetch --shallow-exclude=$prev origin - shell: bash - if: >- - ${{ - matrix.os != 'windows-latest' && - matrix.ruby == needs.ruby-versions.outputs.latest - }} - - - id: build - run: | + branch="$(git symbolic-ref --short HEAD)" + git fetch ${prev:+--shallow-exclude=}${prev:---unshallow} origin ${branch} + : Re-checkout with LF + git config core.autocrlf false + git config core.eol lf + git checkout -f + : Build package rake build - ls -l pkg/*.gem - shasum -a 256 pkg/*.gem echo "pkg=${GITHUB_REPOSITORY#*/}-${RUNNING_OS%-*}" >> $GITHUB_OUTPUT + : Show info + ls -l pkg/*.gem + "${sha256sum[@]}" pkg/*.gem + shell: bash env: RUNNING_OS: ${{matrix.os}} - if: ${{ steps.fetch.outcome == 'success' }} + if: ${{ matrix.ruby == needs.ruby-versions.outputs.latest }} - name: Upload package uses: actions/upload-artifact@v4 From d6f03d722005788e2b9a27d3b1eefb75fed159ea Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 18 Apr 2024 01:40:54 +0900 Subject: [PATCH 31/64] Split package action --- .github/actions/package/action.yml | 62 ++++++++++++++++++++++++++++++ .github/workflows/test.yml | 36 +---------------- 2 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 .github/actions/package/action.yml diff --git a/.github/actions/package/action.yml b/.github/actions/package/action.yml new file mode 100644 index 0000000..bc0a2cd --- /dev/null +++ b/.github/actions/package/action.yml @@ -0,0 +1,62 @@ +name: Upload built package +description: >- + Build and update package. + +inputs: + upload-files: + required: false + default: 'pkg/*.gem' + description: File name pattern to upload. + + package-name: + required: false + default: '${GITHUB_REPOSITORY#*/}-${RUNNER_OS%-*}' + description: Package name to upload. + + build-program: + required: false + default: rake build + description: Command to build package files. + +runs: + using: composite + + steps: + - id: setup + run: | + : Setup + PS4="##[command]"; set -x + : Fetch deeper for changelogs + git fetch --force --no-tags origin 'refs/tags/v*:refs/tags/v*' + set -- "$(git symbolic-ref --short HEAD)" $(git tag --list --no-contains HEAD --sort -version:refname) + branch=$1 prev=$2 + git fetch ${prev:+--shallow-exclude=}${prev:---unshallow} origin ${branch} + : Re-checkout with LF + git config core.autocrlf false + git config core.eol lf + git checkout -f + shell: bash + + - id: build + run: | + : Build + if command -v shasum > /dev/null; then + shasum=(shasum -a 256 -b) # Ubuntu, macOS + elif command -v sha256sum > /dev/null; then + shasum=(sha256sum -b) # Windows + else # fallback + shasum=(ruby -rdigest -e "ARGV.each{|f| print Digest::SHA256.file(f).hexdigest, ' *'; puts f}") + fi + PS4="##[command]"; set -x + ${{ inputs.build-program }} + : Show info + ls -l ${{ inputs.upload-files }} + "${shasum[@]}" ${{ inputs.upload-files }} + echo pkg="${{ inputs.package-name }}" >> $GITHUB_OUTPUT + shell: bash + + - id: upload + uses: actions/upload-artifact@v4 + with: + path: ${{ inputs.upload-files }} + name: ${{ steps.build.outputs.pkg }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ada1aa..786c622 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,39 +37,5 @@ jobs: - name: Run test run: rake test - - id: build - name: Build package - run: | - if command -v shasum > /dev/null; then - sha256sum=(shasum -a 256) - else - sha256sum=(ruby -rdigest -e 'ARGV.each{|f| print Digest::SHA256.file(f).hexdigest, " "; puts f}') - fi - PS4='##[command]' - set -x - : Fetch deeper for changelogs - git fetch --force --no-tags origin 'refs/tags/v*:refs/tags/v*' - prev="$(git tag --list --no-contains HEAD --sort version:refname | tail -1)" - branch="$(git symbolic-ref --short HEAD)" - git fetch ${prev:+--shallow-exclude=}${prev:---unshallow} origin ${branch} - : Re-checkout with LF - git config core.autocrlf false - git config core.eol lf - git checkout -f - : Build package - rake build - echo "pkg=${GITHUB_REPOSITORY#*/}-${RUNNING_OS%-*}" >> $GITHUB_OUTPUT - : Show info - ls -l pkg/*.gem - "${sha256sum[@]}" pkg/*.gem - shell: bash - env: - RUNNING_OS: ${{matrix.os}} + - uses: ./.github/actions/package if: ${{ matrix.ruby == needs.ruby-versions.outputs.latest }} - - - name: Upload package - uses: actions/upload-artifact@v4 - with: - path: pkg/*.gem - name: ${{steps.build.outputs.pkg}} - if: ${{ steps.build.outcome == 'success' }} From 1211f70b6b73e2f142a5c8158f38359d2d0bc1ef Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 6 May 2024 02:04:57 +0900 Subject: [PATCH 32/64] Exclude older than 2.6 on macos-14 --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 786c622..2736e12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,7 @@ jobs: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest, windows-latest ] exclude: + - { os: macos-latest, ruby: 2.5 } - { os: windows-latest, ruby: truffleruby-head } - { os: windows-latest, ruby: truffleruby } - { os: windows-latest, ruby: jruby-head } From 19700e96d843b6f69d9c2ed28cc06e26e44a45f1 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Mon, 5 Aug 2024 05:28:01 +0300 Subject: [PATCH 33/64] Fix parsing array arguments with `:into` option --- lib/optparse.rb | 14 ++++++++------ test/optparse/test_acceptable.rb | 2 ++ test/optparse/test_optparse.rb | 13 +++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 069c3e4..d52401a 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1729,9 +1729,9 @@ def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, end end begin - 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 + 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 @@ -1761,7 +1761,7 @@ def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, 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 @@ -1769,8 +1769,8 @@ def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, 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 + 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 @@ -1798,6 +1798,8 @@ def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, # Calls callback with _val_. 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 diff --git a/test/optparse/test_acceptable.rb b/test/optparse/test_acceptable.rb index c7ea215..8b578ef 100644 --- a/test/optparse/test_acceptable.rb +++ b/test/optparse/test_acceptable.rb @@ -199,5 +199,7 @@ def test_decimal_numeric def test_array assert_equal(%w"", no_error {@opt.parse!(%w"--array a,b,c")}) assert_equal(%w"a b c", @array) + assert_equal(%w"", no_error {@opt.parse!(%w"--array a")}) + assert_equal(%w"a", @array) end end diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index 8d09e0f..7f35cb4 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -74,6 +74,7 @@ def test_into @opt.def_option "-v", "--verbose" do @verbose = true end @opt.def_option "-q", "--quiet" do @quiet = true end @opt.def_option "-o", "--option [OPT]" do |opt| @option = opt end + @opt.def_option "-a", "--array [VAL]", Array do |val| val end result = {} @opt.parse %w(--host localhost --port 8000 -v), into: result assert_equal({host: "localhost", port: 8000, verbose: true}, result) @@ -84,6 +85,18 @@ def test_into result = {} @opt.parse %w(--option OPTION -v), into: result assert_equal({verbose: true, option: "OPTION"}, result) + result = {} + @opt.parse %w(-a b,c,d), into: result + assert_equal({array: %w(b c d)}, result) + result = {} + @opt.parse %w(--array b,c,d), into: result + assert_equal({array: %w(b c d)}, result) + result = {} + @opt.parse %w(-a b), into: result + assert_equal({array: %w(b)}, result) + result = {} + @opt.parse %w(--array b), into: result + assert_equal({array: %w(b)}, result) end def test_require_exact From bb08cd47a85e0fbaea414cdcda3cbbdb81ac2d84 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 3 Sep 2024 13:35:00 +0900 Subject: [PATCH 34/64] Prefer `require_relative` --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index d52401a..5064186 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1115,7 +1115,7 @@ def help_exit Switch::OptionalArgument.new do |pkg| if pkg begin - require 'optparse/version' + require_relative 'optparse/version' rescue LoadError else show_version(*pkg.split(/,/)) or From e834978adc28f882bc4b27b0578c6d8111ece339 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 10 Sep 2024 13:57:23 +0900 Subject: [PATCH 35/64] Added BSDL file mentioned by COPYING --- BSDL | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 BSDL diff --git a/BSDL b/BSDL new file mode 100644 index 0000000..66d9359 --- /dev/null +++ b/BSDL @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. From 3739c150afbc752526d1e3c74e04129aad26cd3c Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Thu, 3 Oct 2024 14:20:09 +0200 Subject: [PATCH 36/64] README.md: Clarify text, remove dead references --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67d829f..160a4e7 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ p ARGV ## Development -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). From a6f519a5d145adec3231756fd71c9a1f819ecee3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 8 Nov 2024 13:08:16 +0900 Subject: [PATCH 37/64] Enabled trusted publisher for rubygems.org --- .github/workflows/push_gem.yml | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/push_gem.yml diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml new file mode 100644 index 0000000..91c6864 --- /dev/null +++ b/.github/workflows/push_gem.yml @@ -0,0 +1,46 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'ruby/optparse' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/optparse + + permissions: + contents: write + id-token: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + + - name: Set up Ruby + uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 + with: + bundler-cache: true + ruby-version: ruby + + - name: Publish to RubyGems + uses: rubygems/release-gem@612653d273a73bdae1df8453e090060bb4db5f31 # v1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} From 080360ffd4578aceb9f711eef8f16ee520cd3f00 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 8 Nov 2024 13:22:26 +0900 Subject: [PATCH 38/64] Bump up v0.6.0 --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 5064186..27e0843 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -425,7 +425,7 @@ # class OptionParser # The version string - OptionParser::Version = "0.5.0" + OptionParser::Version = "0.6.0" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze From 6561ef9c6054e5b997602bc95b96bc5be6961634 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 03:02:32 +0000 Subject: [PATCH 39/64] Bump rubygems/release-gem Bumps [rubygems/release-gem](https://github.com/rubygems/release-gem) from 612653d273a73bdae1df8453e090060bb4db5f31 to 9e85cb11501bebc2ae661c1500176316d3987059. - [Release notes](https://github.com/rubygems/release-gem/releases) - [Commits](https://github.com/rubygems/release-gem/compare/612653d273a73bdae1df8453e090060bb4db5f31...9e85cb11501bebc2ae661c1500176316d3987059) --- updated-dependencies: - dependency-name: rubygems/release-gem dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 91c6864..4c04fb0 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -36,7 +36,7 @@ jobs: ruby-version: ruby - name: Publish to RubyGems - uses: rubygems/release-gem@612653d273a73bdae1df8453e090060bb4db5f31 # v1 + uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1 - name: Create GitHub release run: | From b33139c1358e37ae0204d4763e0cdb9576db8118 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 25 Nov 2024 12:33:45 +0900 Subject: [PATCH 40/64] Fixed version number of rubygems/release-gem --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 4c04fb0..64b710d 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -36,7 +36,7 @@ jobs: ruby-version: ruby - name: Publish to RubyGems - uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1 + uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1.1.0 - name: Create GitHub release run: | From 2bff57ee8715296cbe668973d6e7fccd24307d09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 03:02:29 +0000 Subject: [PATCH 41/64] Bump step-security/harden-runner from 2.10.1 to 2.10.2 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.1 to 2.10.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/91182cccc01eb5e619899d80e4e971d6181294a7...0080882f6c36860b6ba35c610c98ce87d4e2f26f) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 64b710d..23653c7 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit From d64869106f769e5ef559bac079a098a29a328981 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:17:41 +0000 Subject: [PATCH 42/64] Bump rubygems/release-gem from 1.1.0 to 1.1.1 Bumps [rubygems/release-gem](https://github.com/rubygems/release-gem) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/rubygems/release-gem/releases) - [Commits](https://github.com/rubygems/release-gem/compare/9e85cb11501bebc2ae661c1500176316d3987059...a25424ba2ba8b387abc8ef40807c2c85b96cbe32) --- updated-dependencies: - dependency-name: rubygems/release-gem dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 23653c7..c290a6f 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -36,7 +36,7 @@ jobs: ruby-version: ruby - name: Publish to RubyGems - uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1.1.0 + uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 - name: Create GitHub release run: | From 7a684dfd7fdbc12710a70bd86400c216284892c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:12:02 +0100 Subject: [PATCH 43/64] Bump step-security/harden-runner from 2.10.2 to 2.10.3 (#85) Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.2 to 2.10.3. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/0080882f6c36860b6ba35c610c98ce87d4e2f26f...c95a14d0e5bab51a9f56296a4eb0e416910cd350) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index c290a6f..a17b546 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 with: egress-policy: audit From 7234771de28a1a711c613ceaccd901a3776293a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:01:51 +0000 Subject: [PATCH 44/64] Bump step-security/harden-runner from 2.10.3 to 2.10.4 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.3 to 2.10.4. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/c95a14d0e5bab51a9f56296a4eb0e416910cd350...cb605e52c26070c328afc4562f0b4ada7618a84e) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index a17b546..59effeb 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 with: egress-policy: audit From 30b6e0ef157c43aec616986930807c1dabb58185 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 03:07:47 +0000 Subject: [PATCH 45/64] Bump step-security/harden-runner from 2.10.4 to 2.11.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.10.4 to 2.11.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/cb605e52c26070c328afc4562f0b4ada7618a84e...4d991eb9b905ef189e4c376166672c3f2f230481) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 59effeb..e05793f 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 with: egress-policy: audit From 475ecbfc80a2c376f7038f8e7d7400adb066c044 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Mar 2025 21:55:21 +0900 Subject: [PATCH 46/64] Use `git rev-list` --- rakelib/changelogs.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rakelib/changelogs.rake b/rakelib/changelogs.rake index 5e00c98..9be2e8a 100644 --- a/rakelib/changelogs.rake +++ b/rakelib/changelogs.rake @@ -23,7 +23,7 @@ end tags = IO.popen(%w[git tag -l v[0-9]*]).grep(/v(.*)/) {$1} unless tags.empty? tags.sort_by! {|tag| tag.scan(/\d+/).map(&:to_i)} - tags.pop if IO.popen(%W[git log --format=%H v#{tags.last}..HEAD --], &:read).empty? + tags.pop if IO.popen(%W[git rev-list --right-only --count v#{tags.last}..HEAD --], &:read).to_i == 0 tags.inject(nil) do |prev, tag| task("logs/ChangeLog-#{tag}") {|t| changelog[t.name, tag, prev]} tag From 61b4ea070439172b39eea0bca0de55435e4cccea Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Mar 2025 21:56:59 +0900 Subject: [PATCH 47/64] [DOC] Manage rdoc options only in .rdoc_options file Make `rdoc .` and `rake rdoc` consistent. --- .rdoc_options | 1 + optparse.gemspec | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.rdoc_options b/.rdoc_options index 79a8fce..24fe2d7 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -2,3 +2,4 @@ page_dir: doc main_page: README.md title: Documentation for OptionParser +op_dir: rdoc diff --git a/optparse.gemspec b/optparse.gemspec index 1aa54aa..126f71b 100644 --- a/optparse.gemspec +++ b/optparse.gemspec @@ -24,7 +24,6 @@ Gem::Specification.new do |spec| spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + %w[README.md ChangeLog COPYING .document .rdoc_options] - spec.rdoc_options = ["--main=README.md", "--op=rdoc", "--page-dir=doc"] spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] From a3f102981548d96f1f905dbec405d444fef7888d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Mar 2025 22:38:30 +0900 Subject: [PATCH 48/64] Use `\A` instead of `^` as the beginning of string --- lib/optparse.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 27e0843..ade9870 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -668,7 +668,7 @@ def compsys(sdone, ldone) # :nodoc: (sopts+lopts).each do |opt| # "(-x -c -r)-l[left justify]" - if /^--\[no-\](.+)$/ =~ opt + if /\A--\[no-\](.+)$/ =~ opt o = $1 yield("--#{o}", desc.join("")) yield("--no-#{o}", desc.join("")) @@ -1508,7 +1508,7 @@ def make_switch(opts, block = nil) 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 @@ -1519,7 +1519,7 @@ def make_switch(opts, block = nil) (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 @@ -1532,7 +1532,7 @@ def make_switch(opts, block = nil) not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << "no-#{o}" - when /^--([^\[\]=\s]*)(.+)?/ + when /\A--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1542,7 +1542,7 @@ def make_switch(opts, block = nil) ldesc << "--#{q}" (o = q.downcase).tr!('_', '-') long << o - when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ + when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a @@ -1553,7 +1553,7 @@ def make_switch(opts, block = nil) end sdesc << "-#{q}" short << Regexp.new(q) - when /^-(.)(.+)?/ + when /\A-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1562,7 +1562,7 @@ def make_switch(opts, block = nil) 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 From 0a0e977b7c0dd2df42853e60aabdf67eef045755 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Mar 2025 22:54:21 +0900 Subject: [PATCH 49/64] Add test for enum arguments --- test/optparse/test_placearg.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index a8a11e6..c6dc7ab 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -7,6 +7,7 @@ def setup @opt.def_option("-x [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("--enum [VAL]", [:Alpha, :Bravo, :Charlie]) {|x| @enum = x} @topt = nil @opt.def_option("-n") {} @opt.def_option("--regexp [REGEXP]", Regexp) {|x| @reopt = x} @@ -93,4 +94,9 @@ def test_lambda assert_equal(%w"", no_error {@opt.parse!(%w"--lambda")}) assert_equal(nil, @flag) end + + def test_enum + assert_equal([], no_error {@opt.parse!(%w"--enum=A")}) + assert_equal(:Alpha, @enum) + end end From c5ec052efc7e7dfc5f2e7c294cff3f12dfdbae6a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 9 Mar 2025 22:39:12 +0900 Subject: [PATCH 50/64] Allow non-string enum list #79 Command line arguments are strings, convert enum list elements to strings to match. --- lib/optparse.rb | 2 +- test/optparse/test_placearg.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index ade9870..11218fe 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1503,7 +1503,7 @@ def make_switch(opts, block = nil) else raise ArgumentError, "argument pattern given twice" end - o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} + o.each {|pat, *v| pattern[pat.to_s] = v.fetch(0) {pat}} when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index c6dc7ab..511541f 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -8,6 +8,7 @@ def setup @opt.def_option("--option [VAL]") {|x| @flag = 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("--integer [VAL]", [1, 2, 3]) {|x| @integer = x} @topt = nil @opt.def_option("-n") {} @opt.def_option("--regexp [REGEXP]", Regexp) {|x| @reopt = x} @@ -99,4 +100,9 @@ def test_enum assert_equal([], no_error {@opt.parse!(%w"--enum=A")}) assert_equal(:Alpha, @enum) end + + def test_enum_conversion + assert_equal([], no_error {@opt.parse!(%w"--integer=1")}) + assert_equal(1, @integer) + end end From 5e71a70cb59dc612fbff33872904a458ea6eba82 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Mar 2025 14:05:16 +0900 Subject: [PATCH 51/64] [DOC] Update documents to use single quotes instead of backqoutes --- doc/optparse/argument_converters.rdoc | 2 +- doc/optparse/option_params.rdoc | 29 ++++++++++++------------ doc/optparse/tutorial.rdoc | 32 +++++++++++++-------------- lib/optparse.rb | 4 ++-- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/doc/optparse/argument_converters.rdoc b/doc/optparse/argument_converters.rdoc index 4b4b30e..5327298 100644 --- a/doc/optparse/argument_converters.rdoc +++ b/doc/optparse/argument_converters.rdoc @@ -377,4 +377,4 @@ Executions: $ ruby match_converter.rb --capitalize foo ["Foo", String] $ ruby match_converter.rb --capitalize "foo bar" - match_converter.rb:9:in `
': invalid argument: --capitalize foo bar (OptionParser::InvalidArgument) + match_converter.rb:9:in '
': invalid argument: --capitalize foo bar (OptionParser::InvalidArgument) diff --git a/doc/optparse/option_params.rdoc b/doc/optparse/option_params.rdoc index 35db8b5..48de12e 100644 --- a/doc/optparse/option_params.rdoc +++ b/doc/optparse/option_params.rdoc @@ -91,7 +91,7 @@ Executions: Usage: short_required [options] -xXXX Short name with required argument $ ruby short_required.rb -x - short_required.rb:6:in `
': missing argument: -x (OptionParser::MissingArgument) + short_required.rb:6:in '
': missing argument: -x (OptionParser::MissingArgument) $ ruby short_required.rb -x FOO ["-x", "FOO"] @@ -181,7 +181,7 @@ Executions: Usage: long_required [options] --xxx XXX Long name with required argument $ ruby long_required.rb --xxx - long_required.rb:6:in `
': missing argument: --xxx (OptionParser::MissingArgument) + long_required.rb:6:in '
': missing argument: --xxx (OptionParser::MissingArgument) $ ruby long_required.rb --xxx FOO ["--xxx", "FOO"] @@ -243,11 +243,11 @@ Usage: mixed_names [options] $ ruby mixed_names.rb --xxx ["--xxx", true] $ ruby mixed_names.rb -y - mixed_names.rb:12:in `
': missing argument: -y (OptionParser::MissingArgument) + mixed_names.rb:12:in '
': missing argument: -y (OptionParser::MissingArgument) $ ruby mixed_names.rb -y FOO ["--yyy", "FOO"] $ ruby mixed_names.rb --yyy - mixed_names.rb:12:in `
': missing argument: --yyy (OptionParser::MissingArgument) + mixed_names.rb:12:in '
': missing argument: --yyy (OptionParser::MissingArgument) $ ruby mixed_names.rb --yyy BAR ["--yyy", "BAR"] $ ruby mixed_names.rb -z @@ -279,7 +279,7 @@ Executions: Usage: argument_keywords [options] -x, --xxx Required argument $ ruby argument_styles.rb --xxx - argument_styles.rb:6:in `
': missing argument: --xxx (OptionParser::MissingArgument) + argument_styles.rb:6:in '
': missing argument: --xxx (OptionParser::MissingArgument) $ ruby argument_styles.rb --xxx FOO ["--xxx", "FOO"] @@ -298,7 +298,7 @@ Executions: Usage: argument_strings [options] -x, --xxx=XXX Required argument $ ruby argument_strings.rb --xxx - argument_strings.rb:9:in `
': missing argument: --xxx (OptionParser::MissingArgument) + argument_strings.rb:9:in '
': missing argument: --xxx (OptionParser::MissingArgument) $ ruby argument_strings.rb --xxx FOO ["--xxx", "FOO"] @@ -331,7 +331,7 @@ Executions: -xXXX Values for required argument -y [YYY] Values for optional argument $ ruby explicit_array_values.rb -x - explicit_array_values.rb:9:in `
': missing argument: -x (OptionParser::MissingArgument) + explicit_array_values.rb:9:in '
': missing argument: -x (OptionParser::MissingArgument) $ ruby explicit_array_values.rb -x foo ["-x", "foo"] $ ruby explicit_array_values.rb -x f @@ -339,9 +339,9 @@ Executions: $ ruby explicit_array_values.rb -x bar ["-x", "bar"] $ ruby explicit_array_values.rb -y ba - explicit_array_values.rb:9:in `
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) + explicit_array_values.rb:9:in '
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) $ ruby explicit_array_values.rb -x baz - explicit_array_values.rb:9:in `
': invalid argument: -x baz (OptionParser::InvalidArgument) + explicit_array_values.rb:9:in '
': invalid argument: -x baz (OptionParser::InvalidArgument) ===== Explicit Values in Hash @@ -361,7 +361,7 @@ Executions: -xXXX Values for required argument -y [YYY] Values for optional argument $ ruby explicit_hash_values.rb -x - explicit_hash_values.rb:9:in `
': missing argument: -x (OptionParser::MissingArgument) + explicit_hash_values.rb:9:in '
': missing argument: -x (OptionParser::MissingArgument) $ ruby explicit_hash_values.rb -x foo ["-x", 0] $ ruby explicit_hash_values.rb -x f @@ -369,7 +369,7 @@ Executions: $ ruby explicit_hash_values.rb -x bar ["-x", 1] $ ruby explicit_hash_values.rb -x baz - explicit_hash_values.rb:9:in `
': invalid argument: -x baz (OptionParser::InvalidArgument) + explicit_hash_values.rb:9:in '
': invalid argument: -x baz (OptionParser::InvalidArgument) $ ruby explicit_hash_values.rb -y ["-y", nil] $ ruby explicit_hash_values.rb -y baz @@ -377,14 +377,15 @@ Executions: $ ruby explicit_hash_values.rb -y bat ["-y", 3] $ ruby explicit_hash_values.rb -y ba - explicit_hash_values.rb:9:in `
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) + explicit_hash_values.rb:9:in '
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) $ ruby explicit_hash_values.rb -y bam ["-y", nil] ==== Argument Value Patterns You can restrict permissible argument values -by specifying a Regexp that the given argument must match. +by specifying a +Regexp+ that the given argument must match, +or a +Range+ or +Array+ that the converted value must be included in. File +matched_values.rb+ defines options with matched argument values. @@ -400,7 +401,7 @@ Executions: $ ruby matched_values.rb --xxx FOO ["--xxx", "FOO"] $ ruby matched_values.rb --xxx bar - matched_values.rb:6:in `
': invalid argument: --xxx bar (OptionParser::InvalidArgument) + matched_values.rb:6:in '
': invalid argument: --xxx bar (OptionParser::InvalidArgument) === Argument Converters diff --git a/doc/optparse/tutorial.rdoc b/doc/optparse/tutorial.rdoc index 6f56bbf..1134f94 100644 --- a/doc/optparse/tutorial.rdoc +++ b/doc/optparse/tutorial.rdoc @@ -111,7 +111,7 @@ Executions: ["x", true] ["input_file.txt", "output_file.txt"] $ ruby basic.rb -a - basic.rb:16:in `
': invalid option: -a (OptionParser::InvalidOption) + basic.rb:16:in '
': invalid option: -a (OptionParser::InvalidOption) === Defining Options @@ -232,11 +232,11 @@ Executions: $ ruby mixed_names.rb --xxx ["--xxx", true] $ ruby mixed_names.rb -y - mixed_names.rb:12:in `
': missing argument: -y (OptionParser::MissingArgument) + mixed_names.rb:12:in '
': missing argument: -y (OptionParser::MissingArgument) $ ruby mixed_names.rb -y FOO ["--yyy", "FOO"] $ ruby mixed_names.rb --yyy - mixed_names.rb:12:in `
': missing argument: --yyy (OptionParser::MissingArgument) + mixed_names.rb:12:in '
': missing argument: --yyy (OptionParser::MissingArgument) $ ruby mixed_names.rb --yyy BAR ["--yyy", "BAR"] $ ruby mixed_names.rb -z @@ -270,9 +270,9 @@ Executions: $ ruby name_abbrev.rb --draft ["--draft", true] $ ruby name_abbrev.rb --d - name_abbrev.rb:9:in `
': ambiguous option: --d (OptionParser::AmbiguousOption) + name_abbrev.rb:9:in '
': ambiguous option: --d (OptionParser::AmbiguousOption) $ ruby name_abbrev.rb --dr - name_abbrev.rb:9:in `
': ambiguous option: --dr (OptionParser::AmbiguousOption) + name_abbrev.rb:9:in '
': ambiguous option: --dr (OptionParser::AmbiguousOption) $ ruby name_abbrev.rb --dry ["--dry-run", true] $ ruby name_abbrev.rb --dra @@ -285,7 +285,7 @@ You can disable abbreviation using method +require_exact+. Executions: $ ruby no_abbreviation.rb --dry-ru - no_abbreviation.rb:10:in `
': invalid option: --dry-ru (OptionParser::InvalidOption) + no_abbreviation.rb:10:in '
': invalid option: --dry-ru (OptionParser::InvalidOption) $ ruby no_abbreviation.rb --dry-run ["--dry-run", true] @@ -323,7 +323,7 @@ Executions: Omitting a required argument raises an error: $ ruby required_argument.rb -x - required_argument.rb:9:in `
': missing argument: -x (OptionParser::MissingArgument) + required_argument.rb:9:in '
': missing argument: -x (OptionParser::MissingArgument) ==== Option with Optional Argument @@ -369,7 +369,7 @@ Executions: $ ruby argument_abbreviation.rb --xxx A ["--xxx", "ABC"] $ ruby argument_abbreviation.rb --xxx c - argument_abbreviation.rb:9:in `
': invalid argument: --xxx c (OptionParser::InvalidArgument) + argument_abbreviation.rb:9:in '
': invalid argument: --xxx c (OptionParser::InvalidArgument) $ ruby argument_abbreviation.rb --yyy a --yyy d ["--yyy", "XYZ"] ["--yyy", "FOO"] @@ -403,7 +403,7 @@ Executions: -xXXX Values for required argument -y [YYY] Values for optional argument $ ruby explicit_array_values.rb -x - explicit_array_values.rb:9:in `
': missing argument: -x (OptionParser::MissingArgument) + explicit_array_values.rb:9:in '
': missing argument: -x (OptionParser::MissingArgument) $ ruby explicit_array_values.rb -x foo ["-x", "foo"] $ ruby explicit_array_values.rb -x f @@ -411,9 +411,9 @@ Executions: $ ruby explicit_array_values.rb -x bar ["-x", "bar"] $ ruby explicit_array_values.rb -y ba - explicit_array_values.rb:9:in `
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) + explicit_array_values.rb:9:in '
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) $ ruby explicit_array_values.rb -x baz - explicit_array_values.rb:9:in `
': invalid argument: -x baz (OptionParser::InvalidArgument) + explicit_array_values.rb:9:in '
': invalid argument: -x baz (OptionParser::InvalidArgument) ===== Explicit Values in Hash @@ -433,7 +433,7 @@ Executions: -xXXX Values for required argument -y [YYY] Values for optional argument $ ruby explicit_hash_values.rb -x - explicit_hash_values.rb:9:in `
': missing argument: -x (OptionParser::MissingArgument) + explicit_hash_values.rb:9:in '
': missing argument: -x (OptionParser::MissingArgument) $ ruby explicit_hash_values.rb -x foo ["-x", 0] $ ruby explicit_hash_values.rb -x f @@ -441,7 +441,7 @@ Executions: $ ruby explicit_hash_values.rb -x bar ["-x", 1] $ ruby explicit_hash_values.rb -x baz - explicit_hash_values.rb:9:in `
': invalid argument: -x baz (OptionParser::InvalidArgument) + explicit_hash_values.rb:9:in '
': invalid argument: -x baz (OptionParser::InvalidArgument) $ ruby explicit_hash_values.rb -y ["-y", nil] $ ruby explicit_hash_values.rb -y baz @@ -449,7 +449,7 @@ Executions: $ ruby explicit_hash_values.rb -y bat ["-y", 3] $ ruby explicit_hash_values.rb -y ba - explicit_hash_values.rb:9:in `
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) + explicit_hash_values.rb:9:in '
': ambiguous argument: -y ba (OptionParser::AmbiguousArgument) $ ruby explicit_hash_values.rb -y bam ["-y", nil] @@ -472,7 +472,7 @@ Executions: $ ruby matched_values.rb --xxx FOO ["--xxx", "FOO"] $ ruby matched_values.rb --xxx bar - matched_values.rb:6:in `
': invalid argument: --xxx bar (OptionParser::InvalidArgument) + matched_values.rb:6:in '
': invalid argument: --xxx bar (OptionParser::InvalidArgument) === Keyword Argument +into+ @@ -524,7 +524,7 @@ Executions: -y, --yyyYYY Short and long, required argument -z, --zzz [ZZZ] Short and long, optional argument $ ruby missing_options.rb --yyy FOO - missing_options.rb:11:in `
': Missing required options: [:xxx, :zzz] (RuntimeError) + missing_options.rb:11:in '
': Missing required options: [:xxx, :zzz] (RuntimeError) ==== Default Values for Options diff --git a/lib/optparse.rb b/lib/optparse.rb index 11218fe..c0e861d 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -142,7 +142,7 @@ # Used: # # $ ruby optparse-test.rb -r -# optparse-test.rb:9:in `
': missing argument: -r (OptionParser::MissingArgument) +# optparse-test.rb:9:in '
': missing argument: -r (OptionParser::MissingArgument) # $ ruby optparse-test.rb -r my-library # You required my-library! # @@ -235,7 +235,7 @@ # $ ruby optparse-test.rb --user 2 # # # $ ruby optparse-test.rb --user 3 -# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) +# optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError) # # === Store options to a Hash # From d7dec6808f908ad616add62891c6660fe8042295 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Mar 2025 17:01:34 +0900 Subject: [PATCH 52/64] Remove extra blank lines [ci skip] --- lib/optparse.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index c0e861d..a53b1d1 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -496,7 +496,6 @@ def convert(opt = nil, val = nil, *) end end - # # Map from option/keyword string to object with completion. # @@ -504,7 +503,6 @@ class OptionMap < Hash include Completion end - # # Individual switch class. Not important to the user. # @@ -1032,7 +1030,6 @@ def match(key) DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} - COMPSYS_HEADER = <<'XXX' # :nodoc: typeset -A opt_args From 71e2b31824cf768a01a5aa4a969d45d2834c7d91 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Mar 2025 17:02:43 +0900 Subject: [PATCH 53/64] Update argument check with save navigation operator --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index a53b1d1..a45d99d 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -780,7 +780,7 @@ 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])) + if !(val = arg) and !(val = argv[0])&.match?(/\A(?!-.)/) return nil, block end opt = (val = parse_arg(val, &error))[1] From 050a87d02915400ee67ca6650dbaa131732f7c73 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Mar 2025 13:12:34 +0900 Subject: [PATCH 54/64] Add post-check of value Fix #80 --- lib/optparse.rb | 38 +++++++++++++++++++++++++++------- test/optparse/test_placearg.rb | 9 +++++++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index a45d99d..731d9bd 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -461,6 +461,10 @@ def self.candidate(key, icase = false, pat = nil, &block) 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 @@ -544,11 +548,11 @@ def self.pattern def initialize(pattern = nil, conv = 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 block ||= _block - @pattern, @conv, @short, @long, @arg, @desc, @block = - pattern, conv, short, long, arg, desc, block + @pattern, @conv, @short, @long, @arg, @desc, @block, @values = + pattern, conv, short, long, arg, desc, block, values end # @@ -581,11 +585,15 @@ def parse_arg(arg) # :nodoc: # exception. # def conv_arg(arg, val = []) # :nodoc: + v, = *val if conv val = conv.call(*val) else 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 @@ -780,7 +788,7 @@ 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 !(val = argv[0])&.match?(/\A(?!-.)/) + if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0])) return nil, block end opt = (val = parse_arg(val, &error))[1] @@ -1464,6 +1472,7 @@ def make_switch(opts, block = nil) klass = nil q, a = nil has_arg = false + values = nil opts.each do |o| # argument class @@ -1477,7 +1486,7 @@ def make_switch(opts, block = nil) end # 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') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc @@ -1492,6 +1501,11 @@ def make_switch(opts, block = nil) when Proc, Method block = notwice(o, block, 'block') 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 when CompletingHash when nil @@ -1500,7 +1514,9 @@ def make_switch(opts, block = nil) else raise ArgumentError, "argument pattern given twice" 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 raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys @@ -1568,12 +1584,18 @@ def make_switch(opts, block = nil) end 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 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) + conv, sdesc, ldesc, arg, desc, block, values) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) @@ -1582,7 +1604,7 @@ def make_switch(opts, block = nil) else short << pattern s = (style || default_style).new(pattern, - conv, nil, nil, arg, desc, block) + 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), diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index 511541f..36edf40 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -8,7 +8,8 @@ def setup @opt.def_option("--option [VAL]") {|x| @flag = 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("--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 @opt.def_option("-n") {} @opt.def_option("--regexp [REGEXP]", Regexp) {|x| @reopt = x} @@ -105,4 +106,10 @@ def test_enum_conversion assert_equal([], no_error {@opt.parse!(%w"--integer=1")}) assert_equal(1, @integer) 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 From e1957d7392bfe9083ca6c0c1779e4f3074859f5b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Mar 2025 16:53:16 +0900 Subject: [PATCH 55/64] [DOC] Mention about post-check --- doc/optparse/option_params.rdoc | 12 +++++++++++- doc/optparse/ruby/matched_values.rb | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/optparse/option_params.rdoc b/doc/optparse/option_params.rdoc index 48de12e..575ee66 100644 --- a/doc/optparse/option_params.rdoc +++ b/doc/optparse/option_params.rdoc @@ -396,12 +396,22 @@ Executions: $ ruby matched_values.rb --help Usage: matched_values [options] --xxx XXX Matched values + --yyy YYY Check by range + --zzz ZZZ Check by list $ ruby matched_values.rb --xxx foo ["--xxx", "foo"] $ ruby matched_values.rb --xxx FOO ["--xxx", "FOO"] $ ruby matched_values.rb --xxx bar - matched_values.rb:6:in '
': invalid argument: --xxx bar (OptionParser::InvalidArgument) + matched_values.rb:12:in '
': invalid argument: --xxx bar (OptionParser::InvalidArgument) + $ ruby matched_values.rb --yyy 1 + ["--yyy", 1] + $ ruby matched_values.rb --yyy 4 + matched_values.rb:12:in '
': invalid argument: --yyy 4 (OptionParser::InvalidArgument) + $ ruby matched_values.rb --zzz 1 + ["--zzz", 1] + $ ruby matched_values.rb --zzz 2 + matched_values.rb:12:in '
': invalid argument: --zzz 2 (OptionParser::InvalidArgument) === Argument Converters diff --git a/doc/optparse/ruby/matched_values.rb b/doc/optparse/ruby/matched_values.rb index f184ca8..a1aba14 100644 --- a/doc/optparse/ruby/matched_values.rb +++ b/doc/optparse/ruby/matched_values.rb @@ -3,4 +3,10 @@ parser.on('--xxx XXX', /foo/i, 'Matched values') do |value| p ['--xxx', value] end +parser.on('--yyy YYY', Integer, 'Check by range', 1..3) do |value| + p ['--yyy', value] +end +parser.on('--zzz ZZZ', Integer, 'Check by list', [1, 3, 4]) do |value| + p ['--zzz', value] +end parser.parse! From 262cf6f9ac9b94df4b649a670163121fd0335b80 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sun, 2 Feb 2025 16:17:37 +0900 Subject: [PATCH 56/64] Make the result of `tty?` obtainable with flexible stdout In mock testing for stdout, `StringIO.new` is sometimes used to redirect the output. In such cases, the assignment is done with `$stdout = StringIO.new`, not the constant `STDOUT`. e.g., https://github.com/rubocop/rubocop/blob/v1.71.1/lib/rubocop/rspec/shared_contexts.rb#L154-L164 After assigning `StringIO.new`, `$stdout.tty?` returns `false`, allowing the standard output destination to be switched during test execution. ```ruby STDOUT.tty? # => true StringIO.new.tty? # => false ``` However, since `STDOUT.tty?` returns `true`, a failure occurred in environments where the environment variables `RUBY_PAGER` or `PAGER` are set. e.g., https://github.com/rubocop/rubocop/pull/13784 To address this, `STDOUT` has been updated to `$stdout` so that the result of `tty?` can be flexibly overridden. A potential concern is that `$stdout`, unlike `STDOUT`, does not always represent the standard output at the time the Ruby process started. However, no concrete examples of issues related to this have been identified. `STDOUT.tty?` is the logic of optparse introduced in https://github.com/ruby/optparse/pull/70. This PR replaces `STDOUT` with `$stdout` throughout, based on the assumption that `$stdout` is sufficient for use with optparse. --- lib/optparse.rb | 6 +++--- test/optparse/test_optparse.rb | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 731d9bd..f849a9c 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1056,7 +1056,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: end def help_exit - if STDOUT.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) + 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| @@ -1065,7 +1065,7 @@ def help_exit # pager terminated end if Process.respond_to?(:fork) and false - IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call(STDOUT)} + IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)} # unreachable end IO.popen(*args, &print) @@ -1107,7 +1107,7 @@ def help_exit # Officious['*-completion-zsh'] = proc do |parser| Switch::OptionalArgument.new do |arg| - parser.compsys(STDOUT, arg) + parser.compsys($stdout, arg) exit end end diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index 7f35cb4..d50203b 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -184,10 +184,9 @@ def test_help_pager File.open(File.join(dir, "options.rb"), "w") do |f| f.puts "#{<<~"begin;"}\n#{<<~'end;'}" begin; - stdout = STDOUT.dup + stdout = $stdout.dup def stdout.tty?; true; end - Object.__send__(:remove_const, :STDOUT) - STDOUT = stdout + $stdout = stdout ARGV.options do |opt| end; 100.times {|i| f.puts " opt.on('--opt-#{i}') {}"} From 30571f91d3dca33f2244883127cb1785694aa4d4 Mon Sep 17 00:00:00 2001 From: Kouhei Yanagita Date: Thu, 9 Jan 2025 11:00:54 +0900 Subject: [PATCH 57/64] Fix LESS environment variable setup in OptionParser#help_exit If the original value of LESS ends with an option starting with "--", simply appending "Fe" would result in an invalid option string. --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index f849a9c..f32edc5 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1058,7 +1058,7 @@ def compsys(to, name = File.basename($0)) # :nodoc: 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"] + args = [{"LESS" => "#{less} -Fe"}, pager, "w"] print = proc do |f| f.puts help rescue Errno::EPIPE From 83e8c23d68b70e32e816b9c1c755f0479a63762c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 26 Apr 2024 19:32:29 +0900 Subject: [PATCH 58/64] [DOC] Extract description from README --- optparse.gemspec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/optparse.gemspec b/optparse.gemspec index 126f71b..8589f18 100644 --- a/optparse.gemspec +++ b/optparse.gemspec @@ -14,7 +14,10 @@ Gem::Specification.new do |spec| spec.email = ["nobu@ruby-lang.org"] spec.summary = %q{OptionParser is a class for command-line option analysis.} - spec.description = %q{OptionParser is a class for command-line option analysis.} + spec.description = File.open(File.join(__dir__, "README.md")) do |readme| + readme.gets("") # heading + readme.gets("").chomp + end rescue spec.summary spec.homepage = "https://github.com/ruby/optparse" spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0") spec.licenses = ["Ruby", "BSD-2-Clause"] From f4d64b0b17af329b774181db220a5daa806d8061 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 10 Mar 2025 20:06:00 +0900 Subject: [PATCH 59/64] bump up to 0.7.0.dev.1 [ci skip] --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index f32edc5..fca5663 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -425,7 +425,7 @@ # class OptionParser # The version string - OptionParser::Version = "0.6.0" + OptionParser::Version = "0.7.0.dev.1" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze From a8d0ba8dac609c815a3dcdeb3203574192c9c41d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 17 Mar 2025 19:13:47 +0900 Subject: [PATCH 60/64] Fix completion of key-value pairs array Enum array may be the list of pairs of key and value. Check if only key is completable, not pair. Fix #93 Fix #94 --- lib/optparse.rb | 2 +- test/optparse/test_placearg.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index fca5663..c9caf32 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1502,7 +1502,7 @@ def make_switch(opts, block = nil) block = notwice(o, block, 'block') when Array, Hash if Array === o - o, v = o.partition {|v| Completion.completable?(v)} + o, v = o.partition {|v,| Completion.completable?(v)} values = notwice(v, values, 'values') unless v.empty? next if o.empty? end diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb index 36edf40..d5be5a6 100644 --- a/test/optparse/test_placearg.rb +++ b/test/optparse/test_placearg.rb @@ -8,6 +8,7 @@ def setup @opt.def_option("--option [VAL]") {|x| @flag = 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("--enumval [VAL]", [[:Alpha, 1], [:Bravo, 2], [:Charlie, 3]]) {|x| @enum = 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 @@ -102,6 +103,11 @@ def test_enum assert_equal(:Alpha, @enum) end + def test_enum_pair + assert_equal([], no_error {@opt.parse!(%w"--enumval=A")}) + assert_equal(1, @enum) + end + def test_enum_conversion assert_equal([], no_error {@opt.parse!(%w"--integer=1")}) assert_equal(1, @integer) From ba649b1f7300dde001d68ab75da69e43c34ff10d Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Mar 2025 16:57:01 +0900 Subject: [PATCH 61/64] bump:dev task --- rakelib/version.rake | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rakelib/version.rake b/rakelib/version.rake index eb430b0..583530d 100644 --- a/rakelib/version.rake +++ b/rakelib/version.rake @@ -28,9 +28,21 @@ class << (helper = Bundler::GemHelper.instance) def bump(major, minor = 0, teeny = 0, pre: nil) self.version = [major, minor, teeny, pre].compact.join(".") end + + def next_prerelease(*prefix, num) + if num + [*prefix, num.succ] + else + "dev.1" + end + end end -major, minor, teeny = helper.gemspec.version.segments +major, minor, teeny, *prerelease = helper.gemspec.version.segments + +task "bump:dev", [:pre] do |t, pre: helper.next_prerelease(*prerelease)| + helper.bump(major, minor, teeny, pre: pre) +end task "bump:teeny", [:pre] do |t, pre: nil| helper.bump(major, minor, teeny+1, pre: pre) From 8c2c7a490343bff6b131d40e2e06d325fa9e47f7 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 19 Mar 2025 16:57:11 +0900 Subject: [PATCH 62/64] bump up to 0.7.0.dev.2 --- lib/optparse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index c9caf32..e1069b3 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -425,7 +425,7 @@ # class OptionParser # The version string - OptionParser::Version = "0.7.0.dev.1" + OptionParser::Version = "0.7.0.dev.2" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze From 24bce42a5c88b078eb0cb9da2bf17cc1102733d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 03:44:44 +0000 Subject: [PATCH 63/64] Bump step-security/harden-runner from 2.11.0 to 2.11.1 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.11.0 to 2.11.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/4d991eb9b905ef189e4c376166672c3f2f230481...c6295a65d1254861815972266d5933fd6e532bdf) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.11.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index e05793f..0a72cfd 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1 with: egress-policy: audit From 541e99d4aadac96c42dbdaed7863cc91a15d6336 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 03:35:10 +0000 Subject: [PATCH 64/64] Bump step-security/harden-runner from 2.11.1 to 2.12.0 Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.11.1 to 2.12.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/c6295a65d1254861815972266d5933fd6e532bdf...0634a2670c59f64b4a01f0f96f84700a4088b9f0) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/push_gem.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml index 0a72cfd..4e02df3 100644 --- a/.github/workflows/push_gem.yml +++ b/.github/workflows/push_gem.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1 + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 with: egress-policy: audit