htx 0.0.1

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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +16 -0
  3. data/README.md +46 -0
  4. data/lib/htx.rb +190 -0
  5. metadata +92 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 94e1883a59d09cce6170061c9e0602a3430da8ca800163ba2fe8cc9d10b316b4
4
+ data.tar.gz: a26e6aa3a874e40569cd62204aa698a6bdec5b02a78dc35ea993d0a11ff8f0f2
5
+ SHA512:
6
+ metadata.gz: 004dca83de5a65ba167eb345d21a1a8c1d771efbe946fc746cfb470d4ca783bad57830ced8ba6f1a7ce47e9e04abc3ee06a6ebf02fc9d5a0eee2892983dbf136
7
+ data.tar.gz: ab814533c1f9ead9fa8daa2ef46959a2a8893c21e3705ee9863fbcaeed90ec0e70f835d36420fe1ade8c1a0bf9c33478987e76d07cbb20c154a4da26d3ef7492
data/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ Copyright 2019 Nate Pickens
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
+ documentation files (the "Software"), to deal in the Software without restriction, including without
5
+ limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
6
+ the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
7
+ conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial
10
+ portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
13
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
14
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
15
+ AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
16
+ OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # HTX Ruby Compiler
2
+
3
+ HTX templates are compiled to JavaScript before being used. This library provides a Ruby implementation of
4
+ the compiler. For more information on HTX, see
5
+ [https://github.com/npickens/htx](https://github.com/npickens/htx).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your Gemfile:
10
+
11
+ ```ruby
12
+ gem('htx')
13
+ ```
14
+
15
+ Or install manually on the command line:
16
+
17
+ ```bash
18
+ gem install htx
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ To compile an HTX template, pass a name (conventionally the path of the template file) and template as
24
+ strings to the `HTX.compile` method:
25
+
26
+ ```ruby
27
+ path = '/my/hot/template.htx'
28
+ template = File.read(File.join('some/asset/dir', path))
29
+
30
+ HTX.compile(path, template)
31
+
32
+ # Result:
33
+ #
34
+ # window['/my/hot/template.htx'] = function(htx) {
35
+ # ...
36
+ # }
37
+ ```
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on GitHub at https://github.com/npickens/htx.
42
+
43
+ ## License
44
+
45
+ The gem is available as open source under the terms of the
46
+ [MIT License](https://opensource.org/licenses/MIT).
data/lib/htx.rb ADDED
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('nokogiri')
4
+
5
+ ##
6
+ # A Ruby compiler for HTX templates.
7
+ #
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
107
+
108
+ ##
109
+ # Appends a string to the compiled template function string with appropriate punctuation and/or
110
+ # whitespace inserted.
111
+ #
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
124
+ end
125
+
126
+ ##
127
+ # Indents each line of a string (except the first).
128
+ #
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.
139
+ #
140
+ def self.process_value(text, is_attr = false)
141
+ return nil if text.nil? || (!is_attr && text.strip == '')
142
+
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')}" })
169
+ 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
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: htx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nate Pickens
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.11'
55
+ description: HTX is a full-featured HTML template system that is simple, lightweight,
56
+ and highly performant. This library is a Ruby implementation of the HTX template
57
+ compiler--it converts HTX templates to their compiled JavaScript form.
58
+ email:
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE
64
+ - README.md
65
+ - lib/htx.rb
66
+ homepage: https://github.com/npickens/htx
67
+ licenses:
68
+ - MIT
69
+ metadata:
70
+ allowed_push_host: https://rubygems.org
71
+ homepage_uri: https://github.com/npickens/htx
72
+ source_code_uri: https://github.com/npickens/htx
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.0.3
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: A Ruby compiler for HTX templates.
92
+ test_files: []