tap 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/History +12 -0
  2. data/MIT-LICENSE +0 -2
  3. data/README +23 -32
  4. data/bin/rap +116 -0
  5. data/bin/tap +6 -9
  6. data/cgi/run.rb +67 -0
  7. data/cmd/console.rb +1 -1
  8. data/cmd/destroy.rb +4 -4
  9. data/cmd/generate.rb +4 -4
  10. data/cmd/manifest.rb +61 -53
  11. data/cmd/run.rb +8 -75
  12. data/doc/Class Reference +130 -121
  13. data/doc/Command Reference +76 -124
  14. data/doc/Syntax Reference +290 -0
  15. data/doc/Tutorial +305 -237
  16. data/lib/tap/app.rb +140 -467
  17. data/lib/tap/constants.rb +2 -2
  18. data/lib/tap/declarations.rb +211 -0
  19. data/lib/tap/env.rb +171 -193
  20. data/lib/tap/exe.rb +100 -21
  21. data/lib/tap/file_task.rb +3 -3
  22. data/lib/tap/generator/base.rb +1 -1
  23. data/lib/tap/generator/destroy.rb +10 -10
  24. data/lib/tap/generator/generate.rb +29 -18
  25. data/lib/tap/generator/generators/command/command_generator.rb +2 -2
  26. data/lib/tap/generator/generators/command/templates/command.erb +2 -2
  27. data/lib/tap/generator/generators/config/config_generator.rb +3 -3
  28. data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
  29. data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
  30. data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
  31. data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
  32. data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
  33. data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
  34. data/lib/tap/generator/generators/root/root_generator.rb +13 -13
  35. data/lib/tap/generator/generators/root/templates/README +0 -0
  36. data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
  37. data/lib/tap/generator/generators/root/templates/gemspec +4 -5
  38. data/lib/tap/generator/generators/root/templates/tapfile +11 -8
  39. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  40. data/lib/tap/generator/generators/task/task_generator.rb +1 -3
  41. data/lib/tap/generator/generators/task/templates/test.erb +1 -3
  42. data/lib/tap/patches/optparse/summarize.rb +62 -0
  43. data/lib/tap/root.rb +41 -29
  44. data/lib/tap/support/aggregator.rb +16 -3
  45. data/lib/tap/support/assignments.rb +10 -9
  46. data/lib/tap/support/audit.rb +58 -64
  47. data/lib/tap/support/class_configuration.rb +33 -44
  48. data/lib/tap/support/combinator.rb +125 -0
  49. data/lib/tap/support/configurable.rb +13 -14
  50. data/lib/tap/support/configurable_class.rb +21 -43
  51. data/lib/tap/support/configuration.rb +55 -9
  52. data/lib/tap/support/constant.rb +87 -13
  53. data/lib/tap/support/constant_manifest.rb +116 -0
  54. data/lib/tap/support/dependencies.rb +54 -0
  55. data/lib/tap/support/dependency.rb +44 -0
  56. data/lib/tap/support/executable.rb +247 -32
  57. data/lib/tap/support/executable_queue.rb +1 -1
  58. data/lib/tap/support/gems/rake.rb +29 -8
  59. data/lib/tap/support/gems.rb +10 -30
  60. data/lib/tap/support/instance_configuration.rb +29 -3
  61. data/lib/tap/support/intern.rb +46 -0
  62. data/lib/tap/support/join.rb +143 -0
  63. data/lib/tap/support/joins/fork.rb +19 -0
  64. data/lib/tap/support/joins/merge.rb +22 -0
  65. data/lib/tap/support/joins/sequence.rb +21 -0
  66. data/lib/tap/support/joins/switch.rb +25 -0
  67. data/lib/tap/support/joins/sync_merge.rb +63 -0
  68. data/lib/tap/support/joins.rb +15 -0
  69. data/lib/tap/support/lazy_attributes.rb +17 -2
  70. data/lib/tap/support/lazydoc/comment.rb +503 -0
  71. data/lib/tap/support/lazydoc/config.rb +17 -0
  72. data/lib/tap/support/lazydoc/definition.rb +36 -0
  73. data/lib/tap/support/lazydoc/document.rb +152 -0
  74. data/lib/tap/support/lazydoc/method.rb +24 -0
  75. data/lib/tap/support/lazydoc.rb +269 -343
  76. data/lib/tap/support/manifest.rb +121 -103
  77. data/lib/tap/support/minimap.rb +90 -0
  78. data/lib/tap/support/node.rb +56 -0
  79. data/lib/tap/support/parser.rb +436 -0
  80. data/lib/tap/support/schema.rb +359 -0
  81. data/lib/tap/support/shell_utils.rb +3 -5
  82. data/lib/tap/support/string_ext.rb +60 -0
  83. data/lib/tap/support/tdoc.rb +7 -2
  84. data/lib/tap/support/templater.rb +30 -16
  85. data/lib/tap/support/validation.rb +77 -8
  86. data/lib/tap/task.rb +431 -143
  87. data/lib/tap/tasks/dump.rb +15 -10
  88. data/lib/tap/tasks/load.rb +112 -0
  89. data/lib/tap/tasks/rake.rb +4 -41
  90. data/lib/tap/test/assertions.rb +38 -0
  91. data/lib/tap/test/env_vars.rb +1 -1
  92. data/lib/tap/test/extensions.rb +79 -0
  93. data/lib/tap/test/file_test.rb +420 -0
  94. data/lib/tap/test/file_test_class.rb +12 -0
  95. data/lib/tap/test/regexp_escape.rb +87 -0
  96. data/lib/tap/test/script_test.rb +46 -0
  97. data/lib/tap/test/script_tester.rb +115 -0
  98. data/lib/tap/test/subset_test.rb +260 -0
  99. data/lib/tap/test/subset_test_class.rb +99 -0
  100. data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
  101. data/lib/tap/test/utils.rb +231 -0
  102. data/lib/tap/test.rb +53 -26
  103. data/lib/tap.rb +3 -20
  104. metadata +50 -27
  105. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
  106. data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
  107. data/lib/tap/patches/rake/testtask.rb +0 -57
  108. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
  109. data/lib/tap/patches/ruby19/parsedate.rb +0 -16
  110. data/lib/tap/support/batchable.rb +0 -47
  111. data/lib/tap/support/batchable_class.rb +0 -107
  112. data/lib/tap/support/command_line.rb +0 -98
  113. data/lib/tap/support/comment.rb +0 -270
  114. data/lib/tap/support/constant_utils.rb +0 -127
  115. data/lib/tap/support/declarations.rb +0 -111
  116. data/lib/tap/support/framework.rb +0 -83
  117. data/lib/tap/support/framework_class.rb +0 -180
  118. data/lib/tap/support/run_error.rb +0 -39
  119. data/lib/tap/support/summary.rb +0 -30
  120. data/lib/tap/test/file_methods.rb +0 -377
  121. data/lib/tap/test/script_methods/script_test.rb +0 -98
  122. data/lib/tap/test/script_methods.rb +0 -107
  123. data/lib/tap/test/subset_methods.rb +0 -420
  124. data/lib/tap/workflow.rb +0 -200
