haml 5.1.2 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/test.yml +36 -0
  4. data/.gitignore +16 -15
  5. data/.yardopts +0 -3
  6. data/CHANGELOG.md +189 -1
  7. data/FAQ.md +1 -1
  8. data/Gemfile +20 -12
  9. data/MIT-LICENSE +1 -1
  10. data/README.md +10 -17
  11. data/REFERENCE.md +129 -164
  12. data/Rakefile +15 -89
  13. data/bin/bench +66 -0
  14. data/bin/console +11 -0
  15. data/bin/ruby +3 -0
  16. data/bin/setup +7 -0
  17. data/bin/stackprof +27 -0
  18. data/bin/test +24 -0
  19. data/exe/haml +6 -0
  20. data/haml.gemspec +34 -35
  21. data/lib/haml/ambles.rb +20 -0
  22. data/lib/haml/attribute_builder.rb +131 -133
  23. data/lib/haml/attribute_compiler.rb +91 -182
  24. data/lib/haml/attribute_parser.rb +92 -126
  25. data/lib/haml/cli.rb +154 -0
  26. data/lib/haml/compiler/children_compiler.rb +155 -0
  27. data/lib/haml/compiler/comment_compiler.rb +51 -0
  28. data/lib/haml/compiler/doctype_compiler.rb +52 -0
  29. data/lib/haml/compiler/script_compiler.rb +114 -0
  30. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  31. data/lib/haml/compiler/tag_compiler.rb +76 -0
  32. data/lib/haml/compiler.rb +63 -296
  33. data/lib/haml/dynamic_merger.rb +67 -0
  34. data/lib/haml/engine.rb +48 -227
  35. data/lib/haml/error.rb +5 -4
  36. data/lib/haml/escape.rb +13 -0
  37. data/lib/haml/escape_any.rb +21 -0
  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 +59 -0
  54. data/lib/haml/filters.rb +54 -378
  55. data/lib/haml/force_escape.rb +29 -0
  56. data/lib/haml/helpers.rb +3 -691
  57. data/lib/haml/html.rb +22 -0
  58. data/lib/haml/identity.rb +13 -0
  59. data/lib/haml/object_ref.rb +35 -0
  60. data/lib/haml/parser.rb +190 -27
  61. data/lib/haml/rails_helpers.rb +53 -0
  62. data/lib/haml/rails_template.rb +62 -0
  63. data/lib/haml/railtie.rb +3 -41
  64. data/lib/haml/ruby_expression.rb +32 -0
  65. data/lib/haml/string_splitter.rb +140 -0
  66. data/lib/haml/template.rb +15 -34
  67. data/lib/haml/temple_line_counter.rb +2 -1
  68. data/lib/haml/util.rb +20 -16
  69. data/lib/haml/version.rb +1 -2
  70. data/lib/haml/whitespace.rb +8 -0
  71. data/lib/haml.rb +8 -20
  72. metadata +205 -53
  73. data/.gitmodules +0 -3
  74. data/.travis.yml +0 -97
  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 -238
  80. data/lib/haml/escapable.rb +0 -50
  81. data/lib/haml/exec.rb +0 -347
  82. data/lib/haml/generator.rb +0 -42
  83. data/lib/haml/helpers/action_view_extensions.rb +0 -60
  84. data/lib/haml/helpers/action_view_mods.rb +0 -132
  85. data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
  86. data/lib/haml/helpers/safe_erubi_template.rb +0 -20
  87. data/lib/haml/helpers/safe_erubis_template.rb +0 -33
  88. data/lib/haml/helpers/xss_mods.rb +0 -111
  89. data/lib/haml/options.rb +0 -273
  90. data/lib/haml/plugin.rb +0 -37
  91. data/lib/haml/sass_rails_filter.rb +0 -47
  92. data/lib/haml/template/options.rb +0 -27
  93. data/lib/haml/temple_engine.rb +0 -123
  94. data/yard/default/.gitignore +0 -1
  95. data/yard/default/fulldoc/html/css/common.sass +0 -15
  96. 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,35 @@
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 =
10
+ if object.respond_to?(:haml_object_ref)
11
+ object.haml_object_ref
12
+ else
13
+ underscore(object.class)
14
+ end
15
+ {
16
+ 'class' => [prefix, suffix].compact.join('_'),
17
+ 'id' => [prefix, suffix, object.id || 'new'].compact.join('_'),
18
+ }
19
+ end
20
+
21
+ private
22
+
23
+ # Haml::Buffer.underscore
24
+ def underscore(camel_cased_word)
25
+ word = camel_cased_word.to_s.dup
26
+ word.gsub!(/::/, '_')
27
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
28
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
29
+ word.tr!('-', '_')
30
+ word.downcase!
31
+ word
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/haml/parser.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ripper'
3
4
  require 'strscan'
