haml 5.2.2 → 6.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +13 -9
  4. data/.gitignore +16 -16
  5. data/CHANGELOG.md +13 -3
  6. data/Gemfile +18 -11
  7. data/MIT-LICENSE +1 -1
  8. data/README.md +13 -19
  9. data/Rakefile +95 -93
  10. data/bin/bench +66 -0
  11. data/bin/console +11 -0
  12. data/bin/ruby +3 -0
  13. data/bin/setup +7 -0
  14. data/bin/stackprof +27 -0
  15. data/bin/test +24 -0
  16. data/exe/haml +6 -0
  17. data/ext/haml/extconf.rb +10 -0
  18. data/ext/haml/haml.c +537 -0
  19. data/ext/haml/hescape.c +108 -0
  20. data/ext/haml/hescape.h +20 -0
  21. data/haml.gemspec +39 -37
  22. data/lib/haml/ambles.rb +20 -0
  23. data/lib/haml/attribute_builder.rb +135 -179
  24. data/lib/haml/attribute_compiler.rb +85 -194
  25. data/lib/haml/attribute_parser.rb +86 -126
  26. data/lib/haml/cli.rb +154 -0
  27. data/lib/haml/compiler/children_compiler.rb +126 -0
  28. data/lib/haml/compiler/comment_compiler.rb +39 -0
  29. data/lib/haml/compiler/doctype_compiler.rb +46 -0
  30. data/lib/haml/compiler/script_compiler.rb +116 -0
  31. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  32. data/lib/haml/compiler/tag_compiler.rb +76 -0
  33. data/lib/haml/compiler.rb +63 -296
  34. data/lib/haml/dynamic_merger.rb +67 -0
  35. data/lib/haml/engine.rb +42 -227
  36. data/lib/haml/error.rb +3 -52
  37. data/lib/haml/escapable.rb +6 -70
  38. data/lib/haml/filters/base.rb +12 -0
  39. data/lib/haml/filters/cdata.rb +20 -0
  40. data/lib/haml/filters/coffee.rb +17 -0
  41. data/lib/haml/filters/css.rb +33 -0
  42. data/lib/haml/filters/erb.rb +10 -0
  43. data/lib/haml/filters/escaped.rb +22 -0
  44. data/lib/haml/filters/javascript.rb +33 -0
  45. data/lib/haml/filters/less.rb +20 -0
  46. data/lib/haml/filters/markdown.rb +11 -0
  47. data/lib/haml/filters/plain.rb +29 -0
  48. data/lib/haml/filters/preserve.rb +22 -0
  49. data/lib/haml/filters/ruby.rb +10 -0
  50. data/lib/haml/filters/sass.rb +15 -0
  51. data/lib/haml/filters/scss.rb +15 -0
  52. data/lib/haml/filters/text_base.rb +25 -0
  53. data/lib/haml/filters/tilt_base.rb +49 -0
  54. data/lib/haml/filters.rb +54 -378
  55. data/lib/haml/force_escapable.rb +29 -0
  56. data/lib/haml/haml_error.rb +66 -0
  57. data/lib/haml/helpers.rb +3 -697
  58. data/lib/haml/html.rb +22 -0
  59. data/lib/haml/identity.rb +13 -0
  60. data/lib/haml/object_ref.rb +30 -0
  61. data/lib/haml/parser.rb +179 -49
  62. data/lib/haml/rails_helpers.rb +51 -0
  63. data/lib/haml/rails_template.rb +55 -0
  64. data/lib/haml/railtie.rb +7 -45
  65. data/lib/haml/ruby_expression.rb +32 -0
  66. data/lib/haml/string_splitter.rb +20 -0
  67. data/lib/haml/template.rb +15 -34
  68. data/lib/haml/temple_line_counter.rb +2 -1
  69. data/lib/haml/util.rb +17 -15
  70. data/lib/haml/version.rb +1 -2
  71. data/lib/haml.rb +8 -20
  72. metadata +211 -57
  73. data/.gitmodules +0 -3
  74. data/.yardopts +0 -22
  75. data/TODO +0 -24
  76. data/benchmark.rb +0 -70
  77. data/bin/haml +0 -9
  78. data/lib/haml/.gitattributes +0 -1
  79. data/lib/haml/buffer.rb +0 -182
  80. data/lib/haml/exec.rb +0 -347
  81. data/lib/haml/generator.rb +0 -42
  82. data/lib/haml/helpers/action_view_extensions.rb +0 -60
  83. data/lib/haml/helpers/action_view_mods.rb +0 -132
  84. data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
  85. data/lib/haml/helpers/safe_erubi_template.rb +0 -20
  86. data/lib/haml/helpers/safe_erubis_template.rb +0 -33
  87. data/lib/haml/helpers/xss_mods.rb +0 -114
  88. data/lib/haml/options.rb +0 -273
  89. data/lib/haml/plugin.rb +0 -54
  90. data/lib/haml/sass_rails_filter.rb +0 -47
  91. data/lib/haml/template/options.rb +0 -27
  92. data/lib/haml/temple_engine.rb +0 -124
  93. data/yard/default/.gitignore +0 -1
  94. data/yard/default/fulldoc/html/css/common.sass +0 -15
  95. data/yard/default/layout/html/footer.erb +0 -12