@@ -2,56 +2,250 @@ require 'tap/support/audit'
2
2
 
3
3
  module Tap
4
4
  module Support
5
- # Executable wraps methods to make them executable by App. Methods are
6
- # wrapped by extending the object that receives them; the easiest way
7
- # to make an object executable is to use Object#_method.
5
+
6
+ # Executable wraps objects to make them executable by App.
8
7
  module Executable
9
8
 
10
- # The method called when an Executable is executed via _execute
9
+ # The App receiving self during enq
10
+ attr_reader :app
11
+
12
+ # The method called during _execute
11
13
  attr_reader :_method_name
12
14
 
13
- # Indicates whether or not to execute in multithread mode.
14
- attr_accessor :multithread
15
-
16
- # Stores the on complete block.
15
+ # The block called when _execute completes
17
16
  attr_reader :on_complete_block
18
-
17
+
18
+ # An array of dependency indicies that will be resolved on _execute
19
+ attr_reader :dependencies
20
+
21
+ # The batch for self
22
+ attr_reader :batch
23
+
19
24
  public
20
25
 
21
26
  # Extends obj with Executable and sets up all required variables. The
22
27
  # specified method will be called on _execute.
23
- def self.initialize(obj, method_name, multithread=false, &on_complete_block)
28
+ def self.initialize(obj, method_name, app=App.instance, batch=[], dependencies=[], &on_complete_block)
24
29
  obj.extend Executable
