From 1c895efc39ed2290c185dcb80523fee381dfe74e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 18 Dec 2023 12:15:31 +0900 Subject: [PATCH 01/28] [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/28] [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/28] [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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] [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/28] [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/28] 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/28] 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/28] [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/28] 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/28] 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/28] 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/28] [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/28] 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/28] [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/28] [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/28] [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/28] 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/28] [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/28] [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/28] [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: