tap 0.19.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/History +100 -45
  2. data/MIT-LICENSE +1 -1
  3. data/README +95 -51
  4. data/bin/tap +11 -57
  5. data/bin/tapexe +84 -0
  6. data/doc/API +91 -139
  7. data/doc/Configuration +93 -0
  8. data/doc/Examples/Command Line +10 -42
  9. data/doc/Examples/Tapfile +124 -0
  10. data/doc/Ruby to Ruby +87 -0
  11. data/doc/Workflow Syntax +185 -0
  12. data/lib/tap.rb +74 -5
  13. data/lib/tap/app.rb +217 -310
  14. data/lib/tap/app/api.rb +44 -23
  15. data/lib/tap/app/queue.rb +11 -12
  16. data/lib/tap/app/stack.rb +4 -4
  17. data/lib/tap/declarations.rb +200 -0
  18. data/lib/tap/declarations/context.rb +31 -0
  19. data/lib/tap/declarations/description.rb +33 -0
  20. data/lib/tap/env.rb +133 -779
  21. data/lib/tap/env/cache.rb +87 -0
  22. data/lib/tap/env/constant.rb +94 -39
  23. data/lib/tap/env/path.rb +71 -0
  24. data/lib/tap/join.rb +42 -78
  25. data/lib/tap/joins/gate.rb +85 -0
  26. data/lib/tap/joins/switch.rb +4 -2
  27. data/lib/tap/joins/sync.rb +3 -3
  28. data/lib/tap/middleware.rb +5 -5
  29. data/lib/tap/middlewares/debugger.rb +18 -58
  30. data/lib/tap/parser.rb +115 -183
  31. data/lib/tap/root.rb +162 -239
  32. data/lib/tap/signal.rb +72 -0
  33. data/lib/tap/signals.rb +20 -2
  34. data/lib/tap/signals/class_methods.rb +38 -43
  35. data/lib/tap/signals/configure.rb +19 -0
  36. data/lib/tap/signals/help.rb +5 -7
  37. data/lib/tap/signals/load.rb +49 -0
  38. data/lib/tap/signals/module_methods.rb +1 -0
  39. data/lib/tap/task.rb +46 -275
  40. data/lib/tap/tasks/dump.rb +21 -16
  41. data/lib/tap/tasks/list.rb +184 -0
  42. data/lib/tap/tasks/load.rb +4 -4
  43. data/lib/tap/tasks/prompt.rb +128 -0
  44. data/lib/tap/tasks/signal.rb +42 -0
  45. data/lib/tap/tasks/singleton.rb +35 -0
  46. data/lib/tap/tasks/stream.rb +64 -0
  47. data/lib/tap/utils.rb +83 -0
  48. data/lib/tap/version.rb +2 -2
  49. data/lib/tap/workflow.rb +124 -0
  50. data/tap.yml +0 -0
  51. metadata +59 -24
  52. data/cmd/console.rb +0 -43
  53. data/cmd/manifest.rb +0 -118
  54. data/cmd/run.rb +0 -145
  55. data/doc/Examples/Workflow +0 -40
  56. data/lib/tap/app/node.rb +0 -29
  57. data/lib/tap/env/context.rb +0 -61
  58. data/lib/tap/env/gems.rb +0 -63
  59. data/lib/tap/env/manifest.rb +0 -179
  60. data/lib/tap/env/minimap.rb +0 -308
  61. data/lib/tap/intern.rb +0 -50
  62. data/lib/tap/joins.rb +0 -9
  63. data/lib/tap/prompt.rb +0 -36
  64. data/lib/tap/root/utils.rb +0 -220
  65. data/lib/tap/root/versions.rb +0 -138
  66. data/lib/tap/signals/signal.rb +0 -68
@@ -1,850 +1,204 @@
1
- require 'tap/root'
1
+ require 'tap/signals'
2
+ require 'tap/env/cache'
2
3
  require 'tap/env/constant'
3
- require 'tap/env/context'
4
- require 'tap/env/manifest'
5
- require 'tap/templater'
6
- autoload(:YAML, 'yaml')
7
4
 
5
+ autoload(:YAML, 'yaml')
8
6
  module Tap
