haml 4.0.6 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +49 -4
  4. data/FAQ.md +4 -14
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +85 -42
  7. data/REFERENCE.md +109 -58
  8. data/Rakefile +46 -54
  9. data/lib/haml/attribute_builder.rb +163 -0
  10. data/lib/haml/attribute_compiler.rb +215 -0
  11. data/lib/haml/attribute_parser.rb +144 -0
  12. data/lib/haml/buffer.rb +26 -136
  13. data/lib/haml/compiler.rb +87 -295
  14. data/lib/haml/engine.rb +25 -41
  15. data/lib/haml/error.rb +3 -0
  16. data/lib/haml/escapable.rb +49 -0
  17. data/lib/haml/exec.rb +33 -19
  18. data/lib/haml/filters.rb +18 -24
  19. data/lib/haml/generator.rb +41 -0
  20. data/lib/haml/helpers/action_view_extensions.rb +3 -2
  21. data/lib/haml/helpers/action_view_mods.rb +36 -58
  22. data/lib/haml/helpers/action_view_xss_mods.rb +1 -0
  23. data/lib/haml/helpers/safe_erubi_template.rb +27 -0
  24. data/lib/haml/helpers/safe_erubis_template.rb +4 -1
  25. data/lib/haml/helpers/xss_mods.rb +18 -12
  26. data/lib/haml/helpers.rb +133 -90
  27. data/lib/haml/options.rb +38 -47
  28. data/lib/haml/parser.rb +278 -216
  29. data/lib/haml/{template/plugin.rb → plugin.rb} +8 -15
  30. data/lib/haml/railtie.rb +21 -12
  31. data/lib/haml/sass_rails_filter.rb +17 -4
  32. data/lib/haml/template/options.rb +12 -2
  33. data/lib/haml/template.rb +12 -6
  34. data/lib/haml/temple_engine.rb +120 -0
  35. data/lib/haml/temple_line_counter.rb +29 -0
  36. data/lib/haml/util.rb +80 -199
  37. data/lib/haml/version.rb +2 -1
  38. data/lib/haml.rb +1 -0
  39. data/test/attribute_parser_test.rb +101 -0
  40. data/test/engine_test.rb +287 -176
  41. data/test/filters_test.rb +32 -19
  42. data/test/gemfiles/Gemfile.rails-4.0.x +9 -3
  43. data/test/gemfiles/Gemfile.rails-4.0.x.lock +87 -0
  44. data/test/gemfiles/Gemfile.rails-4.1.x +5 -0
  45. data/test/gemfiles/Gemfile.rails-4.2.x +5 -0
  46. data/test/gemfiles/Gemfile.rails-5.0.x +4 -0
  47. data/test/helper_test.rb +224 -112
  48. data/test/options_test.rb +22 -0
  49. data/test/parser_test.rb +71 -4
  50. data/test/results/bemit.xhtml +4 -0
  51. data/test/results/eval_suppressed.xhtml +4 -4
  52. data/test/results/helpers.xhtml +43 -41
  53. data/test/results/helpful.xhtml +6 -3
  54. data/test/results/just_stuff.xhtml +21 -20
  55. data/test/results/list.xhtml +9 -9
  56. data/test/results/nuke_inner_whitespace.xhtml +22 -22
  57. data/test/results/nuke_outer_whitespace.xhtml +84 -92
  58. data/test/results/original_engine.xhtml +17 -17
  59. data/test/results/partial_layout.xhtml +4 -3
  60. data/test/results/partial_layout_erb.xhtml +4 -3
  61. data/test/results/partials.xhtml +11 -10
  62. data/test/results/silent_script.xhtml +63 -63
  63. data/test/results/standard.xhtml +156 -159
  64. data/test/results/tag_parsing.xhtml +19 -19
  65. data/test/results/very_basic.xhtml +2 -2
  66. data/test/results/whitespace_handling.xhtml +77 -76
  67. data/test/template_test.rb +24 -56
  68. data/test/template_test_helper.rb +38 -0
  69. data/test/templates/bemit.haml +3 -0
  70. data/test/templates/just_stuff.haml +1 -0
  71. data/test/templates/standard_ugly.haml +1 -0
  72. data/test/templates/with_bom.haml +1 -0
  73. data/test/temple_line_counter_test.rb +40 -0
  74. data/test/test_helper.rb +26 -8
  75. data/test/util_test.rb +6 -47
  76. metadata +53 -36
  77. data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
  78. data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
  79. data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
  80. data/test/templates/_av_partial_1_ugly.haml +0 -9
  81. data/test/templates/_av_partial_2_ugly.haml +0 -5
  82. data/test/templates/action_view_ugly.haml +0 -47
  83. data/test/templates/standard_ugly.haml +0 -43
