tap 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/History +35 -1
  2. data/MIT-LICENSE +1 -1
  3. data/README +16 -15
  4. data/bin/tap +1 -1
  5. data/cmd/console.rb +4 -3
  6. data/cmd/manifest.rb +2 -2
  7. data/cmd/run.rb +12 -15
  8. data/doc/Class Reference +120 -117
  9. data/doc/Command Reference +27 -27
  10. data/doc/Syntax Reference +55 -111
  11. data/doc/Tutorial +69 -26
  12. data/lib/tap.rb +3 -8
  13. data/lib/tap/app.rb +122 -146
  14. data/lib/tap/constants.rb +2 -2
  15. data/lib/tap/env.rb +178 -252
  16. data/lib/tap/exe.rb +67 -30
  17. data/lib/tap/file_task.rb +224 -411
  18. data/lib/tap/generator/arguments.rb +13 -0
  19. data/lib/tap/generator/base.rb +112 -30
  20. data/lib/tap/generator/destroy.rb +36 -13
  21. data/lib/tap/generator/generate.rb +69 -48
  22. data/lib/tap/generator/generators/command/templates/command.erb +3 -3
  23. data/lib/tap/generator/generators/config/config_generator.rb +82 -10
  24. data/lib/tap/generator/generators/generator/generator_generator.rb +16 -6
  25. data/lib/tap/generator/generators/generator/templates/task.erb +2 -2
  26. data/lib/tap/generator/generators/generator/templates/test.erb +26 -0
  27. data/lib/tap/generator/generators/root/root_generator.rb +24 -13
  28. data/lib/tap/generator/generators/root/templates/Rakefile +4 -4
  29. data/lib/tap/generator/generators/root/templates/{tapfile → Rapfile} +6 -6
  30. data/lib/tap/generator/generators/root/templates/gemspec +0 -1
  31. data/lib/tap/generator/generators/task/task_generator.rb +3 -3
  32. data/lib/tap/generator/generators/task/templates/test.erb +1 -1
  33. data/lib/tap/generator/manifest.rb +7 -1
  34. data/lib/tap/generator/preview.rb +76 -0
  35. data/lib/tap/root.rb +222 -156
  36. data/lib/tap/spec.rb +41 -0
  37. data/lib/tap/support/aggregator.rb +25 -28
  38. data/lib/tap/support/audit.rb +278 -357
  39. data/lib/tap/support/constant.rb +2 -1
  40. data/lib/tap/support/constant_manifest.rb +28 -25
  41. data/lib/tap/support/dependency.rb +1 -1
  42. data/lib/tap/support/executable.rb +52 -183
  43. data/lib/tap/support/executable_queue.rb +50 -20
  44. data/lib/tap/support/gems.rb +1 -1
  45. data/lib/tap/support/intern.rb +0 -6
  46. data/lib/tap/support/join.rb +49 -83
  47. data/lib/tap/support/joins.rb +0 -3
  48. data/lib/tap/support/joins/switch.rb +13 -11
  49. data/lib/tap/support/joins/sync_merge.rb +25 -50
  50. data/lib/tap/support/manifest.rb +1 -0
  51. data/lib/tap/support/node.rb +140 -20
  52. data/lib/tap/support/parser.rb +56 -42
  53. data/lib/tap/support/schema.rb +183 -157
  54. data/lib/tap/support/templater.rb +9 -1
  55. data/lib/tap/support/versions.rb +39 -0
  56. data/lib/tap/task.rb +150 -177
  57. data/lib/tap/tasks/dump.rb +4 -4
  58. data/lib/tap/tasks/load.rb +29 -29
  59. data/lib/tap/test.rb +66 -53
  60. data/lib/tap/test/env_vars.rb +3 -3
  61. data/lib/tap/test/extensions.rb +11 -17
  62. data/lib/tap/test/file_test.rb +74 -132
  63. data/lib/tap/test/file_test_class.rb +4 -1
  64. data/lib/tap/test/regexp_escape.rb +2 -2
  65. data/lib/tap/test/script_test.rb +2 -2
  66. data/lib/tap/test/subset_test.rb +6 -6
  67. data/lib/tap/test/tap_test.rb +28 -154
  68. metadata +30 -51
  69. data/bin/rap +0 -118
  70. data/cgi/run.rb +0 -97
  71. data/lib/tap/declarations.rb +0 -229
  72. data/lib/tap/generator/generators/config/templates/doc.erb +0 -12
  73. data/lib/tap/generator/generators/config/templates/nodoc.erb +0 -8
  74. data/lib/tap/generator/generators/file_task/file_task_generator.rb +0 -27
  75. data/lib/tap/generator/generators/file_task/templates/file.txt +0 -11
  76. data/lib/tap/generator/generators/file_task/templates/result.yml +0 -6
  77. data/lib/tap/generator/generators/file_task/templates/task.erb +0 -33
  78. data/lib/tap/generator/generators/file_task/templates/test.erb +0 -29
  79. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +0 -5
  80. data/lib/tap/patches/optparse/summarize.rb +0 -62
  81. data/lib/tap/support/assignments.rb +0 -173
  82. data/lib/tap/support/class_configuration.rb +0 -182
  83. data/lib/tap/support/combinator.rb +0 -125
  84. data/lib/tap/support/configurable.rb +0 -113
  85. data/lib/tap/support/configurable_class.rb +0 -271
  86. data/lib/tap/support/configuration.rb +0 -170
  87. data/lib/tap/support/gems/rake.rb +0 -111
  88. data/lib/tap/support/instance_configuration.rb +0 -173
  89. data/lib/tap/support/joins/fork.rb +0 -19
  90. data/lib/tap/support/joins/merge.rb +0 -22
  91. data/lib/tap/support/joins/sequence.rb +0 -21
  92. data/lib/tap/support/lazy_attributes.rb +0 -45
  93. data/lib/tap/support/lazydoc.rb +0 -386
  94. data/lib/tap/support/lazydoc/comment.rb +0 -503
  95. data/lib/tap/support/lazydoc/config.rb +0 -17
  96. data/lib/tap/support/lazydoc/definition.rb +0 -36
  97. data/lib/tap/support/lazydoc/document.rb +0 -152
  98. data/lib/tap/support/lazydoc/method.rb +0 -24
  99. data/lib/tap/support/tdoc.rb +0 -409
  100. data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -38
  101. data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -42
  102. data/lib/tap/support/validation.rb +0 -479
  103. data/lib/tap/tasks/rake.rb +0 -57
