rvc 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|