30
+ obj.instance_variable_set(:@app, app)
25
31
  obj.instance_variable_set(:@_method_name, method_name)
26
- obj.instance_variable_set(:@multithread, multithread)
27
32
  obj.instance_variable_set(:@on_complete_block, on_complete_block)
33
+ obj.instance_variable_set(:@dependencies, dependencies)
34
+ obj.instance_variable_set(:@batch, batch)
35
+ batch << obj
36
+
28
37
  obj
29
38
  end
30
-
31
- # Sets a block to receive the results of _execute. Raises an error
32
- # if an on_complete block is already set. Override an existing
33
- # on_complete block by specifying override = true.
39
+
40
+ # Initializes a new batch object and adds the object to batch.
41
+ # The object will be a duplicate of self. (Note this method
42
+ # can raise an error for objects that don't support dup,
43
+ # notably Method objects generated by Object#_method).
44
+ def initialize_batch_obj
45
+ obj = self.dup
46
+
47
+ if obj.kind_of?(Executable)
48
+ batch << obj
49
+ obj
50
+ else
51
+ Executable.initialize(obj, _method_name, app, batch, dependencies, &on_complete_block)
52
+ end
53
+ end
54
+
55
+ # Returns true if the batch size is greater than one
56
+ # (the one is assumed to be self).
57
+ def batched?
58
+ batch.length > 1
59
+ end
60
+
61
+ # Returns the index of self in batch.
62
+ def batch_index
63
+ batch.index(self)
64
+ end
65
+
66
+ # Merges the batches for self and the specified Executables,
67
+ # removing duplicates.
68
+ #
69
+ # class BatchExecutable
70
+ # include Tap::Support::Executable
71
+ # def initialize(batch=[])
72
+ # @batch = batch
73
+ # batch << self
74
+ # end
75
+ # end
76
+ #
77
+ # b1 = BatchExecutable.new
78
+ # b2 = BatchExecutable.new
79
+ # b3 = BatchExecutable.new
80
+ #
81
+ # b1.batch_with(b2, b3)
82
+ # b1.batch # => [b1, b2, b3]
83
+ # b3.batch # => [b1, b2, b3]
84
+ #
85
+ # Note that batch_with is not recursive (ie it does not
86
+ # merge the batches of each member in the batch):
87
+ #
88
+ # b4 = BatchExecutable.new
89
+ # b4.batch_with(b3)
90
+ #
91
+ # b4.batch # => [b4, b1, b2, b3]
92
+ # b3.batch # => [b4, b1, b2, b3]
93
+ # b2.batch # => [b1, b2, b3]
94
+ # b1.batch # => [b1, b2, b3]
95
+ #
96
+ # However it does affect all objects that share the same
97
+ # underlying batch:
98
+ #
99
+ # b5 = BatchExecutable.new(b1.batch)
100
+ # b6 = BatchExecutable.new
101
+ #
102
+ # b5.batch.object_id # => b1.batch.object_id
103
+ # b5.batch # => [b1, b2, b3, b5]
104
+ #
105
+ # b5.batch_with(b6)
106
+ #
107
+ # b5.batch # => [b1, b2, b3, b5, b6]
108
+ # b1.batch # => [b1, b2, b3, b5, b6]
109
+ #
110
+ # Returns self.
111
+ def batch_with(*executables)
112
+ batches = [batch] + executables.collect {|executable| executable.batch }
113
+ batches.uniq!
114
+
115
+ merged = []
116
+ batches.each do |batch|
117
+ merged.concat(batch)
118
+ batch.clear
119
+ end
120
+
121
+ merged.uniq!
122
+ batches.each {|batch| batch.concat(merged) }
123
+ self
124
+ end
125
+
126
+ # Enqueues each member of batch (and implicitly self) to app with the
127
+ # inputs. The number of inputs provided should match the number of
128
+ # inputs for the _method_name method.
129
+ def enq(*inputs)
130
+ batch.each do |executable|
131
+ executable.unbatched_enq(*inputs)
132
+ end
133
+ self
134
+ end
135
+
136
+ # Like enq, but only enques self.
137
+ def unbatched_enq(*inputs)
138
+ app.queue.enq(self, inputs)
139
+ self
140
+ end
141
+
142
+ # Sets a block to receive the results of _execute for each member of
143
+ # batch (and implicitly self). Raises an error if on_complete_block
144
+ # is already set within the batch. Override the existing
145
+ # on_complete_block by specifying override = true.
34
146
  #
