haml 6.0.0.beta.1-java

Sign up to get free protection for your applications and to get access to all the features.
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