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.
Files changed (66) hide show
  1. data/Rakefile +1 -1
  2. data/VERSION +1 -1
  3. data/bin/rvc +46 -70
  4. data/devel/test-dependencies.sh +4 -0
  5. data/lib/rvc.rb +5 -6
  6. data/lib/rvc/command.rb +65 -0
  7. data/lib/rvc/command_slate.rb +112 -0
  8. data/lib/rvc/completion.rb +89 -58
  9. data/lib/rvc/connection.rb +48 -0
  10. data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +1 -1
  11. data/lib/rvc/extensions/DistributedVirtualSwitch.rb +3 -3
  12. data/lib/rvc/extensions/HostSystem.rb +90 -0
  13. data/lib/rvc/extensions/VirtualMachine.rb +37 -7
  14. data/lib/rvc/field.rb +59 -12
  15. data/lib/rvc/fs.rb +34 -4
  16. data/lib/rvc/inventory.rb +5 -1
  17. data/lib/rvc/modules/alarm.rb +2 -0
  18. data/lib/rvc/modules/basic.rb +66 -61
  19. data/lib/rvc/modules/cluster.rb +117 -22
  20. data/lib/rvc/modules/connection.rb +40 -0
  21. data/lib/rvc/modules/core.rb +4 -16
  22. data/lib/rvc/modules/datacenter.rb +2 -0
  23. data/lib/rvc/modules/datastore.rb +11 -78
  24. data/lib/rvc/modules/device.rb +40 -5
  25. data/lib/rvc/modules/diagnostics.rb +169 -0
  26. data/lib/rvc/modules/esxcli.rb +9 -5
  27. data/lib/rvc/modules/find.rb +5 -3
  28. data/lib/rvc/modules/host.rb +46 -3
  29. data/lib/rvc/modules/issue.rb +2 -0
  30. data/lib/rvc/modules/mark.rb +5 -3
  31. data/lib/rvc/modules/perf.rb +99 -33
  32. data/lib/rvc/modules/permissions.rb +2 -0
  33. data/lib/rvc/modules/resource_pool.rb +2 -0
  34. data/lib/rvc/modules/role.rb +3 -1
  35. data/lib/rvc/modules/snapshot.rb +12 -4
  36. data/lib/rvc/modules/statsinterval.rb +13 -11
  37. data/lib/rvc/modules/vds.rb +67 -10
  38. data/lib/rvc/modules/vim.rb +19 -53
  39. data/lib/rvc/modules/vm.rb +27 -6
  40. data/lib/rvc/modules/vm_guest.rb +490 -0
  41. data/lib/rvc/modules/vmrc.rb +60 -32
  42. data/lib/rvc/modules/vnc.rb +2 -0
  43. data/lib/rvc/namespace.rb +114 -0
  44. data/lib/rvc/option_parser.rb +12 -15
  45. data/lib/rvc/readline-ffi.rb +4 -1
  46. data/lib/rvc/ruby_evaluator.rb +84 -0
  47. data/lib/rvc/shell.rb +68 -83
  48. data/lib/rvc/uri_parser.rb +59 -0
  49. data/lib/rvc/util.rb +134 -29
  50. data/lib/rvc/{extensions/PerfCounterInfo.rb → version.rb} +2 -4
  51. data/lib/rvc/{memory_session.rb → vim.rb} +10 -32
  52. data/test/modules/foo.rb +9 -0
  53. data/test/modules/foo/bar.rb +9 -0
  54. data/test/test_completion.rb +17 -0
  55. data/test/test_fs.rb +9 -11
  56. data/test/test_help.rb +46 -0
  57. data/test/test_helper.rb +12 -0
  58. data/test/test_metric.rb +1 -2
  59. data/test/test_modules.rb +38 -0
  60. data/test/test_parse_path.rb +1 -2
  61. data/test/test_shell.rb +138 -0
  62. data/test/test_uri.rb +34 -0
  63. metadata +115 -81
  64. data/lib/rvc/extensions/PerformanceManager.rb +0 -83
  65. data/lib/rvc/filesystem_session.rb +0 -101
  66. 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.5.0'
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.6.0
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
- VIM = RbVmomi::VIM
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
- RVC.reload_modules false
64
-
65
- session = if ENV['RVC_SESSION'] and not ENV['RVC_SESSION'].empty?
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 $shell.session.connections.empty? and ARGV.empty? and $interactive
66
+ if ARGV.empty? and $interactive
75
67
  ARGV << Readline.readline("Host to connect to (user@host): ")
