Skip to content

Commit

Permalink
* lib/optparse.rb: shell completion support for bash.
Browse files Browse the repository at this point in the history
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@29832 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
  • Loading branch information
nobu committed Nov 19, 2010
1 parent 3c1f696 commit 644f044
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 9 deletions.
70 changes: 62 additions & 8 deletions lib/optparse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -234,7 +242,16 @@ def complete(key, icase = false, pat = nil)
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
Expand Down Expand Up @@ -717,9 +734,17 @@ def match(key)
# --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
Expand Down Expand Up @@ -1461,6 +1486,35 @@ def complete(typ, opt, icase = false, *pat)
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.
Expand Down Expand Up @@ -1818,6 +1872,6 @@ module Acceptables
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
20 changes: 20 additions & 0 deletions misc/rb_optparse.bash
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions test/optparse/test_bash_completion.rb
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion test/optparse/test_getopts.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 644f044

Please sign in to comment.