executable 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.ruby +61 -0
  2. data/.yardopts +7 -0
  3. data/COPYING.rdoc +35 -0
  4. data/DEMO.rdoc +568 -0
  5. data/HISTORY.rdoc +55 -0
  6. data/README.rdoc +101 -51
  7. data/Schedule.reap +17 -0
  8. data/demo/00_introduction.rdoc +6 -0
  9. data/demo/01_single_command.rdoc +44 -0
  10. data/demo/02_multiple_commands.rdoc +125 -0
  11. data/demo/03_help_text.rdoc +109 -0
  12. data/demo/04_manpage.rdoc +14 -0
  13. data/demo/05_optparse_example.rdoc +152 -0
  14. data/demo/06_delegate_example.rdoc +40 -0
  15. data/demo/07_command_methods.rdoc +36 -0
  16. data/demo/08_dispatach.rdoc +29 -0
  17. data/demo/applique/ae.rb +1 -0
  18. data/demo/applique/compare.rb +4 -0
  19. data/demo/applique/exec.rb +1 -0
  20. data/demo/samples/bin/hello +31 -0
  21. data/demo/samples/man/hello.1 +22 -0
  22. data/demo/samples/man/hello.1.html +102 -0
  23. data/demo/samples/man/hello.1.ronn +19 -0
  24. data/lib/executable.rb +67 -128
  25. data/lib/executable/core_ext.rb +102 -0
  26. data/lib/executable/dispatch.rb +30 -0
  27. data/lib/executable/domain.rb +106 -0
  28. data/lib/executable/errors.rb +22 -0
  29. data/lib/executable/help.rb +430 -0
  30. data/lib/executable/parser.rb +208 -0
  31. data/lib/executable/utils.rb +41 -0
  32. data/lib/executable/version.rb +23 -0
  33. data/meta/authors +2 -0
  34. data/meta/copyrights +3 -0
  35. data/meta/created +1 -0
  36. data/meta/description +6 -0
  37. data/meta/name +1 -0
  38. data/meta/organization +1 -0
  39. data/meta/repositories +2 -0
  40. data/meta/requirements +6 -0
  41. data/meta/resources +7 -0
  42. data/meta/summary +1 -0
  43. data/meta/version +1 -0
  44. data/test/test_executable.rb +40 -19
  45. metadata +124 -68
  46. data/History.rdoc +0 -35
  47. data/NOTICE.rdoc +0 -23
  48. data/Profile +0 -30
  49. data/Version +0 -1
  50. data/meta/license/Apache2.txt +0 -177
