syntax_tree-haml 1.2.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b81fe1099e22a95015983cabc44340820430e2b1273d89b8042cd5c0799e9eec
4
- data.tar.gz: 81dac915a833b4a8a4fd82e5f14dc1b98b6db5d94733701c57b18cc9d86cd983
3
+ metadata.gz: 0dfb86a00b3723b124c178e28e4a6af8a604aba895a495324ea151732449bde3
4
+ data.tar.gz: d597c33107dc7d04b1b52dbe645b2b8b0bf0830e0dc328f83971e95305dab32f
5
5
  SHA512:
6
- metadata.gz: fd3c147a0e384a3b95196ed051420f8d873c960bc28af8df964b9fa0e5550adcf1072c1f76b4b39675ccdbdc8099fd7fb02ec1e5c6062a028ee4f0317afca19e
7
- data.tar.gz: 764aa4c5a07616dd5a6a43f8dde3efa0b61490573ff414b5c271a2ee25b417def1b8586b7ce7bfaa4d51871bb8bd66356e826fc0131b9cabadf0b31e441a5183
6
+ metadata.gz: b6c225f3fcbd9af942f4a6cc00ca8a018a9ebec362b18e987f3b38895164da276ceec928fc02303fcb5055b4a4d787e0224d37cea9402de94f3310171e290c87
7
+ data.tar.gz: 40ae9ef5f96374fed98f1e00302bfda455e3b279f4f653c733cd806bdc52af37ee4f56bd6464b3a16442dd522a56afc23a8773d07692213d978bad16d68b87ac
@@ -22,7 +22,9 @@ jobs:
22
22
  bundler-cache: true
23
23
  ruby-version: ${{ matrix.ruby }}
24
24
  - name: Test
25
- run: bundle exec rake test
25
+ run: |
26
+ bundle exec rake test
27
+ bundle exec rake stree:check
26
28
  automerge:
27
29
  name: AutoMerge
28
30
  needs: ci
data/CHANGELOG.md CHANGED
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.3.1] - 2022-08-01
10
+
11
+ ### Changed
12
+
13
+ - Use Syntax Tree to handle properly quoting strings.
14
+
15
+ ## [1.3.0] - 2022-07-22
16
+
17
+ ### Added
18
+
19
+ - Support changing the preferred quote through the single quotes plugin.
20
+
21
+ ## [1.2.1] - 2022-07-22
22
+
23
+ ### Changed
24
+
25
+ - Fix formatting for when empty `%div` or `%div` with no attributes and just children is present.
26
+
9
27
  ## [1.2.0] - 2022-05-13
10
28
 
11
29
  ### Added
@@ -36,7 +54,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
36
54
 
37
55
  - 🎉 Initial release! 🎉
38
56
 