9
-
10
- # == Description
11
- #
12
- # Env provides access to an execution environment spanning many directories,
13
- # such as the working directory plus a series of gem directories. Envs merge
14
- # the files from each directory into an abstract directory that may be
15
- # globbed and accessed as a single unit. For example:
16
- #
17
- # # /one
18
- # # |-- a.rb
19
- # # `-- b.rb
20
- # #
21
- # # /two
22
- # # |-- b.rb
23
- # # `-- c.rb
24
- # env = Env.new('/one')
25
- # env << Env.new('/two')
26
- #
27
- # env.collect {|e| e.root.root}
28
- # # => ["/one", "/two"]
29
- #
30
- # env.glob(:root, "*.rb")
31
- # # => [
32
- # # "/one/a.rb",
33
- # # "/one/b.rb",
34
- # # "/two/c.rb"
35
- # # ]
36
- #
37
- # As illustrated, files in the nested environment are accessible within the
38
- # nesting environment. Envs provide methods for finding files associated
39
- # with a specific class, and allow the generation of manifests that provide
40
- # succinct access to various environment resources.
41
- #
42
- # Usage of Envs is fairly straightforward, but the internals and default
43
- # setup require some study as they have to span numerous functional domains.
44
- # The most common features are detailed below.
45
- #
46
- # ==== Class Paths
47
- #
48
- # Class paths are a kind of inheritance for files associated with a class.
49
- # Say we had the following classes:
50
- #
51
- # class A; end
52
- # class B < A; end
53
- #
54
- # The naturally associated directories are 'a' and 'b'. To look these up:
55
- #
56
- # env.class_path(:root, A) # => "/one/a"
57
- # env.class_path(:root, B) # => "/one/b"
58
- #
59
- # And to look up an associated file:
60
- #
61
- # env.class_path(:root, A, "index.html") # => "/one/a/index.html"
62
- # env.class_path(:root, B, "index.html") # => "/one/b/index.html"
63
- #
64
- # A block may be given to filter paths, for instance to test if a given file
65
- # exists. The class_path method will check each env then roll up the
66
- # inheritance hierarchy until the block returns true.
67
- #
68
- # FileUtils.touch("/two/a/index.html")
69
- #
70
- # visited_paths = []
71
- # env.class_path(:root, B, "index.html) do |path|
72
- # visited_paths << path
73
- # File.exists?(path)
74
- # end # => "/two/a/index.html"
75
- #
76
- # visited_paths
77
- # # => [
78
- # # "/one/b/index.html",
79
- # # "/two/b/index.html",
80
- # # "/one/a/index.html",
81
- # # "/two/a/index.html"
82
- # # ]
83
- #
84
- # This behavior is very useful for associating views with a class.
85
- #
86
- # ==== Manifest
87
- #
88
- # Envs can generate manifests of various resources so they may be identified
89
- # using minipaths (see Minimap for details regarding minipaths). Command
90
- # files used by the tap executable are one example of a resource, and the
91
- # constants used in building a workflow are another.
92
- #
93
- # Manifest are generated by defining a builder, typically a block, that
94
- # receives an env and returns an array of the associated resources.
95
- # Using the same env as above:
96
- #
97
- # manifest = env.manifest {|e| e.root.glob(:root, "*.rb") }
98
- #
99
- # manifest.seek("a") # => "/one/a.rb"
100
- # manifest.seek("b") # => "/one/b.rb"
101
- # manifest.seek("c") # => "/two/c.rb"
102
- #
103
- # As illustrated, seek finds the first entry across all envs that matches the
104
- # input minipath. A minipath for the env may be prepended to only search
105
- # within a specific env.
106
- #
107
- # manifest.seek("one:b") # => "/one/b.rb"
108
- # manifest.seek("two:b") # => "/two/b.rb"
109
- #
110
- # Env caches a manifest of constants identified by {constant attributes}[http://tap.rubyforge.org/lazydoc]
111
- # in files specified by under the Env.const_paths. These constants are used
112
- # when interpreting signals from the command line. Constants may be manually
113
- # registered to the constants manifest and classified by type like this:
114
- #
115
- # class CustomTask
116
- # def call; end
117
- # end
118
- # env.register(CustomTask).register_as(:task, "this is a custom task")
119
- #
120
- # const = env.constants.seek('custom_task')
121
- # const.const_name # => "CustomTask"
122
- # const.types # => {:task => "this is a custom task"}
123
- # const.constantize # => CustomTask
124
- #
125
- # == Setup
126
- #
127
- # Envs may be manually setup in code by individually generating instances
128
- # and nesting them. More commonly envs are defined in configuration files
129
- # and instantiated by specifying where the files are located. The default
130
- # config basename is 'tap.yml'; any env_paths specified in the config file
131
- # will be added.
132
- #
133
- # This type of instantiation is recursive:
134
- #
135
- # # [/one/tap.yml]
136
- # # env_paths: [/two]
137
- # #
138
- # # [/two/tap.yml]
139
- # # env_paths: [/three]
140
- # #
141
- #
142
- # env = Env.new("/one", :basename => 'tap.yml')
143
- # env.collect {|e| e.root.root}
144
- # # => ["/one", "/two", "/three"]
145
- #
146
- # Gem directories are fair game. Env allows specific gems to be specified
147
- # by name (via the 'gems' config), and if a gem has a tap.yml file then it
148
- # will be used to configure the gem env. Alternatively, an env may be set
149
- # to automatically discover and nest gem environments. In this case gems
150
- # are discovered when they have a tap.yml file.
151
- #
152
- # ==== ENV configs
153
- #
154
- # Configurations may be also specified as an ENV variables. This type of
155
- # configuration is very useful on the command line. Config variables
156
- # should be prefixed by TAP_ and named like the capitalized config key
157
- # (ex: TAP_GEMS or TAP_ENV_PATHS). See the
158
- # {Command Line Examples}[link:files/doc/Examples/Command%20Line.html]
159
- # to see ENV configs in action.
160
- #
161
- # These configurations may be accessed from Env#config, and are
162
- # automatically incorporated by Env#setup.
163
- #
164
7
  class Env
