autoloaded 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/History.md +4 -0
- data/README.md +411 -60
- data/autoloaded.gemspec +19 -14
- data/lib/autoloaded.rb +104 -91
- data/lib/autoloaded/autoloader.rb +260 -0
- data/lib/autoloaded/compatibility/refine_and_using.rb +2 -0
- data/lib/autoloaded/constant.rb +5 -2
- data/lib/autoloaded/deprecation.rb +50 -0
- data/lib/autoloaded/inflection.rb +71 -0
- data/lib/autoloaded/load_pathed_directory.rb +112 -0
- data/lib/autoloaded/refine.rb +7 -1
- data/lib/autoloaded/refine/string.rb +7 -0
- data/lib/autoloaded/refine/string/to_source_filename.rb +12 -0
- data/lib/autoloaded/specification.rb +97 -0
- data/lib/autoloaded/specifications.rb +66 -0
- data/lib/autoloaded/version.rb +3 -1
- data/lib/autoloaded/warning.rb +125 -0
- data/spec/autoloaded/autoloader_spec.rb +469 -0
- data/spec/autoloaded/constant_spec.rb +0 -2
- data/spec/autoloaded/deprecation_spec.rb +23 -0
- data/spec/autoloaded/inflection_spec.rb +30 -0
- data/spec/autoloaded/load_pathed_directory_spec.rb +120 -0
- data/spec/autoloaded/refine/string/to_source_filename_spec.rb +0 -2
- data/spec/autoloaded/specification_spec.rb +98 -0
- data/spec/autoloaded/specifications_spec.rb +191 -0
- data/spec/autoloaded/version_spec.rb +0 -2
- data/spec/autoloaded/warning_spec.rb +115 -0
- data/spec/autoloaded_macro_sharedspec.rb +24 -0
- data/spec/autoloaded_spec.rb +277 -95
- data/spec/fixtures/autoloaded_with_conventional_filename.rb +3 -1
- data/spec/fixtures/autoloaded_with_conventional_filename/nested.rb +12 -1
- data/spec/fixtures/autoloaded_with_conventional_filename/nested/doubly_nested.rb +9 -0
- data/spec/fixtures/autoloaded_with_unconventional_filename.rb +12 -0
- data/spec/fixtures/autoloaded_with_unconventional_filename/N-est-ed.rb +7 -0
- data/spec/fixtures/autoloaded_with_unconventional_filename/nest_ed.rb +1 -0
- data/spec/fixtures/autoloaded_with_unconventional_filename/old_school_autoload.rb +5 -0
- data/spec/fixtures/not_autoloaded/nested.rb +1 -0
- data/spec/fixtures/old_api/autoloaded_with_conventional_filename.rb +10 -0
- data/spec/fixtures/old_api/autoloaded_with_conventional_filename/N-est-ed.rb +1 -0
- data/spec/fixtures/old_api/autoloaded_with_conventional_filename/nest_ed.rb +1 -0
- data/spec/fixtures/old_api/autoloaded_with_conventional_filename/nested.rb +5 -0
- data/spec/fixtures/old_api/autoloaded_with_conventional_filename/old_school_autoload.rb +5 -0
- data/spec/fixtures/{autoloaded_with_conventional_filename_only.rb → old_api/autoloaded_with_conventional_filename_only.rb} +1 -1
- data/spec/fixtures/{autoloaded_with_conventional_filename_only → old_api/autoloaded_with_conventional_filename_only}/nested.rb +0 -0
- data/spec/fixtures/{autoloaded_with_conventional_filename_only → old_api/autoloaded_with_conventional_filename_only}/old_school_autoload.rb +0 -0
- data/spec/fixtures/{autoloaded_with_unconventional_filenames.rb → old_api/autoloaded_with_unconventional_filenames.rb} +1 -1
- data/spec/fixtures/{autoloaded_with_unconventional_filenames → old_api/autoloaded_with_unconventional_filenames}/N-est-ed.rb +0 -0
- data/spec/fixtures/{autoloaded_with_unconventional_filenames → old_api/autoloaded_with_unconventional_filenames}/nest_ed.rb +0 -0
- data/spec/fixtures/{autoloaded_with_unconventional_filenames → old_api/autoloaded_with_unconventional_filenames}/old_school_autoload.rb +0 -0
- data/spec/fixtures/old_api/not_autoloaded.rb +6 -0
- data/spec/fixtures/old_api/not_autoloaded/nested.rb +1 -0
- data/spec/fixtures/old_api/not_autoloaded/old_school_autoload.rb +5 -0
- data/spec/matchers.rb +4 -33
- data/spec/spec_helper.rb +2 -0
- metadata +95 -41
data/autoloaded.gemspec
CHANGED
@@ -3,27 +3,32 @@
|
|
3
3
|
lib = File.expand_path('../lib', __FILE__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
|
6
|
-
require 'autoloaded
|
6
|
+
require 'autoloaded'
|
7
7
|
|
8
8
|
Gem::Specification.new do |spec|
|
9
9
|
spec.name = 'autoloaded'
|
10
10
|
spec.version = Autoloaded::VERSION
|
11
11
|
spec.authors = ['Nils Jonsson']
|
12
12
|
spec.email = ['autoloaded@nilsjonsson.com']
|
13
|
-
spec.summary = <<-end_summary.gsub(/^\s+/, '').gsub("\n", ' ')
|
14
|
-
|
15
|
-
|
13
|
+
spec.summary = <<-end_summary.chomp.gsub(/^\s+/, '').gsub("\n", ' ')
|
14
|
+
Eliminates the drudgery of handcrafting a Ruby Core
|
15
|
+
library `autoload` statement for each Ruby source code
|
16
|
+
file in your project. It also avoids the limitations of
|
17
|
+
rigid convention-driven facilities such as those
|
18
|
+
provided by the ActiveSupport RubyGem.
|
16
19
|
end_summary
|
17
|
-
spec.description =
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
snake_case correspondence
|
26
|
-
and source files.
|
20
|
+
spec.description = <<-end_description.chomp.gsub(/^\s+/, '').gsub("\n", ' ')
|
21
|
+
If you like the ‘Module#autoload’ feature of the Ruby
|
22
|
+
Core library, you may have wished for Autoloaded. It
|
23
|
+
eliminates the drudgery of handcrafting an `autoload`
|
24
|
+
statement for each Ruby source code file in your
|
25
|
+
project. It also avoids the limitations of rigid
|
26
|
+
convention-driven facilities such as those provided by
|
27
|
+
the ActiveSupport RubyGem. Autoloaded assumes, but does
|
28
|
+
not enforce, `CamelCase`-to-`snake_case` correspondence
|
29
|
+
between the names of constants and source files. You can
|
30
|
+
combine conventions, even putting multiple autoloaded
|
31
|
+
constants in a single source file.
|
27
32
|
end_description
|
28
33
|
spec.homepage = 'http://njonsson.github.io/autoloaded'
|
29
34
|
spec.license = 'MIT'
|
data/lib/autoloaded.rb
CHANGED
@@ -1,100 +1,25 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# When used to extend a module, the _Autoloaded_ module dynamically loads
|
4
|
-
# constants into that module from source files contained in its filesystem path.
|
5
|
-
#
|
6
|
-
# @note You must extend a namespace with _Autoloaded_ from within the file in
|
7
|
-
# which the namespace is defined. This is because _Autoloaded_ utilizes the
|
8
|
-
# source file path of the namespace to establish which directory will be
|
9
|
-
# autoloaded. That path is discoverable only via the stack trace of `extend
|
10
|
-
# Autoloaded`.
|
11
|
-
#
|
12
|
-
# Suppose you have the following source files:
|
13
|
-
#
|
14
|
-
# * lib/
|
15
|
-
# * my_awesome_gem/
|
16
|
-
# * db/
|
17
|
-
# * mysql.rb
|
18
|
-
# * postgresql.rb
|
19
|
-
# * sql_server.rb
|
20
|
-
# * db.rb
|
21
|
-
# * my_awesome_gem.rb
|
22
|
-
#
|
23
|
-
# The following statements establish autoloading — one statement per namespace:
|
24
|
-
#
|
25
|
-
# # lib/my_awesome_gem.rb
|
26
|
-
# module MyAwesomeGem
|
27
|
-
#
|
28
|
-
# extend Autoloaded
|
29
|
-
#
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# # lib/my_awesome_gem/db.rb
|
33
|
-
# module MyAwesomeGem
|
34
|
-
#
|
35
|
-
# module DB
|
36
|
-
#
|
37
|
-
# extend Autoloaded
|
38
|
-
#
|
39
|
-
# end
|
40
|
-
#
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# Note that your preferred casing of constants is accommodated automatically.
|
44
|
-
#
|
45
|
-
# # Unlike Kernel#autoload and Module#autoload, Autoloaded is not clairvoyant about
|
46
|
-
# # what constants will be autoloaded.
|
47
|
-
# MyAwesomeGem::DB.constants # => []
|
48
|
-
#
|
49
|
-
# # But like Kernel#autoload and Module#autoload, Autoloaded does tell you which
|
50
|
-
# # source files will be autoloaded. (The difference is that it may return an array
|
51
|
-
# # of potential matches instead of just one filename.)
|
52
|
-
# MyAwesomeGem::DB.autoload? :MySQL # => 'db/mysql'
|
53
|
-
# MyAwesomeGem::DB.autoload? :PostgreSQL # => 'db/postgresql'
|
54
|
-
# MyAwesomeGem::DB.autoload? :SQLServer # => 'db/sql_server'
|
55
|
-
# MyAwesomeGem::DB.autoload? :Nonexistent # => nil
|
56
|
-
#
|
57
|
-
# MyAwesomeGem::DB::MySQL
|
58
|
-
# MyAwesomeGem::DB.constants # => [:MySQL]
|
59
|
-
# MyAwesomeGem::DB::PostgreSQL
|
60
|
-
# MyAwesomeGem::DB.constants # => [:MySQL, :PostgreSQL]
|
61
|
-
# MyAwesomeGem::DB::SQLServer
|
62
|
-
# MyAwesomeGem::DB.constants # => [:MySQL, :PostgreSQL, :SQLServer]
|
63
|
-
#
|
64
|
-
# _Autoloaded_ does not perform deep autoloading of nested namespaces and
|
65
|
-
# directories. This is by design.
|
66
|
-
#
|
67
|
-
# In the following example, autoloading of the _MyAwesomeGem_ namespace will not occur because the name of the source file in which the `extend` statement is invoked does not match the name of the namespace.
|
68
|
-
#
|
69
|
-
# # lib/my_awesome_gem.rb
|
70
|
-
# module MyAwesomeGem; end
|
71
|
-
#
|
72
|
-
# # lib/my_awesome_gem/db.rb
|
73
|
-
# module MyAwesomeGem
|
74
|
-
#
|
75
|
-
# # WRONG!
|
76
|
-
# extend Autoloaded
|
77
|
-
#
|
78
|
-
# module DB
|
79
|
-
#
|
80
|
-
# extend Autoloaded
|
81
|
-
#
|
82
|
-
# end
|
83
|
-
#
|
84
|
-
# end
|
85
|
-
#
|
86
|
-
# # some_other_file.rb
|
87
|
-
# require 'my_awesome_gem'
|
88
|
-
# MyAwesomeGem::DB # NameError is raised!
|
1
|
+
# Eliminates the drudgery of handcrafting a Ruby Core library +autoload+
|
2
|
+
# statement for each Ruby source code file in your project.
|
89
3
|
#
|
4
|
+
# @since 0.0.1
|
90
5
|
module Autoloaded
|
91
6
|
|
92
|
-
autoload :
|
93
|
-
autoload :
|
94
|
-
autoload :
|
7
|
+
autoload :Autoloader, 'autoloaded/autoloader'
|
8
|
+
autoload :Constant, 'autoloaded/constant'
|
9
|
+
autoload :Deprecation, 'autoloaded/deprecation'
|
10
|
+
autoload :Inflection, 'autoloaded/inflection'
|
11
|
+
autoload :LoadPathedDirectory, 'autoloaded/load_pathed_directory'
|
12
|
+
autoload :Refine, 'autoloaded/refine'
|
13
|
+
autoload :Specification, 'autoloaded/specification'
|
14
|
+
autoload :Specifications, 'autoloaded/specifications'
|
15
|
+
autoload :VERSION, 'autoloaded/version'
|
16
|
+
autoload :Warning, 'autoloaded/warning'
|
95
17
|
|
96
18
|
def self.extended(other_module)
|
97
19
|
caller_file_path = caller_locations.first.absolute_path
|
20
|
+
Deprecation.deprecate deprecated_usage: "extend #{name}",
|
21
|
+
sanctioned_usage: "#{name}.module { }",
|
22
|
+
source_filename: caller_file_path
|
98
23
|
dir_path = "#{::File.dirname caller_file_path}/#{::File.basename caller_file_path, '.rb'}"
|
99
24
|
other_module.module_eval <<-end_module_eval, __FILE__, __LINE__
|
100
25
|
def self.autoload?(symbol)
|
@@ -127,4 +52,92 @@ module Autoloaded
|
|
127
52
|
end_module_eval
|
128
53
|
end
|
129
54
|
|
55
|
+
# @!method self.module
|
56
|
+
# Autoloads constants that match files in the source directory.
|
57
|
+
#
|
58
|
+
# @yield [Autoloader] accepts options for regulating autoloading
|
59
|
+
#
|
60
|
+
# @return [Array of Array] the arguments passed to each +autoload+ statement
|
61
|
+
# made
|
62
|
+
#
|
63
|
+
# @raise [LocalJumpError] no block is given
|
64
|
+
#
|
65
|
+
# @since 1.3
|
66
|
+
#
|
67
|
+
# @example Autoloading a namespace
|
68
|
+
# # lib/my_awesome_gem/db.rb
|
69
|
+
#
|
70
|
+
# module MyAwesomeGem
|
71
|
+
#
|
72
|
+
# Autoloaded.module { }
|
73
|
+
#
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# @example Autoloading with optional specifications
|
77
|
+
# # lib/my_awesome_gem/db.rb
|
78
|
+
#
|
79
|
+
# module MyAwesomeGem
|
80
|
+
#
|
81
|
+
# class DB
|
82
|
+
#
|
83
|
+
# results = Autoloaded.class do |autoloaded|
|
84
|
+
# autoloaded.with :MySQL, :PostgreSQL, [:Access, :SQLServer] => 'MicroSoft'
|
85
|
+
# autoloaded.except 'SELF-DESTRUCT!'
|
86
|
+
# end
|
87
|
+
# STDOUT.puts results.inspect # See output below.
|
88
|
+
#
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# # [[:Access, 'my_awesome_gem/db/MicroSoft' ],
|
94
|
+
# # [:SQLServer, 'my_awesome_gem/db/MicroSoft' ],
|
95
|
+
# # [:MySQL, 'my_awesome_gem/db/mysql' ],
|
96
|
+
# # [:Oracle, 'my_awesome_gem/db/oracle' ],
|
97
|
+
# # [:PostgreSQL, 'my_awesome_gem/db/postgre_sql']]
|
98
|
+
def self.module(&block)
|
99
|
+
raise(::LocalJumpError, 'no block given (yield)') unless block
|
100
|
+
|
101
|
+
yield(autoloader = Autoloader.new(block.binding))
|
102
|
+
autoloader.autoload!
|
103
|
+
end
|
104
|
+
|
105
|
+
# Enables or disables warning messages depending on the specified _enabling_
|
106
|
+
# argument.
|
107
|
+
#
|
108
|
+
# @param [Object] enabling disables warnings if +nil+ or +false+
|
109
|
+
#
|
110
|
+
# @yield if a block is given, the value of _#warn?_ is reset after the block is
|
111
|
+
# called
|
112
|
+
#
|
113
|
+
# @return if a block is given, the result of calling the block
|
114
|
+
# @return [Autoloaded] if a block is not given, _Autoloaded_ (_Module_)
|
115
|
+
#
|
116
|
+
# @since 1.3
|
117
|
+
#
|
118
|
+
# @see .warn?
|
119
|
+
def self.warn(enabling, &block)
|
120
|
+
result = Warning.enable(enabling, &block)
|
121
|
+
|
122
|
+
block ? result : self
|
123
|
+
end
|
124
|
+
|
125
|
+
# Indicates whether warning messages are enabled or disabled.
|
126
|
+
#
|
127
|
+
# @return [true] if warning messages are enabled
|
128
|
+
# @return [false] if warning messages are disabled
|
129
|
+
#
|
130
|
+
# @since 1.3
|
131
|
+
#
|
132
|
+
# @see .warn
|
133
|
+
def self.warn?
|
134
|
+
Warning.enabled?
|
135
|
+
end
|
136
|
+
|
137
|
+
class << self
|
138
|
+
|
139
|
+
alias_method :class, :module
|
140
|
+
|
141
|
+
end
|
142
|
+
|
130
143
|
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
module Autoloaded
|
2
|
+
|
3
|
+
# Autoloads files in a source directory.
|
4
|
+
#
|
5
|
+
# @since 1.3
|
6
|
+
class Autoloader
|
7
|
+
|
8
|
+
# The source code context in which autoloading is to occur.
|
9
|
+
#
|
10
|
+
# @return [Binding] the source code context in which autoloading is to occur.
|
11
|
+
#
|
12
|
+
# @see #autoload!
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
attr_reader :host_binding
|
16
|
+
|
17
|
+
# Constructs a new _Autoloader_ with the specified _host_binding_.
|
18
|
+
#
|
19
|
+
# @param [Binding] host_binding a value for _#host_binding_
|
20
|
+
#
|
21
|
+
# @raise [ArgumentError] _host_binding_ is +nil+
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
def initialize(host_binding)
|
25
|
+
raise(::ArgumentError, "can't be nil") if host_binding.nil?
|
26
|
+
|
27
|
+
@host_binding = host_binding
|
28
|
+
@specifications = Specifications.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Issues +autoload+ statements for source files found in _#from_. The
|
32
|
+
# constants are renamed by _#with_ and _#only_. The source files are filtered
|
33
|
+
# by _#except_ and _#only_.
|
34
|
+
#
|
35
|
+
# @return [Array of Array] the arguments passed to each +autoload+ statement
|
36
|
+
# made
|
37
|
+
#
|
38
|
+
# @see http://ruby-doc.org/core/Module.html#method-i-autoload Module#autoload
|
39
|
+
# @see http://ruby-doc.org/core/Kernel.html#method-i-autoload Kernel#autoload
|
40
|
+
# @see #from
|
41
|
+
# @see #except
|
42
|
+
# @see #only
|
43
|
+
# @see #with
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
def autoload!
|
47
|
+
result = []
|
48
|
+
from_load_pathed_directory.each_source_filename do |source_filename|
|
49
|
+
source_basename = ::File.basename(source_filename)
|
50
|
+
next if specifications.except.any? { |spec| spec.match source_basename }
|
51
|
+
|
52
|
+
unless specifications.only.empty? ||
|
53
|
+
specifications.only.any? { |spec| spec.match source_basename }
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
first_match = (specifications.with + specifications.only).inject(nil) do |match, spec|
|
58
|
+
match || spec.match(source_basename)
|
59
|
+
end
|
60
|
+
constant_names = Array(first_match ||
|
61
|
+
Inflection.to_constant_name(source_basename))
|
62
|
+
existing_source_filenames = constant_names.collect do |const|
|
63
|
+
existing_autoload? const
|
64
|
+
end
|
65
|
+
if existing_source_filenames.all? { |file| file == source_filename }
|
66
|
+
next
|
67
|
+
end
|
68
|
+
|
69
|
+
existing_source_filenames.zip(constant_names).each do |file, const|
|
70
|
+
if file
|
71
|
+
Warning.changing_autoload constant_name: constant_full_name(const),
|
72
|
+
old_source_filename: file,
|
73
|
+
new_source_filename: source_filename,
|
74
|
+
host_source_location: host_source_location
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if existing_source_filenames.compact.empty?
|
79
|
+
constant_names.each do |const|
|
80
|
+
next unless existing_constant?(const)
|
81
|
+
|
82
|
+
Warning.existing_constant constant_name: constant_full_name(const),
|
83
|
+
source_filename: source_filename,
|
84
|
+
host_source_location: host_source_location
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
constant_names.each do |const|
|
89
|
+
establish_autoload const, source_filename
|
90
|
+
result << [const, source_filename]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
# @!method except(*arguments)
|
97
|
+
# Specifies constants and/or source files not to be autoloaded. _Symbol_
|
98
|
+
# arguments signify the names of constants and _String_ arguments signify
|
99
|
+
# the names of source files. You can specify _#except_ multiple times, and
|
100
|
+
# its effects are cumulative.
|
101
|
+
#
|
102
|
+
# Source file names specified are relative to _#from_.
|
103
|
+
#
|
104
|
+
# Valid arguments include:
|
105
|
+
#
|
106
|
+
# * _Symbol_ values
|
107
|
+
# * _String_ values
|
108
|
+
# * _Array_ values comprising _Symbol_ and/or _String_ values
|
109
|
+
# * _Hash_ values comprising _Symbol_, _String_, and/or _Array_ values
|
110
|
+
# described above
|
111
|
+
# * Any combination of the options described above
|
112
|
+
#
|
113
|
+
# @return [Autoloader] the _Autoloader_
|
114
|
+
#
|
115
|
+
# @raise [RuntimeError] _#only_ already has a specification
|
116
|
+
#
|
117
|
+
# @see #autoload!
|
118
|
+
# @see #from
|
119
|
+
# @see #only
|
120
|
+
#
|
121
|
+
# @!method only(*arguments)
|
122
|
+
# Specifies constants and/or source files to be autoloaded exclusively.
|
123
|
+
# _Symbol_ arguments signify the names of constants and _String_ arguments
|
124
|
+
# signify the names of source files. You can specify _#only_ multiple
|
125
|
+
# times, and its effects are cumulative.
|
126
|
+
#
|
127
|
+
# Source file names specified are relative to _#from_.
|
128
|
+
#
|
129
|
+
# Valid arguments include:
|
130
|
+
#
|
131
|
+
# * _Symbol_ values
|
132
|
+
# * _String_ values
|
133
|
+
# * _Array_ values comprising _Symbol_ and/or _String_ values
|
134
|
+
# * _Hash_ values comprising _Symbol_, _String_, and/or _Array_ values
|
135
|
+
# described above, which will autoload specified constants from their
|
136
|
+
# associated source files
|
137
|
+
# * Any combination of the options described above
|
138
|
+
#
|
139
|
+
# @return [Autoloader] the _Autoloader_
|
140
|
+
#
|
141
|
+
# @raise [RuntimeError] _#except_ already has a specification
|
142
|
+
#
|
143
|
+
# @see #autoload!
|
144
|
+
# @see #from
|
145
|
+
# @see #except
|
146
|
+
#
|
147
|
+
# @!method with(*arguments)
|
148
|
+
# Specifies constants and/or source files to be autoloaded whose names may
|
149
|
+
# have unpredictable spellings, stylization, or organization. _Symbol_
|
150
|
+
# arguments signify the names of constants and _String_ arguments signify
|
151
|
+
# the names of source files. You can specify _#with_ multiple times, and
|
152
|
+
# its effects are cumulative.
|
153
|
+
#
|
154
|
+
# Source file names specified are relative to _#from_.
|
155
|
+
#
|
156
|
+
# Valid arguments include:
|
157
|
+
#
|
158
|
+
# * _Symbol_ values
|
159
|
+
# * _Array_ values comprising _Symbol_ values
|
160
|
+
# * _Hash_ values comprising _Symbol_, _String_, and/or _Array_ values
|
161
|
+
# described above, which will autoload specified constants from their
|
162
|
+
# associated source files
|
163
|
+
# * Any combination of the options described above
|
164
|
+
#
|
165
|
+
# @return [Autoloader] the _Autoloader_
|
166
|
+
#
|
167
|
+
# @see #autoload!
|
168
|
+
# @see #from
|
169
|
+
%i(except only with).each do |attr|
|
170
|
+
define_method attr do |*arguments|
|
171
|
+
attr_specs = specifications.send(attr)
|
172
|
+
if arguments.empty?
|
173
|
+
return attr_specs.collect(&:value)
|
174
|
+
end
|
175
|
+
|
176
|
+
attr_specs << Specification.new(*arguments)
|
177
|
+
begin
|
178
|
+
specifications.validate! attr
|
179
|
+
rescue
|
180
|
+
attr_specs.pop
|
181
|
+
raise
|
182
|
+
end
|
183
|
+
|
184
|
+
self
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# The directory from which source files are autoloaded.
|
189
|
+
#
|
190
|
+
# Defaults to the directory corresponding to the +__FILE__+ of
|
191
|
+
# _#host_binding_. For example, if <code>eval('__FILE__',
|
192
|
+
# host_binding)</code> evaluates to +'/absolute/path/to/my_awesome_gem.rb'+,
|
193
|
+
# then the default value of _#from_ is +'/absolute/path/to/my_awesome_gem'+.
|
194
|
+
#
|
195
|
+
# @param [String] value a source directory path; optional
|
196
|
+
#
|
197
|
+
# @return [String] if _value_ is +nil+, the source directory
|
198
|
+
# @return [Autoloader] if _value_ is not +nil+, the _Autoloader_
|
199
|
+
#
|
200
|
+
# @raise [ArgumentError] _value_ is a relative path
|
201
|
+
#
|
202
|
+
# @see #autoload!
|
203
|
+
# @see #host_binding
|
204
|
+
def from(value=nil)
|
205
|
+
return((@from && @from.path) || default_from) if value.nil?
|
206
|
+
|
207
|
+
# Validate value.
|
208
|
+
@from = LoadPathedDirectory.new(value)
|
209
|
+
|
210
|
+
self
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
attr_reader :specifications
|
216
|
+
|
217
|
+
def constant_full_name(constant_name)
|
218
|
+
"#{host_eval 'name rescue nil'}::#{constant_name}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def default_from
|
222
|
+
filename = host_source_filename
|
223
|
+
dirname = ::File.dirname(filename)
|
224
|
+
basename = ::File.basename(filename, ::File.extname(filename))
|
225
|
+
::File.join dirname, basename
|
226
|
+
end
|
227
|
+
|
228
|
+
def establish_autoload(constant_name, source_filename)
|
229
|
+
host_eval "autoload #{constant_name.to_sym.inspect}, #{source_filename.inspect}"
|
230
|
+
end
|
231
|
+
|
232
|
+
def existing_autoload?(constant_name)
|
233
|
+
host_eval "autoload? #{constant_name.to_sym.inspect}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def existing_constant?(constant_name)
|
237
|
+
host_eval "constants.include? #{constant_name.to_sym.inspect}"
|
238
|
+
end
|
239
|
+
|
240
|
+
def from_load_pathed_directory
|
241
|
+
@from || LoadPathedDirectory.new(default_from)
|
242
|
+
end
|
243
|
+
|
244
|
+
def host_eval(statement)
|
245
|
+
# TODO: Why does adding the third and fourth arguments break Kernel#eval ?
|
246
|
+
# ::Kernel.eval statement, host_binding, __FILE__, __LINE__
|
247
|
+
::Kernel.eval statement, host_binding
|
248
|
+
end
|
249
|
+
|
250
|
+
def host_source_filename
|
251
|
+
host_eval '::File.expand_path __FILE__'
|
252
|
+
end
|
253
|
+
|
254
|
+
def host_source_location
|
255
|
+
host_eval('[__FILE__, __LINE__]').collect(&:to_s).join ':'
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|