class-loader 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/Rakefile +60 -0
  2. data/lib/class_loader.rb +12 -0
  3. data/lib/class_loader/chained_adapter.rb +44 -0
  4. data/lib/class_loader/class_loader.rb +219 -0
  5. data/lib/class_loader/file_system_adapter.rb +130 -0
  6. data/lib/class_loader/file_system_adapter/camel_case_translator.rb +11 -0
  7. data/lib/class_loader/file_system_adapter/underscored_translator.rb +11 -0
  8. data/lib/class_loader/support.rb +37 -0
  9. data/readme.md +45 -0
  10. data/spec/class_loader_spec.rb +139 -0
  11. data/spec/class_loader_spec_data/anonymous_class/AnonymousSpec/ClassInsideOfAnonymousClass.rb +2 -0
  12. data/spec/class_loader_spec_data/another_namespace/AnotherNamespace/NamespaceA.rb +2 -0
  13. data/spec/class_loader_spec_data/another_namespace/AnotherNamespace/NamespaceB.rb +5 -0
  14. data/spec/class_loader_spec_data/basic/BasicSpec.rb +2 -0
  15. data/spec/class_loader_spec_data/basic/BasicSpec/SomeNamespace/SomeClass.rb +2 -0
  16. data/spec/class_loader_spec_data/infinity_loop/InfinityLoop.rb +2 -0
  17. data/spec/class_loader_spec_data/namespace_type_resolving/NamespaceIsAlreadyDefinedAsClass/SomeClass.rb +2 -0
  18. data/spec/class_loader_spec_data/namespace_type_resolving/NamespaceTypeResolving.rb +2 -0
  19. data/spec/class_loader_spec_data/namespace_type_resolving/NamespaceTypeResolving/SomeClass.rb +2 -0
  20. data/spec/class_loader_spec_data/only_once/OnlyOnceSpec.rb +2 -0
  21. data/spec/class_loader_spec_data/preloading/PreloadingSpec.rb +2 -0
  22. data/spec/class_loader_spec_data/underscored/underscored_namespace/underscored_class.rb +2 -0
  23. data/spec/class_loader_spec_data/unload_old_class/UnloadOldClass.rb +2 -0
  24. data/spec/file_system_adapter_spec.rb +91 -0
  25. data/spec/file_system_adapter_spec_data/common/SomeNamespace/SomeClass.rb +1 -0
  26. data/spec/file_system_adapter_spec_data/multiple_class_paths/path_a/ClassInPathA.rb +2 -0
  27. data/spec/file_system_adapter_spec_data/multiple_class_paths/path_b/ClassInPathB.rb +2 -0
  28. data/spec/helper.rb +22 -0
  29. data/spec/spec.opts +2 -0
  30. metadata +95 -0
