phlex 2.0.0.rc1 → 2.0.0.rc2
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/fifo.rb +10 -3
- data/lib/phlex/fifo_cache_store.rb +49 -0
- data/lib/phlex/helpers.rb +1 -1
- data/lib/phlex/html/standard_elements.rb +0 -101
- data/lib/phlex/html/void_elements.rb +0 -11
- data/lib/phlex/html.rb +3 -3
- data/lib/phlex/kit.rb +20 -15
- data/lib/phlex/null_cache_store.rb +9 -0
- data/lib/phlex/sgml/elements.rb +19 -53
- data/lib/phlex/sgml/state.rb +118 -0
- data/lib/phlex/sgml.rb +131 -68
- data/lib/phlex/svg/standard_elements.rb +0 -63
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex.rb +16 -5
- metadata +6 -7
- data/lib/phlex/context.rb +0 -60
data/lib/phlex/sgml.rb
CHANGED
@@ -8,6 +8,7 @@ class Phlex::SGML
|
|
8
8
|
autoload :Elements, "phlex/sgml/elements"
|
9
9
|
autoload :SafeObject, "phlex/sgml/safe_object"
|
10
10
|
autoload :SafeValue, "phlex/sgml/safe_value"
|
11
|
+
autoload :State, "phlex/sgml/state"
|
11
12
|
|
12
13
|
include Phlex::Helpers
|
13
14
|
|
@@ -28,25 +29,13 @@ class Phlex::SGML
|
|
28
29
|
super
|
29
30
|
end
|
30
31
|
end
|
31
|
-
|
32
|
-
def __element_method__?(method_name)
|
33
|
-
if instance_methods.include?(method_name)
|
34
|
-
owner = instance_method(method_name).owner
|
35
|
-
|
36
|
-
if Phlex::SGML::Elements === owner && owner.__registered_elements__[method_name]
|
37
|
-
true
|
38
|
-
else
|
39
|
-
false
|
40
|
-
end
|
41
|
-
else
|
42
|
-
false
|
43
|
-
end
|
44
|
-
end
|
45
32
|
end
|
46
33
|
|
47
34
|
def view_template
|
48
35
|
if block_given?
|
49
36
|
yield
|
37
|
+
else
|
38
|
+
plain "Phlex Warning: Your `#{self.class.name}` class doesn't define a `view_template` method. If you are upgrading to Phlex 2.x make sure to rename your `template` method to `view_template`. See: https://beta.phlex.fun/guides/v2-upgrade.html"
|
50
39
|
end
|
51
40
|
end
|
52
41
|
|
@@ -54,25 +43,35 @@ class Phlex::SGML
|
|
54
43
|
proc { |c| c.render(self) }
|
55
44
|
end
|
56
45
|
|
57
|
-
def call(buffer = +"", context: {}, view_context: nil,
|
58
|
-
|
59
|
-
|
60
|
-
|
46
|
+
def call(buffer = +"", context: {}, view_context: nil, fragments: nil, &)
|
47
|
+
state = Phlex::SGML::State.new(
|
48
|
+
user_context: context,
|
49
|
+
view_context:,
|
50
|
+
output_buffer: buffer,
|
51
|
+
fragments: fragments&.to_set,
|
52
|
+
)
|
53
|
+
|
54
|
+
internal_call(parent: nil, state:, &)
|
55
|
+
|
56
|
+
state.output_buffer << state.buffer
|
57
|
+
end
|
61
58
|
|
62
|
-
|
63
|
-
|
59
|
+
def internal_call(parent: nil, state: nil, &block)
|
60
|
+
return "" unless render?
|
64
61
|
|
65
|
-
if
|
66
|
-
|
62
|
+
if @_state
|
63
|
+
raise Phlex::DoubleRenderError.new(
|
64
|
+
"You can't render a #{self.class.name} more than once."
|
65
|
+
)
|
67
66
|
end
|
68
67
|
|
69
|
-
|
68
|
+
@_state = state
|
70
69
|
|
71
|
-
|
70
|
+
block ||= @_content_block
|
72
71
|
|
73
|
-
Thread.current[:__phlex_component__] = [self, Fiber.current.object_id]
|
72
|
+
Thread.current[:__phlex_component__] = [self, Fiber.current.object_id].freeze
|
74
73
|
|
75
|
-
|
74
|
+
state.around_render(self) do
|
76
75
|
before_template(&block)
|
77
76
|
|
78
77
|
around_template do
|
@@ -91,18 +90,12 @@ class Phlex::SGML
|
|
91
90
|
|
92
91
|
after_template(&block)
|
93
92
|
end
|
94
|
-
|
95
|
-
unless parent
|
96
|
-
buffer << phlex_context.buffer
|
97
|
-
end
|
98
93
|
ensure
|
99
|
-
Thread.current[:__phlex_component__] = [parent, Fiber.current.object_id]
|
94
|
+
Thread.current[:__phlex_component__] = [parent, Fiber.current.object_id].freeze
|
100
95
|
end
|
101
96
|
|
102
|
-
protected def __context__ = @_context
|
103
|
-
|
104
97
|
def context
|
105
|
-
@
|
98
|
+
@_state.user_context
|
106
99
|
end
|
107
100
|
|
108
101
|
# Output plain text.
|
@@ -116,10 +109,10 @@ class Phlex::SGML
|
|
116
109
|
|
117
110
|
# Output a single space character. If a block is given, a space will be output before and after the block.
|
118
111
|
def whitespace(&)
|
119
|
-
|
120
|
-
return
|
112
|
+
state = @_state
|
113
|
+
return unless state.should_render?
|
121
114
|
|
122
|
-
buffer =
|
115
|
+
buffer = state.buffer
|
123
116
|
|
124
117
|
buffer << " "
|
125
118
|
|
@@ -135,10 +128,10 @@ class Phlex::SGML
|
|
135
128
|
#
|
136
129
|
# [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Comments)
|
137
130
|
def comment(&)
|
138
|
-
|
139
|
-
return
|
131
|
+
state = @_state
|
132
|
+
return unless state.should_render?
|
140
133
|
|
141
|
-
buffer =
|
134
|
+
buffer = state.buffer
|
142
135
|
|
143
136
|
buffer << "<!-- "
|
144
137
|
__yield_content__(&)
|
@@ -151,10 +144,10 @@ class Phlex::SGML
|
|
151
144
|
def raw(content)
|
152
145
|
case content
|
153
146
|
when Phlex::SGML::SafeObject
|
154
|
-
|
155
|
-
return
|
147
|
+
state = @_state
|
148
|
+
return unless state.should_render?
|
156
149
|
|
157
|
-
|
150
|
+
state.buffer << content.to_s
|
158
151
|
when nil, "" # do nothing
|
159
152
|
else
|
160
153
|
raise Phlex::ArgumentError.new("You passed an unsafe object to `raw`.")
|
@@ -168,12 +161,21 @@ class Phlex::SGML
|
|
168
161
|
return "" unless block
|
169
162
|
|
170
163
|
if args.length > 0
|
171
|
-
@
|
164
|
+
@_state.capturing_into(+"") { __yield_content_with_args__(*args, &block) }
|
172
165
|
else
|
173
|
-
@
|
166
|
+
@_state.capturing_into(+"") { __yield_content__(&block) }
|
174
167
|
end
|
175
168
|
end
|
176
169
|
|
170
|
+
# Define a named fragment that can be selectively rendered.
|
171
|
+
def fragment(name)
|
172
|
+
state = @_state
|
173
|
+
state.begin_fragment(name)
|
174
|
+
yield
|
175
|
+
state.end_fragment(name)
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
177
179
|
# Mark the given string as safe for HTML output.
|
178
180
|
def safe(value)
|
179
181
|
case value
|
@@ -187,20 +189,18 @@ class Phlex::SGML
|
|
187
189
|
alias_method :🦺, :safe
|
188
190
|
|
189
191
|
def flush
|
190
|
-
|
191
|
-
|
192
|
-
buffer = @_context.buffer
|
193
|
-
@_buffer << buffer.dup
|
194
|
-
buffer.clear
|
192
|
+
@_state.flush
|
195
193
|
end
|
196
194
|
|
197
195
|
def render(renderable = nil, &)
|
198
196
|
case renderable
|
199
197
|
when Phlex::SGML
|
200
|
-
|
198
|
+
Thread.current[:__phlex_component__] = [renderable, Fiber.current.object_id].freeze
|
199
|
+
renderable.internal_call(state: @_state, parent: self, &)
|
200
|
+
Thread.current[:__phlex_component__] = [self, Fiber.current.object_id].freeze
|
201
201
|
when Class
|
202
202
|
if renderable < Phlex::SGML
|
203
|
-
renderable.new
|
203
|
+
render(renderable.new, &)
|
204
204
|
end
|
205
205
|
when Enumerable
|
206
206
|
renderable.each { |r| render(r, &) }
|
@@ -221,15 +221,78 @@ class Phlex::SGML
|
|
221
221
|
nil
|
222
222
|
end
|
223
223
|
|
224
|
+
# Cache a block of content.
|
225
|
+
#
|
226
|
+
# ```ruby
|
227
|
+
# @products.each do |product|
|
228
|
+
# cache product do
|
229
|
+
# h1 { product.name }
|
230
|
+
# end
|
231
|
+
# end
|
232
|
+
# ```
|
233
|
+
def cache(*cache_key, **, &content)
|
234
|
+
location = caller_locations(1, 1)[0]
|
235
|
+
|
236
|
+
full_key = [
|
237
|
+
Phlex::DEPLOY_KEY, # invalidates the key when deploying new code in case of changes
|
238
|
+
self.class.name, # prevents collisions between classes
|
239
|
+
location.base_label, # prevents collisions between different methods
|
240
|
+
location.lineno, # prevents collisions between different lines
|
241
|
+
cache_key, # allows for custom cache keys
|
242
|
+
].freeze
|
243
|
+
|
244
|
+
low_level_cache(full_key, **, &content)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Cache a block of content where you control the entire cache key.
|
248
|
+
# If you really know what you’re doing and want to take full control
|
249
|
+
# and responsibility for the cache key, use this method.
|
250
|
+
#
|
251
|
+
# ```ruby
|
252
|
+
# low_level_cache([Commonmarker::VERSION, Digest::MD5.hexdigest(@content)]) do
|
253
|
+
# markdown(@content)
|
254
|
+
# end
|
255
|
+
# ```
|
256
|
+
#
|
257
|
+
# Note: To allow you more control, this method does not take a splat of cache keys.
|
258
|
+
# If you need to pass multiple cache keys, you should pass an array.
|
259
|
+
def low_level_cache(cache_key, **options, &content)
|
260
|
+
state = @_state
|
261
|
+
|
262
|
+
cached_buffer, fragment_map = cache_store.fetch(cache_key, **options) { state.caching(&content) }
|
263
|
+
|
264
|
+
if state.should_render?
|
265
|
+
fragment_map.each do |fragment_name, (offset, length, nested_fragments)|
|
266
|
+
state.record_fragment(fragment_name, offset, length, nested_fragments)
|
267
|
+
end
|
268
|
+
state.buffer << cached_buffer
|
269
|
+
else
|
270
|
+
fragment_map.each do |fragment_name, (offset, length, nested_fragments)|
|
271
|
+
if state.fragments.include?(fragment_name)
|
272
|
+
state.fragments.delete(fragment_name)
|
273
|
+
state.fragments.subtract(nested_fragments)
|
274
|
+
state.buffer << cached_buffer.byteslice(offset, length)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Points to the cache store used by this component.
|
281
|
+
# By default, it points to `Phlex::NullCacheStore`, which does no caching.
|
282
|
+
# Override this method to use a different cache store.
|
283
|
+
def cache_store
|
284
|
+
Phlex::NullCacheStore
|
285
|
+
end
|
286
|
+
|
224
287
|
private
|
225
288
|
|
226
289
|
def vanish(*args)
|
227
290
|
return unless block_given?
|
228
291
|
|
229
292
|
if args.length > 0
|
230
|
-
@
|
293
|
+
@_state.capturing_into(Phlex::Vanish) { yield(*args) }
|
231
294
|
else
|
232
|
-
@
|
295
|
+
@_state.capturing_into(Phlex::Vanish) { yield(self) }
|
233
296
|
end
|
234
297
|
|
235
298
|
nil
|
@@ -262,7 +325,7 @@ class Phlex::SGML
|
|
262
325
|
def __yield_content__
|
263
326
|
return unless block_given?
|
264
327
|
|
265
|
-
buffer = @
|
328
|
+
buffer = @_state.buffer
|
266
329
|
|
267
330
|
original_length = buffer.bytesize
|
268
331
|
content = yield(self)
|
@@ -274,7 +337,7 @@ class Phlex::SGML
|
|
274
337
|
def __yield_content_with_no_args__
|
275
338
|
return unless block_given?
|
276
339
|
|
277
|
-
buffer = @
|
340
|
+
buffer = @_state.buffer
|
278
341
|
|
279
342
|
original_length = buffer.bytesize
|
280
343
|
content = yield
|
@@ -286,7 +349,7 @@ class Phlex::SGML
|
|
286
349
|
def __yield_content_with_args__(*a)
|
287
350
|
return unless block_given?
|
288
351
|
|
289
|
-
buffer = @
|
352
|
+
buffer = @_state.buffer
|
290
353
|
|
291
354
|
original_length = buffer.bytesize
|
292
355
|
content = yield(*a)
|
@@ -296,21 +359,21 @@ class Phlex::SGML
|
|
296
359
|
end
|
297
360
|
|
298
361
|
def __implicit_output__(content)
|
299
|
-
|
300
|
-
return true
|
362
|
+
state = @_state
|
363
|
+
return true unless state.should_render?
|
301
364
|
|
302
365
|
case content
|
303
366
|
when Phlex::SGML::SafeObject
|
304
|
-
|
367
|
+
state.buffer << content.to_s
|
305
368
|
when String
|
306
|
-
|
369
|
+
state.buffer << Phlex::Escape.html_escape(content)
|
307
370
|
when Symbol
|
308
|
-
|
371
|
+
state.buffer << Phlex::Escape.html_escape(content.name)
|
309
372
|
when nil
|
310
373
|
nil
|
311
374
|
else
|
312
375
|
if (formatted_object = format_object(content))
|
313
|
-
|
376
|
+
state.buffer << Phlex::Escape.html_escape(formatted_object)
|
314
377
|
else
|
315
378
|
return false
|
316
379
|
end
|
@@ -321,19 +384,19 @@ class Phlex::SGML
|
|
321
384
|
|
322
385
|
# same as __implicit_output__ but escapes even `safe` objects
|
323
386
|
def __text__(content)
|
324
|
-
|
325
|
-
return true
|
387
|
+
state = @_state
|
388
|
+
return true unless state.should_render?
|
326
389
|
|
327
390
|
case content
|
328
391
|
when String
|
329
|
-
|
392
|
+
state.buffer << Phlex::Escape.html_escape(content)
|
330
393
|
when Symbol
|
331
|
-
|
394
|
+
state.buffer << Phlex::Escape.html_escape(content.name)
|
332
395
|
when nil
|
333
396
|
nil
|
334
397
|
else
|
335
398
|
if (formatted_object = format_object(content))
|
336
|
-
|
399
|
+
state.buffer << Phlex::Escape.html_escape(formatted_object)
|
337
400
|
else
|
338
401
|
return false
|
339
402
|
end
|