actionview_precompiler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +62 -0
- data/README.md +54 -0
- data/Rakefile +10 -0
- data/actionview_precompiler.gemspec +31 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/actionview_precompiler.rb +32 -0
- data/lib/actionview_precompiler/parsed_filename.rb +51 -0
- data/lib/actionview_precompiler/precompiler.rb +89 -0
- data/lib/actionview_precompiler/render_parser.rb +128 -0
- data/lib/actionview_precompiler/template_parser.rb +37 -0
- data/lib/actionview_precompiler/version.rb +3 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fadda94921df406ea9a921ae1c2a0ef61cb9a70a5a082ed906ead3a91ca0ab79
|
4
|
+
data.tar.gz: a4eb7f984757e30d29eecdf348dc4ce1456f1f2d51e1092dd1bbc86b50d5511a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c86dcaa26a379be5cb1832c7ea036e0dcca6ea2a26582706aa54fc35ba9abc95cd23507bba329e1f6968b70516878b5b8ea153bf419c7b2b67bf249197e3f430
|
7
|
+
data.tar.gz: f00fd1a1202d766df59977b982c75364dfe350f54a47131f74dde6ba0413c9e19b14818b2bc56f94f26521b3516bf383319fcc7a3663a9dd3bbf8c89f2fae269
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at john@hawthorn.email. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
actionview_precompiler (0.1.0)
|
5
|
+
actionview (>= 6.0.a)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionview (6.0.0.rc1)
|
11
|
+
activesupport (= 6.0.0.rc1)
|
12
|
+
builder (~> 3.1)
|
13
|
+
erubi (~> 1.4)
|
14
|
+
rails-dom-testing (~> 2.0)
|
15
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
16
|
+
activesupport (6.0.0.rc1)
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
|
+
i18n (>= 0.7, < 2)
|
19
|
+
minitest (~> 5.1)
|
20
|
+
tzinfo (~> 1.1)
|
21
|
+
zeitwerk (~> 2.1, >= 2.1.4)
|
22
|
+
builder (3.2.3)
|
23
|
+
coderay (1.1.2)
|
24
|
+
concurrent-ruby (1.1.5)
|
25
|
+
crass (1.0.4)
|
26
|
+
erubi (1.8.0)
|
27
|
+
i18n (1.6.0)
|
28
|
+
concurrent-ruby (~> 1.0)
|
29
|
+
loofah (2.2.3)
|
30
|
+
crass (~> 1.0.2)
|
31
|
+
nokogiri (>= 1.5.9)
|
32
|
+
method_source (0.9.2)
|
33
|
+
mini_portile2 (2.4.0)
|
34
|
+
minitest (5.11.3)
|
35
|
+
nokogiri (1.10.3)
|
36
|
+
mini_portile2 (~> 2.4.0)
|
37
|
+
pry (0.12.2)
|
38
|
+
coderay (~> 1.1.0)
|
39
|
+
method_source (~> 0.9.0)
|
40
|
+
rails-dom-testing (2.0.3)
|
41
|
+
activesupport (>= 4.2.0)
|
42
|
+
nokogiri (>= 1.6)
|
43
|
+
rails-html-sanitizer (1.0.4)
|
44
|
+
loofah (~> 2.2, >= 2.2.2)
|
45
|
+
rake (12.3.2)
|
46
|
+
thread_safe (0.3.6)
|
47
|
+
tzinfo (1.2.5)
|
48
|
+
thread_safe (~> 0.1)
|
49
|
+
zeitwerk (2.1.5)
|
50
|
+
|
51
|
+
PLATFORMS
|
52
|
+
ruby
|
53
|
+
|
54
|
+
DEPENDENCIES
|
55
|
+
actionview_precompiler!
|
56
|
+
bundler (~> 2.0)
|
57
|
+
minitest (~> 5.0)
|
58
|
+
pry
|
59
|
+
rake (~> 12.0)
|
60
|
+
|
61
|
+
BUNDLED WITH
|
62
|
+
2.0.1
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# ActionviewPrecompiler
|
2
|
+
|
3
|
+
Precompiles ActionView templates at app boot.
|
4
|
+
|
5
|
+
The main challenge in precompiling these templates is determining the locals they're going to be passed.
|
6
|
+
Without the initialization, local vars look the same as method calls, so we need to compile separate copies for each different set of local variable passed in.
|
7
|
+
|
8
|
+
We determine the locals passed to each template by parsing all templates looking for render calls and extracting the local keys passed to that.
|
9
|
+
|
10
|
+
Right now this assumes every template with the same `virtual_path` takes the same locals (there may be smarter options, we just aren't doing them).
|
11
|
+
A curse/blessing/actually still a curse of this approach is that mis-predicting render calls doesn't cause any issues, it just wastes RAM.
|
12
|
+
|
13
|
+
Templates are half-compiled using standard ActionView handlers, so this should work for erb/builder/haml/whatever.
|
14
|
+
Parsing is done using Ruby 2.6's `RubyVM::AbstractSyntaxTree`.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'actionview_precompiler'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle install
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
``` ruby
|
31
|
+
ActionviewPrecompiler.precompile
|
32
|
+
```
|
33
|
+
|
34
|
+
## TODO
|
35
|
+
|
36
|
+
* Doesn't understand (common) relative renders: `render "form"`
|
37
|
+
* Support more `render` invocations
|
38
|
+
* Parse controllers/helpers for more renders
|
39
|
+
* Cache detected locals to avoid parsing cost
|
40
|
+
* Upstream more bits to Rails
|
41
|
+
|
42
|
+
## Development
|
43
|
+
|
44
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
45
|
+
|
46
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/actionview_precompiler. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
51
|
+
|
52
|
+
## Code of Conduct
|
53
|
+
|
54
|
+
Everyone interacting in the ActionviewPrecompiler project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jhawthorn/actionview_precompiler/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "actionview_precompiler/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "actionview_precompiler"
|
7
|
+
spec.version = ActionviewPrecompiler::VERSION
|
8
|
+
spec.authors = ["John Hawthorn"]
|
9
|
+
spec.email = ["john@hawthorn.email"]
|
10
|
+
|
11
|
+
spec.summary = %q{Precompiles ActionView templates}
|
12
|
+
spec.description = %q{Parses templates for render calls and uses them to precompile}
|
13
|
+
spec.homepage = "https://github.com/jhawthorn/actionview_precompiler"
|
14
|
+
|
15
|
+
# Specify which files should be added to the gem when it is released.
|
16
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.required_ruby_version = ">= 2.6"
|
25
|
+
|
26
|
+
spec.add_dependency "actionview", ">= 6.0.a"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
29
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
30
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
31
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "actionview_precompiler"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "actionview_precompiler/version"
|
2
|
+
require "actionview_precompiler/template_parser"
|
3
|
+
require "actionview_precompiler/render_parser"
|
4
|
+
require "actionview_precompiler/precompiler"
|
5
|
+
require "actionview_precompiler/parsed_filename"
|
6
|
+
|
7
|
+
module ActionviewPrecompiler
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
def self.precompile(verbose: false)
|
11
|
+
target = ActionController::Base # fixme
|
12
|
+
view_paths = target.view_paths
|
13
|
+
lookup_context = ActionView::LookupContext.new(view_paths)
|
14
|
+
paths = view_paths.map(&:path)
|
15
|
+
precompiler = Precompiler.new(paths)
|
16
|
+
|
17
|
+
mod = target.view_context_class
|
18
|
+
count = 0
|
19
|
+
precompiler.each_lookup_args do |args|
|
20
|
+
templates = lookup_context.find_all(*args)
|
21
|
+
templates.each do |template|
|
22
|
+
puts "precompiling: #{template.inspect}" if verbose
|
23
|
+
count += 1
|
24
|
+
template.send(:compile!, mod)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if verbose
|
29
|
+
puts "Precompiled #{count} Templates"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ActionviewPrecompiler
|
2
|
+
class ParsedFilename
|
3
|
+
attr_reader :path, :action, :prefix, :options, :details
|
4
|
+
|
5
|
+
def initialize(path)
|
6
|
+
@path = path
|
7
|
+
|
8
|
+
details = parse_template_path(path)
|
9
|
+
@prefix = details.delete(:prefix)
|
10
|
+
@action = details.delete(:action)
|
11
|
+
@partial = details.delete(:partial)
|
12
|
+
@details = details
|
13
|
+
end
|
14
|
+
|
15
|
+
def partial?
|
16
|
+
@partial
|
17
|
+
end
|
18
|
+
|
19
|
+
def path_regex
|
20
|
+
handlers = ActionView::Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
|
21
|
+
formats = ActionView::Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
|
22
|
+
locales = "[a-z]{2}(?:-[A-Z]{2})?"
|
23
|
+
variants = "[^.]*"
|
24
|
+
%r{
|
25
|
+
\A
|
26
|
+
(?:(?<prefix>.*)/)?
|
27
|
+
(?<partial>_)?
|
28
|
+
(?<action>.*?)
|
29
|
+
(?:\.(?<locale>#{locales}))??
|
30
|
+
(?:\.(?<format>#{formats}))??
|
31
|
+
(?:\+(?<variant>#{variants}))??
|
32
|
+
(?:\.(?<handler>#{handlers}))?
|
33
|
+
\z
|
34
|
+
}x
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_template_path(path)
|
38
|
+
match = path_regex.match(path)
|
39
|
+
|
40
|
+
{
|
41
|
+
prefix: match[:prefix] || "",
|
42
|
+
action: match[:action],
|
43
|
+
partial: !!match[:partial],
|
44
|
+
locale: match[:locale]&.to_sym,
|
45
|
+
handler: match[:handler]&.to_sym,
|
46
|
+
format: match[:format]&.to_sym,
|
47
|
+
variant: match[:variant]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ActionviewPrecompiler
|
2
|
+
class Precompiler
|
3
|
+
class Template
|
4
|
+
attr_reader :fullpath, :relative_path, :virtual_path
|
5
|
+
attr_reader :action, :prefix, :details
|
6
|
+
|
7
|
+
def initialize(fullpath, relative_path)
|
8
|
+
@fullpath = fullpath
|
9
|
+
@relative_path = relative_path
|
10
|
+
@virtual_path = relative_path.slice(0, relative_path.index("."))
|
11
|
+
|
12
|
+
parsed = ParsedFilename.new(relative_path)
|
13
|
+
@prefix = parsed.prefix
|
14
|
+
@action = parsed.action
|
15
|
+
@partial = parsed.partial?
|
16
|
+
@details = parsed.details
|
17
|
+
end
|
18
|
+
|
19
|
+
def partial?
|
20
|
+
@partial
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :templates
|
25
|
+
|
26
|
+
def initialize(view_dirs)
|
27
|
+
@templates =
|
28
|
+
view_dirs.flat_map do |view_dir|
|
29
|
+
Dir["**/*", base: view_dir].map do |file|
|
30
|
+
fullpath = File.expand_path(file, view_dir)
|
31
|
+
next if File.directory?(fullpath)
|
32
|
+
|
33
|
+
Template.new(fullpath, file)
|
34
|
+
end.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
determine_locals
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_lookup_args
|
41
|
+
return enum_for(__method__) unless block_given?
|
42
|
+
|
43
|
+
each_template_render do |template, locals|
|
44
|
+
details = {
|
45
|
+
locale: Array(template.details[:locale]),
|
46
|
+
variants: Array(template.details[:variant]),
|
47
|
+
formats: Array(template.details[:format]),
|
48
|
+
handlers: Array(template.details[:handler])
|
49
|
+
}
|
50
|
+
|
51
|
+
yield [template.action, template.prefix, template.partial?, locals, details]
|
52
|
+
end
|
53
|
+
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_template_render
|
58
|
+
return enum_for(__method__) unless block_given?
|
59
|
+
|
60
|
+
@templates.each do |template|
|
61
|
+
locals_set = @locals_sets[template.virtual_path]
|
62
|
+
if locals_set
|
63
|
+
locals_set.each do |locals|
|
64
|
+
yield template, locals
|
65
|
+
end
|
66
|
+
elsif !template.partial?
|
67
|
+
# For now, guess that non-partials we haven't seen take no locals
|
68
|
+
yield template, []
|
69
|
+
else
|
70
|
+
# Locals unknown
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def determine_locals
|
76
|
+
@locals_sets = {}
|
77
|
+
|
78
|
+
@templates.each do |template|
|
79
|
+
parser = TemplateParser.new(template.fullpath)
|
80
|
+
parser.render_calls.each do |render_call|
|
81
|
+
@locals_sets[render_call.virtual_path] ||= []
|
82
|
+
@locals_sets[render_call.virtual_path] << render_call.locals_keys.map(&:to_s).sort
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
@locals_sets.each_value(&:uniq!)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module ActionviewPrecompiler
|
2
|
+
RenderCall = Struct.new(:render_type, :template, :locals, :locals_keys) do
|
3
|
+
def virtual_path
|
4
|
+
if render_type == :partial
|
5
|
+
@virtual_path ||= template.gsub(%r{/([^/]*)\z}, '/_\1')
|
6
|
+
else
|
7
|
+
template
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class RenderParser
|
13
|
+
def initialize(code)
|
14
|
+
@code = code
|
15
|
+
@code = RubyVM::AbstractSyntaxTree.parse(code) if code.is_a?(String)
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_calls
|
19
|
+
render_nodes = extract_render_nodes(@code)
|
20
|
+
render_nodes.map do |node|
|
21
|
+
parse_render(node)
|
22
|
+
end.compact
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_render(node)
|
28
|
+
node = node.children[1]
|
29
|
+
node = node.children
|
30
|
+
if (node.length == 2 || node.length == 3) && node[0].type == :STR
|
31
|
+
# FIXME: from template vs controller
|
32
|
+
options = {}
|
33
|
+
options[:partial] = node[0]
|
34
|
+
if node.length == 3
|
35
|
+
return unless node[1].type == :HASH
|
36
|
+
options[:locals] = node[1]
|
37
|
+
end
|
38
|
+
return parse_render_from_options(options)
|
39
|
+
elsif node.length == 2 && node[0].type == :HASH
|
40
|
+
options = parse_hash_to_symbols(node[0])
|
41
|
+
return parse_render_from_options(options)
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_hash(node)
|
48
|
+
return nil unless node.type == :HASH
|
49
|
+
|
50
|
+
node.children[0].children[0..-2].each_slice(2).to_h
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_hash_to_symbols(node)
|
54
|
+
hash = parse_hash(node)
|
55
|
+
return unless hash
|
56
|
+
hash.transform_keys do |node|
|
57
|
+
key = parse_sym(node)
|
58
|
+
return unless key
|
59
|
+
key
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
RENDER_TYPE_KEYS = [:partial, :template, :layout]
|
64
|
+
IGNORED_KEYS = [:formats]
|
65
|
+
ALL_KNOWN_KEYS = [*RENDER_TYPE_KEYS, *IGNORED_KEYS, :locals]
|
66
|
+
|
67
|
+
def parse_render_from_options(options_hash)
|
68
|
+
keys = options_hash.keys
|
69
|
+
|
70
|
+
unless (keys & RENDER_TYPE_KEYS).one?
|
71
|
+
# Must have one of partial:, template:, or layout:
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
unless (keys - ALL_KNOWN_KEYS).empty?
|
76
|
+
# de-opt in case of unknown option
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
render_type = (keys & RENDER_TYPE_KEYS)[0]
|
81
|
+
template = parse_str(options_hash[render_type])
|
82
|
+
return unless template
|
83
|
+
|
84
|
+
if options_hash.key?(:locals)
|
85
|
+
locals = options_hash[:locals]
|
86
|
+
parsed_locals = parse_hash(locals)
|
87
|
+
return nil unless parsed_locals
|
88
|
+
locals_keys = parsed_locals.keys.map do |local|
|
89
|
+
return nil unless local.type == :LIT
|
90
|
+
local.children[0]
|
91
|
+
end
|
92
|
+
else
|
93
|
+
locals = nil
|
94
|
+
locals_keys = []
|
95
|
+
end
|
96
|
+
|
97
|
+
RenderCall.new(render_type, template, locals, locals_keys)
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_str(node)
|
101
|
+
node.children[0] if node.type == :STR && String === node.children[0]
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_sym(node)
|
105
|
+
node.children[0] if node.type == :LIT && Symbol === node.children[0]
|
106
|
+
end
|
107
|
+
|
108
|
+
def debug(message)
|
109
|
+
warn message
|
110
|
+
end
|
111
|
+
|
112
|
+
def extract_render_nodes(node)
|
113
|
+
return [] unless RubyVM::AbstractSyntaxTree::Node === node
|
114
|
+
renders = node.children.flat_map { |c| extract_render_nodes(c) }
|
115
|
+
if render_call?(node)
|
116
|
+
renders << node
|
117
|
+
end
|
118
|
+
renders
|
119
|
+
end
|
120
|
+
|
121
|
+
def render_call?(node)
|
122
|
+
node.type == :FCALL &&
|
123
|
+
node.children[0] == :render &&
|
124
|
+
node.children[1] &&
|
125
|
+
node.children[1].type == :ARRAY
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module ActionviewPrecompiler
|
4
|
+
class TemplateParser
|
5
|
+
attr_reader :filename, :basename, :handler
|
6
|
+
|
7
|
+
class FakeTemplate
|
8
|
+
def type
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(filename)
|
14
|
+
@filename = filename
|
15
|
+
@basename = File.basename(filename)
|
16
|
+
handler_ext = @basename.split(".").last
|
17
|
+
@handler = ActionView::Template.handler_for_extension(handler_ext)
|
18
|
+
@is_partial = !!@basename.start_with?("_")
|
19
|
+
end
|
20
|
+
|
21
|
+
def partial?
|
22
|
+
@is_partial
|
23
|
+
end
|
24
|
+
|
25
|
+
def render_calls
|
26
|
+
RenderParser.new(parsed).render_calls
|
27
|
+
end
|
28
|
+
|
29
|
+
def parsed
|
30
|
+
@parsed ||= RubyVM::AbstractSyntaxTree.parse(compiled_source)
|
31
|
+
end
|
32
|
+
|
33
|
+
def compiled_source
|
34
|
+
@handler.call(FakeTemplate.new, File.read(@filename))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: actionview_precompiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Hawthorn
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: actionview
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 6.0.a
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 6.0.a
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
description: Parses templates for render calls and uses them to precompile
|
70
|
+
email:
|
71
|
+
- john@hawthorn.email
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- CODE_OF_CONDUCT.md
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- actionview_precompiler.gemspec
|
83
|
+
- bin/console
|
84
|
+
- bin/setup
|
85
|
+
- lib/actionview_precompiler.rb
|
86
|
+
- lib/actionview_precompiler/parsed_filename.rb
|
87
|
+
- lib/actionview_precompiler/precompiler.rb
|
88
|
+
- lib/actionview_precompiler/render_parser.rb
|
89
|
+
- lib/actionview_precompiler/template_parser.rb
|
90
|
+
- lib/actionview_precompiler/version.rb
|
91
|
+
homepage: https://github.com/jhawthorn/actionview_precompiler
|
92
|
+
licenses: []
|
93
|
+
metadata: {}
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '2.6'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubygems_version: 3.0.3
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Precompiles ActionView templates
|
113
|
+
test_files: []
|