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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66e3e322ccf2b92d128fb3bf904426cff77ba86e88ec47eb8ba9c5101dd0ae28
4
- data.tar.gz: 5495ff350b972c47e6c992f744245d12baa57c0929d7878241f6c9a91922f17c
3
+ metadata.gz: f174cf5ba8b7da4a2a70310c1adffc4a5e4b15c7f2c37cfba8a28e341b6c2c77
4
+ data.tar.gz: d3c95deea100141f9cb797c805361cbadc8803a3d129fdae08d4a755e5cee713
5
5
  SHA512:
6
- metadata.gz: 1fd824da5c6ee8cd78342b5e0cb945353147970ebe26a4c6e16005bf8245cd5e743223441c76b7ab04216dd1757a8b3925a71b32825402d1f4becf7cbbf488c0
7
- data.tar.gz: 8f296f7561c97af7ff6863a87fb73c3ef1c2bccb6379ca4aceb5300c13ccde937d17189de93e877c6016ab76a62f12225ce04e2494e77e229f59172c5dfd0de7
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
@@ -9,6 +9,7 @@ end
9
9
 
10
10
  group :development do
11
11
  gem 'appraisal'
12
+ gem 'appraisal-run'
12
13
  gem 'benchmark-ips'
13
14
  end
14
15
 
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
- If you're using [asdf](https://asdf-vm.com/), run `./script/run_appraisal.rb` to run Rails and plain Ruby tests for all supported versions.
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?(:loader_for)
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
- parent, cname = loader.send(:autoloads)[file]
30
+ autoload_entry = if loader && loader.respond_to?(:autoloads, true)
31
+ loader.send(:autoloads)[file]
32
+ end
25
33
 
26
- if defined?(Zeitwerk::Cref) && parent.is_a?(Zeitwerk::Cref)
27
- parent.remove
28
- else
29
- parent.send(:remove_const, cname)
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
- def ruby?(path)
11
- super || Onload.process?(path)
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 Zeitwerk::Loader.instance_method(:autoload_file).arity == 2
15
- def autoload_file(cref, file)
16
- if !Onload.process?(file)
17
- if (unprocessed_file = Onload.unprocessed_file_for(file))
18
- file = unprocessed_file
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
- super
56
+ super
57
+ end
23
58
  end
24
- else
25
- def autoload_file(parent, cname, file)
26
- if Onload.process?(file)
27
- # Some older versions of Zeitwerk very naïvely try to remove only the
28
- # last 3 characters in an attempt to strip off the .rb file extension,
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
- # introduced in Zeitwerk v2.6.10
49
- def cname_for(basename, abspath)
50
- super(Onload.basename(basename), abspath)
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 Zeitwerk
56
- class Loader
57
- prepend Onload::ZeitwerkLoaderPatch
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Onload
4
- VERSION = "1.0.5"
4
+ VERSION = "1.1.1"
5
5
  end
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 :File, "onload/file"
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)
@@ -0,0 +1,5 @@
1
+ class Hello
2
+ def hello
3
+ "hello".upcase
4
+ end
5
+ end
@@ -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