roll 0.8.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.
@@ -0,0 +1,7 @@
1
+
2
+ t = Time.now
3
+
4
+ require 'roll/library'
5
+
6
+ puts "Load Time: #{Time.now - t} seconds"
7
+
@@ -0,0 +1,12 @@
1
+ require 'roll/library'
2
+
3
+ p Library.list
4
+
5
+
6
+ foolib = Library.open('foo')
7
+
8
+ p foolib
9
+
10
+ foolib.require 'tryme'
11
+ foolib.require 'trymetoo'
12
+
@@ -0,0 +1 @@
1
+ puts "Try Me v1.0"
@@ -0,0 +1 @@
1
+ puts "Try Me v1.1"
@@ -0,0 +1,2 @@
1
+ require 'roll/library'
2
+
@@ -0,0 +1,72 @@
1
+ module Roll
2
+
3
+ # Attributes DSL
4
+ module Attributes
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ #
11
+ def valid?
12
+ begin
13
+ validate
14
+ return true
15
+ rescue ValidationError
16
+ return false
17
+ end
18
+ end
19
+
20
+ #
21
+ def validate
22
+ self.class.validation.each do |message, block|
23
+ raise(ValidationError, message) unless instance_eval(&block)
24
+ end
25
+ end
26
+
27
+ alias_method :assert_valid, :validate
28
+
29
+ #
30
+
31
+ module ClassMethods
32
+
33
+ def instance_attributes
34
+ @@attributes ||= []
35
+ end
36
+
37
+ # Define an attribute.
38
+
39
+ def attr_accessor(name, *aliases)
40
+ instance_attributes << name.to_s
41
+ instance_attributes.uniq!
42
+ super(name)
43
+ aliases.each{ |aliaz| alias_accessor(aliaz, name) }
44
+ end
45
+
46
+ # Define an attribute alias.
47
+
48
+ def alias_accessor(aliaz, name)
49
+ alias_method aliaz, name
50
+ alias_method "#{aliaz}=", "#{name}="
51
+ end
52
+
53
+ def validation
54
+ @@validation ||= []
55
+ end
56
+
57
+ # # Does this class provide open access?
58
+ # def open_access?
59
+ # false
60
+ # end
61
+
62
+ def validate(message, &block)
63
+ validation << [message, block]
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+
70
+ #
71
+ class ValidationError < Exception
72
+ end
@@ -0,0 +1,635 @@
1
+ # TITLE:
2
+ #
3
+ # library.rb
4
+ #
5
+ # LICENSE:
6
+ #
7
+ # Copyright (c)2007 Thomas Sawyer, Ruby License
8
+ #
9
+ # TODO:
10
+ #
11
+ # - Commented out #import for now. Facets' module_require and module_load
12
+ # are probably better. Implement those for Rolls instead. (Yes?)
13
+ # - Maybe add autopackage install system?
14
+ # - What about remotes? What about version tiers when remote requiring?
15
+
16
+ require 'rbconfig'
17
+ require 'fileutils'
18
+ require 'roll/package'
19
+ require 'roll/version'
20
+ #require 'roll/sign'
21
+
22
+ # We need to hold a copy of the original $LOAD_PATH
23
+ # for specified "ruby: ..." loads.
24
+
25
+ $RUBY_PATH = $LOAD_PATH.dup
26
+
27
+ # Locations of rolls-ready libraries.
28
+ # # We could use the site_ruby locations instead, but that creates an odd sort of overlap in
29
+ # # the load paths, and would also promote putting the information file in the lib/ dir. So
30
+ # # I think it's better to use a separate location. (After all site_ruby may just fade away.)
31
+ # $LOAD_SITE = $LOAD_PATH - Config::CONFIG.values_at('rubylibdir', 'archdir') - ['.']
32
+
33
+ rolldir = 'ruby_library'
34
+ sitedir = ::Config::CONFIG['sitedir']
35
+ version = ::Config::CONFIG['ruby_version']
36
+
37
+ $LOAD_SITE = [File.join(File.dirname(sitedir), rolldir, version)] + ENV['ROLL_PATH'].to_s.split(':')
38
+
39
+ # = Library Class
40
+ #
41
+ # The Library class serves as an objecified location in Ruby's load paths.
42
+ #
43
+ # The Library qua class also serves as the library manager, storing a ledger of
44
+ # available libraries.
45
+ #
46
+ # A library is roll-ready when it supplies a {name}-{verison}.roll file in either its
47
+ # base directory or in the base's meta/ directory. The roll file name is specifically
48
+ # designed to make library lookup fast. There is no need for Rolls to open the roll
49
+ # file until an actual version is used. It also gives the most flexability in repository
50
+ # layout. Rolls searches up to three subdirs deep looking for roll files. This is
51
+ # suitable to non-versioned libs, versioned libs, non-versioned subprojects and subprojects,
52
+ # including typical Subversion repository layouts.
53
+
54
+ class Library
55
+
56
+ # Dynamic link extension.
57
+ def self.dlext
58
+ @dlext ||= '.' + ::Config::CONFIG['DLEXT']
59
+ end
60
+
61
+ # Location of rolls-ready libs.
62
+ def self.load_site
63
+ $LOAD_SITE
64
+ end
65
+
66
+ #
67
+ def self.load_path
68
+ $LOAD_PATH
69
+ end
70
+
71
+ #
72
+ def self.ruby_path
73
+ $RUBY_PATH
74
+ end
75
+
76
+ # Name of library.
77
+ attr_reader :name
78
+
79
+ # Version of library. This is a VersionNumber object.
80
+ attr_reader :version
81
+
82
+ # Path to library.
83
+ attr_reader :location
84
+
85
+ # New libray.
86
+ def initialize(location, identity=nil) #name, version, location=nil)
87
+ @location = location
88
+
89
+ if identity
90
+ @name = identity[:name]
91
+ @version = identity[:version]
92
+ @load_path = identity[:load_path] || identity[:load_paths]
93
+ else
94
+ identify(location)
95
+ end
96
+
97
+ raise unless @name
98
+ raise unless @version
99
+
100
+ @identity = identity
101
+
102
+ # TODO Version number needs to be more flexiable in handling non-numeric tuples.
103
+ @version = VersionNumber.new(@version) unless VersionNumber === @version
104
+ end
105
+
106
+ # Identify a library based on it's location.
107
+ def identify(location)
108
+ file = File.join(location,'{,meta/}*.roll')
109
+ if file = Dir.glob(file).first
110
+ @name, @version = file.chomp('.roll').split('-')
111
+ end
112
+ end
113
+
114
+ # Inspection.
115
+ def inspect
116
+ if version
117
+ "#<Library #{name}/#{version}>"
118
+ else
119
+ "#<Library #{name}>"
120
+ end
121
+ end
122
+
123
+ def roll_file
124
+ find = "{,meta/}*.roll" # "{,meta/}#{name}-#{version}.roll"
125
+ file = Dir.glob(File.join(location, find), File::FNM_CASEFOLD).first
126
+ end
127
+
128
+ # Read roll file.
129
+ def load_roll
130
+ return unless location # NEED TO DO THIS BETTER. THIS IS HERE FOR THE ONE 'ruby' CASE.
131
+ raise LoadError unless File.directory?(location)
132
+ glob = File.join(location, "{,meta/}*.roll") # "{,meta/}#{name}-#{version}.roll"
133
+ file = Dir.glob(glob, File::FNM_CASEFOLD).first
134
+ if file
135
+ @roll_file = file
136
+ @roll = Roll::Package.open(file, :name=>name, :version=>version.to_s)
137
+ else
138
+ raise "THIS SHOULD NOT POSSIBLE!"
139
+ #@roll = false #Roll::Package.new # THIS IN NO LONGER POSSIBLE
140
+ end
141
+ #use #?
142
+ return @roll
143
+ end
144
+
145
+ # Does this library have an roll file?
146
+ #def rolled?
147
+ # roll ? true : false
148
+ #end
149
+
150
+ # Does this library have a remote source?
151
+
152
+ #def remote?
153
+ # rolled? and source and pubkey
154
+ #end
155
+
156
+ # Meta information about the libray.
157
+ def roll
158
+ @roll = load_roll if @roll.nil? # If it's false, we don't have one.
159
+ @roll
160
+ end
161
+ alias_method :info, :roll
162
+
163
+ # Compare by version.
164
+ def <=>(other)
165
+ version <=> other.verison
166
+ end
167
+
168
+ # List of subdirectories that are searched when loading libs.
169
+ # In general this should include all the libs internal load paths,
170
+ # so long as there will be no name conflicts between directories.
171
+ def lib_path
172
+ return unless roll # NEED TO DO THIS BETTER. THIS IS HERE FOR THE ONE 'ruby' CASE.
173
+ return @load_path if @load_path
174
+ if roll.load_path
175
+ @load_path = roll.load_path.collect{ |path| File.join(location, path) }
176
+ else
177
+ @load_path = ['lib'] # ["lib/#{name}"]
178
+ end
179
+ end
180
+ alias_method :load_path, :lib_path
181
+ alias_method :load_paths, :lib_path
182
+
183
+ # List of subdirectories that are searched for binaries.
184
+ #++
185
+ # TODO I think it would be better just to make this a fix
186
+ # convention that bin/ is always the place to put these.
187
+ # (Unlike lib/ which needs to be more flexable.)
188
+ #--
189
+ def bin_path
190
+ return @bin_path if @bin_path
191
+ return [] unless location # NEED TO DO THIS BETTER.
192
+ if roll.bin_path
193
+ @bin_path = roll.bin_path.collect{ |path| File.join(location, path) }
194
+ else
195
+ if File.directory?(File.join(location, 'bin'))
196
+ @bin_path = [File.join(location, 'bin')]
197
+ else
198
+ @bin_path = []
199
+ end
200
+ end
201
+ return @bin_path
202
+ end
203
+
204
+ # Return the path to the data directory associated with the given
205
+ # package name. Normally this is just
206
+ # "#{Config::CONFIG['datadir']}/#{package_name}", but may be
207
+ # modified by packages like RubyGems and Rolls to handle
208
+ # versioned data directories.
209
+ def datadir(versionless=false)
210
+ if version and not versionless
211
+ File.join(Config::CONFIG['datadir'], name, version)
212
+ else
213
+ File.join(Config::CONFIG['datadir'], name)
214
+ end
215
+ end
216
+
217
+ # Return the path to the configuration directory.
218
+ #--
219
+ # Can or should configuration directories be versioned?
220
+ #++
221
+ def confdir
222
+ File.join(location, 'data')
223
+ #if version
224
+ # File.join(Config::CONFIG['confdir'], name, version)
225
+ #else
226
+ # File.join(Config::CONFIG['datadir'], name)
227
+ #end
228
+ end
229
+
230
+ # Require find.
231
+ def require_find(file)
232
+ file = roll.index_file if (file.nil? or file.empty?)
233
+ glob = File.join('{' + load_path.join(',') + '}', file + "{.rb,#{Library.dlext},}")
234
+ Dir.glob(glob).first
235
+ end
236
+
237
+ # Load find.
238
+ def load_find(file)
239
+ file = roll.index_file if (file.nil? or file.empty?)
240
+ glob = File.join('{' + load_path.join(',') + '}', file)
241
+ Dir.glob(glob).first
242
+ end
243
+
244
+ # Library specific #require.
245
+ def require(file)
246
+ if path = require_find(file)
247
+ Kernel.require(path)
248
+ else
249
+ raise LoadError, "no such file to load -- #{name}:#{file}"
250
+ end
251
+ end
252
+
253
+ # Library specific load.
254
+ def load(file, wrap=nil)
255
+ if path = load_find(file)
256
+ Kernel.load(path, wrap)
257
+ else
258
+ raise LoadError, "no such file to load -- #{name}:#{file}"
259
+ end
260
+ end
261
+
262
+ # Library specific autoload.
263
+ def autoload(base, file)
264
+ if path = require_find(file)
265
+ Kernel.autoload(base, file)
266
+ else
267
+ raise LoadError, "no such file to autoload -- #{name}:#{file}"
268
+ end
269
+ end
270
+
271
+ # Require into module.
272
+ def module_require(mod, file)
273
+ if path = require_find(file)
274
+ mod.module_require(path) # FIXME
275
+ else
276
+ raise LoadError, "no such file to load -- #{name}:#{file}"
277
+ end
278
+ end
279
+
280
+ # Load into module.
281
+ def module_load(mod, file)
282
+ if path = load_find(file)
283
+ mod.module_load(path) # FIXME
284
+ else
285
+ raise LoadError, "no such file to load -- #{name}:#{file}"
286
+ end
287
+ end
288
+
289
+ # Library specific autoload for module.
290
+ def module_autoload(mod, base, file)
291
+ if path = require_find(file)
292
+ mod.autoload_without_roll(base, file)
293
+ else
294
+ raise LoadError, "no such file to autoload -- #{name}:#{file}"
295
+ end
296
+ end
297
+
298
+ # Put the libs load paths into the global lookup.
299
+ #--
300
+ # TODO Maybe call 'import' instead?
301
+ #++
302
+ def utilize
303
+ lib_path.each do |path|
304
+ Library.load_path.unshift(path)
305
+ end
306
+ Library.load_path.uniq!
307
+ self
308
+ end
309
+
310
+ # Class instance variable @ledger stores the library references.
311
+
312
+ @ledger = {}
313
+
314
+ class << self
315
+
316
+ # Scan the site locations for libraries.
317
+ def scan
318
+ # First we add Ruby core and standard libraries.
319
+ @ledger['ruby'] = Library.new(nil, :name=>'ruby', :version=>VERSION, :load_path=>Library.ruby_path)
320
+
321
+ scan_working() if $DEBUG
322
+
323
+ projs1 = Dir.glob( '{' + $LOAD_SITE.join(',') + '}/*{/meta,}/*.roll', File::FNM_CASEFOLD )
324
+ projs2 = Dir.glob( '{' + $LOAD_SITE.join(',') + '}/*/*{/meta,}/*.roll', File::FNM_CASEFOLD )
325
+ projs3 = Dir.glob( '{' + $LOAD_SITE.join(',') + '}/*/*/*{/meta,}/*.roll', File::FNM_CASEFOLD )
326
+
327
+ projs = projs1 + projs2 + projs3
328
+
329
+ #dirs = projs.collect do |prj|
330
+ # metafile = File.basename(prj)
331
+ # dir = File.dirname(prj)
332
+ # dir = File.dirname(dir) if dir =~ /meta$/
333
+ # dir
334
+ #end
335
+
336
+ projs.uniq!
337
+
338
+ #dirs -= $LOAD_SITE
339
+
340
+ projs.each do |proj|
341
+ name, ver = *File.basename(proj).chomp('.roll').split('-')
342
+ dir = File.dirname(proj)
343
+ dir = File.dirname(dir) if File.basename(dir) == 'meta'
344
+
345
+ name = name.downcase
346
+
347
+ #if versions.empty?
348
+ # @ledger[name] ||= Library.new(dir, :name=>name, :version=>'0') #Version.new('0', dir)
349
+ #else
350
+ @ledger[name] ||= []
351
+ @ledger[name] << Library.new(dir, :name=>name, :version=>ver)
352
+ #end
353
+ end
354
+ end
355
+
356
+ # Scan current working location to see if there's
357
+ # a library. This will ascend from the current
358
+ # working directy to one level below root looking
359
+ # for a lib/ directory.
360
+ #--
361
+ # TODO CHANGE TO LOOK FRO INDEX FILE.
362
+ #++
363
+ def scan_working
364
+ paths = Dir.pwd.split('/')
365
+ (paths.size-1).downto(1) do |n|
366
+ dir = File.join( *(paths.slice(0..n) << 'lib') )
367
+ if File.directory? dir
368
+ $LOAD_SITE.unshift dir
369
+ end
370
+ end
371
+ end
372
+
373
+ # Return a list of library names.
374
+ def list
375
+ @ledger.keys
376
+ end
377
+
378
+ # Libraries are Singleton pattern.
379
+ def instance(name, constraint=nil)
380
+ name = name.to_s
381
+ #raise "no library -- #{name}" unless @ledger.include?( name )
382
+ return nil unless @ledger.include?(name)
383
+ case lib = @ledger[name]
384
+ when Library
385
+ return lib unless constraint
386
+ raise VersionConflict, "previously selected library version" # -- #{lib.version}"
387
+ #when Version
388
+ # @ledger[name] = new(lib.location, :name=>name, :version=>lib) #new(name, lib, lib.location)
389
+ when Array
390
+ if constraint
391
+ compare = VersionNumber.constrant_lambda(constraint)
392
+ version = lib.select(&compare).max
393
+ else
394
+ version = lib.max
395
+ end
396
+ unless version
397
+ raise VersionError, "no library version -- #{name} #{constraint}"
398
+ end
399
+ @ledger[name] = version #new(version.location, :name=>name, :version=>version) #new(name, version, version.location)
400
+ else
401
+ raise "this should never happen"
402
+ end
403
+ @ledger[name].roll # Make sure the roll file is loaded.
404
+ @ledger[name]
405
+ end
406
+
407
+ # A shortcut for #instance.
408
+ alias_method :[], :instance
409
+
410
+ # Same as #instance but will raise and error if the library is not found.
411
+ def open(name, constraint=nil, &yld)
412
+ lib = instance(name, constraint)
413
+ unless lib
414
+ raise LoadError, "no library -- #{name}"
415
+ end
416
+ yield(lib) if yld
417
+ lib
418
+ end
419
+
420
+ end
421
+
422
+ # VersionError is raised when a requested version cannot be found.
423
+ class VersionError < ::RangeError # :nodoc:
424
+ end
425
+
426
+ # VersionConflict is raised when selecting another version
427
+ # of a library when a previous version has already been selected.
428
+ class VersionConflict < ::LoadError # :nodoc:
429
+ end
430
+ end
431
+
432
+
433
+ module ::Config
434
+ # Return the path to the data directory associated with the given
435
+ # package name. Normally this is just
436
+ # "#{Config::CONFIG['datadir']}/#{package_name}", but may be
437
+ # modified by packages like RubyGems and Rolls to handle
438
+ # versioned data directories.
439
+ def self.datadir(name, versionless=false)
440
+ if lib = Library.instance( name )
441
+ lib.datadir( versionless )
442
+ else
443
+ File.join(CONFIG['datadir'], name)
444
+ end
445
+ end
446
+
447
+ # Return the path to the configuration directory.
448
+ def self.confdir(name)
449
+ if lib = Library.instance( name )
450
+ lib.confdir
451
+ else
452
+ File.join(CONFIG['datadir'], name)
453
+ end
454
+ end
455
+ end
456
+
457
+
458
+ module ::Kernel
459
+
460
+ # Activate a library.
461
+ def library(name, constraint=nil)
462
+ Library.open(name, constraint)
463
+ end
464
+ module_function :library
465
+
466
+ # Use library. This activates a library, and adds
467
+ # it's load_path to the global $LOAD_PATH.
468
+ #--
469
+ # Maybe call #import instead ?
470
+ #++
471
+ def use(name, constraint=nil)
472
+ Library.open(name, constraint).utilize
473
+ end
474
+
475
+
476
+ def self.parse_load_parameters(file)
477
+ if must = file.index(':')
478
+ name, path = file.split(':')
479
+ else
480
+ name, *rest = file.split('/')
481
+ path = File.join(*rest)
482
+ end
483
+ name = nil if name == ''
484
+ lib = name ? Library.instance(name) : nil
485
+ raise LoadError, "no library found -- #{file}" if must && !lib
486
+ return lib, path
487
+ end
488
+
489
+ # Rolls requires a modification to #require and #load.
490
+ # So that it is not neccessary to make the library() call
491
+ # if you just want the latest version.
492
+ #
493
+ # This would be a bit simpler if we mandated the
494
+ # use of the ':' notation when specifying the library
495
+ # name. Use of the ':' is robust. But we cannot do this
496
+ # w/o loosing backward-compatability. Using '/' in its
497
+ # place has the potential for pathname clashing, albeit
498
+ # the likelihood is small. There are two ways to bypass
499
+ # the problem if it arises. Use 'ruby:{path}' if the
500
+ # conflicting lib is a ruby core or standard library.
501
+ # Use ':{path}' to bypass Roll system altogether.
502
+
503
+ # # Require script.
504
+ # def require(file)
505
+ # if file.index(':')
506
+ # name, file = file.split(':')
507
+ # if name == ''
508
+ # Kernel.require(file)
509
+ # #if lib == 'ruby'
510
+ # # Ruby.require(file)
511
+ # elsif lib = Library.instance(name)
512
+ # lib.require(file)
513
+ # else
514
+ # raise LoadError, "no library found -- #{name}"
515
+ # end
516
+ # else
517
+ # name, *rest = file.split('/')
518
+ # if lib = Library.instance(name)
519
+ # lib.require(File.join(*rest))
520
+ # else
521
+ # Kernel.require(file)
522
+ # end
523
+ # end
524
+ # end
525
+
526
+ # Require script.
527
+ def require(file)
528
+ lib, path = *Kernel.parse_load_parameters(file)
529
+ if lib
530
+ lib.require(path)
531
+ else
532
+ Kernel.require(file)
533
+ end
534
+ end
535
+
536
+ # # Load script.
537
+ # def load(file, wrap=nil)
538
+ # if file.index(':')
539
+ # name, file = file.split(':')
540
+ # if name == ''
541
+ # Kernel.require(file)
542
+ # #if lib == 'ruby'
543
+ # # Ruby.load(file, safe)
544
+ # elsif lib = Library.instance(name)
545
+ # lib.load(file, wrap)
546
+ # else
547
+ # raise LoadError, "no library found -- #{name}"
548
+ # end
549
+ # else
550
+ # name, *rest = file.split('/')
551
+ # if lib = Library.instance(name)
552
+ # lib.load(File.join(*rest))
553
+ # else
554
+ # Kernel.load(file, wrap)
555
+ # end
556
+ # end
557
+ # end
558
+
559
+ # Require script.
560
+ def load(file, wrap=false)
561
+ lib, path = *Kernel.parse_load_parameters(file)
562
+ if lib
563
+ lib.load(path, wrap)
564
+ else
565
+ Kernel.load(file, wrap)
566
+ end
567
+ end
568
+
569
+ # #
570
+ # def autoload(base, file)
571
+ # if file.index(':')
572
+ # name, file = file.split(':')
573
+ # if name == ''
574
+ # Kernel.autoload(base, file)
575
+ # elsif lib = Library.instance(name)
576
+ # lib.autoload(base, file)
577
+ # else
578
+ # raise LoadError, "no library found -- #{name}"
579
+ # end
580
+ # else
581
+ # name, *rest = file.split('/')
582
+ # if lib = Library.instance(name)
583
+ # lib.autoload(base, File.join(*rest))
584
+ # else
585
+ # Kernel.autoload(base, file)
586
+ # end
587
+ # end
588
+ # end
589
+
590
+ =begin
591
+ # Autoload script.
592
+ def autoload(base, file)
593
+ lib, path = *Kernel.parse_load_parameters(file)
594
+ if lib
595
+ lib.autoload(base, path)
596
+ else
597
+ Kernel.autoload(base, file)
598
+ end
599
+ end
600
+ =end
601
+
602
+ end
603
+
604
+ # TODO
605
+ class ::Module
606
+
607
+ =begin
608
+ alias_method :autoload_without_roll, :autoload
609
+
610
+ #
611
+ def autoload(base, file)
612
+ lib, path = *Kernel.parse_load_parameters(file)
613
+ if lib
614
+ lib.module_autoload(self, base, path)
615
+ else
616
+ autoload_without_roll(base, file)
617
+ end
618
+ end
619
+ =end
620
+
621
+ # TODO
622
+
623
+ # Adds importing to Module class called against +self+ as default.
624
+
625
+ # def module_require()
626
+ # end
627
+
628
+ # def module_load()
629
+ # end
630
+
631
+ end
632
+
633
+ # Prime the library ledger.
634
+ Library.scan
635
+