data/REFERENCE.md CHANGED
@@ -121,7 +121,7 @@ see {Haml::Options}.
121
121
 
122
122
  ### Encodings
123
123
 
124
- When using Ruby 1.9 or later, Haml supports the same sorts of
124
+ Haml supports the same sorts of
125
125
  encoding-declaration comments that Ruby does. Although both Ruby and Haml
126
126
  support several different styles, the easiest it just to add `-# coding:
127
127
  encoding-name` at the beginning of the Haml template (it must come before all
@@ -233,8 +233,7 @@ is compiled to:
233
233
 
234
234
  <script src='javascripts/script_9' type='text/javascript'></script>
235
235
 
236
- #### `:class` and `:id` Attributes
237
- {#class-and-id-attributes}
236
+ #### `:class` and `:id` Attributes {#class-and-id-attributes}
238
237
 
239
238
  The `:class` and `:id` attributes can also be specified as a Ruby array whose
240
239
  elements will be joined together. A `:class` array is joined with `" "` and an
@@ -306,7 +305,7 @@ hash-style attributes:
306
305
 
307
306
  #### Ruby 1.9-style Hashes
308
307
 
309
- On Ruby 1.9, Haml also supports Ruby's new hash syntax:
308
+ Haml also supports Ruby's new hash syntax:
310
309
 
311
310
  %a{title: @title, href: href} Stuff
312
311
 
@@ -329,7 +328,7 @@ This is compiled to:
329
328
  </html>
330
329
 
331
330
  You can use as many such attribute methods as you want by separating them with
332
- commas, like a Ruby argument list. All the hashes will me merged together, from
331
+ commas, like a Ruby argument list. All the hashes will be merged together, from
333
332
  left to right. For example, if you defined
334
333
 
335
334
  def hash1
@@ -389,27 +388,49 @@ or using `true` and `false`:
389
388
 
390
389
  %input(selected=true)
391
390
 
392
- #### HTML5 Custom Data Attributes
391
+ <!-- The title to the next section (Prefixed Attributes) has changed. This
392
+ <a> tag is so old links to here still work. -->
393
+ <a id="html5_custom_data_attributes" style="border:0;"></a>
393
394
 
394
- HTML5 allows for adding [custom non-visible data
395
- attributes](http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#embedding-custom-non-visible-data)
396
- to elements using attribute names beginning with `data-`. Custom data attributes
397
- can be used in Haml by using the key `:data` with a Hash value in an attribute
398
- hash. Each of the key/value pairs in the Hash will be transformed into a custom
399
- data attribute. For example:
395
+ #### Prefixed Attributes
400
396
 
401
- %a{:href=>"/posts", :data => {:author_id => 123}} Posts By Author
397
+ HTML5 allows for adding
398
+ [custom non-visible data attributes](http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
399
+ to elements using attribute names beginning with `data-`. The
400
+ [Accessible Rich Internet Applications](http://www.w3.org/WAI/intro/aria)
401
+ specification makes use of attributes beginning with `aria-`. There are also
402
+ frameworks that use non-standard attributes with a common prefix.
403
+
404
+ Haml can help generate collections of attributes that share a prefix like
405
+ these. Any entry in an attribute hash that has a Hash as its value is expanded
406
+ into a series of attributes, one for each key/value pair in the hash, with the
407
+ attribute name formed by joining the “parent” key name to the key name with a
408
+ hyphen.
409
+
410
+ For example:
411
+
412
+ %a{:href=>"/posts", :data => {:author_id => 123, :category => 7}} Posts By Author
402
413
 
403
414
  will render as:
404
415
 
405
- <a data-author-id='123' href='/posts'>Posts By Author</a>
416
+ <a data-author-id='123' data-category='7' href='/posts'>Posts By Author</a>
406
417
 
407
418
  Notice that the underscore in `author_id` was replaced by a hyphen. If you wish
408
419
  to suppress this behavior, you can set Haml's
409
420
  {Haml::Options#hyphenate_data_attrs `:hyphenate_data_attrs` option} to `false`,
410
421
  and the output will be rendered as:
411
422
 
412
- <a data-author_id='123' href='/posts'>Posts By Author</a>
423
+ <a data-author_id='123' data-category='7' href='/posts'>Posts By Author</a>
424
+
425
+ This expansion of hashes is recursive – any value of the child hash that is
426
+ itself a hash will create an attribute for each entry, with the attribute name
427
+ prefixed with all ancestor keys. For example:
428
+
429
+ .book-info{:data => {:book => {:id => 123, :genre => 'programming'}, :category => 7}}
430
+
431
+ will render as:
432
+
433
+ <div class='book-info' data-book-genre='programming' data-book-id='123' data-category='7'></div>
413
434
 
414
435
  ### Class and ID: `.` and `#`
415
436
 
@@ -496,23 +517,23 @@ and is compiled to:
496
517
  The forward slash character, when placed at the end of a tag definition, causes
497
518
  Haml to treat it as being an empty (or void) element. Depending on the format,
498
519
  the tag will be rendered either without a closing tag (`:html4` or `:html5`), or
499
- as a self-closing tag (`:xhtml`). For example:
520
+ as a self-closing tag (`:xhtml`).
521
+
522
+ Taking the following as an example:
500
523
 
501
524
  %br/
502
525
  %meta{'http-equiv' => 'Content-Type', :content => 'text/html'}/
503
526
 
504
- is compiled to:
527
+ When the format is `:html4` or `:html5` this is compiled to:
505
528
 
506
529
  <br>
507
530
  <meta content='text/html' http-equiv='Content-Type'>
508
531
 
509
- when the format is `:html4` or `:html5`, and to
532
+ and when the format is `:xhtml` it is compiled to:
510
533
 
511
534
  <br />
512
535
  <meta content='text/html' http-equiv='Content-Type' />
513
536
 
514
- when the format is `:xhtml`.
515
-
516
537
  Some tags are automatically treated as being empty, as long as they have no
517
538
  content in the Haml source. `meta`, `img`, `link`, `br`, `hr`, `input`,
518
539
  `area`, `param`, `col` and `base` tags are treated as empty by default. This
@@ -779,6 +800,21 @@ is compiled to:
779
800
  </a>
780
801
  <![endif]-->
781
802
 
803
+ To generate “downlevel-revealed” conditional comments, where the content is
804
+ hidden from IE but not other browsers, add a `!` before the brackets: `/![]`.
805
+ Haml will produce valid HTML when generating this kind of conditional comment.
806
+
807
+ For example:
808
+
809
+ /![if !IE]
810
+ You are not using Internet Explorer, or are using version 10+.
811
+
812
+ is compiled to:
813
+
814
+ <!--[if !IE]><!-->
815
+ You are not using Internet Explorer, or are using version 10+.
816
+ <!--<![endif]-->
817
+
782
818
  ### Haml Comments: `-#`
783
819
 
784
820
  The hyphen followed immediately by the pound sign signifies a silent comment.
@@ -985,6 +1021,21 @@ might compile to:
985
1021
  //]]>
986
1022
  </script>
987
1023
 
1024
+ #### Gotchas
1025
+
1026
+ Haml uses an overly simplistic regular expression to identify string
1027
+ interpolation rather than a full-blown Ruby parser. This is fast and works for
1028
+ most code but you may have errors with code like the following:
1029
+
1030
+ %span #{'{'}
1031
+
1032
+ This code will generate a syntax error, complaining about unbalanced brackets.
1033
+ In cases like this, the recommended workaround is output the code as a Ruby
1034
+ string to force Haml to parse the code with Ruby.
1035
+
1036
+ %span= "#{'{'}"
1037
+
1038
+
988
1039
  ### Escaping HTML: `&=` {#escaping_html}
989
1040
 
990
1041
  An ampersand followed by one or two equals characters evaluates Ruby code just
@@ -1087,53 +1138,53 @@ more info.
1087
1138
 
1088
1139
  Haml comes with the following filters defined:
1089
1140
 
1090
- {#cdata-filter}
1091
- ### `:cdata`
1141
+ ### `:cdata` {#cdata-filter}
1142
+
1092
1143
  Surrounds the filtered text with CDATA tags.
1093
1144
 
1094
- {#coffee-filter}
1095
- ### `:coffee`
1096
- Compiles the filtered text to Javascript using Cofeescript. You can also
1145
+ ### `:coffee` {#coffee-filter}
1146
+
1147
+ Compiles the filtered text to Javascript using Coffeescript. You can also
1097
1148
  reference this filter as `:coffeescript`. This filter is implemented using
1098
1149
  Tilt.
1099
1150
 
1100
- {#css-filter}
1101
- ### `:css`
1151
+ ### `:css` {#css-filter}
1152
+
1102
1153
  Surrounds the filtered text with `<style>` and (optionally) CDATA tags. Useful
1103
1154
  for including inline CSS. Use the {Haml::Options#cdata `:cdata` option} to
1104
1155
  control when CDATA tags are added.
1105
1156
 
1106
- {#erb-filter}
1107
- ### `:erb`
1108
- Parses the filtered text with ERb, like an RHTML template. Not available if the
1157
+ ### `:erb` {#erb-filter}
1158
+
1159
+ Parses the filtered text with ERB, like an RHTML template. Not available if the
1109
1160
  {Haml::Options#suppress_eval `:suppress_eval`} option is set to true. Embedded
1110
1161
  Ruby code is evaluated in the same context as the Haml template. This filter is
1111
1162
  implemented using Tilt.
1112
1163
 
1113
- {#escaped-filter}
1114
- ### `:escaped`
1164
+ ### `:escaped` {#escaped-filter}
1165
+
1115
1166
  Works the same as plain, but HTML-escapes the text
1116
1167
  before placing it in the document.
1117
1168
 
1118
- {#javascript-filter}
1119
- ### `:javascript`
1169
+ ### `:javascript` {#javascript-filter}
1170
+
1120
1171
  Surrounds the filtered text with `<script>` and (optionally) CDATA tags.
1121
1172
  Useful for including inline Javascript. Use the {Haml::Options#cdata `:cdata`
1122
1173
  option} to control when CDATA tags are added.
1123
1174
 
1124
- {#less-filter}
1125
- ### `:less`
1175
+ ### `:less` {#less-filter}
1176
+
1126
1177
  Parses the filtered text with [Less](http://lesscss.org/) to produce CSS output.
1127
1178
  This filter is implemented using Tilt.
1128
1179
 
1129
- {#markdown-filter}
1130
- ### `:markdown`
1180
+ ### `:markdown` {#markdown-filter}
1181
+
1131
1182
  Parses the filtered text with
1132
1183
  [Markdown](http://daringfireball.net/projects/markdown). This filter is
1133
1184
  implemented using Tilt.
1134
1185
 
1135
- {#maruku-filter}
1136
- ### `:maruku`
1186
+ ### `:maruku` {#maruku-filter}
1187
+
1137
1188
  Parses the filtered text with [Maruku](https://github.com/nex3/maruku), which
1138
1189
  has some non-standard extensions to Markdown.
1139
1190
 
@@ -1142,39 +1193,39 @@ contrib](https://github.com/haml/haml-contrib) but is loaded automatically for
1142
1193
  historical reasons. In future versions of Haml it will likely not be loaded by
1143
1194
  default. This filter is implemented using Tilt.
1144
1195
 
1145
- {#plain-filter}
1146
- ### `:plain`
1196
+ ### `:plain` {#plain-filter}
1197
+
1147
1198
  Does not parse the filtered text. This is useful for large blocks of text
1148
1199
  without HTML tags, when you don't want lines starting with `.` or `-` to be
1149
1200
  parsed.
1150
1201
 
1151
- {#preserve-filter}
1152
- ### `:preserve`
1202
+ ### `:preserve` {#preserve-filter}
1203
+
1153
1204
  Inserts the filtered text into the template with whitespace preserved.
1154
1205
  `preserve`d blocks of text aren't indented, and newlines are replaced with the
1155
1206
  HTML escape code for newlines, to preserve nice-looking output. See also
1156
1207
  [Whitespace Preservation](#whitespace_preservation).
1157
1208
 
1158
- {#ruby-filter}
1159
- ### `:ruby`
1209
+ ### `:ruby` {#ruby-filter}
1210
+
1160
1211
  Parses the filtered text with the normal Ruby interpreter. Creates an `IO`
1161
1212
  object named `haml_io`, anything written to it is output into the Haml document.
1162
1213
  Not available if the {Haml::Options#suppress_eval `:suppress_eval`} option is
1163
1214
  set to true. The Ruby code is evaluated in the same context as the Haml
1164
1215
  template.
1165
1216
 
1166
- {#sass-filter}
1167
- ### `:sass`
1217
+ ### `:sass` {#sass-filter}
1218
+
1168
1219
  Parses the filtered text with [Sass](http://sass-lang.com/) to produce CSS
1169
1220
  output. This filter is implemented using Tilt.
1170
1221
 
1171
- {#scss-filter}
1172
- ### `:scss`
1222
+ ### `:scss` {#scss-filter}
1223
+
1173
1224
  Parses the filtered text with Sass like the `:sass` filter, but uses the newer
1174
1225
  SCSS syntax to produce CSS output. This filter is implemented using Tilt.
1175
1226
 
1176
- {#textile-filter}
1177
- ### `:textile`
1227
+ ### `:textile` {#textile-filter}
1228
+
1178
1229
  Parses the filtered text with [Textile](http://www.textism.com/tools/textile).
1179
1230
  Only works if [RedCloth](http://redcloth.org) is installed.
1180
1231
 
@@ -1194,8 +1245,8 @@ the whitespace removal methods allow. There are a few helper methods that are
1194
1245
  useful when dealing with inline content. All these methods take a Haml block to
1195
1246
  modify.
1196
1247
 
1197
- {#surround}
1198
- ### surround
1248
+ ### surround {#surround}
1249
+
1199
1250
  Surrounds a Haml block with text. Expects 1 or 2 string arguments used to
1200
1251
  surround the Haml block. If a second argument is not provided, the first
1201
1252
  argument is used as the second.
@@ -1203,15 +1254,15 @@ argument is used as the second.
1203
1254
  = surround "(", ")" do
1204
1255
  = link_to "learn more", "#"
1205
1256
 
1206
- {#precede}
1207
- ### precede
1257
+ ### precede {#precede}
1258
+
1208
1259
  Prepends a Haml block with text. Expects 1 argument.
1209
1260
 
1210
1261
  = precede "*" do
1211
1262
  %span Required
1212
1263
 
1213
- {#succeed}
1214
- ### succeed
1264
+ ### succeed {#succeed}
1265
+
1215
1266
  Appends a Haml block with text. Expects 1 argument.
1216
1267
 
1217
1268
  Begin by
data/Rakefile CHANGED
@@ -1,45 +1,38 @@
1
1
  require "rake/clean"
2
2
  require "rake/testtask"
3
- require "rubygems/package_task"
3
+ require "bundler/gem_tasks"
4
4
 
5
5
  task :default => :test
6
6
 
7
- CLEAN.replace %w(pkg doc coverage .yardoc test/haml vendor)
8
-
9
- def silence_warnings
10
- the_real_stderr, $stderr = $stderr, StringIO.new
11
- yield
12
- ensure
13
- $stderr = the_real_stderr
7
+ # FIXME: Redefining :test task to run test/options_test.rb in isolated process since it depends on whether Rails is loaded or not.
8
+ # Remove this task when we finished changing escape_html option to be true by default.
9
+ isolated_test = Rake::TestTask.new do |t|
10
+ t.libs << 'test'
11
+ t.test_files = %w[test/options_test.rb]
12
+ t.warning = true
13
+ t.verbose = true
14
+ end
15
+ Rake::TestTask.new do |t|
16
+ t.libs << 'test'
17
+ t.test_files = Dir['test/*_test.rb'] + Dir['test/haml-spec/*_test.rb'] - isolated_test.file_list
18
+ t.warning = true
19
+ t.verbose = true
14
20
  end
15
21
 
16
- desc "Benchmark Haml against ERb. TIMES=n sets the number of runs, default is 1000."
22
+ CLEAN.replace %w(pkg doc coverage .yardoc test/haml vendor)
23
+
24
+ desc "Benchmark Haml against ERB. TIMES=n sets the number of runs, default is 1000."
17
25
  task :benchmark do
18
26
  sh "ruby benchmark.rb #{ENV['TIMES']}"
19
27
  end
20
28
 
21
- Rake::TestTask.new do |t|
22
- t.libs << 'lib' << 'test'
23
- # haml-spec tests are explicitly added after other tests so they don't
24
- # interfere with the Haml loading process which can cause test failures
25
- files = Dir["test/*_test.rb"]
26
- files.concat(Dir['test/haml-spec/*_test.rb'])
27
- t.test_files = files
28
- t.verbose = true
29
- end
30
-
31
29
  task :set_coverage_env do
32
30
  ENV["COVERAGE"] = "true"
33
31
  end
34
32
 
35
- desc "Run Simplecov (only works on 1.9)"
33
+ desc "Run Simplecov"
36
34
  task :coverage => [:set_coverage_env, :test]
37
35
 
38
- gemspec = File.expand_path("../haml.gemspec", __FILE__)
39
- if File.exist? gemspec
40
- Gem::PackageTask.new(eval(File.read(gemspec))) { |pkg| }
41
- end
42
-
43
36
  task :submodules do
44
37
  if File.exist?(File.dirname(__FILE__) + "/.git")
45
38
  sh %{git submodule sync}
@@ -47,31 +40,32 @@ task :submodules do
47
40
  end
48
41
  end
49
42
 
50
- begin
51
- silence_warnings do
52
- require 'yard'
53
- end
54
-
55
- namespace :doc do
56
- desc "List all undocumented methods and classes."
57
- task :undocumented do
58
- command = 'yard --list --query '
59
- command << '"object.docstring.blank? && '
60
- command << '!(object.type == :method && object.is_alias?)"'
61
- sh command
43
+ namespace :doc do
44
+ task :sass do
45
+ require 'sass'
46
+ Dir["yard/default/**/*.sass"].each do |sass|
47
+ File.open(sass.gsub(/sass$/, 'css'), 'w') do |f|
48
+ f.write(Sass::Engine.new(File.read(sass)).render)
49
+ end
62
50
  end
63
51
  end
64
52
 
65
- desc "Generate documentation"
66
- task(:doc) {sh "yard"}
53
+ desc "List all undocumented methods and classes."
54
+ task :undocumented do
55
+ command = 'yard --list --query '
56
+ command << '"object.docstring.blank? && '
57
+ command << '!(object.type == :method && object.is_alias?)"'
58
+ sh command
59
+ end
60
+ end
67
61
 
68
- desc "Generate documentation incrementally"
69
- task(:redoc) {sh "yard -c"}
62
+ desc "Generate documentation"
63
+ task(:doc => 'doc:sass') {sh "yard"}
70
64
 
71
- rescue LoadError
72
- end
65
+ desc "Generate documentation incrementally"
66
+ task(:redoc) {sh "yard -c"}
73
67
 
74
- desc <<END
68
+ desc <<END
75
69
  Profile Haml.
76
70
  TIMES=n sets the number of runs. Defaults to 1000.
77
71
  FILE=str sets the file to profile. Defaults to 'standard'
@@ -80,15 +74,14 @@ Profile Haml.
80
74
  END
81
75
  task :profile do
82
76
  times = (ENV['TIMES'] || '1000').to_i
83
- file = ENV['FILE']
77
+ file = ENV['FILE'] || 'test/templates/standard.haml'
84
78
 
85
79
  require 'bundler/setup'
86
80
  require 'ruby-prof'
87
81
  require 'haml'
88
-
89
- file = File.read(File.expand_path("../test/templates/#{file || 'standard'}.haml", __FILE__))
82
+ file = File.read(File.expand_path("../#{file}", __FILE__))
90
83
  obj = Object.new
91
- Haml::Engine.new(file, :ugly => true).def_method(obj, :render)
84
+ Haml::Engine.new(file).def_method(obj, :render)
92
85
  result = RubyProf.profile { times.times { obj.render } }
93
86
 
94
87
  RubyProf.const_get("#{(ENV['OUTPUT'] || 'Flat').capitalize}Printer").new(result).print
@@ -103,14 +96,13 @@ def gemfiles
103
96
  end
104
97
 
105
98
  def with_each_gemfile
106
- old_env = ENV['BUNDLE_GEMFILE']
107
99
  gemfiles.each do |gemfile|
108
- puts "Using gemfile: #{gemfile}"
109
- ENV['BUNDLE_GEMFILE'] = gemfile
110
- yield
100
+ Bundler.with_clean_env do
101
+ puts "Using gemfile: #{gemfile}"
102
+ ENV['BUNDLE_GEMFILE'] = gemfile
103
+ yield
104
+ end
111
105
  end
112
- ensure
113
- ENV['BUNDLE_GEMFILE'] = old_env
114
106
  end
115
107
 
116
108
  namespace :test do
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+ module Haml
3
+ module AttributeBuilder
4
+ # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
5
+ INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
6
+
7
+ class << self
8
+ def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
9
+ # @TODO this is an absolutely ridiculous amount of arguments. At least
10
+ # some of this needs to be moved into an instance method.
11
+ join_char = hyphenate_data_attrs ? '-' : '_'
12
+
13
+ attributes.each do |key, value|
14
+ if value.is_a?(Hash)
15
+ data_attributes = attributes.delete(key)
16
+ data_attributes = flatten_data_attributes(data_attributes, '', join_char)
17
+ data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
18
+ verify_attribute_names!(data_attributes.keys)
19
+ attributes = data_attributes.merge(attributes)
20
+ end
21
+ end
22
+
23
+ result = attributes.collect do |attr, value|
24
+ next if value.nil?
25
+
26
+ value = filter_and_join(value, ' ') if attr == 'class'
27
+ value = filter_and_join(value, '_') if attr == 'id'
28
+
29
+ if value == true
30
+ next " #{attr}" if is_html
31
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
32
+ elsif value == false
33
+ next
34
+ end
35
+
36
+ value =
37
+ if escape_attrs == :once
38
+ Haml::Helpers.escape_once(value.to_s)
39
+ elsif escape_attrs
40
+ Haml::Helpers.html_escape(value.to_s)
41
+ else
42
+ value.to_s
43
+ end
44
+ " #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
45
+ end
46
+ result.compact!
47
+ result.sort!
48
+ result.join
49
+ end
50
+
51
+ # @return [String, nil]
52
+ def filter_and_join(value, separator)
53
+ return '' if (value.respond_to?(:empty?) && value.empty?)
54
+
55
+ if value.is_a?(Array)
56
+ value = value.flatten
57
+ value.map! {|item| item ? item.to_s : nil}
58
+ value.compact!
59
+ value = value.join(separator)
60
+ else
61
+ value = value ? value.to_s : nil
62
+ end
63
+ !value.nil? && !value.empty? && value
64
+ end
65
+
66
+ # Merges two attribute hashes.
67
+ # This is the same as `to.merge!(from)`,
68
+ # except that it merges id, class, and data attributes.
69
+ #
70
+ # ids are concatenated with `"_"`,
71
+ # and classes are concatenated with `" "`.
72
+ # data hashes are simply merged.
73
+ #
74
+ # Destructively modifies `to`.
75
+ #
76
+ # @param to [{String => String,Hash}] The attribute hash to merge into
77
+ # @param from [{String => Object}] The attribute hash to merge from
78
+ # @return [{String => String,Hash}] `to`, after being merged
79
+ def merge_attributes!(to, from)
80
+ from.keys.each do |key|
81
+ to[key] = merge_value(key, to[key], from[key])
82
+ end
83
+ to
84
+ end
85
+
86
+ # Merge multiple values to one attribute value. No destructive operation.
87
+ #
88
+ # @param key [String]
89
+ # @param values [Array<Object>]
90
+ # @return [String,Hash]
91
+ def merge_values(key, *values)
92
+ values.inject(nil) do |to, from|
93
+ merge_value(key, to, from)
94
+ end
95
+ end
96
+
97
+ def verify_attribute_names!(attribute_names)
98
+ attribute_names.each do |attribute_name|
99
+ if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
100
+ raise InvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
101
+ end
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ # Merge a couple of values to one attribute value. No destructive operation.
108
+ #
109
+ # @param to [String,Hash,nil]
110
+ # @param from [Object]
111
+ # @return [String,Hash]
112
+ def merge_value(key, to, from)
113
+ if from.kind_of?(Hash) || to.kind_of?(Hash)
114
+ from = { nil => from } if !from.is_a?(Hash)
115
+ to = { nil => to } if !to.is_a?(Hash)
116
+ to.merge(from)
117
+ elsif key == 'id'
118
+ merged_id = filter_and_join(from, '_')
119
+ if to && merged_id
120
+ merged_id = "#{to}_#{merged_id}"
121
+ elsif to || merged_id
122
+ merged_id ||= to
123
+ end
124
+ merged_id
125
+ elsif key == 'class'
126
+ merged_class = filter_and_join(from, ' ')
127
+ if to && merged_class
128
+ merged_class = (merged_class.split(' ') | to.split(' ')).sort.join(' ')
129
+ elsif to || merged_class
130
+ merged_class ||= to
131
+ end
132
+ merged_class
133
+ else
134
+ from
135
+ end
136
+ end
137
+
138
+ def build_data_keys(data_hash, hyphenate, attr_name="data")
139
+ Hash[data_hash.map do |name, value|
140
+ if name == nil
141
+ [attr_name, value]
142
+ elsif hyphenate
143
+ ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
144
+ else
145
+ ["#{attr_name}-#{name}", value]
146
+ end
147
+ end]
148
+ end
149
+
150
+ def flatten_data_attributes(data, key, join_char, seen = [])
151
+ return {key => data} unless data.is_a?(Hash)
152
+
153
+ return {key => nil} if seen.include? data.object_id
154
+ seen << data.object_id
155
+
156
+ data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
157
+ joined = key == '' ? k : [key, k].join(join_char)
158
+ hash.merge! flatten_data_attributes(v, joined, join_char, seen)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end