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