data/lib/tap.rb CHANGED
@@ -1,10 +1,5 @@
1
- autoload(:YAML, 'yaml') # expensive to load
2
-
3
- $:.unshift File.expand_path(File.dirname(__FILE__))
1
+ lib = File.expand_path(File.dirname(__FILE__))
2
+ $:.unshift(lib) unless $:.include?(lib)
4
3
 
5
4
  require 'tap/constants'
6
-
7
- # require in order...
8
- require 'tap/exe'
9
- require 'tap/task'
10
- require 'tap/file_task'
5
+ require 'tap/exe'
@@ -1,50 +1,95 @@
1
1
  require 'logger'
2
+
3
+ require 'tap/root'
2
4
  require 'tap/support/aggregator'
3
5
  require 'tap/support/dependencies'
4
6
  require 'tap/support/executable_queue'
7
+ require 'tap/task'
5
8
 
6
9
  module Tap
7
10
 
8
11
  # App coordinates the setup and running of tasks, and provides an interface
9
- # to the application directory structure. All tasks have an App (by default
10
- # App.instance) through which tasks access access application-wide resources
11
- # like the logger, executable queue, aggregator, and dependencies.
12
+ # to the application directory structure. All tasks have an app (by default
13
+ # App.instance) through which they access application-wide resources like
14
+ # the logger, executable queue, and dependencies.
12
15
  #
13
16
  # === Running Tasks
14
17
  #
15
- # Task enque command are forwarded to App#enq:
18
+ # Tasks may be enqued and run by an App:
19
+ #
20
+ # app = App.instance
16
21
  #
17
- # t0 = Task.intern {|task, input| "#{input}.0" }
18
- # t0.enq('a')
19
- # app.enq(t0, 'b')
22
+ # t = Task.intern {|task, *inputs| inputs }
23
+ # t.enq('a', 'b', 'c')
24
+ # t.enq(1)
25
+ # t.enq(2)
26
+ # t.enq(3)
20
27
  #
21
28
  # app.run
22
- # app.results(t0) # => ['a.0', 'b.0']
29
+ # app.results(t) # => [['a', 'b', 'c'], [1], [2], [3]]
23
30
  #
24
- # When a task completes, the results will be passed to the task on_complete
25
- # block, if set, or be collected into an Aggregator (aggregated results may
26
- # be accessed per-task, as shown above); on_complete blocks typically
27
- # execute or enque other tasks, allowing the construction of imperative
28
- # workflows:
31
+ # By default apps simply run tasks and collect the results. To construct
32
+ # a workflow, set an on_complete block to receive the audited result and
33
+ # enque or execute the next series of tasks. Here is a simple sequence:
29
34
  #
30
- # # clear the previous results
31
- # app.aggregator.clear
35
+ # t0 = Task.intern {|task| "0" }
36
+ # t1 = Task.intern {|task, input| "#{input}:1" }
37
+ # t2 = Task.intern {|task, input| "#{input}:2"}
32
38
  #
33
- # t1 = Task.intern {|task, input| "#{input}.1" }
34
39
  # t0.on_complete {|_result| t1.enq(_result) }
35
- # t0.enq 'c'
36
- #
40
+ # t1.on_complete {|_result| t2.enq(_result) }
41
+ #
42
+ # t0.enq
43
+ # app.run
44
+ # app.results(t0, t1) # => []
45
+ # app.results(t2) # => ["0:1:2"]
46
+ #
47
+ # Apps may be assigned an on_complete block as well; the app on_complete
48
+ # block is called when a task has no on_complete block set. If neither the
49
+ # task nor the app has an on_complete block, the app stores the audit in
50
+ # app.aggregator and makes it available through app.results. Note how after
51
+ # the sequence, the t0 and t1 results are not in the app (they were handled
52
+ # by the on_complete block).
53
+ #
54
+ # Tracking how inputs evolve through a workflow can be onerous. To help,
55
+ # Tap audits changes to the inputs. Audit values are by convention prefixed
56
+ # by an underscore; all on_complete block receive the audited values and not
57
+ # the actual result of a task. Aggregated audits are available through
58
+ # app._results.
59
+ #
60
+ # t2.enq("a")
61
+ # t1.enq("b")
37
62
  # app.run
38
- # app.results(t0) # => []
39
- # app.results(t1) # => ['c.0.1']
63
+ # app.results(t2) # => ["0:1:2", "a:2", "b:1:2"]
64
+ #
65
+ # t0.name = "zero"
66
+ # t1.name = "one"
67
+ # t2.name = "two"
68
+ #
69
+ # trails = app._results(t2).collect do |_result|
70
+ # _result.dump
71
+ # end
40
72
  #
41
- # Here t0 has no results because the on_complete block passed them to t1 in
42
- # a simple sequence.
73
+ # "\n" + trails.join("\n")
74
+ # # => %q{
75
+ # # o-[zero] "0"
76
+ # # o-[one] "0:1"
77
+ # # o-[two] "0:1:2"
78
+ # #
79
+ # # o-[] "a"
80
+ # # o-[two] "a:2"
81
+ # #
82
+ # # o-[] "b"
83
+ # # o-[one] "b:1"
84
+ # # o-[two] "b:1:2"
85
+ # # }
86
+ #
87
+ # See Audit for more details.
43
88
  #
44
89
  # === Dependencies
45
90
  #
