haml 5.1.2 → 6.3.0

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 (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