reactionview 0.4.1 → 0.6.0
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/README.md +22 -39
- data/app/assets/javascripts/reactionview-dev-tools.esm.js +59495 -2633
- data/app/assets/javascripts/reactionview-dev-tools.esm.js.map +1 -1
- data/app/assets/javascripts/reactionview-dev-tools.umd.js +59476 -2617
- data/app/assets/javascripts/reactionview-dev-tools.umd.js.map +1 -1
- data/app/assets/javascripts/reactionview.esm.js +26 -0
- data/app/assets/javascripts/reactionview.esm.js.map +1 -0
- data/lib/generators/reactionview/install_generator.rb +39 -4
- data/lib/reactionview/asset_manifest.rb +1 -1
- data/lib/reactionview/config.rb +99 -4
- data/lib/reactionview/importmap.rb +3 -0
- data/lib/reactionview/instrumentation.rb +143 -0
- data/lib/reactionview/railtie.rb +117 -5
- data/lib/reactionview/slots/dev_compiler.rb +25 -0
- data/lib/reactionview/slots/rendering.rb +157 -0
- data/lib/reactionview/slots/resolver.rb +27 -0
- data/lib/reactionview/slots/state_overrides.rb +68 -0
- data/lib/reactionview/slots.rb +38 -0
- data/lib/reactionview/template/dependencies.rb +90 -0
- data/lib/reactionview/template/handlers/erb.rb +25 -1
- data/lib/reactionview/template/handlers/herb/herb.rb +69 -57
- data/lib/reactionview/template/handlers/herb.rb +188 -13
- data/lib/reactionview/template/local_template.rb +37 -6
- data/lib/reactionview/template/partial_resolver.rb +90 -0
- data/lib/reactionview/version.rb +1 -1
- data/lib/reactionview.rb +13 -0
- data/reactionview.gemspec +3 -2
- metadata +30 -11
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReActionView
|
|
4
|
+
module Slots
|
|
5
|
+
class Resolver < ActionView::FileSystemResolver
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def build_unbound_template(template)
|
|
9
|
+
unbound = super
|
|
10
|
+
details = unbound.details
|
|
11
|
+
|
|
12
|
+
return unbound unless details.format == :html
|
|
13
|
+
|
|
14
|
+
ActionView::UnboundTemplate.new(
|
|
15
|
+
source_for_template(template),
|
|
16
|
+
template,
|
|
17
|
+
details: ActionView::TemplateDetails.new(details.locale, details.handler, Slots::FORMAT, details.variant),
|
|
18
|
+
virtual_path: unbound.virtual_path
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def unbound_templates_from_path(path)
|
|
23
|
+
super.select { |unbound| unbound.format == Slots::FORMAT }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module ReActionView
|
|
6
|
+
module Slots
|
|
7
|
+
# Parses the `Herb-State` request header a client sends with a values request.
|
|
8
|
+
#
|
|
9
|
+
# The header carries the client's state so the compiled values program evaluates
|
|
10
|
+
# the branches the client is showing. The parse is lenient on purpose, since a
|
|
11
|
+
# malformed or oversized header must degrade to the compiled defaults and never
|
|
12
|
+
# break a render.
|
|
13
|
+
#
|
|
14
|
+
module StateOverrides
|
|
15
|
+
HEADER = "Herb-State" #: String
|
|
16
|
+
MAX_BYTES = 8192 #: Integer
|
|
17
|
+
|
|
18
|
+
#: (String?) -> Hash[String, Hash[String, untyped]]?
|
|
19
|
+
def self.parse(header_value)
|
|
20
|
+
return nil if header_value.nil? || header_value.strip.empty?
|
|
21
|
+
return nil if header_value.bytesize > MAX_BYTES
|
|
22
|
+
|
|
23
|
+
raw = ::JSON.parse(header_value)
|
|
24
|
+
|
|
25
|
+
return nil unless raw.is_a?(::Hash)
|
|
26
|
+
|
|
27
|
+
overrides = raw.select { |key, value| key.is_a?(::String) && value.is_a?(::Hash) }
|
|
28
|
+
|
|
29
|
+
overrides.empty? ? nil : overrides
|
|
30
|
+
rescue ::JSON::ParserError
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module StateOverridesHelper
|
|
36
|
+
#: () -> Hash[String, Hash[String, untyped]]?
|
|
37
|
+
def __herb_state_overrides
|
|
38
|
+
return @__herb_state_overrides if defined?(@__herb_state_overrides)
|
|
39
|
+
|
|
40
|
+
header = __herb_values_request? ? request.headers[StateOverrides::HEADER] : nil
|
|
41
|
+
|
|
42
|
+
@__herb_state_overrides = StateOverrides.parse(header)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#: () -> bool
|
|
46
|
+
def __herb_values_request?
|
|
47
|
+
return false unless respond_to?(:request) && request
|
|
48
|
+
return false unless request.respond_to?(:format)
|
|
49
|
+
|
|
50
|
+
request.format&.symbol == Slots::FORMAT
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#: (?String?, ?untyped) -> untyped
|
|
54
|
+
def herb_state(name = nil, default = nil)
|
|
55
|
+
overrides = __herb_state_overrides
|
|
56
|
+
|
|
57
|
+
return overrides if name.nil?
|
|
58
|
+
return default if overrides.nil?
|
|
59
|
+
|
|
60
|
+
overrides.each_value do |states|
|
|
61
|
+
return states[name] if states.key?(name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
default
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReActionView
|
|
4
|
+
module Slots
|
|
5
|
+
FORMAT = :slots #: Symbol
|
|
6
|
+
MIME_TYPE = "application/vnd.herb.slots+json" #: String
|
|
7
|
+
REGION_MARKER = "herb-region:" #: String
|
|
8
|
+
|
|
9
|
+
#: () -> untyped
|
|
10
|
+
def self.dependencies
|
|
11
|
+
root = ReActionView.config.project_path
|
|
12
|
+
|
|
13
|
+
@dependencies = nil if @root != root
|
|
14
|
+
@root = root
|
|
15
|
+
@dependencies ||= ::Herb::Engine::Slots::Dependencies.new(
|
|
16
|
+
root,
|
|
17
|
+
compile: ->(source, path) { ReActionView::Template::Handlers::Herb.compile_for_dependencies(source, path) }
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: () -> void
|
|
22
|
+
def self.reset_dependencies!
|
|
23
|
+
@dependencies = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: (String, Integer) -> String?
|
|
27
|
+
def self.block_program(path, index)
|
|
28
|
+
mtime = ::File.mtime(path)
|
|
29
|
+
key = [path, index, mtime]
|
|
30
|
+
|
|
31
|
+
(@block_programs_lock ||= ::Mutex.new).synchronize do
|
|
32
|
+
programs = (@block_programs ||= {})
|
|
33
|
+
programs.clear if programs.size > 128 && !programs.key?(key)
|
|
34
|
+
programs[key] ||= ReActionView::Template::Handlers::Herb.compile_for_block(::File.read(path), path, index)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReActionView
|
|
4
|
+
class Template
|
|
5
|
+
class Dependencies
|
|
6
|
+
def self.current
|
|
7
|
+
@current ||= new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.reset!
|
|
11
|
+
@current = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def engine_roots
|
|
15
|
+
@engine_roots ||= engines.filter_map { |engine| directory(engine.root) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def gem_paths_outside(app_root)
|
|
19
|
+
return [] unless app_root
|
|
20
|
+
|
|
21
|
+
gem_paths.reject { |path| app_root.start_with?(path) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def app_view_paths
|
|
25
|
+
@app_view_paths ||= (registered_view_paths - engine_view_paths).filter_map { |path| directory(path) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def view_paths
|
|
29
|
+
@view_paths ||= registered_view_paths.filter_map { |path| directory(path) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def gem_paths
|
|
35
|
+
@gem_paths ||= begin
|
|
36
|
+
specs = defined?(::Gem) && ::Gem.respond_to?(:loaded_specs) ? ::Gem.loaded_specs.values : []
|
|
37
|
+
|
|
38
|
+
specs.filter_map { |spec| directory(spec.full_gem_path) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def engines
|
|
43
|
+
return [] unless defined?(::Rails::Engine)
|
|
44
|
+
|
|
45
|
+
@engines ||= ::Rails::Engine.subclasses.filter_map { |klass| engine_instance(klass) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def engine_instance(klass)
|
|
49
|
+
return nil unless klass.respond_to?(:instance)
|
|
50
|
+
|
|
51
|
+
engine = klass.instance
|
|
52
|
+
|
|
53
|
+
return nil if engine.nil? || engine == application
|
|
54
|
+
return nil unless engine.respond_to?(:root) && engine.root
|
|
55
|
+
|
|
56
|
+
engine
|
|
57
|
+
rescue StandardError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def engine_view_paths
|
|
62
|
+
engines.flat_map do |engine|
|
|
63
|
+
engine.paths["app/views"].existent
|
|
64
|
+
rescue StandardError
|
|
65
|
+
[]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def registered_view_paths
|
|
70
|
+
return [] unless defined?(::ActionController::Base)
|
|
71
|
+
|
|
72
|
+
::ActionController::Base.view_paths.filter_map { |resolver| resolver.path if resolver.respond_to?(:path) }
|
|
73
|
+
rescue StandardError
|
|
74
|
+
[]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def application
|
|
78
|
+
::Rails.application if defined?(::Rails) && ::Rails.respond_to?(:application)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def directory(path)
|
|
82
|
+
return nil if path.nil?
|
|
83
|
+
|
|
84
|
+
expanded = File.expand_path(path.to_s)
|
|
85
|
+
|
|
86
|
+
expanded.empty? ? nil : "#{expanded.chomp("/")}/"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -6,9 +6,17 @@ module ReActionView
|
|
|
6
6
|
class ERB < ActionView::Template::Handlers::ERB
|
|
7
7
|
include ReActionView::Template::LocalTemplate
|
|
8
8
|
|
|
9
|
+
INTERCEPTED_FORMATS = [:html, ::ReActionView::Slots::FORMAT].freeze
|
|
10
|
+
|
|
9
11
|
autoload :Herb, "reactionview/template/handlers/herb/herb"
|
|
10
12
|
|
|
11
13
|
def call(template, source)
|
|
14
|
+
if skip_external_template?(template)
|
|
15
|
+
@erb_fallback = true
|
|
16
|
+
|
|
17
|
+
return super
|
|
18
|
+
end
|
|
19
|
+
|
|
12
20
|
return super unless intercept_template?(template)
|
|
13
21
|
|
|
14
22
|
::ReActionView::Template::Handlers::Herb.call(
|
|
@@ -19,13 +27,29 @@ module ReActionView
|
|
|
19
27
|
|
|
20
28
|
log_external_template_error(template, e)
|
|
21
29
|
|
|
30
|
+
@erb_fallback = true
|
|
31
|
+
|
|
22
32
|
super
|
|
23
33
|
end
|
|
24
34
|
|
|
25
35
|
private
|
|
26
36
|
|
|
37
|
+
def implementation_for(template)
|
|
38
|
+
return self.class.erb_implementation if @erb_fallback
|
|
39
|
+
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def skip_external_template?(template)
|
|
44
|
+
return false unless INTERCEPTED_FORMATS.include?(template.format) && ReActionView.config.intercept_erb
|
|
45
|
+
return false if framework_template?(template) || local_template?(template)
|
|
46
|
+
|
|
47
|
+
ReActionView.config.external_template_mode == :skip
|
|
48
|
+
end
|
|
49
|
+
|
|
27
50
|
def intercept_template?(template)
|
|
28
|
-
return false unless template.format
|
|
51
|
+
return false unless INTERCEPTED_FORMATS.include?(template.format) && ReActionView.config.intercept_erb
|
|
52
|
+
return false if framework_template?(template)
|
|
29
53
|
|
|
30
54
|
local_template?(template) || ReActionView.config.external_template_mode != :skip
|
|
31
55
|
end
|
|
@@ -1,89 +1,101 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "herb"
|
|
4
|
+
require "herb/engine"
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
require "action_view/template/handlers/erb/herb"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# Rails 8.1 and earlier ship no builtin Herb implementation
|
|
10
|
+
end
|
|
4
11
|
|
|
5
12
|
module ReActionView
|
|
6
13
|
class Template
|
|
7
14
|
module Handlers
|
|
8
15
|
class Herb
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
if defined?(::ActionView::Template::Handlers::ERB::Herb)
|
|
17
|
+
class Herb < ::ActionView::Template::Handlers::ERB::Herb
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
class Herb < ::Herb::Engine
|
|
21
|
+
def initialize(input, properties = {})
|
|
22
|
+
@newline_pending = 0
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
# Dup properties so that we don't modify argument
|
|
25
|
+
properties = properties.to_h
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
properties[:bufvar] ||= "@output_buffer"
|
|
28
|
+
properties[:preamble] ||= ""
|
|
29
|
+
properties[:postamble] ||= properties[:bufvar].to_s
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
# Tell Herb whether the template will be compiled with `frozen_string_literal: true`
|
|
32
|
+
properties[:freeze_template_literals] = !::ActionView::Template.frozen_string_literal
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
# Disable all Herb escape functions - let ActionView::OutputBuffer handle escaping
|
|
35
|
+
properties[:escapefunc] = ""
|
|
36
|
+
properties[:attrfunc] = nil
|
|
37
|
+
properties[:jsfunc] = nil
|
|
38
|
+
properties[:cssfunc] = nil
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
super
|
|
41
|
+
end
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
private
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
def add_text(text)
|
|
46
|
+
return if text.empty?
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
if text == "\n"
|
|
49
|
+
@newline_pending += 1
|
|
50
|
+
else
|
|
51
|
+
with_buffer do
|
|
52
|
+
@src << ".safe_append='"
|
|
53
|
+
@src << ("\n" * @newline_pending) if @newline_pending.positive?
|
|
54
|
+
@src << text.gsub(/['\\]/, '\\\\\&') << @text_end
|
|
55
|
+
end
|
|
45
56
|
|
|
46
|
-
|
|
57
|
+
@newline_pending = 0
|
|
58
|
+
end
|
|
47
59
|
end
|
|
48
|
-
end
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
def add_expression(indicator, code)
|
|
62
|
+
flush_newline_if_pending(@src)
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
with_buffer do
|
|
65
|
+
@src << expression_append_method(indicator)
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
if expression_block?
|
|
68
|
+
@src << " " << code
|
|
69
|
+
else
|
|
70
|
+
@src << "(" << code << trailing_newline(code) << ")"
|
|
71
|
+
end
|
|
60
72
|
end
|
|
61
73
|
end
|
|
62
|
-
end
|
|
63
74
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
def expression_append_method(indicator)
|
|
76
|
+
if (indicator == "==") || @escape
|
|
77
|
+
".safe_expr_append="
|
|
78
|
+
else
|
|
79
|
+
".append="
|
|
80
|
+
end
|
|
69
81
|
end
|
|
70
|
-
end
|
|
71
82
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
def add_code(code)
|
|
84
|
+
flush_newline_if_pending(@src)
|
|
85
|
+
super
|
|
86
|
+
end
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
def add_postamble(_)
|
|
89
|
+
flush_newline_if_pending(@src)
|
|
90
|
+
super
|
|
91
|
+
end
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
def flush_newline_if_pending(src)
|
|
94
|
+
return unless @newline_pending.positive?
|
|
84
95
|
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
with_buffer { src << ".safe_append='#{"\n" * @newline_pending}" << @text_end }
|
|
97
|
+
@newline_pending = 0
|
|
98
|
+
end
|
|
87
99
|
end
|
|
88
100
|
end
|
|
89
101
|
end
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "herb"
|
|
4
|
+
require "herb/engine"
|
|
5
|
+
require "herb/engine/slots/dynamics_compiler"
|
|
6
|
+
require "herb/engine/slots/schema_compiler"
|
|
7
|
+
|
|
8
|
+
require "herb/engine/visitors/debug_visitor"
|
|
9
|
+
require "herb/engine/slots/visitor"
|
|
10
|
+
require "herb/engine/slots/dependencies"
|
|
11
|
+
require "herb/engine/visitors/content_for_visitor"
|
|
12
|
+
require "herb/engine/validators"
|
|
13
|
+
|
|
3
14
|
module ReActionView
|
|
4
15
|
class Template
|
|
5
16
|
module Handlers
|
|
@@ -10,33 +21,188 @@ module ReActionView
|
|
|
10
21
|
|
|
11
22
|
class_attribute :erb_implementation, default: Handlers::Herb::Herb
|
|
12
23
|
|
|
24
|
+
VALUES_ESCAPING = {
|
|
25
|
+
escape: true,
|
|
26
|
+
escapefunc: "::ERB::Util.h",
|
|
27
|
+
attrfunc: nil,
|
|
28
|
+
jsfunc: nil,
|
|
29
|
+
cssfunc: nil,
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
13
32
|
def self.call(template, source, validation_mode: nil)
|
|
14
33
|
new.call(template, source, validation_mode: validation_mode)
|
|
15
34
|
end
|
|
16
35
|
|
|
36
|
+
def self.compile_for_dependencies(source, path)
|
|
37
|
+
new.compile_for_dependencies(source, path)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.compile_for_schema(source, path)
|
|
41
|
+
new.compile_for_schema(source, path)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.compile_for_block(source, path, index)
|
|
45
|
+
new.compile_for_block(source, path, index)
|
|
46
|
+
end
|
|
47
|
+
|
|
17
48
|
def call(template, source, validation_mode: nil)
|
|
18
|
-
|
|
49
|
+
mode = validation_mode || ReActionView.config.validation_mode
|
|
19
50
|
|
|
20
|
-
|
|
21
|
-
visitors << ::Herb::Engine::DebugVisitor.new(
|
|
22
|
-
file_path: translate_path_for_editor(template.identifier),
|
|
23
|
-
project_path: ::ReActionView.config.project_path
|
|
24
|
-
)
|
|
25
|
-
end
|
|
51
|
+
visitors = base_visitors(template, validation_mode: mode)
|
|
26
52
|
|
|
27
53
|
config = {
|
|
28
|
-
filename: template.identifier,
|
|
29
|
-
project_path:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
visitors: visitors + ReActionView.config.transform_visitors,
|
|
54
|
+
filename: translate_path_for_editor(template.identifier),
|
|
55
|
+
project_path: ::ReActionView.config.project_path,
|
|
56
|
+
visitors: visitors,
|
|
57
|
+
**engine_options,
|
|
33
58
|
}
|
|
34
59
|
|
|
60
|
+
return values_source(template, source, config) if values_format?(template)
|
|
61
|
+
|
|
62
|
+
slots = slot_visitors(template, source)
|
|
63
|
+
|
|
64
|
+
config[:visitors] = compile_stack(template, validation_mode: mode, slots: slots)
|
|
65
|
+
|
|
35
66
|
erb_implementation.new(source, config).src
|
|
36
67
|
end
|
|
37
68
|
|
|
69
|
+
def compile_for_dependencies(source, path)
|
|
70
|
+
template = ::Struct.new(:identifier, :format).new(path, :html)
|
|
71
|
+
visitor = slot_visitors(template, source, mark: false).first
|
|
72
|
+
|
|
73
|
+
return nil unless visitor
|
|
74
|
+
|
|
75
|
+
erb_implementation.new(
|
|
76
|
+
source,
|
|
77
|
+
filename: translate_path_for_editor(path),
|
|
78
|
+
project_path: ::ReActionView.config.project_path,
|
|
79
|
+
visitors: [*base_visitors(template), visitor],
|
|
80
|
+
**engine_options
|
|
81
|
+
).src
|
|
82
|
+
|
|
83
|
+
visitor
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def compile_for_block(source, path, index)
|
|
87
|
+
template = ::Struct.new(:identifier, :format).new(path, :html)
|
|
88
|
+
visitor = slot_visitors(template, source, mark: false).first
|
|
89
|
+
|
|
90
|
+
return nil unless visitor
|
|
91
|
+
|
|
92
|
+
::Herb::Engine::Slots::DynamicsCompiler.new(
|
|
93
|
+
source,
|
|
94
|
+
filename: translate_path_for_editor(path),
|
|
95
|
+
project_path: ::ReActionView.config.project_path,
|
|
96
|
+
visitors: base_visitors(template),
|
|
97
|
+
slot_visitor: visitor,
|
|
98
|
+
block: index,
|
|
99
|
+
**engine_options,
|
|
100
|
+
**VALUES_ESCAPING
|
|
101
|
+
).src
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def compile_for_schema(source, path)
|
|
105
|
+
template = ::Struct.new(:identifier, :format).new(path, :html)
|
|
106
|
+
|
|
107
|
+
::Herb::Engine::Slots::SchemaCompiler.call(
|
|
108
|
+
source,
|
|
109
|
+
filename: translate_path_for_editor(path),
|
|
110
|
+
mode: ::ReActionView.config.slot_mode_for(source),
|
|
111
|
+
visitors: -> { base_visitors(template, validation_mode: schema_validation_mode) },
|
|
112
|
+
engine: erb_implementation,
|
|
113
|
+
options: { project_path: ::ReActionView.config.project_path, **engine_options }
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
38
117
|
private
|
|
39
118
|
|
|
119
|
+
def base_visitors(template, validation_mode: ReActionView.config.validation_mode)
|
|
120
|
+
compile_stack(template, validation_mode: validation_mode).reject { |visitor| rewrites_erb_source?(visitor) }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def compile_stack(template, validation_mode:, slots: [])
|
|
124
|
+
visitors = [
|
|
125
|
+
*validation_visitors(validation_mode),
|
|
126
|
+
*debug_visitors(template),
|
|
127
|
+
*head_visitors(template),
|
|
128
|
+
*slots,
|
|
129
|
+
*::ReActionView.config.engine.visitors
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
::Herb::Visitor::Stack.arrange(visitors)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def engine_options
|
|
136
|
+
configured = ::ReActionView.config.engine.parser_options
|
|
137
|
+
options = resolver_option
|
|
138
|
+
|
|
139
|
+
return options if configured.empty?
|
|
140
|
+
|
|
141
|
+
defaults = ::Herb.configuration.engine_option("parser_options", {})
|
|
142
|
+
|
|
143
|
+
options.merge(parser_options: defaults.transform_keys(&:to_sym).merge(configured))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def resolver_option
|
|
147
|
+
return {} unless defined?(::Herb::Analysis::PartialResolver)
|
|
148
|
+
|
|
149
|
+
{ resolver: ::ReActionView::Template::PartialResolver.current }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def rewrites_erb_source?(visitor)
|
|
153
|
+
klass = visitor.class
|
|
154
|
+
|
|
155
|
+
klass.respond_to?(:rewrites_erb_source?) && klass.rewrites_erb_source?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def schema_validation_mode
|
|
159
|
+
ReActionView.config.validation_mode == :none ? :none : :overlay
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def values_source(template, source, config)
|
|
163
|
+
visitor = slot_visitors(template, source, mark: false).first
|
|
164
|
+
|
|
165
|
+
return "{}" unless visitor
|
|
166
|
+
|
|
167
|
+
::Herb::Engine::Slots::DynamicsCompiler.new(source, config.merge(slot_visitor: visitor, **VALUES_ESCAPING)).src
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def values_format?(template)
|
|
171
|
+
template.respond_to?(:format) && template.format == :slots
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def validation_visitors(mode)
|
|
175
|
+
case mode
|
|
176
|
+
when :raise then ::Herb::Engine::Validators.all(fatal: true).to_a
|
|
177
|
+
when :overlay then ::Herb::Engine::Validators.all(fatal: false).to_a
|
|
178
|
+
else []
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def debug_visitors(template)
|
|
183
|
+
return [] unless ::ReActionView.config.debug_mode_enabled? && local_template?(template)
|
|
184
|
+
|
|
185
|
+
[::Herb::Engine::DebugVisitor.new]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def head_visitors(template)
|
|
189
|
+
markup = reactionview_dev_tools_markup(template)
|
|
190
|
+
|
|
191
|
+
return [] if markup.nil? || markup.empty?
|
|
192
|
+
|
|
193
|
+
[::Herb::Engine::ContentForVisitor.new(markup, tag_name: "head")]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def slot_visitors(template, source, mark: true)
|
|
197
|
+
return [] unless values_format?(template) || (template.respond_to?(:format) && template.format == :html)
|
|
198
|
+
|
|
199
|
+
mode = ::ReActionView.config.slot_mode_for(source)
|
|
200
|
+
|
|
201
|
+
return [] unless mode
|
|
202
|
+
|
|
203
|
+
[::Herb::Engine::Slots::Visitor.new(mode: mode, mark: mark)]
|
|
204
|
+
end
|
|
205
|
+
|
|
40
206
|
def layout_template?(template)
|
|
41
207
|
return false unless template.respond_to?(:identifier) && template.identifier
|
|
42
208
|
|
|
@@ -76,9 +242,9 @@ module ReActionView
|
|
|
76
242
|
end
|
|
77
243
|
|
|
78
244
|
def reactionview_dev_tools_markup(template)
|
|
79
|
-
return nil unless ::ReActionView.config.debug_mode_enabled?
|
|
80
245
|
return nil unless layout_template?(template)
|
|
81
246
|
return nil unless local_template?(template)
|
|
247
|
+
return slots_meta_markup unless ::ReActionView.config.debug_mode_enabled?
|
|
82
248
|
|
|
83
249
|
<<~HTML
|
|
84
250
|
<meta name="herb-debug-mode" content="true">
|
|
@@ -91,6 +257,15 @@ module ReActionView
|
|
|
91
257
|
HTML
|
|
92
258
|
end
|
|
93
259
|
|
|
260
|
+
def slots_meta_markup
|
|
261
|
+
return nil unless ::ReActionView.config.slots && ::ReActionView.config.development?
|
|
262
|
+
|
|
263
|
+
<<~HTML
|
|
264
|
+
<meta name="herb-project-path" content="#{Rails.root}">
|
|
265
|
+
#{dev_server_port_meta_tag}
|
|
266
|
+
HTML
|
|
267
|
+
end
|
|
268
|
+
|
|
94
269
|
def dismiss_hint_template
|
|
95
270
|
return unless ::ReActionView.config.validation_mode == :overlay
|
|
96
271
|
|