@@ -0,0 +1,60 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ Dir.chdir File.dirname(__FILE__)
5
+
6
+ # Specs
7
+ task :default => :spec
8
+
9
+ Spec::Rake::SpecTask.new('spec') do |t|
10
+ t.spec_files = FileList["spec/**/*_spec.rb"].select{|f| f !~ /\/_/}
11
+ t.libs = ['lib'].collect{|f| "#{File.dirname __FILE__}/#{f}"}
12
+ # t.spec_opts = ["--backtrace"]
13
+ end
14
+
15
+ # Gem
16
+ require 'rake/clean'
17
+ require 'rake/gempackagetask'
18
+ require 'fileutils'
19
+
20
+ spec = Gem::Specification.new do |s|
21
+ s.name = "class-loader"
22
+ s.version = "0.3.1"
23
+ s.summary = "Makes any directory looks like Ruby Gem"
24
+ s.description = "Makes any directory looks like Ruby Gem"
25
+ s.author = "Alexey Petrushin"
26
+ # s.email = ""
27
+ s.homepage = "http://github.com/alexeypetrushin/class-loader"
28
+
29
+ s.platform = Gem::Platform::RUBY
30
+ s.has_rdoc = true
31
+
32
+ s.files = (['Rakefile', 'readme.md'] + Dir.glob("{lib,spec}/**/*"))
33
+
34
+ s.require_paths = ["lib"]
35
+ end
36
+
37
+ PACKAGE_DIR = "#{File.expand_path File.dirname(__FILE__)}/build"
38
+
39
+ Rake::GemPackageTask.new(spec) do |p|
40
+ package_dir = PACKAGE_DIR
41
+ # FileUtils.mkdir package_dir unless File.exist? package_dir
42
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
43
+ p.need_zip = true
44
+ p.package_dir = package_dir
45
+ end
46
+
47
+ # CLEAN.include [ 'pkg', '*.gem']
48
+
49
+ task :push do
50
+ dir = Dir.chdir PACKAGE_DIR do
51
+ gem_file = Dir.glob("class-loader*.gem").first
52
+ system "gem push #{gem_file}"
53
+ end
54
+ end
55
+
56
+ task :clean do
57
+ system "rm -r #{PACKAGE_DIR}"
58
+ end
59
+
60
+ task :release => [:gem, :push, :clean]
@@ -0,0 +1,12 @@
1
+ %w(
2
+ support
3
+ file_system_adapter/camel_case_translator
4
+ file_system_adapter/underscored_translator
5
+ file_system_adapter
6
+ chained_adapter
7
+ class_loader
8
+ ).each{|f| require "class_loader/#{f}"}
9
+
10
+ def autoload_dir *args, &block
11
+ ClassLoader.autoload_dir *args, &block
12
+ end
@@ -0,0 +1,44 @@
1
+ module ClassLoader
2
+ class ChainedAdapter
3
+ attr_accessor :adapters
4
+
5
+ def initialize
6
+ @adapters = []
7
+ end
8
+
9
+ %w(
10
+ exist?
11
+ read
12
+ to_file_path
13
+ to_class_name
14
+ ).each do |method|
15
+ define_method method do |*args|
16
+ catch :found do
17
+ adapters.each do |a|
18
+ value = a.send method, *args
19
+ throw :found, value if value
20
+ end
21
+ nil
22
+ end
23
+ end
24
+ end
25
+
26
+ def each_changed_class &block
27
+ adapters.each{|a| a.each_changed_class &block}
28
+ end
29
+
30
+ def each_class &block
31
+ adapters.each{|a| a.each_class &block}
32
+ end
33
+
34
+ def clear
35
+ adapters.each{|a| a.clear}
36
+ end
37
+
38
+ def add_path *args
39
+ adapters.each do |a|
40
+ a.add_path *args if a.respond_to? :add_path
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,219 @@
1
+ require 'monitor'
2
+
3
+ module ClassLoader
4
+ @observers = []
5
+ SYNC = Monitor.new
6
+
7
+ class << self
8
+ #
9
+ # Class loading logic
10
+ #
11
+ attr_accessor :error_on_defined_constant
12
+ def loaded_classes; @loaded_classes ||= {} end
13
+
14
+ def load_class namespace, const, reload = false
15
+ SYNC.synchronize do
16
+ namespace = nil if namespace == Object or namespace == Module
17
+ target_namespace = namespace
18
+
19
+ # Name hack (for anonymous classes)
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 try_load(class_name, const)
28
+ defined_in_home_scope = namespace ? namespace.const_defined?(const) : Object.const_defined?(const)
29
+
30
+ unless defined_in_home_scope
31
+ msg = "Class Name '#{class_name}' doesn't correspond to File Name '#{adapter.to_file_path(class_name)}'!"
32
+ raise_without_self NameError, msg
33
+ end
34
+
35
+ unless reload
36
+ if loaded_classes.include? class_name
37
+ if error_on_defined_constant
38
+ raise_without_self NameError, "Class '#{class_name}' is not defined in the '#{target_namespace}' Namespace!"
39
+ else
40
+ warn "Warn: Class '#{class_name}' is not defined in the '#{target_namespace}' Namespace!"
41
+ puts caller
42
+ end
43
+ end
44
+ end
45
+
46
+ result = namespace ? namespace.const_get(const) : Object.const_get(const)
47
+
48
+ loaded_classes[class_name] = target_namespace
49
+ notify_observers result
50
+ return result
51
+ elsif namespace
52
+ namespace = Module.namespace_for(namespace.name)
53
+ class_name = namespace ? "#{namespace.name}::#{const}" : const
54
+ end
55
+ end until simple_also_tried
56
+
57
+ raise_without_self NameError, "uninitialized constant '#{class_name}'!"
58
+ end
59
+ end
60
+
61
+ def reload_class class_name
62
+ SYNC.synchronize do
63
+ class_name = class_name.sub(/^::/, "")
64
+ namespace = Module.namespace_for(class_name)
65
+ name = class_name.sub(/^#{namespace}::/, "")
66
+
67
+ # removing old class
68
+ class_container = (namespace || Object)
69
+ class_container.send :remove_const, name if class_container.const_defined? name
70
+
71
+ return load_class namespace, name, true
72
+ end
73
+ end
74
+
75
+ def wrap_inside_namespace namespace, script
76
+ nesting = []
77
+ if namespace
78
+ current_scope = ""
79
+ namespace.name.split("::").each do |level|
80
+ current_scope += "::#{level}"
81
+ type = eval current_scope, TOPLEVEL_BINDING, __FILE__, __LINE__
82
+ nesting << [level, (type.class == Module ? "module" : "class")]
83
+ end
84
+ end
85
+ begining = nesting.collect{|l, t| "#{t} #{l};"}.join(' ')
86
+ ending = nesting.collect{"end"}.join('; ')
87
+ return "#{begining}#{script} \n#{ending}"
88
+ end
89
+
90
+
91
+ #
92
+ # Utilities
93
+ #
94
+ def autoload_dir path, watch = false, start_watch_thread = true
95
+ hook!
96
+ start_watching! if watch and start_watch_thread
97
+ adapter.add_path path, watch
98
+ end
99
+
100
+ def clear
101
+ self.adapter = nil
102
+ self.observers = []
103
+ self.error_on_defined_constant = false
104
+ end
105
+
106
+ attr_accessor :observers
107
+ def add_observer &block; observers << block end
108
+ def notify_observers o
109
+ observers.each{|obs| obs.call o}
110
+ end
111
+
112
+ def hook!
113
+ return if @hooked
114
+
115
+ ::Module.class_eval do
116
+ alias_method :const_missing_without_cl, :const_missing
117
+ def const_missing const
118
+ return ClassLoader.load_class self, const.to_s
119
+ end
120
+ end
121
+ @hooked = true
122
+ end
123
+
124
+ attr_writer :adapter
125
+ def adapter
126
+ @adapter ||= default_adapter
127
+ end
128
+
129
+
130
+ #
131
+ # Watcher thread
132
+ #
133
+ attr_accessor :watch_interval
134
+ def start_watching!
135
+ unless watching_thread
136
+ self.watching_thread = Thread.new do
137
+ while true
138
+ sleep(watch_interval || 2)
139
+ adapter.each_changed_class do |class_name|
140
+ puts "reloading #{class_name}"
141
+ reload_class class_name
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def stop_watching!
149
+ if watching_thread
150
+ watching_thread.kill
151
+ self.watching_thread = nil
152
+ end
153
+ end
154
+
155
+ def preload!
156
+ adapter.each_class do |class_name|
157
+ reload_class class_name
158
+ end
159
+ end
160
+
161
+
162
+ protected
163
+ def default_adapter
164
+ adapter = ChainedAdapter.new
165
+ adapter.adapters << FileSystemAdapter.new(CamelCaseTranslator)
166
+ adapter.adapters << FileSystemAdapter.new(UnderscoredTranslator)
167
+ adapter
168
+ end
169
+
170
+ def try_load class_name, const
171
+ if adapter.exist? class_name
172
+ script = adapter.read class_name
173
+ script = wrap_inside_namespace Module.namespace_for(class_name), script
174
+ file_path = adapter.to_file_path(class_name)
175
+ eval script, TOPLEVEL_BINDING, file_path
176
+ else
177
+ return false
178
+ end
179
+ return true
180
+ end
181
+
182
+ def raise_without_self exception, message
183
+ raise exception, message, caller.select{|path| path !~ /\/lib\/class_loader\//}
184
+ end
185
+
186
+ def name_hack namespace
187
+ if namespace
188
+ namespace.to_s.gsub("#<Class:", "").gsub(">", "")
189
+ else
190
+ ""
191
+ end
192
+ # Namespace Hack description
193
+ # Module.name doesn't works correctly for Anonymous classes.
194
+ # try to execute this code:
195
+ #
196
+ #class Module
197
+ # def const_missing const
198
+ # p self.to_s
199
+ # end
200
+ #end
201
+ #
202
+ #class A
203
+ # class << self
204
+ # def a
205
+ # p self
206
+ # MissingConst
207
+ # end
208
+ # end
209
+ #end
210
+ #
211
+ #A.a
212
+ #
213
+ # the output will be:
214
+ # A
215
+ # "#<Class:A>"
216
+ #
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,130 @@
1
+ module ClassLoader
2
+ class FileSystemAdapter
3
+ attr_reader :translator
4
+
5
+ def initialize class_name_translator
6
+ @translator = class_name_translator
7
+ @paths, @watched_paths, @file_name_cache = [], [], {}
8
+ @watched_files, @first_check = {}, true
9
+ end
10
+
11
+ def exist? class_name
12
+ !!to_file_path(class_name)
13
+ end
14
+ alias_method :exists?, :exist?
15
+
16
+ def read class_name
17
+ file_path = to_file_path class_name
18
+ return nil unless file_path
19
+
20
+ if file_path =~ /\.rb$/
21
+ File.open(file_path){|f| f.read}
22
+ else
23
+ "module #{class_name}; end;"
24
+ end
25
+ end
26
+
27
+ def to_file_path class_name
28
+ file_path, exist = @file_name_cache[class_name] || []
29
+ unless exist
30
+ file_name = translator.to_file_path class_name
31
+ file_path = catch :found do
32
+ # files
33
+ paths.each do |base|
34
+ try = "#{base}/#{file_name}.rb"
35
+ if File.exist? try
36
+ throw :found, try
37
+ end
38
+ end
39
+
40
+ # dirs
41
+ paths.each do |base|
42
+ try = "#{base}/#{file_name}"
43
+ if File.exist? try
44
+ throw :found, try
45
+ end
46
+ end
47
+
48
+ nil
49
+ end
50
+
51
+ @file_name_cache[class_name] = [file_path, true]
52
+ end
53
+ file_path
54
+ end
55
+
56
+ def to_class_name normalized_path
57
+ raise "Internal error, file_name should be absolute path (#{normalized_path})!" unless normalized_path =~ /^\//
58
+ raise "Internal error, file_name should be without .rb suffix (#{normalized_path})!" if normalized_path =~ /\.rb$/
59
+
60
+ if base_path = paths.find{|path| normalized_path.start_with? path}
61
+ _to_class_name normalized_path, base_path
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ def add_path path, watch = false
68
+ path = File.expand_path(path)
69
+ raise "#{path} already added!" if paths.include? path
70
+
71
+ paths << path
72
+ watched_paths << path if watch
73
+ end
74
+
75
+ def clear
76
+ @paths, @watched_paths, @file_name_cache = [], [], {}
77
+ @watched_files, @first_check = {}, true
78
+ end
79
+
80
+ def each_changed_class &block
81
+ if @first_check
82
+ each_watched_file{|base_path, file_path| remember_file file_path}
83
+ @first_check = false
84
+ else
85
+ each_watched_file do |base_path, file_path|
86
+ if file_changed? file_path
87
+ remember_file file_path
88
+
89
+ normalized_path = file_path.sub(/\.rb$/, "")
90
+ block.call _to_class_name(normalized_path, base_path)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def each_class &block
97
+ @paths.each do |base_path|
98
+ Dir.glob("#{base_path}/**/*.rb").each do |file_path|
99
+ normalized_path = file_path.sub(/\.rb$/, "")
100
+ block.call _to_class_name(normalized_path, base_path)
101
+ end
102
+ end
103
+ end
104
+
105
+ protected
106
+ attr_reader :paths, :watched_paths, :watcher, :watched_files
107
+
108
+ def each_watched_file &block
109
+ @watched_paths.each do |base_path|
110
+ Dir.glob("#{base_path}/**/*.rb").each do |file_path|
111
+ block.call base_path, file_path
112
+ end
113
+ end
114
+ end
115
+
116
+ def file_changed? path
117
+ old_time = watched_files[path]
118
+ old_time == nil or old_time != File.mtime(path)
119
+ end
120
+
121
+ def remember_file path
122
+ watched_files[path] = File.mtime(path)
123
+ end
124
+
125
+ def _to_class_name file_path, base_path
126
+ relative_name = file_path.sub(base_path, '')
127
+ translator.to_class_name(relative_name)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,11 @@
1
+ module ClassLoader
2
+ class CamelCaseTranslator
3
+ def self.to_class_name file_path
4
+ file_path.sub(/^\//, '').gsub('/', '::')
5
+ end
6
+
7
+ def self.to_file_path class_name
8
+ class_name.sub(/^::/, '').gsub('::', '/')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module ClassLoader
2
+ class UnderscoredTranslator
3
+ def self.to_class_name file_path
4
+ file_path.sub(/^\//, '').camelize
5
+ end
6
+
7
+ def self.to_file_path class_name
8
+ class_name.sub(/^::/, '').underscore
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ class String
2
+ unless "".respond_to? :underscore
3
+ def underscore
4
+ word = self.dup
5
+ word.gsub!(/::/, '/')
6
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
7
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
8
+ word.tr!("-", "_")
9
+ word.downcase!
10
+ word
11
+ end
12
+ end
13
+
14
+ unless "".respond_to? :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
+ end
24
+
25
+ class Module
26
+ unless respond_to? :namespace_for
27
+ def self.namespace_for class_name
28
+ list = class_name.split("::")
29
+ if list.size > 1
30
+ list.pop
31
+ return eval(list.join("::"), TOPLEVEL_BINDING, __FILE__, __LINE__)
32
+ else
33
+ return nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ # ClassLoader automatically finds and loads classes
2
+
3
+ ## Overview
4
+ There's only one method - :autoload_dir, kind of turbocharged :autoload, it understands namespaces, figure out dependencies and can watch and reload changed files.
5
+
6
+ Let's say your application has the following structure
7
+
8
+ /your_app
9
+ /lib
10
+ /animals
11
+ /dog.rb
12
+ /zoo.rb
13
+
14
+ Just point ClassLoader to the directory(ies) your classes are located and it will find and load them automatically
15
+
16
+ require 'class_loader'
17
+ autoload_dir '/your_app/lib'
18
+
19
+ Zoo.add Animals::Dog.new # <= all classes loaded automatically
20
+
21
+ no need for
22
+
23
+ # require 'animals/dog'
24
+ # require 'app'
25
+
26
+ you can specify multiple autoload directories, and tell it to watch them
27
+
28
+ autoload_dir '/your_app/lib', true # <= provide true as the second argument
29
+ autoload_dir '/your_app/another_lib'
30
+
31
+ **Note**: In the dog.rb we write just the "class Dog; end", instead of "module Animals; class Dog; end; end', and there are no really the 'Animals' module, ClassLoader smart enough to figure it out that there's should be one by looking at files structure and it will generate it on the fly.
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?
38
+
39
+ ## Installation
40
+
41
+ $ gem install class-loader
42
+
43
+ ## Please let me know about bugs and your proposals, there's the 'Issues' tab at the top, feel free to submit.
44
+
45
+ Copyright (c) 2010 Alexey Petrushin http://bos-tec.com, released under the MIT license.
@@ -0,0 +1,139 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/helper"
2
+ require "class_loader"
3
+
4
+ describe ClassLoader do
5
+ before :all do
6
+ @dir = prepare_spec_data __FILE__
7
+ end
8
+
9
+ after :all do
10
+ clean_spec_data __FILE__
11
+
12
+ remove_constants %w(
13
+ BasicSpec
14
+ OnlyOnceSpec
15
+ NamespaceTypeResolving NamespaceIsAlreadyDefinedAsClass
16
+ InvalidInfinityLoopClassName
17
+ AnonymousSpec
18
+ AnotherNamespace
19
+ ClassReloadingSpec
20
+ UnloadOldClass
21
+ PreloadingSpec
22
+ UnderscoredNamespace
23
+ )
24
+ end
25
+
26
+ after :each do
27
+ ClassLoader.clear
28
+ end
29
+
30
+ it "should load classes from class path" do
31
+ autoload_dir "#{@dir}/basic"
32
+
33
+ BasicSpec
34
+ BasicSpec::SomeNamespace::SomeClass
35
+ end
36
+
37
+ it "should load classes only once" do
38
+ autoload_dir "#{@dir}/only_once"
39
+
40
+ check = mock
41
+ check.should_receive(:loaded).once
42
+ ClassLoader.add_observer do |klass|
43
+ klass.name.should == "OnlyOnceSpec"
44
+ check.loaded
45
+ end
46
+
47
+ OnlyOnceSpec
48
+ OnlyOnceSpec
49
+ end
50
+
51
+ it "should resolve is namespace a class or module" do
52
+ autoload_dir "#{@dir}/namespace_type_resolving"
53
+
54
+ NamespaceTypeResolving.class.should == Class
55
+ NamespaceTypeResolving::SomeClass
56
+
57
+ class NamespaceIsAlreadyDefinedAsClass; end
58
+ NamespaceIsAlreadyDefinedAsClass::SomeClass
59
+ end
60
+
61
+ it "should recognize infinity loop" do
62
+ autoload_dir "#{@dir}/infinity_loop"
63
+
64
+ lambda{InfinityLoop}.should raise_error(/Class Name .+ doesn't correspond to File Name/)
65
+ end
66
+
67
+ it "should correctly works inside of anonymous class" do
68
+ autoload_dir "#{@dir}/anonymous_class"
69
+
70
+ module ::AnonymousSpec
71
+ class << self
72
+ def anonymous
73
+ ClassInsideOfAnonymousClass
74
+ end
75
+ end
76
+ end
77
+
78
+ AnonymousSpec.anonymous
79
+ end
80
+
81
+ it "should raise exception if class defined in another namespace" do
82
+ autoload_dir "#{@dir}/another_namespace"
83
+
84
+ AnotherNamespace::NamespaceA
85
+ ClassLoader.error_on_defined_constant = true
86
+ lambda{
87
+ AnotherNamespace::NamespaceB
88
+ }.should raise_error(/Class '.+' is not defined in the '.+' Namespace!/)
89
+ end
90
+
91
+ describe "reloading" do
92
+ it "should reload class files" do
93
+ spec_dir = "#{@dir}/class_reloading"
94
+ fname = "#{spec_dir}/ClassReloadingSpec.rb"
95
+ autoload_dir spec_dir
96
+
97
+ code = <<-RUBY
98
+ class ClassReloadingSpec
99
+ def self.check; :value end
100
+ end
101
+ RUBY
102
+
103
+ File.open(fname, 'w'){|f| f.write code}
104
+
105
+ ClassReloadingSpec.check.should == :value
106
+
107
+ code = <<-RUBY
108
+ class ClassReloadingSpec
109
+ def self.check; :another_value end
110
+ end
111
+ RUBY
112
+
113
+ File.open(fname, 'w'){|f| f.write code}
114
+
115
+ ClassLoader.reload_class(ClassReloadingSpec.name)
116
+ ClassReloadingSpec.check.should == :another_value
117
+ end
118
+
119
+ it "should unload old classes before reloading" do
120
+ autoload_dir "#{@dir}/unload_old_class"
121
+ UnloadOldClass.instance_variable_set "@value", :value
122
+ ClassLoader.reload_class(UnloadOldClass.name)
123
+ UnloadOldClass.instance_variable_get("@value").should == nil
124
+ end
125
+ end
126
+
127
+ it "should be able to preload all classes in production" do
128
+ autoload_dir "#{@dir}/preloading"
129
+ Object.const_defined?(:PreloadingSpec).should be_false
130
+ ClassLoader.preload!
131
+ Object.const_defined?(:PreloadingSpec).should be_true
132
+ end
133
+
134
+ it "underscored smoke test" do
135
+ autoload_dir "#{@dir}/underscored"
136
+
137
+ UnderscoredNamespace::UnderscoredClass
138
+ end
139
+ end
@@ -0,0 +1,5 @@
1
+ class NamespaceB
2
+ class InnerClass; end
3
+
4
+ InnerClass::NamespaceA
5
+ end
@@ -0,0 +1,2 @@
1
+ class BasicSpec
2
+ end
@@ -0,0 +1,2 @@
1
+ class InvalidInfinityLoopClassName
2
+ end
@@ -0,0 +1,2 @@
1
+ class OnlyOnceSpec
2
+ end
@@ -0,0 +1,2 @@
1
+ class PreloadingSpec
2
+ end
@@ -0,0 +1,2 @@
1
+ class UnloadOldClass
2
+ end
@@ -0,0 +1,91 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/helper"
2
+ require "class_loader/file_system_adapter/camel_case_translator"
3
+ require "class_loader/file_system_adapter"
4
+ require "class_loader/chained_adapter"
5
+
6
+ describe ClassLoader::FileSystemAdapter do
7
+ before :all do
8
+ @dir = prepare_spec_data __FILE__
9
+ end
10
+
11
+ before :each do
12
+ @fs_adapter = ClassLoader::FileSystemAdapter.new(ClassLoader::CamelCaseTranslator)
13
+
14
+ # Actually we are testing both ChainedAdapter and FileSystemAdapter
15
+ @adapter = ClassLoader::ChainedAdapter.new
16
+ @adapter.adapters << @fs_adapter
17
+
18
+ @adapter.add_path "#{@dir}/common"
19
+ end
20
+
21
+ after :all do
22
+ clean_spec_data __FILE__
23
+ end
24
+
25
+ def write_file path, klass
26
+ File.open("#{@dir}/#{path}", 'w'){|f| f.write "class #{klass}; end"}
27
+ end
28
+
29
+ it "exist?" do
30
+ @adapter.exist?("SomeNamespace").should be_true
31
+ @adapter.exist?("SomeNamespace::SomeClass").should be_true
32
+ @adapter.exist?("SomeNamespace::NonExistingClass").should be_false
33
+ end
34
+
35
+ it "should works with multiple class paths" do
36
+ @adapter.add_path "#{@dir}/multiple_class_paths/path_a"
37
+ @adapter.add_path "#{@dir}/multiple_class_paths/path_b"
38
+
39
+ @adapter.exist?("ClassInPathA").should be_true
40
+ @adapter.exist?("ClassInPathB").should be_true
41
+ end
42
+
43
+ it "read" do
44
+ @adapter.read("SomeNamespace::SomeClass").should == "class SomeClass; end"
45
+ end
46
+
47
+ it "to_file_path" do
48
+ @adapter.to_file_path("NonExistingClass").should be_nil
49
+ @adapter.to_file_path("SomeNamespace::SomeClass").should =~ /SomeNamespace\/SomeClass/
50
+ end
51
+
52
+ it "to_class_name" do
53
+ @adapter.to_class_name("#{@dir}/non_existing_path").should be_nil
54
+ @adapter.to_class_name("#{@dir}/common/SomeNamespace").should == "SomeNamespace"
55
+ @adapter.to_class_name("#{@dir}/common/SomeNamespace/SomeClass").should == "SomeNamespace::SomeClass"
56
+ end
57
+
58
+ it "shouldn't allow to add path twice" do
59
+ @adapter.clear
60
+ @adapter.add_path "#{@dir}/common"
61
+ lambda{@adapter.add_path "#{@dir}/common"}.should raise_error(/already added/)
62
+ end
63
+
64
+ describe "file watching" do
65
+ def changed_classes
66
+ changed = []
67
+ @adapter.each_changed_class{|c| changed << c}
68
+ changed
69
+ end
70
+
71
+ it "each_changed_class shouldn't affect paths not specified for watching" do
72
+ @adapter.add_path "#{@dir}/search_only_watched", false
73
+ changed_classes.should == []
74
+
75
+ sleep(1) && write_file("watching/SomeClass.rb", "SomeClass")
76
+ changed_classes.should == []
77
+ end
78
+
79
+ it "each_changed_class" do
80
+ @adapter.add_path "#{@dir}/watching", true
81
+
82
+ changed_classes.should == []
83
+
84
+ sleep(1) && write_file("watching/SomeClass.rb", "SomeClass")
85
+ changed_classes.should == ["SomeClass"]
86
+
87
+ sleep(1) && write_file("watching/SomeClass.rb", "SomeClass")
88
+ changed_classes.should == ["SomeClass"]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec'
2
+ require 'fileutils'
3
+
4
+ def prepare_spec_data spec_file_name
5
+ dir = File.expand_path(spec_file_name.sub(/\.rb$/, ''))
6
+ original_data_dir = dir + "_data"
7
+
8
+ FileUtils.rm_r dir if File.exist? dir
9
+ FileUtils.cp_r original_data_dir, dir
10
+
11
+ dir
12
+ end
13
+
14
+ def clean_spec_data spec_file_name
15
+ dir = spec_file_name.sub(/\.rb$/, '')
16
+ FileUtils.rm_r dir if File.exist? dir
17
+ end
18
+
19
+ def remove_constants *args
20
+ args = args.first if args.size == 1 and args.first.is_a?(Array)
21
+ args.each{|c| Object.send :remove_const, c if Object.const_defined? c}
22
+ end
@@ -0,0 +1,2 @@
1
+ --color
2
+ --reverse
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: class-loader
3
+ version: !ruby/object:Gem::Version
4
+ hash: 17
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 1
10
+ version: 0.3.1
11
+ platform: ruby
12
+ authors:
13
+ - Alexey Petrushin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-10 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Makes any directory looks like Ruby Gem
23
+ email:
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - Rakefile
32
+ - readme.md
33
+ - lib/class_loader/chained_adapter.rb
34
+ - lib/class_loader/class_loader.rb
35
+ - lib/class_loader/file_system_adapter/camel_case_translator.rb
36
+ - lib/class_loader/file_system_adapter/underscored_translator.rb
37
+ - lib/class_loader/file_system_adapter.rb
38
+ - lib/class_loader/support.rb
39
+ - lib/class_loader.rb
40
+ - spec/class_loader_spec.rb
41
+ - spec/class_loader_spec_data/anonymous_class/AnonymousSpec/ClassInsideOfAnonymousClass.rb
42
+ - spec/class_loader_spec_data/another_namespace/AnotherNamespace/NamespaceA.rb
43
+ - spec/class_loader_spec_data/another_namespace/AnotherNamespace/NamespaceB.rb
44
+ - spec/class_loader_spec_data/basic/BasicSpec/SomeNamespace/SomeClass.rb
45
+ - spec/class_loader_spec_data/basic/BasicSpec.rb
46
+ - spec/class_loader_spec_data/infinity_loop/InfinityLoop.rb
47
+ - spec/class_loader_spec_data/namespace_type_resolving/NamespaceIsAlreadyDefinedAsClass/SomeClass.rb
48
+ - spec/class_loader_spec_data/namespace_type_resolving/NamespaceTypeResolving/SomeClass.rb
49
+ - spec/class_loader_spec_data/namespace_type_resolving/NamespaceTypeResolving.rb
50
+ - spec/class_loader_spec_data/only_once/OnlyOnceSpec.rb
51
+ - spec/class_loader_spec_data/preloading/PreloadingSpec.rb
52
+ - spec/class_loader_spec_data/underscored/underscored_namespace/underscored_class.rb
53
+ - spec/class_loader_spec_data/unload_old_class/UnloadOldClass.rb
54
+ - spec/file_system_adapter_spec.rb
55
+ - spec/file_system_adapter_spec_data/common/SomeNamespace/SomeClass.rb
56
+ - spec/file_system_adapter_spec_data/multiple_class_paths/path_a/ClassInPathA.rb
57
+ - spec/file_system_adapter_spec_data/multiple_class_paths/path_b/ClassInPathB.rb
58
+ - spec/helper.rb
59
+ - spec/spec.opts
60
+ has_rdoc: true
61
+ homepage: http://github.com/alexeypetrushin/class-loader
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.3.7
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Makes any directory looks like Ruby Gem
94
+ test_files: []
95
+