35
- # Note the block recieves an audited result and not
36
- # the result itself (see Audit for more information).
147
+ # Note: the block recieves an audited result and not the result
148
+ # itself (see Audit for more information).
37
149
  def on_complete(override=false, &block) # :yields: _result
150
+ batch.each do |executable|
151
+ executable.unbatched_on_complete(override, &block)
152
+ end
153
+ self
154
+ end
155
+
156
+ # Like on_complete, but only sets the on_complete_block for self.
157
+ def unbatched_on_complete(override=false, &block) # :yields: _result
38
158
  unless on_complete_block == nil || override
39
159
  raise "on_complete_block already set: #{self}"
40
160
  end
41
161
  @on_complete_block = block
162
+ self
163
+ end
164
+
165
+ # Sets a sequence workflow pattern for the tasks; each task
166
+ # enques the next task with it's results, starting with self.
167
+ # See Joins::Sequence.
168
+ def sequence(*tasks, &block) # :yields: _result
169
+ Joins::Sequence.join(self, tasks, &block)
170
+ end
171
+
172
+ # Sets a fork workflow pattern for self; each target
173
+ # will enque the results of self. See Joins::Fork.
174
+ def fork(*targets, &block) # :yields: _result
175
+ Joins::Fork.join(self, targets, &block)
42
176
  end
43
177
 
44
- # Auditing method call. Executes _method_name for self, but audits
45
- # the result. Sends the audited result to the on_complete_block if set.
178
+ # Sets a simple merge workflow pattern for the source tasks. Each
179
+ # source enques self with it's result; no synchronization occurs,
180
+ # nor are results grouped before being enqued. See Joins::Merge.
181
+ def merge(*sources, &block) # :yields: _result
182
+ Joins::Merge.join(self, sources, &block)
183
+ end
184
+
185
+ # Sets a synchronized merge workflow for the source tasks. Results
186
+ # from each source are collected and enqued as a single group to
187
+ # self. The collective results are not enqued until all sources
188
+ # have completed. See Joins::SyncMerge.
189
+ #
190
+ # Raises an error if a source returns twice before the target is enqued.
191
+ def sync_merge(*sources, &block) # :yields: _result
192
+ Joins::SyncMerge.join(self, sources, &block)
193
+ end
194
+
195
+ # Sets a switch workflow pattern for self. When _execute completes,
196
+ # switch yields the audited result to the block which should return
197
+ # the index of the target to enque with the results. No target will
198
+ # be enqued if the index is false or nil; an error is raised if no
199
+ # target can be found for the specified index. See Joins::Switch.
200
+ def switch(*targets, &block) # :yields: _result
201
+ Joins::Switch.join(self, targets, &block)
202
+ end
203
+
204
+ # Adds the dependency to each member in batch (and implicitly self).
205
+ # The dependency will be resolved with the input arguments during
206
+ # _execute, using resolve_dependencies.
207
+ def depends_on(dependency)
208
+ batch.each do |e|
209
+ e.unbatched_depends_on(dependency)
210
+ end
211
+ self
212
+ end
213
+
214
+ # Like depends_on, but only adds the dependency to self.
215
+ def unbatched_depends_on(dependency)
216
+ raise ArgumentError, "cannot depend on self" if dependency == self
217
+
218
+ app.dependencies.register(dependency)
219
+ dependencies << dependency unless dependencies.include?(dependency)
220
+ self
221
+ end
222
+
223
+ # Resolves dependencies. (See Dependency#resolve).
224
+ def resolve_dependencies
225
+ dependencies.each {|dependency| dependency.resolve }
226
+ self
227
+ end
228
+
229
+ # Resets dependencies so they will be re-resolved on
230
+ # resolve_dependencies. (See Dependency#reset).
231
+ def reset_dependencies
232
+ dependencies.each {|dependency| dependency.reset }
233
+ self
234
+ end
235
+
236
+ # Auditing method call. Resolves dependencies, executes _method_name,
237
+ # and sends the audited result to the on_complete_block (if set).
46
238
  #
