* lib/optparse.rb: shell completion support for bash.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@29832 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
3c1f696a50
commit
644f0445e8
@ -195,6 +195,11 @@
|
|||||||
# options = OptparseExample.parse(ARGV)
|
# options = OptparseExample.parse(ARGV)
|
||||||
# pp options
|
# pp options
|
||||||
#
|
#
|
||||||
|
# === Shell Completion
|
||||||
|
#
|
||||||
|
# For modern shells (e.g. bash, zsh, etc.), you can use shell
|
||||||
|
# completion for command line options.
|
||||||
|
#
|
||||||
# === Further documentation
|
# === Further documentation
|
||||||
#
|
#
|
||||||
# The above examples should be enough to learn how to use this class. If you
|
# 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.
|
# and resolved against a list of acceptable values.
|
||||||
#
|
#
|
||||||
module Completion
|
module Completion
|
||||||
def complete(key, icase = false, pat = nil)
|
def self.regexp(key, icase)
|
||||||
pat ||= Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'),
|
Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase)
|
||||||
icase)
|
end
|
||||||
|
|
||||||
|
def self.candidate(key, icase = false, pat = nil, &block)
|
||||||
|
pat ||= Completion.regexp(key, icase)
|
||||||
canon, sw, cn = nil
|
canon, sw, cn = nil
|
||||||
candidates = []
|
candidates = []
|
||||||
each do |k, *v|
|
block.call do |k, *v|
|
||||||
(if Regexp === k
|
(if Regexp === k
|
||||||
kn = nil
|
kn = nil
|
||||||
k === key
|
k === key
|
||||||
@ -234,7 +242,16 @@ class OptionParser
|
|||||||
v << k if v.empty?
|
v << k if v.empty?
|
||||||
candidates << [k, v, kn]
|
candidates << [k, v, kn]
|
||||||
end
|
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
|
if candidates.size == 1
|
||||||
canon, sw, * = candidates[0]
|
canon, sw, * = candidates[0]
|
||||||
elsif candidates.size > 1
|
elsif candidates.size > 1
|
||||||
@ -717,9 +734,17 @@ class OptionParser
|
|||||||
# --help
|
# --help
|
||||||
# Shows option summary.
|
# Shows option summary.
|
||||||
#
|
#
|
||||||
|
# --help=complete=WORD
|
||||||
|
# Shows candidates for command line completion.
|
||||||
|
#
|
||||||
Officious['help'] = proc do |parser|
|
Officious['help'] = proc do |parser|
|
||||||
Switch::NoArgument.new do
|
Switch::OptionalArgument.new do |arg|
|
||||||
puts parser.help
|
case arg
|
||||||
|
when /\Acomplete=(.*)/
|
||||||
|
puts parser.candidate($1)
|
||||||
|
else
|
||||||
|
puts parser.help
|
||||||
|
end
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1461,6 +1486,35 @@ class OptionParser
|
|||||||
end
|
end
|
||||||
private :complete
|
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
|
# Loads options from file names as +filename+. Does nothing when the file
|
||||||
# is not present. Returns whether successfully loaded.
|
# is not present. Returns whether successfully loaded.
|
||||||
@ -1818,6 +1872,6 @@ ARGV.extend(OptionParser::Arguable)
|
|||||||
if $0 == __FILE__
|
if $0 == __FILE__
|
||||||
Version = OptionParser::Version
|
Version = OptionParser::Version
|
||||||
ARGV.options {|q|
|
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)
|
} or abort(ARGV.options.to_s)
|
||||||
end
|
end
|
||||||
|
20
misc/rb_optparse.bash
Normal file
20
misc/rb_optparse.bash
Normal file
@ -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 "$@"
|
||||||
|
}
|
42
test/optparse/test_bash_completion.rb
Normal file
42
test/optparse/test_bash_completion.rb
Normal file
@ -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
|
@ -1,7 +1,9 @@
|
|||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
require 'optparse'
|
require 'optparse'
|
||||||
|
|
||||||
class TestOptionParserGetopts < Test::Unit::TestCase
|
class TestOptionParser < Test::Unit::TestCase
|
||||||
|
end
|
||||||
|
class TestOptionParser::Getopts < Test::Unit::TestCase
|
||||||
def setup
|
def setup
|
||||||
@opt = OptionParser.new
|
@opt = OptionParser.new
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user