executable 1.1.0 → 1.2.0

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 (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
+