tap 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,10 +17,10 @@ module Tap
17
17
  # === Unloading
18
18
  #
19
19
  # Constant also supports constant unloading. Unloading can be useful in
20
- # various development modes, but make cause code to behave unpredictably.
21
- # When a Constant unloads, the constant value is detached from the nesting
22
- # constant and the require path is removed from $". This allows a require
23
- # statement to re-require, and in theory, reload the constant.
20
+ # various development modes, but may cause code to behave unpredictably.
21
+ # When a Constant unloads, the constant value is removed from the nesting
22
+ # constant and the require paths are removed from $". This allows a
23
+ # require statement to re-require, and in theory, reload the constant.
24
24
  #
25
25
  # # [simple.rb]
26
26
  # # class Simple
@@ -39,6 +39,17 @@ module Tap
39
39
  # Unloading and reloading works best for scripts that have no side effects;
40
40
  # ie scripts that do not require other files and only define the specified
41
41
  # class or module.
42
+ #
43
+ #--
44
+ # ==== Rationale for that last statement
45
+ #
46
+ # Scripts that require other files will not re-require the other files
47
+ # because unload doesn't remove the other files from $". Likewise scripts
48
+ # that define other constants effectively overwrite the existing constant;
49
+ # that may or may not be a big deal, but it can cause warnings. Moreover,
50
+ # if a script actually DOES something (like create a file), that something
51
+ # will be repeated when it gets re-required.
52
+ #
42
53
  class Constant
43
54
  class << self
44
55
 
@@ -72,12 +83,40 @@ module Tap
72
83
  end
73
84
  const
74
85
  end
86
+
87
+ # Scans the directory and pattern for constants and adds them to the
88
+ # constants hash by name.
89
+ def scan(dir, pattern="**/*.rb", constants={})
90
+ if pattern.include?("..")
91
+ raise "patterns cannot include relative paths: #{pattern.inspect}"
92
+ end
93
+
94
+ # note changing dir here makes require paths relative to load_path,
95
+ # hence they can be directly converted into a default_const_name
96
+ # rather than first performing Root.relative_path
97
+ Dir.chdir(dir) do
98
+ Dir.glob(pattern).each do |path|
99
+ default_const_name = path.chomp(File.extname(path)).camelize
100
+
101
+ # scan for constants
102
+ Lazydoc::Document.scan(File.read(path)) do |const_name, type, summary|
103
+ const_name = default_const_name if const_name.empty?
104
+
105
+ constant = (constants[const_name] ||= Constant.new(const_name))
106
+ constant.register_as(type, summary)
107
+ constant.require_paths << path
108
+ end
109
+ end
110
+ end
111
+
112
+ constants
113
+ end
75
114
 
76
115
  private
77
116
 
78
117
  # helper method. Determines if the named constant is defined in const.
79
- # The implementation (annoyingly) has to be different for ruby 1.9 due
80
- # to changes in the API.
118
+ # The implementation has to be different for ruby 1.9 due to changes
119
+ # in the API.
81
120
  case RUBY_VERSION
82
121
  when /^1.9/
83
122
  def const_is_defined?(const, const_name) # :nodoc:
@@ -96,19 +135,20 @@ module Tap
96
135
  # The full constant name
97
136
  attr_reader :const_name
98
137
 
99
- # The path to load to initialize a missing constant
100
- attr_reader :require_path
101
-
102
- # An optional comment
103
- attr_accessor :comment
138
+ # An array of paths that will be required when the constantize is called
139
+ # and the constant does not exist. Require paths are required in order.
140
+ attr_reader :require_paths
141
+
142
+ # A hash of (type, summary) pairs used to classify self.
143
+ attr_reader :types
104
144
 
105
145
  # Initializes a new Constant with the specified constant name,
106
146
  # require_path, and comment. The const_name should be a valid
107
147
  # constant name.
108
- def initialize(const_name, require_path=nil, comment=nil)
148
+ def initialize(const_name, *require_paths)
109
149
  @const_name = const_name
110
- @require_path = require_path
111
- @comment = comment
150
+ @require_paths = require_paths
151
+ @types = {}
112
152
  end
113
153
 
114
154
  # Returns the underscored const_name.
@@ -158,35 +198,45 @@ module Tap
158
198
  def nesting_depth
159
199
  @nesting_depth ||= nesting.split(/::/).length
160
200
  end
