omg-actionview 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +25 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +316 -0
- data/lib/action_view/buffers.rb +165 -0
- data/lib/action_view/cache_expiry.rb +69 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +41 -0
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +130 -0
- data/lib/action_view/flows.rb +75 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers/active_model_helper.rb +54 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
- data/lib/action_view/helpers/asset_url_helper.rb +473 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +315 -0
- data/lib/action_view/helpers/capture_helper.rb +236 -0
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +42 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1266 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +2765 -0
- data/lib/action_view/helpers/form_options_helper.rb +927 -0
- data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
- data/lib/action_view/helpers/javascript_helper.rb +96 -0
- data/lib/action_view/helpers/number_helper.rb +165 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +218 -0
- data/lib/action_view/helpers/sanitize_helper.rb +201 -0
- data/lib/action_view/helpers/tag_helper.rb +621 -0
- data/lib/action_view/helpers/tags/base.rb +138 -0
- data/lib/action_view/helpers/tags/check_box.rb +65 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +33 -0
- data/lib/action_view/helpers/tags/color_field.rb +26 -0
- data/lib/action_view/helpers/tags/date_field.rb +14 -0
- data/lib/action_view/helpers/tags/date_select.rb +75 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +26 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
- data/lib/action_view/helpers/tags/label.rb +84 -0
- data/lib/action_view/helpers/tags/month_field.rb +14 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +32 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +45 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +33 -0
- data/lib/action_view/helpers/tags/time_field.rb +23 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +14 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +47 -0
- data/lib/action_view/helpers/text_helper.rb +568 -0
- data/lib/action_view/helpers/translation_helper.rb +161 -0
- data/lib/action_view/helpers/url_helper.rb +812 -0
- data/lib/action_view/helpers.rb +68 -0
- data/lib/action_view/layouts.rb +434 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +132 -0
- data/lib/action_view/lookup_context.rb +299 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +84 -0
- data/lib/action_view/railtie.rb +132 -0
- data/lib/action_view/record_identifier.rb +118 -0
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
- data/lib/action_view/render_parser.rb +40 -0
- data/lib/action_view/renderer/abstract_renderer.rb +186 -0
- data/lib/action_view/renderer/collection_renderer.rb +204 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
- data/lib/action_view/renderer/partial_renderer.rb +267 -0
- data/lib/action_view/renderer/renderer.rb +107 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
- data/lib/action_view/renderer/template_renderer.rb +115 -0
- data/lib/action_view/rendering.rb +190 -0
- data/lib/action_view/routing_url_for.rb +149 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +264 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
- data/lib/action_view/template/handlers/erb.rb +157 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +66 -0
- data/lib/action_view/template/html.rb +33 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +30 -0
- data/lib/action_view/template/resolver.rb +212 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +32 -0
- data/lib/action_view/template/types.rb +50 -0
- data/lib/action_view/template.rb +580 -0
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +449 -0
- data/lib/action_view/testing/resolvers.rb +44 -0
- data/lib/action_view/unbound_template.rb +67 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +117 -0
- data/lib/action_view.rb +104 -0
- metadata +275 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module RenderParser
|
|
5
|
+
class PrismRenderParser < Base # :nodoc:
|
|
6
|
+
def render_calls
|
|
7
|
+
queue = [Prism.parse(@code).value]
|
|
8
|
+
templates = []
|
|
9
|
+
|
|
10
|
+
while (node = queue.shift)
|
|
11
|
+
queue.concat(node.compact_child_nodes)
|
|
12
|
+
next unless node.is_a?(Prism::CallNode)
|
|
13
|
+
|
|
14
|
+
options = render_call_options(node)
|
|
15
|
+
next unless options
|
|
16
|
+
|
|
17
|
+
render_type = (options.keys & RENDER_TYPE_KEYS)[0]
|
|
18
|
+
template, object_template = render_call_template(options[render_type])
|
|
19
|
+
next unless template
|
|
20
|
+
|
|
21
|
+
if options.key?(:object) || options.key?(:collection) || object_template
|
|
22
|
+
next if options.key?(:object) && options.key?(:collection)
|
|
23
|
+
next unless options.key?(:partial)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if options[:spacer_template].is_a?(Prism::StringNode)
|
|
27
|
+
templates << partial_to_virtual_path(:partial, options[:spacer_template].unescaped)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
templates << partial_to_virtual_path(render_type, template)
|
|
31
|
+
|
|
32
|
+
if render_type != :layout && options[:layout].is_a?(Prism::StringNode)
|
|
33
|
+
templates << partial_to_virtual_path(:layout, options[:layout].unescaped)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
templates
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
# Accept a call node and return a hash of options for the render call.
|
|
42
|
+
# If it doesn't match the expected format, return nil.
|
|
43
|
+
def render_call_options(node)
|
|
44
|
+
# We are only looking for calls to render or render_to_string.
|
|
45
|
+
name = node.name.to_sym
|
|
46
|
+
return if name != :render && name != :render_to_string
|
|
47
|
+
|
|
48
|
+
# We are only looking for calls with arguments.
|
|
49
|
+
arguments = node.arguments
|
|
50
|
+
return unless arguments
|
|
51
|
+
|
|
52
|
+
arguments = arguments.arguments
|
|
53
|
+
length = arguments.length
|
|
54
|
+
|
|
55
|
+
# Get rid of any parentheses to get directly to the contents.
|
|
56
|
+
arguments.map! do |argument|
|
|
57
|
+
current = argument
|
|
58
|
+
|
|
59
|
+
while current.is_a?(Prism::ParenthesesNode) &&
|
|
60
|
+
current.body.is_a?(Prism::StatementsNode) &&
|
|
61
|
+
current.body.body.length == 1
|
|
62
|
+
current = current.body.body.first
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
current
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# We are only looking for arguments that are either a string with an
|
|
69
|
+
# array of locals or a keyword hash with symbol keys.
|
|
70
|
+
options =
|
|
71
|
+
if (length == 1 || length == 2) && !arguments[0].is_a?(Prism::KeywordHashNode)
|
|
72
|
+
{ partial: arguments[0], locals: arguments[1] }
|
|
73
|
+
elsif length == 1 &&
|
|
74
|
+
arguments[0].is_a?(Prism::KeywordHashNode) &&
|
|
75
|
+
arguments[0].elements.all? do |element|
|
|
76
|
+
element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode)
|
|
77
|
+
end
|
|
78
|
+
arguments[0].elements.to_h do |element|
|
|
79
|
+
[element.key.unescaped.to_sym, element.value]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
return unless options
|
|
84
|
+
|
|
85
|
+
# Here we validate that the options have the keys we expect.
|
|
86
|
+
keys = options.keys
|
|
87
|
+
return if !keys.intersect?(RENDER_TYPE_KEYS)
|
|
88
|
+
return if (keys - ALL_KNOWN_KEYS).any?
|
|
89
|
+
|
|
90
|
+
# Finally, we can return a valid set of options.
|
|
91
|
+
options
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Accept the node that is being passed in the position of the template
|
|
95
|
+
# and return the template name and whether or not it is an object
|
|
96
|
+
# template.
|
|
97
|
+
def render_call_template(node)
|
|
98
|
+
object_template = false
|
|
99
|
+
template =
|
|
100
|
+
case node.type
|
|
101
|
+
when :string_node
|
|
102
|
+
path = node.unescaped
|
|
103
|
+
path.include?("/") ? path : "#{directory}/#{path}"
|
|
104
|
+
when :interpolated_string_node
|
|
105
|
+
node.parts.map do |node|
|
|
106
|
+
case node.type
|
|
107
|
+
when :string_node
|
|
108
|
+
node.unescaped
|
|
109
|
+
when :embedded_statements_node
|
|
110
|
+
"*"
|
|
111
|
+
else
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
end.join("")
|
|
115
|
+
else
|
|
116
|
+
dependency =
|
|
117
|
+
case node.type
|
|
118
|
+
when :class_variable_read_node
|
|
119
|
+
node.slice[2..]
|
|
120
|
+
when :instance_variable_read_node
|
|
121
|
+
node.slice[1..]
|
|
122
|
+
when :global_variable_read_node
|
|
123
|
+
node.slice[1..]
|
|
124
|
+
when :local_variable_read_node
|
|
125
|
+
node.slice
|
|
126
|
+
when :call_node
|
|
127
|
+
node.name.to_s
|
|
128
|
+
else
|
|
129
|
+
return
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
"#{dependency.pluralize}/#{dependency.singularize}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
[template, object_template]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module RenderParser
|
|
5
|
+
class RipperRenderParser < Base # :nodoc:
|
|
6
|
+
class Node < ::Array # :nodoc:
|
|
7
|
+
attr_reader :type
|
|
8
|
+
|
|
9
|
+
def initialize(type, arr, opts = {})
|
|
10
|
+
@type = type
|
|
11
|
+
super(arr)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def children
|
|
15
|
+
to_a
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def inspect
|
|
19
|
+
typeinfo = type && type != :list ? ":" + type.to_s + ", " : ""
|
|
20
|
+
"s(" + typeinfo + map(&:inspect).join(", ") + ")"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fcall?
|
|
24
|
+
type == :command || type == :fcall
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def fcall_named?(name)
|
|
28
|
+
fcall? &&
|
|
29
|
+
self[0].type == :@ident &&
|
|
30
|
+
self[0][0] == name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def argument_nodes
|
|
34
|
+
raise unless fcall?
|
|
35
|
+
return [] if self[1].nil?
|
|
36
|
+
if self[1].last == false || self[1].last.type == :vcall
|
|
37
|
+
self[1][0...-1]
|
|
38
|
+
else
|
|
39
|
+
self[1][0..-1]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def string?
|
|
44
|
+
type == :string_literal
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def variable_reference?
|
|
48
|
+
type == :var_ref
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def vcall?
|
|
52
|
+
type == :vcall
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def call?
|
|
56
|
+
type == :call
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def variable_name
|
|
60
|
+
self[0][0]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call_method_name
|
|
64
|
+
self[2].first
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def to_string
|
|
68
|
+
raise unless string?
|
|
69
|
+
|
|
70
|
+
# s(:string_literal, s(:string_content, map))
|
|
71
|
+
self[0].map do |node|
|
|
72
|
+
case node.type
|
|
73
|
+
when :@tstring_content
|
|
74
|
+
node[0]
|
|
75
|
+
when :string_embexpr
|
|
76
|
+
"*"
|
|
77
|
+
end
|
|
78
|
+
end.join("")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def hash?
|
|
82
|
+
type == :bare_assoc_hash || type == :hash
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_hash
|
|
86
|
+
if type == :bare_assoc_hash
|
|
87
|
+
hash_from_body(self[0])
|
|
88
|
+
elsif type == :hash && self[0] == nil
|
|
89
|
+
{}
|
|
90
|
+
elsif type == :hash && self[0].type == :assoclist_from_args
|
|
91
|
+
hash_from_body(self[0][0])
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def hash_from_body(body)
|
|
96
|
+
body.to_h do |hash_node|
|
|
97
|
+
return nil if hash_node.type != :assoc_new
|
|
98
|
+
|
|
99
|
+
[hash_node[0], hash_node[1]]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def symbol?
|
|
104
|
+
type == :@label || type == :symbol_literal
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def to_symbol
|
|
108
|
+
if type == :@label && self[0] =~ /\A(.+):\z/
|
|
109
|
+
$1.to_sym
|
|
110
|
+
elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
|
|
111
|
+
self[0][0][0].to_sym
|
|
112
|
+
else
|
|
113
|
+
raise "not a symbol?: #{self.inspect}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class NodeParser < ::Ripper # :nodoc:
|
|
119
|
+
PARSER_EVENTS.each do |event|
|
|
120
|
+
arity = PARSER_EVENT_TABLE[event]
|
|
121
|
+
if arity == 0 && event.to_s.end_with?("_new")
|
|
122
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
|
123
|
+
def on_#{event}(*args)
|
|
124
|
+
Node.new(:list, args, lineno: lineno(), column: column())
|
|
125
|
+
end
|
|
126
|
+
eof
|
|
127
|
+
elsif event.to_s.match?(/_add(_.+)?\z/)
|
|
128
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
|
129
|
+
begin; undef on_#{event}; rescue NameError; end
|
|
130
|
+
def on_#{event}(list, item)
|
|
131
|
+
list.push(item)
|
|
132
|
+
list
|
|
133
|
+
end
|
|
134
|
+
eof
|
|
135
|
+
else
|
|
136
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
|
137
|
+
begin; undef on_#{event}; rescue NameError; end
|
|
138
|
+
def on_#{event}(*args)
|
|
139
|
+
Node.new(:#{event}, args, lineno: lineno(), column: column())
|
|
140
|
+
end
|
|
141
|
+
eof
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
SCANNER_EVENTS.each do |event|
|
|
146
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
|
147
|
+
def on_#{event}(tok)
|
|
148
|
+
Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
|
|
149
|
+
end
|
|
150
|
+
End
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class RenderCallExtractor < NodeParser # :nodoc:
|
|
155
|
+
attr_reader :render_calls
|
|
156
|
+
|
|
157
|
+
METHODS_TO_PARSE = %w(render render_to_string)
|
|
158
|
+
|
|
159
|
+
def initialize(*args)
|
|
160
|
+
super
|
|
161
|
+
|
|
162
|
+
@render_calls = []
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
def on_fcall(name, *args)
|
|
167
|
+
on_render_call(super)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def on_command(name, *args)
|
|
171
|
+
on_render_call(super)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def on_render_call(node)
|
|
175
|
+
METHODS_TO_PARSE.each do |method|
|
|
176
|
+
if node.fcall_named?(method)
|
|
177
|
+
@render_calls << [method, node]
|
|
178
|
+
return node
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
node
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def on_arg_paren(content)
|
|
185
|
+
content
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def on_paren(content)
|
|
189
|
+
content.size == 1 ? content.first : content
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def render_calls
|
|
194
|
+
parser = RenderCallExtractor.new(@code)
|
|
195
|
+
parser.parse
|
|
196
|
+
|
|
197
|
+
parser.render_calls.group_by(&:first).to_h do |method, nodes|
|
|
198
|
+
[ method.to_sym, nodes.collect { |v| v[1] } ]
|
|
199
|
+
end.map do |method, nodes|
|
|
200
|
+
nodes.map { |n| parse_render(n) }
|
|
201
|
+
end.flatten.compact
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
def resolve_path_directory(path)
|
|
206
|
+
if path.include?("/")
|
|
207
|
+
path
|
|
208
|
+
else
|
|
209
|
+
"#{directory}/#{path}"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Convert
|
|
214
|
+
# render("foo", ...)
|
|
215
|
+
# into either
|
|
216
|
+
# render(template: "foo", ...)
|
|
217
|
+
# or
|
|
218
|
+
# render(partial: "foo", ...)
|
|
219
|
+
def normalize_args(string, options_hash)
|
|
220
|
+
if options_hash
|
|
221
|
+
{ partial: string, locals: options_hash }
|
|
222
|
+
else
|
|
223
|
+
{ partial: string }
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def parse_render(node)
|
|
228
|
+
node = node.argument_nodes
|
|
229
|
+
|
|
230
|
+
if (node.length == 1 || node.length == 2) && !node[0].hash?
|
|
231
|
+
if node.length == 1
|
|
232
|
+
options = normalize_args(node[0], nil)
|
|
233
|
+
elsif node.length == 2
|
|
234
|
+
options = normalize_args(node[0], node[1])
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
return nil unless options
|
|
238
|
+
|
|
239
|
+
parse_render_from_options(options)
|
|
240
|
+
elsif node.length == 1 && node[0].hash?
|
|
241
|
+
options = parse_hash_to_symbols(node[0])
|
|
242
|
+
|
|
243
|
+
return nil unless options
|
|
244
|
+
|
|
245
|
+
parse_render_from_options(options)
|
|
246
|
+
else
|
|
247
|
+
nil
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def parse_hash(node)
|
|
252
|
+
node.hash? && node.to_hash
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def parse_hash_to_symbols(node)
|
|
256
|
+
hash = parse_hash(node)
|
|
257
|
+
|
|
258
|
+
return unless hash
|
|
259
|
+
|
|
260
|
+
hash.transform_keys do |key_node|
|
|
261
|
+
key = parse_sym(key_node)
|
|
262
|
+
|
|
263
|
+
return unless key
|
|
264
|
+
|
|
265
|
+
key
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def parse_render_from_options(options_hash)
|
|
270
|
+
renders = []
|
|
271
|
+
keys = options_hash.keys
|
|
272
|
+
|
|
273
|
+
if (keys & RENDER_TYPE_KEYS).size < 1
|
|
274
|
+
# Must have at least one of render keys
|
|
275
|
+
return nil
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
if (keys - ALL_KNOWN_KEYS).any?
|
|
279
|
+
# de-opt in case of unknown option
|
|
280
|
+
return nil
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
render_type = (keys & RENDER_TYPE_KEYS)[0]
|
|
284
|
+
|
|
285
|
+
node = options_hash[render_type]
|
|
286
|
+
|
|
287
|
+
if node.string?
|
|
288
|
+
template = resolve_path_directory(node.to_string)
|
|
289
|
+
else
|
|
290
|
+
if node.variable_reference?
|
|
291
|
+
dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "")
|
|
292
|
+
elsif node.vcall?
|
|
293
|
+
dependency = node.variable_name
|
|
294
|
+
elsif node.call?
|
|
295
|
+
dependency = node.call_method_name
|
|
296
|
+
else
|
|
297
|
+
return
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
object_template = true
|
|
301
|
+
template = "#{dependency.pluralize}/#{dependency.singularize}"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
return unless template
|
|
305
|
+
|
|
306
|
+
if spacer_template = render_template_with_spacer?(options_hash)
|
|
307
|
+
virtual_path = partial_to_virtual_path(:partial, spacer_template)
|
|
308
|
+
renders << virtual_path
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
if options_hash.key?(:object) || options_hash.key?(:collection) || object_template
|
|
312
|
+
return nil if options_hash.key?(:object) && options_hash.key?(:collection)
|
|
313
|
+
return nil unless options_hash.key?(:partial)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
virtual_path = partial_to_virtual_path(render_type, template)
|
|
317
|
+
renders << virtual_path
|
|
318
|
+
|
|
319
|
+
# Support for rendering multiple templates (i.e. a partial with a layout)
|
|
320
|
+
if layout_template = render_template_with_layout?(render_type, options_hash)
|
|
321
|
+
virtual_path = partial_to_virtual_path(:layout, layout_template)
|
|
322
|
+
|
|
323
|
+
renders << virtual_path
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
renders
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def parse_str(node)
|
|
330
|
+
node.string? && node.to_string
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def parse_sym(node)
|
|
334
|
+
node.symbol? && node.to_symbol
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def render_template_with_layout?(render_type, options_hash)
|
|
338
|
+
if render_type != :layout && options_hash.key?(:layout)
|
|
339
|
+
parse_str(options_hash[:layout])
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def render_template_with_spacer?(options_hash)
|
|
344
|
+
if options_hash.key?(:spacer_template)
|
|
345
|
+
parse_str(options_hash[:spacer_template])
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module RenderParser # :nodoc:
|
|
5
|
+
ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template]
|
|
6
|
+
RENDER_TYPE_KEYS = [:partial, :template, :layout]
|
|
7
|
+
|
|
8
|
+
class Base # :nodoc:
|
|
9
|
+
def initialize(name, code)
|
|
10
|
+
@name = name
|
|
11
|
+
@code = code
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def directory
|
|
16
|
+
File.dirname(@name)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def partial_to_virtual_path(render_type, partial_path)
|
|
20
|
+
if render_type == :partial || render_type == :layout
|
|
21
|
+
partial_path.gsub(%r{(/|^)([^/]*)\z}, '\1_\2')
|
|
22
|
+
else
|
|
23
|
+
partial_path
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Check if prism is available. If it is, use it. Otherwise, use ripper.
|
|
29
|
+
begin
|
|
30
|
+
require "prism"
|
|
31
|
+
rescue LoadError
|
|
32
|
+
require "ripper"
|
|
33
|
+
require_relative "render_parser/ripper_render_parser"
|
|
34
|
+
Default = RipperRenderParser
|
|
35
|
+
else
|
|
36
|
+
require_relative "render_parser/prism_render_parser"
|
|
37
|
+
Default = PrismRenderParser
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
|
|
5
|
+
module ActionView
|
|
6
|
+
# This class defines the interface for a renderer. Each class that
|
|
7
|
+
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
|
|
8
|
+
# render a specific type of object.
|
|
9
|
+
#
|
|
10
|
+
# The base +Renderer+ class uses its +render+ method to delegate to the
|
|
11
|
+
# renderers. These currently consist of
|
|
12
|
+
#
|
|
13
|
+
# PartialRenderer - Used for rendering partials
|
|
14
|
+
# TemplateRenderer - Used for rendering other types of templates
|
|
15
|
+
# StreamingTemplateRenderer - Used for streaming
|
|
16
|
+
#
|
|
17
|
+
# Whenever the +render+ method is called on the base +Renderer+ class, a new
|
|
18
|
+
# renderer object of the correct type is created, and the +render+ method on
|
|
19
|
+
# that new object is called in turn. This abstracts the set up and rendering
|
|
20
|
+
# into a separate classes for partials and templates.
|
|
21
|
+
class AbstractRenderer # :nodoc:
|
|
22
|
+
delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
|
|
23
|
+
|
|
24
|
+
def initialize(lookup_context)
|
|
25
|
+
@lookup_context = lookup_context
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def render
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module ObjectRendering # :nodoc:
|
|
33
|
+
PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
|
|
34
|
+
h.compute_if_absent(k) { Concurrent::Map.new }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(lookup_context, options)
|
|
38
|
+
super
|
|
39
|
+
@context_prefix = lookup_context.prefixes.first
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def local_variable(path)
|
|
44
|
+
if as = @options[:as]
|
|
45
|
+
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
|
|
46
|
+
as.to_sym
|
|
47
|
+
else
|
|
48
|
+
base = path.end_with?("/") ? "" : File.basename(path)
|
|
49
|
+
raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
|
|
50
|
+
$1.to_sym
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
|
|
55
|
+
"make sure your partial name starts with underscore."
|
|
56
|
+
|
|
57
|
+
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
|
|
58
|
+
"make sure it starts with lowercase letter, " \
|
|
59
|
+
"and is followed by any combination of letters, numbers and underscores."
|
|
60
|
+
|
|
61
|
+
def raise_invalid_identifier(path)
|
|
62
|
+
raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def raise_invalid_option_as(as)
|
|
66
|
+
raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Obtains the path to where the object's partial is located. If the object
|
|
70
|
+
# responds to +to_partial_path+, then +to_partial_path+ will be called and
|
|
71
|
+
# will provide the path. If the object does not respond to +to_partial_path+,
|
|
72
|
+
# then an +ArgumentError+ is raised.
|
|
73
|
+
#
|
|
74
|
+
# If +prefix_partial_path_with_controller_namespace+ is true, then this
|
|
75
|
+
# method will prefix the partial paths with a namespace.
|
|
76
|
+
def partial_path(object, view)
|
|
77
|
+
object = object.to_model if object.respond_to?(:to_model)
|
|
78
|
+
|
|
79
|
+
path = if object.respond_to?(:to_partial_path)
|
|
80
|
+
object.to_partial_path
|
|
81
|
+
else
|
|
82
|
+
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement #to_partial_path.")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if view.prefix_partial_path_with_controller_namespace
|
|
86
|
+
PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
|
|
87
|
+
else
|
|
88
|
+
path
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def merge_prefix_into_object_path(prefix, object_path)
|
|
93
|
+
if prefix.include?(?/) && object_path.include?(?/)
|
|
94
|
+
prefixes = []
|
|
95
|
+
prefix_array = File.dirname(prefix).split("/")
|
|
96
|
+
object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
|
|
97
|
+
|
|
98
|
+
prefix_array.each_with_index do |dir, index|
|
|
99
|
+
break if dir == object_path_array[index]
|
|
100
|
+
prefixes << dir
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
(prefixes << object_path).join("/")
|
|
104
|
+
else
|
|
105
|
+
object_path
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class RenderedCollection # :nodoc:
|
|
111
|
+
def self.empty(format)
|
|
112
|
+
EmptyCollection.new format
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
attr_reader :rendered_templates
|
|
116
|
+
|
|
117
|
+
def initialize(rendered_templates, spacer)
|
|
118
|
+
@rendered_templates = rendered_templates
|
|
119
|
+
@spacer = spacer
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def body
|
|
123
|
+
@rendered_templates.map(&:body).join(@spacer.body).html_safe
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def format
|
|
127
|
+
rendered_templates.first.format
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class EmptyCollection
|
|
131
|
+
attr_reader :format
|
|
132
|
+
|
|
133
|
+
def initialize(format)
|
|
134
|
+
@format = format
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def body; nil; end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class RenderedTemplate # :nodoc:
|
|
142
|
+
attr_reader :body, :template
|
|
143
|
+
|
|
144
|
+
def initialize(body, template)
|
|
145
|
+
@body = body
|
|
146
|
+
@template = template
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def format
|
|
150
|
+
template.format
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
EMPTY_SPACER = Struct.new(:body).new
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
NO_DETAILS = {}.freeze
|
|
158
|
+
|
|
159
|
+
def extract_details(options) # :doc:
|
|
160
|
+
details = nil
|
|
161
|
+
LookupContext.registered_details.each do |key|
|
|
162
|
+
value = options[key]
|
|
163
|
+
|
|
164
|
+
if value
|
|
165
|
+
(details ||= {})[key] = Array(value)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
details || NO_DETAILS
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def prepend_formats(formats) # :doc:
|
|
172
|
+
formats = Array(formats)
|
|
173
|
+
return if formats.empty? || @lookup_context.html_fallback_for_js
|
|
174
|
+
|
|
175
|
+
@lookup_context.formats = formats | @lookup_context.formats
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def build_rendered_template(content, template)
|
|
179
|
+
RenderedTemplate.new content, template
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def build_rendered_collection(templates, spacer)
|
|
183
|
+
RenderedCollection.new templates, spacer
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|