46
- # Tasks allow the construction of dependency-based workflows such that a
47
- # dependent task only executes after its dependencies have been resolved.
91
+ # Tasks allow the construction of dependency-based workflows. A dependent
92
+ # task only executes after its dependencies have been resolved.
48
93
  #
49
94
  # runlist = []
50
95
  # t0 = Task.intern {|task| runlist << task }
@@ -62,114 +107,35 @@ module Tap
62
107
  # app.run
63
108
  # runlist # => [t1, t0, t0]
64
109
  #
65
- # === Batching
66
- #
67
- # Tasks can be batched, allowing the same input to be enqued to multiple
68
- # tasks at once.
69
- #
70
- # t0 = Task.intern {|task, input| "#{input}.0" }
71
- # t1 = Task.intern {|task, input| "#{input}.1" }
72
- #
73
- # t0.batch_with(t1)
74
- # t0.enq 'a'
75
- # t1.enq 'b'
76
- #
77
- # app.run
78
- # app.results(t0) # => ['a.0', 'b.0']
79
- # app.results(t1) # => ['a.1', 'b.1']
80
- #
81
110
  # === Executables
82
111
  #
83
112
  # App can enque and run any Executable object. Arbitrary methods may be
84
- # made into Executables using Object#_method. The mq (method enq) method
85
- # generates and enques methods in one step.
113
+ # made into Executables using Object#_method.
86
114
  #
87
115
  # array = []
88
116
  #
89
- # # longhand
90
117
  # m = array._method(:push)
91
118
  # m.enq(1)
92
- #
93
- # # shorthand
94
- # app.mq(array, :push, 2)
119
+ # m.enq(2)
120
+ # m.enq(3)
95
121
  #
96
122
  # array.empty? # => true
97
123
  # app.run
98
- # array # => [1, 2]
99
- #
100
- # === Auditing
101
- #
102
- # All results are audited to track how a given input evolves during a workflow.
103
- # To illustrate auditing, consider and addition workflow that ends in eights.
104
- #
105
- # add_one = Tap::Task.intern({}, 'add_one') {|task, input| input += 1 }
106
- # add_five = Tap::Task.intern({}, 'add_five') {|task, input| input += 5 }
107
- #
108
- # add_one.on_complete do |_result|
109
- # # _result is the audit; use the _current method
110
- # # to get the current value in the audit trail
111
- # current_value = _result._current
112
- #
113
- # if current_value < 3
114
- # add_one.enq(_result)
115
- # else
116
- # add_five.enq(_result)
117
- # end
118
- # end
119
- #
120
- # add_one.enq(0)
121
- # add_one.enq(1)
122
- # add_one.enq(2)
123
- #
124
- # app.run
125
- # app.results(add_five) # => [8,8,8]
126
- #
127
- # Although the results are indistinguishable, each achieved the final value
128
- # through a different series of tasks. With auditing you can see how each
129
- # input came to the final value of 8:
130
- #
131
- # # app.results returns the actual result values
132
- # # app._results returns the audits for these values
133
- # app._results(add_five).each do |_result|
134
- # puts "How #{_result._original} became #{_result._current}:"
135
- # puts _result._to_s
136
- # puts
137
- # end
138
- #
139
- # Prints:
140
- #
141
- # How 2 became 8:
142
- # o-[] 2
143
- # o-[add_one] 3
144
- # o-[add_five] 8
124
+ # array # => [1, 2, 3]
145
125
  #
146
- # How 1 became 8:
147
- # o-[] 1
148
- # o-[add_one] 2
149
- # o-[add_one] 3
150
- # o-[add_five] 8
151
- #
152
- # How 0 became 8:
153
- # o-[] 0
154
- # o-[add_one] 1
155
- # o-[add_one] 2
156
- # o-[add_one] 3
157
- # o-[add_five] 8
158
- #
159
- # See Tap::Support::Audit for more details.
160
126
  class App < Root
161
127
  class << self
162
128
  # Sets the current app instance
163
129
  attr_writer :instance
164
130
 
165
131
  # Returns the current instance of App. If no instance has been set,
166
- # then a new App with the default configuration will be initialized.
132
+ # then instance initializes a new App with the default configuration.
167
133
  def instance
168
134
  @instance ||= App.new
169
135
  end
170
136
  end
171
137
 
172
- # The shared logger
138
+ # The application logger
173
139
  attr_reader :logger
174
140
 
175
141
  # The application queue
@@ -178,13 +144,19 @@ module Tap
178
144
  # The state of the application (see App::State)
179
145
  attr_reader :state
180
146
 
181
- # A Tap::Support::Aggregator to collect the results of
147
+ # A Tap::Support::Aggregator that collects the results of
182
148
  # methods that have no on_complete block
183
149
  attr_reader :aggregator
184
150
 
185
151
  # A Tap::Support::Dependencies to track dependencies.
186
152
  attr_reader :dependencies
187
153
 
154
+ # The block called when an executable completes and has no
155
+ # on_complete_block set. An on_complete_block effectively
156
+ # takes the place of aggregation (ie when set, no results
157
+ # will be collected by self).
158
+ attr_reader :on_complete_block
159
+
188
160
  config :debug, false, &c.flag # Flag debugging
189
161
  config :force, false, &c.flag # Force execution at checkpoints
190
162
  config :quiet, false, &c.flag # Suppress logging
@@ -199,7 +171,7 @@ module Tap
199
171
 
200
172
  module_function
201
173
 
202
- # Returns the string corresponding to the input state value.
174
+ # Returns a string corresponding to the input state value.
203
175
  # Returns nil for unknown states.
204
176
  #
205
177
  # State.state_str(0) # => 'READY'
@@ -209,16 +181,19 @@ module Tap
209
181
  end
210
182
  end
211
183
 
184
+ include MonitorMixin
185
+
212
186
  # Creates a new App with the given configuration.
213
- def initialize(config={}, logger=DEFAULT_LOGGER)
187
+ def initialize(config={}, logger=DEFAULT_LOGGER, &block)
214
188
  super()
