htx 0.0.1 → 0.0.2

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/htx.rb +63 -33
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94e1883a59d09cce6170061c9e0602a3430da8ca800163ba2fe8cc9d10b316b4
4
- data.tar.gz: a26e6aa3a874e40569cd62204aa698a6bdec5b02a78dc35ea993d0a11ff8f0f2
3
+ metadata.gz: 32fe6cdd2942dae5d197592bc9dbe2248b93bebcfe0d0fb5a82b463b9e953c28
4
+ data.tar.gz: 36496d0d5395836bb8196f6a1fe2b02af7634c97c2028536e149b7a69d1603d9
5
5
  SHA512:
6
- metadata.gz: 004dca83de5a65ba167eb345d21a1a8c1d771efbe946fc746cfb470d4ca783bad57830ced8ba6f1a7ce47e9e04abc3ee06a6ebf02fc9d5a0eee2892983dbf136
7
- data.tar.gz: ab814533c1f9ead9fa8daa2ef46959a2a8893c21e3705ee9863fbcaeed90ec0e70f835d36420fe1ade8c1a0bf9c33478987e76d07cbb20c154a4da26d3ef7492
6
+ metadata.gz: e9c9bf24ca54886e6f4b9ad8e2bd8796436f299b2c0af7a0d742ac1b6ade2e55a30e477e4e5bb5b6d17a766641cd20cac8b3a0140e1fbfa48dda234664bf9c95
7
+ data.tar.gz: 85640a72d956e08d1f1467b8b3ef609e91f9bbc918e15b5c1faaf9065410b3b1f9324d30f82b65f9b12deae5417132f0aa6fa8fb081b8ccd7cb173f5f5af9360
data/lib/htx.rb CHANGED
@@ -6,7 +6,9 @@ require('nokogiri')
6
6
  # A Ruby compiler for HTX templates.
7
7
  #
8
8
  class HTX
9
- VERSION = '0.0.1'
9
+ class MalformedTemplateError < StandardError; end
10
+
11
+ VERSION = '0.0.2'
10
12
 
11
13
  CHILDLESS = 0b01
12
14
  TEXT_NODE = 0b10
@@ -33,29 +35,57 @@ class HTX
33
35
  CLOSE_STATEMENT = /;?\s*htx\.close\((\d*)\);?(\s*)\z/.freeze
34
36
 
35
37
  ##
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
+ # Convenience method to create a new instance and immediately call compile on it.
38
39
  #
39
40
  def self.compile(name, template)
40
- doc = Nokogiri::HTML::DocumentFragment.parse(template)
41
- js = ''.dup
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
42
52
 
43
- process(doc, js, static_key: 0)
44
- js.rstrip!
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!
45
73
 
46
74
  <<~EOS
47
- window['#{name}'] = function(htx) {
48
- #{js}
75
+ window['#{@name}'] = function(htx) {
76
+ #{@compiled}
49
77
  }
50
78
  EOS
51
79
  end
52
80
 
81
+ private
82
+
53
83
  ##
54
- # Processes a DOM node.
84
+ # Processes a DOM node's descendents.
55
85
  #
56
- def self.process(base, js, options = {})
86
+ def process(base)
57
87
  base.children.each do |node|
58
- next if node.comment?
88
+ next unless node.element? || node.text?
59
89
 
60
90
  dynamic_key = process_value(node.attr(DYNAMIC_KEY_ATTR), :attr)
61
91
 
@@ -63,17 +93,17 @@ class HTX
63
93
  text = (node.text? ? node : node.children).text
64
94
 
65
95
  if (value = process_value(text))
66
- append(js,
96
+ append(
67
97
  "#{indent(text[LEADING_WHITESPACE])}"\
68
98
  "htx.node(#{[
69
99
  value,
70
100
  dynamic_key,
71
- ((options[:static_key] += 1) << FLAG_BITS) | TEXT_NODE,
101
+ ((@static_key += 1) << FLAG_BITS) | TEXT_NODE,
72
102
  ].compact.join(', ')})"\
73
103
  "#{indent(text[TRAILING_WHITESPACE])}"
74
104
  )
75
105
  else
76
- append(js, indent(text))
106
+ append(indent(text))
77
107
  end
78
108
  else
79
109
  attrs = node.attributes.inject([]) do |attrs, (_, attr)|
@@ -83,50 +113,50 @@ class HTX
83
113
  attrs << process_value(attr.value, :attr)
84
114
  end
85
115
 
86
- append(js, "htx.node(#{[
116
+ append("htx.node(#{[
87
117
  "'#{TAG_MAP[node.name] || node.name}'",
88
118
  attrs,
89
119
  dynamic_key,
90
- ((options[:static_key] += 1) << FLAG_BITS) | (node.children.empty? ? CHILDLESS : 0),
120
+ ((@static_key += 1) << FLAG_BITS) | (node.children.empty? ? CHILDLESS : 0),
91
121
  ].compact.flatten.join(', ')})")
92
122
 
93
123
  unless node.children.empty?
94
- process(node, js, options)
124
+ process(node)
95
125
 
96
126
  count = ''
97
- js.sub!(CLOSE_STATEMENT) do
127
+ @compiled.sub!(CLOSE_STATEMENT) do
98
128
  count = $1 == '' ? 2 : $1.to_i + 1
99
129
  $2
100
130
  end
101
131
 
102
- append(js, "htx.close(#{count})")
132
+ append("htx.close(#{count})")
103
133
  end
104
134
  end
105
135
  end
106
136
  end
107
137
 
108
138
  ##
109
- # Appends a string to the compiled template function string with appropriate punctuation and/or
110
- # whitespace inserted.
139
+ # Appends a string to the compiled template function string with appropriate punctuation and/or whitespace
140
+ # inserted.
111
141
  #
112
- def self.append(js, text)
113
- if js == ''
142
+ def append(text)
143
+ if @compiled == ''
114
144
  # 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 << ' '
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 << ' '
121
151
  end
122
152
 
123
- js << text
153
+ @compiled << text
124
154
  end
125
155
 
126
156
  ##
127
157
  # Indents each line of a string (except the first).
128
158
  #
129
- def self.indent(text)
159
+ def indent(text)
130
160
  return '' unless text
131
161
 
132
162
  text.gsub!(NEWLINE_NON_BLANK, "\n ")
@@ -137,7 +167,7 @@ class HTX
137
167
  # Processes, formats, and encodes an attribute or text node value. Returns nil if the value is determined
138
168
  # to be a control statement.
139
169
  #
140
- def self.process_value(text, is_attr = false)
170
+ def process_value(text, is_attr = false)
141
171
  return nil if text.nil? || (!is_attr && text.strip == '')
142
172
 
143
173
  if (value = text[RAW_VALUE, 1])
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.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Pickens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-03 00:00:00.000000000 Z
11
+ date: 2019-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri