papercraft 0.11 → 0.15

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: 3452d115d85742018d1497b369ea27970f0c144e3dcb1772f4d81e33315dce51
4
- data.tar.gz: e7217a9d9bcfd7af0dc92482650bc03392b0406cf4f3d6141c253a2918638545
3
+ metadata.gz: 2200153b1b339d33c7e9e81c45c23bd4c117c274a52b1d1bfc97f83995727697
4
+ data.tar.gz: 3dae3cf3b6ea2530df8c498cdafb876e06d45d527b6e2bbbcb6302e6ef0842e9
5
5
  SHA512:
6
- metadata.gz: 8f76b9e73aa48f91af932af3308d3dd43239c3d5ca39e305cb1535417b5997483aa884f0e34ed128800b07a3135ebc1d18ea81d0df64e6170726a66c86969288
7
- data.tar.gz: dbe744ca25996c3d263ff609ca26f42076784fdbceabd2dd1604dd1016e2d840830230f4205a673b9e5ba14eb8e480627612971b71ca6631e0a6e473d59ccb73
6
+ metadata.gz: 5f9727de69aac6d48de53ca174280c68dce7825b9a4aa6f8c955b6122f30d9f4541a7d47e7ed7caa319feda5659b14b73a3ad343db2733b4bb9518e9ade94f8c
7
+ data.tar.gz: 765e786959a6fdda6c25cddda11f5f987837d6853245e5bc8533dbe7b904792b09fc39e726371c81981275ef0abdb8d1d95de63a76889af34b037f34d01c6c3a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 0.15 2022-01-20
2
+
3
+ - Fix tag method line reference
4
+ - Don't clobber ArgumentError exception
5
+
6
+ ## 0.14 2022-01-19
7
+
8
+ - Add support for #emit_yield in applied component (#4)
9
+
10
+ ## 0.13 2022-01-19
11
+
12
+ - Add support for partial parameter application (#3)
13
+
14
+ ## 0.12 2022-01-06
15
+
16
+ - Improve documentation
17
+ - Add `Renderer#tag` method
18
+ - Add `HTML#style`, `HTML#script` methods
19
+
1
20
  ## 0.11 2022-01-04
2
21
 
3
22
  - Add deferred evaluation
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  <h1 align="center">
2
+ <img src="papercraft.png">
3
+ <br>
2
4
  Papercraft
3
5
  </h1>
4
6
 
@@ -20,8 +22,6 @@
20
22
  <a href="https://www.rubydoc.info/gems/papercraft">API reference</a>
21
23
  </p>
22
24
 
23
- # Papercraft - Composable HTML templating for Ruby
24
-
25
25
  ## What is Papercraft?
26
26
 
27
27
  ```ruby
@@ -368,7 +368,7 @@ The default Kramdown options are:
368
368
  ```
369
369
 
370
370
  The deafult options can be configured by accessing
371
- `Papercraft::HTML.kramdown_options`:
371
+ `Papercraft::HTML.kramdown_options`, e.g.:
372
372
 
373
373
  ```ruby
374
374
  Papercraft::HTML.kramdown_options[:auto_ids] = false
@@ -430,7 +430,7 @@ page = default_layout.apply {
430
430
 
431
431
  Papercraft extensions are modules that contain one or more methods that can be
432
432
  used to render complex HTML components. Extension modules can be used by
433
- installing them as a namespaced extension using `Papercraft.extension`.
433
+ installing them as a namespaced extension using `Papercraft::extension`.
434
434
  Extensions are particularly useful when you work with CSS frameworks such as
435
435
  [Bootstrap](https://getbootstrap.com/), [Tailwind](https://tailwindui.com/) or
436
436
  [Primer](https://primer.style/).
@@ -476,7 +476,7 @@ end
476
476
  Papercraft.extension(bootstrap: BootstrapComponents)
477
477
  ```
478
478
 
479
- The call to `Papercraft.extension` lets us access the different methods of
479
+ The call to `Papercraft::extension` lets us access the different methods of
480
480
  `BootstrapComponents` by calling `#bootstrap` inside a template. With this,
481
481
  we'll be able to express the above markup as follows:
482
482
 
@@ -106,14 +106,9 @@ module Papercraft
106
106
  template = self
107
107
  Renderer.verify_proc_parameters(template, a, b)
108
108
  renderer_class.new do
109
- if block
110
- with_block(block) { instance_exec(*a, **b, &template) }
111
- else
112
- instance_exec(*a, **b, &template)
113
- end
109
+ push_emit_yield_block(block) if block
110
+ instance_exec(*a, **b, &template)
114
111
  end.to_s
115
- rescue ArgumentError => e
116
- raise Papercraft::Error, e.message
117
112
  end
118
113
 
119
114
  # Creates a new component, applying the given parameters and or block to the
@@ -136,15 +131,10 @@ module Papercraft
136
131
  # @return [Papercraft::Component] applied component
137
132
  def apply(*a, **b, &block)
138
133
  template = self
139
- if block
140
- Component.new(&proc do |*x, **y|
141
- with_block(block) { instance_exec(*x, **y, &template) }
142
- end)
143
- else
144
- Component.new(&proc do |*x, **y|
145
- instance_exec(*a, **b, &template)
146
- end)
147
- end
134
+ Component.new(&proc do |*x, **y|
135
+ push_emit_yield_block(block) if block
136
+ instance_exec(*a, *x, **b, **y, &template)
137
+ end)
148
138
  end
149
139
 
150
140
  # Returns the Renderer class used for rendering the templates, according to
@@ -44,15 +44,53 @@ module Papercraft
44
44
  link(**attributes)
45
45
  end
46
46
 
47
- def emit_markdown(markdown, **opts)
48
- emit Kramdown::Document.new(markdown, **kramdown_options(opts)).to_html
47
+ # Emits an inline CSS style element.
48
+ #
49
+ # @param css [String] CSS code
50
+ # @param **props [Hash] optional element attributes
51
+ # @return [void]
52
+ def style(css, **props, &block)
53
+ @buffer << '<style'
54
+ emit_props(props) unless props.empty?
55
+
56
+ @buffer << '>' << css << '</style>'
49
57
  end
58
+
59
+ # Emits an inline JS script element.
60
+ #
61
+ # @param js [String, nil] Javascript code
62
+ # @param **props [Hash] optional element attributes
63
+ # @return [void]
64
+ def script(js = nil, **props, &block)
65
+ @buffer << '<script'
66
+ emit_props(props) unless props.empty?
50
67
 
51
- def kramdown_options(opts)
52
- HTML.kramdown_options.merge(**opts)
68
+ if js
69
+ @buffer << '>' << js << '</script>'
70
+ else
71
+ @buffer << '></script>'
72
+ end
73
+ end
74
+
75
+ # Converts and emits the given markdown. Papercraft uses
76
+ # [Kramdown](https://github.com/gettalong/kramdown/) to do the Markdown to
77
+ # HTML conversion. Optional Kramdown settings can be provided in order to
78
+ # control the conversion. Those are merged with the default Kramdown
79
+ # settings, which can be controlled using
80
+ # `Papercraft::HTML.kramdown_options`.
81
+ #
82
+ # @param markdown [String] Markdown content
83
+ # @param **opts [Hash] Kramdown options
84
+ # @return [void]
85
+ def emit_markdown(markdown, **opts)
86
+ emit Kramdown::Document.new(markdown, **kramdown_options(opts)).to_html
53
87
  end
54
88
 
55
89
  class << self
90
+ # Returns the default Kramdown options used for converting Markdown to
91
+ # HTML.
92
+ #
93
+ # @return [Hash] Default Kramdown options
56
94
  def kramdown_options
57
95
  @kramdown_options ||= {
58
96
  entity_output: :numeric,
@@ -62,9 +100,24 @@ module Papercraft
62
100
  }
63
101
  end
64
102
 
103
+ # Sets the default Kramdown options used for converting Markdown to
104
+ # HTML.
105
+ #
106
+ # @param opts [Hash] New deafult Kramdown options
107
+ # @return [Hash] New default Kramdown options
65
108
  def kramdown_options=(opts)
66
109
  @kramdown_options = opts
67
110
  end
68
111
  end
112
+
113
+ private
114
+
115
+ # Returns the default Kramdown options, merged with the given overrides.
116
+ #
117
+ # @param opts [Hash] Kramdown option overrides
118
+ # @return [Hash] Merged Kramdown options
119
+ def kramdown_options(opts)
120
+ HTML.kramdown_options.merge(**opts)
121
+ end
69
122
  end
70
123
  end
@@ -34,8 +34,29 @@ module Papercraft
34
34
  end
35
35
  end
36
36
 
37
- # Installs the given extensions, mapping a method name to the extension
38
- # module.
37
+ # call_seq:
38
+ # Papercraft::Renderer.extension(name => mod, ...)
39
+ # Papercraft.extension(name => mod, ...)
40
+ #
41
+ # Installs the given extensions, passed in the form of a Ruby hash mapping
42
+ # methods to extension modules. The methods will be available to all
43
+ # Papercraft components. Extension methods are executed in the context of
44
+ # the the renderer instance, so they can look just like normal proc
45
+ # components. In cases where method names in the module clash with HTML
46
+ # tag names, you can use the `#tag` method to emit the relevant tag.
47
+ #
48
+ # module ComponentLibrary
49
+ # def card(title, content)
50
+ # div(class: 'card') {
51
+ # h3 title
52
+ # div(class: 'card-content') { emit_markdown content }
53
+ # }
54
+ # end
55
+ # end
56
+ #
57
+ # Papercraft.extension(components: ComponentLibrary)
58
+ # H { components.card('Foo', '**Bar**') }
59
+ #
39
60
  # @param map [Hash] hash mapping methods to extension modules
40
61
  # @return [void]
41
62
  def extension(map)
@@ -89,10 +110,12 @@ module Papercraft
89
110
  @buffer
90
111
  end
91
112
 
92
- S_TAG_METHOD_LINE = __LINE__ + 1
113
+ # The tag method template below is optimized for performance. Do not touch!
114
+
115
+ S_TAG_METHOD_LINE = __LINE__ + 2
93
116
  S_TAG_METHOD = <<~EOF
94
- S_TAG_%<TAG>s_PRE = '<%<tag>s'.tr('_', '-')
95
- S_TAG_%<TAG>s_CLOSE = '</%<tag>s>'.tr('_', '-')
117
+ S_TAG_%<TAG>s_PRE = %<tag_pre>s
118
+ S_TAG_%<TAG>s_CLOSE = %<tag_close>s
96
119
 
97
120
  def %<tag>s(text = nil, **props, &block)
98
121
  if text.is_a?(Hash) && props.empty?
@@ -119,6 +142,48 @@ module Papercraft
119
142
  end
120
143
  EOF
121
144
 
145
+ # Emits an HTML tag with the given content, properties and optional block.
146
+ # This method is an alternative to emitting HTML tags using dynamically
147
+ # created methods. This is particularly useful when using extensions that
148
+ # have method names that clash with HTML tags, such as `button` or `a`, or
149
+ # when you need to override the behaviour of a particular HTML tag.
150
+ #
151
+ # The following two method calls have the same effect:
152
+ #
153
+ # button 'text', id: 'button1'
154
+ # tag :button, 'text', id: 'button1'
155
+ #
156
+ # @param sym [Symbol, String] HTML tag
157
+ # @param text [String, nil] tag content
158
+ # @param **props [Hash] tag attributes
159
+ # @param &block [Proc] optional inner HTML
160
+ # @return [void]
161
+ def tag(sym, text = nil, **props, &block)
162
+ if text.is_a?(Hash) && props.empty?
163
+ props = text
164
+ text = nil
165
+ end
166
+
167
+ tag = sym.to_s.tr('_', '-')
168
+
169
+ @buffer << S_LT << tag
170
+ emit_props(props) unless props.empty?
171
+
172
+ if block
173
+ @buffer << S_GT
174
+ instance_eval(&block)
175
+ @buffer << S_LT_SLASH << tag << S_GT
176
+ elsif Proc === text
177
+ @buffer << S_GT
178
+ emit(text)
179
+ @buffer << S_LT_SLASH << tag << S_GT
180
+ elsif text
181
+ @buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
182
+ else
183
+ @buffer << S_SLASH_GT
184
+ end
185
+ end
186
+
122
187
  # Catches undefined tag method call and handles it by defining the method.
123
188
  #
124
189
  # @param sym [Symbol] HTML tag or component identifier
@@ -128,7 +193,12 @@ module Papercraft
128
193
  # @return [void]
129
194
  def method_missing(sym, *args, **opts, &block)
130
195
  tag = sym.to_s
131
- code = S_TAG_METHOD % { tag: tag, TAG: tag.upcase }
196
+ code = S_TAG_METHOD % {
197
+ tag: tag,
198
+ TAG: tag.upcase,
199
+ tag_pre: "<#{tag.tr('_', '-')}".inspect,
200
+ tag_close: "</#{tag.tr('_', '-')}>".inspect
201
+ }
132
202
  self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
133
203
  send(sym, *args, **opts, &block)
134
204
  end
@@ -172,9 +242,10 @@ module Papercraft
172
242
  # @param **b [Hash] named arguments to pass to a proc
173
243
  # @return [void]
174
244
  def emit_yield(*a, **b)
175
- raise Papercraft::Error, "No block given" unless @inner_block
245
+ block = @emit_yield_stack&.pop
246
+ raise Papercraft::Error, "No block given" unless block
176
247
 
177
- instance_exec(*a, **b, &@inner_block)
248
+ instance_exec(*a, **b, &block)
178
249
  end
179
250
 
180
251
  # Defers the given block to be evaluated later. Deferred evaluation allows
@@ -234,19 +305,19 @@ module Papercraft
234
305
  private
235
306
 
236
307
  # Escapes text. This method must be overriden in descendant classes.
308
+ #
309
+ # @param text [String] text to be escaped
237
310
  def escape_text(text)
238
311
  raise NotImplementedError
239
312
  end
240
313
 
241
- # Sets up a block to be called with `#emit_yield`
242
- def with_block(block, &run_block)
243
- old_block = @inner_block
244
- @inner_block = block
245
- instance_eval(&run_block)
246
- ensure
247
- @inner_block = old_block
314
+ # Pushes the given block onto the emit_yield stack.
315
+ #
316
+ # @param block [Proc] block
317
+ def push_emit_yield_block(block)
318
+ (@emit_yield_stack ||= []) << block
248
319
  end
249
-
320
+
250
321
  # Emits tag attributes into the rendering buffer
251
322
  # @param props [Hash] tag attributes
252
323
  # @return [void]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '0.11'
4
+ VERSION = '0.15'
5
5
  end
data/lib/papercraft.rb CHANGED
@@ -16,6 +16,12 @@ module Papercraft
16
16
  # by adding namespaced methods to emplates. An extension is implemented as a
17
17
  # Ruby module containing one or more methods. Each method in the extension
18
18
  # module can be used to render a specific HTML element or a set of elements.
19
+ #
20
+ # This is a convenience method. For more information on using Papercraft
21
+ # extensions, see `Papercraft::Renderer::extension`
22
+ #
23
+ # @param map [Hash] hash mapping methods to extension modules
24
+ # @return [void]
19
25
  def self.extension(map)
20
26
  Renderer.extension(map)
21
27
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: papercraft
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.11'
4
+ version: '0.15'
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-01-04 00:00:00.000000000 Z
11
+ date: 2022-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '='
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.2.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '='
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.2.1
27
27
  - !ruby/object:Gem::Dependency
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 2.3.0
33
+ version: 2.3.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 2.3.0
40
+ version: 2.3.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rouge
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 3.26.0
47
+ version: 3.27.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 3.26.0
54
+ version: 3.27.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: kramdown-parser-gfm
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -70,56 +70,56 @@ dependencies:
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '='
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 5.11.3
75
+ version: '5.15'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '='
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 5.11.3
82
+ version: '5.15'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: benchmark-ips
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '='
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: 2.7.2
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '='
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 2.7.2
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: erubis
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '='
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
103
  version: 2.7.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '='
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: 2.7.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: tilt
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - '='
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
117
  version: 2.0.9
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - '='
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: 2.0.9
125
125
  description: