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,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
6
+ doc/**/*
7
+ rdoc/**/*
8
+ vendor/**/*
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/LICENCE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Mike Bethany
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,409 @@
1
+ # Commandable
2
+ The easiest way to add command line control to your Ruby app. If you don't find **Commandable** incredibly easy to use I will give you one billions dollars!\* (\*not a legally binding offer)
3
+
4
+ Stop wasting time writing wet command line interpreters or even writing code for the existing ones. Then writing help functions that you have to constantly change as your code changes. Now you can add a single line above an existing method and that method will be available from the command line. Best of all the help/usage instructions are automatically generated using the method itself so if you change your methods the help instructions change without any more effort on your part!
5
+
6
+ The whole process can take as little as four lines of code:
7
+
8
+ * You put a `command "I do something!"` line above your method.
9
+ * Add a `require 'commandable'` line somewhere (I'd put it in my bin).
10
+ * Then an `extend Commandable` inside your class.
11
+ * And finally a call to `Commandable.execute(ARGV)` in your bin file.
12
+
13
+ Don't think of **Commandable** as a way to add command line switches to your app but as a way to allow your app to be driven directly from the command line. No more confusing switches that mean one thing in one program and something completely different in another. (Can you believe some apps actually use `-v` for something other than "version" and `-h` for something other than "help?" Madness I say! Madness!)
14
+
15
+ You can now "use your words" to let people interact with your apps in a natural way.
16
+
17
+ ## Status
18
+
19
+ 2011-03-17 - Final testing and building the example app. You could use it now, it's in feature freeze except for adding the example app (Widget).
20
+
21
+ ## Installation
22
+ From the command line:
23
+
24
+ $ [sudo] gem install commandable
25
+
26
+
27
+ ## Usage Instructions
28
+
29
+ After installing the **Commandable** gem require it somewhere that gets loaded before your class does:
30
+
31
+ require 'commandable'
32
+
33
+ Extend your class with the **Commandable** module:
34
+
35
+ class Widget
36
+ extend Commandable
37
+
38
+ Then put `command` and a description above the method you want to make accessible. The description is optional but can be helpful
39
+ since it's used when automatically building your help/usage instructions.
40
+
41
+ command "create a new widget"
42
+ def new(name)
43
+ ...
44
+ end
45
+
46
+ ### The "`command`" command and its options
47
+
48
+ command ["description"], [:required], [:default], [:priority=>(0...n)], [:xor[=>:group_name]]
49
+
50
+ _**command**_ _(required)_
51
+ This is the only thing that's required. It tells **Commandable** to add the method that follows to the list of methods available from the command line.
52
+
53
+ _**description**_ [optional]
54
+ As you would imagine this is a short description of what the method does. You can have multiple lines by using a new line, `\n`, in the description and your description will be lined up properly. This prints in the help/usage instructions when a user calls your programing using the command "help" or if they try to issue a command that doesn't exist. Help instructions will also print if they try to use your app without any parameters (if there isn't a default method that doesn't require parameters.).
55
+
56
+ _**:required**_ [optional]
57
+ You can mark a method as required and the user must specify this command and any required parameters every time they run your app. Note that while you can have a method marked as both :default and :required that would be kind of pointless since :required means they have to type out the name of the function and :default means they don't.
58
+
59
+ _**:default**_ [optional]
60
+ You can have one and only one default method. This method will be called if your app is called with just parameters or if the first command line parameter isn't a command. The user can still give more commands after the parameters for the default command too.
61
+
62
+ For instance say your default method is :foo that takes one parameter and you have another method called :bar that also takes one parameter. A user could do this:
63
+
64
+ yourapp "Some Parameter" bar "A parameter for bar"
65
+
66
+ Just be aware that if they give an option that has the same name as a function the app will think it's a command.
67
+
68
+ _**priority=>n**_ [optional]
69
+ This optional setting allows you to assign priorities to your methods so if you need them to be executed in a specific order, regardless of how the user specifies them on the command line, you can use this. Then when you execute the command line or ask for a queue of commands they will be sorted for you by priority.
70
+
71
+ The higher the priority the sooner the method will be executed. If you do not specify a priority a method will have a priority of 0, the lowest priority.
72
+
73
+ Note that you can have a default method with a lower priority than a non-default method.
74
+
75
+ _**:xor[=>:whatever]**_ [optional]
76
+ The :xor parameter allows you to configure a group of methods as mutually exclusive, i.e. if method1 and method2 are in the same :xor group the user of your application can only call one of them at a time.
77
+
78
+ You can use just the :xor symbol and the method will be put into the default XOR group, called :xor so :xor=>:xor, but if you need multiple XOR groups you can specify a group name by using a hash instead of just the :xor symbol.
79
+
80
+ The XOR group name will be printed in the front to the description text so it's probably a good idea to use :xor as the prefix.
81
+
82
+
83
+ ### Parameter lists
84
+ The parameter lists for each method that are printed out in the usage/help instructions are built using the names you give them so you should make sure to use descriptive names. Also keep in mind that all command line parameters are strings so you need to deal with that inside your methods if what you really want is a number.
85
+
86
+ If none of your methods have parameters then there won't be any reference to parameters in the help/usage instructions.
87
+
88
+ ### A complete class
89
+
90
+ A complete class might look like this:
91
+
92
+ require 'commandable'
93
+
94
+ class Widget
95
+ extend Commandable
96
+
97
+ command "create a new widget", :default, :priority=>10
98
+ def new(name)
99
+ "You made a widget named: #{name}"
100
+ end
101
+
102
+ command "destroy an existing widget", :xor
103
+ def disassemble(name)
104
+ "No dissaemble #{name}! #{name} is alive!"
105
+ end
106
+
107
+ command "spend lots of money to update a widget", :xor
108
+ def upgrade(name)
109
+ "You just gave #{name} a nice new coat of paint!"
110
+ end
111
+
112
+ end
113
+
114
+ #### Class methods
115
+ You can also use it on class methods:
116
+
117
+ require "commandable"
118
+
119
+ class ClassMethods
120
+ extend Commandable
121
+
122
+ command 'does some stuff'
123
+ def self.class_method(string_arg1)
124
+ ...
125
+ end
126
+
127
+ # The first class method will get the command
128
+ command "another one"
129
+ class << self
130
+ def class_method2(integer_arg1)
131
+ ...
132
+ end
133
+ end
134
+ end
135
+
136
+ If you want to do a block of class commands using `class << self` you need to put `extend Commandable` inside the block:
137
+
138
+ require "commandable"
139
+
140
+ class ClassMethodsNested
141
+ class << self
142
+ extend Commandable
143
+
144
+ command "hello world"
145
+ def class_foo(int_arg1, number_arg2)
146
+ [int_arg1, number_arg2]
147
+ end
148
+
149
+ command "look a function"
150
+ def class_bar(int_arg1, string_arg2="Number 42")
151
+ [int_arg1, string_arg2]
152
+ end
153
+ end
154
+ end
155
+
156
+
157
+ Note: Class methods are called directly on the class while instance methods have an instance created for that call. Keep that in mind if you need to share data between calls. In that case you might want to store your data in a model you create outside the execution queue.
158
+
159
+ ### Automatic usage/help generation ###
160
+
161
+ One of the great features of **Commandable** is that it will automatically create usage instructions based on your methods and the descriptions you provide for them. The `help` command is also added for you automatically. If your app has no default or it has a default command that requires parameters the help/usage instructions will be printed if a user just runs your app without any input.
162
+
163
+ A typical help output looks something like this:
164
+
165
+ Commandable - The easiest way to add command line control to your app.
166
+ Copyrighted free software - Copyright (c) 2011 Mike Bethany.
167
+ Version: 0.2.0.beta01
168
+
169
+ Usage: commandable <command> [parameters] [<command> [parameters]...]
170
+
171
+ Command Parameters Description
172
+ error : Will raise a programmer error, not a user error
173
+ so you see what happens when you have bad code
174
+ examples [path] : Copies the test classes to a folder so
175
+ you can see a bunch of small examples
176
+ readme : displays the readme file (default)
177
+ v : <xor> Application Version
178
+ version : <xor> Application Version
179
+ widget [path] : Copies a fully working app demonstrating how
180
+ to use **Commandable** with RSpec and Cucumber
181
+ help : you're looking at it now
182
+
183
+
184
+
185
+ ### Complete demonstration app and some example classes ###
186
+ For a fully working example with RSpec and Cucumber tests run this command:
187
+
188
+ $ commandable widget [path]
189
+
190
+ If you would like to see a bunch of simple classes that demonstrate its uses run:
191
+
192
+ $ commandable example [path]
193
+
194
+ ### Commandable Options
195
+
196
+ There are the basic options you will want to be aware of. Specifically you really want to set `Commandable#app_name` and `Commandable#app_info` so that the help/usage instructions are fully fleshed out.
197
+
198
+ **Commandable.app\_name**
199
+ _default = ""_
200
+ Set this and the help instructions will include your application's name.
201
+
202
+ **Commandable.app\_info**
203
+ _default = ""_
204
+ This is informational text that will print above the help/usage instructions.
205
+
206
+ **Commandable.verbose\_parameters**
207
+ _default = true_
208
+ If set to false help instructions will not print out default values.
209
+ **Important!** This should only be set once, before any files load. Changing the value after files are loaded will make no difference since parameters are only parsed when the source file loads.
210
+
211
+ Commandable.verbose_parameters = true
212
+ # Will print:
213
+ command arg1 [arg2="default value"]
214
+
215
+ Commandable.verbose_parameters = false
216
+ # Will print:
217
+ command arg1 [arg2]
218
+
219
+ ### Colorized Output Options
220
+
221
+ The help information can be colored using the standard ANSI escape commands found in the `term-ansicolor-hi` gem. The `term-ansicolor-hi` gem it installed as a dependency but just in case you can install it yourself by running:
222
+
223
+ $ [sudo] gem install term-ansicolor-hi
224
+
225
+ Then you can do something like this:
226
+
227
+ require 'term/ansicolor'
228
+
229
+ c = Term::ANSIColor
230
+
231
+ Commandable.color_app_info = c.intense_white + c.bold
232
+ Commandable.color_app_name = c.intense_green + c.bold
233
+ Commandable.color_command = c.intense_yellow
234
+ Commandable.color_description = c.intense_white
235
+ Commandable.color_parameter = c.intense_cyan
236
+ Commandable.color_usage = c.intense_black + c.bold
237
+
238
+ Commandable.color_error_word = c.intense_black + c.bold
239
+ Commandable.color_error_name = c.intense_red + c.bold
240
+ Commandable.color_error_description = c.intense_white + c.bold
241
+
242
+ ###Color options
243
+
244
+ **Commandable.color\_output**
245
+ _default = false_
246
+ Set to true to enable colorized help/usage instructions.
247
+
248
+ **Commandable.color\_app\_info**
249
+ _default = intense\_white_ + bold
250
+ The color the app_info text will be in the help message
251
+
252
+ **Commandable.color\_app\_name**
253
+ _default = intense\_green_ + bold
254
+ The color the app_name will be in the usage line in the help message
255
+
256
+ **Commandable.color\_command**
257
+ _default = intense\_yellow_
258
+ The color the word "command" and the commands themselves will be in the help message
259
+
260
+ **Commandable.color\_description**
261
+ _default = intense\_white_
262
+ The color the word "command" and the commands themselves will be in the help message
263
+
264
+ **Commandable.color\_parameter**
265
+ _default = intense\_cyan_
266
+ The color the word "parameter" and the parameters themselves will be in the help message
267
+
268
+ **Commandable.color\_usage**
269
+ _default = intense\_black_ + bold
270
+ The color the word "Usage:" will be in the help message.
271
+
272
+ **Commandable.color\_error\_word**
273
+ _default = intense\_white_
274
+ The color the word "Error:" text will be in error messages
275
+
276
+ **Commandable.color\_error\_name**
277
+ _default = intense\_cyan_
278
+ The color the friendly name of the error will be in error messages
279
+
280
+ **Commandable.color\_error\_description**
281
+ _default = intense\_black_ + bold
282
+ The color the error description will be in error messages
283
+
284
+
285
+ ### Executing the Command Line
286
+
287
+ There are two ways of using **Commandable** to run your methods. You can use its built in execute method to automatically run whatever is entered on the command line or you can have **Commandable** build an array of procs that you can execute yourself. This allows you to have finer grain control over the execution of the commands as you can deal with the return values as you run each command.
288
+
289
+ ### The Easy way
290
+
291
+ **Commandable#execution_queue(ARGV)**
292
+
293
+ Now that you've added a command to a method you can send the command line arguments (ARGV) to `Commandable#execution_queue` and it will generate an array of procs you should run sorted in the order of priority you specified when creating the commands.
294
+
295
+ # execution_queue returns an array of hashes which
296
+ # in turn contains the method name keyed to :method
297
+ # and a proc key to, you guessed it, :proc
298
+ # It looks like this:
299
+ # [{:method => :method_name, :xor=>(:xor group or nil), :parameters=>[...], :priority, :proc => #<proc:>}, ...]
300
+ #
301
+ # The array is automatically sorted by priority (higher numbers first, 10 > 0)
302
+
303
+ # First get the array of commands
304
+ command_queue = Commandable.execution_queue(ARGV) # no need to give it ARGV, it's there for testing
305
+
306
+ return_values = (command_queue.shift)[:proc].call
307
+ # do something with the return values
308
+
309
+ # check for more values however you want
310
+ more_return_values = command_queue.shift[:proc].call unless command_queue.empty?
311
+
312
+ If you need a little more control:
313
+
314
+ # First get the array of commands
315
+ command_queue = Commandable.execution_queue(ARGV) # no need to give it ARGV
316
+
317
+ # Loop through the array calling the commands and dealing with the results
318
+ command_queue.each do |cmd|
319
+
320
+ # If you need more data about the method you can
321
+ # get the method properties from Commandable[]
322
+ method_name = cmd[:method]
323
+ description = Commandable[method_name][:description]
324
+ puts description
325
+
326
+ return_values = cmd[:proc].call
327
+
328
+ case method_name
329
+ when :some_method
330
+ # do something with the return values
331
+ # based on it being some_method
332
+ when :some_other_method
333
+ # do something with the return values
334
+ # based on it being some_other_method
335
+ else
336
+ # do something by default
337
+ end
338
+
339
+ end
340
+
341
+ ### The easiest way
342
+
343
+ **Commandable.execute(ARGV)**
344
+
345
+ The easiest way to use **Commandable** is to just let it do all the work. This works great if all you need to do is make your methods available from the command line. You can also design a controller class with **Commandable** in mind and run all you command from there.
346
+
347
+ When you call the `Commandable#execute` method it will return an array of hashes for each method called. Each hash in the array contains the method name and its return values.
348
+
349
+ [:method_name=>[return,values,array], :method_name2=>[return,values,array],...]
350
+
351
+ Simply configure your bin file to run `Commandable#execute`:
352
+
353
+ **[your Ruby app directory]/bin/your\_app\_name**
354
+
355
+ #!/usr/bin/env ruby
356
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
357
+ require 'yourappname'
358
+ require 'commandable'
359
+ Commandable.verbose_parameters = false
360
+ Commandable.color_output = true
361
+ Commandable.app_name = "My App"
362
+ Commandable.app_info =
363
+ """
364
+ My App - It does stuff and things!
365
+ Copyright (c) 2011 Acme Inc.
366
+ """
367
+ return_values = Commandable.execute(ARGV)
368
+ # do stuff
369
+
370
+
371
+ I actually prefer to create a separate file for my **Commandable** configuration and load it in my apps main file in the `lib` directory. Just make sure you load the **Commandable** configuration file first. For a complete example of this run `commandable widget [path]` and it will copy the example app to the directory you specify.
372
+
373
+ ## In closing... ##
374
+
375
+ One really cool thing about this design is you can extend another app and add your own command line controls without having to crack open their code. The other app doesn't even have to use **Commandable**. You can just write your own methods that call the methods of the original program.
376
+
377
+ I should also say the code is really, really ugly right now. Thats the very next thing I will be working on for this project. This is the "rough draft" version that works perfectly well but is very ugly code-wise. I needed to use it right now so am putting it out in beta.
378
+
379
+ If you have any questions about how the code works I've tried to give as much info in these docs as possible but I am also an OCD level commenter so you should be able to find fairly good explanations of what I'm doing in the code.
380
+
381
+ Most of all it should be simple to use so if you have any problems please drop me a line. Also if you make any changes please send me a pull request. I hate people that don't respond to them, even to deny them, so I'm pretty good about that sort of thing.
382
+
383
+
384
+ ## To Do
385
+
386
+ Still working on for this version:
387
+
388
+ * Finish **Widget** example app. It will be in the release version but I want to release a beta so I can test the gem via RubyGems.
389
+
390
+ ###Next version:
391
+
392
+ * Needs a massive refactoring.
393
+ * Generator to add Commandable support to your app.
394
+ * Reorganize docs to be more logical and less the result of my scribblings as I develop.
395
+ * Try to figure out how to trap `alias` without an additional `command` use
396
+ * Better formatting of help instructions, the existing one is fairly ugly.
397
+ * Use comments below `command` as the description text so you don't have to repeat yourself to get RDoc to give you docs for your functions.
398
+ * Clean up RSpecs. I'm doing too many ugly tests of internal state instead of specifying behavior.
399
+ * Allow sorting of commands alphabetically or by priority
400
+
401
+ ###Future versions:
402
+
403
+ * Accepting your suggestions...
404
+ * Make the help/usage directions format available to programmers without having to hack the code.
405
+ * More edge case testing.
406
+ * Allow optional parameters values to be reloaded so changing things like verbose_parameters makes the command list change. (**very** low priority)
407
+
408
+
409
+ .
@@ -0,0 +1,29 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rake"
5
+ require "rake/rdoctask"
6
+ require "rspec/core/rake_task"
7
+
8
+ desc "Run all examples"
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.rspec_opts = %w[--color]
11
+ t.verbose = false
12
+ end
13
+
14
+ task :default => [:spec, :build]
15
+
16
+ task :clobber do
17
+ rm_rf 'pkg'
18
+ rm_rf 'tmp'
19
+ rm_rf 'coverage'
20
+ end
21
+
22
+ Rake::RDocTask.new do |rdoc|
23
+ rdoc.rdoc_dir = 'rdoc'
24
+ rdoc.title = "commandable #{Commandable::VERSION}"
25
+ rdoc.rdoc_files.exclude('autotest/*')
26
+ rdoc.rdoc_files.exclude('spec/**/*')
27
+ rdoc.rdoc_files.include('README*')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end