47
239
  # Audits are initialized in the follwing manner:
48
- # no inputs:: create a new, empty Audit. The first value of the audit
49
- # will be the result of call
50
- # one input:: forks the input if it is an audit, otherwise initializes
51
- # a new audit using the input
52
- # multiple inputs:: merges the inputs into a new Audit.
240
+ # no inputs:: Creates a new, empty Audit. The first value of the audit
241
+ # will be the result of call.
242
+ # one input:: Forks the input if it is an audit, otherwise initializes
243
+ # a new audit using the input.
244
+ # multiple inputs:: Merges the inputs into a new Audit.
53
245
  #
54
246
  def _execute(*inputs)
247
+ resolve_dependencies
248
+
55
249
  audit = case inputs.length
56
250
  when 0 then Audit.new
57
251
  when 1
@@ -77,10 +271,30 @@ module Tap
77
271
  end
78
272
 
79
273
  audit._record(self, send(_method_name, *inputs))
80
- on_complete_block.call(audit) if on_complete_block
81
-
274
+ on_complete_block ? on_complete_block.call(audit) : app.aggregator.store(audit)
275
+
82
276
  audit
83
277
  end
278
+
279
+ # Calls _execute with the inputs and returns the un-audited result.
280
+ # Execute is not a batched method.
281
+ def execute(*inputs)
282
+ _execute(*inputs)._current
283
+ end
284
+
285
+ # Raises a TerminateError if app.state == State::TERMINATE.
286
+ # check_terminate may be called at any time to provide a
287
+ # breakpoint in long-running processes.
288
+ def check_terminate
289
+ if app.state == App::State::TERMINATE
290
+ raise App::TerminateError.new
291
+ end
292
+ end
293
+
294
+ def inspect
295
+ "#<#{self.class.to_s}:#{object_id} _method: #{_method_name} batch_length: #{batch.length} app: #{app}>"
296
+ end
297
+
84
298
  end
85
299
  end
86
300
  end
@@ -92,7 +306,7 @@ end
92
306
  # push_to_array = array._method(:push)
93
307
  #
94
308
  # task = Tap::Task.new
95
- # task.app.sequence(task, push_to_array)
309
+ # task.sequence(push_to_array)
96
310
  #
97
311
  # task.enq(1).enq(2,3)
98
312
  # task.app.run
@@ -101,11 +315,12 @@ end
101
315
  #
102
316
  class Object
103
317
 
104
- # Initializes a Tap::Support::Executable using the Method returned by
105
- # Object#method(method_name), setting multithread and the on_complete
106
- # block as specified. Returns nil if Object#method returns nil.
107
- def _method(method_name, multithread=false, &on_complete_block) # :yields: _result
318
+ # Initializes a Tap::Support::Executable using the object returned by
319
+ # Object#method(method_name).
320
+ #
321
+ # Returns nil if Object#method returns nil.
322
+ def _method(method_name, app=Tap::App.instance)
108
323
  return nil unless m = method(method_name)
109
- Tap::Support::Executable.initialize(m, :call, multithread, &on_complete_block)
324
+ Tap::Support::Executable.initialize(m, :call, app)
110
325
  end
111
326
  end
@@ -71,7 +71,7 @@ module Tap
71
71
 
72
72
  protected
