haml 6.0.0.beta.1-java

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +40 -0
  4. data/.gitignore +19 -0
  5. data/CHANGELOG.md +1515 -0
  6. data/FAQ.md +147 -0
  7. data/Gemfile +23 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +210 -0
  10. data/REFERENCE.md +1380 -0
  11. data/Rakefile +116 -0
  12. data/bin/bench +66 -0
  13. data/bin/console +11 -0
  14. data/bin/ruby +3 -0
  15. data/bin/setup +7 -0
  16. data/bin/stackprof +27 -0
  17. data/bin/test +24 -0
  18. data/exe/haml +6 -0
  19. data/ext/haml/extconf.rb +10 -0
  20. data/ext/haml/haml.c +537 -0
  21. data/ext/haml/hescape.c +108 -0
  22. data/ext/haml/hescape.h +20 -0
  23. data/haml.gemspec +47 -0
  24. data/lib/haml/ambles.rb +20 -0
  25. data/lib/haml/attribute_builder.rb +175 -0
  26. data/lib/haml/attribute_compiler.rb +128 -0
  27. data/lib/haml/attribute_parser.rb +110 -0
  28. data/lib/haml/cli.rb +154 -0
  29. data/lib/haml/compiler/children_compiler.rb +126 -0
  30. data/lib/haml/compiler/comment_compiler.rb +39 -0
  31. data/lib/haml/compiler/doctype_compiler.rb +46 -0
  32. data/lib/haml/compiler/script_compiler.rb +116 -0
  33. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  34. data/lib/haml/compiler/tag_compiler.rb +76 -0
  35. data/lib/haml/compiler.rb +97 -0
  36. data/lib/haml/dynamic_merger.rb +67 -0
  37. data/lib/haml/engine.rb +53 -0
  38. data/lib/haml/error.rb +16 -0
  39. data/lib/haml/escapable.rb +13 -0
  40. data/lib/haml/filters/base.rb +12 -0
  41. data/lib/haml/filters/cdata.rb +20 -0
  42. data/lib/haml/filters/coffee.rb +17 -0
  43. data/lib/haml/filters/css.rb +33 -0
  44. data/lib/haml/filters/erb.rb +10 -0
  45. data/lib/haml/filters/escaped.rb +22 -0
  46. data/lib/haml/filters/javascript.rb +33 -0
  47. data/lib/haml/filters/less.rb +20 -0
  48. data/lib/haml/filters/markdown.rb +11 -0
  49. data/lib/haml/filters/plain.rb +29 -0
  50. data/lib/haml/filters/preserve.rb +22 -0
  51. data/lib/haml/filters/ruby.rb +10 -0
  52. data/lib/haml/filters/sass.rb +15 -0
  53. data/lib/haml/filters/scss.rb +15 -0
  54. data/lib/haml/filters/text_base.rb +25 -0
  55. data/lib/haml/filters/tilt_base.rb +49 -0
  56. data/lib/haml/filters.rb +75 -0
  57. data/lib/haml/force_escapable.rb +29 -0
  58. data/lib/haml/haml_error.rb +66 -0
  59. data/lib/haml/helpers.rb +15 -0
  60. data/lib/haml/html.rb +22 -0
  61. data/lib/haml/identity.rb +13 -0
  62. data/lib/haml/object_ref.rb +30 -0
  63. data/lib/haml/parser.rb +986 -0
  64. data/lib/haml/rails_helpers.rb +51 -0
  65. data/lib/haml/rails_template.rb +55 -0
  66. data/lib/haml/railtie.rb +15 -0
  67. data/lib/haml/ruby_expression.rb +32 -0
  68. data/lib/haml/string_splitter.rb +20 -0
  69. data/lib/haml/template.rb +20 -0
  70. data/lib/haml/temple_line_counter.rb +31 -0
  71. data/lib/haml/util.rb +260 -0
  72. data/lib/haml/version.rb +4 -0
  73. data/lib/haml.rb +13 -0
  74. metadata +359 -0
