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.
Files changed (90) hide show
  1. data/MIT-LICENSE +0 -2
  2. data/README +20 -31
  3. data/bin/rap +18 -8
  4. data/cgi/run.rb +47 -37
  5. data/cmd/console.rb +1 -1
  6. data/cmd/destroy.rb +3 -3
  7. data/cmd/generate.rb +3 -3
  8. data/cmd/manifest.rb +61 -53
  9. data/cmd/run.rb +1 -1
  10. data/doc/Class Reference +119 -110
  11. data/doc/Command Reference +76 -123
  12. data/doc/Syntax Reference +290 -0
  13. data/doc/Tutorial +307 -237
  14. data/lib/tap.rb +1 -12
  15. data/lib/tap/app.rb +46 -71
  16. data/lib/tap/constants.rb +1 -1
  17. data/lib/tap/declarations.rb +110 -100
  18. data/lib/tap/env.rb +141 -173
  19. data/lib/tap/exe.rb +5 -5
  20. data/lib/tap/file_task.rb +2 -2
  21. data/lib/tap/generator/base.rb +0 -4
  22. data/lib/tap/generator/destroy.rb +8 -12
  23. data/lib/tap/generator/generate.rb +19 -14
  24. data/lib/tap/generator/generators/command/command_generator.rb +1 -1
  25. data/lib/tap/generator/generators/config/config_generator.rb +3 -3
  26. data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
  27. data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
  28. data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
  29. data/lib/tap/generator/generators/root/root_generator.rb +12 -12
  30. data/lib/tap/generator/generators/root/templates/Rakefile +1 -2
  31. data/lib/tap/generator/generators/root/templates/tapfile +11 -8
  32. data/lib/tap/generator/generators/task/task_generator.rb +1 -3
  33. data/lib/tap/generator/generators/task/templates/test.erb +1 -3
  34. data/lib/tap/root.rb +4 -2
  35. data/lib/tap/support/aggregator.rb +16 -3
  36. data/lib/tap/support/assignments.rb +10 -9
  37. data/lib/tap/support/audit.rb +58 -62
  38. data/lib/tap/support/class_configuration.rb +32 -43
  39. data/lib/tap/support/combinator.rb +7 -7
  40. data/lib/tap/support/configurable.rb +13 -14
  41. data/lib/tap/support/configurable_class.rb +6 -30
  42. data/lib/tap/support/configuration.rb +36 -9
  43. data/lib/tap/support/constant.rb +75 -13
  44. data/lib/tap/support/constant_manifest.rb +115 -0
  45. data/lib/tap/support/dependencies.rb +27 -67
  46. data/lib/tap/support/dependency.rb +44 -0
  47. data/lib/tap/support/executable.rb +78 -109
  48. data/lib/tap/support/executable_queue.rb +1 -1
  49. data/lib/tap/support/gems.rb +6 -0
  50. data/lib/tap/support/gems/rack.rb +197 -84
  51. data/lib/tap/support/instance_configuration.rb +29 -3
  52. data/lib/tap/support/intern.rb +46 -0
  53. data/lib/tap/support/join.rb +67 -11
  54. data/lib/tap/support/joins.rb +2 -0
  55. data/lib/tap/support/joins/fork.rb +1 -0
  56. data/lib/tap/support/joins/merge.rb +3 -1
  57. data/lib/tap/support/joins/sequence.rb +2 -2
  58. data/lib/tap/support/joins/switch.rb +3 -1
  59. data/lib/tap/support/joins/sync_merge.rb +6 -0
  60. data/lib/tap/support/lazy_attributes.rb +16 -1
  61. data/lib/tap/support/lazydoc.rb +21 -21
  62. data/lib/tap/support/lazydoc/comment.rb +59 -55
  63. data/lib/tap/support/lazydoc/definition.rb +36 -0
  64. data/lib/tap/support/lazydoc/document.rb +37 -13
  65. data/lib/tap/support/manifest.rb +120 -131
  66. data/lib/tap/support/minimap.rb +90 -0
  67. data/lib/tap/support/node.rb +4 -6
  68. data/lib/tap/support/parser.rb +63 -6
  69. data/lib/tap/support/schema.rb +11 -2
  70. data/lib/tap/support/shell_utils.rb +3 -5
  71. data/lib/tap/support/string_ext.rb +60 -0
  72. data/lib/tap/support/tdoc.rb +2 -2
  73. data/lib/tap/support/templater.rb +29 -15
  74. data/lib/tap/support/validation.rb +22 -11
  75. data/lib/tap/task.rb +155 -156
  76. data/lib/tap/tasks/load.rb +95 -8
  77. data/lib/tap/test/extensions.rb +2 -1
  78. data/lib/tap/test/script_tester.rb +7 -1
  79. data/template/index.erb +39 -32
  80. metadata +13 -13
  81. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
  82. data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
  83. data/lib/tap/patches/rake/testtask.rb +0 -57
  84. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
  85. data/lib/tap/patches/ruby19/parsedate.rb +0 -16
  86. data/lib/tap/spec.rb +0 -42
  87. data/lib/tap/spec/adapter.rb +0 -25
  88. data/lib/tap/spec/inheritable_class_test_root.rb +0 -9
  89. data/lib/tap/support/constant_utils.rb +0 -127
  90. 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
