htx 0.0.2 → 0.0.7

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: 32fe6cdd2942dae5d197592bc9dbe2248b93bebcfe0d0fb5a82b463b9e953c28
4
- data.tar.gz: 36496d0d5395836bb8196f6a1fe2b02af7634c97c2028536e149b7a69d1603d9
3
+ metadata.gz: dc1adc5f7b7b12da438f6c96c5a81b3585a8f9a8b761d54eee3895b5195898ec
4
+ data.tar.gz: cfc75dd48759fdb2151db74de71ce9c7ae07a73e07d20af415e96a0a37c07bda
5
5
  SHA512:
6
- metadata.gz: e9c9bf24ca54886e6f4b9ad8e2bd8796436f299b2c0af7a0d742ac1b6ade2e55a30e477e4e5bb5b6d17a766641cd20cac8b3a0140e1fbfa48dda234664bf9c95
7
- data.tar.gz: 85640a72d956e08d1f1467b8b3ef609e91f9bbc918e15b5c1faaf9065410b3b1f9324d30f82b65f9b12deae5417132f0aa6fa8fb081b8ccd7cb173f5f5af9360
6
+ metadata.gz: 81660a171c9eb0a384b483f5391bd929bec1833aceef2128ecefcfba1190ea4343efbc02f286f241fcf435dcd3fd860fd7f9844a6c29358d84e5005b10e2a62b
7
+ data.tar.gz: c8f764200a3e0f5fb19d1c19cc8f8e5a71209b4d0e8e87f7cf27941fbf0595090395ee141a136c67670be865de8dba50235d7f10ce8fc0ccd4a2cd060809af09
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2019 Nate Pickens
1
+ Copyright 2019-2022 Nate Pickens
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
4
  documentation files (the "Software"), to deal in the Software without restriction, including without
data/README.md CHANGED
@@ -29,11 +29,20 @@ template = File.read(File.join('some/asset/dir', path))
29
29
 
30
30
  HTX.compile(path, template)
31
31
 
32
+ # Or to attach to a custom object instead of `window`:
33
+ HTX.compile(path, template, assign_to: 'myTemplates')
34
+
32
35
  # Result:
33
36
  #
34
37
  # window['/my/hot/template.htx'] = function(htx) {
35
38
  # ...
36
39
  # }