165
- autoload(:Gems, 'tap/env/gems')
166
-
167
8
  class << self
168
-
169
- # Returns the Env configs specified in ENV. Config variables are
170
- # prefixed by TAP_ and named like the capitalized config key
171
- # (ex: TAP_GEMS or TAP_ENV_PATHS).
172
- def config(env_vars=ENV)
173
- config = {}
174
- env_vars.each_pair do |key, value|
175
- if key =~ /\ATAP_(.*)\z/
176
- config[$1.downcase] = value
177
- end
178
- end
179
- config
180
- end
181
-
182
- # Initializes and activates an env as described in the config file under
183
- # dir. The config file should be a relative path and will be used for
184
- # determining configuration files under each env_path.
185
- #
186
- # The env configuration is determined by merging the following in order:
187
- # * defaults {root => dir, gems => all}
188
- # * ENV configs
189
- # * config_file configs
190
- #
191
- # The HOME directory for Tap will be added as an additonal environment
192
- # if not already added somewhere in the env hierarchy. By default all
193
- # gems will be included in the Env.
194
- def setup(dir=Dir.pwd, config_file=CONFIG_FILE)
195
- # setup configurations
196
- config = {'root' => dir, 'gems' => :all}
197
-
198
- user_config_file = config_file ? File.join(dir, config_file) : nil
199
- user = load_config(user_config_file)
200
-
201
- config.merge!(self.config)
202
- config.merge!(user)
203
-
204
- # keys must be symbolized as they are immediately
205
- # used to initialize the Env configs
206
- config = config.inject({}) do |options, (key, value)|
207
- options[key.to_sym || key] = value
208
- options
209
- end
9
+ def generate(options={})
10
+ options = {
11
+ :register => true,
12
+ :load_paths => true,
13
+ :set => true
14
+ }.merge(options)
210
15
 
211
- # instantiate
212
- context = Context.new(:basename => config_file)
213
- env = new(config, context)
16
+ dir = File.expand_path(options[:dir] || Dir.pwd)
17
+ pathfile = options[:pathfile] || File.expand_path(Path::FILE, dir)
18
+ map = options[:map] || Path.load(pathfile)
19
+ lib = options[:lib] || 'lib'
20
+ pattern = options[:pattern] || '**/*.rb'
214
21
 
215
- # add the tap env if necessary
216
- unless env.any? {|e| e.root.root == HOME }
217
- env.push new(HOME, context)
218
- end
22
+ register = options[:register]
23
+ load_paths = options[:load_paths]
24
+ set = options[:set]
219
25
 
220
- env.activate
221
- env
222
- end
223
-
224
- # Generates an Env for the specified gem or Gem::Specification. The
225
- # gemspec for the gem is used to determine the env configuration in
226
- # the following way:
227
- #
228
- # root: the gem path
229
- # gems: all gem dependencies with a config_file
230
- # const_paths: the gem require paths
231
- # set_const_paths: false (because RubyGems sets them for you)
232
- #
233
- # Configurations specified in the gem config_file override these
234
- # defaults.
235
- def setup_gem(gem_name, context=Context.new)
236
- spec = Gems.gemspec(gem_name)
237
- path = spec.full_gem_path
26
+ lines = []
27
+ lines << "register #{Path.escape(dir)}" if register
238
28
 
239
- # determine gem dependencies that have a config_file;
240
- # these will be set as the gems for the new Env
241
- dependencies = []
242
- spec.dependencies.each do |dependency|
243
- unless dependency.type == :runtime
244
- next
245
- end
246
-
247
- unless gemspec = Gems.gemspec(dependency)
248
- # this error may result when a dependency has
249
- # been uninstalled for a particular gem
250
- warn "missing gem dependency: #{dependency.to_s} (#{spec.full_name})"
251
- next
252
- end
29
+ path = Path.new(dir, map)
30
+ path[lib].each do |lib_dir|
31
+ lines << "loadpath #{Path.escape(lib_dir)}" if load_paths
253
32
 
254
- if config_file = context.config_file(gemspec.full_gem_path)
255
- next unless File.exists?(config_file)
256
- end
257
-
258
- dependencies << gemspec
259
- end
260
-
261
- config = {
262
- 'root' => path,
263
- 'gems' => dependencies,
264
- 'const_paths' => spec.require_paths,
265
- 'set_const_paths' => false
266
- }
267
-
268
- # override the default configs with whatever configs
269
- # are specified in the gem config file
270
- if config_file = context.config_file(path)
271
- config.merge!(load_config(config_file))
33
+ Constant.scan(lib_dir, pattern).each do |constant|
34
+ require_paths = Path.join(constant.require_paths)
35
+ types = constant.types.to_a.collect {|type| Path.escape(Path.join(type)) }
36
+ lines << "set #{constant.const_name} #{Path.escape require_paths} #{types.join(' ')}"
37
+ end if set
272
38
  end