5
+ require 'haml/error'
6
+ require 'haml/util'
4
7
 
5
8
  module Haml
6
9
  class Parser
@@ -75,7 +78,7 @@ module Haml
75
78
  #
76
79
  BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
77
80
 
78
- MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when].freeze
81
+ MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when in].freeze
79
82
  START_BLOCK_KEYWORDS = %w[if begin case unless].freeze
80
83
  # Try to parse assignments to block starters as best as possible
81
84
  START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
@@ -90,16 +93,27 @@ module Haml
90
93
  ID_KEY = 'id'.freeze
91
94
  CLASS_KEY = 'class'.freeze
92
95
 
96
+ # Used for scanning old attributes, substituting the first '{'
97
+ METHOD_CALL_PREFIX = 'a('
98
+
93
99
  def initialize(options)
94
- @options = Options.wrap(options)
100
+ @options = ParserOptions.new(options)
95
101
  # Record the indent levels of "if" statements to validate the subsequent
96
102
  # elsif and else statements are indented at the appropriate level.
97
103
  @script_level_stack = []
98
104
  @template_index = 0
99
105
  @template_tabs = 0
106
+ # When used in Haml::Engine, which gives options[:generator] to every filter
107
+ # in the engine, including Haml::Parser, we don't want to throw exceptions.
108
+ # However, when Haml::Parser is used as a library, we want to throw exceptions.
109
+ @raise_error = !options.key?(:generator)
100
110
  end
101
111
 
102
112
  def call(template)
113
+ template = Haml::Util.check_haml_encoding(template) do |msg, line|
114
+ raise Haml::Error.new(msg, line)
115
+ end
116
+
103
117
  match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
104
118
  # discard the last match which is always blank
105
119
  match.pop
@@ -148,7 +162,8 @@ module Haml
148
162
  @root
149
163
  rescue Haml::Error => e
150
164
  e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
151
- raise
165
+ raise if @raise_error
166
+ error_with_lineno(e)
152
167
  end
153
168
 
154
169
  def compute_tabs(line)
@@ -178,6 +193,16 @@ module Haml
178
193
 
179
194
  private
180
195
 
196
+ def error_with_lineno(error)
197
+ return error if error.line
198
+
199
+ trace = error.backtrace.first
200
+ return error unless trace
201
+
202
+ line = trace.match(/\d+\z/).to_s.to_i
203
+ SyntaxError.new(error.message, line)
204
+ end
205
+
181
206
  # @private
182
207
  Line = Struct.new(:whitespace, :text, :full, :index, :parser, :eod) do
183
208
  alias_method :eod?, :eod
@@ -217,7 +242,7 @@ module Haml
217
242
  self[:old] = value
218
243
  end
219
244
 
220
- # This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
245
+ # This will be a literal for Haml::HamlBuffer#attributes's last argument, `attributes_hashes`.
221
246
  def to_literal
222
247
  [new, stripped_old].compact.join(', ')
223
248
  end
@@ -303,13 +328,13 @@ module Haml
303
328
  raise SyntaxError.new(Error.message(:illegal_nesting_plain), @next_line.index)
304
329
  end
305
330
 
306
- unless contains_interpolation?(line.text)
331
+ unless Util.contains_interpolation?(line.text)
307
332
  return ParseNode.new(:plain, line.index + 1, :text => line.text)
308
333
  end
309
334
 
310
- escape_html = @options.escape_html if escape_html.nil?
311
- line.text = unescape_interpolation(line.text, escape_html)
312
- script(line, false)
335
+ escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
336
+ line.text = Util.unescape_interpolation(line.text)
337
+ script(line, false).tap { |n| n.value[:escape_interpolation] = true if escape_html }
313
338
  end
