class_loader 0.3.5
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 +66 -0
- data/lib/class_loader/chained_adapter.rb +44 -0
- data/lib/class_loader/class_loader.rb +219 -0
- data/lib/class_loader/file_system_adapter/camel_case_translator.rb +11 -0
- data/lib/class_loader/file_system_adapter/underscored_translator.rb +11 -0
- data/lib/class_loader/file_system_adapter.rb +130 -0
- data/lib/class_loader/support.rb +37 -0
- data/lib/class_loader.rb +12 -0
- data/readme.md +45 -0
- data/spec/class_loader_spec.rb +139 -0
- data/spec/class_loader_spec_data/anonymous_class/AnonymousSpec/ClassInsideOfAnonymousClass.rb +2 -0
- data/spec/class_loader_spec_data/another_namespace/AnotherNamespace/NamespaceA.rb +2 -0
- data/spec/class_loader_spec_data/another_namespace/AnotherNamespace/NamespaceB.rb +5 -0
- data/spec/class_loader_spec_data/basic/BasicSpec/SomeNamespace/SomeClass.rb +2 -0
- data/spec/class_loader_spec_data/basic/BasicSpec.rb +2 -0
- data/spec/class_loader_spec_data/infinity_loop/InfinityLoop.rb +2 -0
- data/spec/class_loader_spec_data/namespace_type_resolving/NamespaceIsAlreadyDefinedAsClass/SomeClass.rb +2 -0
- data/spec/class_loader_spec_data/namespace_type_resolving/NamespaceTypeResolving/SomeClass.rb +2 -0
- data/spec/class_loader_spec_data/namespace_type_resolving/NamespaceTypeResolving.rb +2 -0
- data/spec/class_loader_spec_data/only_once/OnlyOnceSpec.rb +2 -0
- data/spec/class_loader_spec_data/preloading/PreloadingSpec.rb +2 -0
- data/spec/class_loader_spec_data/underscored/underscored_namespace/underscored_class.rb +2 -0
- data/spec/class_loader_spec_data/unload_old_class/UnloadOldClass.rb +2 -0
- data/spec/file_system_adapter_spec.rb +91 -0
- data/spec/file_system_adapter_spec_data/common/SomeNamespace/SomeClass.rb +1 -0
- data/spec/file_system_adapter_spec_data/multiple_class_paths/path_a/ClassInPathA.rb +2 -0
- data/spec/file_system_adapter_spec_data/multiple_class_paths/path_b/ClassInPathB.rb +2 -0
- data/spec/helper.rb +22 -0
- data/spec/spec.opts +2 -0
- metadata +95 -0
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            require 'rake'
         | 
| 2 | 
            +
            require 'fileutils'
         | 
| 3 | 
            +
            current_dir = File.expand_path(File.dirname(__FILE__))
         | 
| 4 | 
            +
            Dir.chdir current_dir
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
            # 
         | 
| 8 | 
            +
            # Specs
         | 
| 9 | 
            +
            # 
         | 
| 10 | 
            +
            require 'spec/rake/spectask'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            task :default => :spec
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            Spec::Rake::SpecTask.new('spec') do |t|
         | 
| 15 | 
            +
            	t.spec_files = FileList["spec/**/*_spec.rb"].select{|f| f !~ /\/_/}
         | 
| 16 | 
            +
              t.libs = ["#{current_dir}/lib"]
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
             | 
| 20 | 
            +
            # 
         | 
| 21 | 
            +
            # Gem
         | 
| 22 | 
            +
            # 
         | 
| 23 | 
            +
            require 'rake/clean'
         | 
| 24 | 
            +
            require 'rake/gempackagetask'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            gem_options = {
         | 
| 27 | 
            +
              :name => "class_loader",
         | 
| 28 | 
            +
              :version => "0.3.5",
         | 
| 29 | 
            +
              :summary => "Automatically finds and loads classes",
         | 
| 30 | 
            +
              :dependencies => %w()
         | 
| 31 | 
            +
            }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            gem_name = gem_options[:name]
         | 
| 34 | 
            +
            spec = Gem::Specification.new do |s|
         | 
| 35 | 
            +
              gem_options.delete(:dependencies).each{|d| s.add_dependency d}
         | 
| 36 | 
            +
              gem_options.each{|k, v| s.send "#{k}=", v}
         | 
| 37 | 
            +
              
         | 
| 38 | 
            +
              s.name = gem_name
         | 
| 39 | 
            +
              s.author = "Alexey Petrushin"
         | 
| 40 | 
            +
              s.homepage = "http://github.com/alexeypetrushin/#{gem_options[:name]}"
         | 
| 41 | 
            +
              s.require_path = "lib"
         | 
| 42 | 
            +
              s.files = (%w{Rakefile readme.md} + Dir.glob("{lib,spec}/**/*"))
         | 
| 43 | 
            +
              
         | 
| 44 | 
            +
              s.platform = Gem::Platform::RUBY
         | 
| 45 | 
            +
              s.has_rdoc = true  
         | 
| 46 | 
            +
            end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            package_dir = "#{current_dir}/build"
         | 
| 49 | 
            +
            Rake::GemPackageTask.new(spec) do |p|
         | 
| 50 | 
            +
              p.need_tar = true if RUBY_PLATFORM !~ /mswin/
         | 
| 51 | 
            +
              p.need_zip = true
         | 
| 52 | 
            +
              p.package_dir = package_dir
         | 
| 53 | 
            +
            end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            task :push do
         | 
| 56 | 
            +
              # dir = Dir.chdir package_dir do
         | 
| 57 | 
            +
              gem_file = Dir.glob("#{package_dir}/#{gem_name}*.gem").first
         | 
| 58 | 
            +
              system "gem push #{gem_file}"
         | 
| 59 | 
            +
              # end
         | 
| 60 | 
            +
            end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            task :clean do
         | 
| 63 | 
            +
              system "rm -r #{package_dir}"
         | 
| 64 | 
            +
            end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            task :release => [:gem, :push, :clean]
         | 
| @@ -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 █ 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,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
         | 
    
        data/lib/class_loader.rb
    ADDED
    
    | @@ -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 
         | 
    
        data/readme.md
    ADDED
    
    | @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            # Automatically finds and loads classes for your Ruby App
         | 
| 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,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 @@ | |
| 1 | 
            +
            class SomeClass; end
         | 
    
        data/spec/helper.rb
    ADDED
    
    | @@ -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
         | 
    
        data/spec/spec.opts
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,95 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: class_loader
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              hash: 25
         | 
| 5 | 
            +
              prerelease: false
         | 
| 6 | 
            +
              segments: 
         | 
| 7 | 
            +
              - 0
         | 
| 8 | 
            +
              - 3
         | 
| 9 | 
            +
              - 5
         | 
| 10 | 
            +
              version: 0.3.5
         | 
| 11 | 
            +
            platform: ruby
         | 
| 12 | 
            +
            authors: 
         | 
| 13 | 
            +
            - Alexey Petrushin
         | 
| 14 | 
            +
            autorequire: 
         | 
| 15 | 
            +
            bindir: bin
         | 
| 16 | 
            +
            cert_chain: []
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            date: 2010-10-11 00:00:00 +04:00
         | 
| 19 | 
            +
            default_executable: 
         | 
| 20 | 
            +
            dependencies: []
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            description: 
         | 
| 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: Automatically finds and loads classes
         | 
| 94 | 
            +
            test_files: []
         | 
| 95 | 
            +
             |