273
39
 
274
- new(config, context)
275
- end
276
-
277
- # Loads configurations from path as YAML. Returns an empty hash if the path
278
- # loads to nil or false (as happens for empty files), or doesn't exist.
279
- #
280
- # Raises a ConfigError if the configurations do not load properly.
281
- def load_config(path)
282
- return {} unless path
283
-
284
- begin
285
- Root::Utils.trivial?(path) ? {} : (YAML.load_file(path) || {})
286
- rescue(Exception)
287
- raise ConfigError.new($!, path)
288
- end
40
+ lines.uniq!
41
+ lines.sort!
42
+ lines
289
43
  end
290
44
  end
291
45
 
292
- include Configurable
293
- include Enumerable
294
- include Minimap
295
-
296
- # The config file path
297
- CONFIG_FILE = "tap.yml"
46
+ include Signals
298
47
 
299
- # The home directory for Tap
300
- HOME = File.expand_path("#{File.dirname(__FILE__)}/../..")
301
-
302
- # An array of nested Envs, by default comprised of the env_path
303
- # + gem environments (in that order). Envs can be manually set
304
- # to override these defaults.
305
- attr_reader :envs
306
-
307
- # A Context tracking information shared among a set of envs.
308
- attr_reader :context
309
-
310
- # The Root directory structure for self.
311
- nest(:root, Root, :init => false)
312
-
313
- # Specify gems to add as nested Envs. Gems may be specified by name
314
- # and/or version, like 'gemname >= 1.2'; by default the latest version
315
- # of the gem is selected.
316
- #
317
- # Several special values also exist:
48
+ # Matches an inline type. After the match:
318
49
  #
319
- # :NONE, :none indicates no gems (same as nil, false)
320
- # :LATEST the latest version of all gems
321
- # :ALL all gems
322
- # :latest the latest version of all gems with a config file
323
- # :all all gems with a config file
50
+ # $1:: The prefix string (ex 'Const' for '::Const::type')
51
+ # $2:: The inline type (ex 'type' for '::Const::type')
324
52
  #
325
- # Gems are not activated by Env.
326
- config_attr :gems, [] do |input|
327
- input = yaml_load(input) if input.kind_of?(String)
328
-
329
- @gems = case input
330
- when false, nil, :NONE, :none
331
- []
332
- when :LATEST, :ALL
333
- # latest and all, no filter
334
- Gems.select_gems(input == :LATEST)
335
- when :latest, :all
336
- # latest and all, filtering by the existence of a
337
- # config file; all gems are selected if no config
338
- # file can be determined.
339
- Gems.select_gems(input == :latest) do |spec|
340
- config_file = context.config_file(spec.full_gem_path)
341
- config_file == nil || File.exists?(config_file)
342
- end
343
- else
344
- # resolve gem names manually
345
- [*input].collect do |name|
346
- Gems.gemspec(name)
347
- end.compact
348
- end
53
+ INLINE_TYPE = /(.*)::([a-z_]*)\z/
349
54
 
350
- reset_envs
351
- end
352
-
353
- # Specify directories to load as nested Envs. Configurations for the
354
- # env are loaded from the config file under dir, if it exists.
355
- config_attr :env_paths, [] do |input|
356
- @env_paths = resolve_paths(input)
357
- reset_envs
358
- end
55
+ attr_reader :paths
56
+ attr_reader :constants
359
57
 
360
- # Designates directories searched for constants. The const_paths are
361
- # added to $LOAD_PATH on activation if set_const_paths is specified.
362
- config_attr :const_paths, [:lib] do |input|
363
- raise "const_paths cannot be modified once active" if active?
364
- @const_paths = resolve_paths(input)
365
- end
58
+ signal_hash :auto, # auto-scan resources from a dir
59
+ :signature => [:dir, :pathfile, :lib, :pattern]
366
60
 
367
- # If set to true const_paths are added to $LOAD_PATH on activation.
368
- config_attr :set_const_paths, true do |input|
369
- raise "set_const_paths cannot be modified once active" if active?
370
- @set_const_paths = Configurable::Validation.boolean[input]
371
- end
61
+ signal :activate, :signature => [:name, :version]
372
62
 