215
189
 
216
190
  @state = State::READY
217
191
  @queue = Support::ExecutableQueue.new
218
192
  @aggregator = Support::Aggregator.new
219
193
  @dependencies = Support::Dependencies.new
194
+ @on_complete_block = block
220
195
 
221
- initialize_config(config)
196
+ reconfigure(config)
222
197
  self.logger = logger
223
198
  end
224
199
 
@@ -250,20 +225,7 @@ module Tap
250
225
  logger.add(level, msg, action.to_s) if !quiet || verbose
251
226
  end
252
227
 
253
- # Returns the configuration filepath for the specified task name,
254
- # File.join(app['config'], task_name + ".yml"). Returns nil if
255
- # task_name is nil.
256
- def config_filepath(name)
257
- name == nil ? nil : filepath('config', "#{name}.yml")
258
- end
259
-
260
- # Sets state = State::READY unless the app is running. Returns self.
261
- def ready
262
- @state = State::READY unless state == State::RUN
263
- self
264
- end
265
-
266
- # Sequentially calls execute with the [executable, inputs] pairs in
228
+ # Sequentially calls execute with the (executable, inputs) pairs in
267
229
  # queue; run continues until the queue is empty and then returns self.
268
230
  #
269
231
  # ==== Run State
@@ -280,8 +242,10 @@ module Tap
280
242
  # Calls to run when the state is not State::READY do nothing and
281
243
  # return immediately.
282
244
  def run
283
- return self unless state == State::READY
284
- @state = State::RUN
245
+ synchronize do
246
+ return self unless state == State::READY
247
+ @state = State::RUN
248
+ end
285
249
 
286
250
  # TODO: log starting run
287
251
  begin
@@ -296,7 +260,7 @@ module Tap
296
260
  raise if debug?
297
261
  log($!.class, $!.message)
298
262
  ensure
299
- @state = State::READY
263
+ synchronize { @state = State::READY }
300
264
  end
301
265
 
302
266
  # TODO: log run complete
@@ -309,7 +273,7 @@ module Tap
309
273
  #
310
274
  # Does nothing unless state is State::RUN.
311
275
  def stop
312
- @state = State::STOP if state == State::RUN
276
+ synchronize { @state = State::STOP if state == State::RUN }
313
277
  self
314
278
  end
315
279
 
@@ -321,7 +285,7 @@ module Tap
321
285
  #
322
286
  # Does nothing if state == State::READY.
323
287
  def terminate
324
- @state = State::TERMINATE unless state == State::READY
288
+ synchronize { @state = State::TERMINATE unless state == State::READY }
325
289
  self
326
290
  end
327
291
 
@@ -333,18 +297,18 @@ module Tap
333
297
  "state: #{state} (#{State.state_str(state)}) queue: #{queue.size} results: #{aggregator.size}"
334
298
  end
335
299
 
336
- # Enques the task (or Executable) with the inputs. If the task is batched,
337
- # then each task in task.batch will be enqued with the inputs. Returns task.
300
+ # Enques the task (or Executable) with the inputs. Raises an error if the
301
+ # input is not an Executable, or is not assigned to self. Returns task.
338
302
  def enq(task, *inputs)
339
- case task
340
- when Tap::Task
341
- raise ArgumentError, "not assigned to enqueing app: #{task}" unless task.app == self
342
- task.enq(*inputs)
343
- when Support::Executable
344
- queue.enq(task, inputs)
345
- else
346
- raise ArgumentError, "not a Task or Executable: #{task}"
303
+ unless task.kind_of?(Support::Executable)
304
+ raise ArgumentError, "not an Executable: #{task.inspect}"
305
+ end
306
+
307
+ unless task.app == self
308
+ raise ArgumentError, "not assigned to enqueing app: #{task.inspect}"
347
309
  end
310
+
311
+ queue.enq(task, inputs)
348
312
  task
349
313
  end
350
314
 
@@ -367,20 +331,32 @@ module Tap
367
331
  #
368
332
  # t0 = Task.intern {|task, input| "#{input}.0" }
369
333
  # t1 = Task.intern {|task, input| "#{input}.1" }
370
- # t2 = Task.intern {|task, input| "#{input}.2" }
371
- # t1.batch_with(t2)
372
334
  #
373
335
  # t0.enq(0)
374
336
  # t1.enq(1)
375
337
  #
376
338
  # app.run
377
- # app.results(t0, t1.batch) # => ["0.0", "1.1", "1.2"]
378
339
  # app.results(t1, t0) # => ["1.1", "0.0"]
379
340
  #
380
341
  def results(*tasks)
381
- _results(tasks).collect {|_result| _result._current}
342
+ _results(tasks).collect {|_result| _result.value }
343
+ end
344
+
345
+ # Sets a block to receive the results tasks with no on_complete_block
346
+ # set. Raises an error if an on_complete_block is already set.
347
+ # Override the existing on_complete_block by specifying override = true.
348
+ #
349
+ # Note: the block recieves an audited result and not the result
350
+ # itself (see Audit for more information).
351
+ def on_complete(override=false, &block) # :yields: _result
352
+ unless on_complete_block == nil || override
353
+ raise "on_complete_block already set: #{self}"
354
+ end
355
+ @on_complete_block = block
356
+ self
382
357
  end
383
358
 
359
+ # Returns a string like: "#<Tap::App:#{object_id} root: #{root} >"
384
360
  def inspect
385
361
  "#<#{self.class.to_s}:#{object_id} root: #{root} >"
386
362
  end
@@ -1,7 +1,7 @@
1
1
  module Tap
2
2
  MAJOR = 0
3
- MINOR = 11
4
- TINY = 1
3
+ MINOR = 12
4
+ TINY = 0
5
5
 
6
6
  VERSION="#{MAJOR}.#{MINOR}.#{TINY}"
7
7
  WEBSITE="http://tap.rubyforge.org"