314
339
 
315
340
  def script(line, escape_html = nil, preserve = false)
@@ -396,7 +421,8 @@ module Haml
396
421
  when '='
397
422
  parse = true
398
423
  if value[0] == ?=
399
- value = unescape_interpolation(value[1..-1].strip, escape_html)
424
+ value = Util.unescape_interpolation(value[1..-1].strip)
425
+ escape_interpolation = true if escape_html
400
426
  escape_html = false
401
427
  end
402
428
  when '&', '!'
@@ -404,19 +430,21 @@ module Haml
404
430
  parse = true
405
431
  preserve_script = (value[0] == ?~)
406
432
  if value[1] == ?=
407
- value = unescape_interpolation(value[2..-1].strip, escape_html)
433
+ value = Util.unescape_interpolation(value[2..-1].strip)
434
+ escape_interpolation = true if escape_html
408
435
  escape_html = false
409
436
  else
410
437
  value = value[1..-1].strip
411
438
  end
412
- elsif contains_interpolation?(value)
413
- value = unescape_interpolation(value, escape_html)
439
+ elsif Util.contains_interpolation?(value)
440
+ value = Util.unescape_interpolation(value)
441
+ escape_interpolation = true if escape_html
414
442
  parse = true
415
443
  escape_html = false
416
444
  end
417
445
  else
418
- if contains_interpolation?(value)
419
- value = unescape_interpolation(value, escape_html)
446
+ if Util.contains_interpolation?(value)
447
+ value = Util.unescape_interpolation(value, escape_html)
420
448
  parse = true
421
449
  escape_html = false
422
450
  end
@@ -427,13 +455,13 @@ module Haml
427
455
 
428
456
  if attributes_hashes[:new]
429
457
  static_attributes, attributes_hash = attributes_hashes[:new]
430
- AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
458
+ AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
431
459
  dynamic_attributes.new = attributes_hash
432
460
  end
433
461
 
434
462
  if attributes_hashes[:old]
435
463
  static_attributes = parse_static_hash(attributes_hashes[:old])
436
- AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
464
+ AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
437
465
  dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
438
466
  end
439
467
 
@@ -455,7 +483,8 @@ module Haml
455
483
  :nuke_inner_whitespace => nuke_inner_whitespace,
456
484
  :nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
457
485
  :escape_html => escape_html, :preserve_tag => preserve_tag,
458
- :preserve_script => preserve_script, :parse => parse, :value => line.text)
486
+ :preserve_script => preserve_script, :parse => parse, :value => line.text,
487
+ :escape_interpolation => escape_interpolation)
459
488
  end
460
489
 
461
490
  # Renders a line that creates an XHTML tag and has an implicit div because of