73
73
 
74
- attr_accessor :queue
74
+ attr_accessor :queue # :nodoc:
75
75
 
76
76
  # Checks if the input method is extended with Executable
77
77
  def check_method(method) # :nodoc:
@@ -21,8 +21,28 @@ module Tap
21
21
 
22
22
  attr_accessor :env
23
23
 
24
- def collect_tasks
25
- ARGV.collect! do |arg|
24
+ def enq_top_level(app)
25
+ # takes the place of rake.top_level
26
+ if options.show_tasks
27
+ display_tasks_and_comments
28
+ exit
29
+ elsif options.show_prereqs
30
+ display_prerequisites
31
+ exit
32
+ else
33
+ top_level_tasks.each do |task_string|
34
+ name, args = parse_task_string(task_string)
35
+ task = self[name]
36
+ app.mq(task, :invoke, *args)
37
+ end
38
+ end
39
+ end
40
+
41
+ def collect_tasks(*args)
42
+ # a little song and dance for compliance with
43
+ # rake pre- and post-0.8.2
44
+ argv = args.empty? ? ARGV : args[0]
45
+ argv.collect! do |arg|
26
46
  next(arg) unless arg =~ /^:([a-z_\d]+):(.*)$/
27
47
  env_pattern = $1
28
48
  rake_task = $2
@@ -55,13 +75,13 @@ module Tap
55
75
  fail "No Rakefile found for '#{env_pattern}' (looking for: #{@rakefiles.join(', ')})"
56
76
  end
57
77
  end
58
-
78
+
59
79
  super
60
80
  end
61
81
 
62
- def have_rakefile(indir=nil)
63
- return super() if indir == nil
64
- Tap::Root.indir(indir) { super() }
82
+ def have_rakefile(dir=nil)
83
+ return super() if dir == nil
84
+ Tap::Root.chdir(dir) { super() }
65
85
  end
66
86
 
67
87
  protected
@@ -86,5 +106,6 @@ end
86
106
  end
87
107
 
88
108
  Rake.application.extend Tap::Support::Gems::Rake
89
- Tap::Env.manifests[:rakefiles] = Tap::Support::RakeManifest
90
-
109
+ Tap::Env.manifest(:rakefiles) do |env|
110
+ Tap::Support::Gems::RakeManifest.new(env)
111
+ end
@@ -1,36 +1,13 @@
1
- autoload(:Gem, 'rubygems')
1
+ require 'rubygems'
2
2
 
3
3
  module Tap
4
4
  module Support
5
- module Gems
6
- module_function
7
-
8
- # Finds the home directory for the user (method taken from Rubygems).
9
- def find_home
10
- ['HOME', 'USERPROFILE'].each do |homekey|
11
- return ENV[homekey] if ENV[homekey]
12
- end
13
-
14
- if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
15
- return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
16
- end
17
-
18
- begin
19
- File.expand_path("~")
20
- rescue
21
- if File::ALT_SEPARATOR then
22
- "C:/"
23
- else
24
- "/"
25
- end
26
- end
27
- end
28
-
29
- # The home directory for the user.
30
- def user_home
31
- @user_home ||= find_home
32
- end
33
-
5
+
6
+ # Methods for working with {RubyGems}[http://www.rubygems.org/]
7
+ # and other gems frequently used by Tap.
8
+ module Gems
9
+ module_function
10
+
34
11
  # Returns the gemspec for the specified gem. A gem version
35
12
  # can be specified in the name, like 'gem >= 1.2'. The gem
36
13
  # will be activated using +gem+ if necessary.
@@ -49,6 +26,9 @@ module Tap
49
26
  Gem.loaded_specs[name]
50
27
  end
51
28
 
29
+ # Selects gem specs for which the block returns true. If
30
+ # latest is specified, only the latest version of each
31
+ # gem will be passed to the block.
52
32
  def select_gems(latest=true)
53
33
  index = latest ?
54
34
  Gem.source_index.latest_specs :
@@ -47,11 +47,24 @@ module Tap
47
47
  @class_config = class_config
48
48
  end
49
49
 
