diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9009c0a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 80 + +[*.zsh] +indent_size = 4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1dee1d..eaac9f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,21 +4,38 @@ on: [push, pull_request] jobs: build: - name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: matrix: - ruby: [ 2.7, 2.6, 2.5, head ] + ruby: [ '3.0', 2.7, 2.6, 2.5, head ] os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@master + - 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@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: Install dependencies - run: | - gem install bundler --no-document - bundle install + run: bundle install - name: Run test run: rake test + + - id: build + run: | + rake build + ls -l pkg/*.gem + shasum -a 256 pkg/*.gem + echo "::set-output name=pkg::${GITHUB_REPOSITORY#*/}-${RUNNING_OS%-*}" + env: + RUNNING_OS: ${{matrix.os}} + shell: bash + - name: Upload package + uses: actions/upload-artifact@v2 + with: + path: pkg/*.gem + name: ${{steps.build.outputs.pkg}} diff --git a/.gitignore b/.gitignore index 9106b2a..b9b830f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ /_yardoc/ /coverage/ /doc/ +/logs/ /pkg/ /spec/reports/ /tmp/ +/Gemfile.lock +/ChangeLog diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 98180a7..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,17 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - power_assert (1.2.0) - rake (13.0.1) - test-unit (3.3.5) - power_assert - -PLATFORMS - ruby - -DEPENDENCIES - rake - test-unit - -BUNDLED WITH - 2.2.0.dev diff --git a/README.md b/README.md index bfd9a4a..67d829f 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,3 @@ To install this gem onto your local machine, run `bundle exec rake install`. To ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/optparse. - diff --git a/bin/console b/bin/console deleted file mode 100755 index 1630962..0000000 --- a/bin/console +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby - -require "bundler/setup" -require "optparse" - -require "irb" -IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup deleted file mode 100755 index cf4ad25..0000000 --- a/bin/setup +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' -set -vx - -bundle install diff --git a/lib/optparse.rb b/lib/optparse.rb index 82582f6..598ebd1 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -72,10 +72,10 @@ # require 'optparse' # # options = {} -# OptionParser.new do |opts| -# opts.banner = "Usage: example.rb [options]" +# OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" # -# opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| +# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| # options[:verbose] = v # end # end.parse! @@ -96,15 +96,15 @@ # def self.parse(options) # args = Options.new("world") # -# opt_parser = OptionParser.new do |opts| -# opts.banner = "Usage: example.rb [options]" +# opt_parser = OptionParser.new do |parser| +# parser.banner = "Usage: example.rb [options]" # -# opts.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| +# parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| # args.name = n # end # -# opts.on("-h", "--help", "Prints this help") do -# puts opts +# parser.on("-h", "--help", "Prints this help") do +# puts parser # exit # end # end @@ -241,10 +241,10 @@ # require 'optparse' # # params = {} -# OptionParser.new do |opts| -# opts.on('-a') -# opts.on('-b NUM', Integer) -# opts.on('-v', '--verbose') +# OptionParser.new do |parser| +# parser.on('-a') +# parser.on('-b NUM', Integer) +# parser.on('-v', '--verbose') # end.parse!(into: params) # # p params @@ -419,7 +419,7 @@ # have any questions, file a ticket at http://bugs.ruby-lang.org. # class OptionParser - OptionParser::Version = "0.1.0" + OptionParser::Version = "0.1.1" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -1091,6 +1091,7 @@ def initialize(banner = nil, width = 32, indent = ' ' * 4) @summary_width = width @summary_indent = indent @default_argv = ARGV + @require_exact = false add_officious yield self if block_given? end @@ -1164,6 +1165,10 @@ def self.reject(*args, &blk) top.reject(*args, &blk) end # Strings to be parsed in default. attr_accessor :default_argv + # Whether to require that options match exactly (disallows providing + # abbreviated long option as short option). + attr_accessor :require_exact + # # Heading banner preceding summary. # @@ -1305,13 +1310,16 @@ def notwice(obj, prv, msg) private :notwice SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: + + # :call-seq: + # make_switch(params, block = nil) # # Creates an OptionParser::Switch from the parameters. The parsed argument # value is passed to the given block, where it can be processed. # # See at the beginning of OptionParser for some full examples. # - # +opts+ can include the following elements: + # +params+ can include the following elements: # # [Argument style:] # One of the following: @@ -1498,11 +1506,16 @@ def make_switch(opts, block = nil) nolong end + # :call-seq: + # define(*params, &block) + # def define(*opts, &block) top.append(*(sw = make_switch(opts, block))) sw[0] end + # :call-seq: + # on(*params, &block) # # Add option switch and handler. See #make_switch for an explanation of # parameters. @@ -1513,11 +1526,16 @@ def on(*opts, &block) end alias def_option define + # :call-seq: + # define_head(*params, &block) + # def define_head(*opts, &block) top.prepend(*(sw = make_switch(opts, block))) sw[0] end + # :call-seq: + # on_head(*params, &block) # # Add option switch like with #on, but at head of summary. # @@ -1527,11 +1545,17 @@ def on_head(*opts, &block) end alias def_head_option define_head + # :call-seq: + # define_tail(*params, &block) + # def define_tail(*opts, &block) base.append(*(sw = make_switch(opts, block))) sw[0] end + # + # :call-seq: + # on_tail(*params, &block) # # Add option switch like with #on, but at tail of summary. # @@ -1583,6 +1607,9 @@ 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) + raise InvalidOption, arg + end rescue ParseError raise $!.set_option(arg, true) end @@ -1607,6 +1634,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 # if no short options match, try completion with long # options. sw, = complete(:long, opt) @@ -1618,7 +1646,12 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: end begin opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} + rescue ParseError + raise $!.set_option(arg, arg.length > 2) + else raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" + 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 @@ -1749,7 +1782,7 @@ def self.getopts(*args) # def visit(id, *args, &block) @stack.reverse_each do |el| - el.send(id, *args, &block) + el.__send__(id, *args, &block) end nil end diff --git a/lib/optparse/kwargs.rb b/lib/optparse/kwargs.rb index ed58cc1..5a2def4 100644 --- a/lib/optparse/kwargs.rb +++ b/lib/optparse/kwargs.rb @@ -2,6 +2,9 @@ require 'optparse' class OptionParser + # :call-seq: + # define_by_keywords(options, method, **params) + # def define_by_keywords(options, meth, **opts) meth.parameters.each do |type, name| case type diff --git a/misc/rb_optparse.bash b/misc/rb_optparse.bash index 8a59ec2..f77d937 100644 --- a/misc/rb_optparse.bash +++ b/misc/rb_optparse.bash @@ -1,4 +1,5 @@ -#! /bin/bash +# -*- bash -*- +# # Completion for bash: # # (1) install this file, @@ -16,5 +17,5 @@ _rb_optparse() { } rb_optparse () { - [ $# = 0 ] || complete -o default -F _rb_optparse "$@" + [ $# = 0 ] || complete -o default -F _rb_optparse "$@" } diff --git a/misc/rb_optparse.zsh b/misc/rb_optparse.zsh old mode 100755 new mode 100644 index 7407e87..258d4f8 --- a/misc/rb_optparse.zsh +++ b/misc/rb_optparse.zsh @@ -1,4 +1,5 @@ -#!/bin/zsh +# -*- zsh -*- +# # Completion for zsh: # (based on ) # @@ -7,9 +8,9 @@ # cp rb_optparse.zsh ~/.zsh.d/rb_optparse.zsh # # (2) load the script, and add a directory to fpath before compinit. -# echo '. ~/.zsh.d/rb_optparse.zsh' >> ~/.zshrc -# echo 'fpath=(~/.zsh.d/Completion $fpath)' >> ~/.zshrc -# echo 'autoload -U compinit; compinit' >> ~/.zshrc +# echo '. ~/.zsh.d/rb_optparse.zsh' >> "${ZDOTDIR:-~}/.zshrc" +# echo 'fpath=(~/.zsh.d/Completion $fpath)' >> "${ZDOTDIR:-~}/.zshrc" +# echo 'autoload -U compinit; compinit' >> "${ZDOTDIR:-~}/.zshrc" # # (3) restart zsh. # @@ -24,8 +25,8 @@ generate-complete-function/ruby/optparse () mkdir -p "${ZSH_COMPLETION_DIR-$HOME/.zsh.d/Completion}" $1 "--*-completion-zsh=${1:t}" >! "${ZSH_COMPLETION_DIR-$HOME/.zsh.d/Completion}/$cmpl" if [[ $(type -w "$cmpl") == "${cmpl}: function" ]]; then - unfunction "$cmpl" - autoload -U "$cmpl" + unfunction "$cmpl" + autoload -U "$cmpl" else compinit "$cmpl" fi diff --git a/optparse.gemspec b/optparse.gemspec index afb9042..831c787 100644 --- a/optparse.gemspec +++ b/optparse.gemspec @@ -23,7 +23,9 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) } + `git ls-files -z`.split("\x0").reject { |f| + f.match(%r{\A(?:(?:test|spec|features)/|Gemfile|\.(?:editor|git))}) + } end spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } diff --git a/rakelib/changelogs.rake b/rakelib/changelogs.rake new file mode 100644 index 0000000..df72f9d --- /dev/null +++ b/rakelib/changelogs.rake @@ -0,0 +1,34 @@ +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| + line = log.gets + FileUtils.mkpath(File.dirname(output)) + File.open(output, "wb") do |f| + f.print "-*- coding: utf-8 -*-\n\n", line + log.each_line do |line| + line.sub!(/^(?!:)(?:Author|Commit)?(?:Date)?: /, ' \&') + line.sub!(/ +$/, '') + f.print(line) + end + end + end +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.inject(nil) do |prev, tag| + task("logs/ChangeLog-#{tag}") {|t| changelog[t.name, tag, prev]} + tag +end + +desc "Make ChangeLog" +task "ChangeLog", [:ver, :prev] do |t, ver: nil, prev: tags.last| + changelog[t.name, ver, prev] +end + +changelogs = ["ChangeLog", *tags.map {|tag| "logs/ChangeLog-#{tag}"}] +task "changelogs" => changelogs +CLOBBER.concat(changelogs) << "logs" diff --git a/rakelib/epoch.rake b/rakelib/epoch.rake new file mode 100644 index 0000000..80f27c9 --- /dev/null +++ b/rakelib/epoch.rake @@ -0,0 +1,5 @@ +task "build" => "date_epoch" + +task "date_epoch" do + ENV["SOURCE_DATE_EPOCH"] = IO.popen(%W[git -C #{__dir__} log -1 --format=%ct], &:read).chomp +end diff --git a/rakelib/version.rake b/rakelib/version.rake new file mode 100644 index 0000000..f3557a7 --- /dev/null +++ b/rakelib/version.rake @@ -0,0 +1,47 @@ +class << (helper = Bundler::GemHelper.instance) + def mainfile + "lib/#{File.basename(gemspec.loaded_from, ".gemspec")}.rb" + end + + def update_version + File.open(mainfile, "r+b") do |f| + d = f.read + if d.sub!(/^(\s*OptionParser::Version\s*=\s*)".*"/) {$1 + gemspec.version.to_s.dump} + f.rewind + f.truncate(0) + f.print(d) + end + end + end + + def commit_bump + sh(%W[git -C #{File.dirname(gemspec.loaded_from)} commit -m bump\ up\ to\ #{gemspec.version} + #{mainfile}]) + end + + def version=(v) + gemspec.version = v + update_version + commit_bump + end +end + +major, minor, teeny = helper.gemspec.version.segments + +task "bump:teeny" do + helper.version = Gem::Version.new("#{major}.#{minor}.#{teeny+1}") +end + +task "bump:minor" do + helper.version = Gem::Version.new("#{major}.#{minor+1}.0") +end + +task "bump:major" do + helper.version = Gem::Version.new("#{major+1}.0.0") +end + +task "bump" => "bump:teeny" + +task "tag" do + helper.__send__(:tag_version) +end diff --git a/test/optparse/test_acceptable.rb b/test/optparse/test_acceptable.rb index 5c3fbdb..12f7886 100644 --- a/test/optparse/test_acceptable.rb +++ b/test/optparse/test_acceptable.rb @@ -196,4 +196,3 @@ def test_decimal_numeric end end - diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index e4aeb07..5f5ea18 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -75,4 +75,34 @@ def test_into assert_equal({host: "localhost", port: 8000, verbose: true}, result) assert_equal(true, @verbose) end + + def test_require_exact + @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 + + @opt.require_exact = true + %w(--zrs -F -Ffoo).each do |arg| + result = {} + @opt.parse([arg, 'foo'], into: result) + assert_equal({zrs: 'foo'}, result) + end + + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(--zr foo))} + assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(--z foo))} + 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))} + end + + def test_nonopt_pattern + @opt.def_option(/^[^-]/) do |arg| + assert(false, "Never gets called") + end + e = assert_raise(OptionParser::InvalidOption) {@opt.parse(%w(-t))} + assert_equal(["-t"], e.args) + end end