rubyoshka 0.6.1 → 0.7
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/CHANGELOG.md +26 -28
- data/README.md +37 -0
- data/lib/rubyoshka/compiler.rb +428 -0
- data/lib/rubyoshka/renderer.rb +10 -4
- data/lib/rubyoshka/version.rb +1 -1
- data/lib/rubyoshka.rb +37 -8
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82ae63e4b9ff27ff20f8635c7e619b4a1dd88c1a6b2e1e7c4a7dbdad64381b40
|
4
|
+
data.tar.gz: ff00cf661c6394e2cd00443cbc7204dff6bbff434d9602cb244a274b257f1059
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a2aff7151025e7a83466da15c21878868bc1160bb6bab37396a38ce6ecad08a49c807c8177bc153f22785ff6e6cdfe2eadb82950d4b06cc4d492d5654aa37b0
|
7
|
+
data.tar.gz: f11bfa4d801a259d0825f91267217009be156feba3fc567ea68de4941779fa40c07755a63a2f9812507c2858d7fb54d0d78f5601546003165fe27b912caf6f1e
|
data/CHANGELOG.md
CHANGED
@@ -1,41 +1,39 @@
|
|
1
|
-
0.
|
2
|
-
----------------
|
1
|
+
## 0.7 2021-09-29
|
3
2
|
|
4
|
-
|
3
|
+
- Add `#emit_yield` for rendering layouts
|
4
|
+
- Add experimental template compilation (WIP)
|
5
5
|
|
6
|
-
0.6 2021-03-03
|
7
|
-
--------------
|
6
|
+
## 0.6.1 2021-03-03
|
8
7
|
|
9
|
-
|
10
|
-
* Refactor and add more tests
|
8
|
+
- Remove support for Ruby 2.6
|
11
9
|
|
12
|
-
0.
|
13
|
-
--------------
|
10
|
+
## 0.6 2021-03-03
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
* Remove Modulation dependency
|
12
|
+
- Fix Rubyoshka on Ruby 3.0
|
13
|
+
- Refactor and add more tests
|
18
14
|
|
19
|
-
0.
|
20
|
-
--------------
|
15
|
+
## 0.5 2021-02-27
|
21
16
|
|
22
|
-
|
17
|
+
- Add support for rendering XML
|
18
|
+
- Add Rubyoshka.component method
|
19
|
+
- Remove Modulation dependency
|
23
20
|
|
24
|
-
0.
|
25
|
-
--------------
|
21
|
+
## 0.4 2019-02-05
|
26
22
|
|
27
|
-
|
28
|
-
* Improve performance
|
29
|
-
* Handle attributes with `false` value correctly
|
23
|
+
- Add support for emitting component modules
|
30
24
|
|
31
|
-
0.
|
32
|
-
--------------
|
25
|
+
## 0.3 2019-01-13
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
|
27
|
+
- Implement caching
|
28
|
+
- Improve performance
|
29
|
+
- Handle attributes with `false` value correctly
|
37
30
|
|
38
|
-
0.
|
39
|
-
--------------
|
31
|
+
## 0.2 2019-01-07
|
40
32
|
|
41
|
-
|
33
|
+
- Better documentation
|
34
|
+
- Fix #text
|
35
|
+
- Add local context
|
36
|
+
|
37
|
+
## 0.1 2019-01-06
|
38
|
+
|
39
|
+
- First working version
|
data/README.md
CHANGED
@@ -284,6 +284,43 @@ H {
|
|
284
284
|
}
|
285
285
|
```
|
286
286
|
|
287
|
+
## Layout templates
|
288
|
+
|
289
|
+
Rubyoshka templates can also be used to implement layout templates by using
|
290
|
+
`#emit_yield`:
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
layout = H {
|
294
|
+
html5 {
|
295
|
+
head { ... }
|
296
|
+
body {
|
297
|
+
header { ... }
|
298
|
+
body {
|
299
|
+
emit_yield
|
300
|
+
}
|
301
|
+
footer { ... }
|
302
|
+
}
|
303
|
+
}
|
304
|
+
}
|
305
|
+
```
|
306
|
+
|
307
|
+
To use the layout, supply a block when calling `#render`:
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
layout.render {
|
311
|
+
h1 'foo'
|
312
|
+
p 'bar'
|
313
|
+
}
|
314
|
+
|
315
|
+
# you can also pass a template as the block
|
316
|
+
inner = H {
|
317
|
+
h1 'foo'
|
318
|
+
p 'bar'
|
319
|
+
}
|
320
|
+
|
321
|
+
layout.render(&inner)
|
322
|
+
```
|
323
|
+
|
287
324
|
## Fragment caching
|
288
325
|
|
289
326
|
Any part of a Rubyoshka template can be cached - a fragment, a component, or a
|
@@ -0,0 +1,428 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rubyoshka
|
4
|
+
# The Compiler class compiles Rubyoshka templates
|
5
|
+
class Compiler
|
6
|
+
DEFAULT_CODE_BUFFER_CAPACITY = 8192
|
7
|
+
DEFAULT_EMIT_BUFFER_CAPACITY = 4096
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@level = 1
|
11
|
+
@code_buffer = String.new(capacity: DEFAULT_CODE_BUFFER_CAPACITY)
|
12
|
+
end
|
13
|
+
|
14
|
+
def emit_output
|
15
|
+
@output_mode = true
|
16
|
+
yield
|
17
|
+
@output_mode = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def emit_code_line_break
|
21
|
+
return if @code_buffer.empty?
|
22
|
+
|
23
|
+
@code_buffer << "\n" if @code_buffer[-1] != "\n"
|
24
|
+
@line_break = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def emit_literal(lit)
|
28
|
+
if @output_mode
|
29
|
+
emit_code_line_break if @line_break
|
30
|
+
@emit_buffer ||= String.new(capacity: DEFAULT_EMIT_BUFFER_CAPACITY)
|
31
|
+
@emit_buffer << lit
|
32
|
+
else
|
33
|
+
emit_code(lit)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def emit_text(str, encoding: :html)
|
38
|
+
emit_code_line_break if @line_break
|
39
|
+
@emit_buffer ||= String.new(capacity: DEFAULT_EMIT_BUFFER_CAPACITY)
|
40
|
+
@emit_buffer << encode(str, encoding).inspect[1..-2]
|
41
|
+
end
|
42
|
+
|
43
|
+
def encode(str, encoding)
|
44
|
+
case encoding
|
45
|
+
when :html
|
46
|
+
__html_encode__(str)
|
47
|
+
when :uri
|
48
|
+
__uri_encode__(str)
|
49
|
+
else
|
50
|
+
raise "Invalid encoding #{encoding.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def emit_expression
|
55
|
+
if @output_mode
|
56
|
+
emit_literal('#{__html_encode__(')
|
57
|
+
yield
|
58
|
+
emit_literal(')}')
|
59
|
+
else
|
60
|
+
yield
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def flush_emit_buffer
|
65
|
+
return if !@emit_buffer
|
66
|
+
|
67
|
+
@code_buffer << "#{' ' * @level}__buffer__ << \"#{@emit_buffer}\"\n"
|
68
|
+
@emit_buffer = nil
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def emit_code(code)
|
73
|
+
if flush_emit_buffer || @line_break
|
74
|
+
emit_code_line_break if @line_break
|
75
|
+
@code_buffer << "#{' ' * @level}#{code}"
|
76
|
+
else
|
77
|
+
if @code_buffer.empty? || (@code_buffer[-1] == "\n")
|
78
|
+
@code_buffer << "#{' ' * @level}#{code}"
|
79
|
+
else
|
80
|
+
@code_buffer << "#{code}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def compile(template)
|
86
|
+
@block = template.to_proc
|
87
|
+
ast = RubyVM::AbstractSyntaxTree.of(@block)
|
88
|
+
# Compiler.pp_ast(ast)
|
89
|
+
parse(ast)
|
90
|
+
flush_emit_buffer
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
attr_reader :code_buffer
|
95
|
+
|
96
|
+
def to_code
|
97
|
+
"->(__buffer__, __context__) do\n#{@code_buffer}end"
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_proc
|
101
|
+
@block.binding.eval(to_code)
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse(node)
|
105
|
+
@line_break = @last_node && node.first_lineno != @last_node.first_lineno
|
106
|
+
@last_node = node
|
107
|
+
# puts "- parse(#{node.type}) (break: #{@line_break.inspect})"
|
108
|
+
send(:"parse_#{node.type.downcase}", node)
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse_scope(node)
|
112
|
+
parse(node.children[2])
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_iter(node)
|
116
|
+
call, scope = node.children
|
117
|
+
if call.type == :FCALL
|
118
|
+
parse_fcall(call, scope)
|
119
|
+
else
|
120
|
+
parse(call)
|
121
|
+
emit_code(" do")
|
122
|
+
args = scope.children[0]
|
123
|
+
emit_code(" |#{args.join(', ')}|") if args
|
124
|
+
emit_code("\n")
|
125
|
+
@level += 1
|
126
|
+
parse(scope)
|
127
|
+
flush_emit_buffer
|
128
|
+
@level -= 1
|
129
|
+
emit_code("end\n")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def parse_ivar(node)
|
134
|
+
ivar = node.children.first.match(/^@(.+)*/)[1]
|
135
|
+
emit_literal("__context__[:#{ivar}]")
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_fcall(node, block = nil)
|
139
|
+
tag, args = node.children
|
140
|
+
args = args.children.compact if args
|
141
|
+
text = fcall_inner_text_from_args(args)
|
142
|
+
atts = fcall_attributes_from_args(args)
|
143
|
+
if block
|
144
|
+
emit_tag(tag, atts) { parse(block) }
|
145
|
+
elsif text
|
146
|
+
emit_tag(tag, atts) do
|
147
|
+
emit_output do
|
148
|
+
if text.is_a?(String)
|
149
|
+
emit_text(text)
|
150
|
+
else
|
151
|
+
emit_expression { parse(text) }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
else
|
156
|
+
emit_tag(tag, atts)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def fcall_inner_text_from_args(args)
|
161
|
+
return nil if !args
|
162
|
+
|
163
|
+
first = args.first
|
164
|
+
case first.type
|
165
|
+
when :STR
|
166
|
+
first.children.first
|
167
|
+
when :LIT
|
168
|
+
first.children.first.to_s
|
169
|
+
when :HASH
|
170
|
+
nil
|
171
|
+
else
|
172
|
+
first
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def fcall_attributes_from_args(args)
|
177
|
+
return nil if !args
|
178
|
+
|
179
|
+
last = args.last
|
180
|
+
(last.type == :HASH) ? last : nil
|
181
|
+
end
|
182
|
+
|
183
|
+
def emit_tag(tag, atts, &block)
|
184
|
+
emit_output do
|
185
|
+
if atts
|
186
|
+
emit_literal("<#{tag}")
|
187
|
+
emit_tag_attributes(atts)
|
188
|
+
emit_literal(block ? '>' : '/>')
|
189
|
+
else
|
190
|
+
emit_literal(block ? "<#{tag}>" : "<#{tag}/>")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
if block
|
194
|
+
block.call
|
195
|
+
emit_output { emit_literal("</#{tag}>") }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def emit_tag_attributes(atts)
|
200
|
+
list = atts.children.first.children
|
201
|
+
while true
|
202
|
+
key = list.shift
|
203
|
+
break unless key
|
204
|
+
|
205
|
+
value = list.shift
|
206
|
+
value_type = value.type
|
207
|
+
case value_type
|
208
|
+
when :FALSE, :NIL
|
209
|
+
next
|
210
|
+
end
|
211
|
+
|
212
|
+
emit_literal(' ')
|
213
|
+
emit_tag_attribute_key(key)
|
214
|
+
next if value_type == :TRUE
|
215
|
+
|
216
|
+
emit_literal('=\"')
|
217
|
+
emit_tag_attribute_value(value, key)
|
218
|
+
emit_literal('\"')
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def emit_tag_attribute_key(key)
|
223
|
+
case key.type
|
224
|
+
when :STR
|
225
|
+
emit_literal(key.children.first)
|
226
|
+
when :LIT
|
227
|
+
emit_literal(key.children.first.to_s)
|
228
|
+
when :NIL
|
229
|
+
emit_literal('nil')
|
230
|
+
else
|
231
|
+
emit_expression { parse(key) }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def emit_tag_attribute_value(value, key)
|
236
|
+
case value.type
|
237
|
+
when :STR
|
238
|
+
encoding = (key.type == :LIT) && (key.children.first == :href) ? :uri : :html
|
239
|
+
emit_text(value.children.first, encoding: encoding)
|
240
|
+
when :LIT
|
241
|
+
emit_text(value.children.first.to_s)
|
242
|
+
else
|
243
|
+
parse(value)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def parse_call(node)
|
248
|
+
receiver, method, args = node.children
|
249
|
+
if receiver.type == :VCALL && receiver.children == [:context]
|
250
|
+
emit_literal('__context__')
|
251
|
+
else
|
252
|
+
parse(receiver)
|
253
|
+
end
|
254
|
+
if method == :[]
|
255
|
+
emit_literal('[')
|
256
|
+
args = args.children.compact
|
257
|
+
while true
|
258
|
+
arg = args.shift
|
259
|
+
break unless arg
|
260
|
+
|
261
|
+
parse(arg)
|
262
|
+
emit_literal(', ') if !args.empty?
|
263
|
+
end
|
264
|
+
emit_literal(']')
|
265
|
+
else
|
266
|
+
emit_literal('.')
|
267
|
+
emit_literal(method.to_s)
|
268
|
+
if args
|
269
|
+
emit_literal('(')
|
270
|
+
args = args.children.compact
|
271
|
+
while true
|
272
|
+
arg = args.shift
|
273
|
+
break unless arg
|
274
|
+
|
275
|
+
parse(arg)
|
276
|
+
emit_literal(', ') if !args.empty?
|
277
|
+
end
|
278
|
+
emit_literal(')')
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def parse_str(node)
|
284
|
+
str = node.children.first
|
285
|
+
emit_literal(str.inspect)
|
286
|
+
end
|
287
|
+
|
288
|
+
def parse_lit(node)
|
289
|
+
value = node.children.first
|
290
|
+
emit_literal(value.inspect)
|
291
|
+
end
|
292
|
+
|
293
|
+
def parse_true(node)
|
294
|
+
emit_expression { emit_literal('true') }
|
295
|
+
end
|
296
|
+
|
297
|
+
def parse_false(node)
|
298
|
+
emit_expression { emit_literal('true') }
|
299
|
+
end
|
300
|
+
|
301
|
+
def parse_list(node)
|
302
|
+
emit_literal('[')
|
303
|
+
items = node.children.compact
|
304
|
+
while true
|
305
|
+
item = items.shift
|
306
|
+
break unless item
|
307
|
+
|
308
|
+
parse(item)
|
309
|
+
emit_literal(', ') if !items.empty?
|
310
|
+
end
|
311
|
+
emit_literal(']')
|
312
|
+
end
|
313
|
+
|
314
|
+
def parse_vcall(node)
|
315
|
+
tag = node.children.first
|
316
|
+
emit_tag(tag, nil)
|
317
|
+
end
|
318
|
+
|
319
|
+
def parse_opcall(node)
|
320
|
+
left, op, right = node.children
|
321
|
+
parse(left)
|
322
|
+
emit_literal(" #{op} ")
|
323
|
+
right.children.compact.each { |c| parse(c) }
|
324
|
+
end
|
325
|
+
|
326
|
+
def parse_block(node)
|
327
|
+
node.children.each { |c| parse(c) }
|
328
|
+
end
|
329
|
+
|
330
|
+
def parse_if(node)
|
331
|
+
cond, then_branch, else_branch = node.children
|
332
|
+
if @output_mode
|
333
|
+
emit_if_output(cond, then_branch, else_branch)
|
334
|
+
else
|
335
|
+
emit_if_code(cond, then_branch, else_branch)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def parse_unless(node)
|
340
|
+
cond, then_branch, else_branch = node.children
|
341
|
+
if @output_mode
|
342
|
+
emit_unless_output(cond, then_branch, else_branch)
|
343
|
+
else
|
344
|
+
emit_unless_code(cond, then_branch, else_branch)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def emit_if_output(cond, then_branch, else_branch)
|
349
|
+
parse(cond)
|
350
|
+
emit_literal(" ? ")
|
351
|
+
parse(then_branch)
|
352
|
+
emit_literal(" : ")
|
353
|
+
if else_branch
|
354
|
+
parse(else_branch)
|
355
|
+
else
|
356
|
+
emit_literal(nil)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def emit_unless_output(cond, then_branch, else_branch)
|
361
|
+
parse(cond)
|
362
|
+
emit_literal(" ? ")
|
363
|
+
if else_branch
|
364
|
+
parse(else_branch)
|
365
|
+
else
|
366
|
+
emit_literal(nil)
|
367
|
+
end
|
368
|
+
emit_literal(" : ")
|
369
|
+
parse(then_branch)
|
370
|
+
end
|
371
|
+
|
372
|
+
def emit_if_code(cond, then_branch, else_branch)
|
373
|
+
emit_code('if ')
|
374
|
+
parse(cond)
|
375
|
+
emit_code("\n")
|
376
|
+
@level += 1
|
377
|
+
parse(then_branch)
|
378
|
+
flush_emit_buffer
|
379
|
+
@level -= 1
|
380
|
+
if else_branch
|
381
|
+
emit_code("else\n")
|
382
|
+
@level += 1
|
383
|
+
parse(else_branch)
|
384
|
+
flush_emit_buffer
|
385
|
+
@level -= 1
|
386
|
+
end
|
387
|
+
emit_code("end\n")
|
388
|
+
end
|
389
|
+
|
390
|
+
def emit_unless_code(cond, then_branch, else_branch)
|
391
|
+
emit_code('unless ')
|
392
|
+
parse(cond)
|
393
|
+
emit_code("\n")
|
394
|
+
@level += 1
|
395
|
+
parse(then_branch)
|
396
|
+
flush_emit_buffer
|
397
|
+
@level -= 1
|
398
|
+
if else_branch
|
399
|
+
emit_code("else\n")
|
400
|
+
@level += 1
|
401
|
+
parse(else_branch)
|
402
|
+
flush_emit_buffer
|
403
|
+
@level -= 1
|
404
|
+
end
|
405
|
+
emit_code("end\n")
|
406
|
+
end
|
407
|
+
|
408
|
+
def parse_dvar(node)
|
409
|
+
|
410
|
+
emit_literal(node.children.first.to_s)
|
411
|
+
end
|
412
|
+
|
413
|
+
def self.pp_ast(node, level = 0)
|
414
|
+
case node
|
415
|
+
when RubyVM::AbstractSyntaxTree::Node
|
416
|
+
puts "#{' ' * level}#{node.type.inspect}"
|
417
|
+
node.children.each { |c| pp_ast(c, level + 1) }
|
418
|
+
when Array
|
419
|
+
puts "#{' ' * level}["
|
420
|
+
node.each { |c| pp_ast(c, level + 1) }
|
421
|
+
puts "#{' ' * level}]"
|
422
|
+
else
|
423
|
+
puts "#{' ' * level}#{node.inspect}"
|
424
|
+
return
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
data/lib/rubyoshka/renderer.rb
CHANGED
@@ -11,10 +11,10 @@ class Rubyoshka
|
|
11
11
|
# @param context [Hash] rendering context
|
12
12
|
# @param block [Proc] template block
|
13
13
|
# @return [void]
|
14
|
-
def initialize(context,
|
14
|
+
def initialize(context, template)
|
15
15
|
@context = context
|
16
16
|
@buffer = +''
|
17
|
-
instance_eval(&
|
17
|
+
instance_eval(&template)
|
18
18
|
end
|
19
19
|
|
20
20
|
# Returns the result of the rendering
|
@@ -74,7 +74,6 @@ class Rubyoshka
|
|
74
74
|
case o
|
75
75
|
when ::Proc
|
76
76
|
self.class.define_method(sym) { |*a, **c, &b| emit(o.(*a, **c, &b)) }
|
77
|
-
STDOUT.puts({o: o, args: args, opts: opts, block: block}.inspect)
|
78
77
|
emit(o.(*args, **opts, &block))
|
79
78
|
when Rubyoshka
|
80
79
|
self.class.define_method(sym) do |**ctx|
|
@@ -104,7 +103,7 @@ class Rubyoshka
|
|
104
103
|
when ::Proc
|
105
104
|
instance_eval(&o)
|
106
105
|
when Rubyoshka
|
107
|
-
instance_eval(&o.
|
106
|
+
instance_eval(&o.template)
|
108
107
|
when Module
|
109
108
|
# If module is given, the component is expected to be a const inside the module
|
110
109
|
emit(o::Component)
|
@@ -114,6 +113,13 @@ class Rubyoshka
|
|
114
113
|
end
|
115
114
|
end
|
116
115
|
alias_method :e, :emit
|
116
|
+
|
117
|
+
def emit_yield
|
118
|
+
block = @context[:__block__]
|
119
|
+
raise LocalJumpError, "no block given (emit_yield)" unless block
|
120
|
+
|
121
|
+
instance_eval(&block)
|
122
|
+
end
|
117
123
|
|
118
124
|
S_LT = '<'
|
119
125
|
S_GT = '>'
|
data/lib/rubyoshka/version.rb
CHANGED
data/lib/rubyoshka.rb
CHANGED
@@ -3,10 +3,21 @@
|
|
3
3
|
require 'escape_utils'
|
4
4
|
|
5
5
|
require_relative 'rubyoshka/renderer'
|
6
|
+
require_relative 'rubyoshka/compiler'
|
6
7
|
|
7
8
|
# A Rubyoshka is a template representing a piece of HTML
|
8
9
|
class Rubyoshka
|
9
|
-
|
10
|
+
module Encoding
|
11
|
+
def __html_encode__(text)
|
12
|
+
EscapeUtils.escape_html(text.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def __uri_encode__(text)
|
16
|
+
EscapeUtils.escape_uri(text.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :template
|
10
21
|
|
11
22
|
# Initializes a Rubyoshka with the given block
|
12
23
|
# @param ctx [Hash] local context
|
@@ -14,7 +25,7 @@ class Rubyoshka
|
|
14
25
|
# @param [void]
|
15
26
|
def initialize(mode: :html, **ctx, &block)
|
16
27
|
@mode = mode
|
17
|
-
@
|
28
|
+
@template = ctx.empty? ? block : proc { with(**ctx, &block) }
|
18
29
|
end
|
19
30
|
|
20
31
|
H_EMPTY = {}.freeze
|
@@ -22,8 +33,12 @@ class Rubyoshka
|
|
22
33
|
# Renders the associated block and returns the string result
|
23
34
|
# @param context [Hash] context
|
24
35
|
# @return [String]
|
25
|
-
def render(context = H_EMPTY)
|
26
|
-
|
36
|
+
def render(context = H_EMPTY, &block)
|
37
|
+
if block
|
38
|
+
context = context.dup if context.frozen?
|
39
|
+
context[:__block__] = block
|
40
|
+
end
|
41
|
+
renderer_class.new(context, @template).to_s
|
27
42
|
end
|
28
43
|
|
29
44
|
def renderer_class
|
@@ -37,6 +52,14 @@ class Rubyoshka
|
|
37
52
|
end
|
38
53
|
end
|
39
54
|
|
55
|
+
def compile
|
56
|
+
Rubyoshka::Compiler.new.compile(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_proc
|
60
|
+
@template
|
61
|
+
end
|
62
|
+
|
40
63
|
@@cache = {}
|
41
64
|
|
42
65
|
def self.cache
|
@@ -53,12 +76,18 @@ class Rubyoshka
|
|
53
76
|
end
|
54
77
|
::H = Rubyoshka
|
55
78
|
|
79
|
+
# Kernel extensions
|
56
80
|
module ::Kernel
|
57
81
|
# Convenience method for creating a new Rubyoshka
|
58
82
|
# @param ctx [Hash] local context
|
59
|
-
# @param
|
83
|
+
# @param template [Proc] template block
|
60
84
|
# @return [Rubyoshka] Rubyoshka template
|
61
|
-
def H(**ctx, &
|
62
|
-
Rubyoshka.new(**ctx, &
|
85
|
+
def H(**ctx, &template)
|
86
|
+
Rubyoshka.new(**ctx, &template)
|
63
87
|
end
|
64
|
-
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Object extensions
|
91
|
+
class Object
|
92
|
+
include Rubyoshka::Encoding
|
93
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyoshka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.7'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: escape_utils
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- CHANGELOG.md
|
91
91
|
- README.md
|
92
92
|
- lib/rubyoshka.rb
|
93
|
+
- lib/rubyoshka/compiler.rb
|
93
94
|
- lib/rubyoshka/html.rb
|
94
95
|
- lib/rubyoshka/renderer.rb
|
95
96
|
- lib/rubyoshka/version.rb
|