76
68
  end
77
69
 
78
- $shell.session.connections.each do |key|
79
- conn = $shell.session.get_connection(key) or fail "no such connection #{key.inspect}"
80
- uri = "#{conn['username']}@#{conn['host']}"
70
+ # TODO cookies, cert digests
71
+ ARGV.each do |str|
81
72
  begin
82
- puts "Connecting to #{conn['host']}..."
83
- CMD.vim.connect uri, {}
84
- rescue RVC::Util::UserError
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 (ARGV+$shell.session.connections).size > 1
110
- CMD.vim.connect uri, {:cookie => cookies[host],
111
- :certdigest => certdigests[host]}
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
- RVC::Completion.install
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
- CMD.basic.cd RVC::Util.lookup_single($opts[:path])
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
- RVC::Util.lookup_single(parent_path).CreateFolder(:name => File.basename($opts[:path]))
136
- CMD.basic.cd RVC::Util.lookup_single($opts[:path])
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 $shell.connections.size == 1 and $interactive
139
- conn_name, conn = $shell.connections.first
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
- CMD.basic.cd RVC::Util.lookup_single(conn_name)
114
+ shell.cmds.basic.cd shell.fs.lookup_single(conn_name)
142
115
  else
143
- CMD.basic.cd RVC::Util.lookup_single("#{conn_name}/ha-datacenter/vm")
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 CMD.vmrc.find_vmrc
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 $shell.connections.empty?
158
- CMD.basic.ls RVC::Util.lookup_single('.')
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
- trap "SIGCHLD" do |sig|
162
- begin
163
- while pid = Process.wait(-1, Process::WNOHANG)
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
- $shell.eval_ruby File.read($opts[:script]), $opts[:script]
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($shell.prompt, false) or break
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 $shell.debug
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
- $shell.eval_input input
160
+ shell.eval_input input
185
161
  rescue Interrupt
186
162
  puts
187
163
  end
188
164
  end
189
165
  end
190
166
 
191
- $shell.connections.each do |name, conn|
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
- require 'rbvmomi'
21
+ module RVC
22
+ SCHEMES = {}
23
+ end
24
+
22
25
  require 'rvc/inventory'
23
- require 'rvc/modules'
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")
@@ -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
@@ -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
- Completor = lambda do |word|
42
- return unless word
43
- begin
44
- line = Readline.line_buffer if Readline.respond_to? :line_buffer
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
- def self.install
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 = Completor
58
+ Readline.completion_proc = lambda { |word| completor(word) }
64
59
  end
65
60
 
66
- def self.complete word, line
67
- line ||= ''
68
- first_whitespace_index = line.index(' ')
69
-
70
- if Readline.respond_to? :point
71
- do_complete_cmd = !first_whitespace_index || first_whitespace_index >= Readline.point
72
- do_complete_args = !do_complete_cmd
73
- else
74
- do_complete_cmd = true
75
- do_complete_args = true
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 do_complete_cmd
81
- candidates.concat cmd_candidates(word)
82
- end
86
+ if line and point
87
+ # Full completion capabilities
88
+ line = line[0...point]
89
+ first_whitespace_index = line.index(' ')
83
90
 
84
- if do_complete_args
85
- mod, cmd, args = Shell.parse_input line
86
- if mod and mod.completor_for cmd
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
- candidates.concat(fs_candidates(word) +
90
- long_option_candidates(mod, cmd, word))
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 self.fs_candidates word
125
+ def fs_candidates word
104
126
  child_candidates(word) + mark_candidates(word)
105
127
  end
106
128
 
107
- def self.cmd_candidates word
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
- prefix_regex = /^#{Regexp.escape(word)}/
110
- MODULES.each do |name,m|
111
- m.commands.each { |s| ret << "#{name}.#{s}" }
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
- def self.long_option_candidates mod, cmd, word
119
- return [] unless mod and cmd
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 self.child_candidates word
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 ? $shell.fs.root : $shell.fs.cur
134
- cur = $shell.fs.traverse(base, arcs).first or return []
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 = Cache[cur, :children] rescue []
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 self.mark_candidates word
175
+ def mark_candidates word
145
176
  return [] unless word.empty? || word[0..0] == '~'
146
177
  prefix_regex = /^#{Regexp.escape(word[1..-1] || '')}/
147
- $shell.session.marks.grep(prefix_regex).sort.
178
+ @shell.fs.marks.keys.grep(prefix_regex).sort.
148
179
  map { |x| ["~#{x}", '/'] }
149
180
  end
150
181
  end