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.
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