papercraft 0.8.2 → 0.10.1

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: 309174da790e301142b03cc8aad3e966c6b1482d514da8d3910c2e84f6981af6
4
- data.tar.gz: 64fb5b791fce05c0347ba21ccf70160323f70ad708ece9c14222b8544efdf8e0
3
+ metadata.gz: 757137ea8d3a2b5d5d329b7e841bad13cc26ea3486ebcb216f1ef016d7d3ff4a
4
+ data.tar.gz: 281ee3b2911b1f4e1ff3337dbefef407ce07b17e21acb949dce158a93d8758a5
5
5
  SHA512:
6
- metadata.gz: d1f56c25e4854cb6b0ffce1d149d831d51d4141159a44fd6a962c27d9e0878b33e9438d9ad621c0c30a42dfa853b96a26b20c6ebe42243844be2b42d4e2901ef
7
- data.tar.gz: 46952658d3e237f784cfd9b608c82b001fdd188b788c931f0a1a3669d5800d8ed3ef89623f88ff9a617237fbfbbd3a0f9f5b30bfc693b2e6f0b5659f66afa41b
6
+ metadata.gz: 22225498fce014a17962541bac3c17f20243239b709e191e75fa2f1abbc0f7f7fdef3b0c4d110753f0de1c2ca4efc19f516cb797a02cbafa70af95085449096e
7
+ data.tar.gz: 57bb7aae102900de14ba9d8f4a0e2be83978e5106ff098ffda5acf39431273fd76be646d012da72094715ad8a3da7fd81a485e51151c74df94091a91e72866f4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.10.1 2021-12-25
2
+
3
+ - Fix tag rendering with empty text in Ruby 3.0
4
+
5
+ ## 0.10 2021-12-25
6
+
7
+ - Add support for extensions
8
+
9
+ ## 0.9 2021-12-23
10
+
11
+ - Add support for emitting Markdown
12
+ - Add support for passing proc as argument to `#H` and `#X`
13
+ - Deprecate `Encoding` module
14
+
1
15
  ## 0.8.1 2021-12-22
2
16
 
3
17
  - Fix gemspec
data/README.md CHANGED
@@ -1,30 +1,56 @@
1
- # Papercraft - Composable HTML templating for Ruby
1
+ <h1 align="center">
2
+ Papercraft
3
+ </h1>
4
+
5
+ <h4 align="center">Composable HTML templating for Ruby</h4>
6
+
7
+ <p align="center">
8
+ <a href="http://rubygems.org/gems/papercraft">
9
+ <img src="https://badge.fury.io/rb/papercraft.svg" alt="Ruby gem">
10
+ </a>
11
+ <a href="https://github.com/digital-fabric/papercraft/actions?query=workflow%3ATests">
12
+ <img src="https://github.com/digital-fabric/papercraft/workflows/Tests/badge.svg" alt="Tests">
13
+ </a>
14
+ <a href="https://github.com/digital-fabric/papercraft/blob/master/LICENSE">
15
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
16
+ </a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://www.rubydoc.info/gems/papercraft">API reference</a>
21
+ </p>
2
22
 