@@ -1,14 +1,23 @@
1
1
  require 'tap/support/constant_manifest'
2
- require 'tap/support/gems'
3
2
 
4
3
  module Tap
5
-
4
+ module Support
5
+ autoload(:Templater, 'tap/support/templater')
6
+ autoload(:Gems, 'tap/support/gems')
7
+ end
8
+
9
+ # Envs are locations on the filesystem that have resources associated with
10
+ # them (commands, tasks, generators, etc). Envs may point to files, but it's
11
+ # more commonly environments are set to a directory and resources are various
12
+ # files within the directory.
13
+ #
14
+ #
6
15
  #--
7
16
  # Note that gems and env_paths reset envs -- custom modifications to envs will be lost
8
17
  # whenever these configs are reset.
9
18
  class Env
10
19
  include Enumerable
11
- include Support::Configurable
20
+ include Configurable
12
21
  include Support::Minimap
13
22
 
14
23
  class << self
@@ -38,68 +47,45 @@ module Tap
38
47
  # # File.expand_path("./path/to/config.yml") => e1,
39
48
  # # File.expand_path("./path/to/dir/#{Tap::Env::DEFAULT_CONFIG_FILE}") => e2 }
40
49
  #
41
- # The Env is initialized using configurations read from the env config file using
42
- # load_config, and a Root initialized to the config file directory. An instance
43
- # will be initialized regardless of whether the config file or directory exists.
44
- def instantiate(path_or_root, default_config={}, logger=nil, &block)
45
- path = path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root
46
- path = pathify(path)
50
+ # The Env is initialized using configurations read from the env config
51
+ # file. An instance will be initialized regardless of whether the config
52
+ # file or directory exists.
53
+ def instantiate(path_or_root)
54
+ path = config_path(path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root)
55
+ return instances[path] if instances.has_key?(path)
47
56
 
48
- begin
49
- root = path_or_root.kind_of?(Root) ? path_or_root : Root.new(File.dirname(path))
50
- config = default_config.merge(load_config(path))
51
-
52
- # note the assignment of env to instances MUST occur before
53
- # reconfigure to prevent infinite looping
54
- (instances[path] = new({}, root, logger)).reconfigure(config, &block)
55
- rescue(Exception)
56
- raise Env::ConfigError.new($!, path)
57
- end
58
- end
59
-
60
- def instance_for(path)
61
- path = pathify(path)
62
- instances.has_key?(path) ? instances[path] : instantiate(path)
63
- end
64
-
65
- def pathify(path)
66
- if File.directory?(path) || (!File.exists?(path) && File.extname(path) == "")
67
- path = File.join(path, DEFAULT_CONFIG_FILE)
68
- end
69
- File.expand_path(path)
57
+ config = load_config(path)
58
+ root = path_or_root.kind_of?(Root) ? path_or_root : File.dirname(path)
59
+
60
+ # note the assignment of env to instances MUST occur
61
+ # before reconfigure to prevent infinite looping
62
+ (instances[path] = new(root)).reconfigure(config)
70
63
  end
71
64
 
72
- def manifest(name, &block) # :yields: env (returns manifest)
65
+ def manifest(name, &block) # :yields: env (and should return a manifest)
73
66
  name = name.to_sym
74
67
  define_method(name) do
75
68
  self.manifests[name] ||= block.call(self).bind(self, name)
76
69
  end
77
70
  end
78
71
 
79
- # Returns the gemspecs for all installed gems with a DEFAULT_CONFIG_FILE.
80
- # If latest==true, then only the specs for the most current gems will be
81
- # returned.
82
- def gemspecs(latest=true)
83
- Support::Gems.select_gems(latest) do |spec|
84
- File.exists?(File.join(spec.full_gem_path, DEFAULT_CONFIG_FILE))
72
+ private
73
+
74
+ def config_path(path) # :nodoc:
75
+ if File.directory?(path) || (!File.exists?(path) && File.extname(path) == "")
76
+ path = File.join(path, DEFAULT_CONFIG_FILE)
85
77
  end
78
+
79
+ File.expand_path(path)
86
80
  end
87
81
 
88
- protected
89
-
90
- # Defines a config that collects the input into a unique,
91
- # compact array where each member has been resolved using
92
- # root[]. In short, ['lib', nil, 'lib', 'alt] becomes
93
- # [root['lib'], root['alt']].
94
- #
95
- # Single and nil arguments are allowed; they are arrayified
96
- # and handled as above. Path configs raise an error if
97
- # modified when the instance is active.
98
- def path_config(key, value=[])
99
- instance_variable = "@#{key}".to_sym
100
- config_attr(key, value) do |input|
101
- check_configurable
102
- instance_variable_set(instance_variable, [*input].compact.collect {|path| root[path]}.uniq)
82
+ # helper to load path as YAML. load_file returns a hash if the path
83
+ # loads to nil or false (as happens for empty files)
84
+ def load_config(path) # :nodoc:
85
+ begin
86
+ Root.trivial?(path) ? {} : (YAML.load_file(path) || {})
87
+ rescue(Exception)
88
+ raise Env::ConfigError.new($!, path)
103
89
  end
104
90
  end
105
91
  end
@@ -110,17 +96,19 @@ module Tap
110
96
  # The default config file path
111
97
  DEFAULT_CONFIG_FILE = "tap.yml"
112
98
 
113
- # The Root directory structure for self.
114
- attr_reader :root
115
-
116
- # Gets or sets the logger for self
117
- attr_accessor :logger
99
+ # An array of nested Envs, by default comprised of the env_path
100
+ # + gem environments (in that order). Nested environments are
101
+ # activated/deactivated with self.
102
+ attr_reader :envs
118
103
 
