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.
@@ -1,408 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './extension_proxy'
4
- require 'escape_utils'
5
-
6
- module Papercraft
7
- # Markup (HTML/XML) extensions
8
- module Tags
9
- S_LT = '<'
10
- S_GT = '>'
11
- S_LT_SLASH = '</'
12
- S_SPACE_LT_SLASH = ' </'
13
- S_SLASH_GT = '/>'
14
- S_GT_LT_SLASH = '></'
15
- S_SPACE = ' '
16
- S_EQUAL_QUOTE = '="'
17
- S_QUOTE = '"'
18
-
19
- # The tag method template below is optimized for performance. Do not touch!
20
-
21
- S_TAG_METHOD_LINE = __LINE__ + 2
22
- S_TAG_METHOD = <<~EOF
23
- S_TAG_%<TAG>s_PRE = %<tag_pre>s
24
- S_TAG_%<TAG>s_CLOSE = %<tag_close>s
25
-
26
- def %<tag>s(text = nil, _for: nil, **attributes, &block)
27
- return if @render_fragment && @fragment != @render_fragment
28
-
29
- return _for.each { |*a| %<tag>s(text, **attributes) { block.(*a)} } if _for
30
-
31
- if text.is_a?(Hash) && attributes.empty?
32
- attributes = text
33
- text = nil
34
- end
35
-
36
- @buffer << S_TAG_%<TAG>s_PRE
37
- emit_attributes(attributes) unless attributes.empty?
38
-
39
- if block
40
- @buffer << S_GT
41
- instance_eval(&block)
42
- @buffer << S_TAG_%<TAG>s_CLOSE
43
- elsif Proc === text
44
- @buffer << S_GT
45
- emit(text)
46
- @buffer << S_TAG_%<TAG>s_CLOSE
47
- elsif text
48
- @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
49
- else
50
- @buffer << S_GT << S_TAG_%<TAG>s_CLOSE
51
- end
52
- end
53
- EOF
54
-
55
- S_VOID_TAG_METHOD_LINE = __LINE__ + 2
56
- S_VOID_TAG_METHOD = <<~EOF
57
- S_TAG_%<TAG>s_PRE = %<tag_pre>s
58
- S_TAG_%<TAG>s_CLOSE = %<tag_close>s
59
-
60
- def %<tag>s(text = nil, _for: nil, **attributes, &block)
61
- return if @render_fragment && @fragment != @render_fragment
62
-
63
- return _for.each { |*a| %<tag>s(text, **attributes) { block.(*a)} } if _for
64
-
65
- if text.is_a?(Hash) && attributes.empty?
66
- attributes = text
67
- text = nil
68
- end
69
-
70
- @buffer << S_TAG_%<TAG>s_PRE
71
- emit_attributes(attributes) unless attributes.empty?
72
-
73
- if block
74
- @buffer << S_GT
75
- instance_eval(&block)
76
- @buffer << S_TAG_%<TAG>s_CLOSE
77
- elsif Proc === text
78
- @buffer << S_GT
79
- emit(text)
80
- @buffer << S_TAG_%<TAG>s_CLOSE
81
- elsif text
82
- @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
83
- else
84
- @buffer << S_SLASH_GT
85
- end
86
- end
87
- EOF
88
-
89
- INITIAL_BUFFER_CAPACITY = 8192
90
-
91
- # Initializes a tag renderer.
92
- def initialize(render_fragment = nil, &template)
93
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
94
- super(render_fragment)
95
- end
96
-
97
- # Returns the rendered template.
98
- #
99
- # @return [String]
100
- def to_s
101
- if @parts
102
- last = @buffer
103
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
104
- parts = @parts
105
- @parts = nil
106
- parts.each do |p|
107
- if Proc === p
108
- render_deferred_proc(&p)
109
- else
110
- @buffer << p
111
- end
112
- end
113
- @buffer << last unless last.empty?
114
- end
115
- @buffer
116
- end
117
-
118
- # Defers the given block to be evaluated later. Deferred evaluation allows
119
- # Papercraft templates to inject state into sibling components, regardless
120
- # of the component's order in the container component. For example, a nested
121
- # component may set an instance variable used by another component. This is
122
- # an elegant solution to the problem of setting the XML page's title, or
123
- # adding elements to the `<head>` section. Here's how a title can be
124
- # controlled from a nested component:
125
- #
126
- # layout = Papercraft.html {
127
- # html {
128
- # head {
129
- # defer { title @title }
130
- # }
131
- # body {
132
- # emit_yield
133
- # }
134
- # }
135
- # }
136
- #
137
- # html.render {
138
- # @title = 'My super page'
139
- # h1 'content'
140
- # }
141
- #
142
- # @return [void]
143
- def defer(&block)
144
- if !@parts
145
- @parts = [@buffer, block]
146
- else
147
- @parts << @buffer unless @buffer.empty?
148
- @parts << block
149
- end
150
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
151
- end
152
-
153
-
154
- # Emits an XML tag with the given content, properties and optional block.
155
- # This method is an alternative to emitting XML tags using dynamically
156
- # created methods. This is particularly useful when using extensions that
157
- # have method names that clash with XML tags, such as `button` or `a`, or
158
- # when you need to override the behaviour of a particular XML tag.
159
- #
160
- # The following two method calls have the same effect:
161
- #
162
- # button 'text', id: 'button1'
163
- # tag :button, 'text', id: 'button1'
164
- #
165
- # @param sym [Symbol, String] XML tag
166
- # @param text [String, nil] tag content
167
- # @param **attributes [Hash] tag attributes
168
- # @return [void]
169
- def tag(sym, text = nil, _for: nil, **attributes, &block)
170
- return if @render_fragment && @fragment != @render_fragment
171
-
172
- return _for.each { |*a| tag(sym, text, **attributes) { block.(*a)} } if _for
173
-
174
- if text.is_a?(Hash) && attributes.empty?
175
- attributes = text
176
- text = nil
177
- end
178
-
179
- tag = tag_repr(sym)
180
-
181
- @buffer << S_LT << tag
182
- emit_attributes(attributes) unless attributes.empty?
183
-
184
- if block
185
- @buffer << S_GT
186
- instance_eval(&block)
187
- @buffer << S_LT_SLASH << tag << S_GT
188
- elsif Proc === text
189
- @buffer << S_GT
190
- emit(text)
191
- @buffer << S_LT_SLASH << tag << S_GT
192
- elsif text
193
- @buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
194
- elsif is_void_element_tag?(sym)
195
- @buffer << S_SLASH_GT
196
- else
197
- @buffer << S_GT_LT_SLASH << tag << S_GT
198
- end
199
- end
200
-
201
- # Catches undefined tag method call and handles it by defining the method.
202
- #
203
- # @param sym [Symbol] tag or component identifier
204
- # @param args [Array] method arguments
205
- # @param opts [Hash] named method arguments
206
- # @return [void]
207
- def method_missing(sym, *args, **opts, &block)
208
- tag = sym.to_s
209
- if tag =~ /^[A-Z]/ && (Object.const_defined?(tag))
210
- define_const_tag_method(tag)
211
- else
212
- define_tag_method(tag)
213
- end
214
-
215
- send(sym, *args, **opts, &block)
216
- end
217
-
218
- # Emits text into the rendering buffer, escaping any special characters to
219
- # the respective XML entities.
220
- #
221
- # @param data [String, nil] text
222
- # @return [void]
223
- def text(data = nil)
224
- return if !data
225
- return if @render_fragment && @fragment != @render_fragment
226
-
227
- @buffer << escape_text(data)
228
- end
229
-
230
- # Defines a custom tag. This is handy for defining helper or extension
231
- # methods inside the template body.
232
- #
233
- # Papercraft.html {
234
- # def_tag(:section) { |title, &inner|
235
- # div {
236
- # h1 title
237
- # emit inner
238
- # }
239
- # }
240
- #
241
- # section('Foo') {
242
- # p 'Bar'
243
- # }
244
- # }
245
- #
246
- # @param tag [Symbol, String] tag/method name
247
- # @param block [Proc] method body
248
- # @return [void]
249
- def def_tag(tag, &block)
250
- self.class.define_method(tag, &block)
251
- end
252
-
253
- alias_method :orig_extend, :extend
254
-
255
- # Extends the template with the provided module or map of modules. When
256
- # given a module, the template body will be extended with the module,
257
- # and will have access to all the module's methods:
258
- #
259
- # module CustomTags
260
- # def label(text)
261
- # span text, class: 'label'
262
- # end
263
- # end
264
- #
265
- # Papercraft.html {
266
- # extend CustomTags
267
- # label('foo')
268
- # }
269
- #
270
- # When given a hash, each module in the hash is namespaced, and can be
271
- # accessed using its key:
272
- #
273
- # Papercraft.html {
274
- # extend custom: CustomTags
275
- # custom.label('foo')
276
- # }
277
- #
278
- # @param ext [Module, Hash] extension module or hash mapping symbols to modules
279
- # @return [Object] self
280
- def extend(ext)
281
- if ext.is_a?(Module)
282
- orig_extend(ext)
283
- else
284
- ext.each do |sym, mod|
285
- define_extension_method(sym, mod)
286
- end
287
- end
288
- end
289
-
290
- private
291
-
292
- # Defines a method that emits the given tag based on a constant. The
293
- # constant must be defined on the main (Object) binding.
294
- #
295
- # @param tag [Symbol, String] tag/method name
296
- # @return [void]
297
- def define_const_tag_method(tag)
298
- const = Object.const_get(tag)
299
- self.class.define_method(tag) { |*a, **b, &blk|
300
- emit const, *a, **b, &blk
301
- }
302
- end
303
-
304
- # Defines a normal tag method.
305
- #
306
- # @param tag [Symbol, String] tag/method name
307
- # @return [void]
308
- def define_tag_method(tag)
309
- repr = tag_repr(tag)
310
- if is_void_element_tag?(tag)
311
- tmpl = S_VOID_TAG_METHOD
312
- line = S_VOID_TAG_METHOD_LINE
313
- else
314
- tmpl = S_TAG_METHOD
315
- line = S_TAG_METHOD_LINE
316
- end
317
- code = tmpl % {
318
- tag: tag,
319
- TAG: tag.upcase,
320
- tag_pre: "<#{repr}".inspect,
321
- tag_close: "</#{repr}>".inspect
322
- }
323
- self.class.class_eval(code, __FILE__, line)
324
- end
325
-
326
- # Defines a namespace referring to the given module.
327
- #
328
- # @param sym [Symbol] namespace
329
- # @param mod [Module] module
330
- # @return [void]
331
- def define_extension_method(sym, mod)
332
- self.singleton_class.define_method(sym) do
333
- (@extension_proxies ||= {})[mod] ||= ExtensionProxy.new(self, mod)
334
- end
335
- end
336
-
337
- # Emits an arbitrary object by converting it to string, then adding it to
338
- # the internal buffer. This method is called internally by `Renderer#emit`.
339
- #
340
- # @param obj [Object] emitted object
341
- # @return [void]
342
- def emit_object(obj)
343
- return if @render_fragment && @fragment != @render_fragment
344
-
345
- @buffer << obj.to_s
346
- end
347
-
348
- # Renders a deferred proc by evaluating it, then adding the rendered result
349
- # to the buffer.
350
- #
351
- # @return [void]
352
- def render_deferred_proc(&block)
353
- old_buffer = @buffer
354
-
355
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
356
- @parts = nil
357
-
358
- instance_eval(&block)
359
-
360
- old_buffer << to_s
361
- @buffer = old_buffer
362
- end
363
-
364
- # Escapes text. This method must be overriden in Renderers which include
365
- # this module.
366
- #
367
- # @param text [String] text to be escaped
368
- def escape_text(text)
369
- raise NotImplementedError
370
- end
371
-
372
- # Converts a tag to its string representation. This method must be overriden
373
- # in Renderers which include this module.
374
- #
375
- # @param tag [Symbol, String] tag
376
- def tag_repr(tag)
377
- raise NotImplementedError
378
- end
379
-
380
- # Converts an attribute to its string representation. This method must be
381
- # overriden in Renderers which include this module.
382
- #
383
- # @param att [Symbol, String] attribute
384
- def att_repr(att)
385
- raise NotImplementedError
386
- end
387
-
388
- # Emits tag attributes into the rendering buffer.
389
- #
390
- # @param attributes [Hash] tag attributes
391
- # @return [void]
392
- def emit_attributes(attributes)
393
- attributes.each do |k, v|
394
- case v
395
- when true
396
- @buffer << S_SPACE << att_repr(k)
397
- when false, nil
398
- # emit nothing
399
- else
400
- v = v.join(S_SPACE) if v.is_a?(Array)
401
- @buffer << S_SPACE << att_repr(k) <<
402
- S_EQUAL_QUOTE << escape_text(v) << S_QUOTE
403
- end
404
- end
405
- end
406
-
407
- end
408
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './tags'
4
- require 'escape_utils'
5
-
6
- module Papercraft
7
- # XML renderer extensions
8
- module XML
9
- include Tags
10
-
11
- private
12
-
13
- # Returns false (no void elements in XML)
14
- #
15
- # @param tag [String] tag
16
- # @return [false] false
17
- def is_void_element_tag?(tag)
18
- false
19
- end
20
-
21
- # Converts a tag to its string representation. Underscores will be converted
22
- # to dashes, double underscores will be converted to colon.
23
- #
24
- # @param tag [Symbol, String] tag
25
- # @return [String] tag string
26
- def tag_repr(tag)
27
- tag.to_s.gsub('__', ':').tr('_', '-')
28
- end
29
-
30
- # Converts an attribute to its string representation. Underscores will be
31
- # converted to dashes, double underscores will be converted to colon.
32
- #
33
- # @param att [Symbol, String] attribute
34
- # @return [String] attribute string
35
- def att_repr(att)
36
- att.to_s.gsub('__', ':').tr('_', '-')
37
- end
38
-
39
- # Escapes the given text using XML entities.
40
- #
41
- # @param text [String] text
42
- # @return [String] escaped text
43
- def escape_text(text)
44
- EscapeUtils.escape_xml(text.to_s)
45
- end
46
- end
47
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'papercraft'
4
- require 'tilt'
5
-
6
- # Tilt.
7
- module Tilt
8
- # Papercraft templating engine for Tilt
9
- class PapercraftTemplate < Template
10
- metadata[:mime_type] = 'text/html'
11
-
12
- protected
13
-
14
- def prepare
15
- inner = eval("proc { |scope:, locals:, block:|\n#{data}\n}")
16
- @template = Papercraft.html(&inner)
17
- end
18
-
19
- def evaluate(scope, locals, &block)
20
- @template.render(scope: scope, locals: locals, block: block)
21
- end
22
- end
23
-
24
- register(PapercraftTemplate, 'papercraft')
25
- end