papercraft 1.1 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,701 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'escape_utils'
5
+
6
+ module Papercraft
7
+ def self.__cache_compiled_template__(value)
8
+ @compiled_template_cache ||= {}
9
+
10
+ if !(compiled = @compiled_template_cache[value])
11
+ value = Papercraft.html(&value) if value.is_a?(Proc)
12
+ compiled = value.compile.to_proc
13
+ @compiled_template_cache[value] = compiled
14
+ end
15
+ end
16
+
17
+ def self.__emit__(value, __buffer__, *args)
18
+ case value
19
+ when Proc, Papercraft::Template
20
+ compiled = __cache_compiled_template__(value)
21
+ compiled.(__buffer__, *args)
22
+ else
23
+ __buffer__ << CGI.escapeHTML(value.to_s)
24
+ end
25
+ end
26
+
27
+ # The Compiler class compiles Papercraft templates
28
+ class CompilerOld
29
+ DEFAULT_CODE_BUFFER_CAPACITY = 8192
30
+ DEFAULT_EMIT_BUFFER_CAPACITY = 4096
31
+
32
+ def initialize
33
+ @level = 0
34
+ @code_buffer = String.new(capacity: DEFAULT_CODE_BUFFER_CAPACITY)
35
+ @sub_templates = []
36
+ end
37
+
38
+ def emit_output
39
+ @output_mode = true
40
+ yield
41
+ @output_mode = false
42
+ end
43
+
44
+ def emit_code_line_break
45
+ return if @code_buffer.empty?
46
+
47
+ @code_buffer << "\n" if @code_buffer[-1] != "\n"
48
+ @line_break = nil
49
+ end
50
+
51
+ def emit_buffer
52
+ @emit_buffer ||= String.new(capacity: DEFAULT_EMIT_BUFFER_CAPACITY)
53
+ end
54
+
55
+ def emit_literal(lit)
56
+ if @output_mode
57
+ emit_code_line_break if @line_break
58
+ emit_buffer << lit
59
+ else
60
+ emit_code(lit)
61
+ end
62
+ end
63
+
64
+ def emit_text(str, encoding: :html)
65
+ emit_code_line_break if @line_break
66
+ emit_buffer << encode(str, encoding).inspect[1..-2]
67
+ end
68
+
69
+ def emit_text_fcall(node)
70
+ case node.type
71
+ when :STR, :LIT, :SYM
72
+ value = node.children.first.to_s
73
+ emit_text(value, encoding: :html)
74
+ when :VCALL
75
+ emit_code("__buffer__ << CGI.escapeHTML((#{node.children.first}).to_s)\n")
76
+ when :CONST
77
+ name = node.children.first.to_s
78
+ value = get_const(name)
79
+ emit_text(value, encoding: :html)
80
+ else
81
+ raise NotImplementedError
82
+ end
83
+ end
84
+
85
+ def get_const(name)
86
+ if @block.binding.eval("singleton_class.const_defined?(#{name.inspect})")
87
+ @block.binding.eval("singleton_class.const_get(#{name.inspect})")
88
+ elsif Papercraft.const_defined?(name)
89
+ Papercraft.const_get(name)
90
+ else
91
+ raise NameError, "Constant #{name} not found"
92
+ end
93
+ end
94
+
95
+ def encode(str, encoding)
96
+ case encoding
97
+ when :html
98
+ __html_encode__(str)
99
+ when :uri
100
+ __uri_encode__(str)
101
+ else
102
+ raise "Invalid encoding #{encoding.inspect}"
103
+ end
104
+ end
105
+
106
+ def __html_encode__(str)
107
+ CGI.escapeHTML(str)
108
+ end
109
+
110
+ def __uri_encode__(str)
111
+ EscapeUtils.escape_uri(str)
112
+ end
113
+
114
+ def emit_expression
115
+ if @output_mode
116
+ emit_literal('#{CGI.escapeHTML((')
117
+ yield
118
+ emit_literal(').to_s)}')
119
+ else
120
+ yield
121
+ end
122
+ end
123
+
124
+ def flush_emit_buffer
125
+ return if !@emit_buffer
126
+
127
+ @code_buffer << "#{' ' * @level}__buffer__ << \"#{@emit_buffer}\"\n"
128
+ @emit_buffer = nil
129
+ true
130
+ end
131
+
132
+ def emit_code(code)
133
+ if flush_emit_buffer || @line_break
134
+ emit_code_line_break if @line_break
135
+ @code_buffer << "#{' ' * @level}#{code}"
136
+ else
137
+ if @code_buffer.empty? || (@code_buffer[-1] == "\n")
138
+ @code_buffer << "#{' ' * @level}#{code}"
139
+ else
140
+ @code_buffer << "#{code}"
141
+ end
142
+ end
143
+ end
144
+
145
+ def compile(template, initial_level = 0)
146
+ @block = template.to_proc
147
+ @level = initial_level
148
+ ast = RubyVM::AbstractSyntaxTree.of(@block)
149
+ Compiler.pp_ast(ast) if ENV['DEBUG'] == '1'
150
+ @level += 1
151
+ parse(ast)
152
+ flush_emit_buffer
153
+ @level -= 1
154
+ self
155
+ end
156
+
157
+ attr_reader :code_buffer
158
+
159
+ def to_code
160
+ pad = ' ' * @level
161
+ "#{pad}->(__buffer__#{proc_args}, &__block__) do\n#{prelude}#{@code_buffer}#{pad} __buffer__\n#{pad}end"
162
+ end
163
+
164
+ def proc_args
165
+ return nil if !@args
166
+
167
+ # ", #{@args.map { "#{_1}:" }.join(", ")}"
168
+ ", #{@args.join(", ")}"
169
+ end
170
+
171
+ def prelude
172
+ return nil if @sub_templates.empty?
173
+
174
+ converted = @sub_templates.map { |t| convert_sub_template(t)}
175
+ "#{' ' * @level} __sub_templates__ = [\n#{converted.join(",\n")}\n ]\n"
176
+ end
177
+
178
+ def convert_sub_template(template)
179
+ template.compile(@level + 2).to_code
180
+ end
181
+
182
+ def to_proc
183
+ @block.binding.eval(to_code)
184
+ end
185
+
186
+ def parse(node, line_break = true)
187
+ @line_break = line_break && @last_node && node.first_lineno != @last_node.first_lineno
188
+ @last_node = node
189
+ method_name = :"parse_#{node.type.downcase}"
190
+ if !respond_to?(method_name)
191
+ raise Papercraft::Error, "Template compiler doesn't know how to convert #{node.type.inspect} node"
192
+ end
193
+ send(method_name, node)
194
+ end
195
+
196
+ def parse_scope(node)
197
+ args = node.children[0]
198
+ if args && !args.empty?
199
+ @args = args.compact
200
+ end
201
+ parse(node.children[2])
202
+ end
203
+
204
+ def parse_iter(node)
205
+ call, scope = node.children
206
+ if call.type == :FCALL
207
+ parse_fcall(call, scope)
208
+ else
209
+ parse(call)
210
+ emit_code(" do")
211
+ args = scope.children[0]
212
+ emit_code(" |#{args.join(', ')}|") if args
213
+ emit_code("\n")
214
+ @level += 1
215
+ parse(scope)
216
+ flush_emit_buffer
217
+ @level -= 1
218
+ emit_code("end\n")
219
+ end
220
+ end
221
+
222
+ def parse_ivar(node)
223
+ emit_literal(node.children.first.to_s)
224
+ end
225
+
226
+ def parse_fcall(node, block = nil)
227
+ tag, args = node.children
228
+ args = args.children.compact if args
229
+
230
+ case tag
231
+ when :html5
232
+ return emit_html5(node, block)
233
+ when :emit
234
+ return emit_emit(node.children[1], block)
235
+ when :emit_yield
236
+ return emit_emit_yield
237
+ when :text
238
+ return emit_text_fcall(args.first)
239
+ end
240
+
241
+ text = fcall_inner_text_from_args(args)
242
+ atts = fcall_attributes_from_args(args)
243
+ if block
244
+ emit_tag(tag, atts) { parse(block) }
245
+ elsif text
246
+ emit_tag(tag, atts) do
247
+ case text
248
+ when Papercraft::Template
249
+ @sub_templates << text
250
+ idx = @sub_templates.size - 1
251
+ emit_code("__sub_templates__[#{idx}].(__buffer__)\n")
252
+ when String
253
+ emit_output { emit_text(text) }
254
+ when RubyVM::AbstractSyntaxTree::Node
255
+ emit_output { emit_expression { parse(text) } }
256
+ else
257
+ emit_text(text.to_s)
258
+ end
259
+ end
260
+ else
261
+ emit_tag(tag, atts)
262
+ end
263
+ end
264
+
265
+ def fcall_inner_text_from_args(args)
266
+ return nil if !args
267
+
268
+ first = args.first
269
+ case first.type
270
+ when :STR
271
+ first.children.first
272
+ when :LIT, :SYM
273
+ first.children.first.to_s
274
+ when :HASH
275
+ nil
276
+ when :CONST
277
+ const_name = first.children.first
278
+ value = get_const(const_name)
279
+ if value.is_a?(Papercraft::Template)
280
+ value
281
+ else
282
+ value
283
+ end
284
+ else
285
+ first
286
+ end
287
+ end
288
+
289
+ def fcall_attributes_from_args(args)
290
+ return nil if !args
291
+
292
+ last = args.last
293
+ (last.type == :HASH) ? last : nil
294
+ end
295
+
296
+ def emit_html5(node, block = nil)
297
+ emit_output do
298
+ emit_literal('<!DOCTYPE html>')
299
+ end
300
+ emit_tag(:html, nil) { parse(block) } if block
301
+ end
302
+
303
+ def emit_tag(tag, atts, &block)
304
+ emit_output do
305
+ if atts
306
+ emit_literal("<#{tag}")
307
+ emit_tag_attributes(atts)
308
+ emit_literal(block ? '>' : '/>')
309
+ else
310
+ emit_literal(block ? "<#{tag}>" : "<#{tag}/>")
311
+ end
312
+ end
313
+ if block
314
+ block.call
315
+ emit_output { emit_literal("</#{tag}>") }
316
+ end
317
+ end
318
+
319
+ def emit_emit(args, block)
320
+ value, *rest = args.children.compact
321
+ case value.type
322
+ when :STR, :LIT, :SYM
323
+ value = value.children.first.to_s
324
+ emit_output { emit_literal(value) }
325
+ when :VCALL
326
+ emit_code("__buffer__ << #{value.children.first}\n")
327
+ when :DVAR
328
+ emit_code("Papercraft.__emit__(#{value.children.first}, __buffer__")
329
+ if !rest.empty?
330
+ emit_code(", ")
331
+ parse_list(args, false, 1..-1)
332
+ end
333
+ emit_code(")\n")
334
+ when :CONST
335
+ name = value.children.first.to_s
336
+ value = get_const(name)
337
+ case value
338
+ when Papercraft::Template, Proc
339
+ value = Papercraft.html(&value) if value.is_a?(Proc)
340
+ @sub_templates << value
341
+ idx = @sub_templates.size - 1
342
+ @line_break = false
343
+ emit_code("__sub_templates__[#{idx}].(__buffer__")
344
+ if !rest.empty?
345
+ emit_code(", ")
346
+ parse_list(args, false, 1..-1)
347
+ end
348
+ emit_code(")")
349
+ emit_code_block(block) if block
350
+ emit_code("\n")
351
+ else
352
+ emit_output { emit_literal(value) }
353
+ end
354
+ else
355
+ raise NotImplementedError
356
+ end
357
+
358
+ # value = fcall_inner_text_from_args(args)
359
+ # emit_output do
360
+ # emit_literal(value)
361
+ # end
362
+ end
363
+
364
+ def emit_code_block(block)
365
+ emit_code(" {\n")
366
+ @level += 1
367
+ parse(block.children[2])
368
+ flush_emit_buffer
369
+ @level -= 1
370
+ emit_code("}")
371
+ end
372
+
373
+ def emit_emit_yield
374
+ emit_code("__block__.call\n")
375
+ end
376
+
377
+ def emit_tag_attributes(atts)
378
+ list = atts.children.first.children
379
+ while true
380
+ key = list.shift
381
+ break unless key
382
+
383
+ value = list.shift
384
+ value_type = value.type
385
+ case value_type
386
+ when :FALSE, :NIL
387
+ next
388
+ end
389
+
390
+ emit_literal(' ')
391
+ emit_tag_attribute_key(key)
392
+ next if value_type == :TRUE
393
+
394
+ emit_literal('=\"')
395
+ emit_tag_attribute_value(value, key)
396
+ emit_literal('\"')
397
+ end
398
+ end
399
+
400
+ def emit_tag_attribute_key(key)
401
+ case key.type
402
+ when :STR
403
+ emit_literal(key.children.first)
404
+ when :LIT, :SYM
405
+ emit_literal(key.children.first.to_s)
406
+ when :NIL
407
+ emit_literal('nil')
408
+ else
409
+ emit_expression { parse(key) }
410
+ end
411
+ end
412
+
413
+ def emit_tag_attribute_value(value, key)
414
+ case value.type
415
+ when :STR
416
+ type = key.type
417
+ is_href_attr = (type == :LIT || type == :SYM) && (key.children.first == :href)
418
+ encoding = is_href_attr ? :uri : :html
419
+ emit_text(value.children.first, encoding: encoding)
420
+ when :LIT, :SYM
421
+ emit_text(value.children.first.to_s)
422
+ else
423
+ parse(value)
424
+ end
425
+ end
426
+
427
+ def parse_call(node)
428
+ receiver, method, args = node.children
429
+ if receiver.type == :VCALL && receiver.children == [:context]
430
+ emit_literal('__ctx__')
431
+ else
432
+ parse(receiver)
433
+ end
434
+ if method == :[]
435
+ emit_literal('[')
436
+ args = args.children.compact
437
+ while true
438
+ arg = args.shift
439
+ break unless arg
440
+
441
+ parse(arg)
442
+ emit_literal(', ') if !args.empty?
443
+ end
444
+ emit_literal(']')
445
+ else
446
+ emit_literal('.')
447
+ emit_literal(method.to_s)
448
+ if args
449
+ emit_literal('(')
450
+ args = args.children.compact
451
+ while true
452
+ arg = args.shift
453
+ break unless arg
454
+
455
+ parse(arg)
456
+ emit_literal(', ') if !args.empty?
457
+ end
458
+ emit_literal(')')
459
+ end
460
+ end
461
+ end
462
+
463
+ def parse_str(node)
464
+ str = node.children.first
465
+ emit_literal(str.inspect)
466
+ end
467
+
468
+ def parse_lit(node)
469
+ value = node.children.first
470
+ emit_literal(value.inspect)
471
+ end
472
+
473
+ def parse_sym(node)
474
+ value = node.children.first
475
+ emit_literal(value.inspect)
476
+ end
477
+
478
+ def parse_integer(node)
479
+ value = node.children.first
480
+ emit_literal(value.inspect)
481
+ end
482
+
483
+ def parse_true(node)
484
+ emit_expression { emit_literal('true') }
485
+ end
486
+
487
+ def parse_false(node)
488
+ emit_expression { emit_literal('true') }
489
+ end
490
+
491
+ def parse_list(node, emit_container = true, range = nil)
492
+ emit_literal('[') if emit_container
493
+ if range
494
+ items = node.children[range].compact
495
+ else
496
+ items = node.children.compact
497
+ end
498
+ while true
499
+ item = items.shift
500
+ break unless item
501
+
502
+ parse(item, emit_container)
503
+ emit_literal(', ') if !items.empty?
504
+ end
505
+ emit_literal(']') if emit_container
506
+ end
507
+
508
+ def parse_hash(node, emit_container = false)
509
+ emit_literal('{') if emit_container
510
+ items = node.children.first.children
511
+ idx = 0
512
+ while idx < items.size
513
+ k = items[idx]
514
+ break if !k
515
+
516
+ v = items[idx + 1]
517
+ p k: k, v: v
518
+ emit_literal(', ') if idx > 0
519
+ idx += 2
520
+
521
+ parse(k)
522
+ emit_literal(' => ')
523
+ parse(v)
524
+ end
525
+ emit_literal('}') if emit_container
526
+ end
527
+
528
+ def parse_vcall(node)
529
+ tag = node.children.first
530
+ case tag
531
+ when :emit_yield
532
+ return emit_emit_yield
533
+ end
534
+ emit_tag(tag, nil)
535
+ end
536
+
537
+ def parse_opcall(node)
538
+ left, op, right = node.children
539
+ parse(left)
540
+ emit_literal(" #{op} ")
541
+ right.children.compact.each { |c| parse(c) }
542
+ end
543
+
544
+ def parse_block(node)
545
+ node.children.each { |c| parse(c) }
546
+ end
547
+
548
+ def parse_begin(node)
549
+ node.children.each { |c| parse(c) if c }
550
+ end
551
+
552
+ def parse_if(node)
553
+ cond, then_branch, else_branch = node.children
554
+ if @output_mode
555
+ emit_if_output(cond, then_branch, else_branch)
556
+ else
557
+ emit_if_code(cond, then_branch, else_branch)
558
+ end
559
+ end
560
+
561
+ def parse_unless(node)
562
+ cond, then_branch, else_branch = node.children
563
+ if @output_mode
564
+ emit_unless_output(cond, then_branch, else_branch)
565
+ else
566
+ emit_unless_code(cond, then_branch, else_branch)
567
+ end
568
+ end
569
+
570
+ def emit_if_output(cond, then_branch, else_branch)
571
+ parse(cond)
572
+ emit_literal(" ? ")
573
+ parse(then_branch)
574
+ emit_literal(" : ")
575
+ if else_branch
576
+ parse(else_branch)
577
+ else
578
+ emit_literal(nil)
579
+ end
580
+ end
581
+
582
+ def emit_unless_output(cond, then_branch, else_branch)
583
+ parse(cond)
584
+ emit_literal(" ? ")
585
+ if else_branch
586
+ parse(else_branch)
587
+ else
588
+ emit_literal(nil)
589
+ end
590
+ emit_literal(" : ")
591
+ parse(then_branch)
592
+ end
593
+
594
+ def emit_if_code(cond, then_branch, else_branch)
595
+ emit_code('if ')
596
+ parse(cond)
597
+ emit_code("\n")
598
+ @level += 1
599
+ parse(then_branch)
600
+ flush_emit_buffer
601
+ @level -= 1
602
+ if else_branch
603
+ emit_code("else\n")
604
+ @level += 1
605
+ parse(else_branch)
606
+ flush_emit_buffer
607
+ @level -= 1
608
+ end
609
+ emit_code("end\n")
610
+ end
611
+
612
+ def emit_unless_code(cond, then_branch, else_branch)
613
+ emit_code('unless ')
614
+ parse(cond)
615
+ emit_code("\n")
616
+ @level += 1
617
+ parse(then_branch)
618
+ flush_emit_buffer
619
+ @level -= 1
620
+ if else_branch
621
+ emit_code("else\n")
622
+ @level += 1
623
+ parse(else_branch)
624
+ flush_emit_buffer
625
+ @level -= 1
626
+ end
627
+ emit_code("end\n")
628
+ end
629
+
630
+ def parse_dvar(node)
631
+ emit_literal(node.children.first.to_s)
632
+ end
633
+
634
+ def parse_case(node)
635
+ value = node.children[0]
636
+ when_clause = node.children[1]
637
+ emit_code("case ")
638
+ parse(value)
639
+ emit_code("\n")
640
+ parse_when(when_clause)
641
+ emit_code("end\n")
642
+ end
643
+
644
+ def parse_when(node)
645
+ values = node.children[0]
646
+ then_clause = node.children[1]
647
+ else_clause = node.children[2]
648
+
649
+ emit_code('when ')
650
+ last_value = nil
651
+ emit_when_clause_values(values)
652
+ emit_code("\n")
653
+ @level += 1
654
+ parse(then_clause)
655
+ @level -= 1
656
+
657
+ return if !else_clause
658
+
659
+ if else_clause.type == :WHEN
660
+ parse_when(else_clause)
661
+ else
662
+ emit_code("else\n")
663
+ @level += 1
664
+ @level -= 1
665
+ parse(else_clause)
666
+ end
667
+ end
668
+
669
+ def emit_when_clause_values(values)
670
+ if values.type != :LIST
671
+ raise Papercraft::Error, "Expected LIST node, found #{values.type} node"
672
+ end
673
+
674
+ idx = 0
675
+ list_items = values.children
676
+ while idx < list_items.size
677
+ value = list_items[idx]
678
+ break if !value
679
+
680
+ emit_code(', ') if idx > 0
681
+ parse(value, idx > 0)
682
+ idx += 1
683
+ end
684
+ end
685
+
686
+ def self.pp_ast(node, level = 0)
687
+ case node
688
+ when RubyVM::AbstractSyntaxTree::Node
689
+ puts "#{' ' * level}#{node.type.inspect}"
690
+ node.children.each { |c| pp_ast(c, level + 1) }
691
+ when Array
692
+ puts "#{' ' * level}["
693
+ node.each { |c| pp_ast(c, level + 1) }
694
+ puts "#{' ' * level}]"
695
+ else
696
+ puts "#{' ' * level}#{node.inspect}"
697
+ return
698
+ end
699
+ end
700
+ end
701
+ end
@@ -24,7 +24,6 @@ module Papercraft
24
24
  # @param sym [Symbol] method name
25
25
  # @param *args [Array] arguments
26
26
  # @param *props [Array] named arguments
27
- # @param &block [Proc] block
28
27
  # @return void
29
28
  def method_missing(sym, *args, **props, &block)
30
29
  @renderer.send(sym, *args, **props, &block)
@@ -34,7 +33,6 @@ module Papercraft
34
33
  #
35
34
  # @param *args [Array] arguments
36
35
  # @param *props [Array] named arguments
37
- # @param &block [Proc] block
38
36
  # @return void
39
37
  def p(text = nil, **props, &block)
40
38
  @renderer.p(text, **props, &block)