executable 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +61 -0
- data/.yardopts +7 -0
- data/COPYING.rdoc +35 -0
- data/DEMO.rdoc +568 -0
- data/HISTORY.rdoc +55 -0
- data/README.rdoc +101 -51
- data/Schedule.reap +17 -0
- data/demo/00_introduction.rdoc +6 -0
- data/demo/01_single_command.rdoc +44 -0
- data/demo/02_multiple_commands.rdoc +125 -0
- data/demo/03_help_text.rdoc +109 -0
- data/demo/04_manpage.rdoc +14 -0
- data/demo/05_optparse_example.rdoc +152 -0
- data/demo/06_delegate_example.rdoc +40 -0
- data/demo/07_command_methods.rdoc +36 -0
- data/demo/08_dispatach.rdoc +29 -0
- data/demo/applique/ae.rb +1 -0
- data/demo/applique/compare.rb +4 -0
- data/demo/applique/exec.rb +1 -0
- data/demo/samples/bin/hello +31 -0
- data/demo/samples/man/hello.1 +22 -0
- data/demo/samples/man/hello.1.html +102 -0
- data/demo/samples/man/hello.1.ronn +19 -0
- data/lib/executable.rb +67 -128
- data/lib/executable/core_ext.rb +102 -0
- data/lib/executable/dispatch.rb +30 -0
- data/lib/executable/domain.rb +106 -0
- data/lib/executable/errors.rb +22 -0
- data/lib/executable/help.rb +430 -0
- data/lib/executable/parser.rb +208 -0
- data/lib/executable/utils.rb +41 -0
- data/lib/executable/version.rb +23 -0
- data/meta/authors +2 -0
- data/meta/copyrights +3 -0
- data/meta/created +1 -0
- data/meta/description +6 -0
- data/meta/name +1 -0
- data/meta/organization +1 -0
- data/meta/repositories +2 -0
- data/meta/requirements +6 -0
- data/meta/resources +7 -0
- data/meta/summary +1 -0
- data/meta/version +1 -0
- data/test/test_executable.rb +40 -19
- metadata +124 -68
- data/History.rdoc +0 -35
- data/NOTICE.rdoc +0 -23
- data/Profile +0 -30
- data/Version +0 -1
- 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'
|
data/.yardopts
ADDED
data/COPYING.rdoc
ADDED
@@ -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.
|
data/DEMO.rdoc
ADDED
@@ -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
|
+
|