html2haml 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ require 'cgi'
2
+ require 'erubis'
3
+ require 'ruby_parser'
4
+
5
+ module Haml
6
+ class HTML
7
+ # A class for converting ERB code into a format that's easier
8
+ # for the {Haml::HTML} Hpricot-based parser to understand.
9
+ #
10
+ # Uses [Erubis](http://www.kuwata-lab.com/erubis)'s extensible parsing powers
11
+ # to parse the ERB in a reliable way,
12
+ # and [ruby_parser](http://parsetree.rubyforge.org/)'s Ruby knowledge
13
+ # to figure out whether a given chunk of Ruby code starts a block or not.
14
+ #
15
+ # The ERB tags are converted to HTML tags in the following way.
16
+ # `<% ... %>` is converted into `<haml:silent> ... </haml:silent>`.
17
+ # `<%= ... %>` is converted into `<haml:loud> ... </haml:loud>`.
18
+ # Finally, if either of these opens a Ruby block,
19
+ # `<haml:block> ... </haml:block>` will wrap the entire contents of the block -
20
+ # that is, everything that should be indented beneath the previous silent or loud tag.
21
+ class ERB < Erubis::Basic::Engine
22
+ # Compiles an ERB template into a HTML document containing `haml:` tags.
23
+ #
24
+ # @param template [String] The ERB template
25
+ # @return [String] The output document
26
+ # @see Haml::HTML::ERB
27
+ def self.compile(template)
28
+ new(template).src
29
+ end
30
+
31
+ # `html2haml` doesn't support HTML-escaped expressions.
32
+ def escaped_expr(code)
33
+ raise Haml::Error.new("html2haml doesn't support escaped expressions.")
34
+ end
35
+
36
+ # The ERB-to-Hamlized-HTML conversion has no preamble.
37
+ def add_preamble(src); end
38
+
39
+ # The ERB-to-Hamlized-HTML conversion has no postamble.
40
+ def add_postamble(src); end
41
+
42
+ # Concatenates the text onto the source buffer.
43
+ #
44
+ # @param src [String] The source buffer
45
+ # @param text [String] The raw text to add to the buffer
46
+ def add_text(src, text)
47
+ src << text
48
+ end
49
+
50
+ # Concatenates a silent Ruby statement onto the source buffer.
51
+ # This uses the `<haml:silent>` tag,
52
+ # and may close and/or open a Ruby block with the `<haml:block>` tag.
53
+ #
54
+ # In particular, a block is closed if this statement is some form of `end`,
55
+ # opened if it's a block opener like `do`, `if`, or `begin`,
56
+ # and both closed and opened if it's a mid-block keyword
57
+ # like `else` or `when`.
58
+ #
59
+ # @param src [String] The source buffer
60
+ # @param code [String] The Ruby statement to add to the buffer
61
+ def add_stmt(src, code)
62
+ src << '</haml:block>' if block_closer?(code) || mid_block?(code)
63
+ src << '<haml:silent>' << h(code) << '</haml:silent>' unless code.strip == "end"
64
+ src << '<haml:block>' if block_opener?(code) || mid_block?(code)
65
+ end
66
+
67
+ # Concatenates a Ruby expression that's printed to the document
68
+ # onto the source buffer.
69
+ # This uses the `<haml:silent>` tag,
70
+ # and may open a Ruby block with the `<haml:block>` tag.
71
+ # An expression never closes a block.
72
+ #
73
+ # @param src [String] The source buffer
74
+ # @param code [String] The Ruby expression to add to the buffer
75
+ def add_expr_literal(src, code)
76
+ src << '<haml:loud>' << h(code) << '</haml:loud>'
77
+ src << '<haml:block>' if block_opener?(code)
78
+ end
79
+
80
+ # `html2haml` doesn't support debugging expressions.
81
+ def add_expr_debug(src, code)
82
+ raise Haml::Error.new("html2haml doesn't support debugging expressions.")
83
+ end
84
+
85
+ private
86
+
87
+ # HTML-escaped some text (in practice, always Ruby code).
88
+ # A utility method.
89
+ #
90
+ # @param text [String] The text to escape
91
+ # @return [String] The escaped text
92
+ def h(text)
93
+ CGI.escapeHTML(text)
94
+ end
95
+
96
+ # Returns whether the code is valid Ruby code on its own.
97
+ #
98
+ # @param code [String] Ruby code to check
99
+ # @return [Boolean]
100
+ def valid_ruby?(code)
101
+ RubyParser.new.parse(code)
102
+ rescue Racc::ParseError
103
+ false
104
+ end
105
+
106
+ # Returns whether the code has any content
107
+ # This is used to test whether lines have been removed by erubis, such as comments
108
+ #
109
+ # @param code [String] Ruby code to check
110
+ # @return [Boolean]
111
+ def has_code?(code)
112
+ code != "\n"
113
+ end
114
+
115
+ # Checks if a string of Ruby code opens a block.
116
+ # This could either be something like `foo do |a|`
117
+ # or a keyword that requires a matching `end`
118
+ # like `if`, `begin`, or `case`.
119
+ #
120
+ # @param code [String] Ruby code to check
121
+ # @return [Boolean]
122
+ def block_opener?(code)
123
+ return unless has_code?(code)
124
+ valid_ruby?(code + "\nend") ||
125
+ valid_ruby?(code + "\nwhen foo\nend")
126
+ end
127
+
128
+ # Checks if a string of Ruby code closes a block.
129
+ # This is always `end` followed optionally by some method calls.
130
+ #
131
+ # @param code [String] Ruby code to check
132
+ # @return [Boolean]
133
+ def block_closer?(code)
134
+ return unless has_code?(code)
135
+ valid_ruby?("begin\n" + code)
136
+ end
137
+
138
+ # Checks if a string of Ruby code comes in the middle of a block.
139
+ # This could be a keyword like `else`, `rescue`, or `when`,
140
+ # or even `end` with a method call that takes a block.
141
+ #
142
+ # @param code [String] Ruby code to check
143
+ # @return [Boolean]
144
+ def mid_block?(code)
145
+ return unless has_code?(code)
146
+ return if valid_ruby?(code)
147
+ valid_ruby?("if foo\n#{code}\nend") || # else, elsif
148
+ valid_ruby?("begin\n#{code}\nend") || # rescue, ensure
149
+ valid_ruby?("case foo\n#{code}\nend") # when
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,3 @@
1
+ module Html2haml
2
+ VERSION = "1.0.0.beta.1"
3
+ end
@@ -0,0 +1,477 @@
1
+ require 'test_helper'
2
+
3
+ class ErbTest < MiniTest::Unit::TestCase
4
+ def test_erb
5
+ assert_equal '- foo = bar', render_erb('<% foo = bar %>')
6
+ assert_equal '- foo = bar', render_erb('<% foo = bar -%>')
7
+ assert_equal '= h @item.title', render_erb('<%=h @item.title %>')
8
+ assert_equal '= h @item.title', render_erb('<%=h @item.title -%>')
9
+ end
10
+
11
+ def test_inline_erb
12
+ assert_equal("%p= foo", render_erb("<p><%= foo %></p>"))
13
+ end
14
+
15
+ def test_non_inline_erb
16
+ assert_equal(<<HAML.rstrip, render_erb(<<HTML))
17
+ %p
18
+ = foo
19
+ HAML
20
+ <p>
21
+ <%= foo %>
22
+ </p>
23
+ HTML
24
+ assert_equal(<<HAML.rstrip, render_erb(<<HTML))
25
+ %p
26
+ = foo
27
+ HAML
28
+ <p>
29
+ <%= foo %></p>
30
+ HTML
31
+ assert_equal(<<HAML.rstrip, render_erb(<<HTML))
32
+ %p
33
+ = foo
34
+ HAML
35
+ <p><%= foo %>
36
+ </p>
37
+ HTML
38
+ end
39
+
40
+ def test_erb_in_cdata
41
+ assert_equal(<<HAML.rstrip, render_erb(<<HTML))
42
+ :cdata
43
+ Foo \#{bar} baz
44
+ HAML
45
+ <![CDATA[Foo <%= bar %> baz]]>
46
+ HTML
47
+ end
48
+
49
+ def test_erb_in_script
50
+ assert_equal(<<HAML.rstrip, render_erb(<<HTML))
51
+ :javascript
52
+ function foo() {
53
+ return \#{foo.to_json};
54
+ }
55
+ HAML
56
+ <script type="text/javascript">
57
+ function foo() {
58
+ return <%= foo.to_json %>;
59
+ }
60
+ </script>
61
+ HTML
62
+ end
63
+
64
+ def test_erb_in_style
65
+ assert_equal(<<HAML.rstrip, render_erb(<<HTML))
66
+ :css
67
+ foo {
68
+ bar: \#{"baz"};
69
+ }
70
+ HAML
71
+ <style type="text/css">
72
+ foo {
73
+ bar: <%= "baz" %>;
74
+ }
75
+ </style>
76
+ HTML
77
+ end
78
+
79
+ def test_erb_in_line
80
+ assert_equal 'foo bar #{baz}', render_erb('foo bar <%= baz %>')
81
+ assert_equal 'foo bar #{baz}! Bang.', render_erb('foo bar <%= baz %>! Bang.')
82
+ end
83
+
84
+ def test_erb_multi_in_line
85
+ assert_equal('foo bar #{baz}! Bang #{bop}.',
86
+ render_erb('foo bar <%= baz %>! Bang <%= bop %>.'))
87
+ assert_equal('foo bar #{baz}#{bop}!',
88
+ render_erb('foo bar <%= baz %><%= bop %>!'))
89
+ end
90
+
91
+ def test_erb_with_html_special_chars
92
+ assert_equal '= 3 < 5 ? "OK" : "Your computer is b0rken"',
93
+ render_erb('<%= 3 < 5 ? "OK" : "Your computer is b0rken" %>')
94
+ end
95
+
96
+ def test_erb_in_class_attribute
97
+ assert_equal "%div{:class => dyna_class} I have a dynamic attribute",
98
+ render_erb('<div class="<%= dyna_class %>">I have a dynamic attribute</div>')
99
+ end
100
+
101
+ def test_erb_in_id_attribute
102
+ assert_equal "%div{:id => dyna_id} I have a dynamic attribute",
103
+ render_erb('<div id="<%= dyna_id %>">I have a dynamic attribute</div>')
104
+ end
105
+
106
+ def test_erb_in_attribute_results_in_string_interpolation
107
+ assert_equal('%div{:id => "item_#{i}"} Ruby string interpolation FTW',
108
+ render_erb('<div id="item_<%= i %>">Ruby string interpolation FTW</div>'))
109
+ end
110
+
111
+ def test_erb_in_attribute_with_trailing_content
112
+ assert_equal('%div{:class => "#{12}!"} Bang!',
113
+ render_erb('<div class="<%= 12 %>!">Bang!</div>'))
114
+ end
115
+
116
+ def test_erb_in_html_escaped_attribute
117
+ assert_equal '%div{:class => "foo"} Bang!',
118
+ render_erb('<div class="<%= "foo" %>">Bang!</div>')
119
+ end
120
+
121
+ def test_erb_in_attribute_to_multiple_interpolations
122
+ assert_equal('%div{:class => "#{12} + #{13}"} Math is super',
123
+ render_erb('<div class="<%= 12 %> + <%= 13 %>">Math is super</div>'))
124
+ end
125
+
126
+ def test_whitespace_eating_erb_tags
127
+ assert_equal '- form_for', render_erb('<%- form_for -%>')
128
+ end
129
+
130
+ def test_interpolation_in_erb
131
+ assert_equal('= "Foo #{bar} baz"', render_erb('<%= "Foo #{bar} baz" %>'))
132
+ end
133
+
134
+ def test_interpolation_in_erb_attrs
135
+ assert_equal('%p{:foo => "#{bar} baz"}',
136
+ render_erb('<p foo="<%= "#{bar} baz" %>"></p>'))
137
+ end
138
+
139
+ def test_multiline_erb_silent_script
140
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
141
+ .blah
142
+ - foo
143
+ - bar
144
+ - baz
145
+ %p foo
146
+ HAML
147
+ <div class="blah">
148
+ <%
149
+ foo
150
+ bar
151
+ baz
152
+ %>
153
+ <p>foo</p>
154
+ </div>
155
+ ERB
156
+ end
157
+
158
+ def test_multiline_erb_loud_script
159
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
160
+ .blah
161
+ = foo + |
162
+ bar.baz.bang + |
163
+ baz |
164
+ %p foo
165
+ HAML
166
+ <div class="blah">
167
+ <%=
168
+ foo +
169
+ bar.baz.bang +
170
+ baz
171
+ %>
172
+ <p>foo</p>
173
+ </div>
174
+ ERB
175
+ end
176
+
177
+ def test_weirdly_indented_multiline_erb_loud_script
178
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
179
+ .blah
180
+ = foo + |
181
+ bar.baz.bang + |
182
+ baz |
183
+ %p foo
184
+ HAML
185
+ <div class="blah">
186
+ <%=
187
+ foo +
188
+ bar.baz.bang +
189
+ baz
190
+ %>
191
+ <p>foo</p>
192
+ </div>
193
+ ERB
194
+ end
195
+
196
+ def test_two_multiline_erb_loud_scripts
197
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
198
+ .blah
199
+ = foo + |
200
+ bar.baz.bang + |
201
+ baz |
202
+ -#
203
+ = foo.bar do |
204
+ bang |
205
+ end |
206
+ %p foo
207
+ HAML
208
+ <div class="blah">
209
+ <%=
210
+ foo +
211
+ bar.baz.bang +
212
+ baz
213
+ %>
214
+ <%= foo.bar do
215
+ bang
216
+ end %>
217
+ <p>foo</p>
218
+ </div>
219
+ ERB
220
+ end
221
+
222
+ def test_multiline_then_single_line_erb_loud_scripts
223
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
224
+ .blah
225
+ = foo + |
226
+ bar.baz.bang + |
227
+ baz |
228
+ = foo.bar
229
+ %p foo
230
+ HAML
231
+ <div class="blah">
232
+ <%=
233
+ foo +
234
+ bar.baz.bang +
235
+ baz
236
+ %>
237
+ <%= foo.bar %>
238
+ <p>foo</p>
239
+ </div>
240
+ ERB
241
+ end
242
+
243
+ def test_multiline_erb_but_really_single_line
244
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
245
+ .blah
246
+ = foo
247
+ %p foo
248
+ HAML
249
+ <div class="blah">
250
+ <%=
251
+ foo
252
+ %>
253
+ <p>foo</p>
254
+ </div>
255
+ ERB
256
+ end
257
+
258
+ ### Block Parsing
259
+
260
+ def test_block_parsing
261
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
262
+ - foo do
263
+ %p bar
264
+ HAML
265
+ <% foo do %>
266
+ <p>bar</p>
267
+ <% end %>
268
+ ERB
269
+ end
270
+
271
+ def test_block_parsing_with_args
272
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
273
+ - foo do |a, b, c|
274
+ %p bar
275
+ HAML
276
+ <% foo do |a, b, c| %>
277
+ <p>bar</p>
278
+ <% end %>
279
+ ERB
280
+ end
281
+
282
+ def test_block_parsing_with_equals
283
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
284
+ = foo do
285
+ %p bar
286
+ HAML
287
+ <%= foo do %>
288
+ <p>bar</p>
289
+ <% end %>
290
+ ERB
291
+ end
292
+
293
+ def test_block_parsing_with_modified_end
294
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
295
+ - foo do
296
+ blah
297
+ - end.bip
298
+ HAML
299
+ <% foo do %>
300
+ blah
301
+ <% end.bip %>
302
+ ERB
303
+ end
304
+
305
+ def test_block_parsing_with_modified_end_with_block
306
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
307
+ - foo do
308
+ blah
309
+ - end.bip do
310
+ brang
311
+ HAML
312
+ <% foo do %>
313
+ blah
314
+ <% end.bip do %>
315
+ brang
316
+ <% end %>
317
+ ERB
318
+ end
319
+
320
+ def test_multiline_block_opener
321
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
322
+ - foo bar
323
+ - baz bang
324
+ - biddle do
325
+ foo
326
+ HAML
327
+ <% foo bar
328
+ baz bang
329
+ biddle do %>
330
+ foo
331
+ <% end %>
332
+ ERB
333
+ end
334
+
335
+ def test_if_elsif_else_parsing
336
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
337
+ - if foo
338
+ %p bar
339
+ - elsif bar.foo("zip")
340
+ #bang baz
341
+ - else
342
+ %strong bibble
343
+ HAML
344
+ <% if foo %>
345
+ <p>bar</p>
346
+ <% elsif bar.foo("zip") %>
347
+ <div id="bang">baz</div>
348
+ <% else %>
349
+ <strong>bibble</strong>
350
+ <% end %>
351
+ ERB
352
+ end
353
+
354
+ def test_case_when_parsing
355
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
356
+ - case foo.bar
357
+ - when "bip"
358
+ %p bip
359
+ - when "bop"
360
+ %p BOP
361
+ - when bizzle.bang.boop.blip
362
+ %em BIZZLE BANG BOOP BLIP
363
+ HAML
364
+ <% case foo.bar %>
365
+ <% when "bip" %>
366
+ <p>bip</p>
367
+ <% when "bop" %>
368
+ <p>BOP</p>
369
+ <% when bizzle.bang.boop.blip %>
370
+ <em>BIZZLE BANG BOOP BLIP</em>
371
+ <% end %>
372
+ ERB
373
+
374
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
375
+ - case foo.bar
376
+ - when "bip"
377
+ %p bip
378
+ - when "bop"
379
+ %p BOP
380
+ - when bizzle.bang.boop.blip
381
+ %em BIZZLE BANG BOOP BLIP
382
+ HAML
383
+ <% case foo.bar
384
+ when "bip" %>
385
+ <p>bip</p>
386
+ <% when "bop" %>
387
+ <p>BOP</p>
388
+ <% when bizzle.bang.boop.blip %>
389
+ <em>BIZZLE BANG BOOP BLIP</em>
390
+ <% end %>
391
+ ERB
392
+ end
393
+
394
+ def test_begin_rescue_ensure
395
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
396
+ - begin
397
+ %p a
398
+ - rescue FooException => e
399
+ %p b
400
+ - ensure
401
+ %p c
402
+ HAML
403
+ <% begin %>
404
+ <p>a</p>
405
+ <% rescue FooException => e %>
406
+ <p>b</p>
407
+ <% ensure %>
408
+ <p>c</p>
409
+ <% end %>
410
+ ERB
411
+ end
412
+
413
+ # Regression
414
+
415
+ def test_tag_inside_block
416
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
417
+ %table
418
+ - foo.each do
419
+ %tr
420
+ HAML
421
+ <table>
422
+ <% foo.each do %>
423
+ <tr></tr>
424
+ <% end %>
425
+ </table>
426
+ ERB
427
+ end
428
+
429
+ def test_silent_inside_block_inside_tag
430
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
431
+ %table
432
+ - foo.each do
433
+ - haml_puts "foo"
434
+ HAML
435
+ <table>
436
+ <% foo.each do %>
437
+ <% haml_puts "foo" %>
438
+ <% end %>
439
+ </table>
440
+ ERB
441
+ end
442
+
443
+ def test_commented_erb_should_not_cause_indentation
444
+ assert_equal(<<HAML.rstrip, render_erb(<<ERB))
445
+ %title
446
+ html2haml and multiline titles
447
+ = # stylesheet_link_tag :first
448
+ = stylesheet_link_tag 'another file'
449
+ HAML
450
+ <title>
451
+ html2haml and multiline titles
452
+ </title>
453
+ <%=# stylesheet_link_tag :first %>
454
+ <%#= stylesheet_link_tag :second %>
455
+ <%# stylesheet_link_tag :third %>
456
+ <%= stylesheet_link_tag 'another file' %>
457
+ ERB
458
+ end
459
+
460
+ def test_should_wrap_in_silent
461
+ assert_equal(<<HTML.rstrip, Haml::HTML::ERB.new(<<ERB).src)
462
+ <haml:silent> some_variable_or_function \n</haml:silent>
463
+ HTML
464
+ <% some_variable_or_function %>
465
+ ERB
466
+ end
467
+
468
+ #comment content is removed by erubis
469
+ def test_should_wrap_process_comments_as_empty_lines
470
+ assert_equal(<<HTML.rstrip, Haml::HTML::ERB.new(<<ERB).src)
471
+ <haml:silent>\n</haml:silent>
472
+ HTML
473
+ <%# some_variable_or_function %>
474
+ ERB
475
+ end
476
+
477
+ end