papercraft 0.19 → 0.23

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: f0f4910bd26625ca0349b1c471338fb4d13e8055e88367b5cad16f91cdee7531
4
- data.tar.gz: 38e43978278ba069e3767abd94d83c676f42692f5eb55f1be4b4067fae2f935e
3
+ metadata.gz: 341520b9db20c53da441299fb449c4eea3321924517f4b93e70af1119b4516c3
4
+ data.tar.gz: 861a5a2747c3a468abb780cf50710698e52fbac1fc236c8672c2bbb328db6f4d
5
5
  SHA512:
6
- metadata.gz: b096452cebe7b6bb34a428f4920a5940e5b745c86b54188a984a77df6175145cf6645753e6ae58b3df89813919920a17a96610e010724bc50077c6f9accc7293
7
- data.tar.gz: 8d1cd5b2d6b70ee9ef0a748a064bbd6d1630cfa926663aded95a01c26ce289a159e2548068bf8210c5e8d37ba26c9671eb4a00fe4790a47bec6dd822b4de5d5d
6
+ metadata.gz: 7668a984a2c7957ba3f65234eab6770b4e969e0d2b4e1345839db8c0711d07afbfef5735b67de9a94c22994b4b6c1d6b2f87e07a810a0cae04ccbecb1ff5b38f
7
+ data.tar.gz: 1605f9b1459ab20b9cec97805529764ece6058ce34609dc7d75fe38d8b35889c65e4afa5174f1c1d89dfb46205d282e65e1020995c999f66b84a5f9b5e505de1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 0.23 2022-02-15
2
+
3
+ - Remove unused `Encoding` module
4
+ - Add SOAP extension (#11, thanks [@aemadrid](https://github.com/aemadrid))
5
+
6
+ ## 0.22 2022-02-14
7
+
8
+ - Fix behaviour of call to `#p` in an extension (#10)
9
+
10
+ ## 0.21 2022-02-13
11
+
12
+ - Refactor and improve documentation
13
+
14
+ ## 0.20 2022-02-13
15
+
16
+ - Add support for XML namespaced tags and attributes (#9)
17
+ - Move and refactor HTML/XML common code to Tags module
18
+
1
19
  ## 0.19 2022-02-05
2
20
 
3
21
  - Rename `Papercraft::Component` to `Papercraft::Template`
data/README.md CHANGED
@@ -64,6 +64,7 @@ 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)
@@ -77,9 +78,10 @@ hello.render('world')
77
78
  - [Emitting Markdown](#emitting-markdown)
78
79
  - [Working with MIME types](#working-with-mime-types)
79
80
  - [Deferred evaluation](#deferred-evaluation)
80
- - [Papercraft extensions](#papercraft-extensions)
81
81
  - [XML templates](#xml-templates)
82
82
  - [JSON templates](#json-templates)
83
+ - [Papercraft extensions](#papercraft-extensions)
84
+ - [Bundled extensions](#bundled-extensions)
83
85
  - [API Reference](#api-reference)
84
86
 
85
87
  ## Installing Papercraft
@@ -154,6 +156,43 @@ Papercraft.html { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
154
156
  Papercraft.html { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
155
157
  ```
156
158
 
159
+ ## Tag and attribute formatting
160
+
161
+ Papercraft does not make any presumption about what tags and attributes you can
162
+ use. You can mix upper and lower case letters, and you can include arbitrary
163
+ characters in tag and attribute names. However, in order to best adhere to the
164
+ HTML and XML specs and common practices, tag names and attributes will be
165
+ formatted according to the following rules, depending on the template type:
166
+
167
+ - HTML: underscores are converted to dashes:
168
+
169
+ ```ruby
170
+ Papercraft.html {
171
+ foo_bar { p 'Hello', data_name: 'world' }
172
+ }.render #=> '<foo-bar><p data-name="world">Hello</p></foo-bar>'
173
+ ```
174
+
175
+ - XML: underscores are converted to dashes, double underscores are converted to
176
+ colons:
177
+
178
+ ```ruby
179
+ Papercraft.xml {
180
+ soap__Envelope(
181
+ xmlns__soap: 'http://schemas.xmlsoap.org/soap/envelope/',
182
+ ) { }
183
+ }.render #=> '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Envelope>'
184
+ ```
185
+
186
+ If you need more precise control over tag names, you can use the `#tag` method,
187
+ which takes the tag name as its first parameter, then the rest of the parameters
188
+ normally used for tags:
189
+
190
+ ```ruby
191
+ Papercraft.html {
192
+ tag 'cra_zy__:!tag', 'foo'
193
+ }.render #=> '<cra_zy__:!tag>foo</cra_zy__:!tag>'
194
+ ```
195
+
157
196
  ## Template parameters
158
197
 
159
198
  In Papercraft, parameters are always passed explicitly. This means that template
@@ -483,6 +522,72 @@ page = default_layout.apply {
483
522
  }
484
523
  ```
485
524
 
525
+ ## XML templates
526
+
527
+ XML templates behave largely the same as HTML templates, with a few minor
528
+ differences. XML templates employ a different encoding algorithm, and lack some
529
+ specific HTML functionality, such as emitting Markdown.
530
+
531
+ Here's an example showing how to create an RSS feed:
532
+
533
+ ```ruby
534
+ rss = Papercraft.xml(mime_type: 'text/xml; charset=utf-8') { |resource:, **props|
535
+ rss(version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom') {
536
+ channel {
537
+ title 'Noteflakes'
538
+ link 'https://noteflakes.com/'
539
+ description 'A website by Sharon Rosner'
540
+ language 'en-us'
541
+ pubDate Time.now.httpdate
542
+ emit '<atom:link href="https://noteflakes.com/feeds/rss" rel="self" type="application/rss+xml" />'
543
+
544
+ article_entries = resource.page_list('/articles').reverse
545
+
546
+ article_entries.each { |e|
547
+ item {
548
+ title e[:title]
549
+ link "https://noteflakes.com#{e[:url]}"
550
+ guid "https://noteflakes.com#{e[:url]}"
551
+ pubDate e[:date].to_time.httpdate
552
+ description e[:html_content]
553
+ }
554
+ }
555
+ }
556
+ }
557
+ }
558
+ ```
559
+
560
+ ## JSON templates
561
+
562
+ JSON templates behave largely the same as HTML and XML templates. The only major
563
+ difference is that for adding array items you'll need to use the `#item` method:
564
+
565
+ ```ruby
566
+ Papercraft.json {
567
+ item 1
568
+ item 2
569
+ item 3
570
+ }.render #=> "[1,2,3]"
571
+ ```
572
+
573
+ Otherwise, you can create arbitrarily complex JSON structures by mixing hashes
574
+ and arrays:
575
+
576
+ ```Ruby
577
+ Papercraft.json {
578
+ foo {
579
+ bar {
580
+ item nil
581
+ item true
582
+ item 123.456
583
+ }
584
+ }
585
+ }.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
586
+ ```
587
+
588
+ Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood in
589
+ order to generate actual JSON.
590
+
486
591
  ## Papercraft extensions
487
592
 
488
593
  Papercraft extensions are modules that contain one or more methods that can be
@@ -513,21 +618,27 @@ and other associated methods:
513
618
 
514
619
  ```ruby
515
620
  module BootstrapComponents
516
- ...
517
-
518
- def card(**props)
621
+ def card(**props, &block)
519
622
  div(class: 'card', **props) {
520
- div(class: 'card-body') {
521
- emit_yield
522
- }
623
+ div(class: 'card-body', &block)
523
624
  }
524
625
  end
525
-
626
+
526
627
  def card_title(title)
527
- h5 title, class: 'card-title'
628
+ h4(title, class: 'card-title')
629
+ end
630
+
631
+ def card_subtitle(subtitle)
632
+ h5(subtitle, class: 'card-subtitle')
633
+ end
634
+
635
+ def card_text(text)
636
+ p(text, class: 'card-text')
528
637
  end
529
638
 
530
- ...
639
+ def card_link(text, **opts)
640
+ a(text, class: 'card-link', **opts)
641
+ end
531
642
  end
532
643
 
533
644
  Papercraft.extension(bootstrap: BootstrapComponents)
@@ -543,80 +654,62 @@ Papercraft.html {
543
654
  bootstrap.card_title 'Card title'
544
655
  bootstrap.card_subtitle 'Card subtitle'
545
656
  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'
657
+ bootstrap.card_link 'Card link', href: '#foo'
658
+ bootstrap.card_link 'Another link', href: '#bar'
548
659
  }
549
660
  }
550
661
  ```
551
662
 
663
+ ## Bundled Extensions
552
664
 
665
+ Papercraft comes bundled with a few extensions that address common use cases.
666
+ All bundled extensions are namespaced under `Papercraft::Extensions`, and must
667
+ be specifically required in order to be available to templates.
553
668
 
554
- ## XML templates
669
+ For all bundled Papercraft extensions, there's no need to call
670
+ `Papercraft.extension`, requiring the extension is sufficient.
555
671
 
556
- XML templates behave largely the same as HTML templates, with a few minor
557
- differences. XML templates employ a different encoding algorithm, and lack some
558
- specific HTML functionality, such as emitting Markdown.
672
+ ### SOAP extension
559
673
 
560
- Here's an example showing how to create an RSS feed:
674
+ > The SOAP extension was contributed by [@aemadrid](https://github.com/aemadrid).
561
675
 
562
- ```ruby
563
- rss = Papercraft.xml(mime_type: 'text/xml; charset=utf-8') { |resource:, **props|
564
- rss(version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom') {
565
- channel {
566
- title 'Noteflakes'
567
- link 'https://noteflakes.com/'
568
- description 'A website by Sharon Rosner'
569
- language 'en-us'
570
- pubDate Time.now.httpdate
571
- emit '<atom:link href="https://noteflakes.com/feeds/rss" rel="self" type="application/rss+xml" />'
572
-
573
- article_entries = resource.page_list('/articles').reverse
676
+ The SOAP extension provides common tags for building SOAP payloads. To load the
677
+ SOAP extensions, require `polyphony/extensions/soap`. The extension provides the
678
+ following methods:
574
679
 
575
- article_entries.each { |e|
576
- item {
577
- title e[:title]
578
- link "https://noteflakes.com#{e[:url]}"
579
- guid "https://noteflakes.com#{e[:url]}"
580
- pubDate e[:date].to_time.httpdate
581
- description e[:html_content]
582
- }
583
- }
584
- }
585
- }
586
- }
587
- ```
680
+ - `soap.Body(...)` - emits a `soap:Body` tag.
681
+ - `soap.Envelope(...)` - emits a `soap:Envelope` tag.
682
+ - `soap.Fault(...)` - emits a `soap:Fault` tag.
683
+ - `soap.Header(...)` - emits a `soap:Header` tag.
588
684
 
589
- ## JSON templates
590
-
591
- JSON templates behave largely the same as HTML and XML templates. The only major
592
- difference is that for adding array items you'll need to use the `#item` method:
685
+ As mentioned above, namespaced tags and attributes may be specified by using
686
+ double underscores for colons. Other tags that contain special characters may be
687
+ emitted using the `#tag` method:
593
688
 
594
689
  ```ruby
595
- Papercraft.json {
596
- item 1
597
- item 2
598
- item 3
599
- }.render #=> "[1,2,3]"
600
- ```
690
+ require 'polyphony/extensions/soap'
601
691
 
602
- Otherwise, you can create arbitrarily complex JSON structures by mixing hashes
603
- and arrays:
604
-
605
- ```Ruby
606
- Papercraft.json {
607
- foo {
608
- bar {
609
- item nil
610
- item true
611
- item 123.456
692
+ xml = Papercraft.xml {
693
+ soap.Envelope(
694
+ xmlns__xsd: 'http://www.w3.org/2001/XMLSchema',
695
+ xmlns__xsi: 'http://www.w3.org/2001/XMLSchema-instance'
696
+ ) {
697
+ soap.Body {
698
+ PosRequest(xmlns: 'http://Some.Site') {
699
+ tag('Ver1.0') {
700
+ Header {
701
+ SecretAPIKey 'some_secret_key'
702
+ }
703
+ Transaction {
704
+ SomeData {}
705
+ }
706
+ }
707
+ }
612
708
  }
613
709
  }
614
- }.render #=> "{\"foo\":{\"bar\":[null,true,123.456]}}"
710
+ }
615
711
  ```
616
712
 
617
- Papercraft uses the [JSON gem](https://rubyapi.org/3.1/o/json) under the hood in
618
- order to generate actual JSON.
619
-
620
713
  ## API Reference
621
714
 
622
715
  The API reference for this library can be found
@@ -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
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Papercraft
4
+ module Extensions
5
+ module Soap
6
+ # Emits a SOAP XML tag that identifies the XML document as a SOAP message.
7
+ #
8
+ # @param **props [Hash] tag attributes
9
+ # @param &block [Proc] optional inner XML
10
+ # @return [void]
11
+ def Envelope(**props, &block)
12
+ props[:xmlns__soap] ||= 'http://schemas.xmlsoap.org/soap/envelope/'
13
+ tag('soap:Envelope', **props, &block)
14
+ end
15
+
16
+ # Emits a SOAP XML tag that contains header information.
17
+ #
18
+ # @param **props [Hash] tag attributes
19
+ # @param &block [Proc] optional inner XML
20
+ # @return [void]
21
+ def Header(**props, &blk)
22
+ tag('soap:Header', **props, &blk)
23
+ end
24
+
25
+ # Emits a SOAP XML tag that contains header information.
26
+ #
27
+ # @param **props [Hash] tag attributes
28
+ # @param &block [Proc] optional inner XML
29
+ # @return [void]
30
+ def Body(**props, &blk)
31
+ tag('soap:Body', **props, &blk)
32
+ end
33
+
34
+ # Emits a SOAP XML tag that contains errors and status information.
35
+ #
36
+ # @param **props [Hash] tag attributes
37
+ # @param &block [Proc] optional inner XML
38
+ # @return [void]
39
+ def Fault(**props, &blk)
40
+ tag('soap:Fault', **props, &blk)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ Papercraft.extension(soap: Papercraft::Extensions::Soap)
@@ -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
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'escape_utils'
4
-
5
3
  require_relative './html'
4
+ require_relative './xml'
6
5
  require_relative './json'
7
6
  require_relative './extension_proxy'
8
7
 
@@ -10,7 +9,7 @@ module Papercraft
10
9
 
11
10
  # A Renderer renders a Papercraft template into a string
12
11
  class Renderer
13
-
12
+
14
13
  class << self
15
14
 
16
15
  # Verifies that the given template proc can be called with the given
@@ -45,7 +44,7 @@ module Papercraft
45
44
  # methods to extension modules. The methods will be available to all
46
45
  # Papercraft templates. Extension methods are executed in the context of
47
46
  # the the renderer instance, so they can look just like normal proc
48
- # components. In cases where method names in the module clash with HTML
47
+ # components. In cases where method names in the module clash with XML
49
48
  # tag names, you can use the `#tag` method to emit the relevant tag.
50
49
  #
51
50
  # module ComponentLibrary
@@ -81,131 +80,14 @@ module Papercraft
81
80
  end
82
81
  end
83
82
 
84
- INITIAL_BUFFER_CAPACITY = 8192
85
-
86
83
  # Initializes the renderer and evaulates the given template in the
87
84
  # renderer's scope.
88
85
  #
89
86
  # @param &template [Proc] template block
90
87
  def initialize(&template)
91
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
92
88
  instance_eval(&template)
93
89
  end
94
90
 
95
- # Returns the rendered template.
96
- #
97
- # @return [String]
98
- def to_s
99
- if @parts
100
- last = @buffer
101
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
102
- parts = @parts
103
- @parts = nil
104
- parts.each do |p|
105
- if Proc === p
106
- render_deferred_proc(&p)
107
- else
108
- @buffer << p
109
- end
110
- end
111
- @buffer << last unless last.empty?
112
- end
113
- @buffer
114
- end
115
-
116
- # The tag method template below is optimized for performance. Do not touch!
117
-
118
- S_TAG_METHOD_LINE = __LINE__ + 2
119
- S_TAG_METHOD = <<~EOF
120
- S_TAG_%<TAG>s_PRE = %<tag_pre>s
121
- S_TAG_%<TAG>s_CLOSE = %<tag_close>s
122
-
123
- def %<tag>s(text = nil, **props, &block)
124
- if text.is_a?(Hash) && props.empty?
125
- props = text
126
- text = nil
127
- end
128
-
129
- @buffer << S_TAG_%<TAG>s_PRE
130
- emit_props(props) unless props.empty?
131
-
132
- if block
133
- @buffer << S_GT
134
- instance_eval(&block)
135
- @buffer << S_TAG_%<TAG>s_CLOSE
136
- elsif Proc === text
137
- @buffer << S_GT
138
- emit(text)
139
- @buffer << S_TAG_%<TAG>s_CLOSE
140
- elsif text
141
- @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
142
- else
143
- @buffer << S_SLASH_GT
144
- end
145
- end
146
- EOF
147
-
148
- # Emits an HTML tag with the given content, properties and optional block.
149
- # This method is an alternative to emitting HTML tags using dynamically
150
- # created methods. This is particularly useful when using extensions that
151
- # have method names that clash with HTML tags, such as `button` or `a`, or
152
- # when you need to override the behaviour of a particular HTML tag.
153
- #
154
- # The following two method calls have the same effect:
155
- #
156
- # button 'text', id: 'button1'
157
- # tag :button, 'text', id: 'button1'
158
- #
159
- # @param sym [Symbol, String] HTML tag
160
- # @param text [String, nil] tag content
161
- # @param **props [Hash] tag attributes
162
- # @param &block [Proc] optional inner HTML
163
- # @return [void]
164
- def tag(sym, text = nil, **props, &block)
165
- if text.is_a?(Hash) && props.empty?
166
- props = text
167
- text = nil
168
- end
169
-
170
- tag = sym.to_s.tr('_', '-')
171
-
172
- @buffer << S_LT << tag
173
- emit_props(props) unless props.empty?
174
-
175
- if block
176
- @buffer << S_GT
177
- instance_eval(&block)
178
- @buffer << S_LT_SLASH << tag << S_GT
179
- elsif Proc === text
180
- @buffer << S_GT
181
- emit(text)
182
- @buffer << S_LT_SLASH << tag << S_GT
183
- elsif text
184
- @buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
185
- else
186
- @buffer << S_SLASH_GT
187
- end
188
- end
189
-
190
- # Catches undefined tag method call and handles it by defining the method.
191
- #
192
- # @param sym [Symbol] HTML tag or component identifier
193
- # @param args [Array] method arguments
194
- # @param opts [Hash] named method arguments
195
- # @param &block [Proc] block passed to method
196
- # @return [void]
197
- def method_missing(sym, *args, **opts, &block)
198
- tag = sym.to_s
199
- code = S_TAG_METHOD % {
200
- tag: tag,
201
- TAG: tag.upcase,
202
- tag_pre: "<#{tag.tr('_', '-')}".inspect,
203
- tag_close: "</#{tag.tr('_', '-')}>".inspect
204
- }
205
- self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
206
- send(sym, *args, **opts, &block)
207
- end
208
-
209
91
  # Emits the given object into the rendering buffer. If the given object is a
210
92
  # proc or a component, `emit` will passes any additional arguments and named
211
93
  # arguments to the object when rendering it. If the given object is nil,
@@ -230,8 +112,9 @@ module Papercraft
230
112
  push_emit_yield_block(block) if block
231
113
  instance_exec(*a, **b, &o)
232
114
  when nil
115
+ # do nothing
233
116
  else
234
- @buffer << o.to_s
117
+ emit_object(o)
235
118
  end
236
119
  end
237
120
  alias_method :e, :emit
@@ -251,140 +134,28 @@ module Papercraft
251
134
 
252
135
  instance_exec(*a, **b, &block)
253
136
  end
254
-
255
- # Defers the given block to be evaluated later. Deferred evaluation allows
256
- # Papercraft templates to inject state into sibling components, regardless
257
- # of the component's order in the container component. For example, a nested
258
- # component may set an instance variable used by another component. This is
259
- # an elegant solution to the problem of setting the HTML page's title, or
260
- # adding elements to the `<head>` section. Here's how a title can be
261
- # controlled from a nested component:
262
- #
263
- # layout = Papercraft.html {
264
- # html {
265
- # head {
266
- # defer { title @title }
267
- # }
268
- # body {
269
- # emit_yield
270
- # }
271
- # }
272
- # }
273
- #
274
- # html.render {
275
- # @title = 'My super page'
276
- # h1 'content'
277
- # }
278
- #
279
- # @param &block [Proc] Deferred block to be emitted
280
- # @return [void]
281
- def defer(&block)
282
- if !@parts
283
- @parts = [@buffer, block]
284
- else
285
- @parts << @buffer unless @buffer.empty?
286
- @parts << block
287
- end
288
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
289
- end
290
137
 
291
- S_LT = '<'
292
- S_GT = '>'
293
- S_LT_SLASH = '</'
294
- S_SPACE_LT_SLASH = ' </'
295
- S_SLASH_GT = '/>'
296
- S_SPACE = ' '
297
- S_EQUAL_QUOTE = '="'
298
- S_QUOTE = '"'
299
-
300
- # Emits text into the rendering buffer, escaping any special characters to
301
- # the respective HTML entities.
302
- #
303
- # @param data [String] text
304
- # @return [void]
305
- def text(data)
306
- @buffer << escape_text(data)
307
- end
308
-
309
138
  private
310
139
 
311
- # Escapes text. This method must be overriden in descendant classes.
312
- #
313
- # @param text [String] text to be escaped
314
- def escape_text(text)
315
- raise NotImplementedError
316
- end
317
-
318
140
  # Pushes the given block onto the emit_yield stack.
319
141
  #
320
142
  # @param block [Proc] block
321
143
  def push_emit_yield_block(block)
322
144
  (@emit_yield_stack ||= []) << block
323
145
  end
324
-
325
- # Emits tag attributes into the rendering buffer
326
- # @param props [Hash] tag attributes
327
- # @return [void]
328
- def emit_props(props)
329
- props.each { |k, v|
330
- case k
331
- when :src, :href
332
- @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
333
- EscapeUtils.escape_uri(v) << S_QUOTE
334
- else
335
- case v
336
- when true
337
- @buffer << S_SPACE << k.to_s.tr('_', '-')
338
- when false, nil
339
- # emit nothing
340
- else
341
- @buffer << S_SPACE << k.to_s.tr('_', '-') <<
342
- S_EQUAL_QUOTE << v << S_QUOTE
343
- end
344
- end
345
- }
346
- end
347
-
348
- # Renders a deferred proc by evaluating it, then adding the rendered result
349
- # to the buffer.
350
- #
351
- # @param &block [Proc] deferred proc
352
- # @return [void]
353
- def render_deferred_proc(&block)
354
- old_buffer = @buffer
355
-
356
- @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
357
- @parts = nil
358
-
359
- instance_eval(&block)
360
-
361
- old_buffer << to_s
362
- @buffer = old_buffer
363
- end
364
146
  end
365
147
 
366
148
  # Implements an HTML renderer
367
149
  class HTMLRenderer < Renderer
368
150
  include HTML
369
-
370
- private
371
-
372
- # Escapes the given text using HTML entities.
373
- def escape_text(text)
374
- EscapeUtils.escape_html(text.to_s)
375
- end
376
151
  end
377
152
 
378
153
  # Implements an XML renderer
379
154
  class XMLRenderer < Renderer
380
- private
381
-
382
- # Escapes the given text using XML entities.
383
- def escape_text(text)
384
- EscapeUtils.escape_xml(text.to_s)
385
- end
155
+ include XML
386
156
  end
387
157
 
158
+ # Implements a JSON renderer
388
159
  class JSONRenderer < Renderer
389
160
  include JSON
390
161
  end
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Papercraft
4
+ # Markup (HTML/XML) extensions
5
+ module Tags
6
+ S_LT = '<'
7
+ S_GT = '>'
8
+ S_LT_SLASH = '</'
9
+ S_SPACE_LT_SLASH = ' </'
10
+ S_SLASH_GT = '/>'
11
+ S_SPACE = ' '
12
+ S_EQUAL_QUOTE = '="'
13
+ S_QUOTE = '"'
14
+
15
+ # The tag method template below is optimized for performance. Do not touch!
16
+
17
+ S_TAG_METHOD_LINE = __LINE__ + 2
18
+ S_TAG_METHOD = <<~EOF
19
+ S_TAG_%<TAG>s_PRE = %<tag_pre>s
20
+ S_TAG_%<TAG>s_CLOSE = %<tag_close>s
21
+
22
+ def %<tag>s(text = nil, **props, &block)
23
+ if text.is_a?(Hash) && props.empty?
24
+ props = text
25
+ text = nil
26
+ end
27
+
28
+ @buffer << S_TAG_%<TAG>s_PRE
29
+ emit_props(props) unless props.empty?
30
+
31
+ if block
32
+ @buffer << S_GT
33
+ instance_eval(&block)
34
+ @buffer << S_TAG_%<TAG>s_CLOSE
35
+ elsif Proc === text
36
+ @buffer << S_GT
37
+ emit(text)
38
+ @buffer << S_TAG_%<TAG>s_CLOSE
39
+ elsif text
40
+ @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
41
+ else
42
+ @buffer << S_SLASH_GT
43
+ end
44
+ end
45
+ EOF
46
+
47
+ INITIAL_BUFFER_CAPACITY = 8192
48
+
49
+ # Initializes a tag renderer.
50
+ def initialize(&template)
51
+ @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
52
+ super
53
+ end
54
+
55
+ # Returns the rendered template.
56
+ #
57
+ # @return [String]
58
+ def to_s
59
+ if @parts
60
+ last = @buffer
61
+ @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
62
+ parts = @parts
63
+ @parts = nil
64
+ parts.each do |p|
65
+ if Proc === p
66
+ render_deferred_proc(&p)
67
+ else
68
+ @buffer << p
69
+ end
70
+ end
71
+ @buffer << last unless last.empty?
72
+ end
73
+ @buffer
74
+ end
75
+
76
+ # Defers the given block to be evaluated later. Deferred evaluation allows
77
+ # Papercraft templates to inject state into sibling components, regardless
78
+ # of the component's order in the container component. For example, a nested
79
+ # component may set an instance variable used by another component. This is
80
+ # an elegant solution to the problem of setting the XML page's title, or
81
+ # adding elements to the `<head>` section. Here's how a title can be
82
+ # controlled from a nested component:
83
+ #
84
+ # layout = Papercraft.html {
85
+ # html {
86
+ # head {
87
+ # defer { title @title }
88
+ # }
89
+ # body {
90
+ # emit_yield
91
+ # }
92
+ # }
93
+ # }
94
+ #
95
+ # html.render {
96
+ # @title = 'My super page'
97
+ # h1 'content'
98
+ # }
99
+ #
100
+ # @param &block [Proc] Deferred block to be emitted
101
+ # @return [void]
102
+ def defer(&block)
103
+ if !@parts
104
+ @parts = [@buffer, block]
105
+ else
106
+ @parts << @buffer unless @buffer.empty?
107
+ @parts << block
108
+ end
109
+ @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
110
+ end
111
+
112
+
113
+ # Emits an XML tag with the given content, properties and optional block.
114
+ # This method is an alternative to emitting XML tags using dynamically
115
+ # created methods. This is particularly useful when using extensions that
116
+ # have method names that clash with XML tags, such as `button` or `a`, or
117
+ # when you need to override the behaviour of a particular XML tag.
118
+ #
119
+ # The following two method calls have the same effect:
120
+ #
121
+ # button 'text', id: 'button1'
122
+ # tag :button, 'text', id: 'button1'
123
+ #
124
+ # @param sym [Symbol, String] XML tag
125
+ # @param text [String, nil] tag content
126
+ # @param **props [Hash] tag attributes
127
+ # @param &block [Proc] optional inner XML
128
+ # @return [void]
129
+ def tag(sym, text = nil, **props, &block)
130
+ if text.is_a?(Hash) && props.empty?
131
+ props = text
132
+ text = nil
133
+ end
134
+
135
+ tag = tag_repr(sym)
136
+
137
+ @buffer << S_LT << tag
138
+ emit_props(props) unless props.empty?
139
+
140
+ if block
141
+ @buffer << S_GT
142
+ instance_eval(&block)
143
+ @buffer << S_LT_SLASH << tag << S_GT
144
+ elsif Proc === text
145
+ @buffer << S_GT
146
+ emit(text)
147
+ @buffer << S_LT_SLASH << tag << S_GT
148
+ elsif text
149
+ @buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
150
+ else
151
+ @buffer << S_SLASH_GT
152
+ end
153
+ end
154
+
155
+ # Catches undefined tag method call and handles it by defining the method.
156
+ #
157
+ # @param sym [Symbol] XML tag or component identifier
158
+ # @param args [Array] method arguments
159
+ # @param opts [Hash] named method arguments
160
+ # @param &block [Proc] block passed to method
161
+ # @return [void]
162
+ def method_missing(sym, *args, **opts, &block)
163
+ tag = sym.to_s
164
+ repr = tag_repr(tag)
165
+ code = S_TAG_METHOD % {
166
+ tag: tag,
167
+ TAG: tag.upcase,
168
+ tag_pre: "<#{repr}".inspect,
169
+ tag_close: "</#{repr}>".inspect
170
+ }
171
+ self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
172
+ send(sym, *args, **opts, &block)
173
+ end
174
+
175
+ # Emits text into the rendering buffer, escaping any special characters to
176
+ # the respective XML entities.
177
+ #
178
+ # @param data [String] text
179
+ # @return [void]
180
+ def text(data)
181
+ @buffer << escape_text(data)
182
+ end
183
+
184
+ private
185
+
186
+ # Emits an arbitrary object by converting it to string, then adding it to
187
+ # the internal buffer. This method is called internally by `Renderer#emit`.
188
+ #
189
+ # @param obj [Object] emitted object
190
+ # @return [void]
191
+ def emit_object(obj)
192
+ @buffer << obj.to_s
193
+ end
194
+
195
+ # Renders a deferred proc by evaluating it, then adding the rendered result
196
+ # to the buffer.
197
+ #
198
+ # @param &block [Proc] deferred proc
199
+ # @return [void]
200
+ def render_deferred_proc(&block)
201
+ old_buffer = @buffer
202
+
203
+ @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
204
+ @parts = nil
205
+
206
+ instance_eval(&block)
207
+
208
+ old_buffer << to_s
209
+ @buffer = old_buffer
210
+ end
211
+
212
+ # Escapes text. This method must be overriden in Renderers which include
213
+ # this module.
214
+ #
215
+ # @param text [String] text to be escaped
216
+ def escape_text(text)
217
+ raise NotImplementedError
218
+ end
219
+
220
+ # Converts a tag to its string representation. This method must be overriden
221
+ # in Renderers which include this module.
222
+ #
223
+ # @param tag [Symbol, String] tag
224
+ def tag_repr(tag)
225
+ raise NotImplementedError
226
+ end
227
+
228
+ # Converts an attribute to its string representation. This method must be
229
+ # overriden in Renderers which include this module.
230
+ #
231
+ # @param att [Symbol, String] attribute
232
+ def att_repr(att)
233
+ raise NotImplementedError
234
+ end
235
+
236
+ # Emits tag attributes into the rendering buffer.
237
+ #
238
+ # @param props [Hash] tag attributes
239
+ # @return [void]
240
+ def emit_props(props)
241
+ props.each { |k, v|
242
+ case k
243
+ when :src, :href
244
+ @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
245
+ EscapeUtils.escape_uri(v) << S_QUOTE
246
+ else
247
+ case v
248
+ when true
249
+ @buffer << S_SPACE << att_repr(k)
250
+ when false, nil
251
+ # emit nothing
252
+ else
253
+ @buffer << S_SPACE << att_repr(k) <<
254
+ S_EQUAL_QUOTE << v << S_QUOTE
255
+ end
256
+ end
257
+ }
258
+ end
259
+ end
260
+ end
@@ -167,6 +167,9 @@ module Papercraft
167
167
  end
168
168
  end
169
169
 
170
+ # Returns the template's associated MIME type.
171
+ #
172
+ # @return [String] MIME type
170
173
  def mime_type
171
174
  @mime_type
172
175
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '0.19'
4
+ VERSION = '0.23'
5
5
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'escape_utils'
4
+ require_relative './tags'
5
+
6
+ module Papercraft
7
+ # XML renderer extensions
8
+ module XML
9
+ include Tags
10
+
11
+ private
12
+
13
+ # Converts a tag to its string representation. Underscores will be converted
14
+ # to dashes, double underscores will be converted to colon.
15
+ #
16
+ # @param tag [Symbol, String] tag
17
+ # @return [String] tag string
18
+ def tag_repr(tag)
19
+ tag.to_s.gsub('__', ':').tr('_', '-')
20
+ end
21
+
22
+ # Converts an attribute to its string representation. Underscores will be
23
+ # converted to dashes, double underscores will be converted to colon.
24
+ #
25
+ # @param att [Symbol, String] attribute
26
+ # @return [String] attribute string
27
+ def att_repr(att)
28
+ att.to_s.gsub('__', ':').tr('_', '-')
29
+ end
30
+
31
+ # Escapes the given text using XML entities.
32
+ #
33
+ # @param text [String] text
34
+ # @return [String] escaped text
35
+ def escape_text(text)
36
+ EscapeUtils.escape_xml(text.to_s)
37
+ end
38
+ end
39
+ end
data/lib/papercraft.rb CHANGED
@@ -6,7 +6,6 @@ require 'kramdown-parser-gfm'
6
6
 
7
7
  require_relative 'papercraft/template'
8
8
  require_relative 'papercraft/renderer'
9
- require_relative 'papercraft/encoding'
10
9
  # require_relative 'papercraft/compiler'
11
10
 
12
11
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: papercraft
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.19'
4
+ version: '0.23'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-05 00:00:00.000000000 Z
11
+ date: 2022-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils
@@ -134,13 +134,15 @@ files:
134
134
  - README.md
135
135
  - lib/papercraft.rb
136
136
  - lib/papercraft/compiler.rb
137
- - lib/papercraft/encoding.rb
138
137
  - lib/papercraft/extension_proxy.rb
138
+ - lib/papercraft/extensions/soap.rb
139
139
  - lib/papercraft/html.rb
140
140
  - lib/papercraft/json.rb
141
141
  - lib/papercraft/renderer.rb
142
+ - lib/papercraft/tags.rb
142
143
  - lib/papercraft/template.rb
143
144
  - lib/papercraft/version.rb
145
+ - lib/papercraft/xml.rb
144
146
  - papercraft.png
145
147
  homepage: http://github.com/digital-fabric/papercraft
146
148
  licenses:
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Papercraft
4
- # Papercraft::Encoding includes common encoding methods
5
- module Encoding
6
- # Encodes the given string to safe HTML text, converting special characters
7
- # into the respective HTML entities. If a non-string value is given, it is
8
- # converted to `String` using `#to_s`.
9
- #
10
- # @param text [String] string to be encoded
11
- # @return [String] HTML-encoded string
12
- def __html_encode__(text)
13
- EscapeUtils.escape_html(text.to_s)
14
- end
15
-
16
- # Encodes the given string to safe URI component, converting special
17
- # characters to URI entities. If a non-string value is given, it is
18
- # converted to `String` using `#to_s`.
19
- #
20
- # @param text [String] string to be encoded
21
- # @return [String] URI-encoded string
22
- def __uri_encode__(text)
23
- EscapeUtils.escape_uri(text.to_s)
24
- end
25
- end
26
- end