161
-
162
- # Returns the Lazydoc document for require_path.
163
- def document
164
- require_path ? Lazydoc[require_path] : nil
165
- end
166
-
201
+
167
202
  # True if another is a Constant with the same const_name,
168
203
  # require_path, and comment as self.
169
204
  def ==(another)
170
205
  another.kind_of?(Constant) &&
171
206
  another.const_name == self.const_name &&
172
- another.require_path == self.require_path &&
173
- another.comment == self.comment
207
+ another.require_paths == self.require_paths
174
208
  end
175
209
 
210
+ # Registers the type and summary with self. Raises an error if self is
211
+ # already registerd as the type and override is false.
212
+ def register_as(type, summary=nil, override=false)
213
+ if types.include?(type) && !override
214
+ raise "already registered as a #{type.inspect}"
215
+ end
216
+
217
+ types[type] = summary
218
+ self
219
+ end
220
+
176
221
  # Looks up and returns the constant indicated by const_name. If the
177
- # constant cannot be found, constantize requires require_path and
178
- # tries again.
222
+ # constant cannot be found, constantize requires the require_paths
223
+ # in order and tries again.
179
224
  #
180
225
  # Raises a NameError if the constant cannot be found.
181
- def constantize
226
+ def constantize(autorequire=true)
182
227
  Constant.constantize(const_name) do
183
- require require_path if require_path
228
+ break unless autorequire
229
+
230
+ require_paths.each do |require_path|
231
+ require require_path
232
+ end
233
+
184
234
  Constant.constantize(const_name)
185
235
  end
186
236
  end
187
237
 
188
238
  # Undefines the constant indicated by const_name. The nesting constants
189
- # are not removed. If specified, require_path will be removed from $".
239
+ # are not removed. If specified, the require_paths will be removed from $".
190
240
  #
191
241
  # When removing require_path, unload will add '.rb' to the require_path if
192
242
  # require_path has no extension (this echos the behavior of require).
@@ -199,10 +249,10 @@ module Tap
199
249
  const = nesting.empty? ? Object : Constant.constantize(nesting) { Object }
200
250
 
201
251
  if const.const_defined?(name)
202
- if unrequire && require_path
252
+ require_paths.each do |require_path|
203
253
  path = File.extname(require_path).empty? ? "#{require_path}.rb" : require_path
204
254
  $".delete(path)
205
- end
255
+ end if unrequire
206
256
 
207
257
  return const.send(:remove_const, name)
208
258
  end
@@ -215,12 +265,12 @@ module Tap
215
265
  # "#<Tap::Env::Constant:object_id Const::Name (require_path)>"
216
266
  #
217
267
  def inspect