119
- # Specify files to require when self is activated.
120
- config :requires, [], &c.array_or_nil
121
-
122
- # Specify files to load when self is activated.
123
- config :loads, [], &c.array_or_nil
104
+ # The Root directory structure for self.
105
+ nest(:root, Tap::Root) do |config|
106
+ case config
107
+ when Root then config
108
+ when String then Root.new(config)
109
+ else Root.new.reconfigure(config)
110
+ end
111
+ end
124
112
 
125
113
  # Specify gems to load as nested Envs. Gems may be specified
126
114
  # by name and/or version, like 'gemname >= 1.2'; by default the
@@ -128,14 +116,24 @@ module Tap
128
116
  #
129
117
  # Gems are immediately loaded (via gem) through this method.
130
118
  config_attr :gems, [] do |input|
131
- check_configurable
132
119
  specs_by_name = {}
120
+
121
+ input = YAML.load(input) if input.kind_of?(String)
122
+ input = case input
123
+ when :latest, :all
124
+ Support::Gems.select_gems(input == :latest) do |spec|
125
+ env_config = File.join(spec.full_gem_path, Tap::Env::DEFAULT_CONFIG_FILE)
126
+ File.exists?(env_config)
127
+ end
128
+ else input
129
+ end
130
+
133
131
  @gems = [*input].compact.collect do |gem_name|
134
132
  spec = Support::Gems.gemspec(gem_name)
135
133
 
136
134
  case spec
137
135
  when nil then log(:warn, "unknown gem: #{gem_name}", Logger::WARN)
138
- else Env.instance_for(spec.full_gem_path)
136
+ else Env.instantiate(spec.full_gem_path)
139
137
  end
140
138
 
141
139
  (specs_by_name[spec.name] ||= []) << spec
@@ -157,21 +155,28 @@ module Tap
157
155
 
158
156
  # Specify configuration files to load as nested Envs.
159
157
  config_attr :env_paths, [] do |input|
160
- check_configurable
158
+ input = YAML.load(input) if input.kind_of?(String)
161
159
  @env_paths = [*input].compact.collect do |path|
162
- Env.instance_for(root[path]).env_path
160
+ Env.instantiate(root[path]).env_path
163
161
  end.uniq
164
162
  reset_envs
165
163
  end
166
164
 
167
165
  # Designate load paths.
168
- path_config :load_paths, ["lib"]
166
+ config_attr :load_paths, ["lib"] do |paths|
167
+ raise "load_paths cannot be modified once active" if active?
168
+ @load_paths = resolve_paths(paths)
169
+ end
169
170
 
170
171
  # Designate paths for discovering and executing commands.
171
- path_config :command_paths, ["cmd"]
172
+ config_attr :command_paths, ["cmd"] do |paths|
173
+ @command_paths = resolve_paths(paths)
174
+ end
172
175
 
173
176
  # Designate paths for discovering generators.
174
- path_config :generator_paths, ["lib"]
177
+ config_attr :generator_paths, ["lib"] do |paths|
178
+ @generator_paths = resolve_paths(paths)
179
+ end
175
180
 
176
181
  manifest(:commands) do |env|
177
182
  paths = []
@@ -203,10 +208,8 @@ module Tap
203
208
  # generators.cache = env.cache[:generators]
204
209
  generators
205
210
  end
206
-
207
- def initialize(config={}, root=Tap::Root.new, logger=nil)
208
- @root = root
209
- @logger = logger
211
+
212
+ def initialize(path_root_or_config=Dir.pwd)
210
213
  @envs = []
211
214
  @active = false
212
215
  @manifests = {}
@@ -215,27 +218,31 @@ module Tap
215
218
  @gems = []
216
219
  @env_paths = []
217
220
 
218
- initialize_config(config)
221
+ initialize_config case path_root_or_config
222
+ when String, Root then {:root => path_root_or_config}
223
+ else path_root_or_config
224
+ end
219
225
  end
220
226
 
221
- # Sets envs removing duplicates and instances of self.
222
- def envs=(envs)
223
- @envs = envs.uniq.delete_if {|e| e == self }
224
- @envs.freeze
225
- @flat_envs = nil
227
+ # Clears manifests so they may be regenerated.
228
+ def reset
229
+ @manifests.clear
226
230
  end
227
231
 
228
- # An array of nested Envs, by default comprised of the
229
- # env_path + gem environments (in that order). These
230
- # nested Envs are activated/deactivated with self.
231
- #
232
- # Returns a flattened array of the unique nested envs
233
- # when flat == true.
234
- def envs(flat=false)
235
- flat ? (@flat_envs ||= self.flatten_envs.freeze) : @envs
232
+ # Returns the key for self in Env.instances.
233
+ def env_path
234
+ Env.instances.each_pair {|path, env| return path if env == self }
235
+ nil
236
+ end
237
+
238
+ # Sets envs removing duplicates and instances of self. Setting envs
239
+ # overrides any environments specified by env_path and gem.
240
+ def envs=(envs)
241
+ raise "envs cannot be modified once active" if active?
242
+ @envs = envs.uniq.delete_if {|e| e == self }
236
243
  end
237
244
 
238
- # Unshifts env onto envs, removing duplicates.
245
+ # Unshifts env onto envs, removing duplicates.
239
246
  # Self cannot be unshifted onto self.
240
247
  def unshift(env)
241
248
  unless env == self || envs[0] == env
@@ -256,27 +263,24 @@ module Tap
256
263
 
257
264
  # Passes each nested env to the block in order, starting with self.
258
265
  def each
259
- envs(true).each {|e| yield(e) }
266
+ visit_envs.each {|e| yield(e) }
260
267
  end
261
268
 
262
269
  # Passes each nested env to the block in reverse order, ending with self.
263
270
  def reverse_each
264
- envs(true).reverse_each {|e| yield(e) }
271
+ visit_envs.reverse_each {|e| yield(e) }
265
272
  end
266
273
 
267
- # Visits each nested env in order, starting with self, and passing
268
- # to the block the env and any arguments generated by the parent of
269
- # the env. The initial arguments are set when recursive_each is
270
- # first called; subsequent arguements are the return values of the
271
- # block.
274
+ # Recursively injects the memo to each env of self. Each env in envs
275
+ # receives the same memo from the parent.
272
276
  #