@@ -477,9 +506,9 @@ module Haml
477
506
  conditional, text = balance(text, ?[, ?]) if text[0] == ?[
478
507
  text.strip!
479
508
 
480
- if contains_interpolation?(text)
509
+ if Util.contains_interpolation?(text)
481
510
  parse = true
482
- text = unescape_interpolation(text)
511
+ text = Util.unescape_interpolation(text)
483
512
  else
484
513
  parse = false
485
514
  end
@@ -548,7 +577,7 @@ module Haml
548
577
 
549
578
  alias :close_script :close_silent_script
550
579
 
551
- # This is a class method so it can be accessed from {Haml::Helpers}.
580
+ # This is a class method so it can be accessed from {Haml::HamlHelpers}.
552
581
  #
553
582
  # Iterates through the classes and ids supplied through `.`
554
583
  # and `#` syntax, and returns a hash with them as attributes,
@@ -572,8 +601,8 @@ module Haml
572
601
  attributes
573
602
  end
574
603
 
575
- # This method doesn't use Haml::AttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
576
- # Ideally this logic should be placed in Haml::AttributeParser instead of here and this method should use it.
604
+ # This method doesn't use Haml::HamlAttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
605
+ # Ideally this logic should be placed in Haml::HamlAttributeParser instead of here and this method should use it.
577
606
  #
578
607
  # @param [String] text - Hash literal or text inside old attributes
579
608
  # @return [Hash,nil] - Return nil if text is not static Hash literal
@@ -651,13 +680,18 @@ module Haml
651
680
  # @return [String] rest
652
681
  # @return [Integer] last_line
653
682
  def parse_old_attributes(text)
654
- text = text.dup
655
683
  last_line = @line.index + 1
656
684
 
657
685
  begin
658
- attributes_hash, rest = balance(text, ?{, ?})
686
+ # Old attributes often look like a valid Hash literal, but it sometimes allow code like
687
+ # `{ hash, foo: bar }`, which is compiled to `_hamlout.attributes({}, nil, hash, foo: bar)`.
688
+ #
689
+ # To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops when there is
690
+ # 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards.
691
+ balanced, rest = balance_tokens(text.sub(?{, METHOD_CALL_PREFIX), :on_embexpr_beg, :on_embexpr_end, count: 1)
692
+ attributes_hash = balanced.sub(METHOD_CALL_PREFIX, ?{)
659
693
  rescue SyntaxError => e
660
- if text.strip[-1] == ?, && e.message == Error.message(:unbalanced_brackets)
694
+ if e.message == Error.message(:unbalanced_brackets) && !@template.empty?
661
695
  text << "\n#{@next_line.text}"
662
696
  last_line += 1
663
697
  next_line
@@ -705,7 +739,7 @@ module Haml
705
739
  if type == :static
706
740
  static_attributes[name] = val
707
741
  else
708
- dynamic_attributes << "#{inspect_obj(name)} => #{val},"
742
+ dynamic_attributes << "#{Util.inspect_obj(name)} => #{val},"
709
743
  end
710
744
  end
711
745
  dynamic_attributes << "}"
@@ -740,7 +774,7 @@ module Haml
740
774
 
741
775
  return name, [:static, content.first[1]] if content.size == 1
742
776
  return name, [:dynamic,
743
- %!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
777
+ %!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? Util.inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
744
778
  end
745
779
 
746
780
  def next_line
@@ -811,6 +845,25 @@ module Haml
811
845
  Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
812
846
  end
813
847
 
848
+ # Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly.
849
+ def balance_tokens(buf, start, finish, count: 0)
850
+ text = ''.dup
851
+ Ripper.lex(buf).each do |_, token, str|
852
+ text << str
853
+ case token
854
+ when start
855
+ count += 1
856
+ when finish
857
+ count -= 1
858
+ end
859
+
860
+ if count == 0
861
+ return text, buf.sub(text, '')
862
+ end
863
+ end
864
+ raise SyntaxError.new(Error.message(:unbalanced_brackets))
865
+ end
866
+
814
867
  def block_opened?
815
868
  @next_line.tabs > @line.tabs
816
869
  end
@@ -824,5 +877,115 @@ module Haml
824
877
  def flat?
825
878
  @flat
826
879
  end
880
+
881
+ class << AttributeMerger = Object.new
882
+ # Merges two attribute hashes.
883
+ # This is the same as `to.merge!(from)`,
884
+ # except that it merges id, class, and data attributes.
885
+ #
886
+ # ids are concatenated with `"_"`,
887
+ # and classes are concatenated with `" "`.
888
+ # data hashes are simply merged.
889
+ #
890
+ # Destructively modifies `to`.
891
+ #
892
+ # @param to [{String => String,Hash}] The attribute hash to merge into
893
+ # @param from [{String => Object}] The attribute hash to merge from
894
+ # @return [{String => String,Hash}] `to`, after being merged
895
+ def merge_attributes!(to, from)
896
+ from.keys.each do |key|
897
+ to[key] = merge_value(key, to[key], from[key])
898
+ end
899
+ to
900
+ end
901
+
902
+ private
903
+
904
+ # @return [String, nil]
905
+ def filter_and_join(value, separator)
906
+ return '' if (value.respond_to?(:empty?) && value.empty?)
907
+
908
+ if value.is_a?(Array)
909
+ value = value.flatten
910
+ value.map! {|item| item ? item.to_s : nil}
911
+ value.compact!
912
+ value = value.join(separator)
913
+ else
914
+ value = value ? value.to_s : nil
915
+ end
916
+ !value.nil? && !value.empty? && value
917
+ end
918
+
919
+ # Merge a couple of values to one attribute value. No destructive operation.
920
+ #
921
+ # @param to [String,Hash,nil]
922
+ # @param from [Object]
923
+ # @return [String,Hash]
924
+ def merge_value(key, to, from)
925
+ if from.kind_of?(Hash) || to.kind_of?(Hash)
926
+ from = { nil => from } if !from.is_a?(Hash)
927
+ to = { nil => to } if !to.is_a?(Hash)
928
+ to.merge(from)
929
+ elsif key == 'id'
930
+ merged_id = filter_and_join(from, '_')
931
+ if to && merged_id
932
+ merged_id = "#{to}_#{merged_id}"
933
+ elsif to || merged_id
934
+ merged_id ||= to
935
+ end
936
+ merged_id
937
+ elsif key == 'class'
938
+ merged_class = filter_and_join(from, ' ')
939
+ if to && merged_class
940
+ merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
941
+ elsif to || merged_class
942
+ merged_class ||= to
943
+ end
944
+ merged_class
945
+ else
946
+ from
947
+ end
948
+ end
949
+ end
950
+ private_constant :AttributeMerger
951
+
952
+ class ParserOptions
953
+ # A list of options that are actually used in the parser
954
+ AVAILABLE_OPTIONS = %i[
955
+ autoclose
956
+ escape_html
957
+ filename
958
+ line
959
+ mime_type
960
+ preserve
961
+ remove_whitespace
962
+ suppress_eval
963
+ ].each do |option|
964
+ attr_reader option
965
+ end
966
+
967
+ DEFAULTS = {
968
+ autoclose: %w(area base basefont br col command embed frame
969
+ hr img input isindex keygen link menuitem meta
970
+ param source track wbr),
971
+ escape_html: false,
972
+ filename: '(haml)',
973
+ line: 1,
974
+ mime_type: 'text/html',
975
+ preserve: %w(textarea pre code),
976
+ remove_whitespace: false,
977
+ suppress_eval: false,
978
+ }
979
+
980
+ def initialize(values = {})
981
+ DEFAULTS.each {|k, v| instance_variable_set :"@#{k}", v}
982
+ AVAILABLE_OPTIONS.each do |key|
983
+ if values.key?(key)
984
+ instance_variable_set :"@#{key}", values[key]
985
+ end
986
+ end
987
+ end
988
+ end
989
+ private_constant :ParserOptions
827
990
  end
828
991
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: false
2
+ require 'haml/helpers'
3
+
4
+ # There are only helpers that depend on ActionView internals.
5
+ module Haml
6
+ module RailsHelpers
7
+ include Helpers
8
+ extend self
9
+
10
+ DEFAULT_PRESERVE_TAGS = %w[textarea pre code].freeze
11
+
12
+ def find_and_preserve(input = nil, tags = DEFAULT_PRESERVE_TAGS, &block)
13
+ return find_and_preserve(capture_haml(&block), input || tags) if block
14
+
15
+ tags = tags.each_with_object('') do |t, s|
16
+ s << '|' unless s.empty?
17
+ s << Regexp.escape(t)
18
+ end
19
+
20
+ re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
21
+ input.to_s.gsub(re) do |s|
22
+ s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
23
+ "<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
24
+ end
25
+ end
26
+
27
+ def preserve(input = nil, &block)
28
+ return preserve(capture_haml(&block)) if block
29
+ super.html_safe
30
+ end
31
+
32
+ def surround(front, back = front, &block)
33
+ output = capture_haml(&block)
34
+ front = escape_once(front) unless front.html_safe?
35
+ back = escape_once(back) unless back.html_safe?
36
+ "#{front}#{output.chomp}#{back}\n".html_safe
37
+ end
38
+
39
+ def precede(str, &block)
40
+ str = escape_once(str) unless str.html_safe?
41
+ "#{str}#{capture_haml(&block).chomp}\n".html_safe
42
+ end
43
+
44
+ def succeed(str, &block)
45
+ str = escape_once(str) unless str.html_safe?
46
+ "#{capture_haml(&block).chomp}#{str}\n".html_safe
47
+ end
48
+
49
+ def capture_haml(*args, &block)
50
+ capture(*args, &block)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
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
+ # Make the filename available in parser etc.
31
+ if template.respond_to?(:identifier)
32
+ options = options.merge(filename: template.identifier)
33
+ end
34
+
35
+ # https://github.com/haml/haml/blob/4.0.7/lib/haml/template/plugin.rb#L19-L20
36
+ # https://github.com/haml/haml/blob/4.0.7/lib/haml/options.rb#L228
37
+ if template.respond_to?(:type) && template.type == 'text/xml'
38
+ options = options.merge(format: :xhtml)
39
+ end
40
+
41
+ if ActionView::Base.try(:annotate_rendered_view_with_filenames) && template.format == :html
42
+ options = options.merge(
43
+ preamble: "<!-- BEGIN #{template.short_identifier} -->\n",
44
+ postamble: "<!-- END #{template.short_identifier} -->\n",
45
+ )
46
+ end
47
+
48
+ Engine.new(options).call(source)
49
+ end
50
+
51
+ def supports_streaming?
52
+ RailsTemplate.options[:streaming]
53
+ end
54
+ end
55
+ ActionView::Template.register_template_handler(:haml, RailsTemplate.new)
56
+ end
57
+
58
+ # Haml extends Haml::Helpers in ActionView each time.
59
+ # It costs much, so Haml includes a compatible module at first.
60
+ ActiveSupport.on_load(:action_view) do
61
+ include Haml::RailsHelpers
62
+ end
data/lib/haml/railtie.rb CHANGED
@@ -1,48 +1,10 @@
1
1
  # frozen_string_literal: true
2
-
3
- require 'haml/template/options'
4
-
5
- # check for a compatible Rails version when Haml is loaded
6
- if (activesupport_spec = Gem.loaded_specs['activesupport'])
7
- if activesupport_spec.version.to_s < '4.0'
8
- raise Exception.new("\n\n** Haml now requires Rails 4.0 and later. Use Haml version 4.0.x\n\n")
9
- end
10
- end
2
+ require 'rails'
11
3
 
12
4
  module Haml
13
- module Filters
14
- module RailsErb
15
- extend Plain
16
- extend TiltFilter
17
- extend PrecompiledTiltFilter
18
- end
19
- end
20
-
21
5
  class Railtie < ::Rails::Railtie
22
- initializer :haml do |app|
23
- ActiveSupport.on_load(:action_view) do
24
- require "haml/template"
25
-
26
- if defined?(::Sass::Rails::SassTemplate) && app.config.assets.enabled
27
- require "haml/sass_rails_filter"
28
- end
29
-
30
- # Any object under ActionView::Template will be defined as the root constant with the same
31
- # name if it exists. If Erubi is loaded at all, ActionView::Template::Handlers::ERB::Erubi
32
- # will turn out to be a reference to the ::Erubi module.
33
- # In Rails 4.2, calling const_defined? results in odd exceptions, which seems to be
34
- # solved by looking for ::Erubi first.
35
- # However, in JRuby, the const_defined? finds it anyway, so we must make sure that it's
36
- # not just a reference to ::Erubi.
37
- if defined?(::Erubi) && (::ActionView::Template::Handlers::ERB.const_get('Erubi') != ::Erubi)
38
- require "haml/helpers/safe_erubi_template"
39
- Haml::Filters::RailsErb.template_class = Haml::SafeErubiTemplate
40
- else
41
- require "haml/helpers/safe_erubis_template"
42
- Haml::Filters::RailsErb.template_class = Haml::SafeErubisTemplate
43
- end
44
- Haml::Template.options[:filters] = { 'erb' => Haml::Filters::RailsErb }
45
- end
6
+ initializer :haml, before: :load_config_initializers do |app|
7
+ require 'haml/rails_template'
46
8
  end
47
9
  end
48
10
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ require 'ripper'
3
+
4
+ module Haml
5
+ class RubyExpression < Ripper
6
+ class ParseError < StandardError; end
7
+
8
+ def self.syntax_error?(code)
9
+ self.new(code).parse
10
+ false
11
+ rescue ParseError
12
+ true
13
+ end
14
+
15
+ def self.string_literal?(code)
16
+ return false if syntax_error?(code)
17
+
18
+ type, instructions = Ripper.sexp(code)
19
+ return false if type != :program
20
+ return false if instructions.size > 1
21
+
22
+ type, _ = instructions.first
23
+ type == :string_literal
24
+ end
25
+
26
+ private
27
+
28
+ def on_parse_error(*)
29
+ raise ParseError
30
+ end
31
+ end
32
+ end