htx 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []