haml 5.2.2 → 6.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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