data/lib/haml/html.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Haml
3
+ class HTML < Temple::HTML::Fast
4
+ DEPRECATED_FORMATS = %i[html4 html5].freeze
5
+
6
+ def initialize(opts = {})
7
+ if DEPRECATED_FORMATS.include?(opts[:format])
8
+ opts = opts.dup
9
+ opts[:format] = :html
10
+ end
11
+ super(opts)
12
+ end
13
+
14
+ # This dispatcher supports Haml's "revealed" conditional comment.
15
+ def on_html_condcomment(condition, content, revealed = false)
16
+ on_html_comment [:multi,
17
+ [:static, "[#{condition}]>#{'<!-->' if revealed}"],
18
+ content,
19
+ [:static, "#{'<!--' if revealed}<![endif]"]]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ module Haml
3
+ class Identity
4
+ def initialize
5
+ @unique_id = 0
6
+ end
7
+
8
+ def generate
9
+ @unique_id += 1
10
+ "_haml_compiler#{@unique_id}"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module Haml
3
+ module ObjectRef
4
+ class << self
5
+ def parse(args)
6
+ object, prefix = args
7
+ return {} unless object
8
+
9
+ suffix = underscore(object.class)
10
+ {
11
+ 'class' => [prefix, suffix].compact.join('_'),
12
+ 'id' => [prefix, suffix, object.id || 'new'].compact.join('_'),
13
+ }
14
+ end
15
+
16
+ private
17
+
18
+ # Haml::Buffer.underscore
19
+ def underscore(camel_cased_word)
20
+ word = camel_cased_word.to_s.dup
21
+ word.gsub!(/::/, '_')
22
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
23
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
24
+ word.tr!('-', '_')
25
+ word.downcase!
26
+ word
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/haml/parser.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'ripper'
4
4
  require 'strscan'
5
+ require 'haml/haml_error'
6
+ require 'haml/util'
5
7
 
6
8
  module Haml
7
9
  class Parser
@@ -95,7 +97,7 @@ module Haml
95
97
  METHOD_CALL_PREFIX = 'a('
96
98
 
97
99
  def initialize(options)
98
- @options = Options.wrap(options)
100
+ @options = ParserOptions.new(options)
99
101
  # Record the indent levels of "if" statements to validate the subsequent
100
102
  # elsif and else statements are indented at the appropriate level.
101
103
  @script_level_stack = []
@@ -104,6 +106,10 @@ module Haml
104
106
  end
105
107
 
106
108
  def call(template)
