loadable 1.2.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/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # Loadable
2
+
3
+ ## 1 Overview
4
+
5
+ | Project | Loadable |
6
+ |--------------|----------------------------------------------------------|
7
+ | Author | Thomas Sawyer |
8
+ | License | BSD-2-Clause |
9
+ | Copyright | (c) 2010 Thomas Sawyer |
10
+ | Website | http://github.com/rubyworks/loadable |
11
+ | Development | http://github.com/rubyworks/loadable |
12
+ | Mailing-List | http://groups.google.com/group/rubyworks-mailinglist |
13
+
14
+
15
+ ## 2 Description
16
+
17
+ The Loadable gem provides a more robust and convenient means of augmenting
18
+ Ruby's load system, namely the `load` and `require` methods. Rather than
19
+ alias and override these methods, Loadable keeps a list of *load wedges*
20
+ that control the routing of require and load calls.
21
+
22
+ In addition, the Loadable gem includes two pre-made load wedges that can be
23
+ used to prevent name clashes between Ruby's standard library and gem packages
24
+ (see INFRACTIONS.rdoc for more on this). There is also a load wedge for
25
+ for developers to make it trivial to make vendored sub-projects loadable.
26
+
27
+
28
+ ## 3 Usage
29
+
30
+ ### 3.1 Installation
31
+
32
+ Installing via RubyGems follows the usual pattern.
33
+
34
+ $ gem install loadable
35
+
36
+ To automatically load both the Gem and Ruby wedges, and the entire Loadable
37
+ system, add `loadable` to your RUBYOPT environment variable.
38
+
39
+ $ export RUBYOPT="-rloadable"
40
+
41
+ Place this in your shell's configuration file, such as `~/.bashrc`.
42
+
43
+ If you do not want the default setup you can `require 'loadable/system'` instead.
44
+ This will load in Loadable system, but only add an `OriginalLoader` to the
45
+ `$LOADERS` list, leaving off the Ruby and Gem loaders.
46
+
47
+ ### 3.2 Custom Loaders
48
+
49
+ Loadable was written initially to provide the specific capability of loading
50
+ Ruby standard libraries without potential interference from libraries
51
+ installed via RubyGems (see INFRACTIONS.rdoc). The code ultimately evolved
52
+ into a more generic tool, useful for writing any kind of plug-in load
53
+ router.
54
+
55
+ The code for the Ruby wedge serves as a good example of writing a load wedge.
56
+ (Note this is leaves out a few details of the real class for simplicity sake.)
57
+
58
+ require 'rbconfig'
59
+ require 'loadable/mixin'
60
+
61
+ class Loadable::RubyLoader
62
+ include Loadable
63
+
64
+ LOCATIONS = ::RbConfig::CONFIG.values_at(
65
+ 'rubylibdir', 'archdir', 'sitelibdir', 'sitearchdir'
66
+ )
67
+
68
+ def call(fname, options={})
69
+ return unless options[:from].to_s == 'ruby'
70
+ LOCATIONS.each do |loadpath|
71
+ if path = lookup(loadpath, fname, options)
72
+ return super(path, options)
73
+ end
74
+ end
75
+ raise_load_error(fname)
76
+ end
77
+
78
+ def each(options={}, &block)
79
+ LOCATIONS.each do |loadpath|
80
+ traverse(loadpath, &block)
81
+ end
82
+ end
83
+ end
84
+
85
+ To put this loader into action we simply need to register it with the Loadable
86
+ domain.
87
+
88
+ Loadable.register(Loadable::RubyLoader.new)
89
+
90
+ Under the hood, this simply appends the instance to the `$LOADERS` global variable.
91
+
92
+ Loaders, also called load wedges, are easy to write as their interface is very
93
+ simple. Any object the responds to #call, taking parameters of
94
+ <code>(fname, options={})</code>, can be used as a load wedge. A load wedge
95
+ should also support `#each(options={}, &block)` which is used to iterate over
96
+ all requirable files a loader supports.
97
+
98
+ The `Loadable` mixin is just a convenience module that makes writing loaders
99
+ a bit easier. Load wedges can be written without it, however the mixin
100
+ provides a few methods that are often useful to any load wedge. An example is
101
+ the `lookup` method used in the above example, which will search a
102
+ load path in accordance with the Ruby's built-in require and load lookup
103
+ procedures, i.e. automatically trying defualt extensions like `.rb`.
104
+
105
+ You might wonder how the single method, `#call`, handles both load and require
106
+ operations. The secret is in the `options` hash. If <code>options[:load]</code>
107
+ resolves to true, then it is a *load* operation, otherwise it is a *require*
108
+ operation. The `$LOADERS` global variable is iterated over in order.
109
+ When `#load` or `#require` is called each wedge is tried in turn. The return
110
+ value of `#call` controls how this loop proceeds. If the return value is `true`
111
+ then the load was successful, and the loop can break. If it is `false` it means
112
+ the loading has already been handled and the loop can also break. But if the
113
+ return value is `nil`, it means the wedge does not apply and the loop should
114
+ continue. If all wedges have been tried and all have returned `nil` then it
115
+ falls back to the original `#load` and `#require` calls, via an instance
116
+ `OriginalLoader` which should always be the last loader in the `$LOADERS` list.
117
+
118
+
119
+ ## 4 Built-in Loaders
120
+
121
+ The Loadable gem provides three special loaders out-of-the-box, the `RubyLoader`,
122
+ the `GemLoader` and the `VendorLoader`. The first two are probably not exaclty
123
+ what you think they are, going just by their names, so keep reading...
124
+
125
+ ### 4.1 RubyLoader
126
+
127
+ The Ruby wedge makes it possible to load a Ruby standard library without
128
+ interference from installed gems or other package systems. It does this by
129
+ checking for a `:from` option passed to the require or load methods.
130
+
131
+ require 'ostruct', :from=>'ruby'
132
+
133
+ This will load the `ostruct.rb` script from the Ruby standard library regardless
134
+ of whether someone else dropped an `ostruct.rb` file in their project's `lib/`
135
+ directory without understanding the potential consequences.
136
+
137
+ ### 4.2 GemLoader
138
+
139
+ The Gem wedge is similar to the Ruby wedge, in that it isolates the loading
140
+ of a gem's files from other gems.
141
+
142
+ gem 'facets', '~>2.8'
143
+
144
+ require 'string/margin', :from=>'facets'
145
+
146
+ With this we can be sure that 'facets/string/margin' was loaded from the Facets
147
+ library regardless of whether some other gem has a 'facets/string/margin' file
148
+ in its `lib/` directory. If no gem has this file, it will fallback to the
149
+ remaining loaders. However, if we use the `:gem` options instead, it will
150
+ raise a load error.
151
+
152
+ require 'string/does_not_exit', :gem=>'facets'
153
+
154
+ The Gem wedge also supports version constraints, so you do not have to use
155
+ `gem()` method for one off requires from a given gem.
156
+
157
+ require 'string/margin', :from=>'facets', :version=>'~>2.8'
158
+
159
+ ### 4.3 VendorLoader
160
+
161
+ The Vendor wedge is used to add vendored projects to the load system.
162
+ This is especially useful for development. Vendored projects can be added
163
+ in two ways, by registering an instance of VendorLoader, e.g.
164
+
165
+ Loadable.register Loadable::VendorLoader.new('vendor/*')
166
+
167
+ Or using the dedicated `Loadable.vendor(*dir)` method that Loadable provides
168
+ to make this more convenient.
169
+
170
+ Loadable.vendor('vendor/*')
171
+
172
+
173
+ ## 5 Development
174
+
175
+ Source code for Loadbable is hosted by [GitHub](http://github.com/rubyworks/loadable).
176
+
177
+ If you has come across and issues, we encourage you to fork the repository and
178
+ submit a pull request with the fix. When submitting a pull request, it is best
179
+ if the changes are orgnanized into a new topic branch.
180
+
181
+ If you don't have time to code up patches yourself, please do not hesitate to
182
+ simply report the issue on the [issue tracker](http://github.com/rubyworks/loadable/issues).
183
+
184
+
185
+ ## 6 Copyrights
186
+
187
+ Copyright (c) 2010 Thomas Sawyer, Rubyworks
188
+
189
+ Load is distributed under the terms of the **FreeBSD** license.
190
+
191
+ See COPYING.rdoc file for details.
192
+
@@ -0,0 +1,10 @@
1
+ class Module
2
+ def module_require(child)
3
+ child = child.to_s.gsub('::','/').downcase
4
+ path = name.to_s.gsub('::','/').downcase
5
+ path = File.join(path, child)
6
+ require_relative(path)
7
+ end
8
+ alias_mehtod :class_require, :module_require
9
+ end
10
+
@@ -0,0 +1,81 @@
1
+ module Gem
2
+
3
+ # TODO: Below are two implementations of the same feature. Currently
4
+ # we are using `Gem.search` code which calls `Gem::Specification.current_specs`.
5
+ # Possibly this could be replaced by simply calling `GemPathSearcher.current_files`.
6
+ # Hoverver, it is unclear to me at this time which is the best course of action.
7
+
8
+ # Search RubyGems for matching paths in current gem versions.
9
+ def self.search(fname, options={})
10
+ return unless defined?(::Gem)
11
+ matches = []
12
+ Gem::Specification.current_specs.each do |spec|
13
+ glob = File.join(spec.lib_dirs_glob, match)
14
+ list = Dir[glob] #.map{ |f| f.untaint }
15
+ list = list.map{ |d| d.chomp('/') }
16
+ matches.concat(list)
17
+ end
18
+ matches
19
+ end
20
+
21
+ class Specification
22
+ # Return a list of actives specs, or latest version if not active.
23
+ def self.current_specs
24
+ named = Hash.new{|h,k| h[k] = [] }
25
+ each{ |spec| named[spec.name] << spec }
26
+ list = []
27
+ named.each do |name, vers|
28
+ if spec = vers.find{ |s| s.activated? }
29
+ list << spec
30
+ else
31
+ spec = vers.max{ |a,b| a.version <=> b.version }
32
+ list << spec
33
+ end
34
+ end
35
+ return list
36
+ end
37
+
38
+ # Return full path of requireable file path given relative path.
39
+ def find_requirable_file(file)
40
+ root = full_gem_path
41
+
42
+ require_paths.each do |lib|
43
+ base = "#{root}/#{lib}/#{file}"
44
+ Gem.suffixes.each do |suf|
45
+ path = "#{base}#{suf}"
46
+ return path if File.file? path
47
+ end
48
+ end
49
+
50
+ return nil
51
+ end
52
+ end
53
+
54
+ =begin
55
+ class GemPathSearcher
56
+ # Return a list of matching files among active or latest gems.
57
+ def current_files(glob)
58
+ matches = {}
59
+ gemspecs = init_gemspecs
60
+
61
+ # loaded specs
62
+ gemspecs.each do |spec|
63
+ next unless spec.loaded?
64
+ next if matches.key?(spec.name)
65
+ files = matching_files(spec, glob)
66
+ matches[spec.name] = files
67
+ end
68
+
69
+ # latest specs
70
+ gemspecs.each do |spec|
71
+ next if matches.key?(spec.name)
72
+ files = matching_files(spec, glob)
73
+ matches[spec.name] = files
74
+ end
75
+
76
+ matches.values.flatten.uniq
77
+ end
78
+ end
79
+ =end
80
+
81
+ end
@@ -0,0 +1,64 @@
1
+ module Loadable
2
+
3
+ # Stores active loaders.
4
+ $LOADERS = []
5
+
6
+ # Require/load script.
7
+ #
8
+ # @param [String] fname
9
+ # The script to require/load.
10
+ #
11
+ def self.call(fname, options={})
12
+ success = nil
13
+ $LOADERS.each do |wedge|
14
+ success = wedge.call(fname, options)
15
+ break unless success.nil?
16
+ end
17
+ return success
18
+ end
19
+
20
+ # Iterate over all requirable files.
21
+ #
22
+ # LoadSystem.each{ |file| p file }
23
+ #
24
+ # Note that esoteric load wedges may return a symbolic path rather than
25
+ # an actual file path.
26
+ #
27
+ def self.each(options={}, &block)
28
+ $LOADERS.each do |wedge|
29
+ wedge.each(options, &block)
30
+ end
31
+ end
32
+
33
+ # Search wedges for all matching paths.
34
+ #
35
+ # LoadSystem.search('detroit-*.rb')
36
+ #
37
+ # Note that "esoteric" wedges might return a symbolic identifier rather
38
+ # than an actual file path.
39
+ #
40
+ # TODO: Handle default ruby extensions in search.
41
+ def self.search(glob, options={}, &criteria)
42
+ matches = []
43
+ $LOADERS.each do |wedge|
44
+ wedge.each do |path|
45
+ next unless criteria.call(path) if criteria
46
+ matches << path if File.fnmatch?(glob, path.to_s) # TODO: add `options[:flags].to_i` ?
47
+ end
48
+ end
49
+ matches
50
+ end
51
+
52
+ # Vendor location.
53
+ #
54
+ def self.vendor(*directory)
55
+ $LOADERS.unshift(VendorLoader.new(*directory))
56
+ end
57
+
58
+ # Add a loader to the $LOADERS global variable.
59
+ #
60
+ def self.register(loader)
61
+ $LOADERS.unshift(loader)
62
+ end
63
+
64
+ end
@@ -0,0 +1,35 @@
1
+ module Kernel
2
+
3
+ # Aliases for original load and require.
4
+ alias_method :load0, :load
5
+ alias_method :require0, :require
6
+
7
+ # TODO: I am not certain `:load` is the best name for this option.
8
+ # Something like `:eval` is more meaningful. OTOH, we could flip
9
+ # it about and use `:feature` or `:cache` to mean the opposite.
10
+
11
+ #
12
+ def require(fname, options={})
13
+ options[:load] = false unless options.key?(:load)
14
+ Loadable.call(fname, options)
15
+ #success = LoadSystem.require(fname, options)
16
+ #if success.nil?
17
+ # success = require_without_wedge(fname)
18
+ #end
19
+ #success
20
+ end
21
+
22
+ #
23
+ def load(fname, options={})
24
+ options = Hash===options ? options : {:wrap=>options}
25
+ options[:load] = true unless options.key?(:load)
26
+ Loadable.call(fname, options)
27
+ #success = LoadSystem.load(fname, options)
28
+ #if success.nil?
29
+ # success = load_without_wedge(fname)
30
+ #end
31
+ #success
32
+ end
33
+
34
+ end
35
+
@@ -0,0 +1,98 @@
1
+ # TODO: Support colon notation again ?
2
+
3
+ # TODO: If :gem AND :from options are given, perhaps try both
4
+ # instead of either-or?
5
+
6
+ require 'loadable/mixin'
7
+ require 'loadable/core_ext/rubygems'
8
+
9
+ module Loadable
10
+
11
+ # The Gem Wedge allows gem files to be loaded in a isolated fashion.
12
+ #
13
+ # require 'tracepoint', :from=>'tracepoint'
14
+ #
15
+ # The example would load the tracepoint file from the tracepoint gem.
16
+ # It will also fallback to the RubyWedge if 'tracepoint' is not found
17
+ # among available gems. Loading can be limited to gems only by using
18
+ # the `:gem` options instead.
19
+ #
20
+ # require 'tracepoint', :gem=>'tracepoint'
21
+ #
22
+ # Now, if the `tracepoint` script is not found among availabe gems,
23
+ # a LoadError will be raised.
24
+ #
25
+ class GemLoader
26
+
27
+ include Loadable
28
+
29
+ # Load script from specific gem.
30
+ #
31
+ # Returns +nil+ if this loader is not applicable, which is determined
32
+ # by the use of `:gem => 'foo'` or `:from => 'foo'` options.
33
+ #
34
+ def call(fname, options={})
35
+ return unless apply?(fname, options)
36
+
37
+ gem_name = options[:gem] || options[:from]
38
+
39
+ if vers = options[:version]
40
+ spec = ::Gem::Specification.find_by_name(gem_name, vers)
41
+ else
42
+ spec = ::Gem::Specification.find_by_name(gem_name)
43
+ end
44
+
45
+ if options[:gem]
46
+ raise_load_error(fname) unless spec
47
+ else
48
+ return unless spec
49
+ end
50
+
51
+ file = spec.find_requirable_file(fname)
52
+ file = spec.find_requirable_file(File.join(gem_name, fname)) unless file
53
+
54
+ if file
55
+ super(file, options)
56
+ else
57
+ raise_load_error(fname)
58
+ end
59
+ end
60
+
61
+ # Iterate over each loadable file in specified gem.
62
+ #
63
+ def each(options={}, &block)
64
+ return unless apply?(nil, options)
65
+
66
+ gem_name = (options[:gem] || options[:from]).to_s
67
+
68
+ if vers = options[:version]
69
+ spec = ::Gem::Specification.find_by_name(gem_name, vers)
70
+ else
71
+ spec = ::Gem::Specification.find_by_name(gem_name)
72
+ end
73
+
74
+ return unless spec
75
+
76
+ #spec.activate
77
+
78
+ spec.require_paths.each do |path|
79
+ traverse(File.join(spec.full_gem_path, path), &block)
80
+ end
81
+ end
82
+
83
+ # Determine if this load4 wedge is applicable given the +fname+
84
+ # and +options+.
85
+ #
86
+ def apply?(fname, options={})
87
+ return true if options[:gem]
88
+ return true if options[:from] && (
89
+ ::Gem::Specification.find{ |s| options[:from].to_s == s.name }
90
+ )
91
+ return false
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ # Copyright 2010 Thomas Sawyer, Rubyworks (BSD-2-Clause license)
@@ -0,0 +1,63 @@
1
+ require 'loadable/mixin'
2
+ require 'loadable/core_ext/rubygems'
3
+
4
+ module Loadable
5
+
6
+ # The original loader is simply an encpsulation of the Ruby's
7
+ # built-in #require and #load functionality. This load wedge is
8
+ # thus the first placed of the $LOADER list and the final
9
+ # fallback if no other loader succeeds.
10
+ #
11
+ class OriginalLoader
12
+
13
+ include Loadable
14
+
15
+ alias_method :original_load, :load
16
+ alias_method :original_require, :require
17
+
18
+ #
19
+ def call(fname, options={})
20
+ if options[:load]
21
+ original_load(fname, options[:wrap])
22
+ else
23
+ original_require(fname)
24
+ end
25
+ end
26
+
27
+ # Iterate over each requirable file. Since RubyGems has become
28
+ # a standard part of Ruby as of 1.9, this includes active and latest
29
+ # versions of inactive gems.
30
+ #
31
+ # NOTE: in older versions of RubyGems, activated gem versions are in
32
+ # the $LOAD_PATH too. This may cause some duplicate iterations.
33
+ #
34
+ def each(options={}, &block)
35
+ each_loadpath(&block)
36
+ each_rubygems(&block)
37
+ self
38
+ end
39
+
40
+ private
41
+
42
+ #
43
+ def each_loadpath(options={}, &block)
44
+ $LOAD_PATH.uniq.each do |path|
45
+ path = File.expand_path(path)
46
+ traverse(path, &block)
47
+ end
48
+ end
49
+
50
+ #
51
+ def each_rubygems(options={}, &block)
52
+ return unless defined?(::Gem)
53
+ Gem::Specification.current_specs.each do |spec|
54
+ spec.require_paths.each do |require_path|
55
+ path = File.join(spec.full_gem_path, require_path)
56
+ traverse(path, &block)
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,21 @@
1
+ require 'loadable/mixin'
2
+
3
+ module Loadable
4
+
5
+ #
6
+ class RollLoader
7
+
8
+ include Loadable
9
+
10
+ #
11
+ def call(fname, options={})
12
+ end
13
+
14
+ #
15
+ def each(options={})
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,81 @@
1
+ # TODO: Maybe add support more refined selection of locations.
2
+
3
+ # TODO: Add new vendor locations.
4
+
5
+ require 'rbconfig'
6
+ require 'loadable/mixin'
7
+
8
+ module Loadable
9
+
10
+ # The Ruby Wedge allows standaard libray scripts to be loaded in a isolated
11
+ # fashion.
12
+ #
13
+ # require 'optparse', :from=>'ruby'
14
+ #
15
+ # The example would load optparse standard library regardless of Gem installed
16
+ # that might have a script by the same name.
17
+
18
+ class RubyLoader
19
+
20
+ include Loadable
21
+
22
+ # Notice that rubylibdir takes precendence.
23
+ LOCATIONS = ::RbConfig::CONFIG.values_at(
24
+ 'rubylibdir', 'archdir', 'sitelibdir', 'sitearchdir'
25
+ )
26
+
27
+ # Load the the first standard Ruby library matching +file+.
28
+ #
29
+ # Returns +nil+ if this loader is not applicable, which is
30
+ # determined by the use of `:from => 'ruby'` option.
31
+ #
32
+ def call(file, options={})
33
+ return unless apply?(file, options)
34
+
35
+ LOCATIONS.each do |site|
36
+ if path = lookup(site, file, options)
37
+ return super(path, options)
38
+ end
39
+ end
40
+
41
+ raise_load_error(file)
42
+ end
43
+
44
+ #
45
+ def each(options={}, &block)
46
+ LOCATIONS.each do |path|
47
+ #path = File.expand_path(path)
48
+ traverse(path, &block)
49
+ end
50
+ end
51
+
52
+ # The `#apply?` methods determines if the load wedge is applicable.
53
+ #
54
+ # Returns +true+ if this loader is not applicable, which is
55
+ # determined by the use of `:from => 'ruby'` option, otherwise `false`.
56
+ #
57
+ def apply?(fname, options={})
58
+ options[:from].to_s == 'ruby'
59
+ end
60
+
61
+
62
+ private
63
+
64
+ # Retun first matching file from Ruby's standard library locations.
65
+ #
66
+ # Returns +nil+ if this loader is not applicable.
67
+ #
68
+ def find(glob, options={})
69
+ return unless apply?(file, options)
70
+
71
+ LOCATIONS.each do |site|
72
+ path = lookup(site, file, options)
73
+ return path if path
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ # Copyright 2010 Thomas Sawyer, Rubyworks (BSD-2-Clause license)