373
- # Initializes a new Env linked to the specified directory. Configurations
374
- # for the env will be loaded from the config file (as determined by the
375
- # context) if it exists.
376
- #
377
- # A configuration hash may be manually provided in the place of dir. In
378
- # that case, no configurations will be loaded, even if the config file
379
- # exists.
380
- #
381
- # Context can be specified as a Context, or a Hash used to initialize a
382
- # Context.
383
- def initialize(config_or_dir=Dir.pwd, context={})
384
-
385
- # setup root
386
- config = nil
387
- @root = case config_or_dir
388
- when Root
389
- config_or_dir
390
- when String
391
- Root.new(config_or_dir)
392
- else
393
- config = config_or_dir
394
-
395
- if config.has_key?(:root) && config.has_key?('root')
396
- raise "multiple values mapped to :root"
397
- end
398
-
399
- root = config.delete(:root) || config.delete('root') || Dir.pwd
400
- root.kind_of?(Root) ? root : Root.new(root)
401
- end
402
-
403
- # note registration requires root.root, and so the
404
- # setup of context must follow the setup of root.
405
- @context = case context
406
- when Context
407
- context
408
- when Hash
409
- Context.new(context)
410
- else raise "cannot convert #{context.inspect} to Tap::Env::Context"
411
- end
412
- @context.register(self)
413
-
414
- # these need to be set for reset_env
415
- @active = false
416
- @gems = nil
417
- @env_paths = nil
418
-
419
- # only load configurations if configs were not provided
420
- config ||= Env.load_config(@context.config_file(@root.root))
421
- initialize_config(config)
422
-
423
- # set the invert flag
424
- @invert = false
425
- end
426
-
427
- # Sets envs removing duplicates and instances of self. Setting envs
428
- # overrides any environments specified by env_path and gem.
429
- def envs=(envs)
430
- raise "envs cannot be modified once active" if active?
431
- @envs = envs.uniq.delete_if {|env| env == self }
432
- end
433
-
434
- # Unshifts env onto envs. Self cannot be unshifted onto self.
435
- def unshift(env)
436
- unless env == self || envs[0] == env
437
- self.envs = envs.dup.unshift(env)
438
- end
439
- self
440
- end
441
-
442
- # Pushes env onto envs, removing duplicates.
443
- # Self cannot be pushed onto self.
444
- def push(env)
445
- unless env == self || envs[-1] == env
446
- envs = self.envs.reject {|e| e == env }
447
- self.envs = envs.push(env)
448
- end
449
- self
450
- end
451
- alias_method :<<, :push
452
-
453
- # Passes each nested env to the block in order, starting with self.
454
- def each
455
- visit_envs.each {|e| yield(e) }
456
- end
457
-
458
- # Passes each nested env to the block in reverse order, ending with self.
459
- def reverse_each
460
- visit_envs.reverse_each {|e| yield(e) }
461
- end
462
-
463
- # Recursively injects the memo to each env of self. Each env in envs
464
- # receives the same memo from the parent. This is different from the
465
- # inject provided via Enumerable, where each subsequent env receives
466
- # the memo from the previous, not the parent, env.
467
- #
468
- # a,b,c,d,e = ('a'..'e').collect {|name| Env.new(:name => name) }
469
- #
470
- # a.push(b).push(c)
471
- # b.push(d).push(e)
472
- #
473
- # lines = []
474
- # a.recursive_inject(0) do |nesting_depth, env|
475
- # lines << "\n#{'..' * nesting_depth}#{env.config[:name]} (#{nesting_depth})"
476
- # nesting_depth + 1
477
- # end
478
- #
479
- # lines.join
480
- # # => %Q{
481
- # # a (0)
482
- # # ..b (1)
483
- # # ....d (2)
484
- # # ....e (2)
485
- # # ..c (1)}
486
- #
487
- def recursive_inject(memo, &block) # :yields: memo, env
488
- inject_envs(memo, &block)
489
- end
490
-
491
- # Activates self by doing the following, in order:
492
- #
493
- # * activate nested environments
494
- # * unshift const_paths to $LOAD_PATH (if set_const_paths is true)
495
- #
496
- # Once active, the current envs and const_paths are frozen and cannot be
497
- # modified until deactivated. Returns true if activate succeeded, or
498
- # false if self is already active.
499
- def activate
500
- return false if active?
501
-
502
- @active = true
503
-
504
- # freeze envs and const paths
505
- @envs.freeze
506
- @const_paths.freeze
507
-
508
- # activate nested envs
509
- envs.reverse_each do |env|
510
- env.activate
511
- end
512
-
513
- # add const paths
514
- if set_const_paths
515
- const_paths.reverse_each do |path|
516
- $LOAD_PATH.unshift(path)
517
- end
518
-
519
- $LOAD_PATH.uniq!
520
- end
521
-
522
- true
523
- end
63
+ signal :register # add a resource path
64
+ signal :loadpath # add a load path
65
+ signal :set # add a constant
524
66
 
