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,61 @@
|
|
1
|
+
module Commandable
|
2
|
+
|
3
|
+
# Programmer errors
|
4
|
+
|
5
|
+
# An error made by the programmer when specifiying a command
|
6
|
+
class SyntaxError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# A programmer's error raised if setting are specifiying incorrectly,
|
10
|
+
# For example if you set more than one default method.
|
11
|
+
class ConfigurationError < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
# A programmer's error raised if the list of commands is accessed using something other than a string or :symbol
|
15
|
+
# This is meant to catch meta-programming errors.
|
16
|
+
class AccessorError < StandardError
|
17
|
+
# Create a new instance of the AccessorError class
|
18
|
+
def initialize(msg = "You may only access Commandable[] using a string or :symbol" )
|
19
|
+
super(msg)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# User errors
|
24
|
+
|
25
|
+
# An error raised if a user does not provide a required command
|
26
|
+
class MissingRequiredCommandError < ScriptError
|
27
|
+
# Returns a more print friendly error name
|
28
|
+
def friendly_name
|
29
|
+
"Missing Required Command"
|
30
|
+
end
|
31
|
+
# Create a new instance of the MissingRequiredCommandError class
|
32
|
+
def initialize(msg)
|
33
|
+
super("The required command \"#{msg}\" is missing.")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# This error is raised if a user gives two or more commands from the same exclusive group
|
38
|
+
class ExclusiveMethodClashError < ScriptError
|
39
|
+
# Returns a more print friendly error name
|
40
|
+
def friendly_name
|
41
|
+
"Exclusive "
|
42
|
+
end
|
43
|
+
# Create a new instance of the MissingRequiredCommandError class
|
44
|
+
def initialize(msg)
|
45
|
+
super("You may only run one of these commands at a time: \"#{msg}\"")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# A error raised if a user tries to run a command that is not in the commands array
|
50
|
+
class UnknownCommandError < ScriptError
|
51
|
+
# Returns a more print friendly error name
|
52
|
+
def friendly_name
|
53
|
+
"Unknown Command"
|
54
|
+
end
|
55
|
+
# Create a new instance of the MissingRequiredCommandError class
|
56
|
+
def initialize(msg)
|
57
|
+
super("There is no \"#{msg}\" command")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commandable do
|
4
|
+
|
5
|
+
before(:each) {
|
6
|
+
Commandable.reset_all
|
7
|
+
Commandable.app_name = "mycoolapp"
|
8
|
+
Commandable.app_info =
|
9
|
+
"""
|
10
|
+
My Cool App - It does stuff and things!
|
11
|
+
Copyright (c) 2011 Acme Inc.
|
12
|
+
"""
|
13
|
+
|
14
|
+
load 'parameter_class.rb'
|
15
|
+
}
|
16
|
+
|
17
|
+
context "when parsing arguments" do
|
18
|
+
|
19
|
+
context "and there is an error in the command line" do
|
20
|
+
|
21
|
+
context "and the default method does not accept parameters" do
|
22
|
+
|
23
|
+
before(:each) {load 'default_method_no_params.rb'}
|
24
|
+
|
25
|
+
context "but something that isn't a method is the first thing on the command line" do
|
26
|
+
specify {lambda{Commandable.execution_queue(["potato"])}.should raise_error(Commandable::UnknownCommandError)}
|
27
|
+
specify { execute_output_s(["potato"]).should match(/Unknown Command/)}
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
context "and running procs from the execution queue" do
|
33
|
+
it "raises an error if there is an invalid command given" do
|
34
|
+
lambda{Commandable.execution_queue(["fly", "navy"])}.should raise_error(Commandable::UnknownCommandError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "and running commands automatically via execute" do
|
39
|
+
|
40
|
+
context "and an unknown command is sent" do
|
41
|
+
it "traps errors" do
|
42
|
+
capture_output{lambda{Commandable.execute(["fly", "navy"])}.should_not raise_error}
|
43
|
+
end
|
44
|
+
it "prints the error" do
|
45
|
+
capture_output{Commandable.execute(["fly", "navy"])}.to_s.should
|
46
|
+
match(/Error: Unknown Command(.*)There is no \\\"fly\\\" command/)
|
47
|
+
end
|
48
|
+
it "prints usage/help instructions" do
|
49
|
+
capture_output{Commandable.execute(["fly", "navy"])}.to_s.should match(/Usage:/)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "and an unknown command is sent" do
|
54
|
+
|
55
|
+
before(:each) { load 'required_methods.rb' }
|
56
|
+
it "traps errors" do
|
57
|
+
capture_output{lambda{Commandable.execute(["non_required_method", "blorp"])}.should_not raise_error}
|
58
|
+
end
|
59
|
+
it "prints the error" do
|
60
|
+
capture_output{Commandable.execute(["non_required_method", "blorp"])}.to_s.should
|
61
|
+
match(/Error: Missing Required Command(.*)The required command \\\"required_method\\\" is missing/)
|
62
|
+
end
|
63
|
+
it "prints usage/help instructions" do
|
64
|
+
capture_output{Commandable.execute(["non_required_method", "blorp"])}.to_s.should match(/Usage:/)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
context "and the command line is valid" do
|
73
|
+
|
74
|
+
it "does not raise an error" do
|
75
|
+
capture_output{Commandable.execute(["foo", "1", "2"])}.to_s.should_not match(/Error:/)
|
76
|
+
end
|
77
|
+
|
78
|
+
context "and automatically executing commands" do
|
79
|
+
specify {execute_output_ary(["foo", "1", "2.4"]).should == ["1", "2.4"]}
|
80
|
+
specify {execute_output_ary(["bar", "234"]).should == ["234", "Number 42"]}
|
81
|
+
specify {execute_output_ary(["bar", "39", "potato"]).should == ["39", "potato"]}
|
82
|
+
specify {execute_output_ary(["qux"]).should == ["1492", "I'm a tricky one"]}
|
83
|
+
specify {execute_output_ary(["qux", "991"]).should == ["991", "I'm a tricky one"]}
|
84
|
+
specify {execute_output_ary(["qux", "1821", "Look I've got %special\"characters\" in me"]).should ==
|
85
|
+
["1821", "Look I've got %special\"characters\" in me"]}
|
86
|
+
end
|
87
|
+
|
88
|
+
context "and using the execution_queue command" do
|
89
|
+
|
90
|
+
context "and only one command is given" do
|
91
|
+
|
92
|
+
it "only has one command in the array" do
|
93
|
+
command_queue = Commandable.execution_queue(["foo", "1", "2.4"])
|
94
|
+
command_queue.length.should == 1
|
95
|
+
end
|
96
|
+
|
97
|
+
it "properly parses parameters" do
|
98
|
+
command_queue = Commandable.execution_queue(["foo", "1", "2.4"])
|
99
|
+
command_queue.first[:parameters].should == ["1", "2.4"]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
context "and more than one command is given" do
|
105
|
+
|
106
|
+
it "has the correct number of commands in the array" do
|
107
|
+
command_queue = Commandable.execution_queue(["foo", "1", "2.4", "bar", "71"])
|
108
|
+
command_queue.length.should == 2
|
109
|
+
end
|
110
|
+
|
111
|
+
it "properly parses parameters" do
|
112
|
+
command_queue = Commandable.execution_queue(["foo", "1", "2.4", "bar", "71"])
|
113
|
+
command_queue.each do |command|
|
114
|
+
command[:parameters].should == ["71"] if command[:method] == :bar
|
115
|
+
command[:parameters].should == ["1", "2.4"] if command[:method] == :foo
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
context "and there are greedy parameters" do
|
124
|
+
# number_arg1, string_arg2 = "blorp", *array_arg3
|
125
|
+
specify {execute_output_ary(["baz", "9"]).should == ["9", "blorp"]}
|
126
|
+
specify {execute_output_ary(["baz", "81", "Fish"]).should == ["81", "Fish"]}
|
127
|
+
specify {execute_output_ary(["baz", "3278", "Bubba", "Blip"]).should == ["3278", "Bubba", "Blip"]}
|
128
|
+
specify {execute_output_ary(["baz", "0.0234", "Yellow", "Mellow", "Fellow", "bellow", "elbow"]).should ==
|
129
|
+
["0.0234", "Yellow", "Mellow", "Fellow", "bellow", "elbow"]
|
130
|
+
}
|
131
|
+
|
132
|
+
context "and it has multiple commands" do
|
133
|
+
|
134
|
+
specify {execute_output_ary(["baz", "0.0234", "Yellow", "Mellow", "elbow", "qux"]).should ==
|
135
|
+
["1492", "I'm a tricky one",
|
136
|
+
"0.0234", "Yellow", "Mellow", "elbow"]
|
137
|
+
}
|
138
|
+
|
139
|
+
specify {execute_output_ary(["baz", "0.0234", "Yellow", "Mellow", "elbow", "foo", "17", "432", "qux"]).should ==
|
140
|
+
["1492", "I'm a tricky one",
|
141
|
+
"17", "432",
|
142
|
+
"0.0234", "Yellow", "Mellow", "elbow"]
|
143
|
+
}
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commandable do
|
4
|
+
|
5
|
+
before(:each) {Commandable.reset_all}
|
6
|
+
|
7
|
+
context "when using Commandable directly" do
|
8
|
+
|
9
|
+
before(:each) { load 'test_class.rb' }
|
10
|
+
|
11
|
+
context "when clearing values" do
|
12
|
+
specify { lambda{Commandable.clear_commands}.should_not raise_error}
|
13
|
+
specify { lambda{Commandable.reset_all}.should change{Commandable.commands.length}.from(2).to(1) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when adding command to methods" do
|
17
|
+
specify { lambda{load 'test_class.rb'}.should_not raise_error }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when checking syntax" do
|
21
|
+
|
22
|
+
it "shouldn't rasie an error if nothing more than 'switch' is used" do
|
23
|
+
lambda{load 'no_description.rb'}.should_not raise_error(Commandable::SyntaxError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "rasies an error if no method follows a description" do
|
27
|
+
lambda{load 'class_bad.rb'}.should raise_error(Commandable::SyntaxError)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when enumerating" do
|
33
|
+
|
34
|
+
it "is enumerable using #each" do
|
35
|
+
lambda{Commandable.each {|x|}}.should_not raise_error
|
36
|
+
end
|
37
|
+
|
38
|
+
it "allows access to methods using []" do
|
39
|
+
lambda{Commandable["test_method"]}.should_not raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
context "when using []" do
|
43
|
+
|
44
|
+
specify { Commandable["test_method"].should_not be_nil }
|
45
|
+
specify { Commandable[:test_method].should_not be_nil }
|
46
|
+
specify { lambda{Commandable[1]}.should raise_error }
|
47
|
+
specify { lambda{Commandable[String]}.should raise_error }
|
48
|
+
|
49
|
+
context "when using a variable" do
|
50
|
+
|
51
|
+
specify {
|
52
|
+
method = "method"
|
53
|
+
Commandable[:test_method].should_not be_nil
|
54
|
+
}
|
55
|
+
|
56
|
+
specify {
|
57
|
+
method = :method
|
58
|
+
Commandable[:test_method].should_not be_nil
|
59
|
+
}
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when accessing method information" do
|
68
|
+
|
69
|
+
it "uses a string in #[] to access methods by name" do
|
70
|
+
lambda{Commandable["test_method"]}.should_not raise_error
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when accessing method descriptions" do
|
76
|
+
|
77
|
+
it "uses :descriptions" do
|
78
|
+
Commandable["test_method"][:description].should == "does some stuff"
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when reading class names" do
|
84
|
+
|
85
|
+
it "uses :class" do
|
86
|
+
Commandable["test_method"][:class].should == "TestClass"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "reads the fully qualified name including parent modules and classes" do
|
90
|
+
load 'deep_class.rb'
|
91
|
+
Commandable["deep_method"][:class].should == "TopModule::ParentClass::DeepClass"
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when reading the method parameters" do
|
99
|
+
|
100
|
+
before(:each) { load 'parameter_class.rb' }
|
101
|
+
|
102
|
+
context "when it has required parameters" do
|
103
|
+
specify{ Commandable[:foo][:argument_list].should == "int_arg1 number_arg2" }
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when it has optional parameters" do
|
107
|
+
specify {Commandable[:bar][:argument_list].should == %q{int_arg1 [string_arg2="Number 42"]} }
|
108
|
+
specify {Commandable[:qux][:argument_list].should == %q{[string_arg1="1492"] [string_arg2="I'm a tricky one"]} }
|
109
|
+
end
|
110
|
+
|
111
|
+
context "when it has greedy parameters" do
|
112
|
+
specify {Commandable[:baz][:argument_list].should == %q{number_arg1 [string_arg2="blorp"] *array_arg3} }
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when it has an instance method" do
|
116
|
+
specify {Commandable[:foo][:class_method].should be_false}
|
117
|
+
end
|
118
|
+
|
119
|
+
it "saves the list of parameters" do
|
120
|
+
Commandable[:foo][:parameters].should == [[:req, :int_arg1], [:req, :number_arg2]]
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
context "when there are methods using command and methods not using command" do
|
126
|
+
|
127
|
+
context "and they are instance methods" do
|
128
|
+
|
129
|
+
before(:each) {load 'command_no_command.rb'}
|
130
|
+
|
131
|
+
specify {Commandable.commands.should include(:command_method1)}
|
132
|
+
specify {Commandable.commands.should include(:command_method2)}
|
133
|
+
specify {Commandable.commands.should_not include(:no_command_method1)}
|
134
|
+
specify {Commandable.commands.should_not include(:no_command_method2)}
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
context "and they are class methods" do
|
139
|
+
|
140
|
+
before(:each) {load 'class_command_no_command.rb'}
|
141
|
+
|
142
|
+
specify {Commandable.commands.should include(:class_command_method1)}
|
143
|
+
specify {Commandable.commands.should include(:class_command_method2)}
|
144
|
+
specify {Commandable.commands.should_not include(:class_no_command_method1)}
|
145
|
+
specify {Commandable.commands.should_not include(:class_no_command_method2)}
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
context "when using class methods" do
|
152
|
+
|
153
|
+
before(:each) { load 'class_methods.rb' }
|
154
|
+
|
155
|
+
specify { Commandable["class_method"].should_not be_nil }
|
156
|
+
specify { Commandable["class_method"][:argument_list].should == "string_arg1" }
|
157
|
+
specify { Commandable["class_method2"][:argument_list].should == "integer_arg1" }
|
158
|
+
|
159
|
+
context "grouped class methods" do
|
160
|
+
|
161
|
+
before(:each) {
|
162
|
+
load 'class_methods_nested.rb'
|
163
|
+
}
|
164
|
+
|
165
|
+
specify{ Commandable[:class_foo][:argument_list].should == "int_arg1 number_arg2" }
|
166
|
+
specify {Commandable[:class_bar][:argument_list].should == %q{int_arg1 [string_arg2="Number 42"]} }
|
167
|
+
specify {Commandable[:class_qux][:argument_list].should == %q{[string_arg1="1492"] [string_arg2="I'm a tricky one"]} }
|
168
|
+
specify {Commandable[:class_baz][:argument_list].should == %q{number_arg1 [string_arg2="blorp"] *array_arg3} }
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
it "knows it is a class method" do
|
173
|
+
Commandable["class_method"][:class_method].should be_true
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when there is a default command" do
|
179
|
+
|
180
|
+
before (:each) { load 'default_method.rb' }
|
181
|
+
|
182
|
+
it "sets a command as default" do
|
183
|
+
Commandable[:default_method][:default].should == true
|
184
|
+
end
|
185
|
+
|
186
|
+
it "raises an error if more than one command is set as default" do
|
187
|
+
lambda{load 'default_method_bad.rb'}.should raise_error(Commandable::ConfigurationError)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "executes the default command if no command is given" do
|
191
|
+
execute_output_ary(["Klaatu"]).should == ["default method called with: Klaatu"]
|
192
|
+
end
|
193
|
+
|
194
|
+
it "executes a default method and a second command" do
|
195
|
+
execute_output_ary(["Klaatu", "not_a_default_method", "28"]).sort.should == [
|
196
|
+
"default method called with: Klaatu",
|
197
|
+
"not a default method, called with: Cleveland",
|
198
|
+
"28"].sort
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
context "when there are no command line parameters" do
|
204
|
+
|
205
|
+
it "prints help info when there isn't a default command" do
|
206
|
+
execute_output_s([]).should match(/Usage:/)
|
207
|
+
end
|
208
|
+
|
209
|
+
context "when there is a default command" do
|
210
|
+
it "runs the default command if there are no require parameters " do
|
211
|
+
load "default_method.rb"
|
212
|
+
execute_output_s([]).should match(/Usage:/)
|
213
|
+
end
|
214
|
+
it "prints help/usage info if there are require parameters" do
|
215
|
+
load "default_method_no_params.rb"
|
216
|
+
execute_output_s([]).should match(/default method called, has no params/)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "when there is a required command" do
|
222
|
+
|
223
|
+
before(:each) {load "required_methods.rb"}
|
224
|
+
|
225
|
+
it "raises an error when a required command isn't given" do
|
226
|
+
lambda{ Commandable.execution_queue(["non_required_method", "this is some input"]) }.should raise_error(Commandable::MissingRequiredCommandError)
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
context "when there are deeply nested classes and modules" do
|
232
|
+
|
233
|
+
it "executes the command" do
|
234
|
+
load "super_deep_class.rb"
|
235
|
+
lambda{Commandable.execution_queue(["super_deep_method"]).first[:proc].call}.should_not raise_error
|
236
|
+
end
|
237
|
+
|
238
|
+
it "returns the correct value" do
|
239
|
+
load "super_deep_class.rb"
|
240
|
+
Commandable.execution_queue(["super_deep_method"]).first[:proc].call.should == "you called a deep method"
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|