onload 1.0.5 → 1.1.1
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.md +9 -0
- data/Gemfile +1 -0
- data/README.md +12 -1
- data/lib/onload/config.rb +17 -0
- data/lib/onload/core_ext/kernel_zeitwerk.rb +24 -8
- data/lib/onload/ext/zeitwerk/loader.rb +63 -32
- data/lib/onload/file.rb +10 -2
- data/lib/onload/ignore_file.rb +112 -0
- data/lib/onload/version.rb +1 -1
- data/lib/onload.rb +30 -1
- data/spec/fixtures/hello.rb +5 -0
- data/spec/rails/controllers/home_controller_spec.rb +9 -0
- data/spec/rails/dummy/log/test.log +5304 -0
- data/spec/ruby/onload_spec.rb +8 -0
- data/spec/spec_setup.rb +18 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f174cf5ba8b7da4a2a70310c1adffc4a5e4b15c7f2c37cfba8a28e341b6c2c77
|
|
4
|
+
data.tar.gz: d3c95deea100141f9cb797c805361cbadc8803a3d129fdae08d4a755e5cee713
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5ffdbe3816eb9c410cdb9749feb160d67a16da6be7fa7fff77a11f1a16091eb7b47e6dbd1bdd0613cb2d6524b0579cd1c0ece2b19a1ab8e33177857caa3a8fe
|
|
7
|
+
data.tar.gz: 744a1ace552ca478f69fc24cc68dbdbd42ea13e3368bf633f1c0c56b14358f5cbc7343c23d017dfee9e01808fb424655c286c3b7768dc97dbc84594578d496fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 1.1.1
|
|
2
|
+
* Fix issues with Zeitwerk v2.8 and later.
|
|
3
|
+
* Add support for Rails 8.1.
|
|
4
|
+
|
|
5
|
+
## 1.1.0
|
|
6
|
+
* Automatically add generated files to the specified ignore file, eg. `.gitignore`, when they are written.
|
|
7
|
+
- Specify an ignore file by setting `Onload.config.ignore_path` to the path to the ignore file you'd like to use.
|
|
8
|
+
* Use appraisal-run to run tests locally for all Ruby and Rails versions.
|
|
9
|
+
|
|
1
10
|
## 1.0.5
|
|
2
11
|
* Fix issues with Zeitwerk v2.7.3 and later.
|
|
3
12
|
* Don't attempt to autoload directories.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -46,14 +46,25 @@ Onload.install!
|
|
|
46
46
|
|
|
47
47
|
Now, the contents of any file with a .up file extension will be passed to `UpcasePreprocessor.call`. The return value will be written to a separate Ruby file and loaded instead of the original .up file.
|
|
48
48
|
|
|
49
|
+
## Ignoring Processed Files
|
|
50
|
+
|
|
51
|
+
It can often be desirable to add processed/generated files to your .gitignore file. Onload comes with a mechanism that can do exactly that. Simply set the config option before running onload:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
Onload.config.ignore_path = ".gitignore"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Now whenever files are processed, the generated file will be automatically updated.
|
|
58
|
+
|
|
49
59
|
## Running Tests
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
Install Docker, then run `./script/run_appraisals` to run Rails and plain Ruby tests for all supported versions.
|
|
52
62
|
|
|
53
63
|
Otherwise, use Appraisal to run tests for Rails or plain ruby:
|
|
54
64
|
|
|
55
65
|
1. Plain ruby: `bundle exec appraisal ruby rake spec:ruby`
|
|
56
66
|
1. Rails: `bundle exec appraisal <version> rake spec:rails`. Run `bundle exec appraisal list` to see the available versions. To run tests for Rails 7.0, try `bundle exec appraisal rails-7.0 rake spec:rails`
|
|
67
|
+
1. Note that you must be running the correct version of Ruby specified in each gemfile.
|
|
57
68
|
|
|
58
69
|
## License
|
|
59
70
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Onload
|
|
4
|
+
class Config
|
|
5
|
+
attr_accessor :ignore_path
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@ignore_path = nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def dup
|
|
12
|
+
self.class.new.tap do |duplicate|
|
|
13
|
+
duplicate.ignore_path = ignore_path.dup
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -15,18 +15,34 @@ module Kernel
|
|
|
15
15
|
# in order to load the resulting file. Otherwise you get an error about
|
|
16
16
|
# an uninitialized constant, and it's like... yeah, I _know_ it's
|
|
17
17
|
# uninitialized, that's why I'm loading this file. Whatevs.
|
|
18
|
-
loader = if Zeitwerk::Registry.respond_to?(:
|
|
18
|
+
loader = if Zeitwerk::Registry.respond_to?(:autoloads)
|
|
19
|
+
autoloads = Zeitwerk::Registry.autoloads
|
|
20
|
+
|
|
21
|
+
if autoloads.respond_to?(:registered?)
|
|
22
|
+
autoloads.registered?(file)
|
|
23
|
+
else
|
|
24
|
+
autoloads[file]
|
|
25
|
+
end
|
|
26
|
+
elsif Zeitwerk::Registry.respond_to?(:loader_for)
|
|
19
27
|
Zeitwerk::Registry.loader_for(file)
|
|
20
|
-
else
|
|
21
|
-
Zeitwerk::Registry.autoloads.registered?(file)
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
autoload_entry = if loader && loader.respond_to?(:autoloads, true)
|
|
31
|
+
loader.send(:autoloads)[file]
|
|
32
|
+
end
|
|
25
33
|
|
|
26
|
-
if
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
if autoload_entry
|
|
35
|
+
if defined?(Zeitwerk::Cref) && autoload_entry.is_a?(Zeitwerk::Cref)
|
|
36
|
+
autoload_entry.remove
|
|
37
|
+
else
|
|
38
|
+
parent, cname = autoload_entry
|
|
39
|
+
|
|
40
|
+
if defined?(Zeitwerk::Cref) && parent.is_a?(Zeitwerk::Cref)
|
|
41
|
+
parent.remove
|
|
42
|
+
else
|
|
43
|
+
parent.send(:remove_const, cname)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
30
46
|
end
|
|
31
47
|
|
|
32
48
|
return onload_orig_load(f.outfile, *args)
|
|
@@ -4,40 +4,63 @@ require "zeitwerk"
|
|
|
4
4
|
require "zeitwerk/loader"
|
|
5
5
|
|
|
6
6
|
module Onload
|
|
7
|
+
module ZeitwerkLoaderHelpers
|
|
8
|
+
class << self
|
|
9
|
+
def method_exists?(method_name)
|
|
10
|
+
Zeitwerk::Loader.method_defined?(method_name) ||
|
|
11
|
+
Zeitwerk::Loader.private_method_defined?(method_name)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
7
16
|
module ZeitwerkLoaderPatch
|
|
8
17
|
private
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
if Onload::ZeitwerkLoaderHelpers.method_exists?(:ruby?)
|
|
20
|
+
def ruby?(path)
|
|
21
|
+
super || Onload.process?(path)
|
|
22
|
+
end
|
|
12
23
|
end
|
|
13
24
|
|
|
14
|
-
if
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
|
|
25
|
+
if Onload::ZeitwerkLoaderHelpers.method_exists?(:autoload_file)
|
|
26
|
+
if Zeitwerk::Loader.instance_method(:autoload_file).arity == 2
|
|
27
|
+
def autoload_file(cref, file)
|
|
28
|
+
if !Onload.process?(file)
|
|
29
|
+
if (unprocessed_file = Onload.unprocessed_file_for(file))
|
|
30
|
+
file = unprocessed_file
|
|
31
|
+
end
|
|
19
32
|
end
|
|
33
|
+
|
|
34
|
+
super
|
|
20
35
|
end
|
|
36
|
+
else
|
|
37
|
+
def autoload_file(parent, cname, file)
|
|
38
|
+
if Onload.process?(file)
|
|
39
|
+
# Some older versions of Zeitwerk very naively try to remove only the
|
|
40
|
+
# last 3 characters in an attempt to strip off the .rb file extension,
|
|
41
|
+
# while newer ones only remove it if it's actually there. This line is
|
|
42
|
+
# necessary to remove the trailing leftover period for older versions,
|
|
43
|
+
# and remove the entire extension for newer versions. Although cname
|
|
44
|
+
# means "constant name," we use Onload.basename to remove all residual
|
|
45
|
+
# file extensions that were left over from the conversion from a file
|
|
46
|
+
# name to a cname.
|
|
47
|
+
cname = Onload.basename(cname.to_s).to_sym
|
|
48
|
+
else
|
|
49
|
+
# if there is a corresponding unprocessed file, autoload it instead of
|
|
50
|
+
# the .rb file
|
|
51
|
+
if (unprocessed_file = Onload.unprocessed_file_for(file))
|
|
52
|
+
file = unprocessed_file
|
|
53
|
+
end
|
|
54
|
+
end
|
|
21
55
|
|
|
22
|
-
|
|
56
|
+
super
|
|
57
|
+
end
|
|
23
58
|
end
|
|
24
|
-
|
|
25
|
-
def
|
|
26
|
-
if Onload.process?(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# while newer ones only remove it if it's actually there. This line is
|
|
30
|
-
# necessary to remove the trailing leftover period for older versions,
|
|
31
|
-
# and remove the entire extension for newer versions. Although cname
|
|
32
|
-
# means "constant name," we use Onload.basename to remove all residual
|
|
33
|
-
# file extensions that were left over from the conversion from a file
|
|
34
|
-
# name to a cname.
|
|
35
|
-
cname = Onload.basename(cname.to_s).to_sym
|
|
36
|
-
else
|
|
37
|
-
# if there is a corresponding unprocessed file, autoload it instead of
|
|
38
|
-
# the .rb file
|
|
39
|
-
if (unprocessed_file = Onload.unprocessed_file_for(file))
|
|
40
|
-
file = unprocessed_file
|
|
59
|
+
elsif Onload::ZeitwerkLoaderHelpers.method_exists?(:define_autoload)
|
|
60
|
+
def define_autoload(cref, abspath)
|
|
61
|
+
if !::File.directory?(abspath) && !Onload.process?(abspath)
|
|
62
|
+
if (unprocessed_file = Onload.unprocessed_file_for(abspath))
|
|
63
|
+
abspath = unprocessed_file
|
|
41
64
|
end
|
|
42
65
|
end
|
|
43
66
|
|
|
@@ -45,15 +68,23 @@ module Onload
|
|
|
45
68
|
end
|
|
46
69
|
end
|
|
47
70
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
if Onload::ZeitwerkLoaderHelpers.method_exists?(:cname_for)
|
|
72
|
+
# introduced in Zeitwerk v2.6.10
|
|
73
|
+
def cname_for(basename, abspath)
|
|
74
|
+
super(Onload.basename(basename), abspath)
|
|
75
|
+
end
|
|
51
76
|
end
|
|
52
77
|
end
|
|
53
|
-
end
|
|
54
78
|
|
|
55
|
-
module
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
module ZeitwerkFileSystemPatch
|
|
80
|
+
def rb_extension?(path)
|
|
81
|
+
super || Onload.process?(path)
|
|
82
|
+
end
|
|
58
83
|
end
|
|
59
84
|
end
|
|
85
|
+
|
|
86
|
+
Zeitwerk::Loader.prepend(Onload::ZeitwerkLoaderPatch)
|
|
87
|
+
|
|
88
|
+
if defined?(Zeitwerk::Loader::FileSystem)
|
|
89
|
+
Zeitwerk::Loader::FileSystem.prepend(Onload::ZeitwerkFileSystemPatch)
|
|
90
|
+
end
|
data/lib/onload/file.rb
CHANGED
|
@@ -8,14 +8,22 @@ module Onload
|
|
|
8
8
|
@path = path
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def write
|
|
11
|
+
def write(ignore_file: nil)
|
|
12
12
|
source = ::File.read(path)
|
|
13
13
|
|
|
14
14
|
::File.extname(path).scan(/\.\w+/).each do |ext|
|
|
15
15
|
source = Onload.processors[ext].call(source)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
::File.write(outfile, source)
|
|
18
|
+
::File.write(outfile, source).tap do
|
|
19
|
+
if ignore_file
|
|
20
|
+
ignore_file.add(outfile)
|
|
21
|
+
elsif Onload.config.ignore_path
|
|
22
|
+
ignore_file = IgnoreFile.load(Onload.config.ignore_path)
|
|
23
|
+
ignore_file.add(outfile)
|
|
24
|
+
ignore_file.persist!
|
|
25
|
+
end
|
|
26
|
+
end
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def outfile
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Onload
|
|
6
|
+
class MalformedIgnoreFileError < StandardError; end
|
|
7
|
+
|
|
8
|
+
class IgnoreFile
|
|
9
|
+
ONLOAD_SECTION_START = "##### ONLOAD BUILD ARTIFACTS (AUTO-GENERATED) #####"
|
|
10
|
+
ONLOAD_SECTION_STOP = "##### END ONLOAD BUILD ARTIFACTS #####"
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def load(manifest_path)
|
|
14
|
+
manifest_path = ::File.expand_path(manifest_path)
|
|
15
|
+
lines = ::File.read(manifest_path).split(/\r?\n/)
|
|
16
|
+
start_idx = lines.index(ONLOAD_SECTION_START)
|
|
17
|
+
stop_idx = lines.index(ONLOAD_SECTION_STOP)
|
|
18
|
+
|
|
19
|
+
# Start and stop indices should either both be numbers or both be nil.
|
|
20
|
+
# Anything else, and something weird is going on.
|
|
21
|
+
if start_idx.class != stop_idx.class
|
|
22
|
+
raise MalformedIgnoreFileError, "the ignore file at #{manifest_path} appears to be malformed and onload doesn't know how to modify it"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ignored_paths = if start_idx && stop_idx
|
|
26
|
+
lines[start_idx..stop_idx].map(&:strip)
|
|
27
|
+
else
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
ignored_paths.reject! do |path|
|
|
32
|
+
path.empty? || path.start_with?("#")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
new(manifest_path, Set.new(ignored_paths))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :manifest_path, :dirty
|
|
40
|
+
|
|
41
|
+
alias dirty? dirty
|
|
42
|
+
|
|
43
|
+
def initialize(manifest_path, ignored_paths)
|
|
44
|
+
@manifest_path = manifest_path
|
|
45
|
+
@ignored_paths = ignored_paths
|
|
46
|
+
@dirty = false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def includes?(path)
|
|
50
|
+
@ignored_paths.include?(path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
alias include? includes?
|
|
54
|
+
|
|
55
|
+
def add(path)
|
|
56
|
+
ignored_path = ::File.expand_path(path)
|
|
57
|
+
ignored_path_segments = ignored_path.split(::File::SEPARATOR)
|
|
58
|
+
|
|
59
|
+
if ignored_path_segments[0...manifest_dirname_segments.size] != manifest_dirname_segments
|
|
60
|
+
raise "file to ignore #{path} is not relative to the specified ignore file at #{manifest_path}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
relative_ignored_path = ignored_path_segments[manifest_dirname_segments.size..-1].join(::File::SEPARATOR)
|
|
64
|
+
@ignored_paths << relative_ignored_path
|
|
65
|
+
|
|
66
|
+
@dirty = true
|
|
67
|
+
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def persist!
|
|
72
|
+
return unless dirty?
|
|
73
|
+
|
|
74
|
+
lines = ::File.read(manifest_path).split(/\r?\n/)
|
|
75
|
+
start_idx = lines.index(ONLOAD_SECTION_START)
|
|
76
|
+
stop_idx = lines.index(ONLOAD_SECTION_STOP)
|
|
77
|
+
|
|
78
|
+
if start_idx && stop_idx
|
|
79
|
+
lines[start_idx..stop_idx] = [
|
|
80
|
+
ONLOAD_SECTION_START,
|
|
81
|
+
*@ignored_paths,
|
|
82
|
+
ONLOAD_SECTION_STOP
|
|
83
|
+
]
|
|
84
|
+
else
|
|
85
|
+
lines += [
|
|
86
|
+
"",
|
|
87
|
+
ONLOAD_SECTION_START,
|
|
88
|
+
*@ignored_paths,
|
|
89
|
+
ONLOAD_SECTION_STOP,
|
|
90
|
+
""
|
|
91
|
+
]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
contents = lines.join("\n")
|
|
95
|
+
contents << "\n" unless contents.end_with?("\n")
|
|
96
|
+
|
|
97
|
+
::File.write(manifest_path, contents)
|
|
98
|
+
|
|
99
|
+
@dirty = false
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def manifest_dirname
|
|
105
|
+
@manifest_dirname ||= ::File.dirname(manifest_path)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def manifest_dirname_segments
|
|
109
|
+
@manifest_diranme_segments ||= manifest_dirname.split(::File::SEPARATOR)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/onload/version.rb
CHANGED
data/lib/onload.rb
CHANGED
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
module Onload
|
|
4
4
|
UNLOADABLE_EXTENSIONS = %w(.bundle .so .dll).freeze
|
|
5
5
|
|
|
6
|
-
autoload :
|
|
6
|
+
autoload :Config, "onload/config"
|
|
7
|
+
autoload :File, "onload/file"
|
|
8
|
+
autoload :IgnoreFile, "onload/ignore_file"
|
|
7
9
|
|
|
8
10
|
class << self
|
|
9
11
|
attr_accessor :enabled
|
|
10
12
|
alias enabled? enabled
|
|
11
13
|
|
|
14
|
+
attr_reader :installed
|
|
15
|
+
alias installed? installed
|
|
16
|
+
|
|
12
17
|
def register(extension, processor_klass)
|
|
13
18
|
processors[extension] = processor_klass
|
|
14
19
|
end
|
|
@@ -41,6 +46,8 @@ module Onload
|
|
|
41
46
|
else
|
|
42
47
|
require "onload/ext/bootsnap/autoload"
|
|
43
48
|
end
|
|
49
|
+
|
|
50
|
+
@installed = true
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
def process?(path)
|
|
@@ -105,6 +112,27 @@ module Onload
|
|
|
105
112
|
@glob ||= "*{#{each_extension.to_a.join(",")}}"
|
|
106
113
|
end
|
|
107
114
|
|
|
115
|
+
def config
|
|
116
|
+
@config ||= Config.new
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def with_config(override_hash)
|
|
120
|
+
new_config = config.dup.tap do |new_config|
|
|
121
|
+
override_hash.each_pair do |k, v|
|
|
122
|
+
new_config.send("#{k}=", v)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
old_config = config
|
|
127
|
+
@config = new_config
|
|
128
|
+
|
|
129
|
+
yield
|
|
130
|
+
|
|
131
|
+
nil
|
|
132
|
+
ensure
|
|
133
|
+
@config = old_config
|
|
134
|
+
end
|
|
135
|
+
|
|
108
136
|
private
|
|
109
137
|
|
|
110
138
|
def path_cache
|
|
@@ -113,6 +141,7 @@ module Onload
|
|
|
113
141
|
end
|
|
114
142
|
|
|
115
143
|
self.enabled = true
|
|
144
|
+
@installed = false
|
|
116
145
|
end
|
|
117
146
|
|
|
118
147
|
if Kernel.const_defined?(:Rails)
|
|
@@ -13,6 +13,15 @@ describe HomeController, type: :request do
|
|
|
13
13
|
)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
it "adds an entry to the specified ignore file" do
|
|
17
|
+
Onload.with_config(ignore_path: @ignore_path) do
|
|
18
|
+
get "/"
|
|
19
|
+
|
|
20
|
+
ignore_file = Onload::IgnoreFile.load(@ignore_path)
|
|
21
|
+
expect(ignore_file).to include("fixtures/hello.rb")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
16
25
|
it "allows hot reloading" do
|
|
17
26
|
get "/"
|
|
18
27
|
|