class_loader 0.4.15 → 3.0.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/Rakefile +2 -1
- data/lib/class_loader/class_loader.rb +99 -207
- data/lib/class_loader/support.rb +0 -11
- data/lib/class_loader/watcher.rb +44 -0
- data/lib/class_loader.rb +6 -19
- data/readme.md +27 -28
- data/spec/class_loader_spec/anonymous_class/some_namespace/some_class.rb +2 -0
- data/spec/class_loader_spec/another_namespace/some_namespace/namespace_a.rb +2 -0
- data/spec/class_loader_spec/another_namespace/{AnotherNamespace/NamespaceB.rb → some_namespace/namespace_b.rb} +1 -1
- data/spec/class_loader_spec/another_namespace/some_namespace.rb +2 -0
- data/spec/class_loader_spec/basics/another_class.rb +2 -0
- data/spec/class_loader_spec/{basic/BasicSpec/SomeNamespace/SomeClass.rb → basics/some_class.rb} +0 -0
- data/spec/class_loader_spec/basics/some_namespace/some_class.rb +2 -0
- data/spec/class_loader_spec/basics/some_namespace.rb +2 -0
- data/spec/class_loader_spec/infinity_loop/some_class.rb +2 -0
- data/spec/class_loader_spec/namespace_resolving/some_namespace/some_class.rb +2 -0
- data/spec/class_loader_spec/namespace_resolving/some_namespace.rb +2 -0
- data/spec/class_loader_spec/{namespace_type_resolving/NamespaceIsAlreadyDefinedAsClass/SomeClass.rb → only_once/some_class.rb} +0 -0
- data/spec/class_loader_spec/{namespace_type_resolving/NamespaceTypeResolving/SomeClass.rb → preloading/some_class.rb} +0 -0
- data/spec/class_loader_spec/preloading/some_namespace/another_class.rb +2 -0
- data/spec/class_loader_spec/preloading/some_namespace.rb +2 -0
- data/spec/class_loader_spec/reloading/some_class.rb +3 -0
- data/spec/class_loader_spec.rb +96 -78
- metadata +20 -27
- data/lib/class_loader/chained_adapter.rb +0 -48
- data/lib/class_loader/file_system_adapter/camel_case_translator.rb +0 -16
- data/lib/class_loader/file_system_adapter/underscored_translator.rb +0 -16
- data/lib/class_loader/file_system_adapter.rb +0 -145
- data/lib/class_loader/spec.rb +0 -17
- data/lib/class_loader/tasks.rb +0 -8
- data/spec/class_loader_spec/anonymous_class/AnonymousSpec/ClassInsideOfAnonymousClass.rb +0 -2
- data/spec/class_loader_spec/another_namespace/AnotherNamespace/NamespaceA.rb +0 -2
- data/spec/class_loader_spec/basic/BasicSpec.rb +0 -2
- data/spec/class_loader_spec/infinity_loop/InfinityLoop.rb +0 -2
- data/spec/class_loader_spec/namespace_type_resolving/NamespaceTypeResolving.rb +0 -2
- data/spec/class_loader_spec/only_once/OnlyOnceSpec.rb +0 -2
- data/spec/class_loader_spec/preloading/PreloadingSpec.rb +0 -2
- data/spec/file_system_adapter_spec/common/SomeNamespace/SomeClass.rb +0 -1
- data/spec/file_system_adapter_spec/multiple_class_paths/path_a/ClassInPathA.rb +0 -2
- data/spec/file_system_adapter_spec/multiple_class_paths/path_b/ClassInPathB.rb +0 -2
- data/spec/file_system_adapter_spec/shouldnt_mess/CamelCaseClass.rb +0 -0
- data/spec/file_system_adapter_spec/shouldnt_mess/underscored_class.rb +0 -0
- data/spec/file_system_adapter_spec.rb +0 -112
- data/spec/translators_spec.rb +0 -35
data/Rakefile
CHANGED
@@ -3,7 +3,8 @@ require 'rake_ext'
|
|
3
3
|
project(
|
4
4
|
name: "class_loader",
|
5
5
|
gem: true,
|
6
|
-
summary: "Automatically
|
6
|
+
summary: "Automatically find, load and reload classes",
|
7
|
+
version: '3.0.0',
|
7
8
|
|
8
9
|
author: "Alexey Petrushin",
|
9
10
|
homepage: "http://github.com/alexeypetrushin/class_loader"
|
@@ -1,210 +1,123 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
warn 'ClassLoader: working in slow, debug mode with explicit tmp file generation!' if defined?(CLASS_LOADER_GENERATE_TMP_FILES)
|
1
|
+
require 'class_loader/support'
|
4
2
|
|
5
3
|
module ClassLoader
|
6
|
-
@
|
7
|
-
SYNC = Monitor.new
|
8
|
-
|
4
|
+
@loaded_classes = {}
|
9
5
|
class << self
|
10
|
-
|
11
|
-
|
12
|
-
def load_class namespace, const, reload = false
|
13
|
-
SYNC.synchronize do
|
14
|
-
original_namespace = namespace
|
15
|
-
namespace = nil if namespace == Object or namespace == Module
|
16
|
-
target_namespace = namespace
|
17
|
-
|
18
|
-
# Name hack (for anonymous classes)
|
19
|
-
|
20
|
-
namespace = eval "#{name_hack(namespace)}" if namespace
|
21
|
-
|
22
|
-
class_name = namespace ? "#{namespace.name}::#{const}" : const
|
23
|
-
simple_also_tried = false
|
24
|
-
begin
|
25
|
-
simple_also_tried = (namespace == nil)
|
26
|
-
|
27
|
-
if adapter.exist? class_name
|
28
|
-
if loaded_classes.include?(class_name) and !reload
|
29
|
-
raise_without_self NameError, "something wrong with '#{const}' referenced from '#{original_namespace}' scope!"
|
30
|
-
end
|
6
|
+
attr_reader :loaded_classes
|
31
7
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
8
|
+
# Hierarchically searching for class file, according to modules hierarchy.
|
9
|
+
#
|
10
|
+
# For example, let's suppose that `C` class defined in '/lib/a/c.rb' file, and we referencing it
|
11
|
+
# in the `A::B` namespace, like this - `A::B::C`, the following files will be checked:
|
12
|
+
#
|
13
|
+
# - '/lib/a/b/c.rb' - there's nothing, moving up in hierarchy.
|
14
|
+
# - '/lib/a/c.rb' - got and load it.
|
15
|
+
def load namespace, const
|
16
|
+
original_namespace = namespace
|
17
|
+
namespace = nil if namespace == Object or namespace == Module
|
18
|
+
target_namespace = namespace
|
19
|
+
|
20
|
+
# Need this hack to work with anonymous classes.
|
21
|
+
namespace = eval "#{name_hack(namespace)}" if namespace
|
22
|
+
|
23
|
+
# Hierarchically searching for class name.
|
24
|
+
begin
|
25
|
+
class_name = namespace ? "#{namespace.name}::#{const}" : const.to_s
|
26
|
+
class_file_name = get_file_name class_name
|
27
|
+
|
28
|
+
# Trying to load class file, if its exist.
|
29
|
+
loaded = begin
|
30
|
+
require class_file_name
|
31
|
+
true
|
32
|
+
rescue LoadError => e
|
33
|
+
# Not the best way - hardcoding error messages, but it's the fastest way
|
34
|
+
# to check existence of file & load it.
|
35
|
+
raise e unless e.message =~ /no such file.*#{Regexp.escape(class_file_name)}/
|
36
|
+
false
|
37
|
+
end
|
42
38
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
39
|
+
if loaded
|
40
|
+
# Checking that class hasn't been loaded previously, sometimes it may be caused by
|
41
|
+
# weird class definition code.
|
42
|
+
if loaded_classes.include? class_name
|
43
|
+
raise_without_self NameError, \
|
44
|
+
"something wrong with '#{const}' referenced from '#{original_namespace}' scope!"
|
49
45
|
end
|
50
|
-
end until simple_also_tried
|
51
|
-
|
52
|
-
return false
|
53
|
-
end
|
54
|
-
end
|
55
46
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
47
|
+
# Checking that class defined in correct namespace, not the another one.
|
48
|
+
unless namespace ? namespace.const_defined?(const, false) : Object.const_defined?(const, false)
|
49
|
+
raise_without_self NameError, \
|
50
|
+
"class name '#{class_name}' doesn't correspond to file name '#{class_file_name}'!"
|
51
|
+
end
|
61
52
|
|
62
|
-
|
63
|
-
|
64
|
-
# class_container.send :remove_const, name if class_container.const_defined? name
|
53
|
+
# Getting the class itself.
|
54
|
+
klass = namespace ? namespace.const_get(const, false) : Object.const_get(const, false)
|
65
55
|
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
56
|
+
loaded_classes[class_name] = klass
|
69
57
|
|
70
|
-
|
71
|
-
nesting = []
|
72
|
-
if namespace
|
73
|
-
current_scope = ""
|
74
|
-
namespace.name.split("::").each do |level|
|
75
|
-
current_scope += "::#{level}"
|
76
|
-
type = eval current_scope, TOPLEVEL_BINDING, __FILE__, __LINE__
|
77
|
-
nesting << [level, (type.class == Module ? "module" : "class")]
|
58
|
+
return klass
|
78
59
|
end
|
79
|
-
end
|
80
|
-
begining = nesting.collect{|l, t| "#{t} #{l};"}.join(' ')
|
81
|
-
ending = nesting.collect{"end"}.join('; ')
|
82
|
-
return "#{begining}#{script} \n#{ending}"
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
#
|
87
|
-
# Utilities
|
88
|
-
#
|
89
|
-
def autoload_path path, watch = false, start_watch_thread = true
|
90
|
-
hook!
|
91
|
-
start_watching! if watch and start_watch_thread
|
92
|
-
adapter.add_path path, watch
|
93
|
-
end
|
94
|
-
def autoload_dir *a, &b
|
95
|
-
warn 'ClassLoader: the :autoload_path method is deprecated, please use :autoload_path'
|
96
|
-
autoload_path *a, &b
|
97
|
-
end
|
98
|
-
def delete_path path
|
99
|
-
adapter.delete_path path
|
100
|
-
end
|
101
60
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
attr_accessor :observers
|
109
|
-
def add_observer █ observers << block end
|
110
|
-
def notify_observers o
|
111
|
-
observers.each{|obs| obs.call o}
|
112
|
-
end
|
113
|
-
|
114
|
-
def hook!
|
115
|
-
return if @hooked
|
116
|
-
|
117
|
-
::Module.class_eval do
|
118
|
-
alias_method :const_missing_without_class_loader, :const_missing
|
119
|
-
protected :const_missing_without_class_loader
|
120
|
-
def const_missing const
|
121
|
-
if klass = ClassLoader.load_class(self, const.to_s)
|
122
|
-
klass
|
123
|
-
else
|
124
|
-
const_missing_without_class_loader const
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
@hooked = true
|
129
|
-
end
|
61
|
+
# Moving to higher namespace.
|
62
|
+
global_also_tried = namespace == nil
|
63
|
+
namespace = Module.namespace_for namespace.name if namespace
|
64
|
+
end until global_also_tried
|
130
65
|
|
131
|
-
|
132
|
-
def adapter
|
133
|
-
@adapter ||= default_adapter
|
66
|
+
return nil
|
134
67
|
end
|
135
68
|
|
136
|
-
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
# reloading doesn works in debug mode, because we by ourself are generating tmp source files
|
143
|
-
return if defined?(CLASS_LOADER_GENERATE_TMP_FILES)
|
144
|
-
|
145
|
-
unless @watching_thread
|
146
|
-
@watching_thread = Thread.new do
|
147
|
-
while true
|
148
|
-
sleep(watch_interval || 2)
|
149
|
-
adapter.each_changed_class do |class_name|
|
150
|
-
puts "reloading #{class_name}"
|
151
|
-
reload_class class_name
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
69
|
+
# Dynamic class loading is not thread safe (known Ruby bug), to workaround it
|
70
|
+
# You can forcefully preload all Your classes in production when Your app starts.
|
71
|
+
def preload path
|
72
|
+
Dir.glob("#{path}/**/*.rb").each do |class_path|
|
73
|
+
class_file_name = class_path.sub("#{path}/", '').sub(/\.rb$/, '')
|
74
|
+
require class_file_name
|
155
75
|
end
|
156
76
|
end
|
157
77
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
78
|
+
# Watch and reload files.
|
79
|
+
def watch path
|
80
|
+
watcher.paths << path
|
81
|
+
watcher.start
|
163
82
|
end
|
164
83
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
168
|
-
end
|
84
|
+
def watcher
|
85
|
+
require 'class_loader/watcher'
|
86
|
+
@watcher ||= ClassLoader::Watcher.new
|
169
87
|
end
|
170
88
|
|
171
|
-
|
172
89
|
protected
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
adapter
|
178
|
-
end
|
179
|
-
|
180
|
-
def load class_name, const
|
181
|
-
script = adapter.read class_name
|
182
|
-
script = wrap_inside_namespace Module.namespace_for(class_name), script
|
183
|
-
file_path = adapter.to_file_path(class_name)
|
184
|
-
|
185
|
-
# sometimes we need to generate file explicitly
|
186
|
-
# for example evaluated code will not be shown in Ruby coverage tool
|
187
|
-
unless defined?(CLASS_LOADER_GENERATE_TMP_FILES)
|
188
|
-
eval script, TOPLEVEL_BINDING, file_path
|
189
|
-
else
|
190
|
-
if file_path =~ /\.rb$/
|
191
|
-
tmp_file_path = file_path.sub /\.rb$/, '.cltmp.rb'
|
192
|
-
begin
|
193
|
-
File.open(tmp_file_path, "w"){|f| f.write(script)}
|
194
|
-
Kernel.load tmp_file_path
|
195
|
-
ensure
|
196
|
-
File.delete tmp_file_path if defined?(CLASS_LOADER_CLEAN) and ::File.exist?(tmp_file_path)
|
197
|
-
end
|
198
|
-
else
|
199
|
-
eval script, TOPLEVEL_BINDING, file_path
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def raise_without_self exception, message
|
205
|
-
raise exception, message, caller.select{|path| path !~ /\/lib\/class_loader\// and path !~ /monitor\.rb/}
|
90
|
+
# Use this method to define class name to file name mapping, by default it uses underscored paths,
|
91
|
+
# but You can override this method to use camel case for example.
|
92
|
+
def get_file_name class_name
|
93
|
+
class_name.underscore
|
206
94
|
end
|
207
95
|
|
96
|
+
# Module.name doesn't works correctly for Anonymous classes.
|
97
|
+
# try to execute this code:
|
98
|
+
#
|
99
|
+
# class Module
|
100
|
+
# def const_missing const
|
101
|
+
# p self.to_s
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# class A
|
106
|
+
# class << self
|
107
|
+
# def a
|
108
|
+
# p self
|
109
|
+
# MissingConst
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# A.a
|
115
|
+
#
|
116
|
+
# the output will be:
|
117
|
+
#
|
118
|
+
# A
|
119
|
+
# "#<Class:A>"
|
120
|
+
#
|
208
121
|
def name_hack namespace
|
209
122
|
if namespace
|
210
123
|
result = namespace.to_s.gsub("#<Class:", "").gsub(">", "")
|
@@ -212,31 +125,10 @@ module ClassLoader
|
|
212
125
|
else
|
213
126
|
""
|
214
127
|
end
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
#class Module
|
220
|
-
# def const_missing const
|
221
|
-
# p self.to_s
|
222
|
-
# end
|
223
|
-
#end
|
224
|
-
#
|
225
|
-
#class A
|
226
|
-
# class << self
|
227
|
-
# def a
|
228
|
-
# p self
|
229
|
-
# MissingConst
|
230
|
-
# end
|
231
|
-
# end
|
232
|
-
#end
|
233
|
-
#
|
234
|
-
#A.a
|
235
|
-
#
|
236
|
-
# the output will be:
|
237
|
-
# A
|
238
|
-
# "#<Class:A>"
|
239
|
-
#
|
128
|
+
end
|
129
|
+
|
130
|
+
def raise_without_self exception, message
|
131
|
+
raise exception, message, caller.select{|path| path !~ /\/lib\/class_loader\//}
|
240
132
|
end
|
241
133
|
end
|
242
134
|
end
|
data/lib/class_loader/support.rb
CHANGED
@@ -10,21 +10,10 @@ class String
|
|
10
10
|
word
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
14
|
-
unless method_defined? :camelize
|
15
|
-
def camelize first_letter_in_uppercase = true
|
16
|
-
if first_letter_in_uppercase
|
17
|
-
gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
18
|
-
else
|
19
|
-
self[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
13
|
end
|
24
14
|
|
25
15
|
class Module
|
26
16
|
unless respond_to? :namespace_for
|
27
|
-
# TODO3 cache it?
|
28
17
|
def self.namespace_for class_name
|
29
18
|
list = class_name.split("::")
|
30
19
|
if list.size > 1
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class ClassLoader::Watcher
|
2
|
+
attr_accessor :paths, :interval
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@paths, @files = [], {}
|
6
|
+
@interval = 2
|
7
|
+
end
|
8
|
+
|
9
|
+
def stop
|
10
|
+
return unless thread
|
11
|
+
thread.kill
|
12
|
+
@thread = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
return if thread
|
17
|
+
@thread = Thread.new do
|
18
|
+
while true
|
19
|
+
sleep interval
|
20
|
+
check
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def check
|
26
|
+
paths.each do |path|
|
27
|
+
Dir.glob("#{path}/**/*.rb").each do |class_path|
|
28
|
+
updated_at = File.mtime class_path
|
29
|
+
if last_updated_at = files[class_path]
|
30
|
+
if last_updated_at < updated_at
|
31
|
+
class_file_name = class_path.sub "#{path}/", ''
|
32
|
+
warn "reloading #{class_file_name}"
|
33
|
+
load class_file_name
|
34
|
+
end
|
35
|
+
else
|
36
|
+
files[class_path] = updated_at
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
attr_reader :files, :thread
|
44
|
+
end
|
data/lib/class_loader.rb
CHANGED
@@ -1,21 +1,8 @@
|
|
1
|
-
|
1
|
+
autoload :ClassLoader, 'class_loader/class_loader'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
file_system_adapter/camel_case_translator
|
9
|
-
file_system_adapter/underscored_translator
|
10
|
-
file_system_adapter
|
11
|
-
chained_adapter
|
12
|
-
class_loader
|
13
|
-
).each{|f| require "class_loader/#{f}"}
|
14
|
-
|
15
|
-
def autoload_path *args, &block
|
16
|
-
ClassLoader.autoload_path *args, &block
|
17
|
-
end
|
18
|
-
def autoload_dir *a, &b
|
19
|
-
warn 'ClassLoader: the :autoload_dir method is deprecated, please use :autoload_path'
|
20
|
-
autoload_path *a, &b
|
3
|
+
class Module
|
4
|
+
alias_method :const_missing_without_autoload, :const_missing
|
5
|
+
def const_missing const
|
6
|
+
ClassLoader.load(self, const) || const_missing_without_autoload(const)
|
7
|
+
end
|
21
8
|
end
|
data/readme.md
CHANGED
@@ -1,40 +1,39 @@
|
|
1
|
-
# Automatically
|
1
|
+
# Automatically find, load and reload classes
|
2
2
|
|
3
|
-
|
4
|
-
There's only one method - :autoload_path, kind of turbocharged :autoload, it understands namespaces, figure out dependencies and can watch and reload changed files.
|
3
|
+
Suppose there's following directory structure:
|
5
4
|
|
6
|
-
|
5
|
+
/lib
|
6
|
+
/some_class.rb # class SomeClass; end
|
7
|
+
/some_namespace
|
8
|
+
/another_class.rb # class SomeNamespace:AnotherClass; end
|
9
|
+
/some_namespace.rb # module SomeNamespace; end
|
7
10
|
|
8
|
-
|
9
|
-
/lib
|
10
|
-
/animals
|
11
|
-
/dog.rb
|
12
|
-
/zoo.rb
|
11
|
+
All these classes will be loaded automatically, on demand:
|
13
12
|
|
14
|
-
|
13
|
+
``` ruby
|
14
|
+
require 'class_loader'
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
SomeClass
|
17
|
+
SomeNamespace::AnotherClass
|
18
|
+
```
|
18
19
|
|
19
|
-
|
20
|
+
No need for require:
|
20
21
|
|
21
|
-
|
22
|
+
``` ruby
|
23
|
+
require 'some_class'
|
24
|
+
require 'some_namespace'
|
25
|
+
require 'some_namespace/another_class'
|
26
|
+
```
|
22
27
|
|
23
|
-
|
24
|
-
# require 'app'
|
28
|
+
or autoload:
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
Also you can use CamelCase notation or provide Your own class_name/file_path translator, or even provide Your own custom resource adapter that for example will look for classes on the net and download them.
|
34
|
-
|
35
|
-
There's currently a known bug in Ruby 1.8.x - class loading isn't thread safe, so in production you should preload all Your classes
|
36
|
-
|
37
|
-
ClassLoader.preload! if app_in_production?
|
30
|
+
``` ruby
|
31
|
+
autoload :SomeClass, 'some_class'
|
32
|
+
autoload :SomeNamespace, 'some_namespace'
|
33
|
+
module SomeNamespace
|
34
|
+
autoload :AnotherClass, 'some_namespace/another_class'
|
35
|
+
end
|
36
|
+
```
|
38
37
|
|
39
38
|
## Installation
|
40
39
|
|