rack-unreloader 2.0.0 → 2.1.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
2
  SHA256:
3
- metadata.gz: f60b8ff5b4384a7f55f92c4e851e2589622112228849b63ed7cf520b2e4b4112
4
- data.tar.gz: e01e616600037edc3eb954dd9cf7fd6c8667fa43b15590ec4bb5041547c5f4af
3
+ metadata.gz: 01a3ddc58ad8308cd61dd10991e7478f546bd7d1bc2fc4bb88203b4a37d7b335
4
+ data.tar.gz: ceff468f0b2755347859e3d2a408087379a056975e00c73fe35adc5854c3cbc0
5
5
  SHA512:
6
- metadata.gz: 5da4eebc546fcc8194667ca1c1176574f6299a2bef8b062f24edf09146fc151f234b8119af9c19120f5245a5b921cb6841bba29c4bfc20abdc5d229ac040305a
7
- data.tar.gz: f7fcb791cb9a0d8f585444ba7bddc4f2ffd80759c4544c8a450c8ebeb38b8620bf24cd05c6a368921c395c9de2ddb1a60f6a31a26a30d76df2d458de35355904
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2021 Jeremy Evans
1
+ Copyright (c) 2014-2023 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
@@ -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={})
@@ -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 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 @@ 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
- 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
@@ -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.0.0
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: 2022-06-23 00:00:00.000000000 Z
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.3.7
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: []