commandable 0.2.0.beta01

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +2 -0
  3. data/LICENCE +19 -0
  4. data/README.markdown +409 -0
  5. data/Rakefile +29 -0
  6. data/_testing/alias_trap.rb +14 -0
  7. data/autotest/discover.rb +2 -0
  8. data/bin/commandable +18 -0
  9. data/commandable.gemspec +24 -0
  10. data/lib/commandable.rb +4 -0
  11. data/lib/commandable/app_controller.rb +47 -0
  12. data/lib/commandable/commandable.rb +394 -0
  13. data/lib/commandable/exceptions.rb +61 -0
  14. data/lib/commandable/version.rb +4 -0
  15. data/lib/monkey_patch/file_utils.rb +11 -0
  16. data/spec/commandable/command_line_execution_spec.rb +154 -0
  17. data/spec/commandable/commandable_spec.rb +245 -0
  18. data/spec/commandable/help_generator_spec.rb +169 -0
  19. data/spec/commandable/helpers_spec.rb +17 -0
  20. data/spec/commandable/reset_spec.rb +26 -0
  21. data/spec/commandable/xor_groups_spec.rb +43 -0
  22. data/spec/source_code_examples/class_command_no_command.rb +27 -0
  23. data/spec/source_code_examples/class_methods.rb +20 -0
  24. data/spec/source_code_examples/class_methods_nested.rb +31 -0
  25. data/spec/source_code_examples/command_no_command.rb +27 -0
  26. data/spec/source_code_examples/deep_class.rb +14 -0
  27. data/spec/source_code_examples/default_method.rb +17 -0
  28. data/spec/source_code_examples/default_method_no_params.rb +17 -0
  29. data/spec/source_code_examples/multi_line_description.rb +17 -0
  30. data/spec/source_code_examples/multi_line_description_no_params.rb +17 -0
  31. data/spec/source_code_examples/no_description.rb +10 -0
  32. data/spec/source_code_examples/parameter_class.rb +27 -0
  33. data/spec/source_code_examples/parameter_free.rb +22 -0
  34. data/spec/source_code_examples/required_methods.rb +18 -0
  35. data/spec/source_code_examples/super_deep_class.rb +28 -0
  36. data/spec/source_code_examples/test_class.rb +13 -0
  37. data/spec/source_code_examples/xor_class.rb +37 -0
  38. data/spec/source_code_for_errors/class_bad.rb +7 -0
  39. data/spec/source_code_for_errors/default_method_bad.rb +17 -0
  40. data/spec/source_code_for_errors/private_methods_bad.rb +10 -0
  41. data/spec/spec_helper.rb +55 -0
  42. 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,4 @@
1
+ module Commandable
2
+ # The application's version number
3
+ VERSION = "0.2.0.beta01"
4
+ end
@@ -0,0 +1,11 @@
1
+ require 'fileutils'
2
+
3
+ module FileUtils
4
+ # Monkeypatch FileUtils really annoying directory copying
5
+ def copy_dir(source, dest)
6
+ files = Dir.glob("#{source}/**")
7
+ mkdir_p dest
8
+ cp_r files, dest
9
+ end
10
+ module_function :copy_dir
11
+ 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