commandable 0.2.0.beta01
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.
- data/.gitignore +8 -0
- data/Gemfile +2 -0
- data/LICENCE +19 -0
- data/README.markdown +409 -0
- data/Rakefile +29 -0
- data/_testing/alias_trap.rb +14 -0
- data/autotest/discover.rb +2 -0
- data/bin/commandable +18 -0
- data/commandable.gemspec +24 -0
- data/lib/commandable.rb +4 -0
- data/lib/commandable/app_controller.rb +47 -0
- data/lib/commandable/commandable.rb +394 -0
- data/lib/commandable/exceptions.rb +61 -0
- data/lib/commandable/version.rb +4 -0
- data/lib/monkey_patch/file_utils.rb +11 -0
- data/spec/commandable/command_line_execution_spec.rb +154 -0
- data/spec/commandable/commandable_spec.rb +245 -0
- data/spec/commandable/help_generator_spec.rb +169 -0
- data/spec/commandable/helpers_spec.rb +17 -0
- data/spec/commandable/reset_spec.rb +26 -0
- data/spec/commandable/xor_groups_spec.rb +43 -0
- data/spec/source_code_examples/class_command_no_command.rb +27 -0
- data/spec/source_code_examples/class_methods.rb +20 -0
- data/spec/source_code_examples/class_methods_nested.rb +31 -0
- data/spec/source_code_examples/command_no_command.rb +27 -0
- data/spec/source_code_examples/deep_class.rb +14 -0
- data/spec/source_code_examples/default_method.rb +17 -0
- data/spec/source_code_examples/default_method_no_params.rb +17 -0
- data/spec/source_code_examples/multi_line_description.rb +17 -0
- data/spec/source_code_examples/multi_line_description_no_params.rb +17 -0
- data/spec/source_code_examples/no_description.rb +10 -0
- data/spec/source_code_examples/parameter_class.rb +27 -0
- data/spec/source_code_examples/parameter_free.rb +22 -0
- data/spec/source_code_examples/required_methods.rb +18 -0
- data/spec/source_code_examples/super_deep_class.rb +28 -0
- data/spec/source_code_examples/test_class.rb +13 -0
- data/spec/source_code_examples/xor_class.rb +37 -0
- data/spec/source_code_for_errors/class_bad.rb +7 -0
- data/spec/source_code_for_errors/default_method_bad.rb +17 -0
- data/spec/source_code_for_errors/private_methods_bad.rb +10 -0
- data/spec/spec_helper.rb +55 -0
- metadata +140 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'pp'
|
2
|
+
# hash1 = {
|
3
|
+
# :c=>{:foo=>"a c command", :description=>"c description"},
|
4
|
+
# :a=>{:foo=>"z a command", :description=>"c description"},
|
5
|
+
# :b=>{:foo=>" buh command", :description=>"c description"}
|
6
|
+
# }
|
7
|
+
|
8
|
+
hash1 = [
|
9
|
+
{:foo=>"a c command", :description=>"c description"},
|
10
|
+
{:foo=>"z a command", :description=>"c description"},
|
11
|
+
{:foo=>" buh command", :description=>"c description"}
|
12
|
+
]
|
13
|
+
|
14
|
+
puts hash1.shift[:foo]
|
data/bin/commandable
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
|
3
|
+
require 'commandable'
|
4
|
+
Commandable.color_output = true
|
5
|
+
Commandable.verbose_parameters = false
|
6
|
+
Commandable.app_name = "commandable"
|
7
|
+
Commandable.app_info =
|
8
|
+
"""
|
9
|
+
\e[92mCommandable\e[0m - The easiest way to add command line control to your app.
|
10
|
+
Copyrighted free software - Copyright (c) 2011 Mike Bethany.
|
11
|
+
Version: #{Commandable::VERSION}
|
12
|
+
"""
|
13
|
+
|
14
|
+
# App controller has to be loaded after commandable settings
|
15
|
+
# or it won't be able to use the settings
|
16
|
+
require 'commandable/app_controller'
|
17
|
+
|
18
|
+
Commandable.execute(ARGV)
|
data/commandable.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "commandable/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "commandable"
|
7
|
+
s.required_ruby_version = "~>1.9.2"
|
8
|
+
s.version = Commandable::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Mike Bethany"]
|
11
|
+
s.email = ["mikbe.tk@gmail.com"]
|
12
|
+
s.homepage = "http://mikbe.tk"
|
13
|
+
s.summary = %q{The easiest way to add command line control to your app.}
|
14
|
+
s.description = %q{Adding command line control to your app is as easy as putting 'command "this command does xyz"' above a method. Parameter lists and a help command are automatically built for you.}
|
15
|
+
|
16
|
+
s.add_dependency("term-ansicolor-hi", "~>1.0.6")
|
17
|
+
|
18
|
+
s.add_development_dependency("rspec", "~>2.5")
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {spec,autotest}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
data/lib/commandable.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Commandable
|
2
|
+
|
3
|
+
# A helper to display the read me file and generate an example app
|
4
|
+
class AppController
|
5
|
+
|
6
|
+
class << self
|
7
|
+
extend Commandable
|
8
|
+
|
9
|
+
# Displays the readme file
|
10
|
+
command "displays the readme file", :default
|
11
|
+
def readme
|
12
|
+
`open #{File.expand_path((File.dirname(__FILE__) + '/../../readme.markdown'))}`
|
13
|
+
end
|
14
|
+
|
15
|
+
command "Copies a fully working app demonstrating how\n to use Commandable with RSpec and Cucumber"
|
16
|
+
# Creates a simple example app demonstrating a fully working app
|
17
|
+
def widget(path="./widget")
|
18
|
+
puts "This feature hasn't been added yet. I'm working on it now and it will be in the release version."
|
19
|
+
end
|
20
|
+
|
21
|
+
command "Copies the test classes to a folder so\n you can see a bunch of small examples"
|
22
|
+
# Copies the test classes to a folder so you can see a bunch of small examples
|
23
|
+
def examples(path="./example_classes")
|
24
|
+
FileUtils.copy_dir(File.expand_path(File.dirname(__FILE__) + '/../../spec/source_code_examples'),path)
|
25
|
+
end
|
26
|
+
|
27
|
+
command "Will raise a programmer error, not a user error\nso you see what happens when you have bad code"
|
28
|
+
# Causes an error so you can see what it will look like if you have an error in your code.
|
29
|
+
def error
|
30
|
+
raise Exception, "An example of a non-user error caused by your bad code trapped in Commandable.execute()"
|
31
|
+
end
|
32
|
+
|
33
|
+
command "Application Version", :xor
|
34
|
+
# Version
|
35
|
+
def v
|
36
|
+
puts "Commandable: #{Commandable::VERSION}"
|
37
|
+
end
|
38
|
+
command "Application Version", :xor
|
39
|
+
alias :version :v
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
@@ -0,0 +1,394 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
# This library allows you to incredibly easily make
|
5
|
+
# your methods directly available from the command line.
|
6
|
+
#
|
7
|
+
# Author:: Mike Bethany (mailto:mikbe.tk@gmail.com)
|
8
|
+
# Copyright:: Copyright (c) 2011 Mike Bethany
|
9
|
+
# License:: Distributed under the MIT licence (See LICENCE file)
|
10
|
+
|
11
|
+
# Extending your class with this module allows you to use the #command
|
12
|
+
# method above your method. This makes them executable from the command line.
|
13
|
+
module Commandable
|
14
|
+
|
15
|
+
# Default command that always gets added to end of the command list
|
16
|
+
HELP_COMMAND = {:help => {:description => "you're looking at it now", :argument_list => "", :class=>"Commandable", :class_method=>true}}
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
# Describes your application, printed at the top of help/usage messages
|
21
|
+
attr_accessor :app_info
|
22
|
+
|
23
|
+
# Used when building the usage line, e.g. Usage: app_name [command] [parameters]
|
24
|
+
attr_accessor :app_name
|
25
|
+
|
26
|
+
# If optional parameters show default values, true by default
|
27
|
+
attr_accessor :verbose_parameters
|
28
|
+
|
29
|
+
# Boolean: If help/usage messages will print in color
|
30
|
+
attr_accessor :color_output
|
31
|
+
# What color the app_info text will be in the help message
|
32
|
+
attr_accessor :color_app_info
|
33
|
+
# What color the app_name will be in the usage line in the help message
|
34
|
+
attr_accessor :color_app_name
|
35
|
+
# What color the word "command" and the commands themselves will be in the help message
|
36
|
+
attr_accessor :color_command
|
37
|
+
# What color the description column header and text will be in the help message
|
38
|
+
attr_accessor :color_description
|
39
|
+
# What color the word "parameter" and the parameters themselves will be in the help message
|
40
|
+
attr_accessor :color_parameter
|
41
|
+
# What color the word "Usage:" will be in the help message
|
42
|
+
attr_accessor :color_usage
|
43
|
+
|
44
|
+
# What color the word "Error:" text will be in error messages
|
45
|
+
attr_accessor :color_error_word
|
46
|
+
# What color the friendly name of the error will be in error messages
|
47
|
+
attr_accessor :color_error_name
|
48
|
+
# What color the error description will be in error messages
|
49
|
+
attr_accessor :color_error_description
|
50
|
+
|
51
|
+
# An array of methods that can be executed from the command line
|
52
|
+
def commands
|
53
|
+
@@commands.dup
|
54
|
+
end
|
55
|
+
|
56
|
+
# Access the command array using the method name (symbol or string)
|
57
|
+
def [](index)
|
58
|
+
raise AccessorError unless index.is_a? String or index.is_a? Symbol
|
59
|
+
@@commands[index.to_sym]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Resets the class to default values clearing any commands
|
63
|
+
# and setting the color back to their default values.
|
64
|
+
def reset_all
|
65
|
+
clear_commands
|
66
|
+
reset_colors
|
67
|
+
@app_info = nil
|
68
|
+
@app_name = nil
|
69
|
+
@verbose_parameters = true
|
70
|
+
@@default_method = nil
|
71
|
+
end
|
72
|
+
alias :init :reset_all
|
73
|
+
|
74
|
+
# Clears all methods from the list of available commands
|
75
|
+
# This is mostly useful for testing.
|
76
|
+
def clear_commands
|
77
|
+
@@commands = HELP_COMMAND.dup
|
78
|
+
end
|
79
|
+
|
80
|
+
# Convenience method to iterate over the array of commands using the Commandable module
|
81
|
+
def each(&block)
|
82
|
+
@@commands.each do |key, value|
|
83
|
+
yield key => value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Generates an array of the available commands with a
|
88
|
+
# list of their parameters and the method's description.
|
89
|
+
# This includes the applicaiton info and app name if given.
|
90
|
+
# It's meant to be printed to the command line.
|
91
|
+
def help(additional_info=nil)
|
92
|
+
|
93
|
+
set_colors
|
94
|
+
|
95
|
+
cmd_length = "Command".length
|
96
|
+
parm_length = "Parameters".length
|
97
|
+
max_command = [(@@commands.keys.max_by{|key| key.to_s.length }).to_s.length, cmd_length].max
|
98
|
+
max_parameter = @@commands[@@commands.keys.max_by{|key| @@commands[key][:argument_list].length }][:argument_list].length
|
99
|
+
max_parameter = [parm_length, max_parameter].max if max_parameter > 0
|
100
|
+
|
101
|
+
usage_text = " #{@c_usage}Usage:#{@c_reset} "
|
102
|
+
|
103
|
+
if Commandable.app_name
|
104
|
+
cmd_text = "<#{@c_command + @c_bold}command#{@c_reset}>"
|
105
|
+
parm_text = " [#{@c_parameter + @c_bold}parameters#{@c_reset}]" if max_parameter > 0
|
106
|
+
usage_text += "#{@c_app_name + app_name + @c_reset} #{cmd_text}#{parm_text} [#{cmd_text}#{parm_text}...]"
|
107
|
+
end
|
108
|
+
|
109
|
+
array = [usage_text, ""]
|
110
|
+
|
111
|
+
array.unshift additional_info if additional_info
|
112
|
+
array.unshift ("\e[2A" + @c_app_info + Commandable.app_info + @c_reset) if Commandable.app_info
|
113
|
+
array.unshift "\e[H\e[2J"
|
114
|
+
|
115
|
+
header_text = " #{" "*(max_command-cmd_length)}#{@c_command + @c_bold}Command#{@c_reset} "
|
116
|
+
header_text += "#{@c_parameter + @c_bold}Parameters #{@c_reset}#{" "*(max_parameter-parm_length)}" if max_parameter > 0
|
117
|
+
header_text += "#{@c_description + @c_bold}Description#{@c_reset}"
|
118
|
+
|
119
|
+
array << header_text
|
120
|
+
|
121
|
+
array += @@commands.keys.collect do |key|
|
122
|
+
default = (@@default_method and key == @@default_method.keys[0]) ? @color_bold : ""
|
123
|
+
|
124
|
+
help_line = " #{" "*(max_command-key.length)}#{@c_command + default + key.to_s + @c_reset}"+
|
125
|
+
" #{default + @c_parameter + @@commands[key][:argument_list] + @c_reset}"
|
126
|
+
help_line += "#{" "*(max_parameter-@@commands[key][:argument_list].length)} " if max_parameter > 0
|
127
|
+
|
128
|
+
# indent new lines
|
129
|
+
description = @@commands[key][:description].gsub("\n", "\n" + (" "*(max_command + max_parameter + (max_parameter > 0 ? 1 : 0) + 4)))
|
130
|
+
|
131
|
+
help_line += ": #{default + @c_description}#{"<#{@@commands[key][:xor]}> " if @@commands[key][:xor]}" +
|
132
|
+
"#{description}" +
|
133
|
+
"#{" (default)" unless default == ""}#{@c_reset}"
|
134
|
+
end
|
135
|
+
array << nil
|
136
|
+
end
|
137
|
+
|
138
|
+
# A wrapper for the execution_queue that runs the queue and traps errors.
|
139
|
+
# If an error occurs inside this method it will print out a complete.
|
140
|
+
# of availavle methods with usage instructios and exit gracefully.
|
141
|
+
def execute(argv)
|
142
|
+
begin
|
143
|
+
command_queue = execution_queue(argv)
|
144
|
+
command_queue.each do |com|
|
145
|
+
puts com[:proc].call
|
146
|
+
end
|
147
|
+
rescue Exception => exception
|
148
|
+
if exception.respond_to?(:friendly_name)
|
149
|
+
set_colors
|
150
|
+
puts help(" #{@c_error_word}Error:#{@c_reset} #{@c_error_name}#{exception.friendly_name}#{@c_reset}\n #{@c_error_description}#{exception.message}#{@c_reset}\n\n")
|
151
|
+
else
|
152
|
+
#rescue Exception => exception
|
153
|
+
|
154
|
+
puts "\n Bleep, bloop, bleep! Danger Will Robinson! Danger!"
|
155
|
+
puts "\n Error: #{exception.inspect}"
|
156
|
+
puts "\n Backtrace:"
|
157
|
+
puts exception.backtrace.collect{|line| " #{line}"}
|
158
|
+
puts
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns an array of executable procs based on the given array of commands and parameters
|
164
|
+
# Normally this would come from the command line parameters in the ARGV array.
|
165
|
+
def execution_queue(argv)
|
166
|
+
arguments = argv.dup
|
167
|
+
method_hash = {}
|
168
|
+
last_method = nil
|
169
|
+
|
170
|
+
if arguments.empty?
|
171
|
+
arguments << @@default_method.keys[0] if @@default_method and !@@default_method.values[0][:parameters].flatten.include?(:req)
|
172
|
+
end
|
173
|
+
arguments << "help" if arguments.empty?
|
174
|
+
|
175
|
+
# Parse the commad line into methods and their parameters
|
176
|
+
arguments.each do |arg|
|
177
|
+
if Commandable[arg]
|
178
|
+
last_method = arg.to_sym
|
179
|
+
method_hash.merge!(last_method=>[])
|
180
|
+
else
|
181
|
+
unless last_method
|
182
|
+
default = find_by_subkey(:default)
|
183
|
+
|
184
|
+
# Raise an error if there is no default method and the first item isn't a method
|
185
|
+
raise UnknownCommandError, arguments.first if default.empty?
|
186
|
+
|
187
|
+
# Raise an error if there is a default method but it doesn't take any parameters
|
188
|
+
raise UnknownCommandError, (arguments.first) if default.values[0][:argument_list] == ""
|
189
|
+
|
190
|
+
last_method = default.keys.first
|
191
|
+
method_hash.merge!(last_method=>[])
|
192
|
+
end
|
193
|
+
method_hash[last_method] << arg
|
194
|
+
end
|
195
|
+
@@commands.select do |key, value|
|
196
|
+
raise MissingRequiredCommandError, key if value[:required] and method_hash[key].nil?
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Build an array of procs to be called for each method and its given parameters
|
201
|
+
proc_array = []
|
202
|
+
method_hash.each do |meth, params|
|
203
|
+
command = @@commands[meth]
|
204
|
+
|
205
|
+
# Test for duplicate XORs
|
206
|
+
proc_array.select{|x| x[:xor] and x[:xor]==command[:xor] }.each {|bad| raise ExclusiveMethodClashError, "#{meth}, #{bad[:method]}"}
|
207
|
+
|
208
|
+
klass = Object
|
209
|
+
command[:class].split(/::/).each { |name| klass = klass.const_get(name) }
|
210
|
+
klass = klass.new unless command[:class_method]
|
211
|
+
proc_array << {:method=>meth, :xor=>command[:xor], :parameters=>params, :priority=>command[:priority], :proc=>lambda{klass.send(meth, *params)}}
|
212
|
+
end
|
213
|
+
proc_array.sort{|a,b| a[:priority] <=> b[:priority]}.reverse
|
214
|
+
end
|
215
|
+
|
216
|
+
# Set colors to their default values
|
217
|
+
def reset_colors
|
218
|
+
# Colors - off by default
|
219
|
+
@color_output ||= false
|
220
|
+
# Build the default colors
|
221
|
+
Term::ANSIColor.coloring = true
|
222
|
+
c = Term::ANSIColor
|
223
|
+
@color_app_info = c.intense_white + c.bold
|
224
|
+
@color_app_name = c.intense_green + c.bold
|
225
|
+
@color_command = c.intense_yellow
|
226
|
+
@color_description = c.intense_white
|
227
|
+
@color_parameter = c.intense_cyan
|
228
|
+
@color_usage = c.intense_black + c.bold
|
229
|
+
|
230
|
+
@color_error_word = c.intense_black + c.bold
|
231
|
+
@color_error_name = c.intense_red + c.bold
|
232
|
+
@color_error_description = c.intense_white + c.bold
|
233
|
+
|
234
|
+
@color_bold = c.bold
|
235
|
+
@color_reset = c.reset
|
236
|
+
@screen_clear = "\e[H\e[2J"
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# Look through commands for a specific subkey
|
242
|
+
def find_by_subkey(key, value=true)
|
243
|
+
@@commands.select {|meth, meth_value| meth_value[key]==value}
|
244
|
+
end
|
245
|
+
|
246
|
+
# Changes the colors used when print the help/usage instructions to those set by the user.
|
247
|
+
def set_colors
|
248
|
+
if color_output
|
249
|
+
@c_app_info = @color_app_info
|
250
|
+
@c_app_name = @color_app_name
|
251
|
+
@c_command = @color_command
|
252
|
+
@c_description = @color_description
|
253
|
+
@c_parameter = @color_parameter
|
254
|
+
@c_usage = @color_usage
|
255
|
+
|
256
|
+
@c_error_word = @color_error_word
|
257
|
+
@c_error_name = @color_error_name
|
258
|
+
@c_error_description = @color_error_description
|
259
|
+
|
260
|
+
@c_bold = @color_bold
|
261
|
+
@c_reset = @color_reset
|
262
|
+
else
|
263
|
+
@c_app_info, @c_app_name, @c_command, @c_description,
|
264
|
+
@c_parameter, @c_usage, @c_bold, @c_reset, @c_error_word,
|
265
|
+
@c_error_name, @c_error_description = [""]*12
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
init # automatically configure the module when it's loaded
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
# This is where the magic happens!
|
275
|
+
# It lets you add a method to the list of command line methods
|
276
|
+
def command(*cmd_parameters)
|
277
|
+
|
278
|
+
@@method_file = nil
|
279
|
+
@@method_line = nil
|
280
|
+
@@command_options = {}
|
281
|
+
|
282
|
+
# Include Commandable in singleton classes so class level methods work
|
283
|
+
include Commandable unless self.include? Commandable
|
284
|
+
|
285
|
+
# parse command parameters
|
286
|
+
while (param = cmd_parameters.shift)
|
287
|
+
case param
|
288
|
+
when Symbol
|
289
|
+
if param == :xor
|
290
|
+
@@command_options.merge!(param=>:xor)
|
291
|
+
else
|
292
|
+
@@command_options.merge!(param=>true)
|
293
|
+
end
|
294
|
+
when Hash
|
295
|
+
@@command_options.merge!(param)
|
296
|
+
when String
|
297
|
+
@@command_options.merge!(:description=>param)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
@@command_options[:priority] ||= 0
|
301
|
+
|
302
|
+
# only one default allowed
|
303
|
+
raise ConfigurationError, "Only one default method is allowed." if @@default_method and @@command_options[:default]
|
304
|
+
|
305
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
306
|
+
|
307
|
+
# Traps the line where the method is defined so we can look up
|
308
|
+
# the method source code later if there are optional parameters
|
309
|
+
if event == "line" and !@@method_file
|
310
|
+
@@method_file = file
|
311
|
+
@@method_line = line
|
312
|
+
end
|
313
|
+
|
314
|
+
# Raise an error if there is no method following a command definition
|
315
|
+
if event == "end"
|
316
|
+
set_trace_func(nil)
|
317
|
+
raise SyntaxError, "A command was specified but no method follows"
|
318
|
+
end
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
# Add a method to the list of available command line methods
|
323
|
+
def add_command(meth)
|
324
|
+
@@commands.delete(:help)
|
325
|
+
argument_list = parse_arguments(@@command_options[:parameters])
|
326
|
+
@@command_options.merge!(:argument_list=>argument_list,:class => self.name)
|
327
|
+
@@commands.merge!(meth => @@command_options)
|
328
|
+
@@default_method = {meth => @@command_options} if @@command_options[:default]
|
329
|
+
|
330
|
+
@@commands.sort.each {|com| @@commands.merge!(com[0]=>@@commands.delete(com[0]))}
|
331
|
+
|
332
|
+
@@commands.merge!(HELP_COMMAND.dup) # makes sure the help command is always last
|
333
|
+
@@command_options = nil
|
334
|
+
end
|
335
|
+
|
336
|
+
# Trap method creation after a command call
|
337
|
+
def method_added(meth)
|
338
|
+
set_trace_func(nil)
|
339
|
+
return super(meth) if meth == :initialize || @@command_options == nil
|
340
|
+
@@command_options.merge!(:parameters=>self.instance_method(meth).parameters,:class_method=>false)
|
341
|
+
add_command(meth)
|
342
|
+
end
|
343
|
+
|
344
|
+
# Trap class methods too
|
345
|
+
def singleton_method_added(meth)
|
346
|
+
set_trace_func(nil)
|
347
|
+
return super(meth) if meth == :initialize || @@command_options == nil
|
348
|
+
@@command_options.merge!(:parameters=>method(meth).parameters, :class_method=>true)
|
349
|
+
add_command(meth)
|
350
|
+
end
|
351
|
+
|
352
|
+
# Parse a method's parameters building the argument list for printing help/usage
|
353
|
+
def parse_arguments(parameters)
|
354
|
+
parameter_string = ""
|
355
|
+
method_definition = nil
|
356
|
+
parameters.each do |parameter|
|
357
|
+
arg_type = parameter[0]
|
358
|
+
arg = parameter[1]
|
359
|
+
case arg_type
|
360
|
+
when :req
|
361
|
+
parameter_string += " #{arg}"
|
362
|
+
when :opt
|
363
|
+
if Commandable.verbose_parameters
|
364
|
+
# figure out what the default value is
|
365
|
+
method_definition ||= readline(@@method_file, @@method_line)
|
366
|
+
default = parse_optional(method_definition, arg)
|
367
|
+
parameter_string += " [#{arg}=#{default}]"
|
368
|
+
else
|
369
|
+
parameter_string += " [#{arg}]"
|
370
|
+
end
|
371
|
+
when :rest
|
372
|
+
parameter_string += " *#{arg}"
|
373
|
+
when :block
|
374
|
+
parameter_string += " &#{arg}"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
parameter_string.strip
|
378
|
+
end
|
379
|
+
|
380
|
+
# Reads a line from a source code file.
|
381
|
+
def readline(file, line_number)
|
382
|
+
current_line = 0
|
383
|
+
File.open(file).each { |line_text|
|
384
|
+
current_line += 1
|
385
|
+
return line_text.strip if current_line == line_number
|
386
|
+
}
|
387
|
+
end
|
388
|
+
|
389
|
+
# Parses a method defition for the optional values of given argument.
|
390
|
+
def parse_optional(method_def, argument)
|
391
|
+
method_def.scan(/#{argument}\s*=\s*("[^"\r\n]*"|'[^'\r\n]*'|[0-9]*)/)[0][0]
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|