rubycom 0.2.5 → 0.3.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.
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