htx 0.0.1 → 0.0.2

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