rack-unreloader 1.6.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 63e0754f2064aacba7745d52c60d136019dd5fdf
4
- data.tar.gz: 31d3d8223106f59d5fa739b429cbf08d6d47db31
2
+ SHA256:
3
+ metadata.gz: f60b8ff5b4384a7f55f92c4e851e2589622112228849b63ed7cf520b2e4b4112
4
+ data.tar.gz: e01e616600037edc3eb954dd9cf7fd6c8667fa43b15590ec4bb5041547c5f4af
5
5
  SHA512:
6
- metadata.gz: 6645aa444fd1c88c52c09f0de56894f640cf9404624f0e25197071b1498d1aa7745a1bb9a8e9416f3cc7d8aeacf4f993e552adaa06f4b1386be95db43aef4272
7
- data.tar.gz: 26811ff5218e1fe20e15f0619dcfb4d4dbabcba2819c82e8c46f428fdfb1714a3348a12a931ba8065d5f6cb182336e989e6d98d93e7a2ac633f4c6c7d783fc43
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2015 Jeremy Evans
1
+ Copyright (c) 2014-2021 Jeremy Evans
2
2
  Copyright (c) 2011 Padrino
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining
data/README.rdoc CHANGED
@@ -1,12 +1,13 @@
1
1
  = Rack::Unreloader
2
2
 
3
- Rack::Unreloader is a rack library that reloads application files when it
4
- detects changes, unloading constants defined in those files before reloading.
5
- Like other rack libraries for reloading, this can make application development
6
- much faster, as you don't need to restart the whole application when you change
7
- a single file. Unlike most other rack libraries for reloading, this unloads
8
- constants before requiring files, avoiding issues when loading a file is not
9
- idempotent.
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
- Assuming a basic web application stored in +app.rb+:
22
-
23
- require 'roda'
22
+ Before:
24
23
 
25
- class App < Roda
26
- route do |r|
27
- "Hello world!"
28
- end
29
- end
24
+ # config.ru
30
25
 
31
- With a basic +config.ru+ like this:
26
+ require './app'
32
27
 
33
- require './app.rb'
34
28
  run App
35
29
 
36
- Change +config.ru+ to:
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
- The block you pass to Rack::Unreloader.new should return the rack application
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
- Note that this causes problems if +app.rb+ loads any new libraries that define
49
- constants, as it will unload those constants first. This is why the example
50
- code requires the +roda+ library normally before requiring +app.rb+ using
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
- However, if +app.rb+ requires more than a single file, it is more
54
- practical to tell <tt>Rack::Unreloader</tt> to only unload specific subclasses:
46
+ == Handling Subclasses
55
47
 
56
- require 'rack/unreloader'
57
- Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda'){App}
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
- When the +:subclasses+ option is given, only subclasses of the given classes
62
- will be unloaded before reloading the file. It is recommended that
63
- you use a +:subclasses+ option when using <tt>Rack::Unreloader</tt>.
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 <tt>Rack::Unreloader</tt> instance is assigned to a constant in
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, <tt>Rack::Unreloader</tt> uses +ObjectSpace+ before and after requiring each
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, <tt>Rack::Unreloader</tt> will no longer need
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 <tt>Rack::Unreloader</tt> is usually in the development of rack applications,
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
- == Implementation Support
287
+ == Ruby Version Support
276
288
 
277
- Rack::Unreloader works correctly on Ruby 1.8.7+, JRuby 9.1+, and Rubinius. It
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
- F = ::File
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 not yet been loaded).
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 the last modified time (or nil if the directory has not
26
- # yet been loaded), an array of files in the directory, and a block to pass to
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 F.directory?(path)
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 F.file?(file)
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
- skip_reload = @skip_reload.map{|f| F.directory?(f) ? Unreloader.ruby_files(f) : f}
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, FORCE)
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 F.directory?(file)
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
- time, files, block = @monitor_dirs[dir]
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
- file = remove(name)
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
- F.mtime(file)
446
+ File.mtime(file)
478
447
  end
479
448
  end
480
449
  end
@@ -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
- F = ::File
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
- map{|f| F.directory?(f) ? ruby_files(f) : f}.
20
- flatten
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
- flatten.
28
- map{|path| Dir.glob(path).sort_by{|filename| filename.count('/')}}.
29
- flatten.
30
- map{|path| F.expand_path(path)}.
31
- uniq
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
- Kernel.require 'rack/unreloader/reloader'
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
- MUTEX.synchronize{reload!}
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
- def require(paths, &block)
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