525
- # Deactivates self by doing the following in order:
526
- #
527
- # * deactivates nested environments
528
- # * removes const_paths from $LOAD_PATH (if set_const_paths is true)
529
- #
530
- # Once deactivated, envs and const_paths are unfrozen and may be modified.
531
- # Returns true if deactivate succeeded, or false if self is not active.
532
- #
533
- # ==== Note
534
- #
535
- # Deactivation does not necessarily leave $LOAD_PATH in the same condition
536
- # as before activation. A pre-existing $LOAD_PATH entry can go missing if
537
- # it is also registered as an env load_path (deactivation doesn't know to
538
- # leave such paths alone).
539
- #
540
- # Deactivation, like constant unloading should be done with caution.
541
- def deactivate
542
- return false unless active?
543
- @active = false
544
-
545
- # dectivate nested envs
546
- envs.reverse_each do |env|
547
- env.deactivate
548
- end
549
-
550
- # remove const paths
551
- const_paths.each do |path|
552
- $LOAD_PATH.delete(path)
553
- end if set_const_paths
554
-
555
- # unfreeze envs and const paths
556
- @envs = @envs.dup
557
- @const_paths = @const_paths.dup
558
-
559
- true
560
- end
67
+ signal :unregister # remove a resource path
68
+ signal :unloadpath # remove a load path
69
+ signal :unset # remove a constant
561
70
 
562
- # Return true if self has been activated.
563
- def active?
564
- @active
565
- end
71
+ define_signal :load, Load # load a tapenv file
72
+ define_signal :help, Help # signals help
566
73
 
567
- # Globs the abstract directory for files in the specified directory alias,
568
- # matching the pattern. The expanded path of each matching file is
569
- # returned.
570
- def glob(dir, pattern="**/*")
571
- hlob(dir, pattern).values.sort!
74
+ def initialize(options={})
75
+ @paths = options[:paths] || []
76
+ @paths.collect! {|path| path.kind_of?(Path) ? path : Path.new(*path) }
77
+ @constants = options[:constants] || []
78
+ @constants.collect! {|constant| constant.kind_of?(Constant) ? constant : Constant.new(constant) }
572
79
  end
573
80
 
574
- # Same as glob but returns results as a hash of (relative_path, path)
575
- # pairs. In short the hash defines matching files in the abstract
576
- # directory, linked to the actual path for these files.
577
- def hlob(dir, pattern="**/*")
578
- results = {}
579
- each do |env|
580
- root = env.root
581
- root.glob(dir, pattern).each do |path|
582
- relative_path = root.relative_path(dir, path)
583
- results[relative_path] ||= path
584
- end
81
+ def path(type)
82
+ result = []
83
+ paths.each do |path|
84
+ result.concat path[type]
585
85
  end
586
- results
86
+ result
587
87
  end
588
88
 
589
- # Returns the path to the specified file, as determined by root.
590
- #
591
- # If a block is given, a path for each env will be yielded until the block
592
- # returns a true value. Returns nil if the block never returns true.
593
- def path(dir = :root, *paths)
594
- each do |env|
595
- path = env.root.path(dir, *paths)
596
- return path if !block_given? || yield(path)
597
- end
598
- nil
89
+ def match(const_str, type=nil)
90
+ const_str = const_str.to_s
91
+ const_str =~ Constant::CONST_REGEXP ? constants_by_const_name($1) : constants_by_path(const_str, type)
599
92
  end
600
93
 
601
- # Retrieves a path associated with the inheritance hierarchy of an object.
602
- # An array of modules (which naturally can include classes) are provided
603
- # and module_path traverses each, forming paths like:
604
- #
605
- # path(dir, module_path, *paths)
606
- #
607
- # By default 'module_path' is 'module.to_s.underscore' but modules can
608
- # specify an alternative by providing a module_path method.
609
- #
610
- # Paths are yielded to the block until the block returns true, at which
611
- # point the current the path is returned. If no block is given, the
612
- # first path is returned. Returns nil if the block never returns true.
613
- def module_path(dir, modules, *paths, &block)
614
- paths.compact!
615
- while current = modules.shift
616
- module_path = if current.respond_to?(:module_path)
617
- current.module_path
618
- else
619
- current.to_s.underscore
620
- end
621
-
622
- if path = self.path(dir, module_path, *paths, &block)
623
- return path
624
- end
94
+ def resolve(const_str, type=nil)
95
+ matches = match(const_str, type)
96
+ case matches.length
97
+ when 0 then raise "unresolvable constant: #{const_str.inspect}"
98
+ when 1 then matches.at(0)
99
+ else raise "multiple matching constants: #{const_str.inspect} (#{matches.join(', ')})"
625
100
  end
626
-
627
- nil
628
- end
629
-
630
- # Returns the module_path traversing the inheritance hierarchy for the
631
- # class of obj (or obj if obj is a Class). Included modules are not
632
- # visited, only the superclasses.
633
- def class_path(dir, obj, *paths, &block)
634
- klass = obj.kind_of?(Class) ? obj : obj.class
635
- superclasses = klass.ancestors - klass.included_modules
636
- module_path(dir, superclasses, *paths, &block)
637
101
  end
638
102
 
639
- # Generates a Manifest for self using the block as a builder. The builder
640
- # receives an env and should return an array of resources, each of which
641
- # can be minimappped. Minimapping requires that the resource is either
642
- # a path string, or provides a 'path' method that returns a path string.
643
- # Alternatively, a Minimap may be returned.
644
- #
645
- # If a type is specified, then the manifest cache will be linked to the
646
- # context cache.
647
- def manifest(type=nil, &block) # :yields: env
648
- cache = type ? (context.cache[type] ||= {}) : {}
649
- Manifest.new(self, block, cache)
103
+ def constant(const_str, type=nil)
104
+ const_str.kind_of?(Module) ? const_str : resolve(const_str, type).constantize
650
105
  end