40
+ #
41
+ # If `assign_to` is specified:
42
+ #
43
+ # myTemplates['/components/people.htx'] = function(htx) {
44
+ # // ...
45
+ # }
37
46
  ```
38
47
 
39
48
  ## Contributing
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.7
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTX
4
+ ##
5
+ # Error class used when a problem is encountered processing a template.
6
+ #
7
+ class MalformedTemplateError < StandardError
8
+ ##
9
+ # Creates a new instance.
10
+ #
11
+ # * +message+ - Description of the error.
12
+ # * +name+ - Name of the template.
13
+ # * +node+ - Nokogiri node being processed when the error was encountered (optional).
14
+ #
15
+ def initialize(message, name, node = nil)
16
+ if node
17
+ line = node.line
18
+ line = node.parent.line if line < 1
19
+ line = nil if line == -1
20
+ end
21
+
22
+ super("Malformed template #{name}#{":#{line}" if line}: #{message}")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('nokogiri')
4
+
5
+ module HTX
6
+ class Template
7
+ ELEMENT = 0b001
8
+ CHILDLESS = 0b010
9
+ XMLNS = 0b100
10
+ FLAG_BITS = 3
11
+
12
+ INDENT_DEFAULT = ' '
13
+ CONTENT_TAG = 'htx-content'
14
+ DYNAMIC_KEY_ATTR = 'htx-key'
15
+
16
+ DEFAULT_XMLNS = {
17
+ 'math' => 'http://www.w3.org/1998/Math/MathML',
18
+ 'svg' => 'http://www.w3.org/2000/svg',
19
+ }.freeze
20
+
21
+ LEADING_WHITESPACE = /\A[ \t]*\n[ \t]*/.freeze
22
+ TRAILING_WHITESPACE = /\n[ \t]*\z/.freeze
23
+ NON_BLANK_NON_FIRST_LINE = /(?<=\n)[ \t]*(?=\S)/.freeze
24
+ NEWLINE_NON_BLANK = /\n(?=[^\n])/.freeze
25
+ INDENT_GUESS = /^[ \t]+/.freeze
26
+
27
+ END_STATEMENT_END = /(;|\n|\{|\})[ \t]*\z/.freeze
28
+ BEGIN_STATEMENT_END = /\A[ \t]*(;|\{|\n|\})/.freeze
29
+ END_WHITESPACE = /\s\z/.freeze
30
+ BEGIN_WHITESPACE = /\A\s/.freeze
31
+
32
+ RAW_VALUE = /\A\s*\${([\S\s]*)}\s*\z/.freeze
33
+ TEMPLATE_STRING = /\A\s*`([\S\s]*)`\s*\z/.freeze
34
+ INTERPOLATION = /\$\\?{([^}]*})?/.freeze
35
+ HTML_ENTITY = /&([a-zA-Z]+|#\d+|x[0-9a-fA-F]+);/.freeze
36
+ NON_CONTROL_STATEMENT = /#{INTERPOLATION}|(#{HTML_ENTITY})/.freeze
37
+ CONTROL_STATEMENT = /[{}();]/.freeze
38
+ UNESCAPED_BACKTICK = /(?<!\\)((\\\\)*)`/.freeze
39
+ CLOSE_STATEMENT = /;?\s*htx\.close\((\d*)\);?(\s*)\z/.freeze
40
+
41
+ ##
42
+ # Returns false. In the near future when support for the <:> tag has been dropped (in favor of
43
+ # <htx-content>), will return true if Nokogiri's HTML5 parser is available. To use it now, monkey patch
44
+ # this method to return true.
45
+ #
46
+ def self.html5_parser?
47
+ false # !!defined?(Nokogiri::HTML5)
48
+ end
49
+
50
+ ##
51
+ # Returns Nokogiri's HTML5 parser if available and enabled, and Nokogiri's regular HTML parser
52
+ # otherwise.
53
+ #
54
+ def self.nokogiri_parser
55
+ html5_parser? ? Nokogiri::HTML5::DocumentFragment : Nokogiri::HTML::DocumentFragment
56
+ end
57
+
58
+ ##
59
+ # Creates a new HTX instance.
60
+ #
61
+ # * +name+ - Name of the template. Conventionally the path of the template file is used for the name,
62
+ # but it can be anything.
63
+ # * +content+ - Template content string.
64
+ #
65
+ def initialize(name, content)
66
+ @name = name
67
+ @content = content
68
+ end
69
+
70
+ ##
71
+ # Compiles the HTX template.
72
+ #
73
+ # * +indent+ - Indent output by this number of spaces if Numeric, or by this string if a String (if the
74
+ # latter, may only contain space and tab characters).
75
+ # * +assign_to+ - Assign the template function to this JavaScript object instead of the <tt>window</tt>
76
+ # object.
77
+ #
78
+ def compile(indent: nil, assign_to: 'window')
79
+ doc = self.class.nokogiri_parser.parse(@content)
80
+ root_nodes = doc.children.select { |n| n.element? || (n.text? && n.text.strip != '') }
81
+
82
+ if (text_node = root_nodes.find(&:text?))
83
+ raise(MalformedTemplateError.new('text nodes are not allowed at root level', @name, text_node))
84
+ elsif root_nodes.size == 0
85
+ raise(MalformedTemplateError.new('a root node is required', @name))
86
+ elsif root_nodes.size > 1
87
+ raise(MalformedTemplateError.new("root node already defined on line #{root_nodes[0].line}", @name,
88
+ root_nodes[1]))
89
+ end
90
+
91
+ @compiled = ''.dup
92
+ @static_key = 0
93
+
94
+ @indent =
95
+ if indent.kind_of?(Numeric)
96
+ ' ' * indent
97
+ elsif indent.kind_of?(String) && indent !~ /^[ \t]+$/
98
+ raise("Invalid indent value #{indent.inspect}: only spaces and tabs are allowed")
99
+ else
100
+ indent || @content[INDENT_GUESS] || INDENT_DEFAULT
101
+ end
102
+
103
+ process(doc)
104
+ @compiled.rstrip!
105
+
106
+ <<~EOS
107
+ #{assign_to}['#{@name}'] = function(htx) {
108
+ #{@indent}#{@compiled}
109
+ }
110
+ EOS
111
+ end
112
+
113
+ private
114
+
115
+ ##
116
+ # Processes a DOM node's descendents.
117
+ #
118
+ # * +base+ - Base Nokogiri node to start from.
119
+ #
120
+ def process(base, xmlns: false)
121
+ base.children.each do |node|
122
+ next unless node.element? || node.text?
123
+
124
+ dynamic_key = process_value(node.attr(DYNAMIC_KEY_ATTR), :attr)
125
+
126
+ if node.text? || node.name == CONTENT_TAG || node.name == 'htx-text' || node.name == ':'
127
+ if !node.text? && node.name != CONTENT_TAG
128
+ warn("#{@name}:#{node.line}: The <#{node.name}> tag has been deprecated. Please use "\
129
+ "<#{CONTENT_TAG}> for identical functionality.")
130
+ end
131
+
132
+ if (node.attributes.size - (dynamic_key ? 1 : 0)) != 0
133
+ raise(MalformedTemplateError.new("<#{node.name}> tags may not have attributes other than "\
134
+ "#{DYNAMIC_KEY_ATTR}", @name, node))
135
+ end
136
+
137
+ if (non_text_node = node.children.find { |n| !n.text? })
138
+ raise(MalformedTemplateError.new("<#{node.name}> tags may not contain child tags", @name,
139
+ non_text_node))
140
+ end
141
+
142
+ text = (node.text? ? node : node.children).text
143
+
144
+ if (value = process_value(text))
145
+ append(
146
+ "#{indent(text[LEADING_WHITESPACE])}"\
147
+ "htx.node(#{[
148
+ value,
149
+ dynamic_key,
150
+ (@static_key += 1) << FLAG_BITS,
151
+ ].compact.join(', ')})"\
152
+ "#{indent(text[TRAILING_WHITESPACE])}"
153
+ )
154
+ else
155
+ append(indent(text))
156
+ end
157
+ else
158
+ childless = node.children.empty? || (node.children.size == 1 && node.children[0].text.strip == '')
159
+ attrs, explicit_xmlns = process_attrs(node)
160
+ xmlns ||= explicit_xmlns
161
+
162
+ append("htx.node(#{[
163
+ "'#{tag_name(node.name)}'",
164
+ attrs,
165
+ dynamic_key,
166
+ ((@static_key += 1) << FLAG_BITS) | ELEMENT | (childless ? CHILDLESS : 0) | (xmlns ? XMLNS : 0),
167
+ ].compact.flatten.join(', ')})")
168
+
169
+ unless childless
170
+ process(node, xmlns: xmlns)
171
+
172
+ count = ''
173
+ @compiled.sub!(CLOSE_STATEMENT) do
174
+ count = $1 == '' ? 2 : $1.to_i + 1
175
+ $2
176
+ end
177
+
178
+ append("htx.close(#{count})")
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ ##
185
+ # Appends a string to the compiled template function string with appropriate punctuation and/or
186
+ # whitespace inserted.
187
+ #
188
+ # * +text+ - String to append to the compiled template string.
189
+ #
190
+ def append(text)
191
+ if @compiled == ''
192
+ # Do nothing.
193
+ elsif @compiled !~ END_STATEMENT_END && text !~ BEGIN_STATEMENT_END
194
+ @compiled << '; '
195
+ elsif @compiled !~ END_WHITESPACE && text !~ BEGIN_WHITESPACE
196
+ @compiled << ' '
197
+ elsif @compiled[-1] == "\n"
198
+ @compiled << @indent
199
+ end
200
+
201
+ @compiled << text
202
+ end
203
+
204
+ ##
205
+ # Indents each line of a string (except the first).
206
+ #
207
+ # * +text+ - String of lines to indent.
208
+ #
209
+ def indent(text)
210
+ return '' unless text
211
+
212
+ text.gsub!(NEWLINE_NON_BLANK, "\n#{@indent}")
213
+ text
214
+ end
215
+
216
+ ##
217
+ # Processes, formats, and encodes an attribute or text node value. Returns nil if the value is
218
+ # determined to be a control statement.
219
+ #
220
+ # * +text+ - String to process.
221
+ # * +is_attr+ - Truthy if the text is an attribute value.
222
+ #
223
+ def process_value(text, is_attr = false)
224
+ return nil if text.nil? || (!is_attr && text.strip == '')
225
+
226
+ if (value = text[RAW_VALUE, 1])
227
+ # Entire text is enclosed in ${...}.
228
+ value.strip!
229
+ quote = false
230
+ escape_quotes = false
231
+ elsif (value = text[TEMPLATE_STRING, 1])
232
+ # Entire text is enclosed in backticks (template string).
233
+ quote = true
234
+ escape_quotes = false
235
+ elsif is_attr || text.gsub(NON_CONTROL_STATEMENT, '') !~ CONTROL_STATEMENT
236
+ # Text is an attribute value or doesn't match control statement pattern.
237
+ value = text.dup
238
+ quote = true
239
+ escape_quotes = true
240
+ else
241
+ return nil
242
+ end
243
+
244
+ # Strip one leading and trailing newline (and attached spaces) and perform outdent. Outdent amount
245
+ # calculation ignores everything before the first newline in its search for the least-indented line.
246
+ outdent = value.scan(NON_BLANK_NON_FIRST_LINE).min
247
+ value.gsub!(/#{LEADING_WHITESPACE}|#{TRAILING_WHITESPACE}|^#{outdent}/, '')
248
+ value.gsub!(UNESCAPED_BACKTICK, '\1\\\`') if escape_quotes
249
+ value.insert(0, '`').insert(-1, '`') if quote
250
+
251
+ # Ensure any Unicode characters get converted to Unicode escape sequences. Also note that since
252
+ # Nokogiri converts HTML entities to Unicode characters, this causes them to be properly passed to
253
+ # `document.createTextNode` calls as Unicode escape sequences rather than (incorrectly) as HTML
254
+ # entities.
255
+ value.encode('ascii', fallback: ->(c) { "\\u#{c.ord.to_s(16).rjust(4, '0')}" })
256
+ end
257
+
258
+ ##
259
+ # Processes a node's attributes, returning two items: a flat array of attribute names and values, and a
260
+ # boolean indicating whether or not an xmlns attribute is present.
261
+ #
262
+ # Note: if the node is a <math> or <svg> tag without an explicit xmlns attribute set, an appropriate one
263
+ # will be automatically added since it is required for those elements to render properly.
264
+ #
265
+ # * +node+ - Nokogiri node to process for attributes.
266
+ #
267
+ def process_attrs(node)
268
+ attrs = []
269
+ xmlns = !!node.attributes['xmlns']
270
+
271
+ if !xmlns && DEFAULT_XMLNS[node.name]
272
+ xmlns = true
273
+
274
+ attrs << "'xmlns'"
275
+ attrs << process_value(DEFAULT_XMLNS[node.name], :attr)
276
+ end
277
+
278
+ node.attributes.each do |_, attr|
279
+ next if attr.name == DYNAMIC_KEY_ATTR
280
+
281
+ attrs << "'#{attr_name(attr.name)}'"
282
+ attrs << process_value(attr.value, :attr)
283
+ end
284
+
285
+ [attrs, xmlns]
286
+ end
287
+
288
+ ##
289
+ # Returns the given text if the HTML5 parser is in use, or looks up the value in the tag map to get the
290
+ # correctly-cased version, falling back to the supplied text if no mapping exists.
291
+ #
292
+ # * +text+ - Tag name as returned by Nokogiri parser.
293
+ #
294
+ def tag_name(text)
295
+ self.class.html5_parser? ? text : (TAG_MAP[text] || text)
296
+ end
297
+
298
+ ##
299
+ # Returns the given text if the HTML5 parser is in use, or looks up the value in the attribute map to
300
+ # get the correctly-cased version, falling back to the supplied text if no mapping exists.
301
+ #
302
+ # * +text+ - Attribute name as returned by Nokogiri parser.
303
+ #
304
+ def attr_name(text)
305
+ self.class.html5_parser? ? text : (ATTR_MAP[text] || text)
306
+ end
307
+
308
+ # The Nokogiri HTML parser downcases all tag and attribute names, but SVG tags and attributes are case
309
+ # sensitive and often mix cased. These maps are used to restore the correct case of such tags and
310
+ # attributes.
311
+ #
312
+ # Note: Nokogiri's newer HTML5 parser resulting from the Nokogumbo merge fixes this issue, but it is
313
+ # currently not available for JRuby. It also does not parse <:> as a tag, which is why it's been
314
+ # deprecated in favor of <htx-content>. Once support for <:> has been completely removed, the HTML5
315
+ # parser will be used for regular Ruby and this tag and attribute mapping hack reserved for JRuby (and
316
+ # any other potential environments where the HTML5 parser is not available).
317
+
318
+ # Source: https://developer.mozilla.org/en-US/docs/Web/SVG/Element
319
+ TAG_MAP = %w[
320
+ animateMotion animateTransform clipPath feBlend feColorMatrix feComponentTransfer feComposite
321
+ feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA
322
+ feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight
323
+ feSpecularLighting feSpotLight feTile feTurbulence foreignObject linearGradient radialGradient
324
+ textPath
325
+ ].map { |tag| [tag.downcase, tag] }.to_h.freeze
326
+
327
+ # Source: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
328
+ ATTR_MAP = %w[
329
+ attributeName attributeType baseFrequency baseProfile calcMode clipPathUnits contentScriptType
330
+ contentStyleType diffuseConstant edgeMode filterRes filterUnits glyphRef gradientTransform
331
+ gradientUnits kernelMatrix kernelUnitLength keyPoints keySplines keyTimes lengthAdjust
332
+ limitingConeAngle markerHeight markerUnits markerWidth maskContentUnits maskUnits numOctaves
333
+ pathLength patternContentUnits patternTransform patternUnits pointsAtX pointsAtY pointsAtZ
334
+ preserveAlpha preserveAspectRatio primitiveUnits refX refY referrerPolicy repeatCount repeatDur
335
+ requiredExtensions requiredFeatures specularConstant specularExponent spreadMethod startOffset
336
+ stdDeviation stitchTiles surfaceScale systemLanguage tableValues targetX targetY textLength viewBox
337
+ viewTarget xChannelSelector yChannelSelector zoomAndPan
338
+ ].map { |attr| [attr.downcase, attr] }.to_h.freeze
339
+ end
340
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTX
4
+ VERSION = '0.0.7'
5
+ end
data/lib/htx.rb CHANGED
@@ -1,220 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require('nokogiri')
3
+ require('htx/malformed_template_error')
4
+ require('htx/template')
5
+ require('htx/version')
4
6
 
5
7
  ##
6
8
  # A Ruby compiler for HTX templates.
7
9
  #
8
- class HTX
9
- class MalformedTemplateError < StandardError; end
10
-
11
- VERSION = '0.0.2'
12
-
13
- CHILDLESS = 0b01
14
- TEXT_NODE = 0b10
15
- FLAG_BITS = 2
16
-
17
- DYNAMIC_KEY_ATTR = 'htx-key'
18
-
19
- LEADING_WHITESPACE = /\A *\n */.freeze
20
- TRAILING_WHITESPACE = /\n *\z/.freeze
21
- NON_BLANK_NON_FIRST_LINE = /(?<=\n) *(?=\S)/.freeze
22
- NEWLINE_NON_BLANK = /\n(?=[^\n]+)/.freeze
23
-
24
- END_STATEMENT_END = /(;|\n|\{|\}) *\z/.freeze
25
- BEGIN_STATEMENT_END = /\A *(;|\{|\n|\})/.freeze
26
- END_WHITESPACE = /\s\z/.freeze
27
- BEGIN_WHITESPACE = /\A\s/.freeze
28
-
29
- RAW_VALUE = /\A\s*\${([\S\s]*)}\s*\z/.freeze
30
- TEMPLATE_STRING = /\A\s*`([\S\s]*)`\s*\z/.freeze
31
- INTERPOLATION = /\$\\?{([^}]*})?/.freeze
32
- HTML_ENTITY = /&([a-zA-Z]+|#\d+|x[0-9a-fA-F]+);/.freeze
33
- NON_CONTROL_STATEMENT = /#{INTERPOLATION}|(#{HTML_ENTITY})/.freeze
34
- CONTROL_STATEMENT = /[{}();]/.freeze
35
- CLOSE_STATEMENT = /;?\s*htx\.close\((\d*)\);?(\s*)\z/.freeze
36
-
37
- ##
38
- # Convenience method to create a new instance and immediately call compile on it.
39
- #
40
- def self.compile(name, template)
41
- new(name, template).compile
42
- end
43
-
44
- ##
45
- # Creates a new HTX instance. Conventionally the path of the template file is used for the name, but it
46
- # can be anything.
47
- #
48
- def initialize(name, template)
49
- @name = name
50
- @template = template
51
- end
52
-
53
- ##
54
- # Compiles the HTX template.
55
- #
56
- def compile
57
- doc = Nokogiri::HTML::DocumentFragment.parse(@template)
58
- root_nodes = doc.children.select { |n| n.element? || (n.text? && n.text.strip != '') }
59
-
60
- if root_nodes.any?(&:text?)
61
- raise(MalformedTemplateError.new('Template contains text at its root level'))
62
- elsif root_nodes.size == 0
63
- raise(MalformedTemplateError.new('Template does not have a root node'))
64
- elsif root_nodes.size > 1
65
- raise(MalformedTemplateError.new('Template has more than one node at its root level'))
66
- end
67
-
68
- @compiled = ''.dup
69
- @static_key = 0
70
-
71
- process(doc)
72
- @compiled.rstrip!
73
-
74
- <<~EOS
75
- window['#{@name}'] = function(htx) {
76
- #{@compiled}
77
- }
78
- EOS
79
- end
80
-
81
- private
82
-
83
- ##
84
- # Processes a DOM node's descendents.
85
- #
86
- def process(base)
87
- base.children.each do |node|
88
- next unless node.element? || node.text?
89
-
90
- dynamic_key = process_value(node.attr(DYNAMIC_KEY_ATTR), :attr)
91
-
92
- if node.text? || node.name == ':'
93
- text = (node.text? ? node : node.children).text
94
-
95
- if (value = process_value(text))
96
- append(
97
- "#{indent(text[LEADING_WHITESPACE])}"\
98
- "htx.node(#{[
99
- value,
100
- dynamic_key,
101
- ((@static_key += 1) << FLAG_BITS) | TEXT_NODE,
102
- ].compact.join(', ')})"\
103
- "#{indent(text[TRAILING_WHITESPACE])}"
104
- )
105
- else
106
- append(indent(text))
107
- end
108
- else
109
- attrs = node.attributes.inject([]) do |attrs, (_, attr)|
110
- next attrs if attr.name == DYNAMIC_KEY_ATTR
111
-
112
- attrs << "'#{ATTR_MAP[attr.name] || attr.name}'"
113
- attrs << process_value(attr.value, :attr)
114
- end
115
-
116
- append("htx.node(#{[
117
- "'#{TAG_MAP[node.name] || node.name}'",
118
- attrs,
119
- dynamic_key,
120
- ((@static_key += 1) << FLAG_BITS) | (node.children.empty? ? CHILDLESS : 0),
121
- ].compact.flatten.join(', ')})")
122
-
123
- unless node.children.empty?
124
- process(node)
125
-
126
- count = ''
127
- @compiled.sub!(CLOSE_STATEMENT) do
128
- count = $1 == '' ? 2 : $1.to_i + 1
129
- $2
130
- end
131
-
132
- append("htx.close(#{count})")
133
- end
134
- end
135
- end
136
- end
10
+ module HTX
11
+ EMPTY_HASH = {}.freeze
137
12
 
