rubycom 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YmZkNDU1NjY2ZGU2MmM0ZGE4OTEyNTkwYTBkMzVhMjhiYWIzOWVhNA==
4
+ NDQwZWY1MzUyMTk1MGQ1OTc2YTEzYzNhOWRlNTZjZTkzMjU3MzliNw==
5
5
  data.tar.gz: !binary |-
6
- NWI4NWMxNjJiN2RhZTA2MWJiNjZhZjZiNTVhMmMzMTMwMWRkN2FkYQ==
6
+ OWVkOTI4MjY3ODhlODM1M2FhMTAxZTc2YjQxYTVkN2JmZDg2ZGE1Yg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MjI1NjA5YjZiMDdhNzQ3M2YzNjVmYTYwYWQyMjMxZmIzZDYwZmMyNWMwM2Jm
10
- MjAwNTQ3NDI0NjMzOWVjMGUzNDAyZjVjZDVjZmZkNzk3MjM5YzJlY2M3NDU3
11
- NzhlNjI2NzNlNjQ4ZTZkYjljNjQ2ZDVmZGE3YjJlNWUxNmI3NDc=
9
+ Nzc5NWUyZTczZWMzZjUxM2UwMmEyNGQxYTZlZTEyYmI1MmViYmUwZDE0NmRm
10
+ ZTYzM2Y0N2Q1MjhkOThjZTdiN2Q5OWIyODEyMmVkMjdiMjQ3OTc0ZWQ2MTcx
11
+ NzUwZTAzMzZjOWMwN2FlZjk1NjUzMTFlM2EzZTdkZTk3NjI5ZGQ=
12
12
  data.tar.gz: !binary |-
13
- MjUwMmNmYjVjMDc0ZDI2YzE5MzMyYTY0ZDNmNmJiOTdkZDQ4YTVlMGE0Yjlj
14
- YTRkOTM5ZTM3NTQyNjcxNGE2ZTY0ODAyYTNmZjFmN2JiNGJkYTFmYjQ3YjVi
15
- Yzk0YjFkMWIxNGViNDlmNDUzNWM4ODdjNWE0MDE1ZmY0ZjczOTQ=
13
+ YzVlZTBiODM0MzU0MmQzNTg1YWI1NDg0MTE0ZGRhOWMyMDA2M2Y5NTIxZTY1
14
+ NTFmM2Y3NDM5MDU5ZmRhYmI2Y2FlMDc1YjdiNzA3OWIzYWVlNGYxNTRhMjU3
15
+ MzZjYzNjMmQ2NzUwZmIxNDM2MzcxODU4OGUzMWYyZGNmOGVjNGY=
data/README.md CHANGED
@@ -20,6 +20,8 @@ by simply including Rubycom at the bottom.
20
20
  * Method parameters become required CLI arguments. Optional (defaulted) parameters become CLI options.
21
21
  * Command consoles can be built up by including other modules before including Rubycom.
22
22
  * Included modules become commands, their public singleton methods become sub-commands.
23
+ * Built in tab completion support for all commands.
24
+ * Users may call `./path/to/my_command.rb register_completions` then `source ~/.bash_profile` to register completions.
23
25
 
24
26
  Usage
25
27
  ---------------