109
+ template = Haml::Util.check_haml_encoding(template) do |msg, line|
110
+ raise Haml::Error.new(msg, line)
111
+ end
112
+
107
113
  match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
108
114
  # discard the last match which is always blank
109
115
  match.pop
@@ -119,7 +125,7 @@ module Haml
119
125
  @indentation = nil
120
126
  @line = next_line
121
127
 
122
- raise SyntaxError.new(Error.message(:indenting_at_start), @line.index) if @line.tabs != 0
128
+ raise HamlSyntaxError.new(HamlError.message(:indenting_at_start), @line.index) if @line.tabs != 0
123
129
 
124
130
  loop do
125
131
  next_line
@@ -142,7 +148,7 @@ module Haml
142
148
  end
143
149
 
144
150
  if !flat? && @next_line.tabs - @line.tabs > 1
145
- raise SyntaxError.new(Error.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
151
+ raise HamlSyntaxError.new(HamlError.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
146
152
  end
147
153
 
148
154
  @line = @next_line
@@ -150,9 +156,9 @@ module Haml
150
156
  # Close all the open tags
151
157
  close until @parent.type == :root
152
158
  @root
153
- rescue Haml::Error => e
159
+ rescue Haml::HamlError => e
154
160
  e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
155
- raise
161
+ error_with_lineno(e)
156
162
  end
157
163
 
158
164
  def compute_tabs(line)
@@ -162,7 +168,7 @@ module Haml
162
168
  @indentation = line.whitespace
163
169
 
164
170
  if @indentation.include?(?\s) && @indentation.include?(?\t)
165
- raise SyntaxError.new(Error.message(:cant_use_tabs_and_spaces), line.index)
171
+ raise HamlSyntaxError.new(HamlError.message(:cant_use_tabs_and_spaces), line.index)
166
172
  end
167
173
 
168
174
  @flat_spaces = @indentation * (@template_tabs+1) if flat?
@@ -173,15 +179,25 @@ module Haml
173
179
  return tabs if line.whitespace == @indentation * tabs
174
180
  return @template_tabs + 1 if flat? && line.whitespace =~ /^#{@flat_spaces}/
175
181
 
176
- message = Error.message(:inconsistent_indentation,
182
+ message = HamlError.message(:inconsistent_indentation,
177
183
  human_indentation(line.whitespace),
178
184
  human_indentation(@indentation)
179
185
  )
180
- raise SyntaxError.new(message, line.index)
186
+ raise HamlSyntaxError.new(message, line.index)
181
187
  end
182
188
 
183
189
  private
184
190
 
191
+ def error_with_lineno(error)
192
+ return error if error.line
193
+
194
+ trace = error.backtrace.first
195
+ return error unless trace
196
+
197
+ line = trace.match(/\d+\z/).to_s.to_i
198
+ HamlSyntaxError.new(error.message, line)
199
+ end
200
+
185
201
  # @private
186
202
  Line = Struct.new(:whitespace, :text, :full, :index, :parser, :eod) do
187
203
  alias_method :eod?, :eod
@@ -221,7 +237,7 @@ module Haml
221
237
  self[:old] = value
222
238
  end
223
239
 
224
- # This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
240
+ # This will be a literal for Haml::HamlBuffer#attributes's last argument, `attributes_hashes`.
225
241
  def to_literal
226
242
  [new, stripped_old].compact.join(', ')
227
243
  end
@@ -304,20 +320,20 @@ module Haml
304
320
 
305
321
  def plain(line, escape_html = nil)
306
322
  if block_opened?
307
- raise SyntaxError.new(Error.message(:illegal_nesting_plain), @next_line.index)
323
+ raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_plain), @next_line.index)
308
324
  end
309
325
 
310
- unless contains_interpolation?(line.text)
326
+ unless Util.contains_interpolation?(line.text)
311
327
  return ParseNode.new(:plain, line.index + 1, :text => line.text)
312
328
  end
313
329
 
314
330
  escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
315
- line.text = unescape_interpolation(line.text, escape_html)
316
- script(line, false)
331
+ line.text = Util.unescape_interpolation(line.text)
332
+ script(line, false).tap { |n| n.value[:escape_interpolation] = true if escape_html }
317
333
  end
318
334
 
319
335
  def script(line, escape_html = nil, preserve = false)
320
- raise SyntaxError.new(Error.message(:no_ruby_code, '=')) if line.text.empty?
336
+ raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '=')) if line.text.empty?
321
337
  line = handle_ruby_multiline(line)
