papercraft 1.2 → 1.3

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.
@@ -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