273
- # e0, e1, e2, e3, e4 = ('a'..'e').collect {|name| Tap::Env.new(:name => name) }
277
+ # a,b,c,d,e = ('a'..'e').collect {|name| Tap::Env.new(:name => name) }
274
278
  #
275
- # e0.push(e1).push(e2)
276
- # e1.push(e3).push(e4)
279
+ # a.push(b).push(c)
280
+ # b.push(d).push(e)
277
281
  #
278
282
  # lines = []
279
- # e0.recursive_each(0) do |env, nesting_depth|
283
+ # a.recursive_inject(0) do |nesting_depth, env|
280
284
  # lines << "\n#{'..' * nesting_depth}#{env.config[:name]} (#{nesting_depth})"
281
285
  # nesting_depth + 1
282
286
  # end
@@ -289,151 +293,74 @@ module Tap
289
293
  # # ....e (2)
290
294
  # # ..c (1)}
291
295
  #
292
- def recursive_each(*args, &block) # :yields: env, *parent_args
293
- each_nested_env(self, [], args, &block)
294
- end
295
-
296
- # Returns the total number of unique envs nested in self (including self).
297
- def count
298
- envs(true).length
296
+ def recursive_inject(memo, &block) # :yields: memo, env
297
+ inject_envs(memo, &block)
299
298
  end
300
299
 
301
- # Processes and resets the input configurations for both root
302
- # and self. Reconfiguration consists of the following steps:
300
+ # Activates self by doing the following, in order:
303
301
  #
304
- # * partition overrides into env, root, and other configs
305
- # * reconfigure root with the root configs
306
- # * reconfigure self with the env configs
307
- # * yield other configs to the block (if given)
302
+ # * sets Env.instance to self (unless already set)
303
+ # * activate nested environments
304
+ # * unshift load_paths to $LOAD_PATH
308
305
  #
309
- # Reconfigure will always yields to the block, even if there
310
- # are no non-root, non-env configurations. Unspecified
311
- # configurations are NOT reconfigured. (Note this means
312
- # that existing path configurations like load_paths will
313
- # not automatically be reset using reconfigured root.)
314
- def reconfigure(overrides={})
315
- check_configurable
316
-
317
- # partiton config into its parts
318
- env_configs = {}
319
- root_configs = {}
320
- other_configs = {}
321
-
322
- env_configurations = self.class.configurations
323
- root_configurations = root.class.configurations
324
- overrides.each_pair do |key, value|
325
- key = key.to_sym
326
-
327
- partition = case
328
- when env_configurations.key?(key) then env_configs
329
- when root_configurations.key?(key) then root_configs
330
- else other_configs
331
- end
332
-
333
- partition[key] = value
334
- end
335
-
336
- # reconfigure root so it can resolve path_configs
337
- root.reconfigure(root_configs)
338
-
339
- # reconfigure self
340
- super(env_configs)
341
-
342
- # handle other configs
343
- case
344
- when block_given?
345
- yield(other_configs)
346
- when !other_configs.empty?
347
- log(:warn, "ignoring non-env configs: #{other_configs.keys.join(',')}", Logger::DEBUG)
348
- end
349
-
350
- self
351
- end
352
-
353
- # Returns the path for self in Env.instances.
354
- def env_path
355
- Env.instances.each_pair {|path, env| return path if env == self }
356
- nil
357
- end
358
-
359
- # Logs the action and message at the input level (default INFO).
360
- # Logging is suppressed if no logger is set.
361
- def log(action, msg="", level=Logger::INFO)
362
- logger.add(level, msg, action.to_s) if logger
363
- end
364
-
365
- # Activates self by unshifting load_paths for self to the load_path_targets.
366
- # Once active, self can be referenced from Env.instance and the current
367
- # configurations are frozen. Env.instance is deactivated, if set, before
368
- # self is activated. Returns true if activate succeeded, or false if self
369
- # is already active.
306
+ # Once active, the current envs and load_paths are frozen and cannot be
307
+ # modified until deactivated. Returns true if activate succeeded, or
308
+ # false if self is already active.
370
309
  def activate
371
310
  return false if active?
372
311
 
373
312
  @active = true
374
313
  @@instance = self if @@instance == nil
375
314
 
376
- # freeze array configs like load_paths
377
- config.each_pair do |key, value|
378
- case value
379
- when Array then value.freeze
380
- end
381
- end
315
+ # freeze envs and load paths
316
+ @envs.freeze
317
+ @load_paths.freeze
382
318
 
383
319
  # activate nested envs
384
320
  envs.reverse_each do |env|
385
321
  env.activate
386
322
  end
387
-
323
+
388
324
  # add load paths
389
325
  load_paths.reverse_each do |path|
390
326
  $LOAD_PATH.unshift(path)
391
327
  end
392
-
393
- $LOAD_PATH.uniq!
394
-
395
- # perform requires
396
- requires.each do |path|
397
- require path
398
- end
399
328
 
400
- # perform loads
401
- loads.each do |path|
402
- load path
403
- end
329
+ $LOAD_PATH.uniq!
404
330
 
405
331
  true
406
332
  end
407
333
 
408
- # Deactivates self by clearing manifests and deleting load_paths for self
409
- # from the load_path_targets. Env.instance will no longer reference self
410
- # and the configurations are unfrozen (using duplication).
334
+ # Deactivates self by doing the following in order:
411
335
  #
336
+ # * deactivates nested environments
337
+ # * removes load_paths from $LOAD_PATH
338
+ # * sets Env.instance to nil (if set to self)
339
+ # * clears cached manifest data
340
+ #
341
+ # Once deactivated, envs and load_paths are unfrozen and may be modified.
412
342
  # Returns true if deactivate succeeded, or false if self is not active.
413
343
  def deactivate
414
344
  return false unless active?
