library 0.1.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.
- data/.ruby +57 -0
- data/.yardopts +6 -0
- data/COPYING.rdoc +30 -0
- data/HISTORY.rdoc +13 -0
- data/README.md +177 -0
- data/lib/library.rb +532 -0
- data/lib/library/autoload.rb +14 -0
- data/lib/library/core_ext.rb +34 -0
- data/lib/library/domain.rb +244 -0
- data/lib/library/errors.rb +49 -0
- data/lib/library/feature.rb +207 -0
- data/lib/library/kernel.rb +81 -0
- data/lib/library/ledger.rb +642 -0
- data/lib/library/metadata.rb +398 -0
- data/lib/library/rbconfig.rb +65 -0
- data/lib/library/rubygems.rb +19 -0
- data/lib/library/rubylib.rb +206 -0
- data/lib/library/version.rb +387 -0
- data/lib/ubylibs.rb +8 -0
- data/qed/01_loading.rdoc +14 -0
- data/qed/02_library.rdoc +55 -0
- data/qed/03_ledger.rdoc +30 -0
- data/qed/04_version.rdoc +101 -0
- data/qed/applique/check.rb +11 -0
- data/qed/applique/project.rb +34 -0
- data/qed/fixtures/foo/.ruby +5 -0
- data/qed/fixtures/foo/lib/foo.rb +1 -0
- data/qed/fixtures/tryme/1.0/.ruby +6 -0
- data/qed/fixtures/tryme/1.0/lib/tryme.rb +1 -0
- data/qed/fixtures/tryme/1.1/.ruby +7 -0
- data/qed/fixtures/tryme/1.1/lib/tryme.rb +1 -0
- data/qed/helpers/new_project.rdoc +29 -0
- data/qed/helpers/projects.rdoc +56 -0
- data/test/case_ledger.rb +23 -0
- data/test/case_library.rb +13 -0
- data/test/case_version.rb +89 -0
- data/test/fixture/.ruby +56 -0
- data/test/fixture/lib/foo.rb +7 -0
- data/test/helper.rb +2 -0
- metadata +144 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'library' # this must be loaded in first
|
2
|
+
|
3
|
+
$RUBY_IGNORE_CALLERS ||= []
|
4
|
+
$RUBY_IGNORE_CALLERS << /#{__FILE__}/ # TODO: should this be more general, e.g. File.dirname(__FILE__) ?
|
5
|
+
|
6
|
+
module ::Kernel
|
7
|
+
|
8
|
+
class << self
|
9
|
+
alias __require__ require
|
10
|
+
alias __load__ load
|
11
|
+
end
|
12
|
+
|
13
|
+
alias __require__ require
|
14
|
+
alias __load__ load
|
15
|
+
|
16
|
+
#
|
17
|
+
# Acquire feature - This is Roll's modern require/load method.
|
18
|
+
# It differs from the usual `#require` or `#load` primarily by
|
19
|
+
# the fact that it will search the current loading library,
|
20
|
+
# i.e. the one belonging to the feature on the top of the
|
21
|
+
# #LOAD_STACK, before looking elsewhere. The reason we can't
|
22
|
+
# adjust `#require` to do this is becuase it could load a local
|
23
|
+
# feature when a non-local feature was intended. For example, if
|
24
|
+
# a library contained 'fileutils.rb' then this would be loaded
|
25
|
+
# rather the Ruby's standard library. When using `#acquire`,
|
26
|
+
# one would have to use the `ruby/` prefix to ensure the Ruby
|
27
|
+
# library gets loaded.
|
28
|
+
#
|
29
|
+
# @param pathname [String]
|
30
|
+
# The pathname of the feature to acquire.
|
31
|
+
#
|
32
|
+
# @param options [Hash]
|
33
|
+
# Load options are `:wrap`, `:load`, `:legacy` and `:search`.
|
34
|
+
#
|
35
|
+
# @return [true, false]
|
36
|
+
# Was the feature newly required or successfully loaded, depending
|
37
|
+
# on the `:load` option settings.
|
38
|
+
#
|
39
|
+
def acquire(pathname, options={}) #, &block)
|
40
|
+
Library.acquire(pathname, options) #, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
module_function :acquire
|
44
|
+
|
45
|
+
#
|
46
|
+
# Require feature - This is the same as acquire except that the
|
47
|
+
# `:legacy` option is fixed as `true`.
|
48
|
+
#
|
49
|
+
# @param pathname [String]
|
50
|
+
# The pathname of the feature to require.
|
51
|
+
#
|
52
|
+
# @param options [Hash]
|
53
|
+
# Load options can be `:wrap`, `:load` and `:search`.
|
54
|
+
#
|
55
|
+
# @return [true,false] if feature was newly required
|
56
|
+
#
|
57
|
+
def require(pathname, options={}) #, &block)
|
58
|
+
Library.require(pathname, options) #, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
module_function :require
|
62
|
+
|
63
|
+
#
|
64
|
+
# Load feature - This is the same as acquire except that the
|
65
|
+
# `:legacy` and `:load` options are fixed as `true`.
|
66
|
+
#
|
67
|
+
# @param pathname [String]
|
68
|
+
# The pathname of the feature to load.
|
69
|
+
#
|
70
|
+
# @param options [Hash]
|
71
|
+
# Load options can be :wrap and :search.
|
72
|
+
#
|
73
|
+
# @return [true, false] if feature was successfully loaded
|
74
|
+
#
|
75
|
+
def load(pathname, options={}) #, &block)
|
76
|
+
Library.load(pathname, options) #, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
module_function :load
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,642 @@
|
|
1
|
+
class Library
|
2
|
+
|
3
|
+
# Ledger class track available libraries by library name.
|
4
|
+
# It is essentially a hash object, but with a special way
|
5
|
+
# of storing them to track versions. Each have key is the
|
6
|
+
# name of a library, as a String, and each value is either
|
7
|
+
# a Library object, if that particular version is active,
|
8
|
+
# or an Array of available versions of the library if inactive.
|
9
|
+
#
|
10
|
+
class Ledger
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
#
|
15
|
+
# State of monitoring setting. This is used for debugging.
|
16
|
+
#
|
17
|
+
def monitor?
|
18
|
+
ENV['monitor'] || $MONITOR
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
def initialize
|
23
|
+
@table = Hash.new(){ |h,k| h[k] = [] }
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Add a library to the ledger.
|
28
|
+
#
|
29
|
+
# @param [String,Library]
|
30
|
+
# A path to a ruby library or a Library object.
|
31
|
+
#
|
32
|
+
# @return [Library] Added library object.
|
33
|
+
#
|
34
|
+
def add(lib)
|
35
|
+
case lib
|
36
|
+
when Library
|
37
|
+
add_library(lib)
|
38
|
+
else
|
39
|
+
add_location(lib)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :<<, :add
|
44
|
+
|
45
|
+
#
|
46
|
+
# Add library to ledger given a location.
|
47
|
+
#
|
48
|
+
# @return [Library] Added library object.
|
49
|
+
#
|
50
|
+
def add_location(location)
|
51
|
+
begin
|
52
|
+
library = Library.new(location)
|
53
|
+
|
54
|
+
entry = @table[library.name]
|
55
|
+
|
56
|
+
if Array === entry
|
57
|
+
entry << library unless entry.include?(library)
|
58
|
+
else
|
59
|
+
# todo: what to do here?
|
60
|
+
end
|
61
|
+
rescue Exception => error
|
62
|
+
warn error.message if ENV['debug']
|
63
|
+
end
|
64
|
+
|
65
|
+
library
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Add library to ledger given a Library object.
|
70
|
+
#
|
71
|
+
# @return [Library] Added library object.
|
72
|
+
#
|
73
|
+
def add_library(library)
|
74
|
+
#begin
|
75
|
+
raise TypeError unless Library === library
|
76
|
+
|
77
|
+
entry = @table[library.name]
|
78
|
+
|
79
|
+
if Array === entry
|
80
|
+
entry << library unless entry.include?(library)
|
81
|
+
end
|
82
|
+
#rescue Exception => error
|
83
|
+
# warn error.message if ENV['debug']
|
84
|
+
#end
|
85
|
+
|
86
|
+
library
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Get library or library version set by name.
|
91
|
+
#
|
92
|
+
# @param [String] name
|
93
|
+
# Name of library.
|
94
|
+
#
|
95
|
+
# @return [Library,Array] Library or lihbrary set referenced by name.
|
96
|
+
#
|
97
|
+
def [](name)
|
98
|
+
@table[name.to_s]
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Set ledger entry.
|
103
|
+
#
|
104
|
+
# @param [String] Name of library.
|
105
|
+
#
|
106
|
+
# @raise [TypeError] If library is not a Library object.
|
107
|
+
#
|
108
|
+
def []=(name, library)
|
109
|
+
raise TypeError unless Library === library
|
110
|
+
|
111
|
+
@table[name.to_s] = library
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
#
|
116
|
+
#
|
117
|
+
def replace(table)
|
118
|
+
initialize
|
119
|
+
table.each do |name, value|
|
120
|
+
@table[name.to_s] = value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Iterate over each ledger entry.
|
126
|
+
#
|
127
|
+
def each(&block)
|
128
|
+
@table.each(&block)
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Size of the ledger is the number of libraries available.
|
133
|
+
#
|
134
|
+
# @return [Fixnum] Size of the ledger.
|
135
|
+
#
|
136
|
+
def size
|
137
|
+
@table.size
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Checks ledger for presents of library by name.
|
142
|
+
#
|
143
|
+
# @return [Boolean]
|
144
|
+
#
|
145
|
+
def key?(name)
|
146
|
+
@table.key?(name.to_s)
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Get a list of names of all libraries in the ledger.
|
151
|
+
#
|
152
|
+
# @return [Array<String>] list of library names
|
153
|
+
#
|
154
|
+
def keys
|
155
|
+
@table.keys
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Get a list of libraries and library version sets in the ledger.
|
160
|
+
#
|
161
|
+
# @return [Array<Library,Array>]
|
162
|
+
# List of libraries and library version sets.
|
163
|
+
#
|
164
|
+
def values
|
165
|
+
@table.values
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Inspection string.
|
170
|
+
#
|
171
|
+
# @return [String] Inspection string.
|
172
|
+
#
|
173
|
+
def inspect
|
174
|
+
@table.inspect
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
# Limit versions of a library to the given constraint.
|
179
|
+
# Unlike `#activate` this does not reduce the possible versions
|
180
|
+
# to a single library, but only reduces the number of possibilites.
|
181
|
+
#
|
182
|
+
# @param [String] name
|
183
|
+
# Name of library.
|
184
|
+
#
|
185
|
+
# @param [String] constraint
|
186
|
+
# Valid version constraint.
|
187
|
+
#
|
188
|
+
# @return [Array] List of conforming versions.
|
189
|
+
#
|
190
|
+
def constrain(name, contraint)
|
191
|
+
libraries = self[name]
|
192
|
+
|
193
|
+
return nil unless Array === libraries
|
194
|
+
|
195
|
+
vers = libraries.select do |library|
|
196
|
+
library.version.satisfy?(constraint)
|
197
|
+
end
|
198
|
+
|
199
|
+
self[name] = vers
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
# Activate a library, retrieving a Library instance by name, or name
|
204
|
+
# and version, and ensuring only that instance will be returned for
|
205
|
+
# all subsequent requests. Libraries are singleton, so once activated
|
206
|
+
# the same object is always returned.
|
207
|
+
#
|
208
|
+
# This method will raise a LoadError if the name is not found.
|
209
|
+
#
|
210
|
+
# Note that activating all runtime requirements of a library being
|
211
|
+
# activated was considered, but decided against. There's no reason
|
212
|
+
# to activatea library until it is actually needed. However this is
|
213
|
+
# not so when testing, or verifying available requirements, so other
|
214
|
+
# methods are provided such as `#activate_requirements`.
|
215
|
+
#
|
216
|
+
# @param [String] name
|
217
|
+
# Name of library.
|
218
|
+
#
|
219
|
+
# @param [String] constraint
|
220
|
+
# Valid version constraint.
|
221
|
+
#
|
222
|
+
# @return [Library]
|
223
|
+
# The activated Library object.
|
224
|
+
#
|
225
|
+
# @todo Should we also check $"? Eg. `return false if $".include?(path)`.
|
226
|
+
#
|
227
|
+
def activate(name, constraint=nil)
|
228
|
+
raise LoadError, "no such library -- #{name}" unless key?(name)
|
229
|
+
|
230
|
+
library = self[name]
|
231
|
+
|
232
|
+
if Library === library
|
233
|
+
if constraint
|
234
|
+
unless library.version.satisfy?(constraint)
|
235
|
+
raise Library::VersionConflict, library
|
236
|
+
end
|
237
|
+
end
|
238
|
+
else # library is an array of versions
|
239
|
+
if constraint
|
240
|
+
verscon = Version::Constraint.parse(constraint)
|
241
|
+
library = library.select{ |lib| verscon.compare(lib.version) }.max
|
242
|
+
else
|
243
|
+
library = library.max
|
244
|
+
end
|
245
|
+
unless library
|
246
|
+
raise VersionError, "no library version -- #{name} #{constraint}"
|
247
|
+
end
|
248
|
+
|
249
|
+
self[name] = library #constrain(library)
|
250
|
+
end
|
251
|
+
|
252
|
+
library
|
253
|
+
end
|
254
|
+
|
255
|
+
#
|
256
|
+
# Find matching library features. This is the "mac daddy" method used by
|
257
|
+
# the #require and #load methods to find the specified +path+ among
|
258
|
+
# the various libraries and their load paths.
|
259
|
+
#
|
260
|
+
def find_feature(path, options={})
|
261
|
+
path = path.to_s
|
262
|
+
|
263
|
+
#suffix = options[:suffix]
|
264
|
+
search = options[:search]
|
265
|
+
local = options[:local]
|
266
|
+
from = options[:from]
|
267
|
+
|
268
|
+
$stderr.print path if monitor? # debugging
|
269
|
+
|
270
|
+
# absolute, home or current path
|
271
|
+
#
|
272
|
+
# NOTE: Ideally we would try to find a matching path among avaliable libraries
|
273
|
+
# so that the library can be activated, however this would probably add a
|
274
|
+
# too much overhead and will by mostly a YAGNI, so we forgo any such
|
275
|
+
# functionality, at least for now.
|
276
|
+
case path[0,1]
|
277
|
+
when '/', '~', '.'
|
278
|
+
$stderr.puts " (absolute)" if monitor? # debugging
|
279
|
+
return nil
|
280
|
+
end
|
281
|
+
|
282
|
+
# from explicit library
|
283
|
+
if from
|
284
|
+
lib = library(from)
|
285
|
+
ftr = lib.find(path, options)
|
286
|
+
raise LoadError, "no such file to load -- #{path}" unless file
|
287
|
+
$stderr.puts " (direct)" if monitor? # debugging
|
288
|
+
return ftr
|
289
|
+
end
|
290
|
+
|
291
|
+
# check the load stack (TODO: just last or all?)
|
292
|
+
if local
|
293
|
+
if last = $LOAD_STACK.last
|
294
|
+
#$LOAD_STACK.reverse_each do |feature|
|
295
|
+
lib = last.library
|
296
|
+
if ftr = lib.find(path, options)
|
297
|
+
unless $LOAD_STACK.include?(ftr) # prevent recursive loading
|
298
|
+
$stderr.puts " (2 stack)" if monitor? # debugging
|
299
|
+
return ftr
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
name, fname = ::File.split_root(path)
|
306
|
+
|
307
|
+
# if the head of the path is the library
|
308
|
+
if fname
|
309
|
+
lib = Library[name]
|
310
|
+
if lib && ftr = lib.find(path, options) || lib.find(fname, options)
|
311
|
+
$stderr.puts " (3 indirect)" if monitor? # debugging
|
312
|
+
return ftr
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# plain library name?
|
317
|
+
if !fname && lib = Library.instance(path)
|
318
|
+
if ftr = lib.default # default feature to load
|
319
|
+
$stderr.puts " (5 plain library name)" if monitor? # debugging
|
320
|
+
return ftr
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# fallback to brute force search
|
325
|
+
#if search #or legacy
|
326
|
+
#options[:legacy] = true
|
327
|
+
if ftr = find_any(path, options)
|
328
|
+
$stderr.puts " (6 brute search)" if monitor? # debugging
|
329
|
+
return ftr
|
330
|
+
end
|
331
|
+
#end
|
332
|
+
|
333
|
+
$stderr.puts " (7 fallback)" if monitor? # debugging
|
334
|
+
|
335
|
+
nil
|
336
|
+
end
|
337
|
+
|
338
|
+
#
|
339
|
+
# Brute force variation of `#find` looks through all libraries for a
|
340
|
+
# matching features. This serves as the fallback method if `#find` comes
|
341
|
+
# up empty.
|
342
|
+
#
|
343
|
+
# @param [String] path
|
344
|
+
# path name for which to search
|
345
|
+
#
|
346
|
+
# @param [Hash] options
|
347
|
+
# Search options.
|
348
|
+
#
|
349
|
+
# @option options [Boolean] :latest
|
350
|
+
# Search only the active or most current version of any library.
|
351
|
+
#
|
352
|
+
# @option options [Boolean] :suffix
|
353
|
+
# Automatically try standard extensions if pathname has none.
|
354
|
+
#
|
355
|
+
# @option options [Boolean] :legacy
|
356
|
+
# Do not match within library's +name+ directory, eg. `lib/foo/*`.
|
357
|
+
#
|
358
|
+
# @return [Feature,Array] Matching feature(s).
|
359
|
+
#
|
360
|
+
def find_any(path, options={})
|
361
|
+
options = options.merge(:main=>true)
|
362
|
+
|
363
|
+
latest = options[:latest]
|
364
|
+
|
365
|
+
# TODO: Perhaps the selected and unselected should be kept in separate lists?
|
366
|
+
unselected, selected = *partition{ |name, libs| Array === libs }
|
367
|
+
|
368
|
+
# broad search of pre-selected libraries
|
369
|
+
selected.each do |(name, lib)|
|
370
|
+
if ftr = lib.find(path, options)
|
371
|
+
next if Library.load_stack.last == ftr
|
372
|
+
return ftr
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# finally a broad search on unselected libraries
|
377
|
+
unselected.each do |(name, libs)|
|
378
|
+
libs = libs.sort
|
379
|
+
libs = [libs.first] if latest
|
380
|
+
libs.each do |lib|
|
381
|
+
ftr = lib.find(path, options)
|
382
|
+
return ftr if ftr
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
nil
|
387
|
+
end
|
388
|
+
|
389
|
+
#
|
390
|
+
# Brute force search looks through all libraries for matching features.
|
391
|
+
# This is the same as #find_any, but returns a list of matches rather
|
392
|
+
# then the first matching feature found.
|
393
|
+
#
|
394
|
+
# @param [String] path
|
395
|
+
# path name for which to search
|
396
|
+
#
|
397
|
+
# @param [Hash] options
|
398
|
+
# Search options.
|
399
|
+
#
|
400
|
+
# @option options [Boolean] :latest
|
401
|
+
# Search only the active or most current version of any library.
|
402
|
+
#
|
403
|
+
# @option options [Boolean] :suffix
|
404
|
+
# Automatically try standard extensions if pathname has none.
|
405
|
+
#
|
406
|
+
# @option options [Boolean] :legacy
|
407
|
+
# Do not match within library's +name+ directory, eg. `lib/foo/*`.
|
408
|
+
#
|
409
|
+
# @return [Feature,Array] Matching feature(s).
|
410
|
+
#
|
411
|
+
def search(path, options={})
|
412
|
+
options = options.merge(:main=>true)
|
413
|
+
|
414
|
+
latest = options[:latest]
|
415
|
+
|
416
|
+
matches = []
|
417
|
+
|
418
|
+
# TODO: Perhaps the selected and unselected should be kept in separate lists?
|
419
|
+
unselected, selected = *partition{ |name, libs| Array === libs }
|
420
|
+
|
421
|
+
# broad search of pre-selected libraries
|
422
|
+
selected.each do |(name, lib)|
|
423
|
+
if ftr = lib.find(path, options)
|
424
|
+
next if Library.load_stack.last == ftr
|
425
|
+
matches << ftr
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# finally a broad search on unselected libraries
|
430
|
+
unselected.each do |(name, libs)|
|
431
|
+
libs = [libs.sort.first] if latest
|
432
|
+
libs.each do |lib|
|
433
|
+
ftr = lib.find(path, options)
|
434
|
+
matches << ftr if ftr
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
matches.uniq
|
439
|
+
end
|
440
|
+
|
441
|
+
#
|
442
|
+
# Search for all matching library files that match the given pattern.
|
443
|
+
# This could be of useful for plugin loader.
|
444
|
+
#
|
445
|
+
# @param [Hash] options
|
446
|
+
# Glob matching options.
|
447
|
+
#
|
448
|
+
# @option options [Boolean] :latest
|
449
|
+
# Search only activated libraries or the most recent version
|
450
|
+
# of a given library.
|
451
|
+
#
|
452
|
+
# @return [Array] Matching file paths.
|
453
|
+
#
|
454
|
+
# @todo Should this return list of Feature objects instead of file paths?
|
455
|
+
#
|
456
|
+
def glob(match, options={})
|
457
|
+
latest = options[:latest]
|
458
|
+
|
459
|
+
matches = []
|
460
|
+
|
461
|
+
each do |name, libs|
|
462
|
+
case libs
|
463
|
+
when Array
|
464
|
+
libs = libs.sort
|
465
|
+
libs = [libs.first] if latest
|
466
|
+
else
|
467
|
+
libs = [libs]
|
468
|
+
end
|
469
|
+
|
470
|
+
libs.each do |lib|
|
471
|
+
lib.loadpath.each do |path|
|
472
|
+
find = File.join(lib.location, path, match)
|
473
|
+
list = Dir.glob(find)
|
474
|
+
list = list.map{ |d| d.chomp('/') }
|
475
|
+
matches.concat(list)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
matches
|
481
|
+
end
|
482
|
+
|
483
|
+
#
|
484
|
+
# Reduce the ledger to only those libraries the given library requires.
|
485
|
+
#
|
486
|
+
# @param [String] name
|
487
|
+
# The name of the primary library.
|
488
|
+
#
|
489
|
+
# @param [String] constraint
|
490
|
+
# The version constraint string.
|
491
|
+
#
|
492
|
+
# @return [Ledger] The ledger.
|
493
|
+
#
|
494
|
+
def isolate(name, constraint=nil)
|
495
|
+
library = activate(name, constraint)
|
496
|
+
|
497
|
+
# TODO: shouldn't this be done in #activate ?
|
498
|
+
acivate_requirements(library)
|
499
|
+
|
500
|
+
unused = []
|
501
|
+
each do |name, libs|
|
502
|
+
ununsed << name if Array === libs
|
503
|
+
end
|
504
|
+
unused.each{ |name| @table.delete(name) }
|
505
|
+
|
506
|
+
self
|
507
|
+
end
|
508
|
+
|
509
|
+
#
|
510
|
+
# Load up the ledger with a given set of paths and add an instance of
|
511
|
+
# the special `RubyLibrary` class after that.
|
512
|
+
#
|
513
|
+
# @param [Array] paths
|
514
|
+
#
|
515
|
+
# @option paths [Boolean] :expound
|
516
|
+
# Expound on path entires. See {#expound_paths}.
|
517
|
+
#
|
518
|
+
# @return [Ledger] The primed ledger.
|
519
|
+
#
|
520
|
+
def prime(*paths)
|
521
|
+
options = Hash === paths.last ? paths.pop : {}
|
522
|
+
|
523
|
+
paths = expound_paths(*paths) if options[:expound]
|
524
|
+
|
525
|
+
require 'library/rubylib'
|
526
|
+
|
527
|
+
paths.each do |path|
|
528
|
+
begin
|
529
|
+
add_location(path) if library_path?(path)
|
530
|
+
rescue => err
|
531
|
+
$stderr.puts err.message if ENV['debug']
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
add_library(RubyLibrary.new)
|
536
|
+
|
537
|
+
self
|
538
|
+
end
|
539
|
+
|
540
|
+
private
|
541
|
+
|
542
|
+
#
|
543
|
+
# For flexible priming, this method can be used to recursively
|
544
|
+
# lookup library locations.
|
545
|
+
#
|
546
|
+
# If a given path is a file, it will considered a lookup "roll",
|
547
|
+
# such that each line entry in the file is considered another
|
548
|
+
# path to be expounded upon.
|
549
|
+
#
|
550
|
+
# If a given path is a directory, it will be returned if it
|
551
|
+
# is a valid Ruby library location, otherwise each subdirectory
|
552
|
+
# will be checked to see if it is a valid Ruby library location,
|
553
|
+
# and returned if so.
|
554
|
+
#
|
555
|
+
# If, on the other hand, a given path is a file glob pattern,
|
556
|
+
# the pattern will be expanded and those paths will expounded
|
557
|
+
# upon in turn.
|
558
|
+
#
|
559
|
+
def expound_paths(*entries)
|
560
|
+
paths = []
|
561
|
+
|
562
|
+
entries.each do |entry|
|
563
|
+
entry = entry.strip
|
564
|
+
|
565
|
+
next if entry.empty?
|
566
|
+
next if entry.start_with?('#')
|
567
|
+
|
568
|
+
if File.directory?(entry)
|
569
|
+
if library_path?(entry)
|
570
|
+
paths << entry
|
571
|
+
else
|
572
|
+
subpaths = Dir.glob(File.join(entry, '*/'))
|
573
|
+
subpaths.each do |subpath|
|
574
|
+
paths << subpath if library_path?(subpath)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
elsif File.file?(entry)
|
578
|
+
paths.concat(expound_paths(*File.readlines(entry)))
|
579
|
+
else
|
580
|
+
glob_paths = Dir.glob(entry)
|
581
|
+
if glob_paths.first != entry
|
582
|
+
paths.concat(expound_paths(*glob_paths))
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
paths
|
588
|
+
end
|
589
|
+
|
590
|
+
#
|
591
|
+
# Is a directory a Ruby library?
|
592
|
+
#
|
593
|
+
# @todo Support gem home location.
|
594
|
+
#
|
595
|
+
def library_path?(path)
|
596
|
+
dotruby?(path) || (ENV['RUBYLIBS_GEMSPEC'] && gemspec?(path))
|
597
|
+
end
|
598
|
+
|
599
|
+
# TODO: First recursively constrain the ledger, then activate. That way
|
600
|
+
# any missing libraries will cause an error. (hmmm... actually that's
|
601
|
+
# an imperfect way to resolve version dependencies). Ultimately we probably
|
602
|
+
# need a separate module to handle this.
|
603
|
+
|
604
|
+
#
|
605
|
+
# Activate library requirements.
|
606
|
+
#
|
607
|
+
# @todo: checklist is used to prevent possible infinite recursion, but
|
608
|
+
# it might be better to put a flag in Library instead.
|
609
|
+
#
|
610
|
+
def acivate_requirements(library, development=false, checklist={})
|
611
|
+
reqs = development ? library.requirements : library.runtime_requirements
|
612
|
+
|
613
|
+
checklist[library] = true
|
614
|
+
|
615
|
+
reqs.each do |req|
|
616
|
+
name = req['name']
|
617
|
+
vers = req['version']
|
618
|
+
|
619
|
+
library = activate(name, vers)
|
620
|
+
|
621
|
+
acivate_requirements(library, development, checklist) unless checklist[library]
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
#
|
626
|
+
# Does the directory have a `.ruby` file?
|
627
|
+
#
|
628
|
+
def dotruby?(path)
|
629
|
+
File.file?(File.join(path, '.ruby'))
|
630
|
+
end
|
631
|
+
|
632
|
+
#
|
633
|
+
# Does a path have a `.gemspec` file? This is fallback measure if a .ruby file is not found.
|
634
|
+
#
|
635
|
+
def gemspec?(path)
|
636
|
+
glob = File.file?(File.join(path, '{,*}.gemspec'))
|
637
|
+
Dir[glob].first
|
638
|
+
end
|
639
|
+
|
640
|
+
end
|
641
|
+
|
642
|
+
end
|