39
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.2.0...HEAD
57
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.3.1...HEAD
58
+ [1.3.1]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.3.0...v1.3.1
59
+ [1.3.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.2.1...v1.3.0
60
+ [1.2.1]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.2.0...v1.2.1
40
61
  [1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.1.0...v1.2.0
41
62
  [1.1.0]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.0.1...v1.1.0
42
63
  [1.0.1]: https://github.com/ruby-syntax-tree/syntax_tree-haml/compare/v1.0.0...v1.0.1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree-haml (1.2.0)
4
+ syntax_tree-haml (1.3.1)
5
5
  haml (>= 5.2)
6
6
  prettier_print
7
7
  syntax_tree (>= 2.0.1)
@@ -13,7 +13,7 @@ GEM
13
13
  haml (5.2.2)
14
14
  temple (>= 0.8.0)
15
15
  tilt
16
- minitest (5.15.0)
16
+ minitest (5.16.2)
17
17
  prettier_print (0.1.0)
18
18
  rake (13.0.6)
19
19
  simplecov (0.21.2)
@@ -22,10 +22,10 @@ GEM
22
22
  simplecov_json_formatter (~> 0.1)
23
23
  simplecov-html (0.12.3)
24
24
  simplecov_json_formatter (0.1.4)
25
- syntax_tree (2.5.0)
25
+ syntax_tree (3.2.1)
26
26
  prettier_print
27
27
  temple (0.8.2)
28
- tilt (2.0.10)
28
+ tilt (2.0.11)
29
29
 
30
30
  PLATFORMS
31
31
  ruby
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
+ require "syntax_tree/rake_tasks"
5
6
 
6
7
  Rake::TestTask.new(:test) do |t|
7
8
  t.libs << "test"
@@ -9,4 +10,10 @@ Rake::TestTask.new(:test) do |t|
9
10
  t.test_files = FileList["test/**/*_test.rb"]
10
11
  end
11
12
 
13
+ SOURCE_FILES =
14
+ FileList[%w[Gemfile Rakefile syntax_tree-haml.gemspec lib/**/*.rb test/*.rb]]
15
+
16
+ SyntaxTree::Rake::CheckTask.new { |t| t.source_files = SOURCE_FILES }
17
+ SyntaxTree::Rake::WriteTask.new { |t| t.source_files = SOURCE_FILES }
18
+
12
19
  task default: :test
@@ -0,0 +1,427 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module Haml
5
+ class Format < Visitor
6
+ attr_reader :q
7
+
8
+ def initialize(q)
9
+ @q = q
10
+ end
11
+
12
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#html-comments-
13
+ def visit_comment(node)
14
+ with_children(node) do
15
+ q.text("/")
16
+ q.text("!") if node.value[:revealed]
17
+
18
+ if node.value[:conditional]
19
+ q.text(node.value[:conditional])
20
+ elsif node.value[:text]
21
+ q.text(" #{node.value[:text]}")
22
+ end
23
+ end
24
+ end
25
+
26
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#doctype-
27
+ def visit_doctype(node)
28
+ parts = ["!!!"]
29
+
30
+ parts << if DOCTYPE_TYPES.key?(node.value[:type])
31
+ DOCTYPE_TYPES[node.value[:type]]
32
+ elsif DOCTYPE_VERSIONS.include?(node.value[:version])
33
+ node.value[:version]
34
+ else
35
+ node.value[:type]
36
+ end
37
+
38
+ parts << node.value[:encoding] if node.value[:encoding]
39
+ q.text(parts.join(" "))
40
+ end
41
+
42
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#filter
43
+ def visit_filter(node)
44
+ q.group do
45
+ q.text(":")
46
+ q.text(node.value[:name])
47
+
48
+ q.indent do
49
+ q.breakable(force: true)
50
+
51
+ segments = node.value[:text].strip.split("\n")
52
+ q.seplist(segments, -> { q.breakable(force: true) }) do |segment|
53
+ q.text(segment)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#haml-comments--
60
+ def visit_haml_comment(node)
61
+ q.text("-#")
62
+ text = node.value[:text].strip
63
+
64
+ if text.include?("\n")
65
+ q.indent do
66
+ q.breakable(force: true)
67
+ q.seplist(
68
+ text.split("\n"),
69
+ -> { q.breakable(force: true) }
70
+ ) { |segment| q.text(segment) }
71
+ end
72
+ else
73
+ q.text(" #{text}")
74
+ end
75
+ end
76
+
77
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#plain-text
78
+ def visit_plain(node)
79
+ text = node.value[:text]
80
+ q.text("\\") if escaped?(text)
81
+ q.text(text)
82
+ end
83
+
84
+ # Visit the root node of the AST.
85
+ def visit_root(node)
86
+ node.children.each do |child|
87
+ visit(child)
88
+ q.breakable(force: true)
89
+ end
90
+ end
91
+
92
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
93
+ def visit_script(node)
94
+ with_children(node) do
95
+ q.text("&") if node.value[:escape_html]
96
+
97
+ node.value[:preserve] ? q.text("~") : q.text("=")
98
+
99
+ q.text(" ")
100
+ q.text(node.value[:text].strip)
101
+ end
102
+ end
103
+
104
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#running-ruby--
105
+ def visit_silent_script(node)
106
+ q.group do
107
+ q.text("- ")
108
+ q.text(node.value[:text].strip)
109
+
110
+ node.children.each do |child|
111
+ if continuation?(node, child)
112
+ q.breakable(force: true)
113
+ visit(child)
114
+ else
115
+ q.indent do
116
+ q.breakable(force: true)
117
+ visit(child)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ LiteralHashValue = Struct.new(:value)
125
+
126
+ StringHashValue = Struct.new(:value, :quote)
127
+
128
+ # When formatting a tag, there are a lot of different kinds of things that
129
+ # can be printed out. There's the tag name, the attributes, the content,
130
+ # etc. This object is responsible for housing all of those parts.
131
+ class PartList
132
+ attr_reader :node, :parts
133
+
134
+ def initialize(node)
135
+ @node = node
136
+ @parts = []
137
+ end
138
+
139
+ def <<(part)
140
+ parts << part
141
+ end
142
+
143
+ def empty?
144
+ parts.empty?
145
+ end
146
+
147
+ def format(q)
148
+ if empty? && node.value[:name] == "div"
149
+ # If we don't have any other parts to print and the tag is a div
150
+ # then we need to make sure to add that to the beginning. Otherwise
151
+ # it's implied by the presence of other operators.
152
+ q.text("%div")
153
+ else
154
+ parts.inject(0) do |align, part|
155
+ part.format(q, align)
156
+ align + part.length
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ class PlainPart < Struct.new(:value)
163
+ def format(q, align)
164
+ q.text(value)
165
+ end
166
+
167
+ def length
168
+ value.length
169
+ end
170
+ end
171
+
172
+ class PrefixPart < Struct.new(:prefix, :value)
173
+ def format(q, align)
174
+ q.text("#{prefix}#{value}")
175
+ end
176
+
177
+ def length
178
+ prefix.length + value.length
179
+ end
180
+ end
181
+
182
+ class HTMLAttributesPart
183
+ attr_reader :values
184
+
185
+ def initialize(raw)
186
+ @values =
187
+ raw[1...-1]
188
+ .split(",")
189
+ .to_h { |keypair| keypair[1..-1].split("\" => ") }
190
+ end
191
+
192
+ def format(q, align)
193
+ q.group do
194
+ q.text("(")
195
+ q.nest(align) do
196
+ q.seplist(
197
+ values,
198
+ -> { q.fill_breakable },
199
+ :each_pair
200
+ ) { |key, value| q.text("#{key}=#{value}") }
201
+ end
202
+ q.text(")")
203
+ end
204
+ end
205
+
206
+ def length
207
+ values.sum { |key, value| key.length + value.length + 3 }
208
+ end
209
+ end
210
+
211
+ class HashAttributesPart < Struct.new(:values)
212
+ def format(q, align)
213
+ format_value(q, values)
214
+ end
215
+
216
+ def length
217
+ values.sum do |key, value|
218
+ key.length + (value.is_a?(String) ? value : value.to_s).length + 3
219
+ end
220
+ end
221
+
222
+ private
223
+
224
+ def format_value(q, hash, level = 0)
225
+ quote = SyntaxTree::Formatter::OPTIONS[:quote]
226
+
227
+ q.group do
228
+ q.text("{")
229
+ q.indent do
230
+ q.group do
231
+ q.breakable(level == 0 ? "" : " ")
232
+ q.seplist(hash, nil, :each_pair) do |key, value|
233
+ if key.match?(/^@|[-:]/)
234
+ q.text("#{quote}#{Quotes.normalize(key, quote)}#{quote}:")
235
+ else
236
+ q.text("#{key}:")
237
+ end
238
+
239
+ q.text(" ")
240
+
241
+ case value
242
+ when Hash
243
+ format_value(q, value, level + 1)
244
+ when LiteralHashValue
245
+ q.text(value.value)
246
+ when StringLiteral
247
+ qq = Formatter.new("")
248
+ qq.with_target(q.target) { value.format(qq) }
249
+ when String
250
+ q.text("#{quote}#{Quotes.normalize(value, quote)}#{quote}")
251
+ else
252
+ q.text(value.to_s)
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ q.breakable(level == 0 ? "" : " ")
259
+ q.text("}")
260
+ end
261
+ end
262
+ end
263
+
264
+ # Visit a tag node.
265
+ def visit_tag(node)
266
+ parts = PartList.new(node)
267
+
268
+ # If we have a tag that isn't a div, then we need to print out that
269
+ # name of that tag first. If it is a div, first we'll check if there
270
+ # are any other things that would force us to print out the div
271
+ # explicitly, and otherwise we'll leave it off.
272
+ if node.value[:name] != "div"
273
+ parts << PrefixPart.new("%", node.value[:name])
274
+ end
275
+
276
+ # If we have a class attribute, then we're going to print that here
277
+ # using the special class syntax.
278
+ if node.value[:attributes].key?("class")
279
+ parts << PrefixPart.new(
280
+ ".",
281
+ node.value[:attributes]["class"].tr(" ", ".")
282
+ )
283
+ end
284
+
285
+ # If we have an id attribute, then we're going to print that here
286
+ # using the special id syntax.
287
+ if node.value[:attributes].key?("id")
288
+ parts << PrefixPart.new("#", node.value[:attributes]["id"])
289
+ end
290
+
291
+ # If we're using dynamic attributes on this tag, then they come in as
292
+ # a string that looks like the output of Hash#inspect from Ruby. So
293
+ # here we're going to split it all up and print it out nicely.
294
+ if node.value[:dynamic_attributes].new
295
+ parts << HTMLAttributesPart.new(node.value[:dynamic_attributes].new)
296
+ end
297
+
298
+ # If there are any static attributes that are not class or id (because
299
+ # we already took care of those), then we're going to print them out
300
+ # here.
301
+ static =
302
+ node.value[:attributes].reject do |key, _|
303
+ key == "class" || key == "id"
304
+ end
305
+
306
+ parts << HashAttributesPart.new(static) if static.any?
307
+
308
+ # If there are dynamic attributes that don't use the newer syntax, then
309
+ # we're going to print them out here.
310
+ if node.value[:dynamic_attributes].old
311
+ parts << PlainPart.new("%div") if parts.empty?
312
+
313
+ if ::Haml::AttributeParser.available?
314
+ dynamic = parse_attributes(node.value[:dynamic_attributes].old)
315
+ parts << if dynamic.is_a?(LiteralHashValue)
316
+ PlainPart.new(dynamic.value)
317
+ else
318
+ HashAttributesPart.new(dynamic)
319
+ end
320
+ else
321
+ parts << PlainPart.new(node.value[:dynamic_attributes].old)
322
+ end
323
+ end
324
+
325
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#object-reference-
326
+ if node.value[:object_ref] != :nil
327
+ parts << PlainPart.new("%div") if parts.empty?
328
+ parts << PlainPart.new(node.value[:object_ref])
329
+ end
330
+
331
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#whitespace-removal--and-
332
+ parts << PlainPart.new(">") if node.value[:nuke_outer_whitespace]
333
+ parts << PlainPart.new("<") if node.value[:nuke_inner_whitespace]
334
+
335
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#empty-void-tags-
336
+ parts << PlainPart.new("/") if node.value[:self_closing]
337
+
338
+ # If there is a value part, then we're going to print slightly
339
+ # differently as the value goes after the tag declaration.
340
+ if (value = node.value[:value]) && !value.empty?
341
+ with_children(node) do
342
+ q.group { parts.format(q) }
343
+ q.indent do
344
+ # Split between the declaration of the tag and the contents of the
345
+ # tag.
346
+ q.breakable("")
347
+
348
+ if node.value[:parse] && value.match?(/#[{$@]/)
349
+ # There's a weird case here where if the value includes
350
+ # interpolation and it's marked as { parse: true }, then we
351
+ # don't actually want the = prefix, and we want to remove extra
352
+ # escaping.
353
+ q.if_break { q.text("") }.if_flat { q.text(" ") }
354
+ q.text(value[1...-1].gsub(/\\"/, "\""))
355
+ elsif node.value[:parse]
356
+ q.text("= ")
357
+ q.text(value)
358
+ else
359
+ q.if_break { q.text("") }.if_flat { q.text(" ") }
360
+ q.text(value)
361
+ end
362
+ end
363
+ end
364
+ else
365
+ with_children(node) { parts.format(q) }
366
+ end
367
+ end
368
+
369
+ private
370
+
371
+ # When printing out sequences of silent scripts, sometimes subsequent nodes
372
+ # will be continuations of previous nodes. In that case we want to dedent
373
+ # them to match.
374
+ def continuation?(node, child)
375
+ return false if child.type != :silent_script
376
+
377
+ case [node.value[:keyword], child.value[:keyword]]
378
+ in ["case", "in" | "when" | "else"]
379
+ true
380
+ in ["if" | "unless", "elsif" | "else"]
381
+ true
382
+ else
383
+ false
384
+ end
385
+ end
386
+
387
+ # If a node comes in as the plain type but starts with one of the special
388
+ # characters that haml parses, then we need to escape it with a \ when
389
+ # printing.
390
+ def escaped?(text)
391
+ ::Haml::Parser::SPECIAL_CHARACTERS.any? do |special|
392
+ text.start_with?(special)
393
+ end
394
+ end
395
+
396
+ # Take a source string and attempt to parse it into a set of attributes that
397
+ # can be used to format the source.
398
+ def parse_attributes(source)
399
+ case Ripper.sexp(source)
400
+ in [:program, [[:hash, *], *]] if parsed =
401
+ ::Haml::AttributeParser.parse(source)
402
+ parsed.to_h { |key, value| [key, parse_attributes(value)] }
403
+ in [:program, [[:string_literal, *], *]]
404
+ SyntaxTree.parse(source).statements.body[0]
405
+ else
406
+ LiteralHashValue.new(source)
407
+ end
408
+ end
409
+
410
+ def with_children(node)
411
+ if node.children.empty?
412
+ q.group { yield }
413
+ else
414
+ q.group do
415
+ q.group { yield }
416
+ q.indent do
417
+ node.children.each do |child|
418
+ q.breakable(force: true)
419
+ visit(child)
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
425
+ end
426
+ end
427
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module Haml
5
+ class PrettyPrint < Visitor
6
+ attr_reader :q
7
+
8
+ def initialize(q)
9
+ @q = q
10
+ end
11
+
12
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#html-comments-
13
+ def visit_comment(node)
14
+ group("comment") do
15
+ if node.value[:conditional]
16
+ pp_field("conditional", node.value[:conditional])
17
+ elsif node.value[:text]
18
+ pp_field("text", node.value[:text])
19
+ end
20
+
21
+ bool_field("revealed") if node.value[:revealed]
22
+ pp_field("children", node.children) if node.children.any?
23
+ end
24
+ end
25
+
26
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#doctype-
27
+ def visit_doctype(node)
28
+ group("doctype") do
29
+ if DOCTYPE_TYPES.key?(node.value[:type])
30
+ pp_field("type", node.value[:type])
31
+ elsif DOCTYPE_VERSIONS.include?(node.value[:version])
32
+ pp_field("version", node.value[:version])
33
+ else
34
+ pp_field("text", node.value[:text])
35
+ end
36
+
37
+ pp_field("encoding", node.value[:encoding]) if node.value[:encoding]
38
+ end
39
+ end
40
+
41
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#filter
42
+ def visit_filter(node)
43
+ group("filter") do
44
+ text_field("name", node.value[:name])
45
+ pp_field("text", node.value[:text])
46
+ end
47
+ end
48
+
49
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#haml-comments--
50
+ def visit_haml_comment(node)
51
+ group("haml_comment") { pp_field("text", node.value[:text]) }
52
+ end
53
+
54
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#plain-text
55
+ def visit_plain(node)
56
+ group("plain") { pp_field("text", node.value[:text]) }
57
+ end
58
+
59
+ # Visit the root node of the AST.
60
+ def visit_root(node)
61
+ group("root") do
62
+ pp_field("children", node.children) if node.children.any?
63
+ end
64
+ end
65
+
66
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
67
+ def visit_script(node)
68
+ group("script") do
69
+ pp_field("text", node.value[:text])
70
+ bool_field("escape_html") if node.value[:escape_html]
71
+ bool_field("preserve") if node.value[:preserve]
72
+ pp_field("children", node.children) if node.children.any?
73
+ end
74
+ end
75
+
76
+ # https://haml.info/docs/yardoc/file.REFERENCE.html#running-ruby--
77
+ def visit_silent_script(node)
78
+ group("silent-script") do
79
+ pp_field("text", node.value[:text])
80
+ pp_field("children", node.children) if node.children.any?
81
+ end
82
+ end
83
+
84
+ # Visit a tag node.
85
+ def visit_tag(node)
86
+ group("tag") do
87
+ pp_field("name", node.value[:name])
88
+
89
+ if node.value[:attributes].any?
90
+ pp_field("attributes", node.value[:attributes])
91
+ end
92
+
93
+ if node.value[:dynamic_attributes].new
94
+ pp_field(
95
+ "dynamic_attributes.new",
96
+ node.value[:dynamic_attributes].new
97
+ )
98
+ end
99
+
100
+ if node.value[:dynamic_attributes].old
101
+ pp_field(
102
+ "dynamic_attributes.old",
103
+ node.value[:dynamic_attributes].old
104
+ )
105
+ end
106
+
107
+ if node.value[:object_ref] != :nil
108
+ pp_field("object_ref", node.value[:object_ref])
109
+ end
110
+
111
+ if node.value[:nuke_outer_whitespace]
112
+ bool_field("nuke_outer_whitespace")
113
+ end
114
+
115
+ if node.value[:nuke_inner_whitespace]
116
+ bool_field("nuke_inner_whitespace")
117
+ end
118
+
119
+ bool_field("self_closing") if node.value[:self_closing]
120
+ pp_field("value", node.value[:value]) if node.value[:value]
121
+ pp_field("children", node.children) if node.children.any?
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def bool_field(name)
128
+ q.breakable
129
+ q.text(name)
130
+ end
131
+
132
+ def group(name)
133
+ q.group do
134
+ q.text("(")
135
+ q.text(name)
136
+
137
+ q.nest(2) { yield }
138
+ q.breakable("")
139
+ q.text(")")
140
+ end
141
+ end
142
+
143
+ def pp_field(name, value)
144
+ q.breakable
145
+ q.text(name)
146
+ q.text("=")
147
+ q.pp(value)
148
+ end
149
+
150
+ def text_field(name, value)
151
+ q.breakable
152
+ q.text(name)
153
+ q.text("=")
154
+ q.text(value)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SyntaxTree
4
4
  module Haml
5
- VERSION = "1.2.0"
5
+ VERSION = "1.3.1"
6
6
  end
7
7
  end