3
- [INSTALL](#installing-papercraft) |
4
- [TUTORIAL](#getting-started) |
5
- [EXAMPLES](examples) |
6
- [REFERENCE](#api-reference)
23
+ # Papercraft - Composable HTML templating for Ruby
7
24
 
8
25
  ## What is Papercraft?
9
26
 
27
+ ```ruby
28
+ require 'papercraft'
29
+
30
+ page = H { |*args|
31
+ html {
32
+ head { }
33
+ body { emit_yield *args }
34
+ }
35
+ }
36
+
37
+ hello = H.apply { |name| h1 "Hello, #{name}!" }
38
+ hello.render('world')
39
+ #=> "<html><head/><body><h1>Hello, world!</h1></body></html>"
40
+ ```
41
+
10
42
  Papercraft is an HTML templating engine for Ruby that offers the following
11
43
  features:
12
44
 
13
- - HTML templating using plain Ruby syntax
45
+ - HTML and XML templating using plain Ruby syntax
14
46
  - Minimal boilerplate
15
47
  - Mix logic and tags freely
16
- - Use global and local contexts to pass values to reusable components
17
- - Automatic HTML escaping
48
+ - Automatic HTML and XML escaping
18
49
  - Composable components
50
+ - Explicit parameter passing to nested components
19
51
  - Higher order components
20
- - Built-in support for rendering Markdown
21
-
22
- > **Note** Papercraft is a new library and as such may be missing features and
23
- > contain bugs. Also, its API may change unexpectedly. Your issue reports and
24
- > code contributions are most welcome!
25
-
26
- With Papercraft you can structure your templates as nested HTML components, in a
27
- somewhat similar fashion to React.
52
+ - Built-in support for rendering [Markdown](#emitting-markdown)
53
+ - Support for namespaced extensions
28
54
 
29
55
  ## Installing Papercraft
30
56
 
@@ -42,42 +68,25 @@ $ gem install papercraft
42
68
 
43
69
  ## Getting started
44
70
 
45
- To use Papercraft in your code just require it:
71
+ To create a template use the global method `Kernel#H`:
46
72
 
47
73
  ```ruby
48
74
  require 'papercraft'
49
- ```
50
-
51
- To create a template use `Papercraft.new` or the global method `Kernel#H`:
52
75
 
53
- ```ruby
54
- # can also use Papercraft.new
55
76
  html = H {
56
- div { p 'hello' }
77
+ div(id: 'greeter') { p 'Hello!' }
57
78
  }
58
79
  ```
59
80
 
60
- ## Rendering a template
61
-
62
- To render a Papercraft template use the `#render` method:
81
+ Rendering a template is done using `#render`:
63
82
 
64
83
  ```ruby
65
- H { span 'best span' }.render #=> "<span>best span</span>"
66
- ```
67
-
68
- The render method accepts an arbitrary context variable:
69
-
70
- ```ruby
71
- html = H {
72
- h1 context[:title]
73
- }
74
-
75
- html.render(title: 'My title') #=> "<h1>My title</h1>"
84
+ html.render #=> "<div id="greeter"><p>Hello!</p></div>"
76
85
  ```
77
86
 
78
87
  ## All about tags
79
88
 
80
- Tags are added using unqualified method calls, and are nested using blocks:
89
+ Tags are added using unqualified method calls, and can be nested using blocks:
81
90
 
82
91
  ```ruby
83
92
  H {
@@ -114,8 +123,9 @@ H { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</
114
123
 
115
124
  ## Template parameters
116
125
 
117
- Template parameters are specified as block parameters, and are passed to the
118
- template on rendering:
126
+ In Papercraft, parameters are always passed explicitly. This means that template
127
+ parameters are specified as block parameters, and are passed to the template on
128
+ rendering:
119
129
 
120
130
  ```ruby
121
131
  greeting = H { |name| h1 "Hello, #{name}!" }
@@ -328,110 +338,109 @@ H { str 'hi&lo' }.render #=> "hi&amp;lo"
328
338
 
329
339
  ## Emitting Markdown
330
340
 
331
- To emit Markdown, use `#emit_markdown`:
341
+ Markdown is rendered using the
342
+ [Kramdown](https://kramdown.gettalong.org/index.html) gem. To emit Markdown, use
343
+ `#emit_markdown`:
332
344
 
333
345
  ```ruby
334
346
  template = H { |md| div { emit_markdown md } }
335
- template.render("Here's some *Markdown*") #=> "<div>Here's some <em>Markdown</em></div>"
347
+ template.render("Here's some *Markdown*") #=> "<div><p>Here's some <em>Markdown</em><p>\n</div>"
336
348
  ```
337
349
 
338
- ## Some interesting use cases
339
-
340
- Papercraft opens up all kinds of new possibilities when it comes to putting
341
- together pieces of HTML. Feel free to explore the API!
350
+ [Kramdown
351
+ options](https://kramdown.gettalong.org/options.html#available-options) can be
352
+ specified by adding them to the `#emit_markdown` call:
342
353
 
343
- ### A higher-order list component
354
+ ```ruby
355
+ template = H { |md| div { emit_markdown md, auto_ids: false } }
356
+ template.render("# title") #=> "<div><h1>title</h1></div>"
357
+ ```
344
358
 
345
- Here's another demonstration of a higher-order component, a list component that
346
- takes an item component as an argument. The `List` component can be reused for
347
- rendering any kind of unordered list, and with any kind of item component:
359
+ The default Kramdown options are:
348
360
 
349
361
  ```ruby
350
- List = ->(items, item_component) {
351
- H {
352
- ul {
353
- items.each { |item|
354
- with(item: item) {
355
- li { emit item_component }
356
- }
357
- }
358
- }
359
- }
360
- }
361
-
362
- TodoItem = H {
363
- span item.text, class: item.completed ? 'completed' : 'pending'
362
+ {
363
+ entity_output: :numeric,
364
+ syntax_highlighter: :rouge,
365
+ input: 'GFM',
366
+ hard_wrap: false
364
367
  }
365
-
366
- def todo_list(items)
367
- H {
368
- div { List(items, TodoItem) }
369
- }
370
- end
371
368
  ```
372
369
 
373
- ## API Reference
374
-
375
- #### `Papercraft#initialize(**context, &block)` a.k.a. `Kernel#H`
376
-
377
- - `context`: local context hash
378
- - `block`: template block
379
-
380
- Initializes a new Papercraft instance. This method takes a block of template
381
- code, and an optional [local context](#local-context) in the form of a hash.
382
- The `Kernel#H` method serves as a shortcut for creating Papercraft instances.
383
-
384
- #### `Papercraft#render(**context)`
385
-
386
- - `context`: global context hash
387
-
388
- Renders the template with an optional [global context](#global-context)
389
- hash.
390
-
391
- #### Methods accessible inside template blocks
392
-
393
- #### `#<tag/component>(*args, **props, &block)`
394
-
395
- - `args`: tag arguments. For an HTML tag Papercraft expects a single `String`
396
- argument containing the inner text of the tag.
397
- - `props`: hash of tag attributes
398
- - `block`: inner HTML block
399
-
400
- Adds a tag or component to the current template. If the method name starts with
401
- an upper-case letter, it is considered a [component](#templates-as-components).
402
-
403
- If a text argument is given for a tag, it will be escaped.
404
-
405
- #### `#cache(*vary, &block)`
406
-
407
- - `vary`: variables used in cached block. The given values will be used to
408
- create a separate cache entry.
409
- - `block`: inner HTML block
370
+ The deafult options can be configured by accessing
371
+ `Papercraft::HTML.kramdown_options`:
410
372
 
411
- Caches the markup in the given block, storing it in the Papercraft cache store.
412
- If a cache entry for the given block is found, it will be used instead of
413
- invoking the block. If one or more variables given, those will be used to create
414
- a separate cache entry.
373
+ ```ruby
374
+ Papercraft::HTML.kramdown_options[:auto_ids] = false
375
+ ```
415
376
 
416
- #### `#context`
377
+ ## Papercraft extensions
378
+
379
+ Papercraft extensions are modules that contain one or more methods that can be
380
+ used to render complex HTML components. Extension modules can be used by
381
+ installing them as a namespaced extension using `Papercraft.extension`.
382
+ Extensions are particularly useful when you work with CSS frameworks such as
383
+ [Bootstrap](https://getbootstrap.com/), [Tailwind](https://tailwindui.com/) or
384
+ [Primer](https://primer.style/).
385
+
386
+ For example, to create a Bootstrap card component, the following HTML markup is
387
+ needed (example taken from the [Bootstrap
388
+ docs](https://getbootstrap.com/docs/5.1/components/card/#titles-text-and-links)):
389
+
390
+ ```html
391
+ <div class="card" style="width: 18rem;">
392
+ <div class="card-body">
393
+ <h5 class="card-title">Card title</h5>
394
+ <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
395
+ <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
396
+ <a href="#" class="card-link">Card link</a>
397
+ <a href="#" class="card-link">Another link</a>
398
+ </div>
399
+ </div>
400
+ ```
417
401
 
418
- Accesses the [global context](#global-context).
402
+ With Papercraft, we could create a `Bootstrap` extension with a `#card` method
403
+ and other associated methods:
419
404
 
420
- #### `#emit(object)` a.k.a. `#e(object)`
405
+ ```ruby
406
+ module BootstrapComponents
407
+ ...
421
408
 
422
- - `object`: `Proc`, `Papercraft` instance or `String`
409
+ def card(**props)
410
+ div(class: 'card', **props) {
411
+ div(class: 'card-body') {
412
+ emit_yield
413
+ }
414
+ }
415
+ end
423
416
 
424
- Adds the given object to the current template. If a `String` is given, it is
425
- rendered verbatim, i.e. without escaping.
417
+ def card_title(title)
418
+ h5 title, class: 'card-title'
419
+ end
426
420
 
427
- #### `html5(&block)`
421
+ ...
422
+ end
428
423
 
429
- - `block`: inner HTML block
424
+ Papercraft.extension(bootstrap: BootstrapComponents)
425
+ ```
430
426
 
431
- Adds an HTML5 `doctype` tag, followed by an `html` tag with the given block.
427
+ The call to `Papercraft.extension` lets us access the different methods of
428
+ `BootstrapComponents` by calling `#bootstrap` inside a template. With this,
429
+ we'll be able to express the above markup as follows:
432
430
 
433
- #### `#text(data)`
431
+ ```ruby
432
+ H {
433
+ bootstrap.card(style: 'width: 18rem') {
434
+ bootstrap.card_title 'Card title'
435
+ bootstrap.card_subtitle 'Card subtitle'
436
+ bootstrap.card_text 'Some quick example text to build on the card title and make up the bulk of the card''s content.'
437
+ bootstrap.card_link '#', 'Card link'
438
+ bootstrap.card_link '#', 'Another link'
439
+ }
440
+ }
441
+ ```
434
442
 
435
- - `data` - text to add
443
+ ## API Reference
436
444
 
437
- Adds text without wrapping it in a tag. The text will be escaped.
445
+ The API reference for this library can be found
446
+ [here](https://www.rubydoc.info/gems/papercraft).
@@ -14,8 +14,8 @@ module Papercraft
14
14
  # Components are usually created using the global methods `H` or `X`, for HTML
15
15
  # or XML templates, respectively:
16
16
  #
17
- # greeter = H { |name| h1 "Hello, #{name}!" } greeter.render('world') #=>
18
- # "<h1>Hello, world!</h1>"
17
+ # greeter = H { |name| h1 "Hello, #{name}!" }
18
+ # greeter.render('world') #=> "<h1>Hello, world!</h1>"
19
19
  #
20
20
  # Components can also be created using the normal constructor:
21
21
  #
@@ -25,7 +25,10 @@ module Papercraft
25
25
  # In the component block, HTML elements are created by simply calling
26
26
  # unqualified methods:
27
27
  #
28
- # page_layout = H { html5 { head { title 'foo'
28
+ # page_layout = H {
29
+ # html5 {
30
+ # head {
31
+ # title 'foo'
29
32
  # }
30
33
  # body {
31
34
  # h1 "Hello, world!"
@@ -75,7 +78,11 @@ module Papercraft
75
78
  # anchor.apply(uri: '/about', text: 'About')
76
79
  # ])
77
80
  # links_with_anchors.render
81
+ #
78
82
  class Component < Proc
83
+
84
+ # Determines the rendering mode: `:html` or `:xml`.
85
+ attr_accessor :mode
79
86
 
80
87
  # Initializes a component with the given block. The rendering mode (HTML or
81
88
  # XML) can be passed in the `mode:` parameter. If `mode:` is not specified,
@@ -113,15 +120,15 @@ module Papercraft
113
120
  # current one. Application is one of the principal methods of composing
114
121
  # components, particularly when passing inner components as blocks:
115
122
  #
116
- # article_wrapper = H {
117
- # article {
118
- # emit_yield
123
+ # article_wrapper = H {
124
+ # article {
125
+ # emit_yield
126
+ # }
127
+ # }
128
+ # wrapped_article = article_wrapper.apply {
129
+ # h1 'Article title'
119
130
  # }
120
- # }
121
- # wrapped_article = article_wrapper.apply {
122
- # h1 'Article title'
123
- # }
124
- # wrapped_article.render #=> "<article><h1>Article title</h1></article>"
131
+ # wrapped_article.render #=> "<article><h1>Article title</h1></article>"
125
132
  #
126
133
  # @param *a [<any>] normal parameters
127
134
  # @param **b [Hash] named parameters
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Papercraft
4
+
5
+ # An ExtensionProxy proxies method calls to a renderer. Extension proxies are
6
+ # used to provide an namespaced interface to Papercraft extensions. When an
7
+ # extension is installed using `Papercraft.extension`, a corresponding method
8
+ # is defined on the `Papercraft::Renderer` class that creates an extension
9
+ # proxy that gives access to the different extension methods.
10
+ class ExtensionProxy
11
+
12
+ # Initializes a new ExtensionProxy.
13
+ # @param renderer [Papercraft::Renderer] renderer to proxy to
14
+ # @param mod [Module] extension module
15
+ # @return [void]
16
+ def initialize(renderer, mod)
17
+ @renderer = renderer
18
+ extend(mod)
19
+ end
20
+
21
+ # Proxies missing methods to the renderer
22
+ # @param sym [Symbol] method name
23
+ # @param *args [Array] arguments
24
+ # @param &block [Proc] block
25
+ # @return void
26
+ def method_missing(sym, *args, &block)
27
+ @renderer.send(sym, *args, &block)
28
+ end
29
+ end
30
+ end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './html'
3
+ require 'kramdown'
4
+ require 'rouge'
5
+ require 'kramdown-parser-gfm'
4
6
 
5
- module Papercraft
6
- # Markup extensions
7
+ module Papercraft
8
+ # HTML Markup extensions
7
9
  module HTML
8
10
  # Emits the p tag (overrides Object#p)
11
+ #
9
12
  # @param text [String] text content of tag
10
13
  # @param props [Hash] tag attributes
11
14
  # @para block [Proc] nested HTML block
@@ -16,7 +19,8 @@ module Papercraft
16
19
 
17
20
  S_HTML5_DOCTYPE = '<!DOCTYPE html>'
18
21
 
19
- # Emits an HTML5 doctype tag and an html tag with the given block
22
+ # Emits an HTML5 doctype tag and an html tag with the given block.
23
+ #
20
24
  # @param block [Proc] nested HTML block
21
25
  # @return [void]
22
26
  def html5(&block)
@@ -24,6 +28,11 @@ module Papercraft
24
28
  self.html(&block)
25
29
  end
26
30
 
31
+ # Emits a link element with a stylesheet.
32
+ #
33
+ # @param href [String] stylesheet URL
34
+ # @param custom_attributes [Hash] optional custom attributes for the link element
35
+ # @return [void]
27
36
  def link_stylesheet(href, custom_attributes = nil)
28
37
  attributes = {
29
38
  rel: 'stylesheet',
@@ -34,5 +43,28 @@ module Papercraft
34
43
  end
35
44
  link(**attributes)
36
45
  end
46
+
47
+ def emit_markdown(markdown, **opts)
48
+ emit Kramdown::Document.new(markdown, **kramdown_options(opts)).to_html
49
+ end
50
+
51
+ def kramdown_options(opts)
52
+ HTML.kramdown_options.merge(**opts)
53
+ end
54
+
55
+ class << self
56
+ def kramdown_options
57
+ @kramdown_options ||= {
58
+ entity_output: :numeric,
59
+ syntax_highlighter: :rouge,
60
+ input: 'GFM',
61
+ hard_wrap: false
62
+ }
63
+ end
64
+
65
+ def kramdown_options=(opts)
66
+ @kramdown_options = opts
67
+ end
68
+ end
37
69
  end
38
70
  end
@@ -1,11 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative './html'
4
+ require_relative './extension_proxy'
4
5
 
5
6
  module Papercraft
7
+
6
8
  # A Renderer renders a Papercraft component into a string
7
9
  class Renderer
10
+
8
11
  class << self
12
+
13
+ # Verifies that the given template proc can be called with the given
14
+ # arguments and named arguments. If the proc demands named argument keys
15
+ # that do not exist in `named_args`, `Papercraft::Error` is raised.
16
+ #
17
+ # @param template [Proc] proc to verify
18
+ # @param args [Array<any>] arguments passed to proc
19
+ # @param named_args [Hash] named arguments passed to proc
9
20
  def verify_proc_parameters(template, args, named_args)
10
21
  param_count = 0
11
22
  template.parameters.each do |(type, name)|
@@ -21,40 +32,58 @@ module Papercraft
21
32
  if param_count > args.size
22
33
  raise Papercraft::Error, "Missing template parameters"
23
34
  end
24
- end
25
- end
35
+ end
26
36
 
27
- attr_reader :context
37
+ # Installs the given extensions, mapping a method name to the extension
38
+ # module.
39
+ # @param map [Hash] hash mapping methods to extension modules
40
+ # @return [void]
41
+ def extension(map)
42
+ map.each do |sym, mod|
43
+ define_extension_method(sym, mod)
44
+ end
45
+ end
28
46
 
29
- # Initializes attributes and renders the given block
30
- # @param context [Hash] rendering context
31
- # @param block [Proc] template block
32
- # @return [void]
47
+ private
48
+
49
+ # Defines a method returning an extension proxy for the given module
50
+ # @param sym [Symbol] method name
51
+ # @param mod [Module] extension module
52
+ # @return [void]
53
+ def define_extension_method(sym, mod)
54
+ define_method(sym) do
55
+ (@extension_proxies ||= {})[mod] ||= ExtensionProxy.new(self, mod)
56
+ end
57
+ end
58
+ end
59
+
60
+ # Initializes the renderer and evaulates the given template in the
61
+ # renderer's scope.
62
+ #
63
+ # @param &template [Proc] template block
33
64
  def initialize(&template)
34
65
  @buffer = +''
35
66
  instance_eval(&template)
36
67
  end
37
68
 
38
- # Returns the result of the rendering
69
+ # Returns the rendered template.
70
+ #
39
71
  # @return [String]
40
72
  def to_s
41
73
  @buffer
42
74
  end
43
75
 
44
- def escape_text(text)
45
- raise NotImplementedError
46
- end
47
-
48
- def escape_uri(uri)
49
- EscapeUtils.escape_uri(v)
50
- end
51
-
52
76
  S_TAG_METHOD_LINE = __LINE__ + 1
53
77
  S_TAG_METHOD = <<~EOF
54
78
  S_TAG_%<TAG>s_PRE = '<%<tag>s'.tr('_', '-')
55
79
  S_TAG_%<TAG>s_CLOSE = '</%<tag>s>'.tr('_', '-')
56
80
 
57
81
  def %<tag>s(text = nil, **props, &block)
82
+ if text.is_a?(Hash) && props.empty?
83
+ props = text
84
+ text = nil
85
+ end
86
+
58
87
  @buffer << S_TAG_%<TAG>s_PRE
59
88
  emit_props(props) unless props.empty?
60
89
 
@@ -74,33 +103,42 @@ module Papercraft
74
103
  end
75
104
  EOF
76
105
 
77
- # Catches undefined tag method call and handles them by defining the method
106
+ # Catches undefined tag method call and handles it by defining the method.
107
+ #
78
108
  # @param sym [Symbol] HTML tag or component identifier
79
- # @param args [Array] method call arguments
80
- # @param block [Proc] block passed to method call
109
+ # @param args [Array] method arguments
110
+ # @param opts [Hash] named method arguments
111
+ # @param &block [Proc] block passed to method
81
112
  # @return [void]
82
113
  def method_missing(sym, *args, **opts, &block)
83
- value = @local && @local[sym]
84
- return value if value
85
-
86
114
  tag = sym.to_s
87
115
  code = S_TAG_METHOD % { tag: tag, TAG: tag.upcase }
88
116
  self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
89
117
  send(sym, *args, **opts, &block)
90
118
  end
91
119
 
92
- # Emits the given object into the rendering buffer
120
+ # Emits the given object into the rendering buffer. If the given object is a
121
+ # proc or a component, `emit` will passes any additional arguments and named
122
+ # arguments to the object when rendering it. If the given object is nil,
123
+ # nothing is emitted. Otherwise, the object is converted into a string using
124
+ # `#to_s` which is then added to the rendering buffer, without any escaping.
125
+ #
126
+ # greeter = proc { |name| h1 "Hello, #{name}!" }
127
+ # H { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
128
+ #
129
+ # H { emit 'hi&<bye>' }.render #=> "hi&<bye>"
130
+ #
131
+ # H { emit nil }.render #=> ""
132
+ #
93
133
  # @param o [Proc, Papercraft::Component, String] emitted object
134
+ # @param *a [Array<any>] arguments to pass to a proc
135
+ # @param **b [Hash] named arguments to pass to a proc
94
136
  # @return [void]
95
137
  def emit(o, *a, **b)
96
138
  case o
97
139
  when ::Proc
98
140
  Renderer.verify_proc_parameters(o, a, b)
99
141
  instance_exec(*a, **b, &o)
100
- # when Papercraft::Component
101
- # o = o.template
102
- # Renderer.verify_proc_parameters(o, a, b)
103
- # instance_exec(*a, **b, &o)
104
142
  when nil
105
143
  else
106
144
  @buffer << o.to_s
@@ -108,14 +146,15 @@ module Papercraft
108
146
  end
109
147
  alias_method :e, :emit
110
148
 
111
- def with_block(block, &run_block)
112
- old_block = @inner_block
113
- @inner_block = block
114
- instance_eval(&run_block)
115
- ensure
116
- @inner_block = old_block
117
- end
118
-
149
+ # Emits a block supplied using `Component#apply` or `Component#render`.
150
+ #
151
+ # div_wrap = H { |*args| div { emit_yield(*args) } }
152
+ # greeter = div_wrap.apply { |name| h1 "Hello, #{name}!" }
153
+ # greeter.render('world') #=> "<div><h1>Hello, world!</h1></div>"
154
+ #
155
+ # @param *a [Array<any>] arguments to pass to a proc
156
+ # @param **b [Hash] named arguments to pass to a proc
157
+ # @return [void]
119
158
  def emit_yield(*a, **b)
120
159
  raise Papercraft::Error, "No block given" unless @inner_block
121
160
 
@@ -131,6 +170,31 @@ module Papercraft
131
170
  S_EQUAL_QUOTE = '="'
132
171
  S_QUOTE = '"'
133
172
 
173
+ # Emits text into the rendering buffer, escaping any special characters to
174
+ # the respective HTML entities.
175
+ #
176
+ # @param data [String] text
177
+ # @return [void]
178
+ def text(data)
179
+ @buffer << escape_text(data)
180
+ end
181
+
182
+ private
183
+
184
+ # Escapes text. This method must be overriden in descendant classes.
185
+ def escape_text(text)
186
+ raise NotImplementedError
187
+ end
188
+
189
+ # Sets up a block to be called with `#emit_yield`
190
+ def with_block(block, &run_block)
191
+ old_block = @inner_block
192
+ @inner_block = block
193
+ instance_eval(&run_block)
194
+ ensure
195
+ @inner_block = old_block
196
+ end
197
+
134
198
  # Emits tag attributes into the rendering buffer
135
199
  # @param props [Hash] tag attributes
136
200
  # @return [void]
@@ -153,23 +217,25 @@ module Papercraft
153
217
  end
154
218
  }
155
219
  end
156
-
157
- # Emits text into the rendering buffer
158
- # @param data [String] text
159
- def text(data)
160
- @buffer << escape_text(data)
161
- end
162
220
  end
163
221
 
222
+ # Implements an HTML renderer
164
223
  class HTMLRenderer < Renderer
165
224
  include HTML
166
225
 
226
+ private
227
+
228
+ # Escapes the given text using HTML entities.
167
229
  def escape_text(text)
168
230
  EscapeUtils.escape_html(text.to_s)
169
231
  end
170
232
  end
171
233
 
234
+ # Implements an XML renderer
172
235
  class XMLRenderer < Renderer
236
+ private
237
+
238
+ # Escapes the given text using XML entities.
173
239
  def escape_text(text)
174
240
  EscapeUtils.escape_xml(text.to_s)
175
241
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Papercraft
4
- VERSION = '0.8.2'
4
+ VERSION = '0.10.1'
5
5
  end
data/lib/papercraft.rb CHANGED
@@ -11,24 +11,44 @@ require_relative 'papercraft/encoding'
11
11
  module Papercraft
12
12
  # Exception class used to signal templating-related errors
13
13
  class Error < RuntimeError; end
14
+
15
+ # Installs one or more extensions. Extensions enhance templating capabilities
16
+ # by adding namespaced methods to emplates. An extension is implemented as a
17
+ # Ruby module containing one or more methods. Each method in the extension
18
+ # module can be used to render a specific HTML element or a set of elements.
19
+ def self.extension(map)
20
+ Renderer.extension(map)
21
+ end
14
22
  end
15
23
 
16
24
  # Kernel extensions
17
25
  module ::Kernel
18
- # Convenience method for creating a new Papercraft
19
- # @param ctx [Hash] local context
26
+
27
+ # Creates a new papercraft component. `#H` can take either a proc argument or
28
+ # a block. In both cases, the proc is converted to a `Papercraft::Component`.
29
+ #
30
+ # H(proc { h1 'hi' }).render #=> "<h1>hi</h1>"
31
+ # H { h1 'hi' }.render #=> "<h1>hi</h1>"
32
+ #
20
33
  # @param template [Proc] template block
21
- # @return [Papercraft] Papercraft template
22
- def H(&template)
23
- Papercraft::Component.new(&template)
34
+ # @return [Papercraft::Component] Papercraft component
35
+ def H(o = nil, &template)
36
+ return o if o.is_a?(Papercraft::Component)
37
+ template ||= o
38
+ Papercraft::Component.new(mode: :html, &template)
24
39
  end
25
40
 
26
- def X(&template)
41
+ # Creates a new papercraft component in XML mode. `#X` can take either a proc argument or
42
+ # a block. In both cases, the proc is converted to a `Papercraft::Component`.
43
+ #
44
+ # X(proc { item 'foo' }).render #=> "<item>foo</item>"
45
+ # X { item 'foo' }.render #=> "<item>foo</item>"
46
+ #
47
+ # @param template [Proc] template block
48
+ # @return [Papercraft::Component] Papercraft component
49
+ def X(o = nil, &template)
50
+ return o if o.is_a?(Papercraft::Component)
51
+ template ||= o
27
52
  Papercraft::Component.new(mode: :xml, &template)
28
53
  end
29
54
  end
30
-
31
- # Object extensions
32
- class Object
33
- include Papercraft::Encoding
34
- end
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.8.2
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-23 00:00:00.000000000 Z
11
+ date: 2021-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils
@@ -24,6 +24,48 @@ dependencies:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: kramdown
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rouge
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.26.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.26.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: kramdown-parser-gfm
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.1.0
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: minitest
29
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +122,7 @@ dependencies:
80
122
  - - '='
81
123
  - !ruby/object:Gem::Version
82
124
  version: 2.0.9
83
- description:
125
+ description:
84
126
  email: sharon@noteflakes.com
85
127
  executables: []
86
128
  extensions: []
@@ -93,6 +135,7 @@ files:
93
135
  - lib/papercraft/compiler.rb
94
136
  - lib/papercraft/component.rb
95
137
  - lib/papercraft/encoding.rb
138
+ - lib/papercraft/extension_proxy.rb
96
139
  - lib/papercraft/html.rb
97
140
  - lib/papercraft/renderer.rb
98
141
  - lib/papercraft/version.rb
@@ -104,7 +147,7 @@ metadata:
104
147
  documentation_uri: https://www.rubydoc.info/gems/papercraft
105
148
  homepage_uri: https://github.com/digital-fabric/papercraft
106
149
  changelog_uri: https://github.com/digital-fabric/papercraft/blob/master/CHANGELOG.md
107
- post_install_message:
150
+ post_install_message:
108
151
  rdoc_options:
109
152
  - "--title"
110
153
  - Papercraft
@@ -123,8 +166,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
166
  - !ruby/object:Gem::Version
124
167
  version: '0'
125
168
  requirements: []
126
- rubygems_version: 3.1.6
127
- signing_key:
169
+ rubygems_version: 3.2.32
170
+ signing_key:
128
171
  specification_version: 4
129
172
  summary: 'Papercraft: component-based HTML templating for Ruby'
130
173
  test_files: []