papercraft 1.4 → 2.13

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,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Prism::InspectVisitor
4
+ def visit_tag_node(node)
5
+ commands << [inspect_node("TagNode", node), indent]
6
+ # flags = [("newline" if node.newline?), ("static_literal" if node.static_literal?), ].compact
7
+ # commands << ["├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n", indent]
8
+ # commands << ["├── left:\n", indent]
9
+ # commands << [node.left, "#{indent}│ "]
10
+ # commands << ["├── right:\n", indent]
11
+ # commands << [node.right, "#{indent}│ "]
12
+ # commands << ["└── operator_loc: #{inspect_location(node.operator_loc)}\n", indent]
13
+ end
14
+ end
15
+
16
+ module Papercraft
17
+ # Represents a tag call
18
+ class TagNode < Prism::Node
19
+ attr_reader :call_node, :location, :tag, :tag_location, :inner_text, :attributes, :block
20
+
21
+ def initialize(call_node, translator)
22
+ @call_node = call_node
23
+ @location = call_node.location
24
+ @tag = call_node.name
25
+ prepare_block(translator)
26
+
27
+ args = call_node.arguments&.arguments
28
+ return if !args
29
+
30
+ if @tag == :tag
31
+ @tag = args[0]
32
+ args = args[1..]
33
+ end
34
+
35
+ if args.size == 1 && args.first.is_a?(Prism::KeywordHashNode)
36
+ @inner_text = nil
37
+ @attributes = args.first
38
+ else
39
+ @inner_text = args.first
40
+ @attributes = args[1].is_a?(Prism::KeywordHashNode) ? args[1] : nil
41
+ end
42
+ end
43
+
44
+ def accept(visitor)
45
+ visitor.visit_tag_node(self)
46
+ end
47
+
48
+ def prepare_block(translator)
49
+ @block = call_node.block
50
+ if @block.is_a?(Prism::BlockNode)
51
+ @block = translator.visit(@block)
52
+ offset = @location.start_offset
53
+ length = @block.opening_loc.start_offset - offset
54
+ @tag_location = @location.copy(start_offset: offset, length: length)
55
+ else
56
+ @tag_location = @location
57
+ end
58
+ end
59
+ end
60
+
61
+ # Represents a render call
62
+ class RenderNode
63
+ attr_reader :call_node, :location, :block
64
+
65
+ include Prism::DSL
66
+
67
+ def initialize(call_node, translator)
68
+ @call_node = call_node
69
+ @location = call_node.location
70
+ @translator = translator
71
+ @block = call_node.block && translator.visit(call_node.block)
72
+
73
+ lambda = call_node.arguments && call_node.arguments.arguments[0]
74
+ end
75
+
76
+ def ad_hoc_string_location(str)
77
+ src = source(str)
78
+ Prism::DSL.location(source: src, start_offset: 0, length: str.bytesize)
79
+ end
80
+
81
+ def transform(node)
82
+ node && @translator.visit(node)
83
+ end
84
+
85
+ def transform_array(array)
86
+ array ? array.map { @translator.visit(it) } : []
87
+ end
88
+
89
+ def accept(visitor)
90
+ visitor.visit_render_node(self)
91
+ end
92
+ end
93
+
94
+ class ConstTagNode
95
+ attr_reader :call_node, :location
96
+
97
+ def initialize(call_node, translator)
98
+ @call_node = call_node
99
+ @location = call_node.location
100
+ end
101
+
102
+ def accept(visitor)
103
+ visitor.visit_const_tag_node(self)
104
+ end
105
+ end
106
+
107
+ # Represents a text call
108
+ class TextNode
109
+ attr_reader :call_node, :location
110
+
111
+ def initialize(call_node, _translator)
112
+ @call_node = call_node
113
+ @location = call_node.location
114
+ end
115
+
116
+ def accept(visitor)
117
+ visitor.visit_text_node(self)
118
+ end
119
+ end
120
+
121
+ # Represents a raw call
122
+ class RawNode
123
+ attr_reader :call_node, :location
124
+
125
+ def initialize(call_node, _translator)
126
+ @call_node = call_node
127
+ @location = call_node.location
128
+ end
129
+
130
+ def accept(visitor)
131
+ visitor.visit_raw_node(self)
132
+ end
133
+ end
134
+
135
+ # Represents a defer call
136
+ class DeferNode
137
+ attr_reader :call_node, :location, :block
138
+
139
+ def initialize(call_node, translator)
140
+ @call_node = call_node
141
+ @location = call_node.location
142
+ @block = call_node.block && translator.visit(call_node.block)
143
+ end
144
+
145
+ def accept(visitor)
146
+ visitor.visit_defer_node(self)
147
+ end
148
+ end
149
+
150
+ # Represents a builtin call
151
+ class BuiltinNode
152
+ attr_reader :tag, :call_node, :location, :block
153
+
154
+ def initialize(call_node, translator)
155
+ @call_node = call_node
156
+ @tag = call_node.name
157
+ @location = call_node.location
158
+ @block = call_node.block && translator.visit(call_node.block)
159
+ end
160
+
161
+ def accept(visitor)
162
+ visitor.visit_builtin_node(self)
163
+ end
164
+ end
165
+
166
+ class ExtensionTagNode
167
+ attr_reader :tag, :call_node, :location, :block
168
+
169
+ def initialize(call_node, translator)
170
+ @call_node = call_node
171
+ @tag = call_node.name
172
+ @location = call_node.location
173
+ @block = call_node.block && translator.visit(call_node.block)
174
+ end
175
+
176
+ def accept(visitor)
177
+ visitor.visit_extension_tag_node(self)
178
+ end
179
+ end
180
+
181
+ class BlockInvocationNode
182
+ attr_reader :call_node, :location, :block
183
+
184
+ def initialize(call_node, translator)
185
+ @call_node = call_node
186
+ @tag = call_node.name
187
+ @location = call_node.location
188
+ @block = call_node.block && translator.visit(call_node.block)
189
+ end
190
+
191
+ def accept(visitor)
192
+ visitor.visit_block_invocation_node(self)
193
+ end
194
+ end
195
+ end
196
+
197
+ class RenderYieldNode
198
+ attr_reader :call_node, :location
199
+
200
+ def initialize(call_node, translator)
201
+ @call_node = call_node
202
+ @tag = call_node.name
203
+ @location = call_node.location
204
+ end
205
+
206
+ def accept(visitor)
207
+ visitor.visit_render_yield_node(self)
208
+ end
209
+ end
210
+
211
+ class RenderChildrenNode
212
+ attr_reader :call_node, :location
213
+
214
+ def initialize(call_node, translator)
215
+ @call_node = call_node
216
+ @tag = call_node.name
217
+ @location = call_node.location
218
+ end
219
+
220
+ def accept(visitor)
221
+ visitor.visit_render_children_node(self)
222
+ end
223
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+ require_relative './nodes'
5
+
6
+ module Papercraft
7
+ # Translates a normal proc AST into an AST containing custom nodes used for
8
+ # generating HTML. This translation is the first step in compiling templates
9
+ # into procs that generate HTML.
10
+ class TagTranslator < Prism::MutationCompiler
11
+ include Prism::DSL
12
+
13
+ def initialize(root)
14
+ @root = root
15
+ super()
16
+ end
17
+
18
+ def self.transform(ast, root)
19
+ ast.accept(new(root))
20
+ end
21
+
22
+ def visit_call_node(node, dont_translate: false)
23
+ return super(node) if dont_translate
24
+
25
+ match_builtin(node) ||
26
+ match_extension(node) ||
27
+ match_const_tag(node) ||
28
+ match_block_call(node) ||
29
+ match_tag(node) ||
30
+ super(node)
31
+ end
32
+
33
+ def match_builtin(node)
34
+ return if node.receiver
35
+
36
+ case node.name
37
+ when :render_yield
38
+ RenderYieldNode.new(node, self)
39
+ when :render_children
40
+ RenderChildrenNode.new(node, self)
41
+ when :raise
42
+ visit_call_node(node, dont_translate: true)
43
+ when :render
44
+ RenderNode.new(node, self)
45
+ when :raw
46
+ RawNode.new(node, self)
47
+ when :text
48
+ TextNode.new(node, self)
49
+ when :defer
50
+ DeferNode.new(node, self)
51
+ when :html5, :markdown
52
+ BuiltinNode.new(node, self)
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def match_extension(node)
59
+ return if node.receiver
60
+ return if !Papercraft::Extensions[node.name]
61
+
62
+ ExtensionTagNode.new(node, self)
63
+ end
64
+
65
+ def match_const_tag(node)
66
+ return if node.receiver
67
+ return if node.name !~ /^[A-Z]/
68
+
69
+ ConstTagNode.new(node, self)
70
+ end
71
+
72
+ def match_block_call(node)
73
+ return if !node.receiver
74
+ return if node.name != :call
75
+
76
+ receiver = node.receiver
77
+ return if !receiver.is_a?(Prism::LocalVariableReadNode)
78
+ return if @root.parameters&.parameters.block&.name != receiver.name
79
+
80
+ if node.block
81
+ raise Papercraft::Error, 'No support for proc invocation with block'
82
+ end
83
+
84
+ BlockInvocationNode.new(node, self)
85
+ end
86
+
87
+ def match_tag(node)
88
+ return if node.receiver
89
+
90
+ TagNode.new(node, self)
91
+ end
92
+ end
93
+ end