aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/optparse.rb70
-rw-r--r--misc/rb_optparse.bash20
-rw-r--r--test/optparse/test_bash_completion.rb42
-rw-r--r--test/optparse/test_getopts.rb4
4 files changed, 127 insertions, 9 deletions
diff --git a/lib/optparse.rb b/lib/optparse.rb
index 4f631fbec6..e36b52f7ec 100644
--- a/lib/optparse.rb
+++ b/lib/optparse.rb
@@ -195,6 +195,11 @@
# options = OptparseExample.parse(ARGV)
# pp options
#
+# === Shell Completion
+#
+# For modern shells (e.g. bash, zsh, etc.), you can use shell
+# completion for command line options.
+#
# === Further documentation
#
# The above examples should be enough to learn how to use this class. If you
@@ -218,12 +223,15 @@ class OptionParser
# and resolved against a list of acceptable values.
#
module Completion
- def complete(key, icase = false, pat = nil)
- pat ||= Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'),
- icase)
+ def self.regexp(key, icase)
+ Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase)
+ end
+
+ def self.candidate(key, icase = false, pat = nil, &block)
+ pat ||= Completion.regexp(key, icase)
canon, sw, cn = nil
candidates = []
- each do |k, *v|
+ block.call do |k, *v|
(if Regexp === k
kn = nil
k === key
@@ -234,7 +242,16 @@ class OptionParser
v << k if v.empty?
candidates << [k, v, kn]
end
- candidates = candidates.sort_by {|k, v, kn| kn.size}
+ candidates
+ end
+
+ def candidate(key, icase = false, pat = nil)
+ Completion.candidate(key, icase, pat, &method(:each))
+ end
+
+ public
+ def complete(key, icase = false, pat = nil)
+ candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size}
if candidates.size == 1
canon, sw, * = candidates[0]
elsif candidates.size > 1
@@ -717,9 +734,17 @@ class OptionParser
# --help
# Shows option summary.
#
+ # --help=complete=WORD
+ # Shows candidates for command line completion.
+ #
Officious['help'] = proc do |parser|
- Switch::NoArgument.new do
- puts parser.help
+ Switch::OptionalArgument.new do |arg|
+ case arg
+ when /\Acomplete=(.*)/
+ puts parser.candidate($1)
+ else
+ puts parser.help
+ end
exit
end
end
@@ -1461,6 +1486,35 @@ class OptionParser
end
private :complete
+ def candidate(word)
+ list = []
+ case word
+ when /\A--/
+ word, arg = word.split(/=/, 2)
+ argpat = Completion.regexp(arg, false) if arg and !arg.empty?
+ long = true
+ when /\A-(!-)/
+ short = true
+ when /\A-/
+ long = short = true
+ end
+ pat = Completion.regexp(word, true)
+ visit(:each_option) do |opt|
+ opts = [*(opt.long if long), *(opt.short if short)]
+ opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat
+ if /\A=/ =~ opt.arg
+ opts = opts.map {|sw| sw + "="}
+ if arg and CompletingHash === opt.pattern
+ if opts = opt.pattern.candidate(arg, false, argpat)
+ opts.map!(&:last)
+ end
+ end
+ end
+ list.concat(opts)
+ end
+ list
+ end
+
#
# Loads options from file names as +filename+. Does nothing when the file
# is not present. Returns whether successfully loaded.
@@ -1818,6 +1872,6 @@ ARGV.extend(OptionParser::Arguable)
if $0 == __FILE__
Version = OptionParser::Version
ARGV.options {|q|
- q.parse!.empty? or puts "what's #{ARGV.join(' ')}?"
+ q.parse!.empty? or print "what's #{ARGV.join(' ')}?\n"
} or abort(ARGV.options.to_s)
end
diff --git a/misc/rb_optparse.bash b/misc/rb_optparse.bash
new file mode 100644
index 0000000000..5022442c94
--- /dev/null
+++ b/misc/rb_optparse.bash
@@ -0,0 +1,20 @@
+#! /bin/bash
+# Completion for bash:
+#
+# (1) install this file,
+#
+# (2) load the script, and
+# . ~/.profile.d/rb_optparse.bash
+#
+# (3) define completions in your .bashrc,
+# rb_optparse command_using_optparse_1
+# rb_optparse command_using_optparse_2
+
+_rb_optparse() {
+ COMPREPLY=($("${COMP_WORDS[0]}" --help=complete="${COMP_WORDS[COMP_CWORD]}"))
+ return 0
+}
+
+rb_optparse () {
+ [ $# = 0 ] || complete -o default -F _rb_optparse "$@"
+}
diff --git a/test/optparse/test_bash_completion.rb b/test/optparse/test_bash_completion.rb
new file mode 100644
index 0000000000..baeb6d9882
--- /dev/null
+++ b/test/optparse/test_bash_completion.rb
@@ -0,0 +1,42 @@
+require 'test/unit'
+require 'optparse'
+
+class TestOptionParser < Test::Unit::TestCase
+end
+class TestOptionParser::BashCompletion < Test::Unit::TestCase
+ def setup
+ @opt = OptionParser.new
+ @opt.define("-z", "zzz") {}
+ @opt.define("--foo") {}
+ @opt.define("--bar=BAR") {}
+ @opt.define("--for=TYPE", [:hello, :help, :zot]) {}
+ end
+
+ def test_empty
+ assert_equal([], @opt.candidate(""))
+ end
+
+ def test_one_hyphen
+ assert_equal(%w[-z --foo --bar= --for=], @opt.candidate("-"))
+ end
+
+ def test_two_hyphen
+ assert_equal(%w[--foo --bar= --for=], @opt.candidate("--"))
+ end
+
+ def test_long_f
+ assert_equal(%w[--foo --for=], @opt.candidate("--f"))
+ end
+
+ def test_long_for_option
+ assert_equal(%w[--for=], @opt.candidate("--for"))
+ end
+
+ def test_long_for_option_args
+ assert_equal(%w[hello help zot], @opt.candidate("--for="))
+ end
+
+ def test_long_for_option_complete
+ assert_equal(%w[hello help], @opt.candidate("--for=h"))
+ end
+end
diff --git a/test/optparse/test_getopts.rb b/test/optparse/test_getopts.rb
index 1ba194ace1..ae22f68184 100644
--- a/test/optparse/test_getopts.rb
+++ b/test/optparse/test_getopts.rb
@@ -1,7 +1,9 @@
require 'test/unit'
require 'optparse'
-class TestOptionParserGetopts < Test::Unit::TestCase
+class TestOptionParser < Test::Unit::TestCase
+end
+class TestOptionParser::Getopts < Test::Unit::TestCase
def setup
@opt = OptionParser.new
end