322
338
  escape_html = @options.escape_html if escape_html.nil?
323
339
 
@@ -329,12 +345,12 @@ module Haml
329
345
  end
330
346
 
331
347
  def flat_script(line, escape_html = nil)
332
- raise SyntaxError.new(Error.message(:no_ruby_code, '~')) if line.text.empty?
348
+ raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '~')) if line.text.empty?
333
349
  script(line, escape_html, :preserve)
334
350
  end
335
351
 
336
352
  def silent_script(line)
337
- raise SyntaxError.new(Error.message(:no_end), line.index) if line.text[1..-1].strip == 'end'
353
+ raise HamlSyntaxError.new(HamlError.message(:no_end), line.index) if line.text[1..-1].strip == 'end'
338
354
 
339
355
  line = handle_ruby_multiline(line)
340
356
  keyword = block_keyword(line.text)
@@ -343,7 +359,7 @@ module Haml
343
359
 
344
360
  if ["else", "elsif", "when"].include?(keyword)
345
361
  if @script_level_stack.empty?
346
- raise Haml::SyntaxError.new(Error.message(:missing_if, keyword), @line.index)
362
+ raise Haml::HamlSyntaxError.new(HamlError.message(:missing_if, keyword), @line.index)
347
363
  end
348
364
 
349
365
  if keyword == 'when' and !@script_level_stack.last[2]
@@ -354,8 +370,8 @@ module Haml
354
370
  end
355
371
 
356
372
  if @script_level_stack.last[1] != @line.tabs
357
- message = Error.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs)
358
- raise Haml::SyntaxError.new(message, @line.index)
373
+ message = HamlError.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs)
374
+ raise Haml::HamlSyntaxError.new(message, @line.index)
359
375
  end
360
376
  end
361
377
 
@@ -400,7 +416,8 @@ module Haml
400
416
  when '='
401
417
  parse = true
402
418
  if value[0] == ?=
403
- value = unescape_interpolation(value[1..-1].strip, escape_html)
419
+ value = Util.unescape_interpolation(value[1..-1].strip)
420
+ escape_interpolation = true if escape_html
404
421
  escape_html = false
405
422
  end
406
423
  when '&', '!'
@@ -408,19 +425,21 @@ module Haml
408
425
  parse = true
409
426
  preserve_script = (value[0] == ?~)
410
427
  if value[1] == ?=
411
- value = unescape_interpolation(value[2..-1].strip, escape_html)
428
+ value = Util.unescape_interpolation(value[2..-1].strip)
429
+ escape_interpolation = true if escape_html
412
430
  escape_html = false
413
431
  else
414
432
  value = value[1..-1].strip
415
433
  end
416
- elsif contains_interpolation?(value)
417
- value = unescape_interpolation(value, escape_html)
434
+ elsif Util.contains_interpolation?(value)
435
+ value = Util.unescape_interpolation(value)
436
+ escape_interpolation = true if escape_html
418
437
  parse = true
419
438
  escape_html = false
420
439
  end
421
440
  else
422
- if contains_interpolation?(value)
423
- value = unescape_interpolation(value, escape_html)
441
+ if Util.contains_interpolation?(value)
442
+ value = Util.unescape_interpolation(value, escape_html)
424
443
  parse = true
425
444
  escape_html = false
426
445
  end