651
106
 
652
- # Returns a manifest of Constants located in .rb files under each of the
653
- # const_paths. Constants are identified using Lazydoc constant attributes;
654
- # all attributes are registered to the constants for classification
655
- # (for example as a task, join, etc).
656
- def constants
657
- @constants ||= manifest(:constants) do |env|
658
- constants = {}
659
-
660
- env.const_paths.each do |load_path|
661
- next unless File.directory?(load_path)
662
- Constant.scan(load_path, "**/*.rb", constants)
663
- end
664
-
665
- constants.keys.sort!.collect! do |key|
666
- constants[key]
667
- end
668
- end
107
+ # Registers the directory and path mappings as a Path, into paths. The
108
+ # path is unshifted to paths to provide similar functionality as loadpath.
109
+ # Returns the new path.
110
+ def register(dir, map={})
111
+ new_path = Path.new(dir, map)
112
+ paths.unshift new_path
113
+ new_path
669
114
  end
670
115
 
671
- # Seeks a constant for the key, constantizing if necessary. If invert is
672
- # true, this method seeks and returns a key for the input constant. In
673
- # both cases AGET returns nil if no key-constant pair can be found.
674
- def [](key, invert=invert?)
675
- if invert
676
- const_name = key.to_s
677
- constants.unseek(true) do |const|
678
- const_name == const.const_name
679
- end
680
- else
681
- if constant = constants.seek(key)
682
- constant.constantize
683
- else
684
- nil
685
- end
116
+ def auto(options, log=nil)
117
+ Env.generate(options).each do |line|
118
+ sig, *args = Utils.shellsplit(line)
119
+ signal(sig).call(args)
686
120
  end
121
+ self
687
122
  end
688
123
 
689
- # Indicates AGET looks up constants from keys (false), or keys from
690
- # constants (true).
691
- def invert?
692
- @invert
124
+ def activate(name, version)
125
+ Gem.activate(name, version)
693
126
  end
694
127
 
695
- # Inverts the AGET lookup for self.
696
- def invert!
697
- @invert = !@invert
128
+ def unregister(*dirs)
129
+ dirs.collect! {|dir| File.expand_path(dir) }
130
+ paths.delete_if {|path| dirs.include?(path.base) }
698
131
  self
699
132
  end
700
133
 
701
- # Returns a duplicate of self with inverted AGET lookup.
702
- def invert
703
- dup.invert!
134
+ # Expands and prepends the specified paths to $LOAD_PATH, removing any
135
+ # duplicates. Returns $LOAD_PATH.
136
+ def loadpath(*paths)
137
+ paths.reverse_each do |path|
138
+ $LOAD_PATH.unshift File.expand_path(path)
139
+ end
140
+
141
+ $LOAD_PATH.uniq!
142
+ $LOAD_PATH
704
143
  end
705
144
 
706
- # Scans the files matched under the directory and pattern for constants
707
- # and adds them to the existing constants for self.
708
- def scan(dir, pattern="**/*.rb")
709
- new_entries = {}
710
- entries = constants.entries(self)
711
- entries.each {|const| new_entries[const.const_name] = const }
712
-
713
- Constant.scan(root[dir], pattern, new_entries)
714
-
715
- entries.replace(new_entries.keys)
716
- entries.sort!.collect! {|key| new_entries[key] }
717
- entries
145
+ # Expands and removes the specified paths from $LOAD_PATH. Returns
146
+ # $LOAD_PATH.
147
+ def unloadpath(*paths)
148
+ paths.each {|path| $LOAD_PATH.delete File.expand_path(path) }
149
+ $LOAD_PATH
718
150
  end
719
151
 
720
- # Registers a constant with self. The constant is stored as a new
721
- # Constant in the constants manifest. Returns the new Constant.
722
- # If the constant is already registered, the existing Constant is
723
- # returned.
724
- def register(constant)
725
- const_name = constant.to_s
726
- entries = constants.entries(self)
152
+ def set(const_name, require_path=nil, *types)
153
+ if const_name =~ INLINE_TYPE
154
+ const_name = $1
155
+ types << $2
156
+ end
157
+
158
+ constant = constants.find {|c| c.const_name == const_name }
727
159
 
728
- # try to find the existing Constant before making a new constant
729
- unless constant = entries.find {|const| const.const_name == const_name}
160
+ unless constant
730
161
  constant = Constant.new(const_name)
731
- entries << constant
732
- entries.replace entries.sort_by {|const| const.const_name }
162
+ constants << constant
733
163
  end
734
164
 
