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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/test.yml +36 -0
- data/.gitignore +16 -15
- data/.yardopts +0 -3
- data/CHANGELOG.md +189 -1
- data/FAQ.md +1 -1
- data/Gemfile +20 -12
- data/MIT-LICENSE +1 -1
- data/README.md +10 -17
- data/REFERENCE.md +129 -164
- data/Rakefile +15 -89
- data/bin/bench +66 -0
- data/bin/console +11 -0
- data/bin/ruby +3 -0
- data/bin/setup +7 -0
- data/bin/stackprof +27 -0
- data/bin/test +24 -0
- data/exe/haml +6 -0
- data/haml.gemspec +34 -35
- data/lib/haml/ambles.rb +20 -0
- data/lib/haml/attribute_builder.rb +131 -133
- data/lib/haml/attribute_compiler.rb +91 -182
- data/lib/haml/attribute_parser.rb +92 -126
- data/lib/haml/cli.rb +154 -0
- data/lib/haml/compiler/children_compiler.rb +155 -0
- data/lib/haml/compiler/comment_compiler.rb +51 -0
- data/lib/haml/compiler/doctype_compiler.rb +52 -0
- data/lib/haml/compiler/script_compiler.rb +114 -0
- data/lib/haml/compiler/silent_script_compiler.rb +24 -0
- data/lib/haml/compiler/tag_compiler.rb +76 -0
- data/lib/haml/compiler.rb +63 -296
- data/lib/haml/dynamic_merger.rb +67 -0
- data/lib/haml/engine.rb +48 -227
- data/lib/haml/error.rb +5 -4
- data/lib/haml/escape.rb +13 -0
- data/lib/haml/escape_any.rb +21 -0
- data/lib/haml/filters/base.rb +12 -0
- data/lib/haml/filters/cdata.rb +20 -0
- data/lib/haml/filters/coffee.rb +17 -0
- data/lib/haml/filters/css.rb +33 -0
- data/lib/haml/filters/erb.rb +10 -0
- data/lib/haml/filters/escaped.rb +22 -0
- data/lib/haml/filters/javascript.rb +33 -0
- data/lib/haml/filters/less.rb +20 -0
- data/lib/haml/filters/markdown.rb +11 -0
- data/lib/haml/filters/plain.rb +29 -0
- data/lib/haml/filters/preserve.rb +22 -0
- data/lib/haml/filters/ruby.rb +10 -0
- data/lib/haml/filters/sass.rb +15 -0
- data/lib/haml/filters/scss.rb +15 -0
- data/lib/haml/filters/text_base.rb +25 -0
- data/lib/haml/filters/tilt_base.rb +59 -0
- data/lib/haml/filters.rb +54 -378
- data/lib/haml/force_escape.rb +29 -0
- data/lib/haml/helpers.rb +3 -691
- data/lib/haml/html.rb +22 -0
- data/lib/haml/identity.rb +13 -0
- data/lib/haml/object_ref.rb +35 -0
- data/lib/haml/parser.rb +190 -27
- data/lib/haml/rails_helpers.rb +53 -0
- data/lib/haml/rails_template.rb +62 -0
- data/lib/haml/railtie.rb +3 -41
- data/lib/haml/ruby_expression.rb +32 -0
- data/lib/haml/string_splitter.rb +140 -0
- data/lib/haml/template.rb +15 -34
- data/lib/haml/temple_line_counter.rb +2 -1
- data/lib/haml/util.rb +20 -16
- data/lib/haml/version.rb +1 -2
- data/lib/haml/whitespace.rb +8 -0
- data/lib/haml.rb +8 -20
- metadata +205 -53
- data/.gitmodules +0 -3
- data/.travis.yml +0 -97
- data/TODO +0 -24
- data/benchmark.rb +0 -70
- data/bin/haml +0 -9
- data/lib/haml/.gitattributes +0 -1
- data/lib/haml/buffer.rb +0 -238
- data/lib/haml/escapable.rb +0 -50
- data/lib/haml/exec.rb +0 -347
- data/lib/haml/generator.rb +0 -42
- data/lib/haml/helpers/action_view_extensions.rb +0 -60
- data/lib/haml/helpers/action_view_mods.rb +0 -132
- data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
- data/lib/haml/helpers/safe_erubi_template.rb +0 -20
- data/lib/haml/helpers/safe_erubis_template.rb +0 -33
- data/lib/haml/helpers/xss_mods.rb +0 -111
- data/lib/haml/options.rb +0 -273
- data/lib/haml/plugin.rb +0 -37
- data/lib/haml/sass_rails_filter.rb +0 -47
- data/lib/haml/template/options.rb +0 -27
- data/lib/haml/temple_engine.rb +0 -123
- data/yard/default/.gitignore +0 -1
- data/yard/default/fulldoc/html/css/common.sass +0 -15
- 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,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 =
|
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::
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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::
|
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::
|
576
|
-
# Ideally this logic should be placed in Haml::
|
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
|
-
|
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
|
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
|
-
|
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
|