138
13
  ##
139
- # Appends a string to the compiled template function string with appropriate punctuation and/or whitespace
140
- # inserted.
14
+ # Convenience method to create a new Template instance and compile it.
141
15
  #
142
- def append(text)
143
- if @compiled == ''
144
- # Do nothing.
145
- elsif @compiled !~ END_STATEMENT_END && text !~ BEGIN_STATEMENT_END
146
- @compiled << '; '
147
- elsif @compiled !~ END_WHITESPACE && text !~ BEGIN_WHITESPACE
148
- @compiled << ' '
149
- elsif @compiled[-1] == "\n"
150
- @compiled << ' '
151
- end
152
-
153
- @compiled << text
16
+ def self.compile(name, template, options = EMPTY_HASH)
17
+ Template.new(name, template).compile(**options)
154
18
  end
155
19
 
156
20
  ##
157
- # Indents each line of a string (except the first).
21
+ # DEPRECATED. Use HTX::Template.new instead. HTX was formerly a class that would be instantiated for
22
+ # compilation. This method allows HTX.new calls to continue working (but support will be removed in the
23
+ # near future).
158
24
  #
159
- def indent(text)
160
- return '' unless text
161
-
162
- text.gsub!(NEWLINE_NON_BLANK, "\n ")
163
- text
164
- end
165
-
166
- ##
167
- # Processes, formats, and encodes an attribute or text node value. Returns nil if the value is determined
168
- # to be a control statement.
25
+ # * +name+ - Name of the template. Conventionally the path of the template file is used for the name,
26
+ # but it can be anything.
27
+ # * +content+ - Template content string.
169
28
  #