data/.ruby ADDED
@@ -0,0 +1,61 @@
1
+ ---
2
+ source:
3
+ - meta
4
+ authors:
5
+ - name: 7rans
6
+ email: transfire@gmail.com
7
+ copyrights:
8
+ - holder: Rubyworks
9
+ year: '2008'
10
+ license: BSD-2-Clause
11
+ replacements: []
12
+ alternatives: []
13
+ requirements:
14
+ - name: qed
15
+ groups:
16
+ - test
17
+ development: true
18
+ - name: ae
19
+ groups:
20
+ - test
21
+ development: true
22
+ - name: detroit
23
+ groups:
24
+ - build
25
+ development: true
26
+ - name: simplecov
27
+ groups:
28
+ - build
29
+ development: true
30
+ dependencies: []
31
+ conflicts: []
32
+ repositories:
33
+ - uri: git://github.com/rubyworks/executable.git
34
+ scm: git
35
+ name: upstream
36
+ resources:
37
+ home: http://rubyworks.github.com/executable
38
+ code: http://github.com/rubyworks/executable
39
+ bugs: http://github.com/rubyworks/executable/issues
40
+ mail: http://groups.google.com/rubyworks-mailinglist
41
+ chat: irc://chat.us.freenode.net#rubyworks
42
+ extra: {}
43
+ load_path:
44
+ - lib
45
+ revision: 0
46
+ created: '2008-08-08'
47
+ summary: Commandline Object Mapper
48
+ version: 1.2.0
49
+ name: executable
50
+ title: Executable
51
+ description: ! 'Think of Executable as a *COM*, a Commandline Object Mapper,
52
+
53
+ in much the same way that ActiveRecord is an ORM,
54
+
55
+ an Object Relational Mapper. A class utilizing Executable
56
+
57
+ can define a complete command line tool using nothing more
58
+
59
+ than Ruby''s own method definitions.'
60
+ organization: rubyworks
61
+ date: '2012-01-31'
@@ -0,0 +1,7 @@
1
+ --title "Executable"
2
+ --readme README.rdoc
3
+ --protected
4
+ --private
5
+ lib
6
+ -
7
+ [A-Z]*.*
@@ -0,0 +1,35 @@
1
+ = COPYRIGHT
2
+
3
+ == NOTICES
4
+
5
+ === Executable
6
+
7
+ Copyright:: (c) 2012 Rubyworks. All rights reserved.
8
+ License:: (r) BSD-2-Clause
9
+ Website:: http://rubyworks.github.com/assay
10
+
11
+
12
+ == LICENSES
13
+
14
+ === BSD-2-Clause License
15
+
16
+ Redistribution and use in source and binary forms, with or without
17
+ modification, are permitted provided that the following conditions are met:
18
+
19
+ 1. Redistributions of source code must retain the above copyright notice,
20
+ this list of conditions and the following disclaimer.
21
+
22
+ 2. Redistributions in binary form must reproduce the above copyright
23
+ notice, this list of conditions and the following disclaimer in the
24
+ documentation and/or other materials provided with the distribution.
25
+
26
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
27
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
28
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29
+ COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
33
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,568 @@
1
+ = Executable
2
+
3
+ Require Executable library.
4
+
5
+ require 'executable'
6
+
7
+
8
+ == No Subcommmands
9
+
10
+ This example demonstrates using Executable::Command to create a simple command line
11
+ interface without subcommands. (Note the Executable mixin could be used just
12
+ as well).
13
+
14
+ class NoSubCommandCLI < Executable::Command
15
+
16
+ attr :result
17
+
18
+ def o?
19
+ @o
20
+ end
21
+
22
+ def o=(flag)
23
+ @o = flag
24
+ end
25
+
26
+ def call
27
+ if o?
28
+ @result = "with"
29
+ else
30
+ @result = "without"
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ Execute the CLI on an example command line.
37
+
38
+ cli = NoSubCommandCLI.run('')
39
+ cli.result.assert == 'without'
40
+
41
+ Execute the CLI on an example command line.
42
+
43
+ cli = NoSubCommandCLI.run('-o')
44
+ cli.result.assert == 'with'
45
+
46
+ There are two important things to notices heres. Frist, that #main is being
47
+ called in each case. It is the method called with no other subcommands are
48
+ defined. And second, the fact the a `o?` method is defined to compliment the
49
+ `o=` writer, informs Executable that `-o` is an option _flag_, not taking
50
+ any parameters.
51
+
52
+
53
+ == Multiple Subcommmands
54
+
55
+ Setup an example CLI subclass.
56
+
57
+ class MyCLI < Executable::Command
58
+ attr :result
59
+
60
+ def initialize
61
+ @result = []
62
+ end
63
+
64
+ def g=(value)
65
+ @result << "g" if value
66
+ end
67
+
68
+ def g?
69
+ @result.include?("g")
70
+ end
71
+
72
+ #
73
+ class C1 < self
74
+ def call
75
+ @result << "c1"
76
+ end
77
+
78
+ def o1=(value)
79
+ @result << "c1_o1 #{value}"
80
+ end
81
+
82
+ def o2=(value)
83
+ @result << "c1_o2 #{value}"
84
+ end
85
+ end
86
+
87
+ #
88
+ class C2 < Executable::Command
89
+ attr :result
90
+
91
+ def initialize
92
+ @result = []
93
+ end
94
+
95
+ def call
96
+ @result << "c2"
97
+ end
98
+
99
+ def o1=(value)
100
+ @result << "c2_o1 #{value}"
101
+ end
102
+
103
+ def o2=(value)
104
+ @result << "c2_o2" if value
105
+ end
106
+
107
+ def o2?
108
+ @result.include?("c2_o2")
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ Instantiate and run the class on an example command line.
115
+
116
+ Just a command.
117
+
118
+ cli = MyCLI.run('c1')
119
+ cli.result.assert == ['c1']
120
+
121
+ Command with global option.
122
+
123
+ cli = MyCLI.run('c1 -g')
124
+ cli.result.assert == ['g', 'c1']
125
+
126
+ Command with an option.
127
+
128
+ cli = MyCLI.run('c1 --o1 A')
129
+ cli.result.assert == ['c1_o1 A', 'c1']
130
+
131
+ Command with two options.
132
+
133
+ cli = MyCLI.run('c1 --o1 A --o2 B')
134
+ cli.result.assert == ['c1_o1 A', 'c1_o2 B', 'c1']
135
+
136
+ Try out the second command.
137
+
138
+ cli = MyCLI.run('c2')
139
+ cli.result.assert == ['c2']
140
+
141
+ Seoncd command with an option.
142
+
143
+ cli = MyCLI.run('c2 --o1 A')
144
+ cli.result.assert == ['c2_o1 A', 'c2']
145
+
146
+ Second command with two options.
147
+
148
+ cli = MyCLI.run('c2 --o1 A --o2')
149
+ cli.result.assert == ['c2_o1 A', 'c2_o2', 'c2']
150
+
151
+ Since C1#main takes not arguments, if we try to issue a command
152
+ that will have left over arguments, then an ArgumentError will be raised.
153
+
154
+ expect ArgumentError do
155
+ cli = MyCLI.run('c1 a')
156
+ end
157
+
158
+ How about a non-existenct subcommand.
159
+
160
+ expect NotImplementedError do
161
+ cli = MyCLI.run('q')
162
+ cli.result.assert == ['q']
163
+ end
164
+
165
+ How about an option only.
166
+
167
+ expect NotImplementedError do
168
+ cli = MyCLI.run('-g')
169
+ cli.result.assert == ['-g']
170
+ end
171
+
172
+ How about a non-existant options.
173
+
174
+ expect Executable::NoOptionError do
175
+ MyCLI.run('c1 --foo')
176
+ end
177
+
178
+
179
+ == Command Help
180
+
181
+ Executable Commands can generate help output. It does this by extracting
182
+ the commenst associated with the option methods. A description of the
183
+ command itself is taken from the comment on the `#call` method. Only
184
+ the first line of a comment is used, so the reset of the comment can
185
+ still be catered to documention tools such as YARD and RDoc.
186
+
187
+ Let's setup an example CLI subclass to demonstrate this.
188
+
189
+ class MyCLI < Executable::Command
190
+
191
+ # This is global option -g.
192
+ # Yadda yadda yadda...
193
+ def g=(bool)
194
+ @g = bool
195
+ end
196
+
197
+ def g?; @g; end
198
+
199
+ # Subcommand `c1`.
200
+ class C1 < self
201
+
202
+ # This does c1.
203
+ def call(*args)
204
+ end
205
+
206
+ # This is option --o1 for c1.
207
+ def o1=(value)
208
+ end
209
+
210
+ # This is option --o2 for c1.
211
+ def o2=(value)
212
+ end
213
+
214
+ end
215
+
216
+ # Subcommand `c2`.
217
+ class C2 < self
218
+
219
+ # This does c2.
220
+ def call(*args)
221
+ end
222
+
223
+ # This is option --o1 for c2.
224
+ def o1=(value)
225
+ end
226
+
227
+ # This is option --o2 for c2.
228
+ def o2=(value)
229
+ end
230
+
231
+ end
232
+
233
+ end
234
+
235
+ === Plain Text
236
+
237
+ The help output,
238
+
239
+ @out = MyCLI::C1.help.to_s
240
+
241
+ should be clearly laid out as follows:
242
+
243
+ Usage: mycli-c1 [options...] [subcommand]
244
+
245
+ This does c1.
246
+
247
+ OPTIONS
248
+ -g This is global option -g.
249
+ --o1=VALUE This is option --o1 for c1.
250
+ --o2=VALUE This is option --o2 for c1.
251
+
252
+ Copyright (c) 2012
253
+
254
+ === Markdown
255
+
256
+ The help feature can also output ronn-style markdown,
257
+
258
+ @out = MyCLI::C1.help.markdown
259
+
260
+ should be clearly laid out as follows:
261
+
262
+ mycli-c1(1) - This does c1.
263
+ ===========================
264
+
265
+ ## SYNOPSIS
266
+
267
+ `mycli-c1` [options...] [subcommand]
268
+
269
+ ## DESCRIPTION
270
+
271
+ This does c1.
272
+
273
+ ## OPTIONS
274
+
275
+ * `-g`:
276
+ This is global option -g.
277
+
278
+ * `--o1=VALUE`:
279
+ This is option --o1 for c1.
280
+
281
+ * `--o2=VALUE`:
282
+ This is option --o2 for c1.
283
+
284
+ ## COPYRIGHT
285
+
286
+ Copyright (c) 2012
287
+
288
+
289
+ == Manpage
290
+
291
+ If a man page is available for a given command using the #show_help
292
+ method will automatically find the manpage and display it.
293
+
294
+ sample = File.dirname(__FILE__) + '/samples'
295
+
296
+ load(sample + '/bin/hello')
297
+
298
+ manpage = Hello.cli.manpage
299
+
300
+ manpage.assert == sample + '/man/hello.1'
301
+
302
+
303
+
304
+ == OptionParser Example
305
+
306
+ This example mimics the one given in optparse.rb documentation.
307
+
308
+ require 'ostruct'
309
+ require 'time'
310
+
311
+ class ExampleCLI < Executable::Command
312
+
313
+ CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
314
+ CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
315
+
316
+ attr :options
317
+
318
+ def initialize
319
+ super
320
+ reset
321
+ end
322
+
323
+ def reset
324
+ @options = OpenStruct.new
325
+ @options.library = []
326
+ @options.inplace = false
327
+ @options.encoding = "utf8"
328
+ @options.transfer_type = :auto
329
+ @options.verbose = false
330
+ end
331
+
332
+ # Require the LIBRARY before executing your script
333
+ def require=(lib)
334
+ options.library << lib
335
+ end
336
+ alias :r= :require=
337
+
338
+ # Edit ARGV files in place (make backup if EXTENSION supplied)
339
+ def inplace=(ext)
340
+ options.inplace = true
341
+ options.extension = ext
342
+ options.extension.sub!(/\A\.?(?=.)/, ".") # ensure extension begins with dot.
343
+ end
344
+ alias :i= :inplace=
345
+
346
+ # Delay N seconds before executing
347
+ # Cast 'delay' argument to a Float.
348
+ def delay=(n)
349
+ options.delay = n.to_float
350
+ end
351
+
352
+ # Begin execution at given time
353
+ # Cast 'time' argument to a Time object.
354
+ def time=(time)
355
+ options.time = Time.parse(time)
356
+ end
357
+ alias :t= :time=
358
+
359
+ # Specify record separator (default \\0)
360
+ # Cast to octal integer.
361
+ def irs=(octal)
362
+ options.record_separator = octal.to_i(8)
363
+ end
364
+ alias :F= :irs=
365
+
366
+ # Example 'list' of arguments
367
+ # List of arguments.
368
+ def list=(args)
369
+ options.list = list.split(',')
370
+ end
371
+
372
+ # Keyword completion. We are specifying a specific set of arguments (CODES
373
+ # and CODE_ALIASES - notice the latter is a Hash), and the user may provide
374
+ # the shortest unambiguous text.
375
+ CODE_LIST = (CODE_ALIASES.keys + CODES)
376
+
377
+ help.option(:code, "Select encoding (#{CODE_LIST})")
378
+
379
+ # Select encoding
380
+ def code=(code)
381
+ codes = CODE_LIST.select{ |x| /^#{code}/ =~ x }
382
+ codes = codes.map{ |x| CODE_ALIASES.key?(x) ? CODE_ALIASES[x] : x }.uniq
383
+ raise ArgumentError unless codes.size == 1
384
+ options.encoding = codes.first
385
+ end
386
+
387
+ # Select transfer type (text, binary, auto)
388
+ # Optional argument with keyword completion.
389
+ def type=(type)
390
+ raise ArgumentError unless %w{text binary auto}.include(type.downcase)
391
+ options.transfer_type = type.downcase
392
+ end
393
+
394
+ # Run verbosely
395
+ # Boolean switch.
396
+ def verbose=(bool)
397
+ options.verbose = bool
398
+ end
399
+ def verbose?
400
+ @options.verbose
401
+ end
402
+ alias :v= :verbose=
403
+ alias :v? :verbose?
404
+
405
+ # Show this message
406
+ # No argument, shows at tail. This will print an options summary.
407
+ def help!
408
+ puts help_text
409
+ exit
410
+ end
411
+ alias :h! :help!
412
+
413
+ # Show version
414
+ # Another typical switch to print the version.
415
+ def version?
416
+ puts Executor::VERSION
417
+ exit
418
+ end
419
+
420
+ #
421
+ def call
422
+ # ... main procedure here ...
423
+ end
424
+ end
425
+
426
+ We will run some scenarios on this example to make sure it works.
427
+
428
+ cli = ExampleCLI.execute('-r=facets')
429
+ cli.options.library.assert == ['facets']
430
+
431
+ Make sure time option parses.
432
+
433
+ cli = ExampleCLI.execute('--time=2010-10-10')
434
+ cli.options.time.assert == Time.parse('2010-10-10')
435
+
436
+ Make sure code lookup words and is limted to the selections provided.
437
+
438
+ cli = ExampleCLI.execute('--code=ji')
439
+ cli.options.encoding.assert == 'iso-2022-jp'
440
+
441
+ expect ArgumentError do
442
+ ExampleCLI.execute('--code=xxx')
443
+ end
444
+
445
+ Ensure +irs+ is set to an octal number.
446
+
447
+ cli = ExampleCLI.execute('-F 32')
448
+ cli.options.record_separator.assert == 032
449
+
450
+ Ensure extension begins with dot and inplace is set to true.
451
+
452
+ cli = ExampleCLI.execute('--inplace txt')
453
+ cli.options.extension.assert == '.txt'
454
+ cli.options.inplace.assert == true
455
+
456
+
457
+ = Subclass Example
458
+
459
+ Lets say we have a class that we would like to work with on
460
+ the command line, but want to keep the class itself unchanaged
461
+ without mixin.
462
+
463
+ class Hello
464
+ attr_accessor :name
465
+
466
+ def initialize(name="World")
467
+ @name = name
468
+ end
469
+
470
+ def hello
471
+ @output = "Hello, #{name}!"
472
+ end
473
+
474
+ def output
475
+ @output
476
+ end
477
+ end
478
+
479
+ Rather then including Exectuable in the class directly, we can
480
+ create a subclass and use it instead.
481
+
482
+ class HelloCommand < Hello
483
+ include Executable
484
+
485
+ def call(*args)
486
+ hello
487
+ end
488
+ end
489
+
490
+ Now we can execute the command perfectly well.
491
+
492
+ cmd = HelloCommand.execute(['hello', '--name=Fred'])
493
+ cmd.output.assert == "Hello, Fred!"
494
+
495
+ And the original class remains undisturbed.
496
+
497
+
498
+ = README Example
499
+
500
+ This is the example used in the documentation.
501
+
502
+ class Example
503
+ include Executable
504
+
505
+ attr_switch :quiet
506
+
507
+ def bread(*args)
508
+ ["bread", quiet?, *args]
509
+ end
510
+
511
+ def butter(*args)
512
+ ["butter", quiet?, *args]
513
+ end
514
+
515
+ # Route call to methods.
516
+ def call(name, *args)
517
+ meth = public_method(name)
518
+ meth.call(*args)
519
+ end
520
+ end
521
+
522
+ Use a subcommand and an argument.
523
+
524
+ c, a = Example.parse(['butter', 'yum'])
525
+ r = c.call(*a)
526
+ r.assert == ["butter", nil, "yum"]
527
+
528
+ A subcommand and a boolean option.
529
+
530
+ c, a = Example.parse(['bread', '--quiet'])
531
+ r = c.call(*a)
532
+ r.assert == ["bread", true]
533
+
534
+
535
+ == Legacy/Dispath
536
+
537
+ The Dispatch mixin, which is also called Legacy b/c this is how older
538
+ version of Executable worked, provides Executable with a `#call` method
539
+ that automatically routes the to a method given by the first argument.
540
+
541
+ class DispatchExample < Executable::Command
542
+ include Legacy
543
+
544
+ attr :result
545
+
546
+ def foo
547
+ @result = :foo
548
+ end
549
+
550
+ def bar
551
+ @result = :bar
552
+ end
553
+
554
+ end
555
+
556
+ Now when we invoke the command, the
557
+
558
+ eg = DispatchExample.run('foo')
559
+ eg.result.assert == :foo
560
+
561
+ eg = DispatchExample.run('bar')
562
+ eg.result.assert == :bar
563
+
564
+
565
+
566
+
567
+
568
+