rvc 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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