345
+ @active = false
346
+
347
+ # dectivate nested envs
348
+ envs.reverse_each do |env|
349
+ env.deactivate
350
+ end
415
351
 
416
352
  # remove load paths
417
353
  load_paths.each do |path|
418
354
  $LOAD_PATH.delete(path)
419
355
  end
420
-
421
- # unfreeze array configs by duplicating
422
- self.config.class_config.each_pair do |key, value|
423
- value = send(key)
424
- case value
425
- when Array then instance_variable_set("@#{key}", value.dup)
426
- end
427
- end
428
356
 
429
- @active = false
430
- @manifests.clear
431
- @@instance = nil if @@instance == self
357
+ # unfreeze envs and load paths
358
+ @envs = @envs.dup
359
+ @load_paths = @load_paths.dup
432
360
 
433
- # dectivate nested envs
434
- envs.reverse_each do |env|
435
- env.deactivate
436
- end
361
+ # clear cached data
362
+ @@instance = nil if @@instance == self
363
+ @manifests.clear
437
364
 
438
365
  true
439
366
  end
@@ -444,36 +371,30 @@ module Tap
444
371
  end
445
372
 
446
373
  # Searches each env for the first existing file or directory at
447
- # env.root.filepath(dir, path). Paths are expanded, and search_path
374
+ # env.root.filepath(dir, path). Paths are expanded, and search
448
375
  # checks to make sure the file is, in fact, relative to env.root[dir].
449
376
  # An optional block may be used to check the file; the file will only
450
377
  # be returned if the block returns true.
451
378
  #
452
379
  # Returns nil if no file can be found.
453
- def search_path(dir, path)
380
+ def search(dir, path, strict=true)
454
381
  each do |env|
455
382
  directory = env.root.filepath(dir)
456
383
  file = env.root.filepath(dir, path)
384
+ next unless File.exists?(file)
457
385
 
458
- # check the file is relative to the
459
- # directory, and that the file exists.
460
- if file.rindex(directory, 0) == 0 &&
461
- File.exists?(file) &&
462
- (!block_given? || yield(file))
463
- return file
386
+ # check the file is relative to directory
387
+ if strict && file.rindex(directory, 0) != 0
388
+ raise "not relative to search dir: #{file} (#{directory})"
464
389
  end
390
+
391
+ # filter
392
+ return file if !block_given? || yield(file)
465
393
  end
466
394
 
467
395
  nil
468
396
  end
469
397
 
470
- # def reset(name, &block)
471
- # each do |env|
472
- # env.manifests[name].each(&block)
473
- # env.manifests[name] = nil
474
- # end
475
- # end
476
-
477
398
  #
478
399
  TEMPLATES = {}
479
400
  TEMPLATES[:commands] = %Q{<% if count > 1 %>
@@ -542,7 +463,7 @@ module Tap
542
463
 
543
464
  attrs = {}
544
465
  templaters = []
545
- recursive_each(*args) do |env, *argv|
466
+ recursive_inject(args) do |argv, env|
546
467
  templater = Support::Templater.new(template, :env => env)
547
468
  next_args = block_given? ? yield(templater, attrs, *argv) : argv
548
469
  templaters << templater if next_args
@@ -564,46 +485,51 @@ module Tap
564
485
  env.root.root
565
486
  end
566
487
 
567
- # Raises an error if self is already active (and hence, configurations
568
- # should not be modified)
569
- def check_configurable
570
- raise "path configurations are disabled when active" if active?
571
- end
572
-
573
488
  # Resets envs using the current env_paths and gems.
574
489
  def reset_envs
575
490
  self.envs = env_paths.collect do |path|
576
- Env.instance_for(path)
491
+ Env.instantiate(path)
577
492
  end + gems.collect do |spec|
578
- Env.instance_for(spec.full_gem_path)
493
+ Env.instantiate(spec.full_gem_path)
579
494
  end
580
495
  end
581
496
 
582
- # Recursively iterates through envs collecting all envs into
583
- # the target. The result is a unique array of all nested
584
- # envs, in order, beginning with self.
585
- def flatten_envs(target=[])
586
- unless target.include?(self)
587
- target << self
497
+ # Arrayifies, compacts, and resolves input paths using root, and
498
+ # removes duplicates. In short
499
+ #
500
+ # resolve_paths ['lib', nil, 'lib', 'alt] # => [root['lib'], root['alt']]
501
+ #
502
+ def resolve_paths(paths) # :nodoc:
503
+ paths = YAML.load(paths) if paths.kind_of?(String)
504
+ [*paths].compact.collect {|path| root[path]}.uniq
505
+ end
506
+
507
+ # Recursively iterates through envs, starting with self, and
508
+ # collects the visited envs in order.
509
+ def visit_envs(visited=[], &block) # :nodoc:
510
+ unless visited.include?(self)
511
+ visited << self
512
+ yield(self) if block_given?
513
+
588
514
  envs.each do |env|
589
- env.flatten_envs(target)
515
+ env.visit_envs(visited, &block)
590
516
  end
591
517
  end
592
518
 
593
- target
519
+ visited
594
520
  end
595
521
 
596
- private
597
-
598
- def each_nested_env(env, visited, args, &block)
599
- return if visited.include?(env)
600
-
601
- visited << env
602
- next_args = yield(env, *args)
603
- next_args = [] if next_args == nil
604
- env.envs.each do |nested_env|
605
- each_nested_env(nested_env, visited, next_args, &block)
522
+ # helper to recursively inject a memo to the children of env
523
+ def inject_envs(memo, visited=[], &block) # :nodoc:
524
+ unless visited.include?(self)
525
+ visited << self
526
+ next_memo = yield(memo, self)
527
+ envs.each do |env|
528
+ env.inject_envs(next_memo, visited, &block)
529
+ end
606
530
  end
531
+
532
+ visited
607
533
  end
608
534
 
609
535
  # Raised when there is a Env-level configuration error.