@@ -131,11 +133,13 @@ If a set of functions needs to be accessible from the terminal, just `include Ru
131
133
  * Usage documentation is pulled from method comments.
132
134
  * Method parameters become required CLI arguments.
133
135
  * Optional (defaulted) parameters become CLI options.
136
+ * Tab completion support if the user has registered it for the file.
134
137
 
135
138
  The result is a function library which can be consumed easily from other classes/modules and which is accessible from the command line.
136
139
 
137
140
  Coming Soon
138
141
  ---------------
142
+ * Support for piping.
139
143
  * Build a job yaml by running each command in sequence with a special option --job_add <path_to_yaml>[:step_number]
140
144
  * Edit job files from the command line using special options.
141
145
  * --job_update <path_to_yaml>[:step_number]
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'yard'
2
+ require 'rake/testtask'
2
3
 
3
4
  task :default => [:package]
4
5
 
@@ -9,19 +10,19 @@ task :clean do
9
10
  FileUtils.rm(Dir.glob('./rubycom-*.gem'))
10
11
  end
11
12
 
12
- task :test do
13
- test_files = Dir.glob("**/test/*/test_*.rb")
14
- test_files.each { |test_case|
15
- ruby test_case rescue SystemExit
16
- if $?.exitstatus != 0;
17
- raise "Error during test phase\n Test: #{test_case}\n Error: #{$!}\n#{$@}" unless $!.nil?
18
- end
19
- }
13
+ task :bundle do
14
+ system("bundle install")
15
+ end
16
+
17
+ Rake::TestTask.new do |t|
18
+ t.libs << "test"
19
+ t.test_files = FileList['test/*/*_test.rb']
20
+ t.verbose = true
20
21
  end
21
22
 
22
23
  YARD::Rake::YardocTask.new
23
24
 
24
- task :package => [:clean, :test, :yard] do
25
+ task :package => [:clean, :bundle, :test, :yard] do
25
26
  gem_specs = Dir.glob("**/*.gemspec")
26
27
  gem_specs.each { |gem_spec|
27
28
  system("gem build #{gem_spec}")
@@ -34,7 +35,15 @@ task :install => :package do
34
35
  system("gem install #{File.expand_path(File.dirname(__FILE__))}/rubycom-#{Rubycom::VERSION}.gem")
35
36
  end
36
37
 
38
+ task :upgrade => :package do
39
+ system("gem uninstall rubycom -a")
40
+ load "#{File.expand_path(File.dirname(__FILE__))}/lib/rubycom/version.rb"
41
+ system("gem install #{File.expand_path(File.dirname(__FILE__))}/rubycom-#{Rubycom::VERSION}.gem")
42
+ end
43
+
37
44
  task :version_set, [:version] do |t, args|
45
+ raise "Must provide a version.\n If you called 'rake version_set 1.2.3', try 'rake version_set[1.2.3]'" if args[:version].nil? || args[:version].empty?
46
+
38
47
  version_file = <<-END.gsub(/^ {4}/, '')
39
48
  module Rubycom
40
49
  VERSION = "#{args[:version]}"
@@ -1,7 +1,9 @@
1
- require "#{File.expand_path(File.dirname(__FILE__))}/rubycom/version.rb"
1
+ require "#{File.dirname(__FILE__)}/rubycom/arguments.rb"
2
+ require "#{File.dirname(__FILE__)}/rubycom/commands.rb"
3
+ require "#{File.dirname(__FILE__)}/rubycom/documentation.rb"
4
+ require "#{File.dirname(__FILE__)}/rubycom/version.rb"
5
+
2
6
  require 'yaml'
3
- require 'find'
4
- require 'method_source'
5
7
 
6
8
  # Upon inclusion in another Module, Rubycom will attempt to call a method in the including module by parsing
7
9
  # ARGV for a method name and a list of arguments.
@@ -35,59 +37,78 @@ module Rubycom
35
37
  command = args[0] || nil
36
38
  arguments = args[1..-1] || []
37
39
 
38
- if command == 'help'
39
- help_topic = arguments[0]
40
- if help_topic.nil?
41
- usage = self.get_usage(base)
42
- puts usage
43
- return usage
44
- else
45
- cmd_usage = self.get_command_usage(base, help_topic, arguments[1..-1])
46
- puts cmd_usage
47
- return cmd_usage
48
- end
49
- elsif command == 'job'
50
- begin
51
- raise CLIError, 'No job specified' if arguments[0].nil? || arguments[0].empty?
52
- job_hash = YAML.load_file(arguments[0])
53
- job_hash = {} if job_hash.nil?
54
- STDOUT.sync = true
55
- if arguments.delete('-test') || arguments.delete('--test')
56
- puts "[Test Job #{arguments[0]}]"
57
- job_hash['steps'].each { |step, step_hash|
58
- step = "[Step: #{step}/#{job_hash['steps'].length}]"
59
- context = step_hash.select{|key| key!="cmd"}.map{|key,val| "[#{key}: #{val}]"}.join(' ')
60
- env = job_hash['env'] || {}
61
- env.map { |key, val| step_hash['cmd'].gsub!("env[#{key}]", "#{((val.class == String)&&(val.match(/\w+/))) ? "\"#{val}\"" : val}") }
62
- cmd = "[cmd: #{step_hash['cmd']}]"
63
- puts "#{[step,context,cmd].join(' ')}"
64
- }
40
+ case command
41
+ when 'register_completions'
42
+ puts self.register_completions(base)
43
+ when 'tab_complete'
44
+ puts self.tab_complete(base, args)
45
+ when 'help'
46
+ help_topic = arguments[0]
47
+ if help_topic.nil?
48
+ usage = Documentation.get_usage(base)
49
+ default_usage = Documentation.get_default_commands_usage
50
+ puts usage
51
+ puts default_usage
52
+ return usage+"\n"+default_usage
53
+ elsif help_topic == 'job'
54
+ usage = Documentation.get_job_usage(base)
55
+ puts usage
56
+ return usage
57
+ elsif help_topic == 'register_completions'
58
+ usage = Documentation.get_register_completions_usage(base)
59
+ puts usage
60
+ return usage
61
+ elsif help_topic == 'tab_complete'
62
+ usage = Documentation.get_tab_complete_usage(base)
63
+ puts usage
64
+ return usage
65
65
  else
66
- puts "[Job #{arguments[0]}]"
67
- job_hash['steps'].each { |step, step_hash|
68
- step = "[Step: #{step}/#{job_hash['steps'].length}]"
69
- context = step_hash.select{|key| key!="cmd"}.map{|key,val| "[#{key}: #{val}]"}.join(' ')
70
- env = job_hash['env'] || {}
71
- env.map { |key, val| step_hash['cmd'].gsub!("env[#{key}]", "#{((val.class == String)&&(val.match(/\w+/))) ? "\"#{val}\"" : val}") }
72
- cmd = "[cmd: #{step_hash['cmd']}]"
73
- puts "#{[step,context,cmd].join(' ')}"
74
- system(step_hash['cmd'])
75
- }
66
+ cmd_usage = Documentation.get_command_usage(base, help_topic, arguments[1..-1])
67
+ puts cmd_usage
68
+ return cmd_usage
76
69
  end
77
- rescue CLIError => e
78
- $stderr.puts e
79
- end
80
- else
81
- output = self.run_command(base, command, arguments)
82
- std_output = nil
83
- std_output = output.to_yaml unless [String, NilClass, TrueClass, FalseClass, Fixnum, Float, Symbol].include?(output.class)
84
- puts std_output || output
85
- return output
70
+ when 'job'
71
+ begin
72
+ raise CLIError, 'No job specified' if arguments[0].nil? || arguments[0].empty?
73
+ job_hash = YAML.load_file(arguments[0])
74
+ job_hash = {} if job_hash.nil?
75
+ STDOUT.sync = true
76
+ if arguments.delete('-test') || arguments.delete('--test')
77
+ puts "[Test Job #{arguments[0]}]"
78
+ job_hash['steps'].each { |step, step_hash|
79
+ step = "[Step: #{step}/#{job_hash['steps'].length}]"
80
+ context = step_hash.select { |key| key!="cmd" }.map { |key, val| "[#{key}: #{val}]" }.join(' ')
81
+ env = job_hash['env'] || {}
82
+ env.each { |key, val| step_hash['cmd'].gsub!("env[#{key}]", "#{((val.class == String)&&(val.match(/\w+/))) ? "\"#{val}\"" : val}") }
83
+ cmd = "[cmd: #{step_hash['cmd']}]"
84
+ puts "#{[step, context, cmd].join(' ')}"
85
+ }
86
+ else
87
+ puts "[Job #{arguments[0]}]"
88
+ job_hash['steps'].each { |step, step_hash|
89
+ step = "[Step: #{step}/#{job_hash['steps'].length}]"
90
+ context = step_hash.select { |key| key!="cmd" }.map { |key, val| "[#{key}: #{val}]" }.join(' ')
91
+ env = job_hash['env'] || {}
92
+ env.each { |key, val| step_hash['cmd'].gsub!("env[#{key}]", "#{((val.class == String)&&(val.match(/\w+/))) ? "\"#{val}\"" : val}") }
93
+ cmd = "[cmd: #{step_hash['cmd']}]"
94
+ puts "#{[step, context, cmd].join(' ')}"
95
+ system(step_hash['cmd'])
96
+ }
97
+ end
98
+ rescue CLIError => e
99
+ $stderr.puts e
100
+ end
101
+ else
102
+ output = self.run_command(base, command, arguments)
103
+ std_output = nil
104
+ std_output = output.to_yaml unless [String, NilClass, TrueClass, FalseClass, Fixnum, Float, Symbol].include?(output.class)
105
+ puts std_output || output
106
+ return output
86
107
  end
87
108
 
88
109
  rescue CLIError => e
89
110
  $stderr.puts e
90
- $stderr.puts self.get_summary(base)
111
+ $stderr.puts Documentation.get_summary(base)
91
112
  end
92
113
  end
93
114
 
@@ -100,314 +121,81 @@ module Rubycom
100
121
  arguments = [] if arguments.nil?
101
122
  raise CLIError, 'No command specified.' if command.nil? || command.length == 0
102
123
  begin
103
- raise CLIError, "Invalid Command: #{command}" unless self.get_top_level_commands(base).include? command.to_sym
124
+ raise CLIError, "Invalid Command: #{command}" unless Commands.get_top_level_commands(base).include? command.to_sym
104
125
  if base.included_modules.map { |mod| mod.name.to_sym }.include?(command.to_sym)
105
126
  self.run_command(eval(command), arguments[0], arguments[1..-1])
106
127
  else
107
128
  method = base.public_method(command.to_sym)
108
129
  raise CLIError, "No public method found for symbol: #{command.to_sym}" if method.nil?
109
- param_defs = self.get_param_definitions(method)
110
- args = self.parse_arguments(param_defs, arguments)
130
+ param_defs = Arguments.get_param_definitions(method)
131
+ args = Arguments.parse_arguments(param_defs, arguments)
111
132
  flatten = false
112
- params = method.parameters.map { |arr| flatten = true if arr[0]==:rest; args[arr[1]]}
133
+ params = method.parameters.map { |arr| flatten = true if arr[0]==:rest; args[arr[1]] }
113
134
  if flatten
114
135
  rest_arr = params.delete_at(-1)
115
- rest_arr.each{|arg| params << arg}
136
+ rest_arr.each { |arg| params << arg }
116
137
  end
117
138
  (arguments.nil? || arguments.empty?) ? method.call : method.call(*params)
118
139
  end
119
140
  rescue CLIError => e
120
141
  $stderr.puts e
121
- $stderr.puts self.get_command_usage(base, command, arguments)
122
- end
123
- end
124
-
125
- # Parses the given arguments and matches them to the given parameters
126
- #
127
- # @param [Hash] parameters a Hash representing the parameters to match.
128
- # Entries should match :param_name => { type: :req||:opt||:rest,
129
- # def:(source_definition),
130
- # default:(default_value || :nil_rubycom_required_param)
131
- # }
132
- # @param [Array] arguments an Array of Strings representing the arguments to be parsed
133
- # @return [Hash] a Hash mapping the defined parameters to their matching argument values
134
- def self.parse_arguments(parameters={}, arguments=[])
135
- raise CLIError, 'parameters may not be nil' if parameters.nil?
136
- raise CLIError, 'arguments may not be nil' if arguments.nil?
137
- sorted_args = arguments.map { |arg|
138
- Rubycom.parse_arg(arg)
139
- }.group_by { |hsh|
140
- hsh.keys.first
141
- }.map { |key, arr|
142
- (key == :rubycom_non_opt_arg) ? Hash[key, arr.map { |hsh| hsh.values }.flatten(1)] : Hash[key, arr.map { |hsh| hsh.values.first }.reduce(&:merge)]
143
- }.reduce(&:merge) || {}
144
-
145
- sorted_arg_count = sorted_args.map{|key,val| val}.flatten(1).length
146
- types = parameters.values.group_by { |hsh| hsh[:type] }.map { |type, defs_arr| Hash[type, defs_arr.length] }.reduce(&:merge) || {}
147
- raise CLIError, "Wrong number of arguments. Expected at least #{types[:req]}, received #{sorted_arg_count}" if sorted_arg_count < (types[:req]||0)
148
- raise CLIError, "Wrong number of arguments. Expected at most #{(types[:req]||0) + (types[:opt]||0)}, received #{sorted_arg_count}" if types[:rest].nil? && (sorted_arg_count > ((types[:req]||0) + (types[:opt]||0)))
149
-
150
- parameters.map { |param_sym, def_hash|
151
- if def_hash[:type] == :req
152
- raise CLIError, "No argument available for #{param_sym}" if sorted_args[:rubycom_non_opt_arg].nil? || sorted_args[:rubycom_non_opt_arg].length == 0
153
- Hash[param_sym, sorted_args[:rubycom_non_opt_arg].shift]
154
- elsif def_hash[:type] == :opt
155
- if sorted_args[param_sym].nil?
156
- arg = (sorted_args[:rubycom_non_opt_arg].nil? || sorted_args[:rubycom_non_opt_arg].empty?) ? parameters[param_sym][:default] : sorted_args[:rubycom_non_opt_arg].shift
157
- else
158
- arg = sorted_args[param_sym]
159
- end
160
- Hash[param_sym, arg]
161
- elsif def_hash[:type] == :rest
162
- ret = Hash[param_sym, ((!sorted_args[param_sym].nil?) ? sorted_args[param_sym] : sorted_args[:rubycom_non_opt_arg])]
163
- sorted_args[:rubycom_non_opt_arg] = []
164
- ret
165
- end
166
- }.reduce(&:merge)
167
- end
168
-
169
- # Uses YAML.load to parse the given String
170
- #
171
- # @param [String] arg a String representing the argument to be parsed
172
- # @return [Object] the result of parsing the given arg with YAML.load
173
- def self.parse_arg(arg)
174
- return Hash[:rubycom_non_opt_arg, nil] if arg.nil?
175
- if arg.is_a?(String) && ((arg.match(/^[-]{3,}\w+/) != nil) || ((arg.match(/^[-]{1,}\w+/) == nil) && (arg.match(/^\w+=/) != nil)))
176
- raise CLIError, "Improper option specification, options must start with one or two dashes. Received: #{arg}"
177
- elsif arg.is_a?(String) && arg.match(/^(-|--)\w+[=|\s]{1}/) != nil
178
- k, v = arg.partition(/^(-|--)\w+[=|\s]{1}/).select { |part|
179
- !part.empty?
180
- }.each_with_index.map { |part, index|
181
- index == 0 ? part.chomp('=').gsub(/^--/, '').gsub(/^-/, '').strip.to_sym : (YAML.load(part) rescue "#{part}")
182
- }
183
- Hash[k, v]
184
- else
185
- begin
186
- parsed_arg = YAML.load("#{arg}")
187
- rescue Exception
188
- parsed_arg = "#{arg}"
189
- end
190
- Hash[:rubycom_non_opt_arg, parsed_arg]
142
+ $stderr.puts Documentation.get_command_usage(base, command, arguments)
191
143
  end
192
144
  end
193
145
 
194
- # Retrieves the summary for each command method in the given Module
146
+ # Inserts a tab completion into the current user's .bash_profile with a command entry to register the function for
147
+ # the current running ruby file
195
148
  #
196
149
  # @param [Module] base the module which invoked 'include Rubycom'
197
- # @return [String] the summary for each command method in the given Module
198
- def self.get_summary(base)
199
- self.get_top_level_commands(base).each_with_index.map { |sym, index|
200
- "#{"Commands:\n" if index == 0}" << self.get_command_summary(base, sym, self.get_separator(sym, self.get_longest_command_name(base).length))
201
- }.reduce(:+) or "No Commands found for #{base}."
202
- end
203
-
204
- # Creates a separator with the appropriate spacing to line up a command/description pair in a command list
205
- #
206
- # @param [Symbol] sym
207
- # @param [Integer] spacer_length
208
- # @return [String] a spaced separator String for use in a command/description list
209
- def self.get_separator(sym, spacer_length=0)
210
- [].unshift(" " * (spacer_length - sym.to_s.length)).join << " - "
211
- end
150
+ # @return [String] a message indicating the result of the command
151
+ def self.register_completions(base)
152
+ completion_function = <<-END.gsub(/^ {4}/, '')
153
+
154
+ _#{base}_complete() {
155
+ COMPREPLY=()
156
+ local completions="$(ruby #{File.absolute_path($0)} tab_complete ${COMP_WORDS[*]} 2>/dev/null)"
157
+ COMPREPLY=( $(compgen -W "$completions") )
158
+ }
159
+ complete -o bashdefault -o default -o nospace -F _#{base}_complete #{$0.split('/').last}
160
+ END
212
161
 
213
- # Retrieves the summary for the given command_name
214
- #
215
- # @param [Module] base the module which invoked 'include Rubycom'
216
- # @param [String] command_name the command to retrieve usage for
217
- # @return [String] a summary of the given command_name
218
- def self.get_command_summary(base, command_name, separator = ' - ')
219
- raise CLIError, "Can not get usage for #{command_name} with base: #{base||"nil"}" if base.nil? || !base.respond_to?(:included_modules)
220
- return 'No command specified.' if command_name.nil? || command_name.length == 0
221
- if base.included_modules.map { |mod| mod.name.to_sym }.include?(command_name.to_sym)
222
- begin
223
- mod_const = Kernel.const_get(command_name.to_sym)
224
- desc = File.read(mod_const.public_method(mod_const.singleton_methods().first).source_location.first).split(//).reduce(""){|str,c|
225
- unless str.gsub("\n",'').gsub(/\s+/,'').include?("module#{mod_const}")
226
- str << c
227
- end
228
- str
229
- }.split("\n").select{|line| line.strip.match(/^#/)}.map{|line| line.strip.gsub(/^#+/,'')}.join("\n")
230
- rescue
231
- desc = ""
232
- end
162
+ already_registered = File.readlines("#{Dir.home}/.bash_profile").map { |line| line.include?("_#{base}_complete()") }.reduce(:|) rescue false
163
+ if already_registered
164
+ "Completion function for #{base} already registered."
233
165
  else
234
- raise CLIError, "Invalid command for #{base}, #{command_name}" unless base.public_methods.include?(command_name.to_sym)
235
- desc = self.get_doc(base.public_method(command_name.to_sym))[:desc].join("\n") rescue ""
166
+ File.open("#{Dir.home}/.bash_profile", 'a+') { |file|
167
+ file.write(completion_function)
168
+ }
169
+ "Registration complete, run 'source #{Dir.home}/.bash_profile' to enable auto-completion."
236
170
  end
237
- (desc.nil?||desc=='nil'||desc.length==0) ? "#{command_name}\n" : self.get_formatted_summary(command_name, desc, separator)
238
- end
239
-
240
- # Arranges the given command_name and command_description with the separator in a standard format
241
- #
242
- # @param [String] command_name the command format
243
- # @param [String] command_description the description for the given command
244
- # @param [String] separator optional separator to use
245
- def self.get_formatted_summary(command_name, command_description, separator = ' - ')
246
- width = 95
247
- prefix = command_name.to_s.split(//).map { " " }.join + separator.split(//).map { " " }.join
248
- line_width = width - prefix.length
249
- description_msg = command_description.gsub(/(.{1,#{line_width}})(?: +|$)\n?|(.{#{line_width}})/, "#{prefix}"+'\1\2'+"\n")
250
- "#{command_name}#{separator}#{description_msg.lstrip}"
251
171
  end
252
172
 
253
- # Retrieves the usage description for the given Module with a list of command methods
173
+ # Discovers a list of possible matches to the given arguments
174
+ # Intended for use with bash tab completion
254
175
  #
255
176
  # @param [Module] base the module which invoked 'include Rubycom'
256
- # @return [String] the usage description for the module with a list of command methods
257
- def self.get_usage(base)
258
- return '' if base.nil? || !base.respond_to?(:included_modules)
259
- return '' if self.get_top_level_commands(base).size == 0
260
- "Usage:\n #{base} <command> [args]\n\n" << self.get_summary(base)
261
- end
262
-
263
- # Retrieves the usage description for the given command_name
264
- #
265
- # @param [Module] base the module which invoked 'include Rubycom'
266
- # @param [String] command_name the command to retrieve usage for
267
- # @param [Array] args the remaining args other than the command_name, used of sub-command look-ups
268
- # @return [String] the detailed usage description for the given command_name
269
- def self.get_command_usage(base, command_name, args=[])
270
- raise CLIError, "Can not get usage for #{command_name} with base: #{base||"nil"}" if base.nil? || !base.respond_to?(:included_modules)
271
- return 'No command specified.' if command_name.nil? || command_name.length == 0
272
- if base.included_modules.map { |mod| mod.name.to_sym }.include?(command_name.to_sym)
273
- if args.empty?
274
- self.get_usage(eval(command_name.to_s))
275
- else
276
- self.get_command_usage(eval(command_name.to_s), args[0], args[1..-1])
277
- end
278
- else
279
- raise CLIError, "Invalid command for #{base}, #{command_name}" unless base.public_methods.include?(command_name.to_sym)
280
- m = base.public_method(command_name.to_sym)
281
- method_doc = self.get_doc(m) || {}
282
-
283
- msg = "Usage: #{m.name} #{self.get_param_usage(m)}\n"
284
- msg << "#{"Parameters:"}\n" unless m.parameters.empty?
285
- msg << "#{method_doc[:param].join("\n ")}\n" unless method_doc[:param].nil?
286
- msg << "#{"Returns:"}\n" unless method_doc[:return].nil?
287
- msg << "#{method_doc[:return].join("\n ")}\n" unless method_doc[:return].nil?
288
- msg
289
- end
290
- end
291
-
292
- # Discovers the given Method's parameters and uses them to build a formatted usage string
293
- #
294
- # @param [Method] method the Method object to generate usage for
295
- def self.get_param_usage(method)
296
- return "" if method.parameters.nil? || method.parameters.empty?
297
- method.parameters.map { |type, param| {type => param}
298
- }.group_by { |entry| entry.keys.first
299
- }.map { |key, val| Hash[key, val.map { |param| param.values.first }]
300
- }.reduce(&:merge).map { |type, arr|
301
- if type == :req
302
- Hash[type, arr.map { |param| " <#{param.to_s}>" }.reduce(:+)]
303
- elsif type == :opt
304
- Hash[type, "[#{arr.map { |param| "-#{param}=val" }.join("|")}]"]
305
- else
306
- Hash[type, "[&#{arr.join(',')}]"]
177
+ # @param [Array] arguments a String Array representing the arguments to be matched
178
+ # @return [Array] a String Array including the possible matches for the given arguments
179
+ def self.tab_complete(base, arguments)
180
+ arguments = [] if arguments.nil?
181
+ args = (arguments.include?("tab_complete")) ? arguments[2..-1] : arguments
182
+ matches = ['']
183
+ if args.nil? || args.empty?
184
+ matches = Rubycom::Commands.get_top_level_commands(base).map { |sym| sym.to_s }
185
+ elsif args.length == 1
186
+ matches = Rubycom::Commands.get_top_level_commands(base).map { |sym| sym.to_s }.select { |word| !word.match(/^#{args[0]}/).nil? }
187
+ if matches.size == 1 && matches[0] == args[0]
188
+ matches = self.tab_complete(Kernel.const_get(args[0].to_sym), args[1..-1])
307
189
  end
308
- }.reduce(&:merge).values.join(" ")
309
- end
310
-
311
- # Builds a hash mapping parameter names (as symbols) to their
312
- # :type (:req,:opt,:rest), :def (source_definition), :default (default_value || :nil_rubycom_required_param)
313
- # for each parameter defined by the given method.
314
- #
315
- # @param [Method] method the Method who's parameter hash should be built
316
- # @return [Hash] a Hash representing the given Method's parameters
317
- def self.get_param_definitions(method)
318
- raise CLIError, 'method must be an instance of the Method class' unless method.class == Method
319
- method.parameters.map { |param|
320
- param[1].to_s
321
- }.map { |param_name|
322
- {param_name.to_sym => method.source.split("\n").select { |line| line.include?(param_name) }.first}
323
- }.map { |param_hash|
324
- param_def = param_hash.flatten[1].gsub(/(def\s+self\.#{method.name.to_s}|def\s+#{method.name.to_s})/, '').lstrip.chomp.chomp(')').reverse.chomp('(').reverse.split(',').map { |param_n| param_n.lstrip }.select { |candidate| candidate.include?(param_hash.flatten[0].to_s) }.first
325
- Hash[
326
- param_hash.flatten[0],
327
- Hash[
328
- :def, param_def,
329
- :type, method.parameters.select { |arr| arr[1] == param_hash.flatten[0] }.flatten[0],
330
- :default, (param_def.include?('=') ? YAML.load(param_def.split('=')[1..-1].join('=')) : :nil_rubycom_required_param)
331
- ]
332
- ]
333
- }.reduce(&:merge) || {}
334
- end
335
-
336
- # Retrieves the given method's documentation from it's source code
337
- #
338
- # @param [Method] method the Method who's documentation should be retrieved
339
- # @return [Hash] a Hash representing the given Method's documentation, documentation parsed as follows:
340
- # :desc = the first general method comment lines,
341
- # :word = each @word comment (i.e.- a line starting with @param will be saved as :param => ["line"])
342
- def self.get_doc(method)
343
- method.comment.split("\n").map { |line|
344
- line.gsub(/#\s*/, '') }.group_by { |doc|
345
- if doc.match(/^@\w+/).nil?
346
- :desc
347
- else
348
- doc.match(/^@\w+/).to_s.gsub('@', '').to_sym
190
+ elsif args.length > 1
191
+ begin
192
+ matches = self.tab_complete(Kernel.const_get(args[0].to_sym), args[1..-1])
193
+ rescue Exception
194
+ matches = ['']
349
195
  end
350
- }.map { |key, val|
351
- Hash[key, val.map { |val_line| val_line.gsub(/^@\w+/, '').lstrip }.select { |line| line != '' }]
352
- }.reduce(&:merge)
353
- end
354
-
355
- # Looks up the commands which will be available on the given base Module and returns the longest command name
356
- # Used in arranging the command list format
357
- #
358
- # @param [Module] base the base Module to look up
359
- # @return [String] the longest command name which will show in a list of commands for the given base Module
360
- def self.get_longest_command_name(base)
361
- return '' if base.nil?
362
- self.get_commands(base, false).map { |_, mod_hash|
363
- mod_hash[:commands] + mod_hash[:inclusions].flatten }.flatten.max_by(&:size) or ''
364
- end
365
-
366
- # Retrieves the singleton methods in the given base and included Modules
367
- #
368
- # @param [Module] base the module which invoked 'include Rubycom'
369
- # @param [Boolean] all if true recursively search for included modules' commands, if false return only top level commands
370
- # @return [Hash] a Hash of Symbols representing the command methods in the given base and it's included modules (if all=true)
371
- def self.get_commands(base, all=true)
372
- return {} if base.nil? || !base.respond_to?(:singleton_methods) || !base.respond_to?(:included_modules)
373
- {
374
- base.name.to_sym => {
375
- commands: base.singleton_methods(true).select { |sym| ![:included, :extended].include?(sym) },
376
- inclusions: base.included_modules.select { |mod|
377
- ![:Rubycom].include?(mod.name.to_sym)
378
- }.map { |mod|
379
- all ? self.get_commands(mod) : mod.name.to_sym
380
- }
381
- }
382
- }
383
- end
384
-
385
- # Discovers the commands specified in the given base without considering the commands contained in sub-modules
386
- #
387
- # @param [Module] base the base Module to search
388
- # @return [Array] a list of command name symbols which are defined in the given Module
389
- def self.get_top_level_commands(base)
390
- return {} if base.nil? || !base.respond_to?(:singleton_methods) || !base.respond_to?(:included_modules)
391
- excluded_commands = [:included, :extended]
392
- excluded_modules = [:Rubycom]
393
- base.singleton_methods(true).select { |sym| !excluded_commands.include?(sym) } +
394
- base.included_modules.select { |mod| !excluded_modules.include?(mod.name.to_sym) }.map { |mod| mod.name.to_sym }.flatten
395
- end
396
-
397
- # Discovers the commands specified in the given base and included Modules
398
- #
399
- # @param [Module] base the base Module to search
400
- # @return [Hash] a set of command name symbols mapped to containing Modules
401
- def self.index_commands(base)
402
- excluded_commands = [:included, :extended]
403
- excluded_modules = [:Rubycom]
404
- Hash[base.singleton_methods(true).select { |sym| !excluded_commands.include?(sym) }.map { |sym|
405
- [sym, base]
406
- }].merge(
407
- base.included_modules.select { |mod| !excluded_modules.include?(mod.name.to_sym) }.map { |mod|
408
- self.index_commands(mod)
409
- }.reduce(&:merge) || {}
410
- )
196
+ end unless base.nil?
197
+ matches = [''] if matches.nil? || matches.include?(args[0])
198
+ matches
411
199
  end
412
200
 
413
201
  end