root 0.0.1
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.
- data/History +3 -0
- data/MIT-LICENSE +22 -0
- data/README +14 -0
- data/lib/root.rb +268 -0
- data/lib/root/constant.rb +140 -0
- data/lib/root/constant_manifest.rb +126 -0
- data/lib/root/gems.rb +40 -0
- data/lib/root/intern.rb +36 -0
- data/lib/root/manifest.rb +168 -0
- data/lib/root/minimap.rb +88 -0
- data/lib/root/string_ext.rb +57 -0
- data/lib/root/utils.rb +391 -0
- data/lib/root/versions.rb +134 -0
- metadata +72 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'lazydoc'
|
2
|
+
require 'root/manifest'
|
3
|
+
require 'root/constant'
|
4
|
+
require 'root/utils'
|
5
|
+
|
6
|
+
class Root
|
7
|
+
|
8
|
+
# :startdoc:::-
|
9
|
+
#
|
10
|
+
# ConstantManifest builds a manifest of Constant entries using Lazydoc.
|
11
|
+
#
|
12
|
+
# Lazydoc can quickly scan files for constant attributes, and thereby
|
13
|
+
# identify constants based upon a flag like the '::manifest' attribute used
|
14
|
+
# to identify task classes. ConstantManifest registers paths that will be
|
15
|
+
# scanned for a specific resource, and lazily builds the references to load
|
16
|
+
# them as necessary.
|
17
|
+
#
|
18
|
+
# :startdoc:::+
|
19
|
+
class ConstantManifest < Manifest
|
20
|
+
|
21
|
+
# The attribute identifying constants in a file
|
22
|
+
attr_reader :const_attr
|
23
|
+
|
24
|
+
# An array of registered (root, [paths]) pairs
|
25
|
+
# that will be searched for const_attr
|
26
|
+
attr_reader :search_paths
|
27
|
+
|
28
|
+
# The current index of search_paths
|
29
|
+
attr_reader :search_path_index
|
30
|
+
|
31
|
+
# The current index of paths
|
32
|
+
attr_reader :path_index
|
33
|
+
|
34
|
+
# Initializes a new ConstantManifest that will identify constants
|
35
|
+
# using the specified constant attribute.
|
36
|
+
def initialize(const_attr)
|
37
|
+
@const_attr = const_attr
|
38
|
+
@search_paths = []
|
39
|
+
@search_path_index = 0
|
40
|
+
@path_index = 0
|
41
|
+
super([])
|
42
|
+
end
|
43
|
+
|
44
|
+
# Registers the files matching pattern under dir. Returns self.
|
45
|
+
def register(dir, pattern)
|
46
|
+
paths = Dir.glob(File.join(dir, pattern)).select {|file| File.file?(file) }
|
47
|
+
search_paths << [dir, paths.sort]
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Searches all paths for entries and adds them to self. Returns self.
|
52
|
+
def build
|
53
|
+
each {|entry| } unless built?
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# True if there are no more paths to search
|
58
|
+
# (ie search_path_index == search_paths.length)
|
59
|
+
def built?
|
60
|
+
search_path_index == search_paths.length
|
61
|
+
end
|
62
|
+
|
63
|
+
# Sets search_path_index and path_index to zero and clears entries.
|
64
|
+
# Returns self.
|
65
|
+
def reset
|
66
|
+
@search_paths.each {|path| Lazydoc[path].resolved = false }
|
67
|
+
@entries.clear
|
68
|
+
@search_path_index = 0
|
69
|
+
@path_index = 0
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
# Yields each Constant entry to the block. Unless built?, each
|
74
|
+
# lazily iterates over search_paths to look for new entries.
|
75
|
+
def each
|
76
|
+
entries.each do |entry|
|
77
|
+
yield(entry)
|
78
|
+
end
|
79
|
+
|
80
|
+
search_paths[search_path_index, search_paths.length - search_path_index].each do |(path_root, paths)|
|
81
|
+
paths[path_index, paths.length - path_index].each do |path|
|
82
|
+
new_entries = resolve(path_root, path)
|
83
|
+
entries.concat(new_entries)
|
84
|
+
|
85
|
+
@path_index += 1
|
86
|
+
new_entries.each {|entry| yield(entry) }
|
87
|
+
end
|
88
|
+
|
89
|
+
@search_path_index += 1
|
90
|
+
@path_index = 0
|
91
|
+
end unless built?
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def minikey(const) # :nodoc:
|
97
|
+
const.path
|
98
|
+
end
|
99
|
+
|
100
|
+
# Scans path for constants having const_attr, and initializes Constant
|
101
|
+
# objects for each. If the document has no default_const_name set,
|
102
|
+
# resolve will set the default_const_name based on the relative
|
103
|
+
# filepath from path_root to path.
|
104
|
+
def resolve(path_root, path) # :nodoc:
|
105
|
+
entries = []
|
106
|
+
document = nil
|
107
|
+
|
108
|
+
Lazydoc::Document.scan(File.read(path), const_attr) do |const_name, key, value|
|
109
|
+
if document == nil
|
110
|
+
relative_path = Utils.relative_filepath(path_root, path).chomp(File.extname(path))
|
111
|
+
document = Lazydoc.register_file(path, relative_path.camelize)
|
112
|
+
end
|
113
|
+
|
114
|
+
const_name = document.default_const_name if const_name.empty?
|
115
|
+
comment = Lazydoc::Subject.new(nil, document)
|
116
|
+
comment.value = value
|
117
|
+
|
118
|
+
document[const_name][key] = comment
|
119
|
+
entries << Constant.new(const_name, path)
|
120
|
+
end
|
121
|
+
|
122
|
+
entries
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
data/lib/root/gems.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
class Root
|
4
|
+
|
5
|
+
# Methods for working with {RubyGems}[http://www.rubygems.org/].
|
6
|
+
module Gems
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Returns the gemspec for the specified gem. A gem version
|
10
|
+
# can be specified in the name, like 'gem >= 1.2'. The gem
|
11
|
+
# will be activated using +gem+ if necessary.
|
12
|
+
def gemspec(gem_name)
|
13
|
+
return gem_name if gem_name.kind_of?(Gem::Specification)
|
14
|
+
|
15
|
+
# figure the version of the gem, by default >= 0.0.0
|
16
|
+
gem_name.to_s =~ /^([^<=>]*)(.*)$/
|
17
|
+
name, version = $1.strip, $2
|
18
|
+
version = ">= 0.0.0" if version.empty?
|
19
|
+
|
20
|
+
return nil if name.empty?
|
21
|
+
|
22
|
+
# load the gem and get the spec
|
23
|
+
gem(name, version)
|
24
|
+
Gem.loaded_specs[name]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Selects gem specs for which the block returns true. If
|
28
|
+
# latest is specified, only the latest version of each
|
29
|
+
# gem will be passed to the block.
|
30
|
+
def select_gems(latest=true)
|
31
|
+
index = latest ?
|
32
|
+
Gem.source_index.latest_specs :
|
33
|
+
Gem.source_index.gems.collect {|(name, spec)| spec }
|
34
|
+
|
35
|
+
index.select do |spec|
|
36
|
+
yield(spec)
|
37
|
+
end.sort
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/root/intern.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
class Root
|
2
|
+
# Generates an Intern module for the specified method_name.
|
3
|
+
#
|
4
|
+
# An Intern module:
|
5
|
+
#
|
6
|
+
# - adds an accessor for <method_name>_block
|
7
|
+
# - overrides <method_name> to call the block
|
8
|
+
#
|
9
|
+
def self.Intern(method_name)
|
10
|
+
mod = INTERN_MODULES[method_name.to_sym]
|
11
|
+
return mod unless mod == nil
|
12
|
+
|
13
|
+
mod = INTERN_MODULES[method_name.to_sym] = Module.new
|
14
|
+
mod.module_eval %Q{
|
15
|
+
attr_accessor :#{method_name}_block
|
16
|
+
|
17
|
+
def #{method_name}(*inputs)
|
18
|
+
raise "no #{method_name} block set" unless #{method_name}_block
|
19
|
+
inputs.unshift(self)
|
20
|
+
|
21
|
+
arity = #{method_name}_block.arity
|
22
|
+
n = inputs.length
|
23
|
+
unless n == arity || (arity < 0 && (-1-n) <= arity)
|
24
|
+
raise ArgumentError.new("wrong number of arguments (\#{n} for \#{arity})")
|
25
|
+
end
|
26
|
+
|
27
|
+
#{method_name}_block.call(*inputs)
|
28
|
+
end
|
29
|
+
}
|
30
|
+
mod
|
31
|
+
end
|
32
|
+
|
33
|
+
# An array of already-declared intern modules,
|
34
|
+
# keyed by method_name.
|
35
|
+
INTERN_MODULES = {}
|
36
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'root/minimap'
|
2
|
+
require 'root/intern'
|
3
|
+
|
4
|
+
class Root
|
5
|
+
|
6
|
+
# Stores an array of paths and makes them available for lookup by minipath.
|
7
|
+
# Manifests may be bound to a Tap::Env, allowing searches across a full
|
8
|
+
# environment (including nested environments).
|
9
|
+
#
|
10
|
+
# Manifest has a number of hooks used by subclasses like ConstantManifest
|
11
|
+
# to lazily add entries as needed.
|
12
|
+
class Manifest
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# Interns a new manifest, overriding the minikey method with the block
|
16
|
+
# (the minikey method converts entries to the path used during minimap
|
17
|
+
# and minimatch lookup, see Minimap).
|
18
|
+
def intern(*args, &block)
|
19
|
+
instance = new(*args)
|
20
|
+
if block_given?
|
21
|
+
instance.extend Intern(:minikey)
|
22
|
+
instance.minikey_block = block
|
23
|
+
end
|
24
|
+
instance
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
include Enumerable
|
29
|
+
include Minimap
|
30
|
+
|
31
|
+
# Matches a compound manifest search key. After the match, if the key is
|
32
|
+
# compound then:
|
33
|
+
#
|
34
|
+
# $1:: env_key
|
35
|
+
# $4:: key
|
36
|
+
#
|
37
|
+
# If the key is not compound, $4 is nil and $1 is the key.
|
38
|
+
SEARCH_REGEXP = /^(([A-z]:)?.*?)(:(.*))?$/
|
39
|
+
|
40
|
+
# An array entries in self.
|
41
|
+
attr_reader :entries
|
42
|
+
|
43
|
+
# The bound Env, or nil.
|
44
|
+
attr_reader :env
|
45
|
+
|
46
|
+
# The reader on Env accessing manifests of the same type as this value.
|
47
|
+
# Reader is set during bind.
|
48
|
+
attr_reader :reader
|
49
|
+
|
50
|
+
# Initializes a new, unbound Manifest.
|
51
|
+
def initialize(entries=[])
|
52
|
+
@entries = entries
|
53
|
+
@env = nil
|
54
|
+
@reader = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Binds self to an env and reader. The manifests returned by env.reader
|
58
|
+
# will be used during traversal methods like search. Raises an error if
|
59
|
+
# env does not respond to reader; returns self.
|
60
|
+
def bind(env, reader)
|
61
|
+
if env == nil
|
62
|
+
raise ArgumentError, "env may not be nil"
|
63
|
+
end
|
64
|
+
|
65
|
+
unless env.respond_to?(reader)
|
66
|
+
raise ArgumentError, "env does not respond to #{reader}"
|
67
|
+
end
|
68
|
+
|
69
|
+
@env = env
|
70
|
+
@reader = reader
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Unbinds self from env. Returns self.
|
75
|
+
def unbind
|
76
|
+
@env = nil
|
77
|
+
@reader = nil
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# True if the env and reader have been set.
|
82
|
+
def bound?
|
83
|
+
@env != nil && @reader != nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# A hook for dynamically building entries. By default build simply
|
87
|
+
# returns self
|
88
|
+
def build
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# A hook to flag when self is built. By default built? returns true.
|
93
|
+
def built?
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
# A hook to reset a build. By default reset simply returns self.
|
98
|
+
def reset
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# True if entries are empty.
|
103
|
+
def empty?
|
104
|
+
entries.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Iterates over each entry entry in self.
|
108
|
+
def each
|
109
|
+
entries.each {|entry| yield(entry) }
|
110
|
+
end
|
111
|
+
|
112
|
+
# Alias for Minimap#minimatch.
|
113
|
+
def [](key)
|
114
|
+
minimatch(key)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Search across env.each for the first entry minimatching key. A single
|
118
|
+
# env can be specified by using a compound key like 'env_key:key'.
|
119
|
+
# Returns nil if no matching entry is found.
|
120
|
+
#
|
121
|
+
# Search raises an error unless bound?
|
122
|
+
def search(key)
|
123
|
+
raise "cannot search unless bound" unless bound?
|
124
|
+
|
125
|
+
key =~ SEARCH_REGEXP
|
126
|
+
envs = if $4 != nil
|
127
|
+
# compound key, match for env
|
128
|
+
key = $4
|
129
|
+
[env.minimatch($1)].compact
|
130
|
+
else
|
131
|
+
# not a compound key, search all
|
132
|
+
# envs by iterating env itself
|
133
|
+
env
|
134
|
+
end
|
135
|
+
|
136
|
+
# traverse envs looking for the first
|
137
|
+
# manifest entry matching key
|
138
|
+
envs.each do |env|
|
139
|
+
if result = env.send(reader).minimatch(key)
|
140
|
+
return result
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
|
147
|
+
def inspect(traverse=true)
|
148
|
+
if traverse && bound?
|
149
|
+
lines = []
|
150
|
+
env.each do |env|
|
151
|
+
manifest = env.send(reader).build
|
152
|
+
next if manifest.empty?
|
153
|
+
|
154
|
+
lines << "== #{env.root.root}"
|
155
|
+
manifest.minimap.each do |mini, value|
|
156
|
+
lines << " #{mini}: #{value.inspect}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
return lines.join("\n")
|
160
|
+
end
|
161
|
+
|
162
|
+
lines = minimap.collect do |mini, value|
|
163
|
+
" #{mini}: #{value.inspect}"
|
164
|
+
end
|
165
|
+
"#{self.class}:#{object_id} (#{bound? ? env.root.root : ''})\n#{lines.join("\n")}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/root/minimap.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'root/utils'
|
2
|
+
|
3
|
+
class Root
|
4
|
+
|
5
|
+
# Minimap adds minimization and search methods to an array of paths (see
|
6
|
+
# Utils.minimize and Utils.minimal_match?).
|
7
|
+
#
|
8
|
+
# paths = %w{
|
9
|
+
# path/to/file-0.1.0.txt
|
10
|
+
# path/to/file-0.2.0.txt
|
11
|
+
# path/to/another_file.txt
|
12
|
+
# }
|
13
|
+
# paths.extend Root::Minimap
|
14
|
+
#
|
15
|
+
# paths.minimatch('file') # => 'path/to/file-0.1.0.txt'
|
16
|
+
# paths.minimatch('file-0.2.0') # => 'path/to/file-0.2.0.txt'
|
17
|
+
# paths.minimatch('another_file') # => 'path/to/another_file.txt'
|
18
|
+
#
|
19
|
+
# More generally, Minimap may extend any object responding to each.
|
20
|
+
# Override the minikey method to convert objects into paths.
|
21
|
+
#
|
22
|
+
# class ConstantMap < Array
|
23
|
+
# include Root::Minimap
|
24
|
+
#
|
25
|
+
# def minikey(const)
|
26
|
+
# const.underscore
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# constants = ConstantMap[Root::Minimap, Root]
|
31
|
+
# constants.minimatch('root') # => Root
|
32
|
+
# constants.minimatch('minimap') # => Root::Minimap
|
33
|
+
#
|
34
|
+
module Minimap
|
35
|
+
|
36
|
+
# Provides a minimized map of the entries using keys provided minikey.
|
37
|
+
#
|
38
|
+
# paths = %w{
|
39
|
+
# path/to/file-0.1.0.txt
|
40
|
+
# path/to/file-0.2.0.txt
|
41
|
+
# path/to/another_file.txt
|
42
|
+
# }.extend Root::Minimap
|
43
|
+
#
|
44
|
+
# paths.minimap
|
45
|
+
# # => [
|
46
|
+
# # ['file-0.1.0', 'path/to/file-0.1.0.txt'],
|
47
|
+
# # ['file-0.2.0', 'path/to/file-0.2.0.txt'],
|
48
|
+
# # ['another_file','path/to/another_file.txt']]
|
49
|
+
#
|
50
|
+
def minimap
|
51
|
+
hash = {}
|
52
|
+
map = []
|
53
|
+
each {|entry| map << (hash[minikey(entry)] = [entry]) }
|
54
|
+
Utils.minimize(hash.keys) do |key, mini_key|
|
55
|
+
hash[key].unshift mini_key
|
56
|
+
end
|
57
|
+
|
58
|
+
map
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the first entry whose minikey mini-matches the input, or nil if
|
62
|
+
# no such entry exists.
|
63
|
+
#
|
64
|
+
# paths = %w{
|
65
|
+
# path/to/file-0.1.0.txt
|
66
|
+
# path/to/file-0.2.0.txt
|
67
|
+
# path/to/another_file.txt
|
68
|
+
# }.extend Root::Minimap
|
69
|
+
#
|
70
|
+
# paths.minimatch('file-0.2.0') # => 'path/to/file-0.2.0.txt'
|
71
|
+
# paths.minimatch('file-0.3.0') # => nil
|
72
|
+
#
|
73
|
+
def minimatch(key)
|
74
|
+
each do |entry|
|
75
|
+
return entry if Utils.minimal_match?(minikey(entry), key)
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
# A hook to convert a non-path entry into a path for minimap and
|
83
|
+
# minimatch. Returns the entry by default.
|
84
|
+
def minikey(entry)
|
85
|
+
entry
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|