735
- constant
736
- end
737
-
738
- # When no template is specified, inspect generates a fairly standard
739
- # inspection string. When a template is provided, inspect builds a
740
- # Templater for each env with the following local variables:
741
- #
742
- # variable value
743
- # env the current env
744
- # env_keys a minihash for all envs
745
- #
746
- # If a block is given, the globals and templater are yielded before
747
- # any templater is built; this allows each env to add env-specific
748
- # variables. After this preparation, each templater is built with
749
- # the globals and the results concatenated.
750
- #
751
- # The template is built with filename, if specified (for debugging).
752
- def inspect(template=nil, globals={}, filename=nil) # :yields: templater, globals
753
- if template == nil
754
- return "#<#{self.class}:#{object_id} root='#{root.root}'>"
165
+ require_paths = require_path ? Path.split(require_path, nil) : []
166
+ if require_paths.empty? && const_name.kind_of?(String)
167
+ require_paths << const_name.underscore
755
168
  end
756
169
 
757
- env_keys = minihash(true)
758
- collect do |env|
759
- templater = Templater.new(template, :env => env, :env_key => env_keys[env])
760
- yield(templater, globals) if block_given?
761
- templater
762
- end.collect! do |templater|
763
- templater.build(globals, filename)
764
- end.join
765
- end
766
-
767
- protected
768
-
769
- # helper for Minimap; note that specifying env.root.root via path
770
- # is not possible because path is required for other purposes.
771
- def entry_to_path(env) # :nodoc:
772
- env.root.root
773
- end
774
-
775
- # resets envs using the current env_paths and gems. does nothing
776
- # until both env_paths and gems are set.
777
- def reset_envs # :nodoc:
778
- if env_paths && gems
779
- self.envs = env_paths.collect do |path|
780
- context.instance(path) || Env.new(path, context)
781
- end + gems.collect do |spec|
782
- context.instance(spec.full_gem_path) || Env.setup_gem(spec, context)
783
- end
784
- end
785
- end
786
-
787
- # arrayifies, compacts, and resolves input paths using root.
788
- # also removes duplicates. in short:
789
- #
790
- # resolve_paths ['lib', nil, 'lib', 'alt] # => [root['lib'], root['alt']]
791
- #
792
- def resolve_paths(paths) # :nodoc:
793
- paths = yaml_load(paths) if paths.kind_of?(String)
794
- [*paths].compact.collect {|path| root[path] }.uniq
795
- end
796
-
797
- # helper to recursively iterate through envs, starting with self.
798
- # visited envs are collected in order and are used to ensure a
799
- # given env will only be visited once.
800
- def visit_envs(visited=[], &block) # :nodoc:
801
- unless visited.include?(self)
802
- visited << self
803
- yield(self) if block_given?
170
+ constant.require_paths.concat(require_paths).uniq!
171
+ types.each {|type| constant.register_as(*Path.split(type, nil)) }
804
172
 
805
- envs.each do |env|
806
- env.visit_envs(visited, &block)
807
- end
808
- end
809
-
810
- visited
173
+ constant
811
174
  end
812
-
813
- # helper to recursively inject a memo to the children of env
814
- def inject_envs(memo, visited=[], &block) # :nodoc:
815
- unless visited.include?(self)
816
- visited << self
817
- next_memo = yield(memo, self)
818
- envs.each do |env|
819
- env.inject_envs(next_memo, visited, &block)
175
+
176
+ def unset(*const_names)
177
+ const_names.each do |const_name|
178
+ constants.delete_if do |constant|
179
+ constant.const_name == const_name
820
180
  end
821
181
  end
822
-
823
- visited
182
+ self
824
183
  end
825
184
 
826
185
  private
827
186
 
828
- # A 'quick' yaml load where empty strings will not cause YAML to autoload.
829
- # This is a silly song and dance, but provides for optimal launch times.
830
- def yaml_load(str) # :nodoc:
831
- str.empty? ? false : YAML.load(str)
187
+ def constants_by_const_name(const_str) # :nodoc:
188
+ constants.select do |constant|
189
+ constant.const_name == const_str
190
+ end
832
191
  end
833
-
834
- # Raised when there is a configuration error from Env.load_config.
835
- class ConfigError < StandardError # :nodoc:
836
- attr_reader :original_error, :env_path
192
+
193
+ def constants_by_path(const_str, type) # :nodoc:
194
+ const_str, inline_type = const_str.split('::', 2)
195
+ type = inline_type if inline_type
837
196
 
838
- def initialize(original_error, env_path)
839
- @original_error = original_error
840
- @env_path = env_path
841
- super()
842
- end
197
+ head, tail = const_str.split(':', 2)
198
+ head, tail = nil, head unless tail
843
199
 
844
- def message
845
- "Configuration error: #{original_error.message}\n" +
846
- ($DEBUG ? "#{original_error.backtrace}\n" : "") +
847
- "Check '#{env_path}' configurations"
200
+ constants.select do |constant|
201
+ constant.type_match?(type) && constant.path_match?(head, tail)
848
202
  end
849
203
  end
850
204
  end