rack-unreloader 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG +22 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +65 -55
- data/lib/rack/unreloader/reloader.rb +47 -78
- data/lib/rack/unreloader.rb +28 -27
- metadata +23 -10
- data/Rakefile +0 -45
- data/spec/spec_helper.rb +0 -76
- data/spec/strip_paths_spec.rb +0 -858
- data/spec/unreloader_spec.rb +0 -449
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f60b8ff5b4384a7f55f92c4e851e2589622112228849b63ed7cf520b2e4b4112
|
4
|
+
data.tar.gz: e01e616600037edc3eb954dd9cf7fd6c8667fa43b15590ec4bb5041547c5f4af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5da4eebc546fcc8194667ca1c1176574f6299a2bef8b062f24edf09146fc151f234b8119af9c19120f5245a5b921cb6841bba29c4bfc20abdc5d229ac040305a
|
7
|
+
data.tar.gz: f7fcb791cb9a0d8f585444ba7bddc4f2ffd80759c4544c8a450c8ebeb38b8620bf24cd05c6a368921c395c9de2ddb1a60f6a31a26a30d76df2d458de35355904
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
= 2.0.0 (2022-06-23)
|
2
|
+
|
3
|
+
* Fix TypeError being raised when requiring a file results in an error (jeremyevans)
|
4
|
+
|
5
|
+
* Drop Unreloader#strip_path_prefix (jeremyevans)
|
6
|
+
|
7
|
+
* Drop Ruby 1.8 support (jeremyevans)
|
8
|
+
|
9
|
+
= 1.8.0 (2021-10-15)
|
10
|
+
|
11
|
+
* Avoid warnings in verbose warning mode on Ruby 3+ (jeremyevans)
|
12
|
+
|
13
|
+
* Check directory timestamps before looking for added or removed files (jeremyevans)
|
14
|
+
|
15
|
+
* Add support for a :delete_hook option to Unreloader#require, called when a file is deleted (jeremyevans)
|
16
|
+
|
17
|
+
* Handle cases where Module#name raises an exception (jeremyevans) (#9)
|
18
|
+
|
19
|
+
= 1.7.0 (2019-03-18)
|
20
|
+
|
21
|
+
* Add :handle_reload_errors option, for returning backtrace with error if there is an exception when reloading (jeremyevans)
|
22
|
+
|
1
23
|
= 1.6.0 (2017-02-24)
|
2
24
|
|
3
25
|
* Add Unreloader#strip_path_prefix, designed to support chrooting (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
= Rack::Unreloader
|
2
2
|
|
3
|
-
Rack::Unreloader is a
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
Rack::Unreloader is a code reloader for {Rack}[https://github.com/rack/rack].
|
4
|
+
It speeds up application development by automatically reloading stale code
|
5
|
+
so that you don't have to restart your dev server every time you change a file.
|
6
|
+
|
7
|
+
Unlike most other code loading libraries for Rack,
|
8
|
+
this one ensures that reloads are clean and idempotent
|
9
|
+
by _unloading_ relevant constants first, and it does so incrementally, only
|
10
|
+
reloading the files that are modified.
|
10
11
|
|
11
12
|
== Installation
|
12
13
|
|
@@ -18,49 +19,61 @@ Source code is available on GitHub at https://github.com/jeremyevans/rack-unrelo
|
|
18
19
|
|
19
20
|
== Basic Usage
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
require 'roda'
|
22
|
+
Before:
|
24
23
|
|
25
|
-
|
26
|
-
route do |r|
|
27
|
-
"Hello world!"
|
28
|
-
end
|
29
|
-
end
|
24
|
+
# config.ru
|
30
25
|
|
31
|
-
|
26
|
+
require './app'
|
32
27
|
|
33
|
-
require './app.rb'
|
34
28
|
run App
|
35
29
|
|
36
|
-
|
30
|
+
After:
|
31
|
+
|
32
|
+
# config.ru
|
37
33
|
|
38
34
|
require 'rack/unreloader'
|
39
35
|
Unreloader = Rack::Unreloader.new{App}
|
40
|
-
require 'roda'
|
41
36
|
Unreloader.require './app.rb'
|
37
|
+
|
42
38
|
run Unreloader
|
43
39
|
|
44
|
-
|
45
|
-
to use. If you make any changes to +app.rb+, <tt>Rack::Unreloader</tt> will remove any
|
46
|
-
constants defined by requiring +app.rb+, and rerequire the file.
|
40
|
+
Now, +app.rb+ will be monitored for changes on each incoming HTTP request.
|
47
41
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
<tt>Rack::Unreloader</tt>.
|
42
|
+
If changes are detected, +Rack::Unreloader+ will
|
43
|
+
unload all constants defined inside it and then re-require it
|
44
|
+
before proceeding with the request.
|
52
45
|
|
53
|
-
|
54
|
-
practical to tell <tt>Rack::Unreloader</tt> to only unload specific subclasses:
|
46
|
+
== Handling Subclasses
|
55
47
|
|
56
|
-
|
57
|
-
|
58
|
-
Unreloader.require './app.rb'
|
59
|
-
run Unreloader
|
48
|
+
By default, +Rack::Unreloader+ unloads *all* constants defined in +app.rb+.
|
49
|
+
That includes third-party libraries, like +Roda+ or +JSON+ in the example below:
|
60
50
|
|
61
|
-
|
62
|
-
|
63
|
-
|
51
|
+
# app.rb
|
52
|
+
|
53
|
+
require 'roda'
|
54
|
+
require 'json'
|
55
|
+
|
56
|
+
class App < Roda
|
57
|
+
...
|
58
|
+
end
|
59
|
+
|
60
|
+
Unloading these classes/modules isn't just unnecessary, it's dangerous.
|
61
|
+
If your own code depends on them, your app will throw a +NameError+ after
|
62
|
+
reloading when it tries to access them.
|
63
|
+
|
64
|
+
To reload only *subclasses* of +Roda+ (i.e. +App+), use the +:subclasses+
|
65
|
+
option:
|
66
|
+
|
67
|
+
Rack::Unreloader.new(:subclasses=>%w'Roda'){App}
|
68
|
+
|
69
|
+
== Handling Errors During Reloading
|
70
|
+
|
71
|
+
By default, +Rack::Unreloader+ instances do not handle exceptions raised
|
72
|
+
during reloading, so that it may be rescued elsewhere (e.g. manually or by middleware).
|
73
|
+
You can use the +:handle_reload_errors+ option to send the backtrace directly to the
|
74
|
+
client as the HTTP response:
|
75
|
+
|
76
|
+
Rack::Unreloader.new(handle_reload_errors: true){App}
|
64
77
|
|
65
78
|
== Dependency Handling
|
66
79
|
|
@@ -83,7 +96,7 @@ to using:
|
|
83
96
|
|
84
97
|
Unreloader.require './models.rb'
|
85
98
|
|
86
|
-
The reason that the
|
99
|
+
The reason that the +Rack::Unreloader+ instance is assigned to a constant in
|
87
100
|
+config.ru+ is to make it easy to add reloadable dependencies in this way.
|
88
101
|
|
89
102
|
It's even a better idea to require this dependency manually in +config.ru+,
|
@@ -207,9 +220,20 @@ The advantage for doing this is that new files added to the directory will be
|
|
207
220
|
picked up automatically, and files deleted from the directory will be removed
|
208
221
|
automatically. This applies to files in subdirectories of that directory as well.
|
209
222
|
|
223
|
+
The +require+ method also supports a +:delete_hook+ option. This option sets
|
224
|
+
a hook that is called when the related file is deleted. This is useful if adding
|
225
|
+
a new file or reloading an existing file will handle things correctly, but
|
226
|
+
removing the file will not. One common case for this is when you have a shared data
|
227
|
+
structure that is updated by the files, where adding or reloading the file will
|
228
|
+
update the data structure, but deleting will not, and will leave stale entries in
|
229
|
+
the data structure. You can use the +:delete_hook+ option to remove the entries
|
230
|
+
related to the file in the data structure:
|
231
|
+
|
232
|
+
Unreloader.require 'models', :delete_hook=>proc{|f| SHARED_HASH.delete(f)}
|
233
|
+
|
210
234
|
== Speeding Things Up
|
211
235
|
|
212
|
-
By default,
|
236
|
+
By default, +Rack::Unreloader+ uses +ObjectSpace+ before and after requiring each
|
213
237
|
file that it monitors, to see which classes and modules were defined by the
|
214
238
|
require. This is slow for large numbers of files. In general use it isn't an
|
215
239
|
issue as generally only a single file will be changed at a time, but it can
|
@@ -218,7 +242,7 @@ time.
|
|
218
242
|
|
219
243
|
If you want to speed things up, you can provide a block to Rack::Unreloader#require,
|
220
244
|
which will take the file name, and should return the name of the constants or array
|
221
|
-
of constants to unload. If you do this,
|
245
|
+
of constants to unload. If you do this, +Rack::Unreloader+ will no longer need
|
222
246
|
to use +ObjectSpace+, which substantially speeds up startup. For example, if all of
|
223
247
|
your models just use a capitalized version of the filename:
|
224
248
|
|
@@ -229,21 +253,9 @@ decide that instead of specifying the constants, ObjectSpace should be used to
|
|
229
253
|
automatically determine the constants loaded. You can specify this by having the
|
230
254
|
block return the :ObjectSpace symbol.
|
231
255
|
|
232
|
-
== chroot Support
|
233
|
-
|
234
|
-
<tt>Rack::Unreloader#strip_path_prefix</tt> exists for supporting reloading in
|
235
|
-
chroot environments, where you chroot an application after it has been fully
|
236
|
-
loaded, but still want to pick up changes to files inside the chroot. Example:
|
237
|
-
|
238
|
-
Unreloader.strip_path_prefix(Dir.pwd)
|
239
|
-
Dir.chroot(Dir.pwd)
|
240
|
-
|
241
|
-
Note that Unreloader.strip_path_prefix also strips the path prefix from
|
242
|
-
$LOADED_FEATURES, as that is necessary for correct operation.
|
243
|
-
|
244
256
|
== Usage Outside Rack
|
245
257
|
|
246
|
-
While
|
258
|
+
While +Rack::Unreloader+ is usually in the development of rack applications,
|
247
259
|
it doesn't depend on rack. You can just instantiate an instance of Unreloader and
|
248
260
|
use it to handle reloading in any ruby application, just by using the +require+ and
|
249
261
|
+record_dependency+ to set up the metadata, and calling +reload!+ manually to
|
@@ -272,11 +284,9 @@ environment anytime there are any changes) are going to be more robust than
|
|
272
284
|
this approach, but probably slower. Be aware that you are trading robustness
|
273
285
|
for speed when using this library.
|
274
286
|
|
275
|
-
==
|
287
|
+
== Ruby Version Support
|
276
288
|
|
277
|
-
Rack::Unreloader works correctly on Ruby 1.
|
278
|
-
also works on older versions of JRuby if you use a proc to specify the constants
|
279
|
-
to unload.
|
289
|
+
Rack::Unreloader works correctly on Ruby 1.9.2+ and JRuby 9.1+.
|
280
290
|
|
281
291
|
== License
|
282
292
|
|
@@ -3,14 +3,11 @@ require 'set'
|
|
3
3
|
module Rack
|
4
4
|
class Unreloader
|
5
5
|
class Reloader
|
6
|
-
|
6
|
+
File = ::File
|
7
7
|
|
8
8
|
# Regexp for valid constant names, to prevent code execution.
|
9
9
|
VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
|
10
10
|
|
11
|
-
# Options hash to force loading of files even if they haven't changed.
|
12
|
-
FORCE = {:force=>true}.freeze
|
13
|
-
|
14
11
|
# Setup the reloader. Supports :logger and :subclasses options, see
|
15
12
|
# Rack::Unloader.new for details.
|
16
13
|
def initialize(opts={})
|
@@ -18,13 +15,15 @@ module Rack
|
|
18
15
|
@classes = opts[:subclasses] ? Array(opts[:subclasses]).map(&:to_s) : %w'Object'
|
19
16
|
|
20
17
|
# Hash of files being monitored for changes, keyed by absolute path of file name,
|
21
|
-
# with values being the last modified time (or nil if the file has
|
18
|
+
# with values being an array containing the last modified time (or nil if the file has
|
19
|
+
# not yet been loaded) and the delete hook.
|
22
20
|
@monitor_files = {}
|
23
21
|
|
24
22
|
# Hash of directories being monitored for changes, keyed by absolute path of directory name,
|
25
|
-
# with values being the an array with
|
26
|
-
# yet been
|
27
|
-
# require_dependency for new files
|
23
|
+
# with values being the an array with a hash of modified times for the directory and
|
24
|
+
# subdirectories (or nil if the directory has not yet been checked), an array of files in
|
25
|
+
# the directory, a block to pass to require_dependency for new files, and the delete_hook
|
26
|
+
# for the files in the directory.
|
28
27
|
@monitor_dirs = {}
|
29
28
|
|
30
29
|
# Hash of procs returning constants defined in files, keyed by absolute path
|
@@ -53,47 +52,6 @@ module Rack
|
|
53
52
|
@skip_reload = []
|
54
53
|
end
|
55
54
|
|
56
|
-
# Strip the given path prefix from the internal data structures.
|
57
|
-
def strip_path_prefix(path_prefix)
|
58
|
-
empty = ''.freeze
|
59
|
-
|
60
|
-
# Strip the path prefix from $LOADED_FEATURES, otherwise the reloading won't work.
|
61
|
-
# Hopefully a future version of ruby will do this automatically when chrooting.
|
62
|
-
$LOADED_FEATURES.map!{|s| s.sub(path_prefix, empty)}
|
63
|
-
|
64
|
-
fix_path = lambda do |s|
|
65
|
-
s.sub(path_prefix, empty)
|
66
|
-
end
|
67
|
-
|
68
|
-
[@dependency_order, @skip_reload].each do |a|
|
69
|
-
a.map!(&fix_path)
|
70
|
-
end
|
71
|
-
|
72
|
-
[@files, @old_entries].each do |hash|
|
73
|
-
hash.each do |k,h|
|
74
|
-
h[:features].map!(&fix_path)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
@monitor_dirs.each_value do |a|
|
79
|
-
a[1].map!(&fix_path)
|
80
|
-
end
|
81
|
-
|
82
|
-
@dependencies.each_value do |a|
|
83
|
-
a.map!(&fix_path)
|
84
|
-
end
|
85
|
-
|
86
|
-
[@files, @old_entries, @monitor_files, @monitor_dirs, @constants_defined, @dependencies].each do |hash|
|
87
|
-
hash.keys.each do |k|
|
88
|
-
if k.start_with?(path_prefix)
|
89
|
-
hash[fix_path.call(k)] = hash.delete(k)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
nil
|
95
|
-
end
|
96
|
-
|
97
55
|
# Unload all reloadable constants and features, and clear the list
|
98
56
|
# of files to monitor.
|
99
57
|
def clear!
|
@@ -117,7 +75,7 @@ module Rack
|
|
117
75
|
order.concat(files)
|
118
76
|
order.uniq!
|
119
77
|
|
120
|
-
if
|
78
|
+
if File.directory?(path)
|
121
79
|
(@monitor_files.keys & Unreloader.ruby_files(path)).each do |file|
|
122
80
|
record_dependency(file, files)
|
123
81
|
end
|
@@ -136,27 +94,26 @@ module Rack
|
|
136
94
|
end
|
137
95
|
|
138
96
|
removed_files = []
|
97
|
+
delete_hooks = []
|
139
98
|
|
140
|
-
@monitor_files.to_a.each do |file, time|
|
141
|
-
if
|
99
|
+
@monitor_files.to_a.each do |file, (time, delete_hook)|
|
100
|
+
if File.file?(file)
|
142
101
|
if file_changed?(file, time)
|
143
102
|
changed_files << file
|
144
103
|
end
|
145
104
|
else
|
105
|
+
delete_hooks << [delete_hook, file] if delete_hook
|
146
106
|
removed_files << file
|
147
107
|
end
|
148
108
|
end
|
149
109
|
|
150
110
|
remove_files(removed_files)
|
111
|
+
delete_hooks.each{|hook, file| hook.call(file)}
|
151
112
|
|
152
113
|
return if changed_files.empty?
|
153
114
|
|
154
115
|
unless @dependencies.empty?
|
155
|
-
changed_files = reload_files(changed_files)
|
156
|
-
changed_files.flatten!
|
157
|
-
changed_files.map!{|f| F.directory?(f) ? Unreloader.ruby_files(f) : f}
|
158
|
-
changed_files.flatten!
|
159
|
-
changed_files.uniq!
|
116
|
+
changed_files = Unreloader.expand_directory_paths(reload_files(changed_files))
|
160
117
|
|
161
118
|
order = @dependency_order
|
162
119
|
order &= changed_files
|
@@ -164,44 +121,45 @@ module Rack
|
|
164
121
|
end
|
165
122
|
|
166
123
|
unless @skip_reload.empty?
|
167
|
-
|
168
|
-
skip_reload.flatten!
|
169
|
-
skip_reload.uniq!
|
170
|
-
changed_files -= skip_reload
|
124
|
+
changed_files -= Unreloader.expand_directory_paths(@skip_reload)
|
171
125
|
end
|
172
126
|
|
127
|
+
changed_files.select! do |file|
|
128
|
+
@monitor_files.has_key?(file)
|
129
|
+
end
|
173
130
|
changed_files.each do |file|
|
174
|
-
safe_load(file
|
131
|
+
safe_load(file)
|
175
132
|
end
|
176
133
|
end
|
177
134
|
|
178
135
|
# Require the given dependencies, monitoring them for changes.
|
179
136
|
# Paths should be a file glob or an array of file globs.
|
180
|
-
def require_dependencies(paths, &block)
|
137
|
+
def require_dependencies(paths, opts={}, &block)
|
181
138
|
options = {:cyclic => true}
|
139
|
+
delete_hook = opts[:delete_hook]
|
182
140
|
error = nil
|
183
141
|
|
184
142
|
Unreloader.expand_paths(paths).each do |file|
|
185
|
-
if
|
186
|
-
@monitor_dirs[file] = [nil, [], block]
|
143
|
+
if File.directory?(file)
|
144
|
+
@monitor_dirs[file] = [nil, [], block, delete_hook]
|
187
145
|
check_monitor_dir(file)
|
188
146
|
next
|
189
147
|
else
|
190
148
|
@constants_defined[file] = block
|
191
|
-
@monitor_files[file] = nil
|
149
|
+
@monitor_files[file] = [nil, delete_hook]
|
192
150
|
end
|
193
151
|
|
194
152
|
begin
|
195
153
|
safe_load(file, options)
|
196
154
|
rescue NameError, LoadError => error
|
197
|
-
log "Cyclic dependency reload for #{error}"
|
155
|
+
log "Cyclic dependency reload for #{error.class}: #{error.message}"
|
198
156
|
rescue Exception => error
|
157
|
+
log "Error: #{error.class}: #{error.message}"
|
199
158
|
break
|
200
159
|
end
|
201
160
|
end
|
202
161
|
|
203
162
|
if error
|
204
|
-
log error
|
205
163
|
raise error
|
206
164
|
end
|
207
165
|
end
|
@@ -234,10 +192,21 @@ module Rack
|
|
234
192
|
@logger.info(s) if @logger
|
235
193
|
end
|
236
194
|
|
195
|
+
# A hash of modify times for all subdirectories of the given directory.
|
196
|
+
def subdir_times(dir)
|
197
|
+
h = {}
|
198
|
+
Find.find(dir) do |f|
|
199
|
+
h[f] = modified_at(f) if File.directory?(f)
|
200
|
+
end
|
201
|
+
h
|
202
|
+
end
|
203
|
+
|
237
204
|
# Check a monitored directory for changes, adding new files and removing
|
238
205
|
# deleted files.
|
239
206
|
def check_monitor_dir(dir, changed_files=nil)
|
240
|
-
|
207
|
+
subdir_times, files, block, delete_hook = md = @monitor_dirs[dir]
|
208
|
+
return if subdir_times && subdir_times.all?{|subdir, time| File.directory?(subdir) && modified_at(subdir) == time}
|
209
|
+
md[0] = subdir_times(dir)
|
241
210
|
|
242
211
|
cur_files = Unreloader.ruby_files(dir)
|
243
212
|
return if files == cur_files
|
@@ -250,8 +219,11 @@ module Rack
|
|
250
219
|
end
|
251
220
|
|
252
221
|
remove_files(removed_files)
|
222
|
+
if delete_hook
|
223
|
+
removed_files.each{|f| delete_hook.call(f)}
|
224
|
+
end
|
253
225
|
|
254
|
-
require_dependencies(new_files, &block)
|
226
|
+
require_dependencies(new_files, :delete_hook=>delete_hook, &block)
|
255
227
|
|
256
228
|
new_files.each do |file|
|
257
229
|
if deps = @dependencies[dir]
|
@@ -281,9 +253,6 @@ module Rack
|
|
281
253
|
# by the require, and rolling back the constants and features if there
|
282
254
|
# are any errors.
|
283
255
|
def safe_load(file, options={})
|
284
|
-
return unless @monitor_files.has_key?(file)
|
285
|
-
return unless options[:force] || file_changed?(file)
|
286
|
-
|
287
256
|
prepare(file) # might call #safe_load recursively
|
288
257
|
log "Loading #{file}"
|
289
258
|
begin
|
@@ -344,7 +313,7 @@ module Rack
|
|
344
313
|
# Store the currently loaded classes and features, so in case of an error
|
345
314
|
# this state can be rolled back to.
|
346
315
|
def prepare(name)
|
347
|
-
|
316
|
+
remove(name)
|
348
317
|
@old_entries[name] = {:features => monitored_features}
|
349
318
|
if constants = constants_for(name)
|
350
319
|
defs = constants.select{|c| constant_defined?(c)}
|
@@ -383,7 +352,7 @@ module Rack
|
|
383
352
|
|
384
353
|
@files[name] = entry
|
385
354
|
@old_entries.delete(name)
|
386
|
-
@monitor_files[name] = modified_at(name)
|
355
|
+
@monitor_files[name][0] = modified_at(name)
|
387
356
|
|
388
357
|
defs, not_defs = entry[:constants].partition{|c| constant_defined?(c)}
|
389
358
|
unless not_defs.empty?
|
@@ -413,7 +382,7 @@ module Rack
|
|
413
382
|
rs = Set.new
|
414
383
|
|
415
384
|
::ObjectSpace.each_object(Module).each do |mod|
|
416
|
-
if !mod.name.to_s.empty? && monitored_module?(mod)
|
385
|
+
if !(mod.name rescue next).to_s.empty? && monitored_module?(mod)
|
417
386
|
rs << mod
|
418
387
|
end
|
419
388
|
end
|
@@ -466,7 +435,7 @@ module Rack
|
|
466
435
|
end
|
467
436
|
|
468
437
|
# Returns true if the file is new or it's modification time changed.
|
469
|
-
def file_changed?(file, time = @monitor_files[file])
|
438
|
+
def file_changed?(file, time = @monitor_files[file][0])
|
470
439
|
!time || modified_at(file) > time
|
471
440
|
end
|
472
441
|
|
@@ -474,7 +443,7 @@ module Rack
|
|
474
443
|
# to base the reloading on something other than the file's modification
|
475
444
|
# time.
|
476
445
|
def modified_at(file)
|
477
|
-
|
446
|
+
File.mtime(file)
|
478
447
|
end
|
479
448
|
end
|
480
449
|
end
|
data/lib/rack/unreloader.rb
CHANGED
@@ -9,26 +9,27 @@ module Rack
|
|
9
9
|
MUTEX = Monitor.new
|
10
10
|
|
11
11
|
# Reference to ::File as File would return Rack::File by default.
|
12
|
-
|
12
|
+
File = ::File
|
13
13
|
|
14
14
|
# Given the list of paths, find all matching files, or matching ruby files
|
15
15
|
# in subdirecories if given a directory, and return an array of expanded
|
16
16
|
# paths.
|
17
17
|
def self.expand_directory_paths(paths)
|
18
|
-
expand_paths(paths)
|
19
|
-
|
20
|
-
|
18
|
+
paths = expand_paths(paths)
|
19
|
+
paths.map!{|f| File.directory?(f) ? ruby_files(f) : f}
|
20
|
+
paths.flatten!
|
21
|
+
paths
|
21
22
|
end
|
22
23
|
|
23
24
|
# Given the path glob or array of path globs, find all matching files
|
24
25
|
# or directories, and return an array of expanded paths.
|
25
26
|
def self.expand_paths(paths)
|
26
|
-
Array(paths).
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
paths = Array(paths).flatten
|
28
|
+
paths.map!{|path| Dir.glob(path).sort_by!{|filename| filename.count('/')}}
|
29
|
+
paths.flatten!
|
30
|
+
paths.map!{|path| File.expand_path(path)}
|
31
|
+
paths.uniq!
|
32
|
+
paths
|
32
33
|
end
|
33
34
|
|
34
35
|
# The .rb files in the given directory or any subdirectory.
|
@@ -47,6 +48,8 @@ module Rack
|
|
47
48
|
#
|
48
49
|
# :cooldown :: The number of seconds to wait between checks for changed files.
|
49
50
|
# Defaults to 1. Set to nil/false to not check for changed files.
|
51
|
+
# :handle_reload_errors :: Whether reload to handle reload errors by returning
|
52
|
+
# a 500 plain text response with the backtrace.
|
50
53
|
# :reload :: Set to false to not setup a reloader, and just have require work
|
51
54
|
# directly. Should be set to false in production mode.
|
52
55
|
# :logger :: A Logger instance which will log information related to reloading.
|
@@ -58,12 +61,13 @@ module Rack
|
|
58
61
|
@app_block = block
|
59
62
|
if opts.fetch(:reload, true)
|
60
63
|
@cooldown = opts.fetch(:cooldown, 1)
|
64
|
+
@handle_reload_errors = opts[:handle_reload_errors]
|
61
65
|
@last = Time.at(0)
|
62
|
-
|
66
|
+
require_relative 'unreloader/reloader'
|
63
67
|
@reloader = Reloader.new(opts)
|
64
68
|
reload!
|
65
69
|
else
|
66
|
-
@reloader = @cooldown = false
|
70
|
+
@reloader = @cooldown = @handle_reload_errors = false
|
67
71
|
end
|
68
72
|
end
|
69
73
|
|
@@ -71,16 +75,25 @@ module Rack
|
|
71
75
|
# Call the app with the environment.
|
72
76
|
def call(env)
|
73
77
|
if @cooldown && Time.now > @last + @cooldown
|
74
|
-
|
78
|
+
begin
|
79
|
+
MUTEX.synchronize{reload!}
|
80
|
+
rescue StandardError, ScriptError => e
|
81
|
+
raise unless @handle_reload_errors
|
82
|
+
content = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
83
|
+
return [500, {'Content-Type' => 'text/plain', 'Content-Length' => content.bytesize.to_s}, [content]]
|
84
|
+
end
|
75
85
|
@last = Time.now
|
76
86
|
end
|
77
87
|
@app_block.call.call(env)
|
78
88
|
end
|
79
89
|
|
80
90
|
# Add a file glob or array of file globs to monitor for changes.
|
81
|
-
|
91
|
+
# Options:
|
92
|
+
# :delete_hook :: When a file being monitored is deleted, call
|
93
|
+
# this hook with the path of the deleted file.
|
94
|
+
def require(paths, opts={}, &block)
|
82
95
|
if @reloader
|
83
|
-
@reloader.require_dependencies(paths, &block)
|
96
|
+
@reloader.require_dependencies(paths, opts, &block)
|
84
97
|
else
|
85
98
|
Unreloader.expand_directory_paths(paths).each{|f| super(f)}
|
86
99
|
end
|
@@ -116,17 +129,5 @@ module Rack
|
|
116
129
|
def reload!
|
117
130
|
@reloader.reload! if @reloader
|
118
131
|
end
|
119
|
-
|
120
|
-
# Strip the given path prefix from all absolute paths used by the
|
121
|
-
# reloader. This is designed when chrooting an application.
|
122
|
-
#
|
123
|
-
# Options:
|
124
|
-
# :strip_core :: Also strips the path prefix from $LOADED_FEATURES and
|
125
|
-
# $LOAD_PATH.
|
126
|
-
def strip_path_prefix(path_prefix, opts={})
|
127
|
-
if @reloader
|
128
|
-
@reloader.strip_path_prefix(path_prefix)
|
129
|
-
end
|
130
|
-
end
|
131
132
|
end
|
132
133
|
end
|