@@ -431,22 +450,22 @@ module Haml
431
450
 
432
451
  if attributes_hashes[:new]
433
452
  static_attributes, attributes_hash = attributes_hashes[:new]
434
- AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
453
+ AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
435
454
  dynamic_attributes.new = attributes_hash
436
455
  end
437
456
 
438
457
  if attributes_hashes[:old]
439
458
  static_attributes = parse_static_hash(attributes_hashes[:old])
440
- AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
459
+ AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
441
460
  dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
442
461
  end
443
462
 
444
- raise SyntaxError.new(Error.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
445
- raise SyntaxError.new(Error.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
446
- raise SyntaxError.new(Error.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
463
+ raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
464
+ raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
465
+ raise HamlSyntaxError.new(HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
447
466
 
448
467
  if block_opened? && !value.empty? && !is_ruby_multiline?(value)
449
- raise SyntaxError.new(Error.message(:illegal_nesting_line, tag_name), @next_line.index)
468
+ raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_line, tag_name), @next_line.index)
450
469
  end
451
470
 
452
471
  self_closing ||= !!(!block_opened? && value.empty? && @options.autoclose.any? {|t| t === tag_name})
@@ -459,7 +478,8 @@ module Haml
459
478
  :nuke_inner_whitespace => nuke_inner_whitespace,
460
479
  :nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
461
480
  :escape_html => escape_html, :preserve_tag => preserve_tag,
462
- :preserve_script => preserve_script, :parse => parse, :value => line.text)
481
+ :preserve_script => preserve_script, :parse => parse, :value => line.text,
482
+ :escape_interpolation => escape_interpolation)
463
483
  end
464
484
 
465
485
  # Renders a line that creates an XHTML tag and has an implicit div because of
