tap 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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