onload 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +17 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/Rakefile +21 -0
- data/lib/onload/core_ext/kernel.rb +68 -0
- data/lib/onload/core_ext/kernel_zeitwerk.rb +61 -0
- data/lib/onload/ext/activesupport/dependencies.rb +48 -0
- data/lib/onload/ext/bootsnap/autoload.rb +28 -0
- data/lib/onload/ext/zeitwerk/loader.rb +42 -0
- data/lib/onload/file.rb +28 -0
- data/lib/onload/railtie.rb +27 -0
- data/lib/onload/tasks/transpile.rake +10 -0
- data/lib/onload/tasks/transpile_rails.rake +17 -0
- data/lib/onload/version.rb +5 -0
- data/lib/onload.rb +118 -0
- data/onload.gemspec +16 -0
- data/spec/fixtures/hello.rb.up +5 -0
- data/spec/rails/controllers/home_controller_spec.rb +42 -0
- data/spec/rails/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/rails/dummy/app/controllers/home_controller.rb +4 -0
- data/spec/rails/dummy/app/views/home/index.html.erb +1 -0
- data/spec/rails/dummy/app/views/layouts/application.html.erb +1 -0
- data/spec/rails/dummy/config/application.rb +15 -0
- data/spec/rails/dummy/config/routes.rb +3 -0
- data/spec/rails/dummy/config/secrets.yml +2 -0
- data/spec/rails/dummy/log/test.log +68081 -0
- data/spec/rails/spec_helper.rb +32 -0
- data/spec/ruby/onload_spec.rb +17 -0
- data/spec/ruby/spec_helper.rb +8 -0
- data/spec/spec_setup.rb +41 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 51cfb4df59e2a2560f42b4e8e12635bba647e53c49f0246be381bf0b4202461e
|
4
|
+
data.tar.gz: 48baf94964ddd076934dd84b36338cec01ff8446a023ef0aca703a84c269ed6c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b4335856e653c8022fae229cae2ecb880dbb6a36ef3340bb8c2685fa44cd7b6874efbd6a632d4134ae6f7e1377dd6929208eb122b78eb62c7c30a46f46d0d700
|
7
|
+
data.tar.gz: cafd4311f223a8f8a29eefad15743c164a39f6904e38d7564e87d978d6feb1e765ed99cfe9c178f1310302aa37585f77e7012c201b1dda144b7ca89383340fb8
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Cameron Dutro
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
## onload
|
2
|
+
|
3
|
+
![Tests](https://github.com/camertron/onload/actions/workflows/test.yml/badge.svg?branch=main)
|
4
|
+
|
5
|
+
A preprocessor system for Ruby.
|
6
|
+
|
7
|
+
## Intro
|
8
|
+
|
9
|
+
Onload makes it possible to preprocess Ruby files before they are loaded into the interpreter. It works with plain 'ol Ruby code, within Rails, or wherever Zeitwerk is used.
|
10
|
+
|
11
|
+
### What is preprocessing?
|
12
|
+
|
13
|
+
Preprocessing has been around for a long time in the C world. The idea is to be able to compile the code differently depending on operating system, architecture, etc. In interpreted languages, preprocessing is most useful for transpilation, i.e. converting code from one dialect to another. Maybe the most familiar example of this is translating TypeScript to JavaScript. The JavaScript interpreters inside web browsers and Node.js can't run TypeScript directly - it has to be converted into JavaScript first.
|
14
|
+
|
15
|
+
In the JavaScript ecosystem it's very common for your code to pass through a build step, but not so in Ruby. That's where onload comes in.
|
16
|
+
|
17
|
+
### Why onload
|
18
|
+
|
19
|
+
Onload lets you transform Ruby code just before it's loaded into the Ruby interpreter. You give it a file extension and a callable object, and it does the rest. Onload is the transpilation system behind [rux](https://github.com/camertron/rux), a tool that let's you write HTML tags inside your [view components](https://viewcomponent.org) (think if it like jsx for Ruby).
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Let's write an (admittedly contrived) preprocessor that upcases literal strings in Ruby files. We'll use the file extension .up to indicate which files to process.
|
24
|
+
|
25
|
+
Preprocessors can be any Ruby object that responds to the `#call` method.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class UpcasePreprocessor
|
29
|
+
def self.call(source)
|
30
|
+
source.gsub(/(\"\w+\")/, '\1.upcase')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Next we'll tell onload about our preprocessor.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
Onload.register(".up", UpcasePreprocessor)
|
39
|
+
```
|
40
|
+
|
41
|
+
Finally, we'll load the necessary monkeypatches by "installing" onload into the interpreter. In Rails environments you can skip this step, as it is done for you via the included railtie.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Onload.install!
|
45
|
+
```
|
46
|
+
|
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
|
+
|
49
|
+
## Running Tests
|
50
|
+
|
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.
|
52
|
+
|
53
|
+
Otherwise, use Appraisal to run tests for Rails or plain ruby:
|
54
|
+
|
55
|
+
1. Plain ruby: `bundle exec appraisal ruby rake spec:ruby`
|
56
|
+
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`
|
57
|
+
|
58
|
+
## License
|
59
|
+
|
60
|
+
Licensed under the MIT license. See LICENSE for details.
|
61
|
+
|
62
|
+
## Authors
|
63
|
+
|
64
|
+
* Cameron C. Dutro: http://github.com/camertron
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "bundler"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "rubygems/package_task"
|
4
|
+
|
5
|
+
require "onload"
|
6
|
+
|
7
|
+
Bundler::GemHelper.install_tasks
|
8
|
+
|
9
|
+
task default: :spec
|
10
|
+
|
11
|
+
task spec: ["spec:ruby", "spec:rails"]
|
12
|
+
|
13
|
+
desc "Run specs"
|
14
|
+
RSpec::Core::RakeTask.new("spec:ruby") do |t|
|
15
|
+
t.pattern = "./spec/ruby/**/*_spec.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run Rails specs"
|
19
|
+
RSpec::Core::RakeTask.new("spec:rails") do |t|
|
20
|
+
t.pattern = "./spec/rails/**/*_spec.rb"
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Onload
|
4
|
+
module KernelLoadPatch
|
5
|
+
def load(file, *args)
|
6
|
+
# ActiveSupport::Dependencies adds an extra .rb to the end
|
7
|
+
if Onload.process?(file.chomp('.rb'))
|
8
|
+
file = file.chomp('.rb')
|
9
|
+
end
|
10
|
+
|
11
|
+
if Onload.process?(file) && Onload.enabled?
|
12
|
+
f = Onload::File.new(file)
|
13
|
+
f.write
|
14
|
+
|
15
|
+
return super(f.outfile, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
super(file, *args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module KernelRequirePatch
|
23
|
+
def require(file)
|
24
|
+
# check to see if there's an unprocessed file somewhere on the load path
|
25
|
+
to_load = nil
|
26
|
+
|
27
|
+
if ::File.absolute_path(file) == file
|
28
|
+
to_load = Onload.unprocessed_file_for(file)
|
29
|
+
elsif file.start_with?(".#{::File::SEPARATOR}")
|
30
|
+
abs_path = ::File.expand_path(file)
|
31
|
+
to_load = Onload.unprocessed_file_for(abs_path)
|
32
|
+
else
|
33
|
+
[$LOAD_PATH[-1]].each do |lp|
|
34
|
+
check_path = ::File.expand_path(::File.join(lp, file))
|
35
|
+
|
36
|
+
if (unprocessed_file = Onload.unprocessed_file_for(check_path))
|
37
|
+
to_load = unprocessed_file
|
38
|
+
break
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
return super(file) unless to_load
|
44
|
+
return false if $LOADED_FEATURES.include?(to_load)
|
45
|
+
|
46
|
+
# Must call the Kernel.load class method here because that's the one
|
47
|
+
# activesupport doesn't mess with, and in fact the one activesupport
|
48
|
+
# itself uses to actually load files. In case you were curious,
|
49
|
+
# activesupport redefines Object#load and Object#require i.e. the
|
50
|
+
# instance versions that get inherited by all other objects. Yeah,
|
51
|
+
# it's pretty awful stuff. Although honestly we're not much better lol.
|
52
|
+
Kernel.load(to_load)
|
53
|
+
$LOADED_FEATURES << to_load
|
54
|
+
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module Kernel
|
61
|
+
class << self
|
62
|
+
prepend Onload::KernelLoadPatch
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Object
|
67
|
+
prepend Onload::KernelRequirePatch
|
68
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
alias_method :onload_orig_require, :require
|
7
|
+
alias_method :onload_orig_load, :load
|
8
|
+
|
9
|
+
def load(file, *args)
|
10
|
+
if Onload.process?(file) && Onload.enabled?
|
11
|
+
f = Onload::File.new(file)
|
12
|
+
f.write
|
13
|
+
|
14
|
+
# I don't understand why, but it's necessary to delete the constant
|
15
|
+
# in order to load the resulting file. Otherwise you get an error about
|
16
|
+
# an uninitialized constant, and it's like... yeah, I _know_ it's
|
17
|
+
# uninitialized, that's why I'm loading this file. Whatevs.
|
18
|
+
loader = Zeitwerk::Registry.loader_for(file)
|
19
|
+
parent, cname = loader.send(:autoloads)[file]
|
20
|
+
parent.send(:remove_const, cname)
|
21
|
+
|
22
|
+
return onload_orig_load(f.outfile, *args)
|
23
|
+
end
|
24
|
+
|
25
|
+
onload_orig_load(file, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def require(file)
|
29
|
+
to_load = nil
|
30
|
+
|
31
|
+
if File.absolute_path(file) == file
|
32
|
+
to_load = Onload.unprocessed_file_for(file)
|
33
|
+
elsif file.start_with?(".#{File::SEPARATOR}")
|
34
|
+
abs_path = File.expand_path(file)
|
35
|
+
to_load = Onload.unprocessed_file_for(abs_path)
|
36
|
+
else
|
37
|
+
$LOAD_PATH.each do |lp|
|
38
|
+
check_path = File.expand_path(File.join(lp, file))
|
39
|
+
|
40
|
+
if (unprocessed_file = Onload.unprocessed_file_for(check_path))
|
41
|
+
to_load = unprocessed_file
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
unless to_load
|
48
|
+
# This will be either Ruby's original require or bootsnap's monkeypatched
|
49
|
+
# require in setups that use bootsnap. Lord help us with all these layers
|
50
|
+
# of patches.
|
51
|
+
return onload_orig_require(file)
|
52
|
+
end
|
53
|
+
|
54
|
+
return false if $LOADED_FEATURES.include?(to_load)
|
55
|
+
|
56
|
+
load to_load
|
57
|
+
$LOADED_FEATURES << to_load
|
58
|
+
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/dependencies"
|
4
|
+
|
5
|
+
module Onload
|
6
|
+
module ActiveSupportDependenciesPatch
|
7
|
+
# Allow activesupport to find unprocessed files.
|
8
|
+
def search_for_file(path_suffix)
|
9
|
+
autoload_paths.each do |root|
|
10
|
+
path = ::File.join(root, path_suffix)
|
11
|
+
unprocessed_path = Onload.unprocessed_file_for(path)
|
12
|
+
return unprocessed_path if unprocessed_path
|
13
|
+
end
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# For some reason, using autoload and a patched Kernel#load doesn't work
|
19
|
+
# by itself for automatically loading unprocessed files. Due to what I can
|
20
|
+
# only surmise is one of the side-effects of autoload, requiring any
|
21
|
+
# unprocessed file that's been marked by autoload will result in a NameError,
|
22
|
+
# i.e. Ruby reports the constant isn't defined. Pretty surprising considering
|
23
|
+
# we're literally in the process of _defining_ that constant. The trick is to
|
24
|
+
# essentially undo the autoload by removing the constant just before
|
25
|
+
# loading the unprocessed file that defines it.
|
26
|
+
def load_missing_constant(from_mod, const_name)
|
27
|
+
if require_path = from_mod.autoload?(const_name)
|
28
|
+
path = search_for_file(require_path)
|
29
|
+
|
30
|
+
if path && Onload.process?(path)
|
31
|
+
from_mod.send(:remove_const, const_name)
|
32
|
+
require require_path
|
33
|
+
return from_mod.const_get(const_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module ActiveSupport
|
43
|
+
module Dependencies
|
44
|
+
class << self
|
45
|
+
prepend Onload::ActiveSupportDependenciesPatch
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Onload
|
4
|
+
module BootsnapAutoloadPatch
|
5
|
+
def autoload(const, path)
|
6
|
+
# Bootsnap monkeypatches Module.autoload in order to leverage its load
|
7
|
+
# path cache, which effectively converts a relative path into an absolute
|
8
|
+
# one without incurring the cost of searching the load path.
|
9
|
+
# Unfortunately, if an unprocessed file has already been transpiled, the
|
10
|
+
# cache seems to always return the corresponding .rb file. Bootsnap's
|
11
|
+
# autoload patch passes the .rb file to Ruby's original autoload,
|
12
|
+
# effectively wiping out the previous autoload that pointed to the
|
13
|
+
# unprocessed file. To fix this we have to intercept the cache lookup and
|
14
|
+
# force autoloading the unprocessed file if one exists.
|
15
|
+
cached_path = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
16
|
+
|
17
|
+
if (unprocessed_path = Onload.unprocessed_file_for(cached_path))
|
18
|
+
return autoload_without_bootsnap(const, unprocessed_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Module
|
27
|
+
prepend Onload::BootsnapAutoloadPatch
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
require "zeitwerk/loader"
|
5
|
+
|
6
|
+
module Onload
|
7
|
+
module ZeitwerkLoaderPatch
|
8
|
+
private
|
9
|
+
|
10
|
+
def ruby?(path)
|
11
|
+
super || Onload.process?(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def autoload_file(parent, cname, file)
|
15
|
+
if Onload.process?(file)
|
16
|
+
# Some older versions of Zeitwerk very naïvely try to remove only the
|
17
|
+
# last 3 characters in an attempt to strip off the .rb file extension,
|
18
|
+
# while newer ones only remove it if it's actually there. This line is
|
19
|
+
# necessary to remove the trailing leftover period for older versions,
|
20
|
+
# and remove the entire extension for newer versions. Although cname
|
21
|
+
# means "constant name," we use Onload.basename to remove all residual
|
22
|
+
# file extensions that were left over from the conversion from a file
|
23
|
+
# name to a cname.
|
24
|
+
cname = Onload.basename(cname.to_s).to_sym
|
25
|
+
else
|
26
|
+
# if there is a corresponding unprocessed file, autoload it instead of
|
27
|
+
# the .rb file
|
28
|
+
if (unprocessed_file = Onload.unprocessed_file_for(file))
|
29
|
+
file = unprocessed_file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module Zeitwerk
|
39
|
+
class Loader
|
40
|
+
prepend Onload::ZeitwerkLoaderPatch
|
41
|
+
end
|
42
|
+
end
|
data/lib/onload/file.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Onload
|
4
|
+
class File
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def write
|
12
|
+
source = ::File.read(path)
|
13
|
+
|
14
|
+
::File.extname(path).scan(/\.\w+/).each do |ext|
|
15
|
+
source = Onload.processors[ext].call(source)
|
16
|
+
end
|
17
|
+
|
18
|
+
::File.write(outfile, source)
|
19
|
+
end
|
20
|
+
|
21
|
+
def outfile
|
22
|
+
@outfile ||= begin
|
23
|
+
base_name = "#{Onload.basename(path)}.rb"
|
24
|
+
::File.join(::File.dirname(path), base_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/railtie"
|
4
|
+
|
5
|
+
module Onload
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer "onload.initialize", before: :set_autoload_paths do |app|
|
8
|
+
Onload.install! if Onload.enabled?
|
9
|
+
|
10
|
+
if Onload.enabled? && app.config.file_watcher
|
11
|
+
paths = Set.new(app.config.eager_load_paths + app.config.autoload_paths)
|
12
|
+
|
13
|
+
dirs = paths.each_with_object({}) do |path, ret|
|
14
|
+
ret[path] = Onload.each_extension.map { |ext| ext.delete_prefix(".") }
|
15
|
+
end
|
16
|
+
|
17
|
+
app.reloaders << app.config.file_watcher.new([], dirs) do
|
18
|
+
# empty block, watcher seems to need it?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
rake_tasks do
|
24
|
+
load ::File.expand_path(::File.join(*%w(. tasks transpile_rails.rake)), __dir__)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
namespace :onload do
|
6
|
+
task transpile: :environment do
|
7
|
+
config = Rails.application.config
|
8
|
+
paths = Set.new(config.autoload_paths + config.eager_load_paths)
|
9
|
+
|
10
|
+
paths.each do |path|
|
11
|
+
Dir.glob(File.join(path, "**", Onload.glob)).each do |file|
|
12
|
+
f = Onload::File.new(file).tap(&:write)
|
13
|
+
puts "Wrote #{f.outfile}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/onload.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Onload
|
4
|
+
autoload :File, "onload/file"
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :enabled
|
8
|
+
alias enabled? enabled
|
9
|
+
|
10
|
+
def register(extension, processor_klass)
|
11
|
+
processors[extension] = processor_klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def install!
|
15
|
+
if Kernel.const_defined?(:Rails)
|
16
|
+
require "onload/railtie"
|
17
|
+
|
18
|
+
if Rails.respond_to?(:autoloaders) && Rails.autoloaders.zeitwerk_enabled?
|
19
|
+
require "onload/core_ext/kernel_zeitwerk"
|
20
|
+
require "onload/ext/zeitwerk/loader"
|
21
|
+
else
|
22
|
+
require "onload/core_ext/kernel"
|
23
|
+
require "onload/ext/activesupport/dependencies"
|
24
|
+
end
|
25
|
+
else
|
26
|
+
begin
|
27
|
+
require "zeitwerk"
|
28
|
+
rescue LoadError
|
29
|
+
require "onload/core_ext/kernel"
|
30
|
+
else
|
31
|
+
require "onload/core_ext/kernel_zeitwerk"
|
32
|
+
require "onload/ext/zeitwerk/loader"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
require "bootsnap"
|
38
|
+
rescue LoadError
|
39
|
+
else
|
40
|
+
require "onload/ext/bootsnap/autoload"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def process?(path)
|
45
|
+
each_extension.any? { |ext| path.end_with?(ext) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def each_extension
|
49
|
+
return to_enum(__method__) unless block_given?
|
50
|
+
|
51
|
+
processors.each { |ext, _| yield ext }
|
52
|
+
end
|
53
|
+
|
54
|
+
def unprocessed_file_for(file)
|
55
|
+
base_name = basename(file)
|
56
|
+
|
57
|
+
unprocessed_files_in(::File.dirname(file)).each do |existing_file|
|
58
|
+
if basename(existing_file) == base_name
|
59
|
+
return existing_file
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def unprocessed_files_in(path)
|
67
|
+
path_cache[path] ||= begin
|
68
|
+
Dir.glob(::File.join(path, glob))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def processors
|
73
|
+
@processors ||= {}
|
74
|
+
end
|
75
|
+
|
76
|
+
def basename(file)
|
77
|
+
basename = ::File.basename(file)
|
78
|
+
|
79
|
+
if (idx = basename.index("."))
|
80
|
+
return basename[0...idx]
|
81
|
+
end
|
82
|
+
|
83
|
+
basename
|
84
|
+
end
|
85
|
+
|
86
|
+
def disable
|
87
|
+
old_enabled = enabled?
|
88
|
+
self.enabled = false
|
89
|
+
yield
|
90
|
+
ensure
|
91
|
+
self.enabled = old_enabled
|
92
|
+
end
|
93
|
+
|
94
|
+
def enable
|
95
|
+
old_enabled = enabled?
|
96
|
+
self.enabled = true
|
97
|
+
yield
|
98
|
+
ensure
|
99
|
+
self.enabled = old_enabled
|
100
|
+
end
|
101
|
+
|
102
|
+
def glob
|
103
|
+
@glob ||= "*{#{each_extension.to_a.join(",")}}"
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def path_cache
|
109
|
+
@path_cache ||= {}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
self.enabled = true
|
114
|
+
end
|
115
|
+
|
116
|
+
if Kernel.const_defined?(:Rails)
|
117
|
+
require "onload/railtie"
|
118
|
+
end
|
data/onload.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
require "onload/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "onload"
|
6
|
+
s.version = ::Onload::VERSION
|
7
|
+
s.authors = ["Cameron Dutro"]
|
8
|
+
s.email = ["camertron@gmail.com"]
|
9
|
+
s.homepage = "http://github.com/camertron/onload"
|
10
|
+
s.description = s.summary = "A preprocessor system for Ruby."
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
|
13
|
+
s.require_path = "lib"
|
14
|
+
|
15
|
+
s.files = Dir["{lib,spec}/**/*", "Gemfile", "LICENSE", "CHANGELOG.md", "README.md", "Rakefile", "onload.gemspec"]
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/spec_helper"
|
4
|
+
|
5
|
+
describe HomeController, type: :request do
|
6
|
+
describe "#index" do
|
7
|
+
it "transpiles the file" do
|
8
|
+
get "/"
|
9
|
+
|
10
|
+
expect(response).to have_http_status(:ok)
|
11
|
+
expect(response.body).to(
|
12
|
+
have_selector("div", text: "HELLO")
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allows hot reloading" do
|
17
|
+
get "/"
|
18
|
+
|
19
|
+
expect(response).to have_http_status(:ok)
|
20
|
+
expect(response.body).to(
|
21
|
+
have_selector("div", text: "HELLO")
|
22
|
+
)
|
23
|
+
|
24
|
+
new_contents = <<~RUBY
|
25
|
+
class Hello
|
26
|
+
def hello
|
27
|
+
"goodbye"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
RUBY
|
31
|
+
|
32
|
+
with_file_contents(File.join(Onload::TestHelpers.fixtures_path, "hello.rb.up"), new_contents) do
|
33
|
+
get "/"
|
34
|
+
|
35
|
+
expect(response).to have_http_status(:ok)
|
36
|
+
expect(response.body).to(
|
37
|
+
have_selector("div", text: "GOODBYE")
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<div><%= Hello.new.hello %></div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= yield %>
|