@@ -8,9 +8,12 @@ class Unreloader
8
8
# Mutex used to synchronize reloads
9
9
MUTEX = Monitor . new
10
10
11
- # Reference to ::File as File would return Rack::File by default.
11
+ # Reference to ::File as File may return Rack::File by default.
12
12
File = ::File
13
13
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
+
14
17
# Given the list of paths, find all matching files, or matching ruby files
15
18
# in subdirecories if given a directory, and return an array of expanded
16
19
# paths.
@@ -41,11 +44,51 @@ def self.ruby_files(dir)
41
44
files . sort
42
45
end
43
46
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
+
44
85
# The Rack::Unreloader::Reloader instead related to this instance, if one.
45
86
attr_reader :reloader
46
87
47
88
# Setup the reloader. Options:
48
89
#
90
+ # :autoload :: Whether to allow autoloading. If not set to true, calls to
91
+ # autoload will eagerly require the related files instead of autoloading.
49
92
# :cooldown :: The number of seconds to wait between checks for changed files.
50
93
# Defaults to 1. Set to nil/false to not check for changed files.
51
94
# :handle_reload_errors :: Whether reload to handle reload errors by returning
@@ -59,12 +102,19 @@ def self.ruby_files(dir)
59
102
# match exactly, since modules don't have superclasses.
60
103
def initialize ( opts = { } , &block )
61
104
@app_block = block
105
+ @autoload = opts [ :autoload ]
106
+ @logger = opts [ :logger ]
62
107
if opts . fetch ( :reload , true )
63
108
@cooldown = opts . fetch ( :cooldown , 1 )
64
109
@handle_reload_errors = opts [ :handle_reload_errors ]
65
110
@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
68
118
reload!
69
119
else
70
120
@reloader = @cooldown = @handle_reload_errors = false
@@ -99,6 +149,24 @@ def require(paths, opts={}, &block)
99
149
end
100
150
end
101
151
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
+
102
170
# Records that each path in +files+ depends on +dependency+. If there
103
171
# is a modification to +dependency+, all related files will be reloaded
104
172
# after +dependency+ is reloaded. Both +dependency+ and each entry in +files+
0 commit comments