papercraft 0.18 → 0.22

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a887e5946cfe19b6874046cb4ef173a1de4deb11509ea4c7dca5c6434e0d710d
4
- data.tar.gz: 3bdd374b54c72dac9f8137f5878cd43f0415c84eadebfef50fc90eb30029bf3b
3
+ metadata.gz: 729c7b1cf7482cb761b005fba8da8ae4c1325559daee15fedd2d695de21ee08d
4
+ data.tar.gz: 0546c527a6a9ac0570c9bd6057101b31591eba1f1ee549c272d9bda13a9f7a04
5
5
  SHA512:
6
- metadata.gz: 8d4a911c94bb39ab8da06e3f61a965a7db13725b143a2cf38feae44cbb1ac0ae0a8d79b899008a14c87d890ba5ebff61fb3616b5a2014bd84049da8af95a9029
7
- data.tar.gz: fe798bfc67006d21e6bc62cfa32d50141720f44d0089d9da58b7b7406b8d1386e8f77d10f4a34863dad05968e0342acd9d94be68afeca98955189853b367a8e0
6
+ metadata.gz: fdaa8d2f254744694ea3624f39871f4b411294a990f6e1bb4c57f56bf36049031b9403820cbadaa57ef6ac3e9021b6afa53da22588462450a6ca0863b6b88184
7
+ data.tar.gz: 4ea21235970af0cdef9ce09cd790742a94632e4f1f02d4a3d90a3e857741ec41952a53fe59b24016c00894dde7131dbbbc79d7616564f159f5646acf24f469f9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.22 2022-02-14
2
+
3
+ - Fix behaviour of call to `#p` in an extension (#10)
4
+
5
+ ## 0.21 2022-02-13
6
+
7
+ - Refactor and improve documentation
8
+
9
+ ## 0.20 2022-02-13
10
+
11
+ - Add support for XML namespaced tags and attributes (#9)
12
+ - Move and refactor HTML/XML common code to Tags module
13
+
14
+ ## 0.19 2022-02-05
15
+
16
+ - Rename `Papercraft::Component` to `Papercraft::Template`
17
+
1
18
  ## 0.18 2022-02-04
2
19
 
3
20
  - Cleanup and update examples
data/README.md CHANGED
@@ -64,13 +64,14 @@ hello.render('world')
64
64
  - [Installing papercraft](#installing-papercraft)
65
65
  - [Basic usage](#basic-usage)
66
66
  - [Adding tags](#adding-tags)
67
+ - [Tag and attribute formatting](#tag-and-attribute-formatting)
67
68
  - [Template parameters](#template-parameters)
68
69
  - [Template logic](#template-logic)
69
70
  - [Template blocks](#template-blocks)
70
71
  - [Plain procs as templates](#plain-procs-as-templates)
71
72
  - [Template composition](#template-composition)
72
73
  - [Parameter and block application](#parameter-and-block-application)
73
- - [Higher-order components](#higher-order-components)
74
+ - [Higher-order templates](#higher-order-templates)
74
75
  - [Layout template composition](#layout-template-composition)
75
76
  - [Emitting raw HTML](#emitting-raw-html)
76
77
  - [Emitting a string with HTML Encoding](#emitting-a-string-with-html-encoding)
@@ -154,6 +155,43 @@ Papercraft.html { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
154
155
  Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
155
156
  ```
156
157
 
158
+ ## Tag and attribute formatting
159
+
160
+ Papercraft does not make any presumption about what tags and attributes you can
161
+ use. You can mix upper and lower case letters, and you can include arbitrary
162
+ characters in tag and attribute names. However, in order to best adhere to the
163
+ HTML and XML specs and common practices, tag names and attributes will be
164
+ formatted according to the following rules, depending on the template type:
165
+
166
+ - HTML: underscores are converted to dashes:
167
+
168
+ ```ruby
169
+ Papercraft.html {
170
+ foo_bar { p 'Hello', data_name: 'world' }
171
+ }.render #=> '<foo-bar><p data-name="world">Hello</p></foo-bar>'
172
+ ```
173
+
174
+ - XML: underscores are converted to dashes, double underscores are converted to
175
+ colons:
176
+
177
+ ```ruby
178
+ Papercraft.xml {
179
+ soap__Envelope(
180
+ xmlns__soap: 'http://schemas.xmlsoap.org/soap/envelope/',
181
+ ) { }
182
+ }.render #=> '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Envelope>'
183
+ ```
184
+
185
+ If you need more precise control over tag names, you can use the `#tag` method,
186
+ which takes the tag name as its first parameter, then the rest of the parameters
187
+ normally used for tags:
188
+
189
+ ```ruby
190
+ Papercraft.html {
191
+ tag 'cra_zy__:!tag', 'foo'
192
+ }.render #=> '<cra_zy__:!tag>foo</cra_zy__:!tag>'
193
+ ```
194
+
157
195
  ## Template parameters
158
196
 
159
197
  In Papercraft, parameters are always passed explicitly. This means that template
@@ -221,8 +259,8 @@ Papercraft.html(&greeting).render('world')
221
259
 
222
260
  ## Template composition
223
261
 
224
- Papercraft makes it easy to compose multiple components into a whole HTML
225
- document. A Papercraft component can contain other components, as the following
262
+ Papercraft makes it easy to compose multiple templates into a whole HTML
263
+ document. A Papercraft template can contain other templates, as the following
226
264
  example shows.
227
265
 
228
266
  ```ruby
@@ -250,14 +288,14 @@ page = Papercraft.html { |title, items|
250
288
  }
251
289
  }
252
290
 
253
- page.render('Hello from components', [
291
+ page.render('Hello from composed templates', [
254
292
  { id: 1, text: 'foo', checked: false },
255
293
  { id: 2, text: 'bar', checked: true }
256
294
  ])
257
295
  ```
258
296
 
259
- In addition to using components defined as constants, you can also use
260
- non-constant components by invoking the `#emit` method:
297
+ In addition to using templates defined as constants, you can also use
298
+ non-constant templates by invoking the `#emit` method:
261
299
 
262
300
  ```ruby
263
301
  greeting = -> { span "Hello, world" }
@@ -272,11 +310,11 @@ Papercraft.html {
272
310
  ## Parameter and block application
273
311
 
274
312
  Parameters and blocks can be applied to a template without it being rendered, by
275
- using `#apply`. This mechanism is what allows component composition and the
276
- creation of higher-order components.
313
+ using `#apply`. This mechanism is what allows template composition and the
314
+ creation of higher-order templates.
277
315
 
278
- The `#apply` method returns a new component which applies the given parameters and
279
- or block to the original component:
316
+ The `#apply` method returns a new template which applies the given parameters and
317
+ or block to the original template:
280
318
 
281
319
  ```ruby
282
320
  # parameter application
@@ -289,19 +327,19 @@ div_wrap = Papercraft.html { div { emit_yield } }
289
327
  wrapped_h1 = div_wrap.apply { h1 'hi' }
290
328
  wrapped_h1.render #=> "<div><h1>hi</h1></div>"
291
329
 
292
- # wrap a component
330
+ # wrap a template
293
331
  wrapped_hello_world = div_wrap.apply(&hello_world)
294
332
  wrapped_hello_world.render #=> "<div><h1>Hello, world!</h1></div>"
295
333
  ```
296
334
 
297
- ## Higher-order components
335
+ ## Higher-order templates
298
336
 
299
- Papercraft also lets you create higher-order components (HOCs), that is,
300
- components that take other components as parameters, or as blocks. Higher-order
301
- components are handy for creating layouts, wrapping components in arbitrary
302
- markup, enhancing components or injecting component parameters.
337
+ Papercraft also lets you create higher-order templates, that is,
338
+ templates that take other templates as parameters, or as blocks. Higher-order
339
+ templates are handy for creating layouts, wrapping templates in arbitrary
340
+ markup, enhancing templates or injecting template parameters.
303
341
 
304
- Here is a HOC that takes a component as parameter:
342
+ Here is a higher-order template that takes a template as parameter:
305
343
 
306
344
  ```ruby
307
345
  div_wrap = Papercraft.html { |inner| div { emit inner } }
@@ -310,7 +348,7 @@ wrapped_greeter = div_wrap.apply(greeter)
310
348
  wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
311
349
  ```
312
350
 
313
- The inner component can also be passed as a block, as shown above:
351
+ The inner template can also be passed as a block, as shown above:
314
352
 
315
353
  ```ruby
316
354
  div_wrap = Papercraft.html { div { emit_yield } }
@@ -320,7 +358,7 @@ wrapped_greeter.render #=> "<div><h1>hi</h1></div>"
320
358
 
321
359
  ## Layout template composition
322
360
 
323
- One of the principal uses of higher-order components is the creation of nested
361
+ One of the principal uses of higher-order templates is the creation of nested
324
362
  layouts. Suppose we have a website with a number of different layouts, and we'd
325
363
  like to avoid having to repeat the same code in the different layouts. We can do
326
364
  this by creating a `default` page template that takes a block, then use `#apply`
@@ -434,8 +472,8 @@ end
434
472
  ## Deferred evaluation
435
473
 
436
474
  Deferred evaluation allows deferring the rendering of parts of a template until
437
- the last moment, thus allowing an inner component to manipulate the state of the
438
- outer component. To in order to defer a part of a template, use `#defer`, and
475
+ the last moment, thus allowing an inner template to manipulate the state of the
476
+ outer template. To in order to defer a part of a template, use `#defer`, and
439
477
  include any markup in the provided block. This technique, in in conjunction with
440
478
  holding state in instance variables, is an alternative to passing parameters,
441
479
  which can be limiting in some situations.
@@ -444,11 +482,11 @@ A few use cases for deferred evaulation come to mind:
444
482
 
445
483
  - Setting the page title.
446
484
  - Adding a flash message to a page.
447
- - Using components that dynamically add static dependencies (JS and CSS) to the
485
+ - Using templates that dynamically add static dependencies (JS and CSS) to the
448
486
  page.
449
487
 
450
488
  The last use case is particularly interesting. Imagine a `DependencyMananger`
451
- class that can collect JS and CSS dependencies from the different components
489
+ class that can collect JS and CSS dependencies from the different templates
452
490
  integrated into the page, and adds them to the page's `<head>` element:
453
491
 
454
492
  ```ruby
@@ -513,21 +551,27 @@ and other associated methods:
513
551
 
514
552
  ```ruby
515
553
  module BootstrapComponents
516
- ...
517
-
518
- def card(**props)
554
+ def card(**props, &block)
519
555
  div(class: 'card', **props) {
520
- div(class: 'card-body') {
521
- emit_yield
522
- }
556
+ div(class: 'card-body', &block)
523
557
  }
524
558
  end
525
-
559
+
526
560
  def card_title(title)
527
- h5 title, class: 'card-title'
561
+ h4(title, class: 'card-title')
562
+ end
563
+
564
+ def card_subtitle(subtitle)
565
+ h5(subtitle, class: 'card-subtitle')
528
566
  end
529
567
 
530
- ...
568
+ def card_text(text)
569
+ p(text, class: 'card-text')
570
+ end
571
+
572
+ def card_link(text, **opts)
573
+ a(text, class: 'card-link', **opts)
574
+ end
531
575
  end
532
576
 
533
577
  Papercraft.extension(bootstrap: BootstrapComponents)
@@ -543,14 +587,12 @@ Papercraft.html {
543
587
  bootstrap.card_title 'Card title'
544
588
  bootstrap.card_subtitle 'Card subtitle'
545
589
  bootstrap.card_text 'Some quick example text to build on the card title and make up the bulk of the card''s content.'
546
- bootstrap.card_link '#', 'Card link'
547
- bootstrap.card_link '#', 'Another link'
590
+ bootstrap.card_link 'Card link', href: '#foo'
591
+ bootstrap.card_link 'Another link', href: '#bar'
548
592
  }
549
593
  }
550
594
  ```
551
595
 
552
-
553
-
554
596
  ## XML templates
555
597
 
556
598
  XML templates behave largely the same as HTML templates, with a few minor
@@ -10,6 +10,7 @@ module Papercraft
10
10
  class ExtensionProxy
11
11
 
12
12
  # Initializes a new ExtensionProxy.
13
+ #
13
14
  # @param renderer [Papercraft::Renderer] renderer to proxy to
14
15
  # @param mod [Module] extension module
15
16
  # @return [void]
@@ -18,13 +19,25 @@ module Papercraft
18
19
  extend(mod)
19
20
  end
20
21
 
21
- # Proxies missing methods to the renderer
22
+ # Proxies missing methods to the renderer.
23
+ #
22
24
  # @param sym [Symbol] method name
23
25
  # @param *args [Array] arguments
26
+ # @param *props [Array] named arguments
24
27
  # @param &block [Proc] block
25
28
  # @return void
26
- def method_missing(sym, *args, &block)
27
- @renderer.send(sym, *args, &block)
29
+ def method_missing(sym, *args, **props, &block)
30
+ @renderer.send(sym, *args, **props, &block)
31
+ end
32
+
33
+ # Overrides the `Kernel#p` method to emit a p tag.
34
+ #
35
+ # @param *args [Array] arguments
36
+ # @param *props [Array] named arguments
37
+ # @param &block [Proc] block
38
+ # @return void
39
+ def p(text = nil, **props, &block)
40
+ @renderer.p(text, **props, &block)
28
41
  end
29
42
  end
30
43
  end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './tags'
4
+
3
5
  module Papercraft
4
6
  # HTML Markup extensions
5
7
  module HTML
8
+ include Tags
9
+
6
10
  # Emits the p tag (overrides Object#p)
7
11
  #
8
12
  # @param text [String] text content of tag
@@ -81,5 +85,33 @@ module Papercraft
81
85
  def emit_markdown(markdown, **opts)
82
86
  emit Papercraft.markdown(markdown, **opts)
83
87
  end
88
+
89
+ private
90
+
91
+ # Escapes the given text using HTML entities.
92
+ #
93
+ # @param text [String] text
94
+ # @return [String] escaped text
95
+ def escape_text(text)
96
+ EscapeUtils.escape_html(text.to_s)
97
+ end
98
+
99
+ # Converts a tag to its string representation. Underscores will be converted
100
+ # to dashes.
101
+ #
102
+ # @param tag [Symbol, String] tag
103
+ # @return [String] tag string
104
+ def tag_repr(tag)
105
+ tag.to_s.tr('_', '-')
106
+ end
107
+
108
+ # Converts an attribute to its string representation. Underscores will be
109
+ # converted to dashes.
110
+ #
111
+ # @param att [Symbol, String] attribute
112
+ # @return [String] attribute string
113
+ def att_repr(att)
114
+ att.to_s.tr('_', '-')
115
+ end
84
116
  end
85
117
  end
@@ -5,69 +5,122 @@ require 'json'
5
5
  module Papercraft
6
6
  # JSON renderer extensions
7
7
  module JSON
8
- def object_stack
9
- @object_stack ||= [nil]
8
+ # Initializes a JSON renderer, setting up an object stack.
9
+ def initialize(&template)
10
+ @object_stack = [nil]
11
+ super
10
12
  end
11
13
 
14
+ # Adds an array item to the current object target. If a block is given, the
15
+ # block is evaulated against a new object target, then added to the current
16
+ # array.
17
+ #
18
+ # @param value [Object] item
19
+ # @param &block [Proc] template block
20
+ # @return [void]
21
+ def item(value = nil, &block)
22
+ verify_array_target
23
+ if block
24
+ value = enter_object(&block)
25
+ end
26
+ push_array_item(value)
27
+ end
28
+
29
+ # Adds a key-value item to the current object target. If a block is given,
30
+ # the block is evaulated against a new object target, then used as the
31
+ # value.
32
+ #
33
+ # @param key [Object] key
34
+ # @param value [Object] value
35
+ # @param &block [Proc] template block
36
+ # @return [void]
37
+ def kv(key, value = nil, &block)
38
+ verify_hash_target
39
+ if block
40
+ value = enter_object(&block)
41
+ end
42
+ push_kv_item(key, value)
43
+ end
44
+
45
+ # Intercepts method calls by adding key-value pairs to the current object
46
+ # target.
47
+ #
48
+ # @param key [Object] key
49
+ # @param value [Object] value
50
+ # @param &block [Proc] template block
51
+ # @return [void]
52
+ def method_missing(sym, value = nil, &block)
53
+ kv(sym, value, &block)
54
+ end
55
+
56
+ # Converts the root object target to JSON.
57
+ #
58
+ # @return [String] JSON template result
59
+ def to_s
60
+ @object_stack[0].to_json
61
+ end
62
+
63
+ private
64
+
65
+ # Adds a new entry to the object stack and evaluates the given block.
66
+ #
67
+ # @param &block [Proc] template block
68
+ # @return [void]
12
69
  def with_object(&block)
13
- object_stack << nil
70
+ @object_stack << nil
14
71
  instance_eval(&block)
15
72
  end
16
73
 
74
+ # Verifies that the current object target is not a hash.
75
+ #
76
+ # @return [bool]
17
77
  def verify_array_target
18
- case object_stack[-1]
78
+ case @object_stack[-1]
19
79
  when nil
20
- object_stack[-1] = []
80
+ @object_stack[-1] = []
21
81
  when Hash
22
82
  raise "Mixing array and hash values"
23
83
  end
24
84
  end
25
85
 
86
+ # Verifies that the current object target is not an array.
87
+ #
88
+ # @return [bool]
26
89
  def verify_hash_target
27
- case object_stack[-1]
90
+ case @object_stack[-1]
28
91
  when nil
29
- object_stack[-1] = {}
92
+ @object_stack[-1] = {}
30
93
  when Array
31
94
  raise "Mixing array and hash values"
32
95
  end
33
96
  end
34
97
 
98
+ # Pushes an array item to the current object target.
99
+ #
100
+ # @param value [Object] item
101
+ # @return [void]
35
102
  def push_array_item(value)
36
- object_stack[-1] << value
103
+ @object_stack[-1] << value
37
104
  end
38
105
 
106
+ # Pushes a key value into the current object target.
107
+ #
108
+ # @param key [Object] key
109
+ # @param value [Object] value
110
+ # @return [void]
39
111
  def push_kv_item(key, value)
40
- object_stack[-1][key] = value
112
+ @object_stack[-1][key] = value
41
113
  end
42
114
 
115
+ # Adds a new object to the object stack, evaluates the given template block,
116
+ # then pops the object off the stack.
117
+ #
118
+ # @param &block [Proc] template block
119
+ # @return [void]
43
120
  def enter_object(&block)
44
- object_stack << nil
121
+ @object_stack << nil
45
122
  instance_eval(&block)
46
- object_stack.pop
47
- end
48
-
49
- def item(value = nil, &block)
50
- verify_array_target
51
- if block
52
- value = enter_object(&block)
53
- end
54
- push_array_item(value)
55
- end
56
-
57
- def kv(key, value, &block)
58
- verify_hash_target
59
- if block
60
- value = enter_object(&block)
61
- end
62
- push_kv_item(key, value)
63
- end
64
-
65
- def method_missing(sym, value = nil, &block)
66
- kv(sym, value, &block)
67
- end
68
-
69
- def to_s
70
- object_stack[0].to_json
71
- end
123
+ @object_stack.pop
124
+ end
72
125
  end
73
126
  end