+
@@ -1,237 +1,307 @@
1
- = Quick Start Tutorial
2
-
3
- === Basic Usage
4
-
5
- Begin by creating a tap directory structure:
6
-
7
- % tap generate root sample
8
- % cd sample
9
-
10
- Comes with a task:
11
-
12
- % tap run -T
13
- sample:
14
- goodnight # your basic goodnight moon task
15
- tap:
16
- dump # the default dump task
17
- rake # run rake tasks
18
-
19
- Test the task:
20
-
21
- % rake test
22
-
23
- Get help for the task:
24
-
25
- % tap run -- goodnight --help
26
- Goodnight -- your basic goodnight moon task
27
- --------------------------------------------------------------------------------
28
- Prints the input with a configurable message.
29
- --------------------------------------------------------------------------------
30
- usage: tap run -- goodnight INPUT
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
- Run the task:
41
-
42
- % tap run -- goodnight moon
43
- I[23:22:19] goodnight moon
44
-
45
- Run the task, setting the 'message' configuration:
46
-
47
- % tap run -- goodnight moon --message hello
48
- I[23:22:46] hello moon
49
-
50
- Run multiple tasks, or in this case the same task twice:
51
-
52
- % tap run -- goodnight moon -- goodnight opus
53
- I[23:23:06] goodnight moon
54
- I[23:23:06] goodnight opus
55
-
56
- Same as above, but now dump the results to a file:
57
-
58
- % tap run -- goodnight moon -- goodnight opus --+ dump output.yml
59
- I[23:23:26] goodnight moon
60
- I[23:23:26] goodnight opus
61
- I[23:23:26] dump output.yml
62
-
63
- The dump file contents look like this:
64
-
65
- # audit:
66
- # o-[] "opus"
67
- # o-[goodnight] "goodnight opus"
68
- #
69
- # o-[] "moon"
70
- # o-[goodnight] "goodnight moon"
71
- #
72
- # date: 2008-08-05 23:23:26
73
- ---
74
- goodnight (2769410):
75
- - goodnight opus
76
- goodnight (2780180):
77
- - goodnight moon
78
-
79
- The comments at the beginning are an audit trace of the run. In this case two
80
- separate tasks were run sequentially, hence you can see each task, the task inputs,
81
- and the task results as separate units. A YAML hash follows the audit with the
82
- aggregated task results, keyed by the task name and object id. Since the results
83
- are represented as a hash, the order of the tasks sometimes gets scrambled, as in
84
- this case.
85
-
86
- === Task Declaration
87
-
88
- Tap provides a declaration syntax a-la rake, accessible through the Tap module to
89
- prevent conflicts with rake. Declarations can get put in any <tt>.rb</tt> file
90
- under the lib directory or in <tt>tapfile.rb</tt>.
91
-
92
- [tapfile.rb]
93
- # Goodnight::manifest your basic goodnight moon task
94
- # Prints the input with a configurable message.
95
-
96
- Tap.task('goodnight', :message => 'goodnight') do |task, name|
97
- task.log task.message, name
98
- "#{task.message} #{name}"
99
- end
100
-
101
- The declaration makes a task class based on the name (ie namespaces are naturally
102
- supported by names like <tt>'nested/task'</tt>). The classes are ready for use in
103
- scripts:
104
-
105
- require 'tapfile'
106
- Goodnight.new.process('moon') # => 'goodnight moon'
107
-
108
- And from the command line, as above.
109
-
110
- === Task Definition
111
-
112
- Sometimes you need more than a block to define a task. Generate a task:
113
-
114
- % tap generate task hello
115
-
116
- Navigate to and open the <tt>lib/hello.rb</tt> file. Inside you can see the class
117
- definition. Notice configurations are mapped to methods, and the task documentation
118
- is located in the comments. Let's change it up a bit:
119
-
120
- [lib/hello.rb]
121
- # Hello::manifest your basic hello world task
122
- #
123
- # Prints hello to a number of things with a configurable,
124
- # reversible message.
125
- #
126
- class Hello < Tap::Task
127
-
128
- config :message, 'hello' # a greeting
129
- config :reverse, false, &c.flag # reverses the message
130
-
131
- def process(*names)
132
- names.collect do |name|
133
- log(reverse ? message.reverse : message, name)
134
- "#{message} #{name}"
135
- end
136
- end
137
- end
138
-
139
- The new configurations and documentation are immediately available:
140
-
141
- % tap run -- hello --help
142
- Hello -- your basic hello world task
143
- --------------------------------------------------------------------------------
144
- Prints hello to a number of things with a configurable, reversible message.
145
- --------------------------------------------------------------------------------
146
- usage: tap run -- hello NAMES...
147
-
148
- configurations:
149
- --message MESSAGE a greeting
150
- --reverse reverses the message
151
-
152
- options:
153
- -h, --help Print this help
154
- --name NAME Specify a name
155
- --use FILE Loads inputs from file
156
-
157
- And the task is ready to go:
158
-
159
- % tap run -- hello moon lamp 'little toy boat'
160
- I[23:29:26] hello moon
161
- I[23:29:26] hello lamp
162
- I[23:29:26] hello little toy boat
163
-
164
- % tap run -- hello mittens --reverse
165
- I[23:29:53] olleh mittens
166
-
167
- Now lets use the previous results; they get loaded and added to the end of the inputs:
168
-
169
- % tap run -- hello --use output.yml
170
- I[23:31:32] hello goodnight moon
171
- I[23:31:32] hello goodnight opus
172
-
173
- === Config Files
174
-
175
- So say you wanted static configs for a task. Make a configuration file:
176
-
177
- % tap generate config goodnight
178
-
179
- Set the configurations here and they get used by the task:
180
-
181
- [config/goodnight.yml]
182
- ###############################################################################
183
- # Goodnight configuration
184
- ###############################################################################
185
-
186
- message: good evening
187
-
188
- As can be seen here:
189
-
190
- % tap run -- goodnight moon
191
- I[23:40:39] good evening moon
192
-
193
- If you need to run a task with multiple sets of configurations, simply define an
194
- array of configurations in the config file:
195
-
196
- [config/goodnight.yml]
197
- - message: good afternoon
198
- - message: good evening
199
- - message: goodnight
200
-
201
- % tap run -- goodnight moon
202
- I[23:42:46] good afternoon moon
203
- I[23:42:46] good evening moon
204
- I[23:42:46] goodnight moon
205
-
206
- The --name option sets the config file used:
207
-
208
- % tap run -- goodnight moon --name no_config_file
209
- I[23:43:20] goodnight moon
210
-
211
- === Tap Configuration
212
-
213
- Tap itself is highly configurable. Say you think the run syntax is unnecessarily
214
- verbose; you can make command aliases to shorten it. Open the <tt>tap.yml</tt>
215
- file in your root directory and set the following:
216
-
217
- [tap.yml]
218
- alias:
219
- --: [run, --]
220
- -T: [run, -T]
221
-
222
- Now:
223
-
224
- % tap -- hello world
225
- I[23:43:59] hello world
226
-
227
- % tap -T
228
- sample:
229
- goodnight # your basic goodnight moon task
230
- hello # your basic hello world task
231
- tap:
232
- dump # the default dump task
233
- rake # run rake tasks
234
-
235
- Global configurations can go in the <tt>~/.tap.yml</tt> file. Using configurations,
236
- you can specify directory aliases, options, gems, and even additional paths to load
237
- as if they were gems.
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.