hamlit 2.9.3

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +45 -0
  4. data/CHANGELOG.md +676 -0
  5. data/Gemfile +28 -0
  6. data/LICENSE.txt +44 -0
  7. data/README.md +150 -0
  8. data/REFERENCE.md +266 -0
  9. data/Rakefile +117 -0
  10. data/benchmark/boolean_attribute.haml +6 -0
  11. data/benchmark/class_attribute.haml +5 -0
  12. data/benchmark/common_attribute.haml +3 -0
  13. data/benchmark/data_attribute.haml +4 -0
  14. data/benchmark/dynamic_attributes/boolean_attribute.haml +4 -0
  15. data/benchmark/dynamic_attributes/class_attribute.haml +4 -0
  16. data/benchmark/dynamic_attributes/common_attribute.haml +2 -0
  17. data/benchmark/dynamic_attributes/data_attribute.haml +2 -0
  18. data/benchmark/dynamic_attributes/id_attribute.haml +2 -0
  19. data/benchmark/dynamic_boolean_attribute.haml +4 -0
  20. data/benchmark/etc/attribute_builder.haml +5 -0
  21. data/benchmark/etc/real_sample.haml +888 -0
  22. data/benchmark/etc/real_sample.rb +11 -0
  23. data/benchmark/etc/static_analyzer.haml +1 -0
  24. data/benchmark/etc/string_interpolation.haml +2 -0
  25. data/benchmark/etc/tags.haml +3 -0
  26. data/benchmark/etc/tags_loop.haml +2 -0
  27. data/benchmark/ext/build_data.rb +17 -0
  28. data/benchmark/ext/build_id.rb +13 -0
  29. data/benchmark/id_attribute.haml +3 -0
  30. data/benchmark/plain.haml +4 -0
  31. data/benchmark/script.haml +4 -0
  32. data/benchmark/slim/LICENSE +21 -0
  33. data/benchmark/slim/context.rb +11 -0
  34. data/benchmark/slim/run-benchmarks.rb +94 -0
  35. data/benchmark/slim/view.erb +23 -0
  36. data/benchmark/slim/view.haml +18 -0
  37. data/benchmark/slim/view.slim +17 -0
  38. data/benchmark/utils/benchmark_ips_extension.rb +43 -0
  39. data/bin/bench +77 -0
  40. data/bin/console +11 -0
  41. data/bin/ruby +3 -0
  42. data/bin/setup +7 -0
  43. data/bin/stackprof +27 -0
  44. data/bin/test +24 -0
  45. data/exe/hamlit +6 -0
  46. data/ext/hamlit/extconf.rb +10 -0
  47. data/ext/hamlit/hamlit.c +553 -0
  48. data/ext/hamlit/hescape.c +108 -0
  49. data/ext/hamlit/hescape.h +20 -0
  50. data/hamlit.gemspec +45 -0
  51. data/lib/hamlit.rb +11 -0
  52. data/lib/hamlit/attribute_builder.rb +173 -0
  53. data/lib/hamlit/attribute_compiler.rb +123 -0
  54. data/lib/hamlit/attribute_parser.rb +110 -0
  55. data/lib/hamlit/cli.rb +130 -0
  56. data/lib/hamlit/compiler.rb +97 -0
  57. data/lib/hamlit/compiler/children_compiler.rb +112 -0
  58. data/lib/hamlit/compiler/comment_compiler.rb +36 -0
  59. data/lib/hamlit/compiler/doctype_compiler.rb +46 -0
  60. data/lib/hamlit/compiler/script_compiler.rb +102 -0
  61. data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
  62. data/lib/hamlit/compiler/tag_compiler.rb +74 -0
  63. data/lib/hamlit/engine.rb +37 -0
  64. data/lib/hamlit/error.rb +15 -0
  65. data/lib/hamlit/escapable.rb +13 -0
  66. data/lib/hamlit/filters.rb +75 -0
  67. data/lib/hamlit/filters/base.rb +12 -0
  68. data/lib/hamlit/filters/cdata.rb +20 -0
  69. data/lib/hamlit/filters/coffee.rb +17 -0
  70. data/lib/hamlit/filters/css.rb +33 -0
  71. data/lib/hamlit/filters/erb.rb +10 -0
  72. data/lib/hamlit/filters/escaped.rb +22 -0
  73. data/lib/hamlit/filters/javascript.rb +33 -0
  74. data/lib/hamlit/filters/less.rb +20 -0
  75. data/lib/hamlit/filters/markdown.rb +10 -0
  76. data/lib/hamlit/filters/plain.rb +29 -0
  77. data/lib/hamlit/filters/preserve.rb +22 -0
  78. data/lib/hamlit/filters/ruby.rb +10 -0
  79. data/lib/hamlit/filters/sass.rb +15 -0
  80. data/lib/hamlit/filters/scss.rb +15 -0
  81. data/lib/hamlit/filters/text_base.rb +25 -0
  82. data/lib/hamlit/filters/tilt_base.rb +49 -0
  83. data/lib/hamlit/force_escapable.rb +29 -0
  84. data/lib/hamlit/helpers.rb +15 -0
  85. data/lib/hamlit/html.rb +14 -0
  86. data/lib/hamlit/identity.rb +13 -0
  87. data/lib/hamlit/object_ref.rb +30 -0
  88. data/lib/hamlit/parser.rb +49 -0
  89. data/lib/hamlit/parser/MIT-LICENSE +20 -0
  90. data/lib/hamlit/parser/README.md +30 -0
  91. data/lib/hamlit/parser/haml_buffer.rb +348 -0
  92. data/lib/hamlit/parser/haml_compiler.rb +553 -0
  93. data/lib/hamlit/parser/haml_error.rb +61 -0
  94. data/lib/hamlit/parser/haml_helpers.rb +727 -0
  95. data/lib/hamlit/parser/haml_options.rb +286 -0
  96. data/lib/hamlit/parser/haml_parser.rb +800 -0
  97. data/lib/hamlit/parser/haml_util.rb +288 -0
  98. data/lib/hamlit/parser/haml_xss_mods.rb +109 -0
  99. data/lib/hamlit/rails_helpers.rb +51 -0
  100. data/lib/hamlit/rails_template.rb +59 -0
  101. data/lib/hamlit/railtie.rb +10 -0
  102. data/lib/hamlit/ruby_expression.rb +32 -0
  103. data/lib/hamlit/string_splitter.rb +88 -0
  104. data/lib/hamlit/template.rb +28 -0
  105. data/lib/hamlit/utils.rb +18 -0
  106. data/lib/hamlit/version.rb +4 -0
  107. metadata +361 -0