218
- "#<#{self.class}:#{object_id} #{const_name}#{@require_path == nil ? "" : " (#{@require_path})"}>"
268
+ "#<#{self.class}:#{object_id} #{const_name} #{require_paths.inspect}>"
219
269
  end
220
270
 
221
- # Returns the minikey for self, ie path. (see Tap::Env::Minimap)
222
- def minikey
223
- path
271
+ # Returns const_name
272
+ def to_s
273
+ const_name
224
274
  end
225
275
  end
226
276
  end
@@ -0,0 +1,61 @@
1
+ module Tap
2
+ class Env
3
+
4
+ # Context instances track information shared by a set of Env instances, for
5
+ # instance cached manifest data. Caching cross-env data in a shared space
6
+ # simplifies managment of this data, especially when dumping and loading it
7
+ # from a static file.
8
+ #
9
+ # Contexts also ensure that only one env in initialized to a given
10
+ # directory (at least among envs that share the same context). This
11
+ # prevents errors that arise when one env eventually nests itself.
12
+ class Context
13
+
14
+ # A hash of cached manifest data
15
+ attr_reader :cache
16
+
17
+ # The config file basename
18
+ attr_reader :basename
19
+
20
+ # An array of Env instances registered with self
21
+ attr_reader :instances
22
+
23
+ # Initializes a new Context. Options can specify a cache or a basename.
24
+ def initialize(options={})
25
+ options = {
26
+ :cache => {},
27
+ :basename => nil
28
+ }.merge(options)
29
+
30
+ @cache = options[:cache]
31
+ @basename = options[:basename]
32
+ @instances = []
33
+ end
34
+
35
+ # Registers env with self by adding env to instances. Raises an error
36
+ # if instances contains an env with the same root directory.
37
+ def register(env)
38
+ path = env.root.root
39
+
40
+ if instance(path)
41
+ raise "context already has an env for: #{path}"
42
+ end
43
+
44
+ instances << env
45
+ self
46
+ end
47
+
48
+ # Gets the instance for the directory currently in instances, or nil
49
+ # if such an instance does not exist.
50
+ def instance(dir)
51
+ instances.find {|env| env.root.root == dir }
52
+ end
53
+
54
+ # Returns the config filepath for the directory (ie basename under dir).
55
+ # If basename is nil, then config_file always return nil.
56
+ def config_file(dir)
57
+ basename ? File.join(dir, basename) : nil
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,89 +1,179 @@
1
1
  require 'tap/env/minimap'
2
- require 'tap/env/constant'
3
2
 
4
3
  module Tap
5
4
  class Env
6
5
 
6
+ # Manifests provide concise access to resources within a nested Env.
7
7
  class Manifest
8
+ class << self
9
+
10
+ # Interns a new Manifest using the block as the builder.
11
+ def intern(env, cache={}, &block)
12
+ new(env, block, cache)
13
+ end
14
+ end
15
+
8
16
  include Enumerable
9
- include Minimap
10
17
 
11
- # The environment this manifest summarizes
18
+ # Matches a compound registry search key. After the match, if the key is
19
+ # compound then:
20
+ #
21
+ # $1:: env_key
22
+ # $2:: key
23
+ #
24
+ # If the key is not compound, $2 is nil and $1 is the key.
25
+ COMPOUND_KEY = /^((?:[A-z]:(?:\/|\\))?.*?)(?::(.*))?$/
26
+
27
+ # The default summary template
28
+ DEFAULT_TEMPLATE = %q{<% if !minimap.empty? && count > 1 %>
29
+ <%= env_key %>:
30
+ <% end %>
31
+ <% minimap.each do |key, entry| %>
32
+ <%= key.ljust(width) %> # <%= entry %>
33
+ <% end %>
34
+ }
35
+
36
+ # The Env queried for manifest data
12
37
  attr_reader :env
13
38
 
14
- attr_reader :type
39
+ # An object that responds to call, typically a block, that recieves
40
+ # an env and returns an array of resources, each of which must be
41
+ # minimappable. Alternatively, the builder may return a Minimap.
42
+ attr_reader :builder
15
43
 
16
- # Initializes a new Manifest.
17
- def initialize(env, type)
18
- @env = env
19
- @type = type
20
- end
44
+ # A cache of (dir, [entries]) pairs mapping the root of an env
45
+ # to the array of resources associated with the env.
46
+ attr_reader :cache
21
47
 
22
- def entries
23
- env.registry[type]
48
+ def initialize(env, builder, cache={})
49
+ @env = env
50
+ @builder = builder
51
+ @cache = cache
52
+
53
+ cache.each_value do |value|
54
+ ensure_minimap(value)
55
+ end
24
56
  end
25
57
 
26
- # True if entries are empty.
27
- def empty?
28
- entries.empty?
58
+ # Builds the manifest for each env in env.
59
+ def build
60
+ self.env.each {|env| entries(env) }
61
+ self
29
62
  end
30
63
 
31
- def all_empty?
32
- env.all? do |current|
33
- current.manifest(type).empty?
64
+ # Returns the entries associated with env. If no entries are currently
65
+ # registered to env, the env is passed to the builder and the results
66
+ # stored in the cache.
67
+ def entries(env)
68
+ cache[env] ||= begin
69
+ ensure_minimap builder.call(env)
34
70
  end
35
71
  end
36
72
 
37
- # Iterates over each entry in self.
73
+ # Yields each entry for each env to the block.
38
74
  def each
39
- entries.each {|entry| yield(entry) }
75
+ self.env.each do |env|
76
+ entries(env).each do |entry|
77
+ yield(entry)
78
+ end
79
+ end
40
80
  end
41
-
42
- # Searches across env.each for the first entry minimatching key. A single
43
- # env can be specified by using a compound key like 'env_key:key'.
81
+
82
+ # Searches for the first entry mini-matching the key. A single env can
83
+ # be specified by using a compound key like 'env_key:key'.
84
+ #
85
+ # If a block is provided, each matching entry is yielded until the
86
+ # block returns true. Set env_also to true to return an array like
87
+ # [env, entry], where env is the env where the entry was found.
44
88
  #
45
- # Returns nil if no matching entry is found.
46
- def seek(key, value_only=true)
47
- env.seek(type, key, value_only)
89
+ # Returns nil if no matching entry is found.
90
+ def seek(key, env_also=false)
91
+ key =~ COMPOUND_KEY
92
+ envs = if $2
93
+ # compound key, match for env
94
+ key = $2
95
+ [env.minimatch($1)].compact
96
+ else
97
+ # not a compound key, search all envs by iterating env
98
+ env
99
+ end
100
+
101
+ # traverse envs looking for the first
102
+ # manifest entry matching key
103
+ envs.each do |env|
104
+ if entry = entries(env).minimatch(key)
105
+ next if block_given? && !yield(entry)
106
+ return env_also ? [env, entry] : entry
107
+ end
108
+ end
109
+
110
+ nil
48
111
  end
49
112
 
50
- def [](key)
51
- entry = seek(key)
52
- entry.kind_of?(Constant) ? entry.constantize : entry
113
+ # Unseek looks up the key identifying a specific entry. The entry is
114
+ # identified by the block, which receives each entry (in order) until
115
+ # the block returns true. Returns nil if no entry returns true.
116
+ #
117
+ # The env key will be prepended to the result if env_also is set to true.
118
+ def unseek(env_also=false)
119
+ self.env.each do |env|
120
+ objects = entries(env)
121
+ if value = objects.find {|entry| yield(entry) }
122
+ key = objects.minihash(true)[value]
123
+ return env_also ? "#{env.minihash(true)[env]}:#{key}" : key
124
+ end
125
+ end
126
+
127
+ nil
53
128
  end
54
129
 
55
- # Same as env.inspect but adds manifest to the templater
56
- def inspect(template=nil, globals={}, filename=nil)
57
- return super() unless template
130
+ # Generates a summary of the entries in self. Summarize uses the inspect
131
+ # functionality of Env to format the entries for each env in order; the
132
+ # results are concatenated.
133
+ #
134
+ # The template should be ERB; it will have the following local variables:
135
+ #
136
+ # env the current env being summarized
137
+ # minimap an array of [key, entry] pairs representing
138
+ # the minipaths and entries for the env
139
+ # width the maximum width of any key across all envs
140
+ # count the number of envs with at least one entry
141
+ #
142
+ # A block may be given to filter and pre-process minimap entries. Each
143
+ # (key, entry) pair will be yielded to the block; the block return
144
+ # replaces the entry and any pairs that return nil are removed.
145
+ #
146
+ def summarize(template=DEFAULT_TEMPLATE)
147
+ env.inspect(template, :width => 11, :count => 0) do |templater, globals|
148
+ width = globals[:width]
58
149
 
59
- env.inspect(template, globals, filename) do |templater, globalz|
60
- env = templater.env
61
- templater.manifest = env.manifest(type)
62
- yield(templater, globalz) if block_given?
63
- end
64
- end
150
+ minimap = entries(templater.env).minimap
65
151
 
66
- SUMMARY_TEMPLATE = %Q{<% if !entries.empty? && count > 1 %>
67
- <%= env_key %>:
68
- <% end %>
69
- <% entries.each do |key, entry| %>
70
- <%= key.ljust(width) %> # <%= entry.respond_to?(:comment) ? entry.comment : entry %>
71
- <% end %>
72
- }
152
+ if block_given?
153
+ minimap.collect! do |key, entry|
154
+ entry = yield(entry)
155
+ entry ? [key, entry] : nil
156
+ end
157
+ minimap.compact!
158
+ end
73
159
 
74
- def summarize(template=SUMMARY_TEMPLATE)
75
- inspect(template, :width => 11, :count => 0) do |templater, globals|
76
- width = globals[:width]
77
- templater.entries = templater.manifest.minimap.collect! do |key, entry|
160
+ minimap.each do |key, entry|
78
161
  width = key.length if width < key.length
79
- [key, entry]
80
162
  end
81
163
 
82
164
  globals[:width] = width
83
- globals[:count] += 1 unless templater.entries.empty?
165
+ globals[:count] += 1 unless minimap.empty?
166
+
167
+ templater.minimap = minimap
84
168
  end
85
169
  end
86
170
 
171
+ private
172
+
173
+ # helper to make obj into a minimap if necessary
174
+ def ensure_minimap(obj) # :nodoc:
175
+ obj.kind_of?(Minimap) ? obj : obj.extend(Minimap)
176
+ end
87
177
  end
88
178
  end
89
179
  end