phlex 2.0.0.rc1 → 2.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|