50
+ # Updates self to ensure that each class_config key
51
+ # has a value in self; the config.default value is
52
+ # set if a value does not already exist.
53
+ #
54
+ # Returns self.
55
+ def update(class_config=self.class_config)
56
+ class_config.each_pair do |key, config|
57
+ self[key] ||= config.default
58
+ end
59
+ self
60
+ end
61
+
50
62
  # Binds self to the specified receiver. Mapped keys are
51
63
  # removed from store and sent to their writer method on
52
64
  # receiver.
53
65
  def bind(receiver)
54
- raise ArgumentError.new("receiver cannot be nil") if receiver == nil
66
+ raise "already bound to: #{@receiver}" if bound?
67
+ raise ArgumentError, "receiver cannot be nil" if receiver == nil
55
68
 
56
69
  class_config.each_pair do |key, config|
57
70
  receiver.send(config.writer, store.delete(key)) if config.writer
@@ -88,7 +101,7 @@ module Tap
88
101
 
89
102
  # Associates the value the key. If bound? and the key
90
103
  # is a class_config key, then the value will be forwarded
91
- # to the class_config.writer method on the receiver.
104
+ # to the config.writer method on the receiver.
92
105
  def []=(key, value)
93
106
  case
94
107
  when bound? && config = class_config.map[key.to_sym]
@@ -99,7 +112,7 @@ module Tap
99
112
 
100
113
  # Retrieves the value corresponding to the key. If bound?
101
114
  # and the key is a class_config key, then the value is
102
- # obtained from the :key method on the receiver.
115
+ # obtained from the config.reader method on the receiver.
103
116
  def [](key)
104
117
  case
105
118
  when bound? && config = class_config.map[key.to_sym]
@@ -138,6 +151,19 @@ module Tap
138
151
  hash
139
152
  end
140
153
 
154
+ def to_yaml(opts)
155
+ hash = {}
156
+ store.each_pair do |key, value|
157
+ hash[key.to_s] = value
158
+ end
159
+
160
+ class_config.each_pair do |key, config|
161
+ hash[key.to_s] = bound? ? self[key] : config.default
162
+ end
163
+
164
+ hash.to_yaml(opts)
165
+ end
166
+
141
167
  # Overrides default inspect to show the to_hash values.
142
168
  def inspect
143
169
  "#<#{self.class}:#{object_id} to_hash=#{to_hash.inspect}>"
@@ -0,0 +1,46 @@
1
+ module Tap
2
+ module Support
3
+
4
+ # Generates an Intern module for the specified method_name.
5
+ # An Intern module:
6
+ # - adds an accessor for <method_name>_block
7
+ # - overrides <method_name> to call the block
8
+ # - ensures initialize_batch_obj extends the batch object
9
+ # with the same Intern module
10
+ #
11
+ def self.Intern(method_name)
12
+ mod = INTERN_MODULES[method_name.to_sym]
13
+ return mod unless mod == nil
14
+
15
+ mod = INTERN_MODULES[method_name.to_sym] = Module.new
16
+ mod.module_eval %Q{
17
+ attr_accessor :#{method_name}_block
18
+
19
+ def #{method_name}(*inputs)
20
+ raise "no #{method_name} block set" unless #{method_name}_block
21
+ inputs.unshift(self)
22
+
23
+ arity = #{method_name}_block.arity
24
+ n = inputs.length
25
+ unless n == arity || (arity < 0 && (-1-n) <= arity)
26
+ raise ArgumentError.new("wrong number of arguments (\#{n} for \#{arity})")
27
+ end
28
+
29
+ #{method_name}_block.call(*inputs)
30
+ end
31
+
32
+ def initialize_batch_obj(*args)
33
+ super(*args).extend Tap::Support::Intern(:#{method_name})
34
+ end
35
+ }
36
+ mod
37
+ end
38
+
39
+ # An array of already-declared intern modules,
40
+ # keyed by method_name.
41
+ INTERN_MODULES = {}
42
+
43
+ # An Intern module for :process.
44
+ Intern = Support.Intern(:process)
45
+ end
46
+ end