@@ -481,15 +501,15 @@ module Haml
481
501
  conditional, text = balance(text, ?[, ?]) if text[0] == ?[
482
502
  text.strip!
483
503
 
484
- if contains_interpolation?(text)
504
+ if Util.contains_interpolation?(text)
485
505
  parse = true
486
- text = unescape_interpolation(text)
506
+ text = Util.unescape_interpolation(text)
487
507
  else
488
508
  parse = false
489
509
  end
490
510
 
491
511
  if block_opened? && !text.empty?
492
- raise SyntaxError.new(Haml::Error.message(:illegal_nesting_content), @next_line.index)
512
+ raise HamlSyntaxError.new(Haml::HamlError.message(:illegal_nesting_content), @next_line.index)
493
513
  end
494
514
 
495
515
  ParseNode.new(:comment, @line.index + 1, :conditional => conditional, :text => text, :revealed => revealed, :parse => parse)
@@ -497,13 +517,13 @@ module Haml
497
517
 
498
518
  # Renders an XHTML doctype or XML shebang.
499
519
  def doctype(text)
500
- raise SyntaxError.new(Error.message(:illegal_nesting_header), @next_line.index) if block_opened?
520
+ raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_header), @next_line.index) if block_opened?
501
521
  version, type, encoding = text[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
502
522
  ParseNode.new(:doctype, @line.index + 1, :version => version, :type => type, :encoding => encoding)
503
523
  end
504
524
 
505
525
  def filter(name)
506
- raise Error.new(Error.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
526
+ raise HamlError.new(HamlError.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
507
527
 
508
528
  if filter_opened?
509
529
  @flat = true
@@ -552,7 +572,7 @@ module Haml
552
572
 
553
573
  alias :close_script :close_silent_script
554
574
 
555
- # This is a class method so it can be accessed from {Haml::Helpers}.
575
+ # This is a class method so it can be accessed from {Haml::HamlHelpers}.
556
576
  #
557
577
  # Iterates through the classes and ids supplied through `.`
558
578
  # and `#` syntax, and returns a hash with them as attributes,
@@ -576,8 +596,8 @@ module Haml
576
596
  attributes
577
597
  end
578
598
 
579
- # This method doesn't use Haml::AttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
580
- # Ideally this logic should be placed in Haml::AttributeParser instead of here and this method should use it.
599
+ # This method doesn't use Haml::HamlAttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
600
+ # Ideally this logic should be placed in Haml::HamlAttributeParser instead of here and this method should use it.
581
601
  #
582
602
  # @param [String] text - Hash literal or text inside old attributes
583
603
  # @return [Hash,nil] - Return nil if text is not static Hash literal
@@ -601,12 +621,12 @@ module Haml
601
621
  # Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
602
622
  def parse_tag(text)
603
623
  match = text.scan(/%([-:\w]+)([-:\w.#\@]*)(.+)?/)[0]
604
- raise SyntaxError.new(Error.message(:invalid_tag, text)) unless match
624
+ raise HamlSyntaxError.new(HamlError.message(:invalid_tag, text)) unless match
605
625
 
606
626
  tag_name, attributes, rest = match
607
627
 
608
628
  if !attributes.empty? && (attributes =~ /[.#](\.|#|\z)/)
609
- raise SyntaxError.new(Error.message(:illegal_element))
629
+ raise HamlSyntaxError.new(HamlError.message(:illegal_element))
610
630
  end
611
631
 
612
632
  new_attributes_hash = old_attributes_hash = last_line = nil
@@ -665,8 +685,8 @@ module Haml
665
685
  # 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards.
666
686
  balanced, rest = balance_tokens(text.sub(?{, METHOD_CALL_PREFIX), :on_embexpr_beg, :on_embexpr_end, count: 1)
667
687
  attributes_hash = balanced.sub(METHOD_CALL_PREFIX, ?{)
668
- rescue SyntaxError => e
669
- if e.message == Error.message(:unbalanced_brackets) && !@template.empty?
688
+ rescue HamlSyntaxError => e
689
+ if e.message == HamlError.message(:unbalanced_brackets) && !@template.empty?
670
690
  text << "\n#{@next_line.text}"
671
691
  last_line += 1
672
692
  next_line
@@ -695,7 +715,7 @@ module Haml
695
715
  if name == false
696
716
  scanned = Haml::Util.balance(text, ?(, ?))
697
717
  text = scanned ? scanned.first : text
698
- raise Haml::SyntaxError.new(Error.message(:invalid_attribute_list, text.inspect), last_line - 1)
718
+ raise Haml::HamlSyntaxError.new(HamlError.message(:invalid_attribute_list, text.inspect), last_line - 1)
699
719
  end
700
720
  attributes[name] = value
701
721
  scanner.scan(/\s*/)
@@ -714,7 +734,7 @@ module Haml
714
734
  if type == :static
715
735
  static_attributes[name] = val
716
736
  else
717
- dynamic_attributes << "#{inspect_obj(name)} => #{val},"
737
+ dynamic_attributes << "#{Util.inspect_obj(name)} => #{val},"
718
738
  end
719
739
  end
720
740
  dynamic_attributes << "}"
@@ -749,7 +769,7 @@ module Haml
749
769
 
750
770
  return name, [:static, content.first[1]] if content.size == 1
751
771
  return name, [:dynamic,
752
- %!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
772
+ %!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? Util.inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
753
773
  end
754
774
 
755
775
  def next_line
@@ -817,7 +837,7 @@ module Haml
817
837
  end
818
838
 
819
839
  def balance(*args)
820
- Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
840
+ Haml::Util.balance(*args) or raise(HamlSyntaxError.new(HamlError.message(:unbalanced_brackets)))
821
841
  end
822
842
 
823
843
  # Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly.
@@ -836,7 +856,7 @@ module Haml
836
856
  return text, buf.sub(text, '')
837
857
  end
838
858
  end
839
- raise SyntaxError.new(Error.message(:unbalanced_brackets))
859
+ raise HamlSyntaxError.new(HamlError.message(:unbalanced_brackets))
840
860
  end
841
861
 
842
862
  def block_opened?
@@ -852,5 +872,115 @@ module Haml
852
872
  def flat?
853
873
  @flat
854
874
  end
875
+
876
+ class << AttributeMerger = Object.new
877
+ # Merges two attribute hashes.
878
+ # This is the same as `to.merge!(from)`,
879
+ # except that it merges id, class, and data attributes.
880
+ #
881
+ # ids are concatenated with `"_"`,
882
+ # and classes are concatenated with `" "`.
883
+ # data hashes are simply merged.
884
+ #
885
+ # Destructively modifies `to`.
886
+ #
887
+ # @param to [{String => String,Hash}] The attribute hash to merge into
888
+ # @param from [{String => Object}] The attribute hash to merge from
889
+ # @return [{String => String,Hash}] `to`, after being merged
890
+ def merge_attributes!(to, from)
891
+ from.keys.each do |key|
892
+ to[key] = merge_value(key, to[key], from[key])
893
+ end
894
+ to
895
+ end
896
+
897
+ private
898
+
899
+ # @return [String, nil]
900
+ def filter_and_join(value, separator)
901
+ return '' if (value.respond_to?(:empty?) && value.empty?)
902
+
903
+ if value.is_a?(Array)
904
+ value = value.flatten
905
+ value.map! {|item| item ? item.to_s : nil}
906
+ value.compact!
907
+ value = value.join(separator)
908
+ else
909
+ value = value ? value.to_s : nil
910
+ end
911
+ !value.nil? && !value.empty? && value
912
+ end
913
+
914
+ # Merge a couple of values to one attribute value. No destructive operation.
915
+ #
916
+ # @param to [String,Hash,nil]
917
+ # @param from [Object]
918
+ # @return [String,Hash]
919
+ def merge_value(key, to, from)
920
+ if from.kind_of?(Hash) || to.kind_of?(Hash)
921
+ from = { nil => from } if !from.is_a?(Hash)
922
+ to = { nil => to } if !to.is_a?(Hash)
923
+ to.merge(from)
924
+ elsif key == 'id'
925
+ merged_id = filter_and_join(from, '_')
926
+ if to && merged_id
927
+ merged_id = "#{to}_#{merged_id}"
928
+ elsif to || merged_id
929
+ merged_id ||= to
930
+ end
931
+ merged_id
932
+ elsif key == 'class'
933
+ merged_class = filter_and_join(from, ' ')
934
+ if to && merged_class
935
+ merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
936
+ elsif to || merged_class
937
+ merged_class ||= to
938
+ end
939
+ merged_class
940
+ else
941
+ from
942
+ end
943
+ end
944
+ end
945
+ private_constant :AttributeMerger
946
+
947
+ class ParserOptions
948
+ # A list of options that are actually used in the parser
949
+ AVAILABLE_OPTIONS = %i[
950
+ autoclose
951
+ escape_html
952
+ filename
953
+ line
954
+ mime_type
955
+ preserve
956
+ remove_whitespace
957
+ suppress_eval
958
+ ].each do |option|
959
+ attr_reader option
960
+ end
961
+
962
+ DEFAULTS = {
963
+ autoclose: %w(area base basefont br col command embed frame
964
+ hr img input isindex keygen link menuitem meta
965
+ param source track wbr),
966
+ escape_html: false,
967
+ filename: '(haml)',
968
+ line: 1,
969
+ mime_type: 'text/html',
970
+ preserve: %w(textarea pre code),
971
+ remove_whitespace: false,
972
+ suppress_eval: false,
973
+ }
974
+
975
+ def initialize(values = {})
976
+ DEFAULTS.each {|k, v| instance_variable_set :"@#{k}", v}
977
+ AVAILABLE_OPTIONS.each do |key|
978
+ if values.key?(key)
979
+ instance_variable_set :"@#{key}", values[key]
980
+ end
981
+ end
982
+ end
983
+ end
984
+ private_constant :ParserOptions
855
985
  end
856
986
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: false
2
+ require 'haml/helpers'
3
+
4
+ # Currently this Haml::Helpers depends on
5
+ # ActionView internal implementation. (not desired)
6
+ module Haml
7
+ module RailsHelpers
8
+ include Helpers
9
+ extend self
10
+
11
+ DEFAULT_PRESERVE_TAGS = %w[textarea pre code].freeze
12
+
13
+ def find_and_preserve(input = nil, tags = DEFAULT_PRESERVE_TAGS, &block)
14
+ return find_and_preserve(capture_haml(&block), input || tags) if block
15
+
16
+ tags = tags.each_with_object('') do |t, s|
17
+ s << '|' unless s.empty?
18
+ s << Regexp.escape(t)
19
+ end
20
+
21
+ re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
22
+ input.to_s.gsub(re) do |s|
23
+ s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
24
+ "<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
25
+ end
26
+ end
27
+
28
+ def preserve(input = nil, &block)
29
+ return preserve(capture_haml(&block)) if block
30
+ super.html_safe
31
+ end
32
+
33
+ def surround(front, back = front, &block)
34
+ output = capture_haml(&block)
35
+
36
+ "#{escape_once(front)}#{output.chomp}#{escape_once(back)}\n".html_safe
37
+ end
38
+
39
+ def precede(str, &block)
40
+ "#{escape_once(str)}#{capture_haml(&block).chomp}\n".html_safe
41
+ end
42
+
43
+ def succeed(str, &block)
44
+ "#{capture_haml(&block).chomp}#{escape_once(str)}\n".html_safe
45
+ end
46
+
47
+ def capture_haml(*args, &block)
48
+ capture(*args, &block)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ require 'temple'
3
+ require 'haml/engine'
4
+ require 'haml/rails_helpers'
5
+ require 'haml/util'
6
+
7
+ module Haml
8
+ class RailsTemplate
9
+ # Compatible with: https://github.com/judofyr/temple/blob/v0.7.7/lib/temple/mixins/options.rb#L15-L24
10
+ class << self
11
+ def options
12
+ @options ||= {
13
+ generator: Temple::Generators::RailsOutputBuffer,
14
+ use_html_safe: true,
15
+ streaming: true,
16
+ buffer_class: 'ActionView::OutputBuffer',
17
+ disable_capture: true,
18
+ }
19
+ end
20
+
21
+ def set_options(opts)
22
+ options.update(opts)
23
+ end
24
+ end
25
+
26
+ def call(template, source = nil)
27
+ source ||= template.source
28
+ options = RailsTemplate.options
29
+
30
+ # https://github.com/haml/haml/blob/4.0.7/lib/haml/template/plugin.rb#L19-L20
31
+ # https://github.com/haml/haml/blob/4.0.7/lib/haml/options.rb#L228
32
+ if template.respond_to?(:type) && template.type == 'text/xml'
33
+ options = options.merge(format: :xhtml)
34
+ end
35
+
36
+ if ActionView::Base.try(:annotate_rendered_view_with_filenames) && template.format == :html
37
+ options = options.merge(
38
+ preamble: "<!-- BEGIN #{template.short_identifier} -->\n",
39
+ postamble: "<!-- END #{template.short_identifier} -->\n",
40
+ )
41
+ end
42
+
43
+ Engine.new(options).call(source)
44
+ end
45
+
46
+ def supports_streaming?
47
+ RailsTemplate.options[:streaming]
48
+ end
49
+ end
50
+ ActionView::Template.register_template_handler(:haml, RailsTemplate.new)
51
+ end
52
+
53
+ # Haml extends Haml::Helpers in ActionView each time.
54
+ # It costs much, so Haml includes a compatible module at first.
55
+ ActionView::Base.send :include, Haml::RailsHelpers