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
@@ -5,15 +5,13 @@ module Tap
5
5
  class Node
6
6
 
7
7
  # An array of arguments used to instantiate
8
- # the node, and to specify arguments enqued
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 (indicating
15
- # the node should be enqued to a round, with
16
- # inputs as specified in argv).
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
@@ -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 a block from an array).
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, only reversing the source
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 = Shellwords.shellwords(argv) if argv.kind_of?(String)
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
@@ -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[node]
248
- task.class.instance.reconfigure(task.config.to_hash)
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
- # No word on more recent versions of Windows. Commands longer than these limits
17
- # fail, usually with something like: 'the input line is too long'
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
@@ -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
- # an OpenStruct, attributes can be assigned/unassigned at will to
9
- # a Templater. When the template is built, all the method of
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 when you look at the src code
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 the
33
- # output to a temporary target which can then be used in string
34
- # transformations. For example, redirection allows indentation of
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
- # {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
66
- # yamlize {'key' => 'value'} # => "key: value"
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
- if template.instance_variable_get(:@src).index('_erbout =') != 0
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 allow
8
- # configurations to be set to objects of a particular class, or to a string
9
- # that can be loaded as YAML into such an object.
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 = case processed_input = yamlize(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
- TIME = lambda do |input|
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
- TIME_OR_NIL = lambda do |input|
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
@@ -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.new(:key => 'one') do |task, input|
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
- # use with caution... should reset dependencies?
183
- attr_writer :instance
184
-
185
- # Returns an instance of self; the instance is a kind of 'global'
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
- # recollect arguments
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(dependency_class, *args)
345
- unless dependency_class.respond_to?(:instance)
346
- raise ArgumentError, "dependency_class does not respond to instance: #{dependency_class}"
324
+ def depends_on(name, dependency_class)
325
+ unless dependencies.include?(dependency_class)
326
+ dependencies << dependency_class
347
327
  end
348
- (dependencies << [dependency_class, args]).uniq!
349
- self
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
- index = app.dependencies.index(dependency_class.instance, args)
359
- app.dependencies.resolve([index])
360
- app.dependencies.results[index]._current
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
- def define(name, klass=Tap::Task, &block)
367
- instance_var = "@#{name}".to_sym
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
- define_method(name) do |*args|
370
- raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" if args.length > 1
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
- define_method("#{name}=") do |input|
378
- input = {name => input} unless input.kind_of?(Hash)
379
- instance_variable_set(instance_var, input)
419
+ if block_given?
420
+ subclass.send(:define_method, :process, &block)
380
421
  end
381
422
 
382
- public(name, "#{name}=")
383
- end
384
-
385
- def define_configurations(configs)
386
- case configs
387
- when Hash
388
- # hash configs are simply added as default configurations
389
- attr_accessor(*configs.keys)
390
- configs.each_pair do |key, value|
391
- configurations.add(key, value)
392
- end
393
- public(*configs.keys)
394
- when Array
395
- # array configs define configuration methods
396
- configs.each do |method, key, value, opts, config_block|
397
- send(method, key, value, opts, &config_block)
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
- end
403
-
404
- def define_dependencies(dependencies)
405
- dependencies.each do |name, dependency_class, args|
406
- dependency(name, dependency_class, *(args ? args : []))
407
- end if dependencies
408
- end
409
-
410
- def define_process(block)
411
- send(:define_method, :process, &block)
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, &task_block)
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
- @config = config
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 |task_class, args|
455
- depends_on(task_class.instance, *args)
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 passes self and the input(s) to the task_block
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
- return inputs if task_block == nil
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