bahuvrihi-tap 0.10.7 → 0.10.8
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +0 -2
- data/README +20 -31
- data/bin/rap +18 -8
- data/cgi/run.rb +47 -37
- data/cmd/console.rb +1 -1
- data/cmd/destroy.rb +3 -3
- data/cmd/generate.rb +3 -3
- data/cmd/manifest.rb +61 -53
- data/cmd/run.rb +1 -1
- data/doc/Class Reference +119 -110
- data/doc/Command Reference +76 -123
- data/doc/Syntax Reference +290 -0
- data/doc/Tutorial +307 -237
- data/lib/tap.rb +1 -12
- data/lib/tap/app.rb +46 -71
- data/lib/tap/constants.rb +1 -1
- data/lib/tap/declarations.rb +110 -100
- data/lib/tap/env.rb +141 -173
- data/lib/tap/exe.rb +5 -5
- data/lib/tap/file_task.rb +2 -2
- data/lib/tap/generator/base.rb +0 -4
- data/lib/tap/generator/destroy.rb +8 -12
- data/lib/tap/generator/generate.rb +19 -14
- data/lib/tap/generator/generators/command/command_generator.rb +1 -1
- data/lib/tap/generator/generators/config/config_generator.rb +3 -3
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
- data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
- data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
- data/lib/tap/generator/generators/root/root_generator.rb +12 -12
- data/lib/tap/generator/generators/root/templates/Rakefile +1 -2
- data/lib/tap/generator/generators/root/templates/tapfile +11 -8
- data/lib/tap/generator/generators/task/task_generator.rb +1 -3
- data/lib/tap/generator/generators/task/templates/test.erb +1 -3
- data/lib/tap/root.rb +4 -2
- data/lib/tap/support/aggregator.rb +16 -3
- data/lib/tap/support/assignments.rb +10 -9
- data/lib/tap/support/audit.rb +58 -62
- data/lib/tap/support/class_configuration.rb +32 -43
- data/lib/tap/support/combinator.rb +7 -7
- data/lib/tap/support/configurable.rb +13 -14
- data/lib/tap/support/configurable_class.rb +6 -30
- data/lib/tap/support/configuration.rb +36 -9
- data/lib/tap/support/constant.rb +75 -13
- data/lib/tap/support/constant_manifest.rb +115 -0
- data/lib/tap/support/dependencies.rb +27 -67
- data/lib/tap/support/dependency.rb +44 -0
- data/lib/tap/support/executable.rb +78 -109
- data/lib/tap/support/executable_queue.rb +1 -1
- data/lib/tap/support/gems.rb +6 -0
- data/lib/tap/support/gems/rack.rb +197 -84
- data/lib/tap/support/instance_configuration.rb +29 -3
- data/lib/tap/support/intern.rb +46 -0
- data/lib/tap/support/join.rb +67 -11
- data/lib/tap/support/joins.rb +2 -0
- data/lib/tap/support/joins/fork.rb +1 -0
- data/lib/tap/support/joins/merge.rb +3 -1
- data/lib/tap/support/joins/sequence.rb +2 -2
- data/lib/tap/support/joins/switch.rb +3 -1
- data/lib/tap/support/joins/sync_merge.rb +6 -0
- data/lib/tap/support/lazy_attributes.rb +16 -1
- data/lib/tap/support/lazydoc.rb +21 -21
- data/lib/tap/support/lazydoc/comment.rb +59 -55
- data/lib/tap/support/lazydoc/definition.rb +36 -0
- data/lib/tap/support/lazydoc/document.rb +37 -13
- data/lib/tap/support/manifest.rb +120 -131
- data/lib/tap/support/minimap.rb +90 -0
- data/lib/tap/support/node.rb +4 -6
- data/lib/tap/support/parser.rb +63 -6
- data/lib/tap/support/schema.rb +11 -2
- data/lib/tap/support/shell_utils.rb +3 -5
- data/lib/tap/support/string_ext.rb +60 -0
- data/lib/tap/support/tdoc.rb +2 -2
- data/lib/tap/support/templater.rb +29 -15
- data/lib/tap/support/validation.rb +22 -11
- data/lib/tap/task.rb +155 -156
- data/lib/tap/tasks/load.rb +95 -8
- data/lib/tap/test/extensions.rb +2 -1
- data/lib/tap/test/script_tester.rb +7 -1
- data/template/index.erb +39 -32
- metadata +13 -13
- data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
- data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
- data/lib/tap/patches/rake/testtask.rb +0 -57
- data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
- data/lib/tap/patches/ruby19/parsedate.rb +0 -16
- data/lib/tap/spec.rb +0 -42
- data/lib/tap/spec/adapter.rb +0 -25
- data/lib/tap/spec/inheritable_class_test_root.rb +0 -9
- data/lib/tap/support/constant_utils.rb +0 -127
- data/lib/tap/support/summary.rb +0 -30
@@ -0,0 +1,290 @@
|
|
1
|
+
= Syntax Reference
|
2
|
+
|
3
|
+
Tap uses several domain-specific languages to declare tasks and workflows. This is a reference for:
|
4
|
+
|
5
|
+
* task declarations
|
6
|
+
* class definitions
|
7
|
+
* workflows
|
8
|
+
|
9
|
+
== Task Declarations
|
10
|
+
|
11
|
+
A task declaration:
|
12
|
+
|
13
|
+
# name:: the name of the task, either alone or as the
|
14
|
+
# key of a {key => dependencies} hash
|
15
|
+
# dependencies:: an array of task names (optional)
|
16
|
+
# arg_names:: an array of argument names (optional)
|
17
|
+
# configurations:: a hash of {key => default} pairs (optional)
|
18
|
+
|
19
|
+
task({<name> => [<dependencies...>]}, <arg_names...>, {<configs>}) do |task, args|
|
20
|
+
# arguments are available through args:
|
21
|
+
args.arg_name
|
22
|
+
|
23
|
+
# configurations are available through task
|
24
|
+
task.key
|
25
|
+
end
|
26
|
+
|
27
|
+
A namespace declaration:
|
28
|
+
|
29
|
+
namespace(<name>) { task ... }
|
30
|
+
|
31
|
+
Simple documentation:
|
32
|
+
|
33
|
+
desc "description"
|
34
|
+
task ...
|
35
|
+
|
36
|
+
Extended documentation:
|
37
|
+
|
38
|
+
# ::desc description
|
39
|
+
# Extended documentation may span multiple lines, and
|
40
|
+
# supports
|
41
|
+
#
|
42
|
+
# code indentation
|
43
|
+
# like this.
|
44
|
+
#
|
45
|
+
# Lines are justified and wrapped on the command line.
|
46
|
+
task ...
|
47
|
+
|
48
|
+
Pains were taken to make task declarations for rap work the similar to rake tasks. In both cases (noting that in general, beyond rap, task classes are not limited in these ways):
|
49
|
+
|
50
|
+
* tasks are singleton instances that may be extended across multiple declarations
|
51
|
+
* tasks do not pass inputs from one task to the next
|
52
|
+
* tasks only execute once
|
53
|
+
|
54
|
+
A few syntactical differences between rake and rap must to be noted, as they can cause errors if you try to migrate rake tasks to tap. Tap declarations.
|
55
|
+
|
56
|
+
==== no needs
|
57
|
+
|
58
|
+
Tasks do not support :needs as a way to specify dependencies. For instance, this will declare a ':needs' configuration with the default value ':another', not a task which depends on another:
|
59
|
+
|
60
|
+
Tap.task :name, :needs => :another
|
61
|
+
|
62
|
+
==== immediate namespace lookup
|
63
|
+
|
64
|
+
Within a namespace, task will look for a literal match to the dependency first. If it doesn't find one, it will declare it within the namespace; one way or the other the dependency is resolved immediately. Hence, in this example the nested task depends on the non-nested task.
|
65
|
+
|
66
|
+
task :outer { print 'non-nested' }
|
67
|
+
namespace :nest do
|
68
|
+
task :inner => :outer { puts 'was executed' }
|
69
|
+
task :outer { print 'nested' }
|
70
|
+
end
|
71
|
+
|
72
|
+
By contrast, rake waits to resolve dependencies and so will always use a match within a namespace in preference to a literal match. For these tasks rap and rake produce different results:
|
73
|
+
|
74
|
+
% rap nest/inner
|
75
|
+
non-nested was executed
|
76
|
+
% rake nest:inner
|
77
|
+
nested was executed
|
78
|
+
|
79
|
+
== Task Classes
|
80
|
+
|
81
|
+
This is a verbose prototype for Tap::Task subclasses:
|
82
|
+
|
83
|
+
# <ClassName>::manifest summary description
|
84
|
+
#
|
85
|
+
# Extended documentation...
|
86
|
+
#
|
87
|
+
class ClassName < Tap::Task
|
88
|
+
|
89
|
+
# Sets up a configuration and makes the 'key' and 'key=' accessors.
|
90
|
+
#
|
91
|
+
# This documentation appears in static config files, and in RDoc.
|
92
|
+
#
|
93
|
+
config :key, 'default value' do |value| # config summary
|
94
|
+
"the config is set to the return value"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Subclasses BaseClass with the specified configs, using the block
|
98
|
+
# to override process.
|
99
|
+
#
|
100
|
+
# Also creates the methods 'name', 'name_config', 'name_config='.
|
101
|
+
# The first will access an instance-specific instance of the
|
102
|
+
# subclass, the other two are accessors for the instance configs.
|
103
|
+
#
|
104
|
+
# Primarily used in conjunction with workflow.
|
105
|
+
#
|
106
|
+
define :name, <BaseClass>, {<configs>} do |*args|
|
107
|
+
# this is the process block
|
108
|
+
end
|
109
|
+
|
110
|
+
# Causes each instance to depend on DependencyClass.instance.
|
111
|
+
#
|
112
|
+
# Also defines a reader 'name' which will access the results
|
113
|
+
# of the dependency.
|
114
|
+
depends_on :name, <DependencyClass>
|
115
|
+
|
116
|
+
def process(*args)
|
117
|
+
# the method defining what this task does
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
|
122
|
+
def workflow
|
123
|
+
# a hook to setup joins between various tasks used
|
124
|
+
# by the current instance. workflow is called
|
125
|
+
# during initialize
|
126
|
+
end
|
127
|
+
|
128
|
+
def before_execute()
|
129
|
+
# a hook to execute code before process
|
130
|
+
# (only works in workflows)
|
131
|
+
end
|
132
|
+
|
133
|
+
def after_execute()
|
134
|
+
# a hook to execute code after process
|
135
|
+
# (only works in workflows)
|
136
|
+
end
|
137
|
+
|
138
|
+
def on_execute_error(err)
|
139
|
+
# a hook to handle errors during process
|
140
|
+
# (only works in workflows)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
== Workflows
|
145
|
+
|
146
|
+
The tap workflow syntax is designed to specify an arbitrary number of tasks, inputs, configurations, and joins. The syntax works both as text and as YAML; 'tap run' and rap both use it to specify which tasks to execute. Basically, the syntax uses double-dash delimiters to separate task vectors and modified delimiters to specify joins for the tasks.
|
147
|
+
|
148
|
+
These both specify three tasks (x,y,z) joined in a sequence:
|
149
|
+
|
150
|
+
% rap x --: y --: z
|
151
|
+
% rap x -- y -- z --0:1:2
|
152
|
+
|
153
|
+
The modified delimiters use numbers to indicate which tasks participate in a join and punctuation to indicate the join type. In the first example, the numbers are implicitly added for the preceding and following task. A variety of joins are supported:
|
154
|
+
|
155
|
+
delimiter function syntax example meaning
|
156
|
+
-- delimiter a -- b -- c enques/configures tasks a, b, c
|
157
|
+
--* dependency --* a enques dependency instance
|
158
|
+
--+ round --+ a --++ b enques a to round one, b to round two
|
159
|
+
--+[] round shorthand +round[targets] --+2[0,1] enques (a,b) to round two
|
160
|
+
|
161
|
+
--: sequence source:target --0:1:2 sequence a, b, c
|
162
|
+
--[] fork source[targets...] --0[1,2] fork a to (b,c)
|
163
|
+
--{} merge target{sources...} --2{0,1} merge (a,b) to c
|
164
|
+
--() sync_merge target(sources...) --2(0,1) synchronize merge (a,b) to c
|
165
|
+
|
166
|
+
Inputs may be specified between delimiters:
|
167
|
+
|
168
|
+
% rap x alpha beta --: y gamma --: z delta
|
169
|
+
|
170
|
+
As may be configurations, in a variety of formats:
|
171
|
+
|
172
|
+
% rap x alpha beta -k --: y gamma --key value --: z delta --key=value
|
173
|
+
|
174
|
+
These are the corresponding task vectors:
|
175
|
+
|
176
|
+
['x', 'alpha', 'beta', '-k']
|
177
|
+
['y', 'gamma', '--key', 'value']
|
178
|
+
['z', 'delta', '--key=value']
|
179
|
+
|
180
|
+
This is how it would look as YAML:
|
181
|
+
|
182
|
+
- - x
|
183
|
+
- alpha
|
184
|
+
- beta
|
185
|
+
- -k
|
186
|
+
- - y
|
187
|
+
- gamma
|
188
|
+
- key: value
|
189
|
+
- - z
|
190
|
+
- delta
|
191
|
+
- --key=value
|
192
|
+
- 0:1:2
|
193
|
+
|
194
|
+
The task vectors are converted into a task and an argument vector by first looking up a task class and then calling TaskClass.parse. The parse method returns a configured instance of the task and an argv to be executed by the task. Once the task are instantiated, instances are joined and enqued to Tap::App for execution (see Tap::Support::Schema#build).
|
195
|
+
|
196
|
+
==== Class Lookup
|
197
|
+
|
198
|
+
Tap looks up classes via Tap::Env. Tap can find tasks from multiple environments; by adding in environments for gems, Tap can find tasks within a gem.
|
199
|
+
|
200
|
+
During lookup, classes are treated like filepaths which match from the basename up:
|
201
|
+
|
202
|
+
class lookup matched by
|
203
|
+
Sample::Task sample/task task, sample/task
|
204
|
+
A::Nested::Task a/nested/task task, nested/task, a/nested/task
|
205
|
+
|
206
|
+
In the event of a name conflict, the path of the environment may also be specified. It seems like this could get confusing, but the tap executable produces manifests that specify the minimal path required to uniquely identify a class. Fragments of the minimized paths will be resolved in order from top to bottom within the specified environment. For example:
|
207
|
+
|
208
|
+
% tap run -T
|
209
|
+
one:
|
210
|
+
sample/task # some sample task
|
211
|
+
another/task # another task
|
212
|
+
two:
|
213
|
+
sample/task # a conflicting sample task
|
214
|
+
|
215
|
+
Runs the 'one' sample/task:
|
216
|
+
|
217
|
+
% tap run -- sample/task
|
218
|
+
% tap run -- one:sample/task
|
219
|
+
|
220
|
+
Runs the 'two' sample/task:
|
221
|
+
|
222
|
+
% tap run -- two:sample/task
|
223
|
+
|
224
|
+
Runs the 'one' sample/task:
|
225
|
+
|
226
|
+
% tap run -- task
|
227
|
+
|
228
|
+
Runs the 'two' sample/task:
|
229
|
+
|
230
|
+
% tap run -- two:task
|
231
|
+
|
232
|
+
Runs another/task:
|
233
|
+
|
234
|
+
% tap run -- another/task
|
235
|
+
% tap run -- one:another/task
|
236
|
+
|
237
|
+
Notice that the full minimized path ('another/task') is required because simply using 'task' will be matched to sample/task. The order of tasks is obviously not alphabetical -- rather it corresponds to the order in which Tap::Env discovers the tasks.
|
238
|
+
|
239
|
+
==== Dependencies (--*)
|
240
|
+
|
241
|
+
Normally each task vector specifies a new instance of a task. This specifies three instance of the 'a' task.
|
242
|
+
|
243
|
+
% rap -- a -- a -- a
|
244
|
+
|
245
|
+
When you want to specify a 'singleton' instance of a task, the instance specified by TaskClass.instance, use the dependency delimiter '--*'.
|
246
|
+
|
247
|
+
% rap --* a
|
248
|
+
|
249
|
+
The dependency instance will be configured and enqued with the arguments. Dependency instances will be executed first, even if they are specified at the end of a workflow, they cannot participate in workflows, and any single dependency may only be specified once in a workflow. These are equivalent:
|
250
|
+
|
251
|
+
% rap --* a -- b -- c
|
252
|
+
% rap b -- c --* a
|
253
|
+
|
254
|
+
While these raise errors:
|
255
|
+
|
256
|
+
% rap --* a --* a
|
257
|
+
% rap --* a --: b
|
258
|
+
|
259
|
+
==== Rounds (--+)
|
260
|
+
|
261
|
+
The workflow syntax allows multiple execution rounds to be specified All tasks in a round are run to completion before the next round begins. Rounds are specified by adding '+' characters after the double-dash break.
|
262
|
+
|
263
|
+
% tap run -- round_one_task --+ round_two_task
|
264
|
+
|
265
|
+
Tasks may be added to rounds in any order and may use the shorthand for multiple tasks. These are equivalents:
|
266
|
+
|
267
|
+
% tap run -- a --+ b --+ c --++ d
|
268
|
+
% tap run --+ b --++ d -- a --+ c
|
269
|
+
% tap run -- a -- b -- c -- d --+1[1,2] --+2[3]
|
270
|
+
|
271
|
+
Rounds are particularly useful for dump tasks; add a dump task as a final round to capture all results from previous rounds:
|
272
|
+
|
273
|
+
% tap run -- task -- task --+ dump
|
274
|
+
|
275
|
+
==== Notes for rap
|
276
|
+
|
277
|
+
Specifying tasks for rap is no different than 'tap run'. The only exception occurs when the task can't be found. Unknown tasks are implicitly run as a Tap::Tasks::Rake task, which causes the task to be run as if by rake. These are the same:
|
278
|
+
|
279
|
+
Run a rake task with inputs and an ENV config:
|
280
|
+
|
281
|
+
% rake task_name[1,2,3] key=value
|
282
|
+
|
283
|
+
Explicitly run a Tap::Tasks::Rake task using the same inputs:
|
284
|
+
|
285
|
+
% rap rake task_name[1,2,3] key=value
|
286
|
+
|
287
|
+
Implicitly run the Tap::Tasks::Rake task:
|
288
|
+
|
289
|
+
% rap task_name[1,2,3] key=value
|
290
|
+
|
data/doc/Tutorial
CHANGED
@@ -1,237 +1,307 @@
|
|
1
|
-
=
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
%
|
26
|
-
Goodnight -- your basic goodnight moon task
|
27
|
-
--------------------------------------------------------------------------------
|
28
|
-
|
29
|
-
--------------------------------------------------------------------------------
|
30
|
-
usage: tap run -- goodnight
|
31
|
-
|
32
|
-
configurations:
|
33
|
-
--message MESSAGE
|
34
|
-
|
35
|
-
options:
|
36
|
-
-h, --help Print this help
|
37
|
-
--name NAME Specify a name
|
38
|
-
--use FILE Loads inputs from file
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
1
|
+
= Tap (Task Application)
|
2
|
+
|
3
|
+
Tap is a framework for creating configurable, distributable tasks and workflows. Although scalable for complex workflows, at it simplest tap works like a supercharged rake. Using the rap executable, you can declare tasks using a syntax almost identical to rake, but with added support for configurations and documentation.
|
4
|
+
|
5
|
+
== Quickstart
|
6
|
+
|
7
|
+
If you've used rake, tap will be easy to pick up. To get started, make a Tapfile with a simple task declaration:
|
8
|
+
|
9
|
+
[Tapfile]
|
10
|
+
|
11
|
+
# ::desc your basic goodnight moon task
|
12
|
+
# Says goodnight with a configurable message.
|
13
|
+
Tap.task(:goodnight, :obj, :message => 'goodnight') do |task, args|
|
14
|
+
puts "#{task.message} #{args.obj}\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
Now from the command line:
|
18
|
+
|
19
|
+
% rap goodnight moon
|
20
|
+
goodnight moon
|
21
|
+
|
22
|
+
% rap goodnight world --message hello
|
23
|
+
hello world
|
24
|
+
|
25
|
+
% rap goodnight --help
|
26
|
+
Goodnight -- your basic goodnight moon task
|
27
|
+
--------------------------------------------------------------------------------
|
28
|
+
Says goodnight with a configurable message.
|
29
|
+
--------------------------------------------------------------------------------
|
30
|
+
usage: tap run -- goodnight obj
|
31
|
+
|
32
|
+
configurations:
|
33
|
+
--message MESSAGE
|
34
|
+
|
35
|
+
options:
|
36
|
+
-h, --help Print this help
|
37
|
+
--name NAME Specify a name
|
38
|
+
--use FILE Loads inputs from file
|
39
|
+
|
40
|
+
Just like that you have a command-line application with inputs, configuration, and documentation.
|
41
|
+
|
42
|
+
The declaration syntax is obviously similar to rake; configurations are new but the task-block style is the same, as are inputs. Other rake constructs are available. Here is a similar goodnight task using dependencies, rake-style.
|
43
|
+
|
44
|
+
[Tapfile]
|
45
|
+
|
46
|
+
# make the declarations available everywhere
|
47
|
+
# (normally they're accessed via Tap, as above)
|
48
|
+
extend Tap::Declarations
|
49
|
+
|
50
|
+
namespace :example do
|
51
|
+
task(:say, :message) do |task, args|
|
52
|
+
print(args.message || 'goodnight')
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "your basic goodnight moon task"
|
56
|
+
task({:goodnight => :say}, :obj) do |task, args|
|
57
|
+
puts " #{args.obj}\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
And now from the command line:
|
62
|
+
|
63
|
+
% rap goodnight moon
|
64
|
+
goodnight moon
|
65
|
+
|
66
|
+
% rap goodnight world --* say hello
|
67
|
+
hello world
|
68
|
+
|
69
|
+
Unlike rake, rap inputs are written out individually and tasks are delimited by a modified double-dash. Aside from that, you can see rap is basically a supercharged rake. Furthermore, rap runs rake. Directly substitute rap for rake on the command line and your tasks should run as normal (see the {Syntax Reference}[link:files/doc/Syntax%20Reference.html] for more details).
|
70
|
+
|
71
|
+
However, supercharging rake isn't the point of Tap. Declarations bridge the gap between rake and tap, but tap itself is a more general framework. To get at other features like imperative workflows, testing, and distribution, we have to go beyond rap and take a look at what declarations do.
|
72
|
+
|
73
|
+
Spoiler: declarations make subclasses of Tap::Task.
|
74
|
+
|
75
|
+
== Beyond Rap
|
76
|
+
|
77
|
+
Going back to the first example, lets take a look at how a task declaration maps to a class definition:
|
78
|
+
|
79
|
+
[Tapfile]
|
80
|
+
|
81
|
+
# ::desc your basic goodnight moon task
|
82
|
+
# Says goodnight with a configurable message.
|
83
|
+
Tap.task(:goodnight, :obj, :message => 'goodnight') do
|
84
|
+
puts "#{task.message} #{args.obj}\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
Here is a corresponding class:
|
88
|
+
|
89
|
+
[Tapfile]
|
90
|
+
|
91
|
+
# Goodnight::manifest your basic goodnight moon task
|
92
|
+
# Says goodnight with a configurable message.
|
93
|
+
class Goodnight < Tap::Task
|
94
|
+
config :message, 'goodnight'
|
95
|
+
|
96
|
+
def process(obj)
|
97
|
+
"#{message} #{obj}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
Simple enough; the name corresponds to the class, configurations (and dependencies, although they aren't show) are written out individually, and the block corresponds to process. There are a few differences, especially relating to process, but for the moment lets gloss over them and see how Goodnight works.
|
102
|
+
|
103
|
+
Goodnight.configurations.to_hash # => {:message => 'goodnight'}
|
104
|
+
|
105
|
+
goodnight = Goodnight.new
|
106
|
+
goodnight.message # => 'goodnight'
|
107
|
+
goodnight.process('moon') # => 'goodnight moon'
|
108
|
+
|
109
|
+
hello = Goodnight.new(:message => 'hello')
|
110
|
+
hello.message # => 'hello'
|
111
|
+
hello.process('world') # => 'hello world'
|
112
|
+
|
113
|
+
Totally straightforward. Goodnight stores the default configurations, each instance has accessors to the configurations, and the defaults may be overridden during initialization, or later. Class definitions allow validation/transformation blocks to be specified for configurations. These blocks process inputs (ex the string inputs from the command line), quite literally defining the writer for a configuration accessor. A set of standard blocks are available through +c+, an alias for the Tap::Support::Validation module.
|
114
|
+
|
115
|
+
[Tapfile]
|
116
|
+
|
117
|
+
# Goodnight::manifest a fancy goodnight moon task
|
118
|
+
# Says goodnight with a configurable message.
|
119
|
+
class Goodnight < Tap::Task
|
120
|
+
config :message, 'goodnight' # a goodnight message
|
121
|
+
config :reverse, false, &c.switch # reverses the message
|
122
|
+
config :n, 1, &c.integer # repeats message n times
|
123
|
+
|
124
|
+
def process(*objects)
|
125
|
+
print "#{reverse == true ? message.reverse : message} " * n
|
126
|
+
puts objects.join(', ')
|
127
|
+
puts
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
A few examples show a validation block in action:
|
132
|
+
|
133
|
+
goodnight = Goodnight.new
|
134
|
+
|
135
|
+
goodnight.n = 10
|
136
|
+
goodnight.n # => 10
|
137
|
+
|
138
|
+
goodnight.n = "100"
|
139
|
+
goodnight.n # => 100
|
140
|
+
|
141
|
+
goodnight.n = "not an integer" # !> ValidationError
|
142
|
+
|
143
|
+
Now from the command line:
|
144
|
+
|
145
|
+
% rap goodnight moon
|
146
|
+
goodnight moon
|
147
|
+
|
148
|
+
% rap goodnight moon mittens "little toy boat"
|
149
|
+
goodnight moon, mittens, little toy boat
|
150
|
+
|
151
|
+
% rap goodnight world --message hello --reverse --n 3
|
152
|
+
olleh olleh olleh world
|
153
|
+
|
154
|
+
% rap goodnight --help
|
155
|
+
Goodnight -- a fancy goodnight moon task
|
156
|
+
--------------------------------------------------------------------------------
|
157
|
+
Says goodnight with a configurable message.
|
158
|
+
--------------------------------------------------------------------------------
|
159
|
+
usage: tap run -- goodnight OBJECTS...
|
160
|
+
|
161
|
+
configurations:
|
162
|
+
--message MESSAGE a goodnight message
|
163
|
+
--[no-]reverse reverses the message
|
164
|
+
--n N repeats message n times
|
165
|
+
|
166
|
+
options:
|
167
|
+
-h, --help Print this help
|
168
|
+
--name NAME Specify a name
|
169
|
+
--use FILE Loads inputs from file
|
170
|
+
|
171
|
+
Take a quick look at the documentation. Class definitions can also map documentation and, in some cases, metadata to the command line; the configurations now have comments and reverse is a switch! Rich mapping allows tasks to act as an script interface, not unlike {OptionParser}[http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html] (surprise, tap uses OptionParser).
|
172
|
+
|
173
|
+
For example this is a stand-alone goodnight script:
|
174
|
+
|
175
|
+
[goodnight]
|
176
|
+
|
177
|
+
#!/usr/bin/env ruby
|
178
|
+
|
179
|
+
require 'rubygems'
|
180
|
+
require 'tap'
|
181
|
+
|
182
|
+
# Goodnight::manifest a goodnight moon script
|
183
|
+
# Says goodnight with a configurable message.
|
184
|
+
class Goodnight < Tap::Task
|
185
|
+
config :message, 'goodnight'
|
186
|
+
|
187
|
+
def process(obj)
|
188
|
+
puts "#{message} #{obj}\n"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
instance, args = Goodnight.parse!(ARGV)
|
193
|
+
instance.execute(*args)
|
194
|
+
|
195
|
+
Now, from the command line:
|
196
|
+
|
197
|
+
% ./goodnight moon
|
198
|
+
goodnight moon
|
199
|
+
|
200
|
+
% ./goodnight --help
|
201
|
+
...
|
202
|
+
|
203
|
+
As simple as it is to take a task to the command line, it's nice to note that tasks may be subclassed, tested, and distributed as usual. No magic, just convenience.
|
204
|
+
|
205
|
+
== Tap
|
206
|
+
|
207
|
+
Tap comes with two executables, rap and tap. The tap executable is more verbose than rap for running tasks, but it is more configurable, scalable, and logically pure. Tap comes with a number of {commands}[link:files/doc/Command%20Reference.html] but we'll focus on generate to make, test, and package a task library. Begin by creating a tap directory structure and a task:
|
208
|
+
|
209
|
+
% tap generate root sample
|
210
|
+
% cd sample
|
211
|
+
% tap generate task goodnight
|
212
|
+
|
213
|
+
Take a look at the task files an you find something like this:
|
214
|
+
|
215
|
+
[lib/goodnight.rb]
|
216
|
+
|
217
|
+
# Goodnight::manifest <replace with manifest summary>
|
218
|
+
# <replace with command line description>
|
219
|
+
|
220
|
+
# Goodnight Documentation
|
221
|
+
class Goodnight < Tap::Task
|
222
|
+
|
223
|
+
# <config file documentation>
|
224
|
+
config :message, 'goodnight' # a sample config
|
225
|
+
|
226
|
+
def process(name)
|
227
|
+
log message, name
|
228
|
+
"#{message} #{name}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
[test/goodnight_test.rb]
|
233
|
+
|
234
|
+
require File.join(File.dirname(__FILE__), 'tap_test_helper.rb')
|
235
|
+
require 'goodnight'
|
236
|
+
|
237
|
+
class GoodnightTest < Test::Unit::TestCase
|
238
|
+
acts_as_tap_test
|
239
|
+
|
240
|
+
def test_goodnight
|
241
|
+
task = Goodnight.new :message => "goodnight"
|
242
|
+
|
243
|
+
# a simple test
|
244
|
+
assert_equal({:message => 'goodnight'}, task.config)
|
245
|
+
assert_equal "goodnight moon", task.process("moon")
|
246
|
+
|
247
|
+
# a more complex test
|
248
|
+
task.enq("moon")
|
249
|
+
app.run
|
250
|
+
|
251
|
+
assert_equal ["goodnight moon"], app.results(task)
|
252
|
+
assert_audit_equal ExpAudit[[nil, "moon"], [task, "goodnight moon"]], app._results(task)[0]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
Run the test:
|
257
|
+
|
258
|
+
% rap test
|
259
|
+
|
260
|
+
Run the task:
|
261
|
+
|
262
|
+
% tap run -- goodnight moon
|
263
|
+
I[23:22:19] goodnight moon
|
264
|
+
|
265
|
+
Ok, lets share it. Print the current gemspec manifest:
|
266
|
+
|
267
|
+
% rap print_manifest
|
268
|
+
true README
|
269
|
+
Rakefile
|
270
|
+
lib/goodnight.rb
|
271
|
+
sample.gemspec
|
272
|
+
tap.yml
|
273
|
+
test/goodnight_test.rb
|
274
|
+
true test/tap_test_helper.rb
|
275
|
+
true test/tap_test_suite.rb
|
276
|
+
|
277
|
+
As you can see, this needs an update to include the task file. Open up sample.gemspec and fix the manifest.
|
278
|
+
|
279
|
+
[sample.gemspec]
|
280
|
+
|
281
|
+
Gem::Specification.new do |s|
|
282
|
+
s.name = "sample"
|
283
|
+
s.version = "0.0.1"
|
284
|
+
s.platform = Gem::Platform::RUBY
|
285
|
+
s.summary = "sample"
|
286
|
+
s.require_path = "lib"
|
287
|
+
s.add_dependency("tap", "~> 0.10.8")
|
288
|
+
s.files = %W{
|
289
|
+
lib/goodnight.rb
|
290
|
+
tap.yml
|
291
|
+
}
|
292
|
+
end
|
293
|
+
|
294
|
+
Now package the gem and install it (gem may require sudo):
|
295
|
+
|
296
|
+
% rap gem
|
297
|
+
% gem install pkg/sample-0.0.1.gem
|
298
|
+
|
299
|
+
Now you can say goodnight anywhere, using 'tap run' or rap:
|
300
|
+
|
301
|
+
% cd ~/Desktop
|
302
|
+
% tap run -- goodnight moon
|
303
|
+
goodnight moon
|
304
|
+
% rap goodnight opus
|
305
|
+
goodnight opus
|
306
|
+
|
307
|
+
And that is that.
|