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 +8 -8
- data/README.md +4 -0
- data/Rakefile +18 -9
- data/lib/rubycom.rb +119 -331
- data/lib/rubycom/arguments.rb +102 -0
- data/lib/rubycom/commands.rb +63 -0
- data/lib/rubycom/documentation.rb +204 -0
- data/lib/rubycom/version.rb +1 -1
- data/test/rubycom/arguments_test.rb +148 -0
- data/test/rubycom/commands_test.rb +51 -0
- data/test/rubycom/documentation_test.rb +186 -0
- data/test/rubycom/rubycom_test.rb +527 -0
- data/test/rubycom/util_test_composite.rb +1 -0
- metadata +13 -4
- data/test/rubycom/test_rubycom.rb +0 -762
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NDQwZWY1MzUyMTk1MGQ1OTc2YTEzYzNhOWRlNTZjZTkzMjU3MzliNw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OWVkOTI4MjY3ODhlODM1M2FhMTAxZTc2YjQxYTVkN2JmZDg2ZGE1Yg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Nzc5NWUyZTczZWMzZjUxM2UwMmEyNGQxYTZlZTEyYmI1MmViYmUwZDE0NmRm
|
10
|
+
ZTYzM2Y0N2Q1MjhkOThjZTdiN2Q5OWIyODEyMmVkMjdiMjQ3OTc0ZWQ2MTcx
|
11
|
+
NzUwZTAzMzZjOWMwN2FlZjk1NjUzMTFlM2EzZTdkZTk3NjI5ZGQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
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 :
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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]}"
|
data/lib/rubycom.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
require "#{File.
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
puts
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
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
|
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 =
|
110
|
-
args =
|
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
|
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
|
-
#
|
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]
|
198
|
-
def self.
|
199
|
-
|
200
|
-
|
201
|
-
}
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
235
|
-
|
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
|
-
#
|
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
|
-
# @
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
"
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
351
|
-
|
352
|
-
|
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
|