phlex 2.3.2 → 2.4.0.beta1
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/lib/phlex/compiler/class_compiler.rb +35 -0
- data/lib/phlex/compiler/compilation.rb +45 -0
- data/lib/phlex/compiler/file_compiler.rb +46 -0
- data/lib/phlex/compiler/method_compiler.rb +591 -0
- data/lib/phlex/compiler.rb +18 -0
- data/lib/phlex/html.rb +4 -4
- data/lib/phlex/sgml/attributes.rb +280 -0
- data/lib/phlex/sgml/elements.rb +8 -8
- data/lib/phlex/sgml.rb +11 -289
- data/lib/phlex/svg.rb +3 -3
- data/lib/phlex/version.rb +1 -1
- metadata +22 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require "refract"
|
|
5
|
+
|
|
6
|
+
module Phlex::Compiler
|
|
7
|
+
Error = Class.new(StandardError)
|
|
8
|
+
|
|
9
|
+
def self.compile(component)
|
|
10
|
+
path, line = Object.const_source_location(component.name)
|
|
11
|
+
return unless File.exist?(path)
|
|
12
|
+
source = File.read(path)
|
|
13
|
+
tree = Prism.parse(source).value
|
|
14
|
+
refract = Refract::Converter.new.visit(tree)
|
|
15
|
+
|
|
16
|
+
Compilation.new(component, path, line, source, refract).compile
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/phlex/html.rb
CHANGED
|
@@ -55,10 +55,10 @@ class Phlex::HTML < Phlex::SGML
|
|
|
55
55
|
raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
if (tag = StandardElements.__registered_elements__[name]) || (
|
|
58
|
+
if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
|
|
59
59
|
if attributes.length > 0 # with attributes
|
|
60
60
|
if block_given # with content block
|
|
61
|
-
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||=
|
|
61
|
+
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
|
|
62
62
|
if tag == "svg"
|
|
63
63
|
render Phlex::SVG.new(&)
|
|
64
64
|
else
|
|
@@ -66,7 +66,7 @@ class Phlex::HTML < Phlex::SGML
|
|
|
66
66
|
end
|
|
67
67
|
buffer << "</#{tag}>"
|
|
68
68
|
else # without content
|
|
69
|
-
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||=
|
|
69
|
+
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << "></#{tag}>"
|
|
70
70
|
end
|
|
71
71
|
else # without attributes
|
|
72
72
|
if block_given # with content block
|
|
@@ -87,7 +87,7 @@ class Phlex::HTML < Phlex::SGML
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
if attributes.length > 0 # with attributes
|
|
90
|
-
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||=
|
|
90
|
+
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
|
|
91
91
|
else # without attributes
|
|
92
92
|
buffer << "<#{tag}>"
|
|
93
93
|
end
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phlex::SGML::Attributes
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
UNSAFE_ATTRIBUTES = Set.new(%w[srcdoc sandbox http-equiv]).freeze
|
|
7
|
+
REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping]).freeze
|
|
8
|
+
|
|
9
|
+
def generate_attributes(attributes, buffer = +"")
|
|
10
|
+
attributes.each do |k, v|
|
|
11
|
+
next unless v
|
|
12
|
+
|
|
13
|
+
name = case k
|
|
14
|
+
when String then k
|
|
15
|
+
when Symbol then k.name.tr("_", "-")
|
|
16
|
+
else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols.")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
value = case v
|
|
20
|
+
when true
|
|
21
|
+
true
|
|
22
|
+
when String
|
|
23
|
+
v.gsub('"', """)
|
|
24
|
+
when Symbol
|
|
25
|
+
v.name.tr("_", "-").gsub('"', """)
|
|
26
|
+
when Integer, Float
|
|
27
|
+
v.to_s
|
|
28
|
+
when Date
|
|
29
|
+
v.iso8601
|
|
30
|
+
when Time
|
|
31
|
+
v.respond_to?(:iso8601) ? v.iso8601 : v.strftime("%Y-%m-%dT%H:%M:%S%:z")
|
|
32
|
+
when Hash
|
|
33
|
+
case k
|
|
34
|
+
when :style
|
|
35
|
+
generate_styles(v).gsub('"', """)
|
|
36
|
+
else
|
|
37
|
+
generate_nested_attributes(v, "#{name}-", buffer)
|
|
38
|
+
end
|
|
39
|
+
when Array
|
|
40
|
+
case k
|
|
41
|
+
when :style
|
|
42
|
+
generate_styles(v).gsub('"', """)
|
|
43
|
+
else
|
|
44
|
+
generate_nested_tokens(v)
|
|
45
|
+
end
|
|
46
|
+
when Set
|
|
47
|
+
case k
|
|
48
|
+
when :style
|
|
49
|
+
generate_styles(v).gsub('"', """)
|
|
50
|
+
else
|
|
51
|
+
generate_nested_tokens(v.to_a)
|
|
52
|
+
end
|
|
53
|
+
when Phlex::SGML::SafeObject
|
|
54
|
+
v.to_s.gsub('"', """)
|
|
55
|
+
else
|
|
56
|
+
raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
lower_name = name.downcase
|
|
60
|
+
|
|
61
|
+
unless Phlex::SGML::SafeObject === v
|
|
62
|
+
normalized_name = lower_name.delete("^a-z-")
|
|
63
|
+
|
|
64
|
+
if value != true && REF_ATTRIBUTES.include?(normalized_name)
|
|
65
|
+
case value
|
|
66
|
+
when String
|
|
67
|
+
if value.downcase.delete("^a-z:").start_with?("javascript:")
|
|
68
|
+
# We just ignore these because they were likely not specified by the developer.
|
|
69
|
+
next
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if normalized_name.bytesize > 2 && normalized_name.start_with?("on") && !normalized_name.include?("-")
|
|
77
|
+
raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if UNSAFE_ATTRIBUTES.include?(normalized_name)
|
|
81
|
+
raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if name.match?(/[<>&"']/)
|
|
86
|
+
raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if lower_name.to_sym == :id && k != :id
|
|
90
|
+
raise Phlex::ArgumentError.new(":id attribute should only be passed as a lowercase symbol.")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
case value
|
|
94
|
+
when true
|
|
95
|
+
buffer << " " << name
|
|
96
|
+
when String
|
|
97
|
+
buffer << " " << name << '="' << value << '"'
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
buffer
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Provides the nested-attributes case for serializing out attributes.
|
|
105
|
+
# This allows us to skip many of the checks the `__attributes__` method must perform.
|
|
106
|
+
def generate_nested_attributes(attributes, base_name, buffer = +"")
|
|
107
|
+
attributes.each do |k, v|
|
|
108
|
+
next unless v
|
|
109
|
+
|
|
110
|
+
if (root_key = (:_ == k))
|
|
111
|
+
name = ""
|
|
112
|
+
original_base_name = base_name
|
|
113
|
+
base_name = base_name.delete_suffix("-")
|
|
114
|
+
else
|
|
115
|
+
name = case k
|
|
116
|
+
when String then k
|
|
117
|
+
when Symbol then k.name.tr("_", "-")
|
|
118
|
+
else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if name.match?(/[<>&"']/)
|
|
122
|
+
raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
case v
|
|
127
|
+
when true
|
|
128
|
+
buffer << " " << base_name << name
|
|
129
|
+
when String
|
|
130
|
+
buffer << " " << base_name << name << '="' << v.gsub('"', """) << '"'
|
|
131
|
+
when Symbol
|
|
132
|
+
buffer << " " << base_name << name << '="' << v.name.tr("_", "-").gsub('"', """) << '"'
|
|
133
|
+
when Integer, Float
|
|
134
|
+
buffer << " " << base_name << name << '="' << v.to_s << '"'
|
|
135
|
+
when Hash
|
|
136
|
+
generate_nested_attributes(v, "#{base_name}#{name}-", buffer)
|
|
137
|
+
when Array
|
|
138
|
+
if (value = generate_nested_tokens(v))
|
|
139
|
+
buffer << " " << base_name << name << '="' << value << '"'
|
|
140
|
+
end
|
|
141
|
+
when Set
|
|
142
|
+
if (value = generate_nested_tokens(v.to_a))
|
|
143
|
+
buffer << " " << base_name << name << '="' << value << '"'
|
|
144
|
+
end
|
|
145
|
+
when Phlex::SGML::SafeObject
|
|
146
|
+
buffer << " " << base_name << name << '="' << v.to_s.gsub('"', """) << '"'
|
|
147
|
+
else
|
|
148
|
+
raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if root_key
|
|
152
|
+
base_name = original_base_name
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
buffer
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def generate_nested_tokens(tokens, sep = " ", gsub_from = nil, gsub_to = "")
|
|
160
|
+
buffer = +""
|
|
161
|
+
|
|
162
|
+
i, length = 0, tokens.length
|
|
163
|
+
|
|
164
|
+
while i < length
|
|
165
|
+
token = tokens[i]
|
|
166
|
+
|
|
167
|
+
case token
|
|
168
|
+
when String
|
|
169
|
+
token = token.gsub(gsub_from, gsub_to) if gsub_from
|
|
170
|
+
if i > 0
|
|
171
|
+
buffer << sep << token
|
|
172
|
+
else
|
|
173
|
+
buffer << token
|
|
174
|
+
end
|
|
175
|
+
when Symbol
|
|
176
|
+
if i > 0
|
|
177
|
+
buffer << sep << token.name.tr("_", "-")
|
|
178
|
+
else
|
|
179
|
+
buffer << token.name.tr("_", "-")
|
|
180
|
+
end
|
|
181
|
+
when Integer, Float, Phlex::SGML::SafeObject
|
|
182
|
+
if i > 0
|
|
183
|
+
buffer << sep << token.to_s
|
|
184
|
+
else
|
|
185
|
+
buffer << token.to_s
|
|
186
|
+
end
|
|
187
|
+
when Array
|
|
188
|
+
if token.length > 0 && (value = generate_nested_tokens(token, sep, gsub_from, gsub_to))
|
|
189
|
+
if i > 0
|
|
190
|
+
buffer << sep << value
|
|
191
|
+
else
|
|
192
|
+
buffer << value
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
when Set
|
|
196
|
+
if token.length > 0 && (value = generate_nested_tokens(token.to_a, sep, gsub_from, gsub_to))
|
|
197
|
+
if i > 0
|
|
198
|
+
buffer << sep << value
|
|
199
|
+
else
|
|
200
|
+
buffer << value
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
when nil
|
|
204
|
+
# Do nothing
|
|
205
|
+
else
|
|
206
|
+
raise Phlex::ArgumentError.new("Invalid token type: #{token.class}.")
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
i += 1
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
return if buffer.empty?
|
|
213
|
+
|
|
214
|
+
buffer.gsub('"', """)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# The result is unsafe so should be escaped.
|
|
218
|
+
def generate_styles(styles)
|
|
219
|
+
case styles
|
|
220
|
+
when Array, Set
|
|
221
|
+
styles.filter_map do |s|
|
|
222
|
+
case s
|
|
223
|
+
when String
|
|
224
|
+
if s == "" || s.end_with?(";")
|
|
225
|
+
s
|
|
226
|
+
else
|
|
227
|
+
"#{s};"
|
|
228
|
+
end
|
|
229
|
+
when Phlex::SGML::SafeObject
|
|
230
|
+
value = s.to_s
|
|
231
|
+
value.end_with?(";") ? value : "#{value};"
|
|
232
|
+
when Hash
|
|
233
|
+
next generate_styles(s)
|
|
234
|
+
when nil
|
|
235
|
+
next nil
|
|
236
|
+
else
|
|
237
|
+
raise Phlex::ArgumentError.new("Invalid style: #{s.inspect}.")
|
|
238
|
+
end
|
|
239
|
+
end.join(" ")
|
|
240
|
+
when Hash
|
|
241
|
+
buffer = +""
|
|
242
|
+
i = 0
|
|
243
|
+
styles.each do |k, v|
|
|
244
|
+
prop = case k
|
|
245
|
+
when String
|
|
246
|
+
k
|
|
247
|
+
when Symbol
|
|
248
|
+
k.name.tr("_", "-")
|
|
249
|
+
else
|
|
250
|
+
raise Phlex::ArgumentError.new("Style keys should be Strings or Symbols.")
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
value = case v
|
|
254
|
+
when String
|
|
255
|
+
v
|
|
256
|
+
when Symbol
|
|
257
|
+
v.name.tr("_", "-")
|
|
258
|
+
when Integer, Float, Phlex::SGML::SafeObject
|
|
259
|
+
v.to_s
|
|
260
|
+
when nil
|
|
261
|
+
nil
|
|
262
|
+
else
|
|
263
|
+
raise Phlex::ArgumentError.new("Invalid style value: #{v.inspect}")
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
if value
|
|
267
|
+
if i == 0
|
|
268
|
+
buffer << prop << ": " << value << ";"
|
|
269
|
+
else
|
|
270
|
+
buffer << " " << prop << ": " << value << ";"
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
i += 1
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
buffer
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
data/lib/phlex/sgml/elements.rb
CHANGED
|
@@ -4,16 +4,16 @@ module Phlex::SGML::Elements
|
|
|
4
4
|
COMMA_SEPARATED_TOKENS = {
|
|
5
5
|
img: <<~RUBY,
|
|
6
6
|
if Array === (srcset_attribute = attributes[:srcset])
|
|
7
|
-
attributes[:srcset] =
|
|
7
|
+
attributes[:srcset] = Phlex::SGML::Attributes.generate_nested_tokens(srcset_attribute, ", ", ",", "%2C")
|
|
8
8
|
end
|
|
9
9
|
RUBY
|
|
10
10
|
link: <<~RUBY,
|
|
11
11
|
if Array === (media_attribute = attributes[:media])
|
|
12
|
-
attributes[:media] =
|
|
12
|
+
attributes[:media] = Phlex::SGML::Attributes.generate_nested_tokens(media_attribute, ", ", ",", "%2C")
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
if Array === (sizes_attribute = attributes[:sizes])
|
|
16
|
-
attributes[:sizes] =
|
|
16
|
+
attributes[:sizes] = Phlex::SGML::Attributes.generate_nested_tokens(sizes_attribute, ", ", ",", "%2C")
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
if Array === (imagesrcset_attribute = attributes[:imagesrcset])
|
|
@@ -21,7 +21,7 @@ module Phlex::SGML::Elements
|
|
|
21
21
|
as_attribute = attributes[:as] || attributes["as"]
|
|
22
22
|
|
|
23
23
|
if ("preload" == rel_attribute || :preload == rel_attribute) && ("image" == as_attribute || :image == as_attribute)
|
|
24
|
-
attributes[:imagesrcset] =
|
|
24
|
+
attributes[:imagesrcset] = Phlex::SGML::Attributes.generate_nested_tokens(imagesrcset_attribute, ", ", ",", "%2C")
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
RUBY
|
|
@@ -30,7 +30,7 @@ module Phlex::SGML::Elements
|
|
|
30
30
|
type_attribute = attributes[:type] || attributes["type"]
|
|
31
31
|
|
|
32
32
|
if "file" == type_attribute || :file == type_attribute
|
|
33
|
-
attributes[:accept] =
|
|
33
|
+
attributes[:accept] = Phlex::SGML::Attributes.generate_nested_tokens(accept_attribute, ", ", ",", "%2C")
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
RUBY
|
|
@@ -59,7 +59,7 @@ module Phlex::SGML::Elements
|
|
|
59
59
|
buffer << "<#{tag}"
|
|
60
60
|
begin
|
|
61
61
|
#{COMMA_SEPARATED_TOKENS[method_name]}
|
|
62
|
-
buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||=
|
|
62
|
+
buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
|
|
63
63
|
ensure
|
|
64
64
|
buffer << ">"
|
|
65
65
|
end
|
|
@@ -90,7 +90,7 @@ module Phlex::SGML::Elements
|
|
|
90
90
|
buffer << "<#{tag}"
|
|
91
91
|
begin
|
|
92
92
|
#{COMMA_SEPARATED_TOKENS[method_name]}
|
|
93
|
-
buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||=
|
|
93
|
+
buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
|
|
94
94
|
ensure
|
|
95
95
|
buffer << "></#{tag}>"
|
|
96
96
|
end
|
|
@@ -152,7 +152,7 @@ module Phlex::SGML::Elements
|
|
|
152
152
|
buffer << "<#{tag}"
|
|
153
153
|
begin
|
|
154
154
|
#{COMMA_SEPARATED_TOKENS[method_name]}
|
|
155
|
-
buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||=
|
|
155
|
+
buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
|
|
156
156
|
ensure
|
|
157
157
|
buffer << ">"
|
|
158
158
|
end
|