rvc 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/rvc +46 -70
- data/devel/test-dependencies.sh +4 -0
- data/lib/rvc.rb +5 -6
- data/lib/rvc/command.rb +65 -0
- data/lib/rvc/command_slate.rb +112 -0
- data/lib/rvc/completion.rb +89 -58
- data/lib/rvc/connection.rb +48 -0
- data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +1 -1
- data/lib/rvc/extensions/DistributedVirtualSwitch.rb +3 -3
- data/lib/rvc/extensions/HostSystem.rb +90 -0
- data/lib/rvc/extensions/VirtualMachine.rb +37 -7
- data/lib/rvc/field.rb +59 -12
- data/lib/rvc/fs.rb +34 -4
- data/lib/rvc/inventory.rb +5 -1
- data/lib/rvc/modules/alarm.rb +2 -0
- data/lib/rvc/modules/basic.rb +66 -61
- data/lib/rvc/modules/cluster.rb +117 -22
- data/lib/rvc/modules/connection.rb +40 -0
- data/lib/rvc/modules/core.rb +4 -16
- data/lib/rvc/modules/datacenter.rb +2 -0
- data/lib/rvc/modules/datastore.rb +11 -78
- data/lib/rvc/modules/device.rb +40 -5
- data/lib/rvc/modules/diagnostics.rb +169 -0
- data/lib/rvc/modules/esxcli.rb +9 -5
- data/lib/rvc/modules/find.rb +5 -3
- data/lib/rvc/modules/host.rb +46 -3
- data/lib/rvc/modules/issue.rb +2 -0
- data/lib/rvc/modules/mark.rb +5 -3
- data/lib/rvc/modules/perf.rb +99 -33
- data/lib/rvc/modules/permissions.rb +2 -0
- data/lib/rvc/modules/resource_pool.rb +2 -0
- data/lib/rvc/modules/role.rb +3 -1
- data/lib/rvc/modules/snapshot.rb +12 -4
- data/lib/rvc/modules/statsinterval.rb +13 -11
- data/lib/rvc/modules/vds.rb +67 -10
- data/lib/rvc/modules/vim.rb +19 -53
- data/lib/rvc/modules/vm.rb +27 -6
- data/lib/rvc/modules/vm_guest.rb +490 -0
- data/lib/rvc/modules/vmrc.rb +60 -32
- data/lib/rvc/modules/vnc.rb +2 -0
- data/lib/rvc/namespace.rb +114 -0
- data/lib/rvc/option_parser.rb +12 -15
- data/lib/rvc/readline-ffi.rb +4 -1
- data/lib/rvc/ruby_evaluator.rb +84 -0
- data/lib/rvc/shell.rb +68 -83
- data/lib/rvc/uri_parser.rb +59 -0
- data/lib/rvc/util.rb +134 -29
- data/lib/rvc/{extensions/PerfCounterInfo.rb → version.rb} +2 -4
- data/lib/rvc/{memory_session.rb → vim.rb} +10 -32
- data/test/modules/foo.rb +9 -0
- data/test/modules/foo/bar.rb +9 -0
- data/test/test_completion.rb +17 -0
- data/test/test_fs.rb +9 -11
- data/test/test_help.rb +46 -0
- data/test/test_helper.rb +12 -0
- data/test/test_metric.rb +1 -2
- data/test/test_modules.rb +38 -0
- data/test/test_parse_path.rb +1 -2
- data/test/test_shell.rb +138 -0
- data/test/test_uri.rb +34 -0
- metadata +115 -81
- data/lib/rvc/extensions/PerformanceManager.rb +0 -83
- data/lib/rvc/filesystem_session.rb +0 -101
- data/lib/rvc/modules.rb +0 -138
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ begin
|
|
7
7
|
gem.email = "rlane@vmware.com"
|
8
8
|
#gem.homepage = ""
|
9
9
|
gem.authors = ["Rich Lane"]
|
10
|
-
gem.add_dependency 'rbvmomi', '>= 1.
|
10
|
+
gem.add_dependency 'rbvmomi', '>= 1.6.0'
|
11
11
|
gem.add_dependency 'trollop', '>= 1.16.2'
|
12
12
|
gem.add_dependency 'backports', '>= 1.18.2'
|
13
13
|
gem.add_dependency 'highline', '>= 1.6.1'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.7.0
|
data/bin/rvc
CHANGED
@@ -18,25 +18,21 @@
|
|
18
18
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
20
|
# THE SOFTWARE.
|
21
|
+
require 'rvc'
|
22
|
+
require 'rvc/version'
|
23
|
+
require 'rvc/uri_parser'
|
21
24
|
require 'readline'
|
22
25
|
require "highline/import"
|
23
26
|
require 'pp'
|
24
|
-
require 'trollop'
|
25
|
-
require 'rbvmomi'
|
26
|
-
require 'rbvmomi/trollop'
|
27
27
|
require 'shellwords'
|
28
28
|
require 'yaml'
|
29
29
|
require 'backports'
|
30
|
-
require 'rvc'
|
31
30
|
require 'tempfile'
|
32
|
-
|
33
|
-
|
31
|
+
require 'trollop'
|
32
|
+
require 'rbvmomi/trollop'
|
34
33
|
|
35
34
|
Thread.abort_on_exception = true
|
36
35
|
|
37
|
-
CMD = Module.new
|
38
|
-
RVC::VERSION = File.read(File.join(File.dirname(__FILE__), '..', 'VERSION'))
|
39
|
-
|
40
36
|
HighLine.use_color = ENV['TERM'] != nil
|
41
37
|
|
42
38
|
$opts = Trollop.options do
|
@@ -55,68 +51,44 @@ EOS
|
|
55
51
|
opt :create_directory, "Create the initial directory if it doesn't exist", :short => :none
|
56
52
|
opt :cmd, "command to evaluate", :short => 'c', :multi => true, :type => :string
|
57
53
|
opt :script, "file to execute", :short => 's', :type => :string
|
54
|
+
opt :script_args, "arguments to script", :short => :none, :default => "", :type => :string
|
58
55
|
opt :cookie, "authentication cookie file", :short => 'k', :type => :string
|
56
|
+
opt :quiet, "silence unnecessary output", :short => 'q', :default => false, :type => :boolean
|
59
57
|
end
|
60
58
|
|
61
59
|
$interactive = $opts[:script].nil? and $stdin.tty?
|
62
60
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
RVC::FilesystemSession.new ENV['RVC_SESSION']
|
67
|
-
else
|
68
|
-
RVC::MemorySession.new
|
69
|
-
end
|
70
|
-
|
71
|
-
$shell = RVC::Shell.new session
|
61
|
+
# TODO remove $shell when all references are gone
|
62
|
+
$shell = shell = RVC::Shell.new
|
63
|
+
shell.reload_modules false
|
72
64
|
|
73
65
|
# Prompt for hostname if none given. Useful on win32.
|
74
|
-
if
|
66
|
+
if ARGV.empty? and $interactive
|
75
67
|
ARGV << Readline.readline("Host to connect to (user@host): ")
|
76
68
|
end
|
77
69
|
|
78
|
-
|
79
|
-
|
80
|
-
uri = "#{conn['username']}@#{conn['host']}"
|
70
|
+
# TODO cookies, cert digests
|
71
|
+
ARGV.each do |str|
|
81
72
|
begin
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
puts "Failed to connect to #{conn['host']}: #{$!.message}"
|
73
|
+
uri = RVC::URIParser.parse str
|
74
|
+
rescue
|
75
|
+
puts "Failed to parse URI #{str.inspect}: #{$!.message}"
|
86
76
|
exit 1
|
87
77
|
end
|
88
|
-
end
|
89
|
-
|
90
|
-
cookies = {}
|
91
|
-
certdigests = {}
|
92
|
-
if $opts[:cookie]
|
93
|
-
File.readlines($opts[:cookie]).each do |info|
|
94
|
-
host, cookie, certdigest = info.chomp.split('|')
|
95
|
-
cookies[host] = cookie
|
96
|
-
certdigests[host] = certdigest
|
97
|
-
end
|
98
|
-
end
|
99
78
|
|
100
|
-
ARGV.each do |uri|
|
101
|
-
# get hostname from uri to display in "connecting to..." message
|
102
|
-
hostinfo = uri.split("@")
|
103
|
-
host = if hostinfo.length > 1
|
104
|
-
hostinfo[1].split(":")[0]
|
105
|
-
else
|
106
|
-
hostinfo[0].split(":")[0]
|
107
|
-
end
|
108
79
|
begin
|
109
|
-
puts "Connecting to #{host}..." if
|
110
|
-
|
111
|
-
|
80
|
+
puts "Connecting to #{uri.host}..." if ARGV.size > 1
|
81
|
+
scheme = uri.scheme || 'vim'
|
82
|
+
scheme_handler = RVC::SCHEMES[scheme] or RVC::Util.err "invalid scheme #{scheme.inspect}"
|
83
|
+
scheme_handler[uri]
|
112
84
|
rescue RVC::Util::UserError
|
113
|
-
puts "Failed to connect to #{host}: #{$!.message}"
|
85
|
+
puts "Failed to connect to #{uri.host}: #{$!.message}"
|
114
86
|
exit 1
|
115
87
|
end
|
116
88
|
end
|
117
89
|
|
118
90
|
if $interactive
|
119
|
-
|
91
|
+
shell.completion.install
|
120
92
|
history_fn = "#{ENV['HOME']}/.rvc-history"
|
121
93
|
IO.foreach(history_fn) { |l| Readline::HISTORY << l.chomp } rescue puts "Welcome to RVC. Try the 'help' command."
|
122
94
|
begin
|
@@ -128,19 +100,20 @@ end
|
|
128
100
|
|
129
101
|
if $opts[:path]
|
130
102
|
begin
|
131
|
-
|
103
|
+
shell.cmds.basic.cd shell.fs.lookup_single($opts[:path])
|
132
104
|
rescue RVC::Util::UserError
|
133
105
|
raise unless $opts[:create_directory]
|
134
106
|
parent_path = File.dirname($opts[:path])
|
135
|
-
|
136
|
-
|
107
|
+
shell.fs.lookup_single(parent_path).CreateFolder(:name => File.basename($opts[:path]))
|
108
|
+
shell.cmds.basic.cd shell.fs.lookup_single($opts[:path])
|
137
109
|
end
|
138
|
-
elsif
|
139
|
-
|
110
|
+
elsif shell.connections.size == 1 and $interactive and
|
111
|
+
shell.connections.first[1].is_a? RbVmomi::VIM # HACK
|
112
|
+
conn_name, conn = shell.connections.first
|
140
113
|
if conn.serviceContent.about.apiType == 'VirtualCenter'
|
141
|
-
|
114
|
+
shell.cmds.basic.cd shell.fs.lookup_single(conn_name)
|
142
115
|
else
|
143
|
-
|
116
|
+
shell.cmds.basic.cd shell.fs.lookup_single("#{conn_name}/ha-datacenter/vm")
|
144
117
|
end
|
145
118
|
end
|
146
119
|
|
@@ -151,43 +124,46 @@ if defined? Ocra
|
|
151
124
|
end
|
152
125
|
|
153
126
|
if $interactive
|
154
|
-
if ENV['DISPLAY']
|
155
|
-
$stderr.puts "VMRC is not installed. You will be unable to view virtual machine consoles. Use the vmrc.install command to install it." unless
|
127
|
+
if ENV['DISPLAY'] and !$opts[:quiet]
|
128
|
+
$stderr.puts "VMRC is not installed. You will be unable to view virtual machine consoles. Use the vmrc.install command to install it." unless shell.cmds.vmrc.slate.check_installed
|
156
129
|
end
|
157
|
-
$stderr.puts "Use the 'connect' command to connect to an ESX or VC server." if
|
158
|
-
|
130
|
+
$stderr.puts "Use the 'connect' command to connect to an ESX or VC server." if shell.connections.empty?
|
131
|
+
shell.cmds.basic.ls shell.fs.lookup_single('.') unless $opts[:quiet]
|
159
132
|
end
|
160
133
|
|
161
|
-
|
162
|
-
|
163
|
-
|
134
|
+
if Signal.list["CHLD"]
|
135
|
+
trap "SIGCHLD" do |sig|
|
136
|
+
begin
|
137
|
+
while pid = Process.wait(-1, Process::WNOHANG)
|
138
|
+
end
|
139
|
+
rescue Errno::ECHILD
|
164
140
|
end
|
165
|
-
rescue Errno::ECHILD
|
166
141
|
end
|
167
142
|
end
|
168
143
|
|
169
144
|
if $opts[:script]
|
170
|
-
|
145
|
+
ARGV.replace(Shellwords.shellwords($opts[:script_args]))
|
146
|
+
shell.eval_ruby File.read($opts[:script]), $opts[:script]
|
171
147
|
else
|
172
148
|
while true
|
173
149
|
begin
|
174
150
|
begin
|
175
|
-
input = $opts[:cmd].shift || Readline.readline(
|
151
|
+
input = $opts[:cmd].shift || Readline.readline(shell.prompt, false) or break
|
176
152
|
rescue
|
177
153
|
puts "\n#{$!.class} during readline: #{$!.message}"
|
178
|
-
$!.backtrace.each { |x| puts x } if
|
154
|
+
$!.backtrace.each { |x| puts x } if shell.debug
|
179
155
|
next
|
180
156
|
end
|
181
157
|
input = input.strip
|
182
158
|
next if input.empty?
|
183
159
|
(history && history.puts(input); Readline::HISTORY << input) unless input == Readline::HISTORY.to_a[-1]
|
184
|
-
|
160
|
+
shell.eval_input input
|
185
161
|
rescue Interrupt
|
186
162
|
puts
|
187
163
|
end
|
188
164
|
end
|
189
165
|
end
|
190
166
|
|
191
|
-
|
167
|
+
shell.connections.each do |name, conn|
|
192
168
|
conn.close if conn.respond_to? :close rescue puts("failed to close connection #{name.inspect}: #{$!.class}: #{$!.message}")
|
193
169
|
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
find lib -maxdepth 2 -name '*.rb' -exec ruby -Ilib -e "puts ARGV[0]; load ARGV[0]" {} \;
|
3
|
+
find lib/rvc/extensions -name '*.rb' -exec ruby -rrbvmomi -rrvc/vim -rrvc/util -Ilib -e 'x = File.basename(ARGV[0])[0...-3]; puts x; VIM.const_get(x)' {} \;
|
4
|
+
find lib/rvc/modules -name '*.rb' -exec ruby -rrbvmomi -rrvc/vim -rrvc/util -Ilib -e 'include RVC::Util; x = ARGV[0]; puts x; def rvc_alias(a,b=nil); end; def opts(s); end; def rvc_completor(a); end; def raw_opts(a,b); end; load(x)' {} \;
|
data/lib/rvc.rb
CHANGED
@@ -18,16 +18,15 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
|
21
|
+
module RVC
|
22
|
+
SCHEMES = {}
|
23
|
+
end
|
24
|
+
|
22
25
|
require 'rvc/inventory'
|
23
|
-
require 'rvc/
|
26
|
+
require 'rvc/namespace'
|
24
27
|
require 'rvc/util'
|
25
28
|
require 'rvc/path'
|
26
29
|
require 'rvc/fs'
|
27
30
|
require 'rvc/completion'
|
28
31
|
require 'rvc/option_parser'
|
29
32
|
require 'rvc/shell'
|
30
|
-
require 'rvc/memory_session'
|
31
|
-
require 'rvc/filesystem_session'
|
32
|
-
|
33
|
-
RbVmomi::VIM.extension_dirs << File.join(File.dirname(__FILE__), "rvc/extensions")
|
data/lib/rvc/command.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Copyright (c) 2011 VMware, Inc. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'rvc/option_parser'
|
22
|
+
|
23
|
+
module RVC
|
24
|
+
|
25
|
+
class Command
|
26
|
+
attr_reader :ns, :name, :summary, :parser
|
27
|
+
attr_accessor :completor
|
28
|
+
|
29
|
+
def initialize ns, name, summary, parser
|
30
|
+
@ns = ns
|
31
|
+
@name = name
|
32
|
+
@summary = summary
|
33
|
+
@parser = parser
|
34
|
+
@completor = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"#<RVC::Command:#{name}>"
|
39
|
+
end
|
40
|
+
|
41
|
+
def invoke *args
|
42
|
+
@ns.slate.send @name, *args
|
43
|
+
end
|
44
|
+
|
45
|
+
def complete word, args
|
46
|
+
if @completor
|
47
|
+
candidates = @completor.call word, args
|
48
|
+
prefix_regex = /^#{Regexp.escape word}/
|
49
|
+
candidates.select { |x,a| x =~ prefix_regex }
|
50
|
+
else
|
51
|
+
return @ns.shell.completion.fs_candidates(word) +
|
52
|
+
long_option_candidates(word)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def long_option_candidates word
|
57
|
+
return [] unless parser.is_a? RVC::OptionParser
|
58
|
+
prefix_regex = /^#{Regexp.escape(word)}/
|
59
|
+
parser.specs.map { |k,v| "--#{v[:long]}" }.
|
60
|
+
grep(prefix_regex).sort.
|
61
|
+
map { |x| [x, ' '] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# Copyright (c) 2011-2012 VMware, Inc. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'rvc/command'
|
22
|
+
require 'rvc/option_parser'
|
23
|
+
require 'rvc/util'
|
24
|
+
|
25
|
+
module RVC
|
26
|
+
|
27
|
+
# Execution environment for command definitions
|
28
|
+
class CommandSlate
|
29
|
+
include RVC::Util
|
30
|
+
|
31
|
+
def initialize ns
|
32
|
+
@ns = ns
|
33
|
+
end
|
34
|
+
|
35
|
+
# Command definition functions
|
36
|
+
|
37
|
+
def opts name, &b
|
38
|
+
fail "command name must be a symbol" unless name.is_a? Symbol
|
39
|
+
|
40
|
+
if name.to_s =~ /[A-Z]/
|
41
|
+
fail "Camel-casing is not allowed (#{name})"
|
42
|
+
end
|
43
|
+
|
44
|
+
parser = OptionParser.new name.to_s, @ns.shell.fs, &b
|
45
|
+
summary = parser.summary?
|
46
|
+
|
47
|
+
parser.specs.each do |opt_name,spec|
|
48
|
+
if opt_name.to_s =~ /[A-Z]/
|
49
|
+
fail "Camel-casing is not allowed (#{name} option #{opt_name})"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@ns.commands[name] = Command.new @ns, name, summary, parser
|
54
|
+
end
|
55
|
+
|
56
|
+
def raw_opts name, summary
|
57
|
+
fail "command name must be a symbol" unless name.is_a? Symbol
|
58
|
+
|
59
|
+
if name.to_s =~ /[A-Z]/
|
60
|
+
fail "Camel-casing is not allowed (#{name})"
|
61
|
+
end
|
62
|
+
|
63
|
+
parser = RawOptionParser.new name.to_s
|
64
|
+
|
65
|
+
@ns.commands[name] = Command.new @ns, name, summary, parser
|
66
|
+
end
|
67
|
+
|
68
|
+
def rvc_completor name, &b
|
69
|
+
fail "command name must be a symbol" unless name.is_a? Symbol
|
70
|
+
cmd = @ns.commands[name] or fail "command #{name} not defined"
|
71
|
+
cmd.completor = b
|
72
|
+
end
|
73
|
+
|
74
|
+
def rvc_alias name, target=nil
|
75
|
+
fail "command name must be a symbol" unless name.is_a? Symbol
|
76
|
+
target ||= name
|
77
|
+
|
78
|
+
cmdpath = [name]
|
79
|
+
cur = @ns
|
80
|
+
while cur.parent
|
81
|
+
cmdpath << cur.name
|
82
|
+
cur = cur.parent
|
83
|
+
end
|
84
|
+
cmdpath.reverse!
|
85
|
+
|
86
|
+
shell.cmds.aliases[target] = cmdpath
|
87
|
+
end
|
88
|
+
|
89
|
+
# Utility functions
|
90
|
+
|
91
|
+
def shell
|
92
|
+
@ns.shell
|
93
|
+
end
|
94
|
+
|
95
|
+
def lookup path
|
96
|
+
shell.fs.lookup path
|
97
|
+
end
|
98
|
+
|
99
|
+
def lookup_single path
|
100
|
+
shell.fs.lookup_single path
|
101
|
+
end
|
102
|
+
|
103
|
+
def lookup! path, types
|
104
|
+
shell.fs.lookup! path, types
|
105
|
+
end
|
106
|
+
|
107
|
+
def lookup_single! path, types
|
108
|
+
shell.fs.lookup_single! path, types
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
data/lib/rvc/completion.rb
CHANGED
@@ -35,24 +35,19 @@ if not defined? RbReadline
|
|
35
35
|
end
|
36
36
|
|
37
37
|
module RVC
|
38
|
-
module Completion
|
39
|
-
Cache = TTLCache.new 10
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
append_char, candidates = RVC::Completion.complete word, line
|
46
|
-
Readline.completion_append_character = append_char
|
47
|
-
candidates
|
48
|
-
rescue RVC::Util::UserError
|
49
|
-
puts
|
50
|
-
puts $!.message
|
51
|
-
Readline.refresh_line
|
52
|
-
end
|
39
|
+
class Completion
|
40
|
+
def initialize shell
|
41
|
+
@shell = shell
|
42
|
+
@cache = TTLCache.new 10
|
53
43
|
end
|
54
44
|
|
55
|
-
|
45
|
+
# Halt infinite loop when printing exceptions
|
46
|
+
def inspect
|
47
|
+
""
|
48
|
+
end
|
49
|
+
|
50
|
+
def install
|
56
51
|
if Readline.respond_to? :char_is_quoted=
|
57
52
|
Readline.completer_word_break_characters = " \t\n\"'"
|
58
53
|
Readline.completer_quote_characters = "\"\\"
|
@@ -60,35 +55,62 @@ module Completion
|
|
60
55
|
Readline.char_is_quoted = is_quoted
|
61
56
|
end
|
62
57
|
|
63
|
-
Readline.completion_proc =
|
58
|
+
Readline.completion_proc = lambda { |word| completor(word) }
|
64
59
|
end
|
65
60
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
61
|
+
def completor word
|
62
|
+
return unless word
|
63
|
+
begin
|
64
|
+
line = Readline.line_buffer if Readline.respond_to? :line_buffer
|
65
|
+
point = Readline.point if Readline.respond_to? :point
|
66
|
+
append_char, candidates = complete word, line, point
|
67
|
+
Readline.completion_append_character = append_char
|
68
|
+
candidates
|
69
|
+
rescue RVC::Util::UserError
|
70
|
+
puts
|
71
|
+
puts $!.message
|
72
|
+
Readline.refresh_line
|
73
|
+
rescue
|
74
|
+
puts
|
75
|
+
puts "#{$!.class}: #{$!.message}"
|
76
|
+
$!.backtrace.each do |x|
|
77
|
+
puts x
|
78
|
+
end
|
79
|
+
Readline.refresh_line
|
76
80
|
end
|
81
|
+
end
|
77
82
|
|
83
|
+
def complete word, line, point
|
78
84
|
candidates = []
|
79
85
|
|
80
|
-
if
|
81
|
-
|
82
|
-
|
86
|
+
if line and point
|
87
|
+
# Full completion capabilities
|
88
|
+
line = line[0...point]
|
89
|
+
first_whitespace_index = line.index(' ')
|
83
90
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
candidates.concat RVC::complete_for_cmd(line, word)
|
91
|
+
if !first_whitespace_index
|
92
|
+
# Command
|
93
|
+
candidates.concat cmd_candidates(word)
|
88
94
|
else
|
89
|
-
|
90
|
-
|
95
|
+
# Arguments
|
96
|
+
begin
|
97
|
+
cmdpath, args = Shell.parse_input line
|
98
|
+
rescue ArgumentError
|
99
|
+
# Unmatched double quote
|
100
|
+
cmdpath, args = Shell.parse_input(line+'"')
|
101
|
+
end
|
102
|
+
|
103
|
+
if cmd = @shell.cmds.lookup(cmdpath)
|
104
|
+
args << word if word == ''
|
105
|
+
candidates.concat cmd.complete(word, args)
|
106
|
+
else
|
107
|
+
candidates.concat fs_candidates(word)
|
108
|
+
end
|
91
109
|
end
|
110
|
+
else
|
111
|
+
# Limited completion
|
112
|
+
candidates.concat cmd_candidates(word)
|
113
|
+
candidates.concat fs_candidates(word)
|
92
114
|
end
|
93
115
|
|
94
116
|
if candidates.size == 1
|
@@ -100,40 +122,49 @@ module Completion
|
|
100
122
|
return append_char, candidates.map(&:first)
|
101
123
|
end
|
102
124
|
|
103
|
-
def
|
125
|
+
def fs_candidates word
|
104
126
|
child_candidates(word) + mark_candidates(word)
|
105
127
|
end
|
106
128
|
|
107
|
-
def
|
129
|
+
def cmd_candidates word
|
130
|
+
cmdpath = word.split '.'
|
131
|
+
cmdpath << '' if cmdpath.empty? or word[-1..-1] == '.'
|
132
|
+
prefix_regex = /^#{Regexp.escape(cmdpath[-1])}/
|
133
|
+
|
134
|
+
ns = @shell.cmds.lookup(cmdpath[0...-1].map(&:to_sym), RVC::Namespace)
|
135
|
+
return [] unless ns
|
136
|
+
|
137
|
+
cmdpath_prefix = cmdpath[0...-1].join('.')
|
138
|
+
cmdpath_prefix << '.' unless cmdpath_prefix.empty?
|
139
|
+
|
108
140
|
ret = []
|
109
|
-
|
110
|
-
|
111
|
-
|
141
|
+
|
142
|
+
ns.commands.each do |cmd_name,cmd|
|
143
|
+
ret << ["#{cmdpath_prefix}#{cmd_name}", ' '] if cmd_name.to_s =~ prefix_regex
|
144
|
+
end
|
145
|
+
|
146
|
+
ns.namespaces.each do |ns_name,ns|
|
147
|
+
ret << ["#{cmdpath_prefix}#{ns_name}.", ''] if ns_name.to_s =~ prefix_regex
|
148
|
+
end
|
149
|
+
|
150
|
+
# Aliases
|
151
|
+
if ns == @shell.cmds then
|
152
|
+
ret.concat @shell.cmds.aliases.keys.map(&:to_s).grep(prefix_regex).map { |x| [x, ' '] }
|
112
153
|
end
|
113
|
-
ret.concat ALIASES.keys
|
114
|
-
ret.grep(prefix_regex).sort.
|
115
|
-
map { |x| [x, ' '] }
|
116
|
-
end
|
117
154
|
|
118
|
-
|
119
|
-
|
120
|
-
parser = mod.opts_for cmd
|
121
|
-
return [] unless parser.is_a? RVC::OptionParser
|
122
|
-
prefix_regex = /^#{Regexp.escape(word)}/
|
123
|
-
parser.specs.map { |k,v| "--#{v[:long]}" }.
|
124
|
-
grep(prefix_regex).sort.
|
125
|
-
map { |x| [x, ' '] }
|
155
|
+
ret.sort_by! { |a,b| a }
|
156
|
+
ret
|
126
157
|
end
|
127
158
|
|
128
159
|
# TODO convert to globbing
|
129
|
-
def
|
160
|
+
def child_candidates word
|
130
161
|
arcs, absolute, trailing_slash = Path.parse word
|
131
162
|
last = trailing_slash ? '' : (arcs.pop || '')
|
132
163
|
arcs.map! { |x| x.gsub '\\', '' }
|
133
|
-
base = absolute ?
|
134
|
-
cur =
|
164
|
+
base = absolute ? @shell.fs.root : @shell.fs.cur
|
165
|
+
cur = @shell.fs.traverse(base, arcs).first or return []
|
135
166
|
arcs.unshift '' if absolute
|
136
|
-
children =
|
167
|
+
children = @cache[cur, :children] rescue []
|
137
168
|
children.
|
138
169
|
select { |k,v| k.gsub(' ', '\\ ') =~ /^#{Regexp.escape(last)}/ }.
|
139
170
|
map { |k,v| (arcs+[k])*'/' }.
|
@@ -141,10 +172,10 @@ module Completion
|
|
141
172
|
map { |x| [x, '/'] }
|
142
173
|
end
|
143
174
|
|
144
|
-
def
|
175
|
+
def mark_candidates word
|
145
176
|
return [] unless word.empty? || word[0..0] == '~'
|
146
177
|
prefix_regex = /^#{Regexp.escape(word[1..-1] || '')}/
|
147
|
-
|
178
|
+
@shell.fs.marks.keys.grep(prefix_regex).sort.
|
148
179
|
map { |x| ["~#{x}", '/'] }
|
149
180
|
end
|
150
181
|
end
|