@@ -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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ require 'rails'
3
+
4
+ module Haml
5
+ class Railtie < ::Rails::Railtie
6
+ initializer :haml, before: :load_config_initializers do |app|
7
+ # Load haml/plugin first to override if available
8
+ begin
9
+ require 'haml/plugin'
10
+ rescue LoadError
11
+ end
12
+ require 'haml/rails_template'
13
+ end
14
+ end
15
+ 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
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ require 'ripper'
3
+ require 'haml/ruby_expression'
4
+
5
+ module Haml
6
+ module StringSplitter
7
+ # `code` param must be valid string literal
8
+ def self.compile(code)
9
+ unless Ripper.respond_to?(:lex) # truffleruby doesn't have Ripper.lex
10
+ return [[:dynamic, code]]
11
+ end
12
+
13
+ begin
14
+ Temple::Filters::StringSplitter.compile(code)
15
+ rescue Temple::FilterError => e
16
+ raise Haml::InternalError.new(e.message)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: false
2
+ require 'temple'
3
+ require 'haml/engine'
4
+ require 'haml/helpers'
5
+
6
+ module Haml
7
+ Template = Temple::Templates::Tilt.create(
8
+ Haml::Engine,
9
+ register_as: [:haml, :haml],
10
+ )
11
+
12
+ module TemplateExtension
13
+ # Activate Haml::Helpers for tilt templates.
14
+ # https://github.com/judofyr/temple/blob/v0.7.6/lib/temple/mixins/template.rb#L7-L11
15
+ def compile(*)
16
+ "extend Haml::Helpers; #{super}"
17
+ end
18
+ end
19
+ Template.send(:extend, TemplateExtension)
20
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module Haml
3
+ # A module to count lines of expected code. This would be faster than actual code generation
4
+ # and counting newlines in it.
5
+ module TempleLineCounter
6
+ class UnexpectedExpression < StandardError; end
7
+
8
+ def self.count_lines(exp)
9
+ type, *args = exp
10
+ case type
11
+ when :multi
12
+ args.map { |a| count_lines(a) }.reduce(:+) || 0
13
+ when :dynamic, :code
14
+ args.first.count("\n")
15
+ when :static
16
+ 0 # It has not real newline "\n" but escaped "\\n".
17
+ when :case
18
+ arg, *cases = args
19
+ arg.count("\n") + cases.map do |cond, e|
20
+ (cond == :else ? 0 : cond.count("\n")) + count_lines(e)
21
+ end.reduce(:+)
22
+ when :escape
23
+ count_lines(args[1])
24
+ when :newline
25
+ 1
26
+ else
27
+ raise UnexpectedExpression.new("[HAML BUG] Unexpected Temple expression '#{type}' is given!")
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/haml/util.rb ADDED
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
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 Haml
13
+ # A module containing various useful functions.
14
+ module Util
15
+ extend self
16
+
17
+ # Java extension is not implemented for JRuby yet.
18
+ # TruffleRuby does not implement `rb_ary_sort_bang`, etc.
19
+ if /java/ === RUBY_PLATFORM || RUBY_ENGINE == 'truffleruby'
20
+ require 'cgi/escape'
21
+
22
+ def self.escape_html(html)
23
+ CGI.escapeHTML(html.to_s)
24
+ end
25
+ else
26
+ require 'haml/haml' # Haml::Util.escape_html
27
+ end
28
+
29
+ def self.escape_html_safe(html)
30
+ html.html_safe? ? html : escape_html(html)
31
+ end
32
+
33
+ # Silence all output to STDERR within a block.
34
+ #
35
+ # @yield A block in which no output will be printed to STDERR
36
+ def silence_warnings
37
+ the_real_stderr, $stderr = $stderr, StringIO.new
38
+ yield
39
+ ensure
40
+ $stderr = the_real_stderr
41
+ end
42
+
43
+ ## Rails XSS Safety
44
+
45
+ # Whether or not ActionView's XSS protection is available and enabled,
46
+ # as is the default for Rails 3.0+, and optional for version 2.3.5+.
47
+ # Overridden in haml/template.rb if this is the case.
48
+ #
49
+ # @return [Boolean]
50
+ def rails_xss_safe?
51
+ false
52
+ end
53
+
54
+ # Checks that the encoding of a string is valid
55
+ # and cleans up potential encoding gotchas like the UTF-8 BOM.
56
+ # If it's not, yields an error string describing the invalid character
57
+ # and the line on which it occurs.
58
+ #
59
+ # @param str [String] The string of which to check the encoding
60
+ # @yield [msg] A block in which an encoding error can be raised.
61
+ # Only yields if there is an encoding error
62
+ # @yieldparam msg [String] The error message to be raised
63
+ # @return [String] `str`, potentially with encoding gotchas like BOMs removed
64
+ def check_encoding(str)
65
+ if str.valid_encoding?
66
+ # Get rid of the Unicode BOM if possible
67
+ # Shortcut for UTF-8 which might be the majority case
68
+ if str.encoding == Encoding::UTF_8
69
+ return str.gsub(/\A\uFEFF/, '')
70
+ elsif str.encoding.name =~ /^UTF-(16|32)(BE|LE)?$/
71
+ return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding)), '')
72
+ else
73
+ return str
74
+ end
75
+ end
76
+
77
+ encoding = str.encoding
78
+ newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding(Encoding::ASCII_8BIT))
79
+ str.force_encoding(Encoding::ASCII_8BIT).split(newlines).each_with_index do |line, i|
80
+ begin
81
+ line.encode(encoding)
82
+ rescue Encoding::UndefinedConversionError => e
83
+ yield <<MSG.rstrip, i + 1
84
+ Invalid #{encoding.name} character #{e.error_char.dump}
85
+ MSG
86
+ end
87
+ end
88
+ return str
89
+ end
90
+
91
+ # Like {\#check\_encoding}, but also checks for a Ruby-style `-# coding:` comment
92
+ # at the beginning of the template and uses that encoding if it exists.
93
+ #
94
+ # The Haml encoding rules are simple.
95
+ # If a `-# coding:` comment exists,
96
+ # we assume that that's the original encoding of the document.
97
+ # Otherwise, we use whatever encoding Ruby has.
98
+ #
99
+ # Haml uses the same rules for parsing coding comments as Ruby.
100
+ # This means that it can understand Emacs-style comments
101
+ # (e.g. `-*- encoding: "utf-8" -*-`),
102
+ # and also that it cannot understand non-ASCII-compatible encodings
103
+ # such as `UTF-16` and `UTF-32`.
104
+ #
105
+ # @param str [String] The Haml template of which to check the encoding
106
+ # @yield [msg] A block in which an encoding error can be raised.
107
+ # Only yields if there is an encoding error
108
+ # @yieldparam msg [String] The error message to be raised
109
+ # @return [String] The original string encoded properly
110
+ # @raise [ArgumentError] if the document declares an unknown encoding
111
+ def check_haml_encoding(str, &block)
112
+ str = str.dup if str.frozen?
113
+
114
+ bom, encoding = parse_haml_magic_comment(str)
115
+ if encoding; str.force_encoding(encoding)
116
+ elsif bom; str.force_encoding(Encoding::UTF_8)
117
+ end
118
+
119
+ return check_encoding(str, &block)
120
+ end
121
+
122
+ # Like `Object#inspect`, but preserves non-ASCII characters rather than escaping them.
123
+ # This is necessary so that the precompiled Haml template can be `#encode`d into `@options[:encoding]`
124
+ # before being evaluated.
125
+ #
126
+ # @param obj {Object}
127
+ # @return {String}
128
+ def inspect_obj(obj)
129
+ case obj
130
+ when String
131
+ %Q!"#{obj.gsub(/[\x00-\x7F]+/) {|s| s.dump[1...-1]}}"!
132
+ when Symbol
133
+ ":#{inspect_obj(obj.to_s)}"
134
+ else
135
+ obj.inspect
136
+ end
137
+ end
138
+
139
+ # Scans through a string looking for the interoplation-opening `#{`
140
+ # and, when it's found, yields the scanner to the calling code
141
+ # so it can handle it properly.
142
+ #
143
+ # The scanner will have any backslashes immediately in front of the `#{`
144
+ # as the second capture group (`scan[2]`),
145
+ # and the text prior to that as the first (`scan[1]`).
146
+ #
147
+ # @yieldparam scan [StringScanner] The scanner scanning through the string
148
+ # @return [String] The text remaining in the scanner after all `#{`s have been processed
149
+ def handle_interpolation(str)
150
+ scan = StringScanner.new(str)
151
+ yield scan while scan.scan(/(.*?)(\\*)#([\{@$])/)
152
+ scan.rest
153
+ end
154
+
155
+ # Moves a scanner through a balanced pair of characters.
156
+ # For example:
157
+ #
158
+ # Foo (Bar (Baz bang) bop) (Bang (bop bip))
159
+ # ^ ^
160
+ # from to
161
+ #
162
+ # @param scanner [StringScanner] The string scanner to move
163
+ # @param start [String] The character opening the balanced pair.
164
+ # @param finish [String] The character closing the balanced pair.
165
+ # @param count [Fixnum] The number of opening characters matched
166
+ # before calling this method
167
+ # @return [(String, String)] The string matched within the balanced pair
168
+ # and the rest of the string.
169
+ # `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
170
+ def balance(scanner, start, finish, count = 0)
171
+ str = ''.dup
172
+ scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
173
+ regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
174
+ while scanner.scan(regexp)
175
+ str << scanner.matched
176
+ count += 1 if scanner.matched[-1] == start
177
+ count -= 1 if scanner.matched[-1] == finish
178
+ return [str.strip, scanner.rest] if count == 0
179
+ end
180
+ end
181
+
182
+ # Formats a string for use in error messages about indentation.
183
+ #
184
+ # @param indentation [String] The string used for indentation
185
+ # @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`)
186
+ def human_indentation(indentation)
187
+ if !indentation.include?(?\t)
188
+ noun = 'space'
189
+ elsif !indentation.include?(?\s)
190
+ noun = 'tab'
191
+ else
192
+ return indentation.inspect
193
+ end
194
+
195
+ singular = indentation.length == 1
196
+ "#{indentation.length} #{noun}#{'s' unless singular}"
197
+ end
198
+
199
+ def contains_interpolation?(str)
200
+ /#[\{$@]/ === str
201
+ end
202
+
203
+ def unescape_interpolation(str, escape_html = nil)
204
+ res = ''.dup
205
+ rest = Haml::Util.handle_interpolation str.dump do |scan|
206
+ escapes = (scan[2].size - 1) / 2
207
+ char = scan[3] # '{', '@' or '$'
208
+ res << scan.matched[0...-3 - escapes]
209
+ if escapes % 2 == 1
210
+ res << "\##{char}"
211
+ else
212
+ interpolated = if char == '{'
213
+ balance(scan, ?{, ?}, 1)[0][0...-1]
214
+ else
215
+ scan.scan(/\w+/)
216
+ end
217
+ content = eval("\"#{interpolated}\"")
218
+ content = "#{char}#{content}" if char == '@' || char == '$'
219
+ content = "CGI.escapeHTML((#{content}).to_s)" if escape_html
220
+
221
+ res << "\#{#{content}}"
222
+ end
223
+ end
224
+ res + rest
225
+ end
226
+
227
+ private
228
+
229
+ # Parses a magic comment at the beginning of a Haml file.
230
+ # The parsing rules are basically the same as Ruby's.
231
+ #
232
+ # @return [(Boolean, String or nil)]
233
+ # Whether the document begins with a UTF-8 BOM,
234
+ # and the declared encoding of the document (or nil if none is declared)
235
+ def parse_haml_magic_comment(str)
236
+ scanner = StringScanner.new(str.dup.force_encoding(Encoding::ASCII_8BIT))
237
+ bom = scanner.scan(/\xEF\xBB\xBF/n)
238
+ return bom unless scanner.scan(/-\s*#\s*/n)
239
+ if (coding = try_parse_haml_emacs_magic_comment(scanner))
240
+ return bom, coding
241
+ end
242
+
243
+ return bom unless scanner.scan(/.*?coding[=:]\s*([\w-]+)/in)
244
+ return bom, scanner[1]
245
+ end
246
+
247
+ def try_parse_haml_emacs_magic_comment(scanner)
248
+ pos = scanner.pos
249
+ return unless scanner.scan(/.*?-\*-\s*/n)
250
+ # From Ruby's parse.y
251
+ return unless scanner.scan(/([^\s'":;]+)\s*:\s*("(?:\\.|[^"])*"|[^"\s;]+?)[\s;]*-\*-/n)
252
+ name, val = scanner[1], scanner[2]
253
+ return unless name =~ /(en)?coding/in
254
+ val = $1 if val =~ /^"(.*)"$/n
255
+ return val
256
+ ensure
257
+ scanner.pos = pos
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Haml
3
+ VERSION = '6.0.0.beta.1'
4
+ end
data/lib/haml.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ require 'haml/engine'
3
+ require 'haml/error'
4
+ require 'haml/version'
5
+ require 'haml/template'
6
+
7
+ if File.basename($0) != 'haml'
8
+ begin
9
+ require 'rails'
10
+ require 'haml/railtie'
11
+ rescue LoadError
12
+ end
13
+ end