@@ -8,9 +8,12 @@ class Unreloader
88 # Mutex used to synchronize reloads
99 MUTEX = Monitor . new
1010
11- # Reference to ::File as File would return Rack::File by default.
11+ # Reference to ::File as File may return Rack::File by default.
1212 File = ::File
1313
14+ # Regexp for valid constant names, to prevent code execution.
15+ VALID_CONSTANT_NAME_REGEXP = /\A (?:::)?([A-Z]\w *(?:::[A-Z]\w *)*)\z / . freeze
16+
1417 # Given the list of paths, find all matching files, or matching ruby files
1518 # in subdirecories if given a directory, and return an array of expanded
1619 # paths.
@@ -41,11 +44,51 @@ def self.ruby_files(dir)
4144 files . sort
4245 end
4346
47+ # Autoload the file for the given objects. objs should be a string, symbol,
48+ # or array of them holding a Ruby constant name. Access to the constant will
49+ # load the related file. A non-nil logger will have output logged to it.
50+ def self . autoload_constants ( objs , file , logger )
51+ strings = Array ( objs ) . map ( &:to_s )
52+ if strings . empty?
53+ # Remove file from $LOADED_FEATURES if there are no constants to autoload.
54+ # In general that is because the file is part of another class that will
55+ # handle loading the file separately, and if that class is reloaded, we
56+ # want to remove the loaded feature so the file can get loaded again.
57+ $LOADED_FEATURES. delete ( file )
58+ else
59+ logger . info ( "Setting up autoload for #{ file } : #{ strings . join ( ' ' ) } " ) if logger
60+ strings . each do |s |
61+ obj , mod = split_autoload ( s )
62+
63+ if obj
64+ obj . autoload ( mod , file )
65+ elsif logger
66+ logger . info ( "Invalid constant name: #{ s } " )
67+ end
68+ end
69+ end
70+ end
71+
72+ # Split the given string into an array. The first is a module/class to add the
73+ # autoload to, and the second is the name of the constant to be autoloaded.
74+ def self . split_autoload ( mod_string )
75+ if m = VALID_CONSTANT_NAME_REGEXP . match ( mod_string )
76+ ns , sep , mod = m [ 1 ] . rpartition ( '::' )
77+ if sep . empty?
78+ [ Object , mod ]
79+ else
80+ [ Object . module_eval ( "::#{ ns } " , __FILE__ , __LINE__ ) , mod ]
81+ end
82+ end
83+ end
84+
4485 # The Rack::Unreloader::Reloader instead related to this instance, if one.
4586 attr_reader :reloader
4687
4788 # Setup the reloader. Options:
4889 #
90+ # :autoload :: Whether to allow autoloading. If not set to true, calls to
91+ # autoload will eagerly require the related files instead of autoloading.
4992 # :cooldown :: The number of seconds to wait between checks for changed files.
5093 # Defaults to 1. Set to nil/false to not check for changed files.
5194 # :handle_reload_errors :: Whether reload to handle reload errors by returning
@@ -59,12 +102,19 @@ def self.ruby_files(dir)
59102 # match exactly, since modules don't have superclasses.
60103 def initialize ( opts = { } , &block )
61104 @app_block = block
105+ @autoload = opts [ :autoload ]
106+ @logger = opts [ :logger ]
62107 if opts . fetch ( :reload , true )
63108 @cooldown = opts . fetch ( :cooldown , 1 )
64109 @handle_reload_errors = opts [ :handle_reload_errors ]
65110 @last = Time . at ( 0 )
66- require_relative 'unreloader/reloader'
67- @reloader = Reloader . new ( opts )
111+ if @autoload
112+ require_relative ( 'unreloader/autoload_reloader' )
113+ @reloader = AutoloadReloader . new ( opts )
114+ else
115+ require_relative ( 'unreloader/reloader' )
116+ @reloader = Reloader . new ( opts )
117+ end
68118 reload!
69119 else
70120 @reloader = @cooldown = @handle_reload_errors = false
@@ -99,6 +149,24 @@ def require(paths, opts={}, &block)
99149 end
100150 end
101151
152+ # Add a file glob or array of file global to autoload and monitor
153+ # for changes. A block is required. It will be called with the
154+ # path to be autoloaded, and should return the symbol for the
155+ # constant name to autoload. Accepts the same options as #require.
156+ def autoload ( paths , opts = { } , &block )
157+ raise ArgumentError , "block required" unless block
158+
159+ if @autoload
160+ if @reloader
161+ @reloader . autoload_dependencies ( paths , opts , &block )
162+ else
163+ Unreloader . expand_directory_paths ( paths ) . each { |f | Unreloader . autoload_constants ( yield ( f ) , f , @logger ) }
164+ end
165+ else
166+ require ( paths , opts , &block )
167+ end
168+ end
169+
102170 # Records that each path in +files+ depends on +dependency+. If there
103171 # is a modification to +dependency+, all related files will be reloaded
104172 # after +dependency+ is reloaded. Both +dependency+ and each entry in +files+
0 commit comments