htx 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +16 -0
- data/README.md +46 -0
- data/lib/htx.rb +190 -0
- 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: []
|