170
- def process_value(text, is_attr = false)
171
- return nil if text.nil? || (!is_attr && text.strip == '')
172
-
173
- if (value = text[RAW_VALUE, 1])
174
- # Entire text is enclosed in ${...}.
175
- value.strip!
176
- quote = false
177
- elsif (value = text[TEMPLATE_STRING, 1])
178
- # Entire text is enclosed in backticks (template string).
179
- quote = true
180
- elsif is_attr || text.gsub(NON_CONTROL_STATEMENT, '') !~ CONTROL_STATEMENT
181
- # Text is an attribute value or doesn't match control statement pattern.
182
- value = text.dup
183
- quote = true
184
- else
185
- return nil
186
- end
187
-
188
- # Strip one leading and trailing newline (and attached spaces) and perform outdent. Outdent amount
189
- # calculation ignores everything before the first newline in its search for the least-indented line.
190
- outdent = value.scan(NON_BLANK_NON_FIRST_LINE).min
191
- value.gsub!(/#{LEADING_WHITESPACE}|#{TRAILING_WHITESPACE}|^#{outdent}/, '')
192
- value.insert(0, '`').insert(-1, '`') if quote
29
+ def self.new(name, content)
30
+ warn('HTX.new is deprecated. Please use HTX::Template.new instead.')
193
31
 
194
- # Ensure any Unicode characters get converted to Unicode escape sequences. Also note that since Nokogiri
195
- # converts HTML entities to Unicode characters, this causes them to be properly passed to
196
- # `document.createTextNode` calls as Unicode escape sequences rather than (incorrectly) as HTML
197
- # entities.
198
- value.encode('ascii', fallback: ->(c) { "\\u#{c.ord.to_s(16).rjust(4, '0')}" })
32
+ Template.new(name, content)
199
33
  end
200
-
201
- # The Nokogiri HTML parser downcases all tag and attribute names, but SVG tags and attributes are case
202
- # sensitive and often mix cased. These maps are used to restore the correct case of such tags and
203
- # attributes.
204
- TAG_MAP = %w[
205
- animateMotion animateTransform clipPath feBlend feColorMatrix feComponentTransfer feComposite
206
- feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB
207
- feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight
208
- feSpecularLighting feSpotLight feTile feTurbulence foreignObject linearGradient radialGradient textPath
209
- ].map { |tag| [tag.downcase, tag] }.to_h.freeze
210
-
211
- ATTR_MAP = %w[
212
- attributeName baseFrequency calcMode clipPathUnits diffuseConstant edgeMode filterUnits
213
- gradientTransform gradientUnits kernelMatrix kernelUnitLength keyPoints keySplines keyTimes lengthAdjust
214
- limitingConeAngle markerHeight markerUnits markerWidth maskContentUnits maskUnits numOctaves pathLength
215
- patternContentUnits patternTransform patternUnits pointsAtX pointsAtY pointsAtZ preserveAlpha
216
- preserveAspectRatio primitiveUnits refX refY repeatCount repeatDur requiredExtensions specularConstant
217
- specularExponent spreadMethod startOffset stdDeviation stitchTiles surfaceScale systemLanguage
218
- tableValues targetX targetY textLength viewBox xChannelSelector yChannelSelector zoomAndPan
219
- ].map { |attr| [attr.downcase, attr] }.to_h.freeze
220
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: htx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Pickens
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-29 00:00:00.000000000 Z
11
+ date: 2022-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
19
+ version: '1.13'
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
- version: '1.10'
26
+ version: '1.13'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -42,27 +42,37 @@ dependencies:
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 5.11.2
48
+ - - "<"
46
49
  - !ruby/object:Gem::Version
47
- version: '5.11'
50
+ version: 6.0.0
48
51
  type: :development
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
52
- - - "~>"
55
+ - - ">="
53
56
  - !ruby/object:Gem::Version
54
- version: '5.11'
57
+ version: 5.11.2
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: 6.0.0
55
61
  description: HTX is a full-featured HTML template system that is simple, lightweight,
56
62
  and highly performant. This library is a Ruby implementation of the HTX template
57
63
  compiler--it converts HTX templates to their compiled JavaScript form.
58
- email:
64
+ email:
59
65
  executables: []
60
66
  extensions: []
61
67
  extra_rdoc_files: []
62
68
  files:
63
69
  - LICENSE
64
70
  - README.md
71
+ - VERSION
65
72
  - lib/htx.rb
73
+ - lib/htx/malformed_template_error.rb
74
+ - lib/htx/template.rb
75
+ - lib/htx/version.rb
66
76
  homepage: https://github.com/npickens/htx
67
77
  licenses:
68
78
  - MIT
@@ -70,7 +80,7 @@ metadata:
70
80
  allowed_push_host: https://rubygems.org
71
81
  homepage_uri: https://github.com/npickens/htx
72
82
  source_code_uri: https://github.com/npickens/htx
73
- post_install_message:
83
+ post_install_message:
74
84
  rdoc_options: []
75
85
  require_paths:
76
86
  - lib
@@ -78,15 +88,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
88
  requirements:
79
89
  - - ">="
80
90
  - !ruby/object:Gem::Version
81
- version: '0'
91
+ version: 2.6.0
82
92
  required_rubygems_version: !ruby/object:Gem::Requirement
83
93
  requirements:
84
94
  - - ">="
85
95
  - !ruby/object:Gem::Version
86
96
  version: '0'
87
97
  requirements: []
88
- rubygems_version: 3.0.3
89
- signing_key:
98
+ rubygems_version: 3.2.32
99
+ signing_key:
90
100
  specification_version: 4
91
101
  summary: A Ruby compiler for HTX templates.
92
102
  test_files: []