root 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|