@@ -0,0 +1,288 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'erubis/tiny'
5
+ rescue LoadError
6
+ require 'erb'
7
+ end
8
+ require 'set'
9
+ require 'stringio'
10
+ require 'strscan'
11
+
12
+ module Hamlit
13
+ # A module containing various useful functions.
14
+ module HamlUtil
15
+ extend self
16
+
17
+ # Silence all output to STDERR within a block.
18
+ #
19
+ # @yield A block in which no output will be printed to STDERR
20
+ def silence_warnings
21
+ the_real_stderr, $stderr = $stderr, StringIO.new
22
+ yield
23
+ ensure
24
+ $stderr = the_real_stderr
25
+ end
26
+
27
+ ## Rails XSS Safety
28
+
29
+ # Whether or not ActionView's XSS protection is available and enabled,
30
+ # as is the default for Rails 3.0+, and optional for version 2.3.5+.
31
+ # Overridden in haml/template.rb if this is the case.
32
+ #
33
+ # @return [Boolean]
34
+ def rails_xss_safe?
35
+ false
36
+ end
37
+
38
+ # Returns the given text, marked as being HTML-safe.
39
+ # With older versions of the Rails XSS-safety mechanism,
40
+ # this destructively modifies the HTML-safety of `text`.
41
+ #
42
+ # It only works if you are using ActiveSupport or the parameter `text`
43
+ # implements the #html_safe method.
44
+ #
45
+ # @param text [String, nil]
46
+ # @return [String, nil] `text`, marked as HTML-safe
47
+ def html_safe(text)
48
+ return unless text
49
+ text.html_safe
50
+ end
51
+
52
+ # Checks that the encoding of a string is valid
53
+ # and cleans up potential encoding gotchas like the UTF-8 BOM.
54
+ # If it's not, yields an error string describing the invalid character
55
+ # and the line on which it occurs.
56
+ #
57
+ # @param str [String] The string of which to check the encoding
58
+ # @yield [msg] A block in which an encoding error can be raised.
59
+ # Only yields if there is an encoding error
60
+ # @yieldparam msg [String] The error message to be raised
61
+ # @return [String] `str`, potentially with encoding gotchas like BOMs removed
62
+ def check_encoding(str)
63
+ if str.valid_encoding?
64
+ # Get rid of the Unicode BOM if possible
65
+ # Shortcut for UTF-8 which might be the majority case
66
+ if str.encoding == Encoding::UTF_8
67
+ return str.gsub(/\A\uFEFF/, '')
68
+ elsif str.encoding.name =~ /^UTF-(16|32)(BE|LE)?$/
69
+ return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding)), '')
70
+ else
71
+ return str
72
+ end
73
+ end
74
+
75
+ encoding = str.encoding
76
+ newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding(Encoding::ASCII_8BIT))
77
+ str.force_encoding(Encoding::ASCII_8BIT).split(newlines).each_with_index do |line, i|
78
+ begin
79
+ line.encode(encoding)
80
+ rescue Encoding::UndefinedConversionError => e
81
+ yield <<MSG.rstrip, i + 1
82
+ Invalid #{encoding.name} character #{e.error_char.dump}
83
+ MSG
84
+ end
85
+ end
86
+ return str
87
+ end
88
+
89
+ # Like {\#check\_encoding}, but also checks for a Ruby-style `-# coding:` comment
90
+ # at the beginning of the template and uses that encoding if it exists.
91
+ #
92
+ # The Haml encoding rules are simple.
93
+ # If a `-# coding:` comment exists,
94
+ # we assume that that's the original encoding of the document.
95
+ # Otherwise, we use whatever encoding Ruby has.
96
+ #
97
+ # Haml uses the same rules for parsing coding comments as Ruby.
98
+ # This means that it can understand Emacs-style comments
99
+ # (e.g. `-*- encoding: "utf-8" -*-`),
100
+ # and also that it cannot understand non-ASCII-compatible encodings
101
+ # such as `UTF-16` and `UTF-32`.
102
+ #
103
+ # @param str [String] The Haml template of which to check the encoding
104
+ # @yield [msg] A block in which an encoding error can be raised.
105
+ # Only yields if there is an encoding error
106
+ # @yieldparam msg [String] The error message to be raised
107
+ # @return [String] The original string encoded properly
108
+ # @raise [ArgumentError] if the document declares an unknown encoding
109
+ def check_haml_encoding(str, &block)
110
+ str = str.dup if str.frozen?
111
+
112
+ bom, encoding = parse_haml_magic_comment(str)
113
+ if encoding; str.force_encoding(encoding)
114
+ elsif bom; str.force_encoding(Encoding::UTF_8)
115
+ end
116
+
117
+ return check_encoding(str, &block)
118
+ end
119
+
120
+ # Like `Object#inspect`, but preserves non-ASCII characters rather than escaping them.
121
+ # This is necessary so that the precompiled Haml template can be `#encode`d into `@options[:encoding]`
122
+ # before being evaluated.
123
+ #
124
+ # @param obj {Object}
125
+ # @return {String}
126
+ def inspect_obj(obj)
127
+ case obj
128
+ when String
129
+ %Q!"#{obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]}}"!
130
+ when Symbol
131
+ ":#{inspect_obj(obj.to_s)}"
132
+ else
133
+ obj.inspect
134
+ end
135
+ end
136
+
137
+ # Scans through a string looking for the interoplation-opening `#{`
138
+ # and, when it's found, yields the scanner to the calling code
139
+ # so it can handle it properly.
140
+ #
141
+ # The scanner will have any backslashes immediately in front of the `#{`
142
+ # as the second capture group (`scan[2]`),
143
+ # and the text prior to that as the first (`scan[1]`).
144
+ #
145
+ # @yieldparam scan [StringScanner] The scanner scanning through the string
146
+ # @return [String] The text remaining in the scanner after all `#{`s have been processed
147
+ def handle_interpolation(str)
148
+ scan = StringScanner.new(str)
149
+ yield scan while scan.scan(/(.*?)(\\*)#([\{@$])/)
150
+ scan.rest
151
+ end
152
+
153
+ # Moves a scanner through a balanced pair of characters.
154
+ # For example:
155
+ #
156
+ # Foo (Bar (Baz bang) bop) (Bang (bop bip))
157
+ # ^ ^
158
+ # from to
159
+ #
160
+ # @param scanner [StringScanner] The string scanner to move
161
+ # @param start [String] The character opening the balanced pair.
162
+ # @param finish [String] The character closing the balanced pair.
163
+ # @param count [Fixnum] The number of opening characters matched
164
+ # before calling this method
165
+ # @return [(String, String)] The string matched within the balanced pair
166
+ # and the rest of the string.
167
+ # `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
168
+ def balance(scanner, start, finish, count = 0)
169
+ str = ''
170
+ scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
171
+ regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
172
+ while scanner.scan(regexp)
173
+ str << scanner.matched
174
+ count += 1 if scanner.matched[-1] == start
175
+ count -= 1 if scanner.matched[-1] == finish
176
+ return [str.strip, scanner.rest] if count == 0
177
+ end
178
+ end
179
+
180
+ # Formats a string for use in error messages about indentation.
181
+ #
182
+ # @param indentation [String] The string used for indentation
183
+ # @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`)
184
+ def human_indentation(indentation)
185
+ if !indentation.include?(?\t)
186
+ noun = 'space'
187
+ elsif !indentation.include?(?\s)
188
+ noun = 'tab'
189
+ else
190
+ return indentation.inspect
191
+ end
192
+
193
+ singular = indentation.length == 1
194
+ "#{indentation.length} #{noun}#{'s' unless singular}"
195
+ end
196
+
197
+ def contains_interpolation?(str)
198
+ /#[\{$@]/ === str
199
+ end
200
+
201
+ # Original Haml::Util.unescape_interpolation
202
+ # ex) slow_unescape_interpolation('foo#{bar}baz"', escape_html: true)
203
+ # #=> "\"foo\#{::Hamlit::HamlHelpers.html_escape((bar))}baz\\\"\""
204
+ def slow_unescape_interpolation(str, escape_html = nil)
205
+ res = ''
206
+ rest = ::Hamlit::HamlUtil.handle_interpolation str.dump do |scan|
207
+ escapes = (scan[2].size - 1) / 2
208
+ char = scan[3] # '{', '@' or '$'
209
+ res << scan.matched[0...-3 - escapes]
210
+ if escapes % 2 == 1
211
+ res << "\##{char}"
212
+ else
213
+ interpolated = if char == '{'
214
+ balance(scan, ?{, ?}, 1)[0][0...-1]
215
+ else
216
+ scan.scan(/\w+/)
217
+ end
218
+ content = eval('"' + interpolated + '"')
219
+ content.prepend(char) if char == '@' || char == '$'
220
+ content = "::Hamlit::HamlHelpers.html_escape((#{content}))" if escape_html
221
+
222
+ res << "\#{#{content}}"
223
+ end
224
+ end
225
+ res + rest
226
+ end
227
+
228
+ # Customized Haml::Util.unescape_interpolation to handle escape by Hamlit.
229
+ # It wraps double quotes to given `str` with escaping `"`.
230
+ #
231
+ # ex) unescape_interpolation('foo#{bar}baz"') #=> "\"foo\#{bar}baz\\\"\""
232
+ def unescape_interpolation(str)
233
+ res = ''
234
+ rest = ::Hamlit::HamlUtil.handle_interpolation str.dump do |scan|
235
+ escapes = (scan[2].size - 1) / 2
236
+ char = scan[3] # '{', '@' or '$'
237
+ res << scan.matched[0...-3 - escapes]
238
+ if escapes % 2 == 1
239
+ res << "\##{char}"
240
+ else
241
+ interpolated = if char == '{'
242
+ balance(scan, ?{, ?}, 1)[0][0...-1]
243
+ else
244
+ scan.scan(/\w+/)
245
+ end
246
+ content = eval('"' + interpolated + '"')
247
+ content.prepend(char) if char == '@' || char == '$'
248
+
249
+ res << "\#{#{content}}"
250
+ end
251
+ end
252
+ res + rest
253
+ end
254
+
255
+ private
256
+
257
+ # Parses a magic comment at the beginning of a Haml file.
258
+ # The parsing rules are basically the same as Ruby's.
259
+ #
260
+ # @return [(Boolean, String or nil)]
261
+ # Whether the document begins with a UTF-8 BOM,
262
+ # and the declared encoding of the document (or nil if none is declared)
263
+ def parse_haml_magic_comment(str)
264
+ scanner = StringScanner.new(str.dup.force_encoding(Encoding::ASCII_8BIT))
265
+ bom = scanner.scan(/\xEF\xBB\xBF/n)
266
+ return bom unless scanner.scan(/-\s*#\s*/n)
267
+ if coding = try_parse_haml_emacs_magic_comment(scanner)
268
+ return bom, coding
269
+ end
270
+
271
+ return bom unless scanner.scan(/.*?coding[=:]\s*([\w-]+)/in)
272
+ return bom, scanner[1]
273
+ end
274
+
275
+ def try_parse_haml_emacs_magic_comment(scanner)
276
+ pos = scanner.pos
277
+ return unless scanner.scan(/.*?-\*-\s*/n)
278
+ # From Ruby's parse.y
279
+ return unless scanner.scan(/([^\s'":;]+)\s*:\s*("(?:\\.|[^"])*"|[^"\s;]+?)[\s;]*-\*-/n)
280
+ name, val = scanner[1], scanner[2]
281
+ return unless name =~ /(en)?coding/in
282
+ val = $1 if val =~ /^"(.*)"$/n
283
+ return val
284
+ ensure
285
+ scanner.pos = pos
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,109 @@
1
+ module Hamlit
2
+ module HamlHelpers
3
+ # This module overrides Haml helpers to work properly
4
+ # in the context of ActionView.
5
+ # Currently it's only used for modifying the helpers
6
+ # to work with Rails' XSS protection methods.
7
+ module XssMods
8
+ def self.included(base)
9
+ %w[html_escape find_and_preserve preserve list_of surround
10
+ precede succeed capture_haml haml_concat haml_internal_concat haml_indent
11
+ escape_once].each do |name|
12
+ base.send(:alias_method, "#{name}_without_haml_xss", name)
13
+ base.send(:alias_method, name, "#{name}_with_haml_xss")
14
+ end
15
+ end
16
+
17
+ # Don't escape text that's already safe,
18
+ # output is always HTML safe
19
+ def html_escape_with_haml_xss(text)
20
+ str = text.to_s
21
+ return text if str.html_safe?
22
+ ::Hamlit::HamlUtil.html_safe(html_escape_without_haml_xss(str))
23
+ end
24
+
25
+ # Output is always HTML safe
26
+ def find_and_preserve_with_haml_xss(*args, &block)
27
+ ::Hamlit::HamlUtil.html_safe(find_and_preserve_without_haml_xss(*args, &block))
28
+ end
29
+
30
+ # Output is always HTML safe
31
+ def preserve_with_haml_xss(*args, &block)
32
+ ::Hamlit::HamlUtil.html_safe(preserve_without_haml_xss(*args, &block))
33
+ end
34
+
35
+ # Output is always HTML safe
36
+ def list_of_with_haml_xss(*args, &block)
37
+ ::Hamlit::HamlUtil.html_safe(list_of_without_haml_xss(*args, &block))
38
+ end
39
+
40
+ # Input is escaped, output is always HTML safe
41
+ def surround_with_haml_xss(front, back = front, &block)
42
+ ::Hamlit::HamlUtil.html_safe(
43
+ surround_without_haml_xss(
44
+ haml_xss_html_escape(front),
45
+ haml_xss_html_escape(back),
46
+ &block))
47
+ end
48
+
49
+ # Input is escaped, output is always HTML safe
50
+ def precede_with_haml_xss(str, &block)
51
+ ::Hamlit::HamlUtil.html_safe(precede_without_haml_xss(haml_xss_html_escape(str), &block))
52
+ end
53
+
54
+ # Input is escaped, output is always HTML safe
55
+ def succeed_with_haml_xss(str, &block)
56
+ ::Hamlit::HamlUtil.html_safe(succeed_without_haml_xss(haml_xss_html_escape(str), &block))
57
+ end
58
+
59
+ # Output is always HTML safe
60
+ def capture_haml_with_haml_xss(*args, &block)
61
+ ::Hamlit::HamlUtil.html_safe(capture_haml_without_haml_xss(*args, &block))
62
+ end
63
+
64
+ # Input will be escaped unless this is in a `with_raw_haml_concat`
65
+ # block. See #Haml::Helpers::ActionViewExtensions#with_raw_haml_concat.
66
+ def haml_concat_with_haml_xss(text = "")
67
+ raw = instance_variable_defined?(:@_haml_concat_raw) ? @_haml_concat_raw : false
68
+ if raw
69
+ haml_internal_concat_raw text
70
+ else
71
+ haml_internal_concat text
72
+ end
73
+ ErrorReturn.new("haml_concat")
74
+ end
75
+
76
+ # Input is escaped
77
+ def haml_internal_concat_with_haml_xss(text="", newline=true, indent=true)
78
+ haml_internal_concat_without_haml_xss(haml_xss_html_escape(text), newline, indent)
79
+ end
80
+ private :haml_internal_concat_with_haml_xss
81
+
82
+ # Output is always HTML safe
83
+ def haml_indent_with_haml_xss
84
+ ::Hamlit::HamlUtil.html_safe(haml_indent_without_haml_xss)
85
+ end
86
+
87
+ # Output is always HTML safe
88
+ def escape_once_with_haml_xss(*args)
89
+ ::Hamlit::HamlUtil.html_safe(escape_once_without_haml_xss(*args))
90
+ end
91
+
92
+ private
93
+
94
+ # Escapes the HTML in the text if and only if
95
+ # Rails XSS protection is enabled *and* the `:escape_html` option is set.
96
+ def haml_xss_html_escape(text)
97
+ return text unless ::Hamlit::HamlUtil.rails_xss_safe? && haml_buffer.options[:escape_html]
98
+ html_escape(text)
99
+ end
100
+ end
101
+
102
+ class ErrorReturn
103
+ # Any attempt to treat ErrorReturn as a string should cause it to blow up.
104
+ alias_method :html_safe, :to_s
105
+ alias_method :html_safe?, :to_s
106
+ alias_method :html_safe!, :to_s
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: false
2
+ require 'hamlit/helpers'
3
+
4
+ # Currently this Hamlit::Helpers depends on
5
+ # ActionView internal implementation. (not desired)
6
+ module Hamlit
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,59 @@
1
+ # frozen_string_literal: true
2
+ require 'temple'
3
+ require 'hamlit/engine'
4
+ require 'hamlit/rails_helpers'
5
+ require 'hamlit/parser/haml_helpers'
6
+ require 'hamlit/parser/haml_util'
7
+
8
+ module Hamlit
9
+ class RailsTemplate
10
+ # Compatible with: https://github.com/judofyr/temple/blob/v0.7.7/lib/temple/mixins/options.rb#L15-L24
11
+ class << self
12
+ def options
13
+ @options ||= {
14
+ generator: Temple::Generators::RailsOutputBuffer,
15
+ use_html_safe: true,
16
+ streaming: true,
17
+ buffer_class: 'ActionView::OutputBuffer',
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
+ Engine.new(options).call(source)
37
+ end
38
+
39
+ def supports_streaming?
40
+ RailsTemplate.options[:streaming]
41
+ end
42
+ end
43
+ ActionView::Template.register_template_handler(:haml, RailsTemplate.new)
44
+
45
+ # https://github.com/haml/haml/blob/4.0.7/lib/haml/template.rb
46
+ module HamlHelpers
47
+ require 'hamlit/parser/haml_xss_mods'
48
+ include Hamlit::HamlHelpers::XssMods
49
+ end
50
+
51
+ module HamlUtil
52
+ undef :rails_xss_safe? if defined? rails_xss_safe?
53
+ def rails_xss_safe?; true; end
54
+ end
55
+ end
56
+
57
+ # Haml extends Haml::Helpers in ActionView each time.
58
+ # It costs much, so Hamlit includes a compatible module at first.
59
+ ActionView::Base.send :include, Hamlit::RailsHelpers