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
data/lib/tap/support/node.rb
CHANGED
@@ -5,15 +5,13 @@ module Tap
|
|
5
5
|
class Node
|
6
6
|
|
7
7
|
# An array of arguments used to instantiate
|
8
|
-
# the node
|
9
|
-
# to the instance (when the node is directly
|
10
|
-
# enqued to a round... see input)
|
8
|
+
# the node
|
11
9
|
attr_accessor :argv
|
12
10
|
|
13
11
|
# The input or source for the node. Inputs
|
14
|
-
# may be a Join, nil, or an Integer
|
15
|
-
#
|
16
|
-
#
|
12
|
+
# may be a Join, nil, or an Integer. An
|
13
|
+
# Integer input indicates that the node should
|
14
|
+
# be enqued to a round using argv as inputs.
|
17
15
|
attr_accessor :input
|
18
16
|
|
19
17
|
# The output for the node. Output may be a
|
data/lib/tap/support/parser.rb
CHANGED
@@ -4,6 +4,8 @@ autoload(:Shellwords, 'shellwords')
|
|
4
4
|
module Tap
|
5
5
|
module Support
|
6
6
|
|
7
|
+
# == Syntax
|
8
|
+
#
|
7
9
|
# ==== Round Assignment
|
8
10
|
# Tasks can be defined and set to a round using the following:
|
9
11
|
#
|
@@ -23,7 +25,7 @@ module Tap
|
|
23
25
|
# ==== Workflow Assignment
|
24
26
|
# All simple workflow patterns except switch can be specified within
|
25
27
|
# the parse syntax (switch is the exception because there is no good
|
26
|
-
# way to define
|
28
|
+
# way to define the switch block).
|
27
29
|
#
|
28
30
|
# break pattern source(s) target(s)
|
29
31
|
# --: sequence last next
|
@@ -40,7 +42,7 @@ module Tap
|
|
40
42
|
# --(2,3,4) last.sync_merge(2,3,4)
|
41
43
|
#
|
42
44
|
# Note how all of the bracketed styles behave similarly; they are
|
43
|
-
# parsed with essentially the same code,
|
45
|
+
# parsed with essentially the same code, but reverse the source
|
44
46
|
# and target in the case of merges.
|
45
47
|
#
|
46
48
|
# Here a and b are sequenced inline. Task c is assigned to no
|
@@ -246,6 +248,55 @@ module Tap
|
|
246
248
|
end
|
247
249
|
options
|
248
250
|
end
|
251
|
+
|
252
|
+
# Parses an arg hash into a schema argv. An arg hash is a hash
|
253
|
+
# using numeric keys to specify the [row][col] in a two-dimensional
|
254
|
+
# array where a set of values should go. Breaks are added between
|
255
|
+
# rows (if necessary) and the array is collapsed to yield the
|
256
|
+
# argv:
|
257
|
+
#
|
258
|
+
# argh = {
|
259
|
+
# 0 => {
|
260
|
+
# 0 => 'a',
|
261
|
+
# 1 => ['b', 'c']},
|
262
|
+
# 1 => 'z'
|
263
|
+
# }
|
264
|
+
# parse_argh(argh) # => ['--', 'a', 'b', 'c', '--', 'z']
|
265
|
+
#
|
266
|
+
# Non-numeric keys are converted to integers using to_i and
|
267
|
+
# existing breaks (such as workflow breaks) occuring at the
|
268
|
+
# start of a row are preseved.
|
269
|
+
#
|
270
|
+
# argh = {
|
271
|
+
# '0' => {
|
272
|
+
# '0' => 'a',
|
273
|
+
# '1' => ['b', 'c']},
|
274
|
+
# '1' => ['--:', 'z']
|
275
|
+
# }
|
276
|
+
# parse_argh(argh) # => ['--', 'a', 'b', 'c', '--:', 'z']
|
277
|
+
#
|
278
|
+
def parse_argh(argh)
|
279
|
+
rows = []
|
280
|
+
argh.each_pair do |row, values|
|
281
|
+
if values.kind_of?(Hash)
|
282
|
+
arry = []
|
283
|
+
values.each_pair {|col, value| arry[col.to_i] = value }
|
284
|
+
values = arry
|
285
|
+
end
|
286
|
+
|
287
|
+
rows[row.to_i] = values
|
288
|
+
end
|
289
|
+
|
290
|
+
argv = []
|
291
|
+
rows.each do |row|
|
292
|
+
row = [row].flatten.compact
|
293
|
+
if row.empty? || row[0] !~ BREAK
|
294
|
+
argv << '--'
|
295
|
+
end
|
296
|
+
argv.concat row
|
297
|
+
end
|
298
|
+
argv
|
299
|
+
end
|
249
300
|
end
|
250
301
|
|
251
302
|
include Utils
|
@@ -266,7 +317,8 @@ module Tap
|
|
266
317
|
# arguments.
|
267
318
|
#
|
268
319
|
# Parse is non-destructive to argv. If a string argv is provided, parse
|
269
|
-
# splits it into an array using Shellwords
|
320
|
+
# splits it into an array using Shellwords; if a hash argv is provided,
|
321
|
+
# parse converts it to an array using Parser::Utils#parse_argh.
|
270
322
|
#
|
271
323
|
def parse(argv)
|
272
324
|
parse!(argv.kind_of?(String) ? argv : argv.dup)
|
@@ -274,7 +326,12 @@ module Tap
|
|
274
326
|
|
275
327
|
# Same as parse, but removes parsed args from argv.
|
276
328
|
def parse!(argv)
|
277
|
-
argv =
|
329
|
+
argv = case argv
|
330
|
+
when Array then argv
|
331
|
+
when String then Shellwords.shellwords(argv)
|
332
|
+
when Hash then parse_argh(argv)
|
333
|
+
else argv
|
334
|
+
end
|
278
335
|
argv.unshift('--')
|
279
336
|
|
280
337
|
escape = false
|
@@ -346,10 +403,10 @@ module Tap
|
|
346
403
|
protected
|
347
404
|
|
348
405
|
# The index of the node currently being parsed.
|
349
|
-
attr_accessor :current_index
|
406
|
+
attr_accessor :current_index # :nodoc:
|
350
407
|
|
351
408
|
# Returns current_index-1, or raises an error if current_index < 1.
|
352
|
-
def previous_index
|
409
|
+
def previous_index # :nodoc:
|
353
410
|
raise ArgumentError, 'there is no previous index' if current_index < 1
|
354
411
|
current_index - 1
|
355
412
|
end
|
data/lib/tap/support/schema.rb
CHANGED
@@ -243,9 +243,18 @@ module Tap
|
|
243
243
|
end
|
244
244
|
|
245
245
|
# instantiate and reconfigure globals
|
246
|
+
instances = []
|
246
247
|
globals.each do |node|
|
247
|
-
task, args = tasks
|
248
|
-
task.class.instance
|
248
|
+
task, args = tasks.delete(node)
|
249
|
+
instance = task.class.instance
|
250
|
+
|
251
|
+
if instances.include?(instance)
|
252
|
+
raise "global specified multple times: #{instance}"
|
253
|
+
end
|
254
|
+
|
255
|
+
instance.reconfigure(task.config.to_hash)
|
256
|
+
instance.enq(*args)
|
257
|
+
instances << instance
|
249
258
|
end
|
250
259
|
|
251
260
|
# build the workflow
|
@@ -4,8 +4,7 @@ module Tap
|
|
4
4
|
module Support
|
5
5
|
# Provides several shell utility methods for calling programs.
|
6
6
|
#
|
7
|
-
# == Windows
|
8
|
-
# A couple warnings when running shell commands in the MSDOS prompt on Windows.
|
7
|
+
# == Windows
|
9
8
|
# MSDOS has command line length limits specific to the version of Windows being
|
10
9
|
# run (from http://www.ss64.com/nt/cmd.html):
|
11
10
|
#
|
@@ -13,9 +12,8 @@ module Tap
|
|
13
12
|
# Windows 2000:: 2046 characters
|
14
13
|
# Windows XP:: 8190 characters
|
15
14
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
15
|
+
# Commands longer than these limits fail, usually with something like: 'the input
|
16
|
+
# line is too long'
|
19
17
|
module ShellUtils
|
20
18
|
|
21
19
|
module_function
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# StringExt provides two common string transformations, camelize and
|
5
|
+
# underscore. StringExt is automatically included in String.
|
6
|
+
#
|
7
|
+
# Both methods are directly taken from the ActiveSupport {Inflections}[http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html]
|
8
|
+
# module. StringExt should not cause conflicts if ActiveSupport is
|
9
|
+
# loaded alongside Tap.
|
10
|
+
#
|
11
|
+
# ActiveSupport is distributed with an MIT-LICENSE:
|
12
|
+
#
|
13
|
+
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
14
|
+
#
|
15
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
16
|
+
# associated documentation files (the "Software"), to deal in the Software without restriction,
|
17
|
+
# including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
18
|
+
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
19
|
+
# subject to the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial
|
22
|
+
# portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
25
|
+
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
26
|
+
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
27
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
28
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
29
|
+
#
|
30
|
+
module StringExt
|
31
|
+
|
32
|
+
# camelize converts self to UpperCamelCase. If the argument to
|
33
|
+
# camelize is set to :lower then camelize produces lowerCamelCase.
|
34
|
+
# camelize will also convert '/' to '::' which is useful for
|
35
|
+
# converting paths to namespaces.
|
36
|
+
def camelize(first_letter = :upper)
|
37
|
+
case first_letter
|
38
|
+
when :upper then self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
39
|
+
when :lower then self.first + camelize[1..-1]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# The reverse of camelize. Makes an underscored, lowercase form
|
44
|
+
# from self. underscore will also change '::' to '/' to convert
|
45
|
+
# namespaces to paths.
|
46
|
+
def underscore
|
47
|
+
self.gsub(/::/, '/').
|
48
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
49
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
50
|
+
tr("-", "_").
|
51
|
+
downcase
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class String # :nodoc:
|
59
|
+
include Tap::Support::StringExt
|
60
|
+
end
|
data/lib/tap/support/tdoc.rb
CHANGED
@@ -84,7 +84,7 @@ module Tap
|
|
84
84
|
# Now execute the rake task like:
|
85
85
|
#
|
86
86
|
# % rake rdoc
|
87
|
-
|
87
|
+
#--
|
88
88
|
# === Implementation
|
89
89
|
# RDoc is a beast to utilize in a non-standard way. One way to make RDoc parse unexpected
|
90
90
|
# flags like 'config' or 'config_attr' is to use the '--accessor' option (see 'rdoc --help' or
|
@@ -134,7 +134,7 @@ module Tap
|
|
134
134
|
|
135
135
|
# Encasulates information about the configuration. Designed to be utilized
|
136
136
|
# by the TDocHTMLGenerator as similarly as possible to standard attributes.
|
137
|
-
class ConfigAttr < RDoc::Attr
|
137
|
+
class ConfigAttr < RDoc::Attr # :nodoc:
|
138
138
|
# Contains the actual declaration for the config attribute. ex: "c [:key, 'value'] # comment"
|
139
139
|
attr_accessor :config_declaration, :default
|
140
140
|
|
@@ -5,10 +5,9 @@ module Tap
|
|
5
5
|
module Support
|
6
6
|
|
7
7
|
# Templater is a convenience class for creating ERB templates. As
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# Templater (and hence all the assigned attributes) are available
|
11
|
-
# in the template.
|
8
|
+
# a subclass of OpenStruct, attributes can be assigned/unassigned
|
9
|
+
# directly. When the template is built, all the method of
|
10
|
+
# Templater (and hence all the assigned attributes) are available.
|
12
11
|
#
|
13
12
|
# t = Templater.new( "key: <%= value %>")
|
14
13
|
# t.value = "default"
|
@@ -23,16 +22,14 @@ module Tap
|
|
23
22
|
#
|
24
23
|
# Templater hooks into the ERB templating mechanism by providing itself
|
25
24
|
# as the ERB output target (_erbout). ERB concatenates each line of an
|
26
|
-
# ERB template to _erbout, as can be seen
|
27
|
-
# evaluated by ERB:
|
25
|
+
# ERB template to _erbout, as can be seen here:
|
28
26
|
#
|
29
27
|
# e = ERB.new("<%= 1 + 2 %>")
|
30
28
|
# e.src # => "_erbout = ''; _erbout.concat(( 1 + 2 ).to_s); _erbout"
|
31
29
|
#
|
32
|
-
# By setting itself as _erbout, instances of Templater can redirect
|
33
|
-
# output to a temporary target
|
34
|
-
#
|
35
|
-
# nested content:
|
30
|
+
# By setting itself as _erbout, instances of Templater can redirect
|
31
|
+
# output to a temporary target and perform string transformations.
|
32
|
+
# For example, redirection allows indentation of nested content:
|
36
33
|
#
|
37
34
|
# template = %Q{
|
38
35
|
# # Un-nested content
|
@@ -62,12 +59,17 @@ module Tap
|
|
62
59
|
# yamlize converts the object to YAML (using to_yaml), omitting
|
63
60
|
# the header and final newline:
|
64
61
|
#
|
65
|
-
|
66
|
-
|
62
|
+
# {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
|
63
|
+
# yamlize {'key' => 'value'} # => "key: value"
|
67
64
|
def yamlize(object)
|
68
65
|
object == nil ? "~" : YAML.dump(object)[4...-1].strip
|
69
66
|
end
|
70
67
|
|
68
|
+
# Comments out the string.
|
69
|
+
def comment(str)
|
70
|
+
str.split("\n").collect {|line| "# #{line}" }.join("\n")
|
71
|
+
end
|
72
|
+
|
71
73
|
# Nest the return of the block in the nesting lines.
|
72
74
|
#
|
73
75
|
# nest([["\nmodule Some", "end\n"],["module Nested", "end"]]) { "class Const\nend" }
|
@@ -117,6 +119,12 @@ module Tap
|
|
117
119
|
end
|
118
120
|
end
|
119
121
|
|
122
|
+
class << self
|
123
|
+
def build(template, attributes={})
|
124
|
+
new(template, attributes).build
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
120
128
|
include Utils
|
121
129
|
|
122
130
|
# Initialized a new Templater. An ERB or String may be provided as the
|
@@ -124,8 +132,10 @@ module Tap
|
|
124
132
|
# ERB with a trim_mode of "<>".
|
125
133
|
def initialize(template, attributes={})
|
126
134
|
@template = case template
|
127
|
-
when ERB
|
128
|
-
|
135
|
+
when ERB
|
136
|
+
# matching w/wo the coding effectively checks @src
|
137
|
+
# across ruby versions (encoding appears in 1.9)
|
138
|
+
if template.instance_variable_get(:@src) !~ /^(#coding:US-ASCII\n)?_erbout =/
|
129
139
|
raise ArgumentError, "Templater does not work with ERB templates where eoutvar != '_erbout'"
|
130
140
|
end
|
131
141
|
template
|
@@ -170,7 +180,11 @@ module Tap
|
|
170
180
|
|
171
181
|
# Build the template. All methods of self will be
|
172
182
|
# accessible in the template.
|
173
|
-
def build
|
183
|
+
def build(attrs={})
|
184
|
+
attrs.each_pair do |key, value|
|
185
|
+
send("#{key}=", value)
|
186
|
+
end
|
187
|
+
|
174
188
|
@template.result(binding)
|
175
189
|
@_erbout
|
176
190
|
end
|
@@ -4,9 +4,9 @@ module Tap
|
|
4
4
|
module Support
|
5
5
|
|
6
6
|
# Validation generates blocks for common validations and transformations of
|
7
|
-
# configurations set through Configurable. In general these blocks
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# configurations set through Configurable. In general these blocks load
|
8
|
+
# string inputs as YAML and valdiate the results; non-string inputs are
|
9
|
+
# simply validated.
|
10
10
|
#
|
11
11
|
# integer = Validation.integer
|
12
12
|
# integer.class # => Proc
|
@@ -23,7 +23,6 @@ module Tap
|
|
23
23
|
#
|
24
24
|
# This syntax plays well with RDoc, which otherwise gets jacked
|
25
25
|
# when you do it all in one step.
|
26
|
-
#++
|
27
26
|
module Validation
|
28
27
|
|
29
28
|
# Raised when Validation blocks fail.
|
@@ -204,9 +203,11 @@ module Tap
|
|
204
203
|
def boolean(); BOOLEAN; end
|
205
204
|
BOOLEAN = yamlize_and_check(true, false, nil)
|
206
205
|
|
206
|
+
# Same as boolean.
|
207
207
|
def switch(); SWITCH; end
|
208
208
|
SWITCH = yamlize_and_check(true, false, nil)
|
209
209
|
|
210
|
+
# Same as boolean.
|
210
211
|
def flag(); FLAG; end
|
211
212
|
FLAG = yamlize_and_check(true, false, nil)
|
212
213
|
|
@@ -229,13 +230,20 @@ module Tap
|
|
229
230
|
def array_or_nil(); ARRAY_OR_NIL; end
|
230
231
|
ARRAY_OR_NIL = yamlize_and_check(Array, nil)
|
231
232
|
|
233
|
+
# Returns a block that checks the input is an array.
|
234
|
+
# If the input is a string the string is split along
|
235
|
+
# commas and each value yamlized into an array.
|
236
|
+
#
|
237
|
+
# list.class # => Proc
|
238
|
+
# list.call([1,2,3]) # => [1,2,3]
|
239
|
+
# list.call('1,2,3') # => [1,2,3]
|
240
|
+
# list.call('str') # => ['str']
|
241
|
+
# list.call(nil) # => ValidationError
|
242
|
+
#
|
232
243
|
def list(); LIST; end
|
233
244
|
list_block = lambda do |input|
|
234
245
|
if input.kind_of?(String)
|
235
|
-
input =
|
236
|
-
when Array then processed_input
|
237
|
-
else input.split(/,/).collect {|arg| yamlize(arg) }
|
238
|
-
end
|
246
|
+
input = input.split(/,/).collect {|arg| yamlize(arg) }
|
239
247
|
end
|
240
248
|
|
241
249
|
validate(input, [Array])
|
@@ -438,10 +446,11 @@ module Tap
|
|
438
446
|
TIME
|
439
447
|
end
|
440
448
|
|
441
|
-
|
449
|
+
time_block = lambda do |input|
|
442
450
|
input = Time.parse(input) if input.kind_of?(String)
|
443
451
|
validate(input, [Time])
|
444
452
|
end
|
453
|
+
TIME = time_block
|
445
454
|
|
446
455
|
# Same as time but allows nil:
|
447
456
|
#
|
@@ -454,7 +463,7 @@ module Tap
|
|
454
463
|
TIME_OR_NIL
|
455
464
|
end
|
456
465
|
|
457
|
-
|
466
|
+
time_or_nil_block = lambda do |input|
|
458
467
|
input = case input
|
459
468
|
when nil, '~' then nil
|
460
469
|
when String then Time.parse(input)
|
@@ -462,7 +471,9 @@ module Tap
|
|
462
471
|
end
|
463
472
|
|
464
473
|
validate(input, [Time, nil])
|
465
|
-
end
|
474
|
+
end
|
475
|
+
TIME_OR_NIL = time_or_nil_block
|
476
|
+
|
466
477
|
end
|
467
478
|
end
|
468
479
|
end
|
data/lib/tap/task.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'tap/support/executable'
|
2
2
|
require 'tap/support/lazydoc/method'
|
3
|
+
require 'tap/support/lazydoc/definition'
|
4
|
+
require 'tap/support/intern'
|
3
5
|
autoload(:OptionParser, 'optparse')
|
4
6
|
|
5
7
|
module Tap
|
@@ -106,7 +108,7 @@ module Tap
|
|
106
108
|
# parent task, although they can be manually assembled using Task.batch.
|
107
109
|
#
|
108
110
|
# app = Tap::App.instance
|
109
|
-
# t1 = Tap::Task.
|
111
|
+
# t1 = Tap::Task.intern(:key => 'one') do |task, input|
|
110
112
|
# input + task.config[:key]
|
111
113
|
# end
|
112
114
|
# t1.batch # => [t1]
|
@@ -161,64 +163,43 @@ module Tap
|
|
161
163
|
include Support::Executable
|
162
164
|
|
163
165
|
class << self
|
164
|
-
# Returns the default name for the class: to_s.underscore
|
165
|
-
attr_accessor :default_name
|
166
|
-
|
167
166
|
# Returns class dependencies
|
168
167
|
attr_reader :dependencies
|
169
168
|
|
169
|
+
# Returns the default name for the class: to_s.underscore
|
170
|
+
attr_writer :default_name
|
171
|
+
|
172
|
+
def default_name
|
173
|
+
# lazy-setting default_name like this (rather than
|
174
|
+
# within inherited, for example) is an optimization
|
175
|
+
# since many subclass operations end up setting
|
176
|
+
# default_name themselves.
|
177
|
+
@default_name ||= to_s.underscore
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns an instance of self; the instance is a kind of 'global'
|
181
|
+
# instance used in class-level dependencies. See depends_on.
|
182
|
+
def instance
|
183
|
+
@instance ||= new
|
184
|
+
end
|
185
|
+
|
170
186
|
def inherited(child)
|
171
187
|
unless child.instance_variable_defined?(:@source_file)
|
172
188
|
caller.first =~ Support::Lazydoc::CALLER_REGEXP
|
173
189
|
child.instance_variable_set(:@source_file, File.expand_path($1))
|
174
190
|
end
|
175
191
|
|
176
|
-
child.instance_variable_set(:@default_name, child.to_s.underscore)
|
177
192
|
child.instance_variable_set(:@dependencies, dependencies.dup)
|
178
193
|
super
|
179
194
|
end
|
180
195
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
# instance used in class-level dependencies. See depends_on.
|
187
|
-
def instance
|
188
|
-
@instance ||= new
|
189
|
-
end
|
190
|
-
|
191
|
-
# Generates or updates the specified subclass of self.
|
192
|
-
def subclass(const_name, configs={}, dependencies=[], options={}, &block)
|
193
|
-
#
|
194
|
-
# Lookup or create the subclass constant.
|
195
|
-
#
|
196
|
-
|
197
|
-
current, constants = const_name.to_s.constants_split
|
198
|
-
subclass = if constants.empty?
|
199
|
-
# The constant exists; validate the constant is a subclass of self.
|
200
|
-
unless current.kind_of?(Class) && current.ancestors.include?(self)
|
201
|
-
raise ArgumentError, "#{current} is already defined and is not a subclass of #{self}!"
|
202
|
-
end
|
203
|
-
current
|
204
|
-
else
|
205
|
-
# Generate the nesting module
|
206
|
-
subclass_const = constants.pop
|
207
|
-
constants.each {|const| current = current.const_set(const, Module.new)}
|
208
|
-
|
209
|
-
# Create and set the subclass constant
|
210
|
-
current.const_set(subclass_const, Class.new(self))
|
196
|
+
def intern(*args, &block)
|
197
|
+
instance = new(*args)
|
198
|
+
if block_given?
|
199
|
+
instance.extend Support::Intern
|
200
|
+
instance.process_block = block
|
211
201
|
end
|
212
|
-
|
213
|
-
#
|
214
|
-
# Define the subclass
|
215
|
-
#
|
216
|
-
|
217
|
-
subclass.define_configurations(configs)
|
218
|
-
subclass.define_dependencies(dependencies)
|
219
|
-
subclass.define_process(block) if block_given?
|
220
|
-
subclass.default_name = subclass.to_s.underscore
|
221
|
-
subclass
|
202
|
+
instance
|
222
203
|
end
|
223
204
|
|
224
205
|
# Parses the argv into an instance of self and an array of arguments (implicitly
|
@@ -300,10 +281,7 @@ module Tap
|
|
300
281
|
end
|
301
282
|
obj.reconfigure(path_configs).reconfigure(argv_config)
|
302
283
|
|
303
|
-
|
304
|
-
argv = (argv + use_args).collect {|str| str =~ /\A---\s*\n/ ? YAML.load(str) : str }
|
305
|
-
|
306
|
-
[obj, argv]
|
284
|
+
[obj, (argv + use_args)]
|
307
285
|
end
|
308
286
|
|
309
287
|
def execute(argv=ARGV)
|
@@ -338,77 +316,141 @@ module Tap
|
|
338
316
|
Tap::Support::Templater.new(DEFAULT_HELP_TEMPLATE, :task_class => self).build
|
339
317
|
end
|
340
318
|
|
319
|
+
protected
|
320
|
+
|
341
321
|
# Sets a class-level dependency. When task class B depends_on another task
|
342
322
|
# class A, instances of B are initialized to depend on A.instance, with the
|
343
323
|
# specified arguments. Returns self.
|
344
|
-
def depends_on(
|
345
|
-
unless
|
346
|
-
|
324
|
+
def depends_on(name, dependency_class)
|
325
|
+
unless dependencies.include?(dependency_class)
|
326
|
+
dependencies << dependency_class
|
347
327
|
end
|
348
|
-
|
349
|
-
|
350
|
-
end
|
351
|
-
|
352
|
-
protected
|
353
|
-
|
354
|
-
def dependency(name, dependency_class, *args)
|
355
|
-
depends_on(dependency_class, *args)
|
356
|
-
|
328
|
+
|
329
|
+
# returns the resolved result of the dependency
|
357
330
|
define_method(name) do
|
358
|
-
|
359
|
-
|
360
|
-
|
331
|
+
instance = dependency_class.instance
|
332
|
+
instance.resolve
|
333
|
+
instance._result._current
|
361
334
|
end
|
362
335
|
|
363
336
|
public(name)
|
337
|
+
self
|
364
338
|
end
|
365
339
|
|
366
|
-
|
367
|
-
|
340
|
+
# Defines a task subclass with the specified configurations and process block.
|
341
|
+
# During initialization the subclass is instantiated and made accessible
|
342
|
+
# through a reader by the specified name.
|
343
|
+
#
|
344
|
+
# Defined tasks may be configured during initialization, through config, or
|
345
|
+
# directly through the instance; in effect you get tasks with nested configs
|
346
|
+
# which greatly facilitates workflows. Indeed, defined tasks are often
|
347
|
+
# joined in the workflow method.
|
348
|
+
#
|
349
|
+
# class AddALetter < Tap::Task
|
350
|
+
# config :letter, 'a'
|
351
|
+
# def process(input); input << letter; end
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
# class AlphabetSoup < Tap::Task
|
355
|
+
# define :a, AddALetter, {:letter => 'a'}
|
356
|
+
# define :b, AddALetter, {:letter => 'b'}
|
357
|
+
# define :c, AddALetter, {:letter => 'c'}
|
358
|
+
#
|
359
|
+
# def workflow
|
360
|
+
# a.sequence(b, c)
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
# def process
|
364
|
+
# a.execute("")
|
365
|
+
# end
|
366
|
+
# end
|
367
|
+
#
|
368
|
+
# AlphabetSoup.new.process # => 'abc'
|
369
|
+
#
|
370
|
+
# i = AlphabetSoup.new(:a => {:letter => 'x'}, :b => {:letter => 'y'}, :c => {:letter => 'z'})
|
371
|
+
# i.process # => 'xyz'
|
372
|
+
#
|
373
|
+
# i.config[:a] = {:letter => 'p'}
|
374
|
+
# i.config[:b][:letter] = 'q'
|
375
|
+
# i.c.letter = 'r'
|
376
|
+
# i.process # => 'pqr'
|
377
|
+
#
|
378
|
+
# ==== Usage
|
379
|
+
#
|
380
|
+
# Define is basically the equivalent of:
|
381
|
+
#
|
382
|
+
# class Sample < Tap::Task
|
383
|
+
# Name = baseclass.subclass(config, &block)
|
384
|
+
#
|
385
|
+
# # accesses an instance of Name
|
386
|
+
# attr_reader :name
|
387
|
+
#
|
388
|
+
# # register name as a config, but with a
|
389
|
+
# # non-standard reader and writer
|
390
|
+
# config :name, {}, {:reader => :name_config, :writer => :name_config=}.merge(options)
|
391
|
+
#
|
392
|
+
# # reader for name.config
|
393
|
+
# def name_config; ...; end
|
394
|
+
#
|
395
|
+
# # reconfigures name with input
|
396
|
+
# def name_config=(input); ...; end
|
397
|
+
#
|
398
|
+
# def initialize(*args)
|
399
|
+
# super
|
400
|
+
# @name = Name.new(config[:name])
|
401
|
+
# end
|
402
|
+
# end
|
403
|
+
#
|
404
|
+
# Note the following:
|
405
|
+
# * define will set a constant like name.camelize
|
406
|
+
# * the block defines the process method in the subclass
|
407
|
+
# * three methods are created by define: name, name_config, name_config=
|
408
|
+
#
|
409
|
+
def define(name, baseclass=Tap::Task, configs={}, options={}, &block)
|
410
|
+
# define the subclass
|
411
|
+
const_name = options.delete(:const_name) || name.to_s.camelize
|
412
|
+
subclass = const_set(const_name, Class.new(baseclass))
|
413
|
+
subclass.default_name = name.to_s
|
368
414
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
instance_name = args[0] || name
|
373
|
-
instance_variable_set(instance_var, {}) unless instance_variable_defined?(instance_var)
|
374
|
-
instance_variable_get(instance_var)[instance_name] ||= config_task(instance_name, klass, &block)
|
415
|
+
configs.each_pair do |key, value|
|
416
|
+
subclass.send(:config, key, value)
|
375
417
|
end
|
376
418
|
|
377
|
-
|
378
|
-
|
379
|
-
instance_variable_set(instance_var, input)
|
419
|
+
if block_given?
|
420
|
+
subclass.send(:define_method, :process, &block)
|
380
421
|
end
|
381
422
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
#
|
396
|
-
|
397
|
-
|
423
|
+
# define methods
|
424
|
+
instance_var = "@#{name}".to_sym
|
425
|
+
reader = (options[:reader] ||= "#{name}_config".to_sym)
|
426
|
+
writer = (options[:writer] ||= "#{name}_config=".to_sym)
|
427
|
+
|
428
|
+
attr_reader name
|
429
|
+
|
430
|
+
define_method(reader) do
|
431
|
+
# return the config for the instance
|
432
|
+
instance_variable_get(instance_var).config
|
433
|
+
end
|
434
|
+
|
435
|
+
define_method(writer) do |value|
|
436
|
+
# initialize or reconfigure the instance of subclass
|
437
|
+
if instance_variable_defined?(instance_var)
|
438
|
+
instance_variable_get(instance_var).reconfigure(value)
|
439
|
+
else
|
440
|
+
instance_variable_set(instance_var, subclass.new(value))
|
398
441
|
end
|
399
|
-
else
|
400
|
-
raise ArgumentError, "cannot define configurations from: #{configs}"
|
401
442
|
end
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
443
|
+
public(name, reader, writer)
|
444
|
+
|
445
|
+
# add the configuration
|
446
|
+
if options[:desc] == nil
|
447
|
+
caller[0] =~ Support::Lazydoc::CALLER_REGEXP
|
448
|
+
desc = Support::Lazydoc.register($1, $3.to_i - 1, Support::Lazydoc::Definition)
|
449
|
+
desc.subclass = subclass
|
450
|
+
options[:desc] = desc
|
451
|
+
end
|
452
|
+
|
453
|
+
configurations.add(name, subclass.configurations.instance_config, options)
|
412
454
|
end
|
413
455
|
end
|
414
456
|
|
@@ -423,20 +465,15 @@ module Tap
|
|
423
465
|
# Currently names may be any object. Audit makes use of name
|
424
466
|
# via to_s, as does app when figuring configuration filepaths.
|
425
467
|
attr_accessor :name
|
426
|
-
|
427
|
-
# The task block provided during initialization.
|
428
|
-
attr_reader :task_block
|
429
468
|
|
430
469
|
# Initializes a new instance and associated batch objects. Batch
|
431
470
|
# objects will be initialized for each configuration template
|
432
471
|
# specified by app.each_config_template(config_file) where
|
433
472
|
# config_file = app.config_filepath(name).
|
434
|
-
def initialize(config={}, name=nil, app=App.instance
|
473
|
+
def initialize(config={}, name=nil, app=App.instance)
|
435
474
|
super()
|
436
475
|
|
437
476
|
@name = name || self.class.default_name
|
438
|
-
@task_block = (task_block == nil ? default_task_block : task_block)
|
439
|
-
|
440
477
|
@app = app
|
441
478
|
@_method_name = :execute_with_callbacks
|
442
479
|
@on_complete_block = nil
|
@@ -444,15 +481,17 @@ module Tap
|
|
444
481
|
@batch = [self]
|
445
482
|
|
446
483
|
case config
|
447
|
-
when Support::InstanceConfiguration
|
448
|
-
|
484
|
+
when Support::InstanceConfiguration
|
485
|
+
# update is prudent to ensure all configs have an input
|
486
|
+
# (and hence, all configs will be initialized)
|
487
|
+
@config = config.update(self.class.configurations)
|
449
488
|
config.bind(self)
|
450
489
|
else
|
451
490
|
initialize_config(config)
|
452
491
|
end
|
453
492
|
|
454
|
-
self.class.dependencies.each do |
|
455
|
-
depends_on(
|
493
|
+
self.class.dependencies.each do |dependency_class|
|
494
|
+
depends_on(dependency_class.instance)
|
456
495
|
end
|
457
496
|
|
458
497
|
workflow
|
@@ -492,29 +531,9 @@ module Tap
|
|
492
531
|
# t.app.run
|
493
532
|
# t.app.results(t) # => [[2,1], [4,3]]
|
494
533
|
#
|
495
|
-
# By default process
|
496
|
-
# provided during initialization. In this case the task block dictates
|
497
|
-
# the number of arguments enq should receive. Simply returns the inputs
|
498
|
-
# if no task_block is set.
|
499
|
-
#
|
500
|
-
# # two arguments in addition to task are specified
|
501
|
-
# # so this Task must be enqued with two inputs...
|
502
|
-
# t = Task.new {|task, a, b| [b,a] }
|
503
|
-
# t.enq(1,2).enq(3,4)
|
504
|
-
# t.app.run
|
505
|
-
# t.app.results(t) # => [[2,1], [4,3]]
|
506
|
-
#
|
534
|
+
# By default, process simply returns the inputs.
|
507
535
|
def process(*inputs)
|
508
|
-
|
509
|
-
inputs.unshift(self)
|
510
|
-
|
511
|
-
arity = task_block.arity
|
512
|
-
n = inputs.length
|
513
|
-
unless n == arity || (arity < 0 && (-1-n) <= arity)
|
514
|
-
raise ArgumentError.new("wrong number of arguments (#{n} for #{arity})")
|
515
|
-
end
|
516
|
-
|
517
|
-
task_block.call(*inputs)
|
536
|
+
inputs
|
518
537
|
end
|
519
538
|
|
520
539
|
# Logs the inputs to the application logger (via app.log)
|
@@ -522,15 +541,6 @@ module Tap
|
|
522
541
|
# TODO - add a task identifier?
|
523
542
|
app.log(action, msg, level)
|
524
543
|
end
|
525
|
-
|
526
|
-
# Raises a TerminateError if app.state == State::TERMINATE.
|
527
|
-
# check_terminate may be called at any time to provide a
|
528
|
-
# breakpoint in long-running processes.
|
529
|
-
def check_terminate
|
530
|
-
if app.state == App::State::TERMINATE
|
531
|
-
raise App::TerminateError.new
|
532
|
-
end
|
533
|
-
end
|
534
544
|
|
535
545
|
# Returns self.name
|
536
546
|
def to_s
|
@@ -544,12 +554,7 @@ module Tap
|
|
544
554
|
end
|
545
555
|
|
546
556
|
protected
|
547
|
-
|
548
|
-
# Hook to set a default task block. By default, nil.
|
549
|
-
def default_task_block
|
550
|
-
nil
|
551
|
-
end
|
552
|
-
|
557
|
+
|
553
558
|
# Hook to define a workflow for defined tasks.
|
554
559
|
def workflow
|
555
560
|
end
|
@@ -580,11 +585,5 @@ module Tap
|
|
580
585
|
result
|
581
586
|
end
|
582
587
|
|
583
|
-
def config_task(name, klass=Tap::Task, &block)
|
584
|
-
configs = config[name] || {}
|
585
|
-
raise ArgumentError, "config '#{name}' is not a hash" unless configs.kind_of?(Hash)
|
586
|
-
klass.new(configs, name, &block)
|
587
|
-
end
|
588
|
-
|
589
588
|
end
|
590
589
|
end
|