rack-unreloader 2.0.0 → 2.1.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 +4 -4
- data/CHANGELOG +6 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +40 -1
- data/lib/rack/unreloader/autoload_reloader.rb +89 -0
- data/lib/rack/unreloader/reloader.rb +0 -3
- data/lib/rack/unreloader.rb +83 -3
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01a3ddc58ad8308cd61dd10991e7478f546bd7d1bc2fc4bb88203b4a37d7b335
|
4
|
+
data.tar.gz: ceff468f0b2755347859e3d2a408087379a056975e00c73fe35adc5854c3cbc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71c0dc1e1ebfcad459d8bf6d1533200ad071f857fe2b53fb7f26af1e079169eca3835b2b1eee4a39d572244bf3208bd51718120f7e89f4dcd5b646cbed6195ca
|
7
|
+
data.tar.gz: d9e9ab98a9b36a44d0d819f0142a1c9c49a21598a390da18c7cee48199cd365a0acac7c4fd95955e49b3f18a7ef8e0fe75bfa138312e3b2d16f57ebc7574731f
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
= 2.1.0 (2023-01-18)
|
2
|
+
|
3
|
+
* Add reload? and autoload? methods for determining what mode the unreloader is operating in (jeremyevans)
|
4
|
+
|
5
|
+
* Support :autoload option and autoload method for autoloading files and directories (jeremyevans)
|
6
|
+
|
1
7
|
= 2.0.0 (2022-06-23)
|
2
8
|
|
3
9
|
* Fix TypeError being raised when requiring a file results in an error (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -195,7 +195,7 @@ class.
|
|
195
195
|
|
196
196
|
== Requiring
|
197
197
|
|
198
|
-
Rack::Unreloader#require is a little different than require in that it takes
|
198
|
+
Rack::Unreloader#require is a little different than Kernel#require in that it takes
|
199
199
|
a file glob, not a normal require path. For that reason, you must specify
|
200
200
|
the extension when requiring the file, and it will only look in the current
|
201
201
|
directory by default:
|
@@ -253,6 +253,45 @@ decide that instead of specifying the constants, ObjectSpace should be used to
|
|
253
253
|
automatically determine the constants loaded. You can specify this by having the
|
254
254
|
block return the :ObjectSpace symbol.
|
255
255
|
|
256
|
+
=== Autoload
|
257
|
+
|
258
|
+
To further speed things up in development mode, or when only running a subset of
|
259
|
+
tests, it can be helpful to autoload files instead of require them, so that if
|
260
|
+
the related constants are not accessed, you don't need to pay the cost of loading
|
261
|
+
the related files. To enable autoloading, pass the +:autoload+ option when
|
262
|
+
creating the reloader:
|
263
|
+
|
264
|
+
Unreloader = Rack::Unreloader.new(autoload: true){App}
|
265
|
+
|
266
|
+
Then, you can call +autoload+ instead of +require+:
|
267
|
+
|
268
|
+
Unreloader.autoload('models'){|f| File.basename(f).sub(/\.rb\z/, '').capitalize}
|
269
|
+
|
270
|
+
This will monitor the models directory for files, setting up autoloads for each
|
271
|
+
file. After the file has been loaded, normal reloading will happen for the
|
272
|
+
file. Note that for +autoload+, a block is required because the constant names
|
273
|
+
are needed before loading the file to setup the autoload.
|
274
|
+
|
275
|
+
If the <tt>reload: false</tt> option is given when creating the reloader,
|
276
|
+
autoloads will still be setup by +autoload+, but no reloading will happen. This
|
277
|
+
can be useful when testing subsets of an application. When testing subsets of
|
278
|
+
an application, you don't need reloading, but you can benefit from autoloading,
|
279
|
+
so parts of the application you are not testing are not loaded.
|
280
|
+
|
281
|
+
If you do not pass the +:autoload+ option when creating the reloader, then calls
|
282
|
+
to +autoload+ will implicitly be transformed to calls to +require+. This makes
|
283
|
+
it possible to use the same +autoload+ call in all cases, and handle four
|
284
|
+
separate scenarios:
|
285
|
+
|
286
|
+
1. Autoload then reload: Fast development mode startup, loading the minimum
|
287
|
+
number of files, but reloading if those files are changed
|
288
|
+
2. Autoload without reload: Useful for faster testing of a subset of an
|
289
|
+
application, so the untested subsets is not loaded.
|
290
|
+
3. Require then reload: Slower development mode startup, but have entire
|
291
|
+
application loaded before accepting requests
|
292
|
+
4. Require without reload: Normal production/testing mode with nothing autoloaded
|
293
|
+
or reloaded
|
294
|
+
|
256
295
|
== Usage Outside Rack
|
257
296
|
|
258
297
|
While +Rack::Unreloader+ is usually in the development of rack applications,
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require_relative 'reloader'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Unreloader
|
5
|
+
class AutoloadReloader < Reloader
|
6
|
+
def initialize(opts={})
|
7
|
+
super
|
8
|
+
|
9
|
+
# Files that autoloads have been setup for, but have not yet been loaded.
|
10
|
+
# Hash with realpath keys and values that are arrays with the
|
11
|
+
# a block that will return constant name strings that will autoload the
|
12
|
+
# file, the modified time the file, and the delete hook.
|
13
|
+
@autoload_files = {}
|
14
|
+
|
15
|
+
# Directories where new files will be setup for autoloading.
|
16
|
+
# Uses same format as @monitor_dirs.
|
17
|
+
@autoload_dirs = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def autoload_dependencies(paths, opts={}, &block)
|
21
|
+
delete_hook = opts[:delete_hook]
|
22
|
+
|
23
|
+
Unreloader.expand_paths(paths).each do |file|
|
24
|
+
if File.directory?(file)
|
25
|
+
@autoload_dirs[file] = [nil, [], block, delete_hook]
|
26
|
+
check_autoload_dir(file)
|
27
|
+
else
|
28
|
+
# Comparisons against $LOADED_FEATURES need realpaths
|
29
|
+
@autoload_files[File.realpath(file)] = [block, modified_at(file), delete_hook]
|
30
|
+
Unreloader.autoload_constants(yield(file), file, @logger)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_autoload(file, strings)
|
36
|
+
strings = Array(strings)
|
37
|
+
log("Removing autoload for #{file}: #{strings.join(" ")}") unless strings.empty?
|
38
|
+
strings.each do |s|
|
39
|
+
obj, mod = Unreloader.split_autoload(s)
|
40
|
+
# Assume that if the autoload string was valid to create the
|
41
|
+
# autoload, it is still valid when removing the autoload.
|
42
|
+
obj.send(:remove_const, mod)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_autoload_dir(dir)
|
47
|
+
subdir_times, files, block, delete_hook = md = @autoload_dirs[dir]
|
48
|
+
return if subdir_times && subdir_times.all?{|subdir, time| File.directory?(subdir) && modified_at(subdir) == time}
|
49
|
+
md[0] = subdir_times(dir)
|
50
|
+
|
51
|
+
cur_files = Unreloader.ruby_files(dir)
|
52
|
+
return if files == cur_files
|
53
|
+
|
54
|
+
removed_files = files - cur_files
|
55
|
+
new_files = cur_files - files
|
56
|
+
|
57
|
+
# Removed files that were never required should have the constant removed
|
58
|
+
# so that accesses to the constant do not attempt to autoload a file that
|
59
|
+
# no longer exists.
|
60
|
+
removed_files.each do |file|
|
61
|
+
remove_autoload(file, block.call(file)) unless @monitor_files[file]
|
62
|
+
end
|
63
|
+
|
64
|
+
# New files not yet loaded should have autoloads added for them.
|
65
|
+
autoload_dependencies(new_files, :delete_hook=>delete_hook, &block) unless new_files.empty?
|
66
|
+
|
67
|
+
files.replace(cur_files)
|
68
|
+
end
|
69
|
+
|
70
|
+
def reload!
|
71
|
+
(@autoload_files.keys & $LOADED_FEATURES).each do |file|
|
72
|
+
# Files setup for autoloads were autoloaded, move metadata to locations
|
73
|
+
# used for required files.
|
74
|
+
log("Autoloaded file required, setting up reloading: #{file}")
|
75
|
+
block, *metadata = @autoload_files.delete(file)
|
76
|
+
@constants_defined[file] = block
|
77
|
+
@monitor_files[file] = metadata
|
78
|
+
@files[file] = {:features=>Set.new, :constants=>Array(block.call(file))}
|
79
|
+
end
|
80
|
+
|
81
|
+
@autoload_dirs.each_key do |dir|
|
82
|
+
check_autoload_dir(dir)
|
83
|
+
end
|
84
|
+
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -5,9 +5,6 @@ module Rack
|
|
5
5
|
class Reloader
|
6
6
|
File = ::File
|
7
7
|
|
8
|
-
# Regexp for valid constant names, to prevent code execution.
|
9
|
-
VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
|
10
|
-
|
11
8
|
# Setup the reloader. Supports :logger and :subclasses options, see
|
12
9
|
# Rack::Unloader.new for details.
|
13
10
|
def initialize(opts={})
|
data/lib/rack/unreloader.rb
CHANGED
@@ -8,9 +8,12 @@ module Rack
|
|
8
8
|
# Mutex used to synchronize reloads
|
9
9
|
MUTEX = Monitor.new
|
10
10
|
|
11
|
-
# Reference to ::File as File
|
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 @@ module Rack
|
|
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 @@ module Rack
|
|
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
|
-
|
67
|
-
|
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
|
@@ -87,6 +137,18 @@ module Rack
|
|
87
137
|
@app_block.call.call(env)
|
88
138
|
end
|
89
139
|
|
140
|
+
# Whether the unreloader is setup for reloading. If false, no reloading
|
141
|
+
# is done after the initial require.
|
142
|
+
def reload?
|
143
|
+
!!@reloader
|
144
|
+
end
|
145
|
+
|
146
|
+
# Whether the unreloader is setup for autoloading. If false, autoloads
|
147
|
+
# are treated as requires.
|
148
|
+
def autoload?
|
149
|
+
!!@autoload
|
150
|
+
end
|
151
|
+
|
90
152
|
# Add a file glob or array of file globs to monitor for changes.
|
91
153
|
# Options:
|
92
154
|
# :delete_hook :: When a file being monitored is deleted, call
|
@@ -99,6 +161,24 @@ module Rack
|
|
99
161
|
end
|
100
162
|
end
|
101
163
|
|
164
|
+
# Add a file glob or array of file global to autoload and monitor
|
165
|
+
# for changes. A block is required. It will be called with the
|
166
|
+
# path to be autoloaded, and should return the symbol for the
|
167
|
+
# constant name to autoload. Accepts the same options as #require.
|
168
|
+
def autoload(paths, opts={}, &block)
|
169
|
+
raise ArgumentError, "block required" unless block
|
170
|
+
|
171
|
+
if @autoload
|
172
|
+
if @reloader
|
173
|
+
@reloader.autoload_dependencies(paths, opts, &block)
|
174
|
+
else
|
175
|
+
Unreloader.expand_directory_paths(paths).each{|f| Unreloader.autoload_constants(yield(f), f, @logger)}
|
176
|
+
end
|
177
|
+
else
|
178
|
+
require(paths, opts, &block)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
102
182
|
# Records that each path in +files+ depends on +dependency+. If there
|
103
183
|
# is a modification to +dependency+, all related files will be reloaded
|
104
184
|
# after +dependency+ is reloaded. Both +dependency+ and each entry in +files+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-unreloader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -67,6 +67,7 @@ files:
|
|
67
67
|
- MIT-LICENSE
|
68
68
|
- README.rdoc
|
69
69
|
- lib/rack/unreloader.rb
|
70
|
+
- lib/rack/unreloader/autoload_reloader.rb
|
70
71
|
- lib/rack/unreloader/reloader.rb
|
71
72
|
homepage: http://github.com/jeremyevans/rack-unreloader
|
72
73
|
licenses:
|
@@ -76,7 +77,7 @@ metadata:
|
|
76
77
|
changelog_uri: https://github.com/jeremyevans/rack-unreloader/blob/master/CHANGELOG
|
77
78
|
mailing_list_uri: https://github.com/jeremyevans/rack-unreloader/discussions
|
78
79
|
source_code_uri: https://github.com/jeremyevans/rack-unreloader
|
79
|
-
post_install_message:
|
80
|
+
post_install_message:
|
80
81
|
rdoc_options:
|
81
82
|
- "--quiet"
|
82
83
|
- "--line-numbers"
|
@@ -98,8 +99,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
99
|
- !ruby/object:Gem::Version
|
99
100
|
version: '0'
|
100
101
|
requirements: []
|
101
|
-
rubygems_version: 3.
|
102
|
-
signing_key:
|
102
|
+
rubygems_version: 3.4.1
|
103
|
+
signing_key:
|
103
104
|
specification_version: 4
|
104
105
|
summary: Reload application when files change, unloading constants first
|
105
106
|
test_files: []
|