htx 0.0.1 → 0.0.6

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