papercraft 1.4 → 2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,41 +0,0 @@
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
- #
14
- # @param renderer [Papercraft::Renderer] renderer to proxy to
15
- # @param mod [Module] extension module
16
- # @return [void]
17
- def initialize(renderer, mod)
18
- @renderer = renderer
19
- extend(mod)
20
- end
21
-
22
- # Proxies missing methods to the renderer.
23
- #
24
- # @param sym [Symbol] method name
25
- # @param *args [Array] arguments
26
- # @param *props [Array] named arguments
27
- # @return void
28
- def method_missing(sym, *args, **props, &block)
29
- @renderer.send(sym, *args, **props, &block)
30
- end
31
-
32
- # Overrides the `Kernel#p` method to emit a p tag.
33
- #
34
- # @param *args [Array] arguments
35
- # @param *props [Array] named arguments
36
- # @return void
37
- def p(text = nil, **props, &block)
38
- @renderer.p(text, **props, &block)
39
- end
40
- end
41
- end
@@ -1,42 +0,0 @@
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
- # @return [void]
10
- def Envelope(**props, &block)
11
- props[:xmlns__soap] ||= 'http://schemas.xmlsoap.org/soap/envelope/'
12
- tag('soap:Envelope', **props, &block)
13
- end
14
-
15
- # Emits a SOAP XML tag that contains header information.
16
- #
17
- # @param **props [Hash] tag attributes
18
- # @return [void]
19
- def Header(**props, &blk)
20
- tag('soap:Header', **props, &blk)
21
- end
22
-
23
- # Emits a SOAP XML tag that contains header information.
24
- #
25
- # @param **props [Hash] tag attributes
26
- # @return [void]
27
- def Body(**props, &blk)
28
- tag('soap:Body', **props, &blk)
29
- end
30
-
31
- # Emits a SOAP XML tag that contains errors and status information.
32
- #
33
- # @param **props [Hash] tag attributes
34
- # @return [void]
35
- def Fault(**props, &blk)
36
- tag('soap:Fault', **props, &blk)
37
- end
38
- end
39
- end
40
- end
41
-
42
- Papercraft.extension(soap: Papercraft::Extensions::Soap)
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './tags'
4
- require 'cgi'
5
-
6
- module Papercraft
7
- # HTML Markup extensions
8
- module HTML
9
- include Tags
10
-
11
- # Emits the p tag (overrides Object#p)
12
- #
13
- # @param text [String] text content of tag
14
- # @param **attributes [Hash] tag attributes
15
- # @para block [Proc] nested HTML block
16
- # @return [void]
17
- def p(text = nil, **attributes, &block)
18
- method_missing(:p, text, **attributes, &block)
19
- end
20
-
21
- S_HTML5_DOCTYPE = '<!DOCTYPE html>'
22
-
23
- # Emits an HTML5 doctype tag and an html tag with the given block.
24
- #
25
- # @param block [Proc] nested HTML block
26
- # @return [void]
27
- def html5(&block)
28
- @buffer << S_HTML5_DOCTYPE
29
- self.html(&block)
30
- end
31
-
32
- # Emits a link element with a stylesheet.
33
- #
34
- # @param href [String] stylesheet URL
35
- # @param custom_attributes [Hash] optional custom attributes for the link element
36
- # @return [void]
37
- def link_stylesheet(href, custom_attributes = nil)
38
- attributes = {
39
- rel: 'stylesheet',
40
- href: href
41
- }
42
- if custom_attributes
43
- attributes = custom_attributes.merge(attributes)
44
- end
45
- link(**attributes)
46
- end
47
-
48
- # Emits an inline CSS style element.
49
- #
50
- # @param css [String] CSS code
51
- # @param **attributes [Hash] optional element attributes
52
- # @return [void]
53
- def style(css, **attributes, &block)
54
- @buffer << '<style'
55
- emit_attributes(attributes) unless attributes.empty?
56
-
57
- @buffer << '>' << css << '</style>'
58
- end
59
-
60
- # Emits an inline JS script element.
61
- #
62
- # @param js [String, nil] Javascript code
63
- # @param **attributes [Hash] optional element attributes
64
- # @return [void]
65
- def script(js = nil, **attributes, &block)
66
- @buffer << '<script'
67
- emit_attributes(attributes) unless attributes.empty?
68
-
69
- if js
70
- @buffer << '>' << js << '</script>'
71
- else
72
- @buffer << '></script>'
73
- end
74
- end
75
-
76
- # Returns a versioned URL for the given file spec.
77
- #
78
- # @param href [String] relative file path
79
- # @param root_path [String] root path for file
80
- # @param root_url [String] root URL
81
- # @return [String] versioned URL
82
- def versioned_file_href(href, root_path, root_url = '')
83
- fn = File.join(root_path, href)
84
- version = File.stat(fn).mtime.to_i rescue 0
85
- "#{root_url}/#{href}?v=#{version}"
86
- end
87
-
88
- # Emits an import map scrit tag. If a hash is given, emits the hash as is.
89
- # If a string is given, searches for all *.js files under the given path,
90
- # and emits an import map including all found files, with versioned URLs.
91
- #
92
- # @param root_path [String, Hash] root path or hash
93
- # @param root_url [String] root URL to construct URLs against
94
- # @return [void]
95
- def import_map(root_path, root_url = '')
96
- if root_path.is_a?(Hash)
97
- script(root_path.to_json, type: 'importmap')
98
- else
99
- map = Dir["#{root_path}/*.js"].sort.each_with_object({}) do |fn, h|
100
- name = File.basename(fn)
101
- m = fn.match(/\/([^\/]+)\.js$/)
102
- h[m[1]] = versioned_file_href(name, root_path, root_url)
103
- end
104
- script(map.to_json, type: 'importmap')
105
- end
106
- end
107
-
108
- # Emits a script tag with type attribute set to module.
109
- #
110
- # @param code [String] JS code
111
- # @return [void]
112
- def js_module(code)
113
- script code, type: 'module'
114
- end
115
-
116
- # Converts and emits the given markdown. Papercraft uses
117
- # [Kramdown](https://github.com/gettalong/kramdown/) to do the Markdown to
118
- # HTML conversion. Optional Kramdown settings can be provided in order to
119
- # control the conversion. Those are merged with the default Kramdown
120
- # settings, which can be controlled using
121
- # `Papercraft::HTML.kramdown_options`.
122
- #
123
- # @param markdown [String] Markdown content
124
- # @param **opts [Hash] Kramdown options
125
- # @return [void]
126
- def emit_markdown(markdown, **opts)
127
- emit Papercraft.markdown(markdown, **opts)
128
- end
129
-
130
- private
131
-
132
- # Returns true if the given tag is a void element, in order to render a self
133
- # closing tag. See spec: https://html.spec.whatwg.org/multipage/syntax.html#void-elements.
134
- #
135
- # @param tag [String] tag
136
- # @return [Bool] is it a void element
137
- def is_void_element_tag?(tag)
138
- case tag
139
- #
140
- when 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'
141
- true
142
- else
143
- false
144
- end
145
- end
146
-
147
- # Escapes the given text using HTML entities.
148
- #
149
- # @param text [String] text
150
- # @return [String] escaped text
151
- def escape_text(text)
152
- CGI.escapeHTML(text.to_s)
153
- end
154
-
155
- # Converts a tag to its string representation. Underscores will be converted
156
- # to dashes.
157
- #
158
- # @param tag [Symbol, String] tag
159
- # @return [String] tag string
160
- def tag_repr(tag)
161
- tag.to_s.tr('_', '-')
162
- end
163
-
164
- # Converts an attribute to its string representation. Underscores will be
165
- # converted to dashes.
166
- #
167
- # @param att [Symbol, String] attribute
168
- # @return [String] attribute string
169
- def att_repr(att)
170
- att.to_s.tr('_', '-')
171
- end
172
- end
173
- end
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Papercraft
6
- # JSON renderer extensions
7
- module JSON
8
- # Initializes a JSON renderer, setting up an object stack.
9
- def initialize(&template)
10
- @object_stack = [nil]
11
- super
12
- end
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
- # Papercraft.json {
19
- # item 'foo'
20
- # item 'bar'
21
- # }.render #=> "[\"foo\", \"bar\"]"
22
- #
23
- # @param value [Object] item
24
- # @return [void]
25
- def item(value = nil, _for: nil, &block)
26
- return _for.each { |*a| item(value) { block.(*a)} } if _for
27
-
28
- verify_array_target
29
- if block
30
- value = enter_object(&block)
31
- end
32
- push_array_item(value)
33
- end
34
-
35
- # Adds a key-value item to the current object target. If a block is given,
36
- # the block is evaulated against a new object target, then used as the
37
- # value.
38
- #
39
- # @param key [Object] key
40
- # @param value [Object] value
41
- # @return [void]
42
- def kv(key, value = nil, &block)
43
- verify_hash_target
44
- if block
45
- value = enter_object(&block)
46
- end
47
- push_kv_item(key, value)
48
- end
49
-
50
- # Intercepts method calls by adding key-value pairs to the current object
51
- # target.
52
- #
53
- # @param key [Symbol] key
54
- # @param value [Object] value
55
- # @return [void]
56
- def method_missing(key, value = nil, &block)
57
- kv(key, value, &block)
58
- end
59
-
60
- # Converts the root object target to JSON.
61
- #
62
- # @return [String] JSON template result
63
- def to_s
64
- @object_stack[0].to_json
65
- end
66
-
67
- private
68
-
69
- # Adds a new entry to the object stack and evaluates the given block.
70
- #
71
- # @return [void]
72
- def with_object(&block)
73
- @object_stack << nil
74
- instance_eval(&block)
75
- end
76
-
77
- # Verifies that the current object target is not a hash.
78
- #
79
- # @return [bool]
80
- def verify_array_target
81
- case @object_stack[-1]
82
- when nil
83
- @object_stack[-1] = []
84
- when Hash
85
- raise "Mixing array and hash values"
86
- end
87
- end
88
-
89
- # Verifies that the current object target is not an array.
90
- #
91
- # @return [bool]
92
- def verify_hash_target
93
- case @object_stack[-1]
94
- when nil
95
- @object_stack[-1] = {}
96
- when Array
97
- raise "Mixing array and hash values"
98
- end
99
- end
100
-
101
- # Pushes an array item to the current object target.
102
- #
103
- # @param value [Object] item
104
- # @return [void]
105
- def push_array_item(value)
106
- @object_stack[-1] << value
107
- end
108
-
109
- # Pushes a key value into the current object target.
110
- #
111
- # @param key [Object] key
112
- # @param value [Object] value
113
- # @return [void]
114
- def push_kv_item(key, value)
115
- @object_stack[-1][key] = value
116
- end
117
-
118
- # Adds a new object to the object stack, evaluates the given template block,
119
- # then pops the object off the stack.
120
- #
121
- # @return [void]
122
- def enter_object(&block)
123
- @object_stack << nil
124
- instance_eval(&block)
125
- @object_stack.pop
126
- end
127
- end
128
- end
@@ -1,190 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './html'
4
- require_relative './xml'
5
- require_relative './json'
6
- require_relative './extension_proxy'
7
-
8
- module Papercraft
9
-
10
- # A Renderer renders a Papercraft template into a string
11
- class Renderer
12
-
13
- class << self
14
-
15
- # Verifies that the given template proc can be called with the given
16
- # arguments and named arguments. If the proc demands named argument keys
17
- # that do not exist in `named_args`, `Papercraft::Error` is raised.
18
- #
19
- # @param template [Proc] proc to verify
20
- # @param args [Array<any>] arguments passed to proc
21
- # @param named_args [Hash] named arguments passed to proc
22
- def verify_proc_parameters(template, args, named_args)
23
- param_count = 0
24
- template.parameters.each do |(type, name)|
25
- case type
26
- when :req
27
- param_count += 1
28
- when :keyreq
29
- if !named_args.has_key?(name)
30
- raise Papercraft::Error, "Missing template parameter #{name.inspect}"
31
- end
32
- end
33
- end
34
- if param_count > args.size
35
- raise Papercraft::Error, "Missing template parameters"
36
- end
37
- end
38
-
39
- # call_seq:
40
- # Papercraft::Renderer.extension(name => mod, ...)
41
- # Papercraft.extension(name => mod, ...)
42
- #
43
- # Installs the given extensions, passed in the form of a Ruby hash mapping
44
- # methods to extension modules. The methods will be available to all
45
- # Papercraft templates. Extension methods are executed in the context of
46
- # the the renderer instance, so they can look just like normal proc
47
- # components. In cases where method names in the module clash with XML
48
- # tag names, you can use the `#tag` method to emit the relevant tag.
49
- #
50
- # module ComponentLibrary
51
- # def card(title, content)
52
- # div(class: 'card') {
53
- # h3 title
54
- # div(class: 'card-content') { emit_markdown content }
55
- # }
56
- # end
57
- # end
58
- #
59
- # Papercraft.extension(components: ComponentLibrary)
60
- # Papercraft.html { components.card('Foo', '**Bar**') }
61
- #
62
- # @param map [Hash] hash mapping methods to extension modules
63
- # @return [void]
64
- def extension(map)
65
- map.each do |sym, mod|
66
- define_extension_method(sym, mod)
67
- end
68
- end
69
-
70
- private
71
-
72
- # Defines a method returning an extension proxy for the given module
73
- # @param sym [Symbol] method name
74
- # @param mod [Module] extension module
75
- # @return [void]
76
- def define_extension_method(sym, mod)
77
- define_method(sym) do
78
- (@extension_proxies ||= {})[mod] ||= ExtensionProxy.new(self, mod)
79
- end
80
- end
81
- end
82
-
83
- # Initializes the renderer and evaulates the given template in the
84
- # renderer's scope.
85
- #
86
- # @param &template [Proc] template block
87
- def initialize(render_fragment = nil, &template)
88
- @render_fragment = render_fragment
89
- instance_eval(&template)
90
- end
91
-
92
- # Emits the given object into the rendering buffer. If the given object is a
93
- # proc or a component, `emit` will passes any additional arguments and named
94
- # arguments to the object when rendering it. If the given object is nil,
95
- # nothing is emitted. Otherwise, the object is converted into a string using
96
- # `#to_s` which is then added to the rendering buffer, without any escaping.
97
- #
98
- # greeter = proc { |name| h1 "Hello, #{name}!" }
99
- # Papercraft.html { emit(greeter, 'world') }.render #=> "<h1>Hello, world!</h1>"
100
- #
101
- # Papercraft.html { emit 'hi&<bye>' }.render #=> "hi&<bye>"
102
- #
103
- # Papercraft.html { emit nil }.render #=> ""
104
- #
105
- # @param o [Proc, Papercraft::Template, String] emitted object
106
- # @param *a [Array<any>] arguments to pass to a proc
107
- # @param **b [Hash] named arguments to pass to a proc
108
- # @return [void]
109
- def emit(o, *a, **b, &block)
110
- case o
111
- when ::Proc
112
- Renderer.verify_proc_parameters(o, a, b)
113
- push_emit_yield_block(block) if block
114
- instance_exec(*a, **b, &o)
115
- when nil
116
- # do nothing
117
- else
118
- emit_object(o)
119
- end
120
- end
121
- alias_method :e, :emit
122
-
123
- # Emits a block supplied using {Template#apply} or {Template#render}.
124
- #
125
- # div_wrap = Papercraft.html { |*args| div { emit_yield(*args) } }
126
- # greeter = div_wrap.apply { |name| h1 "Hello, #{name}!" }
127
- # greeter.render('world') #=> "<div><h1>Hello, world!</h1></div>"
128
- #
129
- # @param *a [Array<any>] arguments to pass to a proc
130
- # @param **b [Hash] named arguments to pass to a proc
131
- # @return [void]
132
- def emit_yield(*a, **b)
133
- block = @emit_yield_stack&.pop
134
- raise Papercraft::Error, "No block given" unless block
135
-
136
- instance_exec(*a, **b, &block)
137
- end
138
-
139
- # Defines a named template fragment. Template fragments allow rendering
140
- # specific parts of the same template. A template fragment can be rendered
141
- # using {Template#render_fragment}. See also
142
- # {https://htmx.org/essays/template-fragments/ HTMX template fragments}.
143
- #
144
- # form = Papercraft.html {
145
- # h1 'Hello'
146
- # fragment(:buttons) {
147
- # button 'OK'
148
- # button 'Cancel'
149
- # }
150
- # }
151
- # form.render_fragment(:buttons) #=> "<button>OK</button><button>Cancel</buttons>"
152
- #
153
- # @param name [Symbol, String] fragment name
154
- # @return [void]
155
- def fragment(name, &block)
156
- raise Papercraft::Error, "Already in fragment" if @fragment
157
-
158
- begin
159
- @fragment = name
160
- emit(block)
161
- ensure
162
- @fragment = nil
163
- end
164
- end
165
-
166
- private
167
-
168
- # Pushes the given block onto the emit_yield stack.
169
- #
170
- # @param block [Proc] block
171
- def push_emit_yield_block(block)
172
- (@emit_yield_stack ||= []) << block
173
- end
174
- end
175
-
176
- # Implements an HTML renderer
177
- class HTMLRenderer < Renderer
178
- include HTML
179
- end
180
-
181
- # Implements an XML renderer
182
- class XMLRenderer < Renderer
183
- include XML
184
- end
185
-
186
- # Implements a JSON renderer
187
- class JSONRenderer < Renderer
188
- include JSON
189
- end
190
- end