haml 2.0.10 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of haml might be problematic. Click here for more details.
- data/.yardopts +5 -0
- data/MIT-LICENSE +1 -1
- data/README.md +347 -0
- data/Rakefile +124 -19
- data/VERSION +1 -1
- data/VERSION_NAME +1 -0
- data/extra/haml-mode.el +397 -78
- data/extra/sass-mode.el +148 -36
- data/extra/update_watch.rb +13 -0
- data/lib/haml.rb +15 -993
- data/lib/haml/buffer.rb +131 -84
- data/lib/haml/engine.rb +129 -97
- data/lib/haml/error.rb +7 -7
- data/lib/haml/exec.rb +127 -42
- data/lib/haml/filters.rb +107 -42
- data/lib/haml/helpers.rb +210 -156
- data/lib/haml/helpers/action_view_extensions.rb +34 -39
- data/lib/haml/helpers/action_view_mods.rb +132 -139
- data/lib/haml/html.rb +77 -65
- data/lib/haml/precompiler.rb +404 -213
- data/lib/haml/shared.rb +78 -0
- data/lib/haml/template.rb +14 -14
- data/lib/haml/template/patch.rb +2 -2
- data/lib/haml/template/plugin.rb +2 -3
- data/lib/haml/util.rb +211 -6
- data/lib/haml/version.rb +30 -13
- data/lib/sass.rb +7 -856
- data/lib/sass/css.rb +169 -161
- data/lib/sass/engine.rb +344 -328
- data/lib/sass/environment.rb +79 -0
- data/lib/sass/error.rb +33 -11
- data/lib/sass/files.rb +139 -0
- data/lib/sass/plugin.rb +160 -117
- data/lib/sass/plugin/merb.rb +7 -6
- data/lib/sass/plugin/rails.rb +5 -6
- data/lib/sass/repl.rb +58 -0
- data/lib/sass/script.rb +59 -0
- data/lib/sass/script/bool.rb +17 -0
- data/lib/sass/script/color.rb +183 -0
- data/lib/sass/script/funcall.rb +50 -0
- data/lib/sass/script/functions.rb +198 -0
- data/lib/sass/script/lexer.rb +178 -0
- data/lib/sass/script/literal.rb +177 -0
- data/lib/sass/script/node.rb +14 -0
- data/lib/sass/script/number.rb +381 -0
- data/lib/sass/script/operation.rb +45 -0
- data/lib/sass/script/parser.rb +172 -0
- data/lib/sass/script/string.rb +12 -0
- data/lib/sass/script/unary_operation.rb +34 -0
- data/lib/sass/script/variable.rb +31 -0
- data/lib/sass/tree/comment_node.rb +73 -10
- data/lib/sass/tree/debug_node.rb +30 -0
- data/lib/sass/tree/directive_node.rb +42 -17
- data/lib/sass/tree/file_node.rb +41 -0
- data/lib/sass/tree/for_node.rb +48 -0
- data/lib/sass/tree/if_node.rb +54 -0
- data/lib/sass/tree/mixin_def_node.rb +29 -0
- data/lib/sass/tree/mixin_node.rb +48 -0
- data/lib/sass/tree/node.rb +214 -11
- data/lib/sass/tree/prop_node.rb +109 -0
- data/lib/sass/tree/rule_node.rb +178 -51
- data/lib/sass/tree/variable_node.rb +34 -0
- data/lib/sass/tree/while_node.rb +31 -0
- data/test/haml/engine_test.rb +331 -36
- data/test/haml/helper_test.rb +12 -1
- data/test/haml/results/content_for_layout.xhtml +0 -3
- data/test/haml/results/filters.xhtml +2 -0
- data/test/haml/results/list.xhtml +1 -1
- data/test/haml/template_test.rb +7 -2
- data/test/haml/templates/content_for_layout.haml +0 -2
- data/test/haml/templates/list.haml +1 -1
- data/test/haml/util_test.rb +92 -0
- data/test/sass/css2sass_test.rb +69 -24
- data/test/sass/engine_test.rb +586 -64
- data/test/sass/functions_test.rb +125 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +81 -28
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/{constants.css → script.css} +4 -4
- data/test/sass/results/subdir/subdir.css +2 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +258 -0
- data/test/sass/templates/import.sass +1 -1
- data/test/sass/templates/importee.sass +7 -2
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/{constants.sass → script.sass} +11 -10
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/subdir.sass +2 -2
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +14 -0
- metadata +77 -19
- data/FAQ +0 -138
- data/README.rdoc +0 -319
- data/lib/sass/constant.rb +0 -216
- data/lib/sass/constant/color.rb +0 -101
- data/lib/sass/constant/literal.rb +0 -54
- data/lib/sass/constant/nil.rb +0 -9
- data/lib/sass/constant/number.rb +0 -87
- data/lib/sass/constant/operation.rb +0 -30
- data/lib/sass/constant/string.rb +0 -22
- data/lib/sass/tree/attr_node.rb +0 -57
- data/lib/sass/tree/value_node.rb +0 -20
@@ -0,0 +1,109 @@
|
|
1
|
+
module Sass::Tree
|
2
|
+
# A static node reprenting a CSS property.
|
3
|
+
#
|
4
|
+
# @see Sass::Tree
|
5
|
+
class PropNode < Node
|
6
|
+
# The name of the property.
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
attr_accessor :name
|
10
|
+
|
11
|
+
# The value of the property,
|
12
|
+
# either a plain string or a SassScript parse tree.
|
13
|
+
#
|
14
|
+
# @return [String, Script::Node]
|
15
|
+
attr_accessor :value
|
16
|
+
|
17
|
+
# @param name [String] See \{#name}
|
18
|
+
# @param value [String] See \{#value}
|
19
|
+
# @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
|
20
|
+
# `:old` if it uses `:a b`-style syntax
|
21
|
+
def initialize(name, value, prop_syntax)
|
22
|
+
@name = name
|
23
|
+
@value = value
|
24
|
+
@prop_syntax = prop_syntax
|
25
|
+
super()
|
26
|
+
end
|
27
|
+
|
28
|
+
# Compares the names and values of two properties.
|
29
|
+
#
|
30
|
+
# @param other [Object] The object to compare with
|
31
|
+
# @return [Boolean] Whether or not this node and the other object
|
32
|
+
# are the same
|
33
|
+
def ==(other)
|
34
|
+
self.class == other.class && name == other.name && value == other.value && super
|
35
|
+
end
|
36
|
+
|
37
|
+
# Computes the CSS for the property.
|
38
|
+
#
|
39
|
+
# @param tabs [Fixnum] The level of indentation for the CSS
|
40
|
+
# @param parent_name [String] The name of the parent property (e.g. `text`) or nil
|
41
|
+
# @return [String] The resulting CSS
|
42
|
+
# @raise [Sass::SyntaxError] if the property uses invalid syntax
|
43
|
+
def to_s(tabs, parent_name = nil)
|
44
|
+
if @options[:property_syntax] == :old && @prop_syntax == :new
|
45
|
+
raise Sass::SyntaxError.new("Illegal property syntax: can't use new syntax when :property_syntax => :old is set.")
|
46
|
+
elsif @options[:property_syntax] == :new && @prop_syntax == :old
|
47
|
+
raise Sass::SyntaxError.new("Illegal property syntax: can't use old syntax when :property_syntax => :new is set.")
|
48
|
+
end
|
49
|
+
|
50
|
+
if value[-1] == ?;
|
51
|
+
raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no \";\" required at end-of-line).", @line)
|
52
|
+
end
|
53
|
+
real_name = name
|
54
|
+
real_name = "#{parent_name}-#{real_name}" if parent_name
|
55
|
+
|
56
|
+
if value.empty? && children.empty?
|
57
|
+
raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value).", @line)
|
58
|
+
end
|
59
|
+
|
60
|
+
join_string = case style
|
61
|
+
when :compact; ' '
|
62
|
+
when :compressed; ''
|
63
|
+
else "\n"
|
64
|
+
end
|
65
|
+
spaces = ' ' * (tabs - 1)
|
66
|
+
to_return = ''
|
67
|
+
if !value.empty?
|
68
|
+
to_return << "#{spaces}#{real_name}:#{style == :compressed ? '' : ' '}#{value};#{join_string}"
|
69
|
+
end
|
70
|
+
|
71
|
+
children.each do |kid|
|
72
|
+
next if kid.invisible?
|
73
|
+
to_return << kid.to_s(tabs, real_name) << join_string
|
74
|
+
end
|
75
|
+
|
76
|
+
(style == :compressed && parent_name) ? to_return : to_return[0...-1]
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
# Runs any SassScript that may be embedded in the property.
|
82
|
+
#
|
83
|
+
# @param environment [Sass::Environment] The lexical environment containing
|
84
|
+
# variable and mixin values
|
85
|
+
def perform!(environment)
|
86
|
+
@name = interpolate(@name, environment)
|
87
|
+
@value = @value.is_a?(String) ? interpolate(@value, environment) : @value.perform(environment).to_s
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns an error message if the given child node is invalid,
|
92
|
+
# and false otherwise.
|
93
|
+
#
|
94
|
+
# {PropNode} only allows other {PropNode}s and {CommentNode}s as children.
|
95
|
+
# @param child [Tree::Node] A potential child node
|
96
|
+
# @return [String] An error message if the child is invalid, or nil otherwise
|
97
|
+
def invalid_child?(child)
|
98
|
+
if !child.is_a?(PropNode) && !child.is_a?(CommentNode)
|
99
|
+
"Illegal nesting: Only properties may be nested beneath properties."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def declaration
|
106
|
+
@prop_syntax == :new ? "#{name}: #{value}" : ":#{name} #{value}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/sass/tree/rule_node.rb
CHANGED
@@ -1,93 +1,220 @@
|
|
1
|
-
require '
|
2
|
-
require 'sass/tree/attr_node'
|
1
|
+
require 'pathname'
|
3
2
|
|
4
3
|
module Sass::Tree
|
5
|
-
class RuleNode <
|
4
|
+
class RuleNode < Node
|
6
5
|
# The character used to include the parent selector
|
7
6
|
PARENT = '&'
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
# The CSS selectors for this rule.
|
9
|
+
# Each string is a selector line, and the lines are meant to be separated by commas.
|
10
|
+
# For example,
|
11
|
+
#
|
12
|
+
# foo, bar, baz,
|
13
|
+
# bip, bop, bup
|
14
|
+
#
|
15
|
+
# would be
|
16
|
+
#
|
17
|
+
# ["foo, bar, baz",
|
18
|
+
# "bip, bop, bup"]
|
19
|
+
#
|
20
|
+
# @return [Array<String>]
|
21
|
+
attr_accessor :rules
|
11
22
|
|
12
|
-
|
13
|
-
|
23
|
+
# The CSS selectors for this rule,
|
24
|
+
# parsed for commas and parent-references.
|
25
|
+
# It's only set once {Tree::Node#perform} has been called.
|
26
|
+
#
|
27
|
+
# It's an array of arrays of arrays.
|
28
|
+
# The first level of arrays represents distinct lines in the Sass file;
|
29
|
+
# the second level represents comma-separated selectors;
|
30
|
+
# the third represents structure within those selectors,
|
31
|
+
# currently only parent-refs (represented by `:parent`).
|
32
|
+
# For example,
|
33
|
+
#
|
34
|
+
# &.foo, bar, baz,
|
35
|
+
# bip, &.bop, bup
|
36
|
+
#
|
37
|
+
# would be
|
38
|
+
#
|
39
|
+
# [[[:parent, "foo"], ["bar"], ["baz"]],
|
40
|
+
# [["bip"], [:parent, "bop"], ["bup"]]]
|
41
|
+
#
|
42
|
+
# @return [Array<Array<Array<String|Symbol>>>]
|
43
|
+
attr_accessor :parsed_rules
|
44
|
+
|
45
|
+
# @param rule [String] The first CSS rule. See \{#rules}
|
46
|
+
def initialize(rule)
|
47
|
+
@rules = [rule]
|
48
|
+
super()
|
14
49
|
end
|
15
50
|
|
16
|
-
|
17
|
-
|
51
|
+
# Compares the contents of two rules.
|
52
|
+
#
|
53
|
+
# @param other [Object] The object to compare with
|
54
|
+
# @return [Boolean] Whether or not this node and the other object
|
55
|
+
# are the same
|
56
|
+
def ==(other)
|
57
|
+
self.class == other.class && rules == other.rules && super
|
18
58
|
end
|
19
59
|
|
60
|
+
# Adds another {RuleNode}'s rules to this one's.
|
61
|
+
#
|
62
|
+
# @param node [RuleNode] The other node
|
20
63
|
def add_rules(node)
|
21
|
-
|
22
|
-
self.rule += node.rules
|
64
|
+
@rules += node.rules
|
23
65
|
end
|
24
66
|
|
67
|
+
# @return [Boolean] Whether or not this rule is continued on the next line
|
25
68
|
def continued?
|
26
|
-
|
69
|
+
@rules.last[-1] == ?,
|
27
70
|
end
|
28
71
|
|
72
|
+
# Computes the CSS for the rule.
|
73
|
+
#
|
74
|
+
# @param tabs [Fixnum] The level of indentation for the CSS
|
75
|
+
# @param super_rules [Array<Array<String>>] The rules for the parent node
|
76
|
+
# (see \{#rules}), or `nil` if there are no parents
|
77
|
+
# @return [String] The resulting CSS
|
78
|
+
# @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
|
29
79
|
def to_s(tabs, super_rules = nil)
|
30
|
-
|
80
|
+
resolved_rules = resolve_parent_refs(super_rules)
|
81
|
+
|
82
|
+
properties = []
|
31
83
|
sub_rules = []
|
32
84
|
|
33
|
-
|
34
|
-
|
35
|
-
line_separator = [:nested, :expanded].include?(@style) ? ",\n" : rule_separator
|
85
|
+
rule_separator = style == :compressed ? ',' : ', '
|
86
|
+
line_separator = [:nested, :expanded].include?(style) ? ",\n" : rule_separator
|
36
87
|
rule_indent = ' ' * (tabs - 1)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
if rule.include?(PARENT)
|
43
|
-
rule.gsub(PARENT, super_rule)
|
44
|
-
else
|
45
|
-
"#{super_rule} #{rule}"
|
46
|
-
end
|
47
|
-
end.join(rule_separator)
|
48
|
-
end.join(line_separator)
|
49
|
-
end.join(rule_separator)
|
50
|
-
end.join(line_separator)
|
51
|
-
elsif self.rules.any? { |r| r.include?(PARENT) }
|
52
|
-
raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.", line)
|
53
|
-
else
|
54
|
-
per_rule_indent, total_indent = [:nested, :expanded].include?(@style) ? [rule_indent, ''] : ['', rule_indent]
|
55
|
-
total_indent + self.rules.map do |r|
|
56
|
-
per_rule_indent + r.gsub(/,$/, '').gsub(rule_split, rule_separator).rstrip
|
57
|
-
end.join(line_separator)
|
58
|
-
end
|
88
|
+
per_rule_indent, total_indent = [:nested, :expanded].include?(style) ? [rule_indent, ''] : ['', rule_indent]
|
89
|
+
|
90
|
+
total_rule = total_indent + resolved_rules.map do |line|
|
91
|
+
per_rule_indent + line.join(rule_separator)
|
92
|
+
end.join(line_separator)
|
59
93
|
|
60
94
|
children.each do |child|
|
95
|
+
next if child.invisible?
|
61
96
|
if child.is_a? RuleNode
|
62
97
|
sub_rules << child
|
63
98
|
else
|
64
|
-
|
99
|
+
properties << child
|
65
100
|
end
|
66
101
|
end
|
67
102
|
|
68
103
|
to_return = ''
|
69
|
-
if !
|
104
|
+
if !properties.empty?
|
70
105
|
old_spaces = ' ' * (tabs - 1)
|
71
106
|
spaces = ' ' * tabs
|
72
|
-
if @style
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
107
|
+
if @options[:line_comments] && style != :compressed
|
108
|
+
to_return << "#{old_spaces}/* line #{line}"
|
109
|
+
|
110
|
+
if filename
|
111
|
+
relative_filename = if @options[:css_filename]
|
112
|
+
begin
|
113
|
+
Pathname.new(filename).relative_path_from(
|
114
|
+
Pathname.new(File.dirname(@options[:css_filename]))).to_s
|
115
|
+
rescue ArgumentError
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
relative_filename ||= filename
|
120
|
+
to_return << ", #{relative_filename}"
|
121
|
+
end
|
122
|
+
|
123
|
+
to_return << " */\n"
|
124
|
+
end
|
125
|
+
|
126
|
+
if style == :compact
|
127
|
+
properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(' ')
|
128
|
+
to_return << "#{total_rule} { #{properties} }\n"
|
129
|
+
elsif style == :compressed
|
130
|
+
properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(';')
|
131
|
+
to_return << "#{total_rule}{#{properties}}"
|
78
132
|
else
|
79
|
-
|
80
|
-
|
81
|
-
to_return << "#{total_rule} {\n#{
|
133
|
+
properties = properties.map { |a| a.to_s(tabs + 1) }.select{|a| a && a.length > 0}.join("\n")
|
134
|
+
end_props = (style == :expanded ? "\n" + old_spaces : ' ')
|
135
|
+
to_return << "#{total_rule} {\n#{properties}#{end_props}}\n"
|
82
136
|
end
|
83
137
|
end
|
84
138
|
|
85
|
-
tabs += 1 unless
|
139
|
+
tabs += 1 unless properties.empty? || style != :nested
|
86
140
|
sub_rules.each do |sub|
|
87
|
-
to_return << sub.to_s(tabs,
|
141
|
+
to_return << sub.to_s(tabs, resolved_rules)
|
88
142
|
end
|
89
143
|
|
90
144
|
to_return
|
91
145
|
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
|
149
|
+
# Runs any SassScript that may be embedded in the rule,
|
150
|
+
# and parses the selectors for commas.
|
151
|
+
#
|
152
|
+
# @param environment [Sass::Environment] The lexical environment containing
|
153
|
+
# variable and mixin values
|
154
|
+
def perform!(environment)
|
155
|
+
@parsed_rules = @rules.map {|r| parse_selector(interpolate(r, environment))}
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def resolve_parent_refs(super_rules)
|
162
|
+
if super_rules.nil?
|
163
|
+
return @parsed_rules.map do |line|
|
164
|
+
line.map do |rule|
|
165
|
+
if rule.include?(:parent)
|
166
|
+
raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.", self.line)
|
167
|
+
end
|
168
|
+
|
169
|
+
rule.join
|
170
|
+
end.compact
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
new_rules = []
|
175
|
+
super_rules.each do |super_line|
|
176
|
+
@parsed_rules.each do |line|
|
177
|
+
new_rules << []
|
178
|
+
|
179
|
+
super_line.each do |super_rule|
|
180
|
+
line.each do |rule|
|
181
|
+
rule = [:parent, " ", *rule] unless rule.include?(:parent)
|
182
|
+
|
183
|
+
new_rules.last << rule.map do |segment|
|
184
|
+
next segment unless segment == :parent
|
185
|
+
super_rule
|
186
|
+
end.join
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
new_rules
|
192
|
+
end
|
193
|
+
|
194
|
+
def parse_selector(text)
|
195
|
+
scanner = StringScanner.new(text)
|
196
|
+
rules = [[]]
|
197
|
+
|
198
|
+
while scanner.rest?
|
199
|
+
rules.last << scanner.scan(/[^",&]*/)
|
200
|
+
case scanner.scan(/./)
|
201
|
+
when '&'; rules.last << :parent
|
202
|
+
when ','
|
203
|
+
scanner.scan(/\s*/)
|
204
|
+
rules << [] if scanner.rest?
|
205
|
+
when '"'
|
206
|
+
rules.last << '"' << scanner.scan(/([^"\\]|\\.)*/)
|
207
|
+
# We don't want to enforce that strings are closed,
|
208
|
+
# but we do want to consume quotes or trailing backslashes.
|
209
|
+
rules.last << scanner.scan(/./) if scanner.rest?
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
rules.map! do |l|
|
214
|
+
Haml::Util.merge_adjacent_strings(l).reject {|r| r.is_a?(String) && r.empty?}
|
215
|
+
end
|
216
|
+
|
217
|
+
rules
|
218
|
+
end
|
92
219
|
end
|
93
220
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sass
|
2
|
+
module Tree
|
3
|
+
# A dynamic node representing a variable definition.
|
4
|
+
#
|
5
|
+
# @see Sass::Tree
|
6
|
+
class VariableNode < Node
|
7
|
+
# @param name [String] The name of the variable
|
8
|
+
# @param expr [Script::Node] The parse tree for the initial variable value
|
9
|
+
# @param guarded [Boolean] Whether this is a guarded variable assignment (`||=`)
|
10
|
+
def initialize(name, expr, guarded)
|
11
|
+
@name = name
|
12
|
+
@expr = expr
|
13
|
+
@guarded = guarded
|
14
|
+
super()
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Loads the new variable value into the environment.
|
20
|
+
#
|
21
|
+
# @param environment [Sass::Environment] The lexical environment containing
|
22
|
+
# variable and mixin values
|
23
|
+
def _perform(environment)
|
24
|
+
if @guarded && environment.var(@name).nil?
|
25
|
+
environment.set_var(@name, @expr.perform(environment))
|
26
|
+
elsif !@guarded
|
27
|
+
environment.set_var(@name, @expr.perform(environment))
|
28
|
+
end
|
29
|
+
|
30
|
+
[]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'sass/tree/node'
|
2
|
+
|
3
|
+
module Sass::Tree
|
4
|
+
# A dynamic node representing a Sass `@while` loop.
|
5
|
+
#
|
6
|
+
# @see Sass::Tree
|
7
|
+
class WhileNode < Node
|
8
|
+
# @param expr [Script::Node] The parse tree for the continue expression
|
9
|
+
def initialize(expr)
|
10
|
+
@expr = expr
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
# Runs the child nodes until the continue expression becomes false.
|
17
|
+
#
|
18
|
+
# @param environment [Sass::Environment] The lexical environment containing
|
19
|
+
# variable and mixin values
|
20
|
+
# @return [Array<Tree::Node>] The resulting static nodes
|
21
|
+
# @see Sass::Tree
|
22
|
+
def _perform(environment)
|
23
|
+
children = []
|
24
|
+
new_environment = Sass::Environment.new(environment)
|
25
|
+
while @expr.perform(environment).to_bool
|
26
|
+
children += perform_children(new_environment)
|
27
|
+
end
|
28
|
+
children
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/test/haml/engine_test.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
2
3
|
require File.dirname(__FILE__) + '/../test_helper'
|
3
4
|
|
4
5
|
class EngineTest < Test::Unit::TestCase
|
@@ -17,39 +18,53 @@ class EngineTest < Test::Unit::TestCase
|
|
17
18
|
"~" => "There's no Ruby code for ~ to evaluate.",
|
18
19
|
"=" => "There's no Ruby code for = to evaluate.",
|
19
20
|
"%p/\n a" => "Illegal nesting: nesting within a self-closing tag is illegal.",
|
20
|
-
"%p\n\ta" => <<END.strip,
|
21
|
-
A tab character was used for indentation. Haml must be indented using two spaces.
|
22
|
-
Are you sure you have soft tabs enabled in your editor?
|
23
|
-
END
|
24
|
-
"%p\n a" => "1 space was used for indentation. Haml must be indented using two spaces.",
|
25
|
-
"%p\n a" => "3 spaces were used for indentation. Haml must be indented using two spaces.",
|
26
|
-
"%p\n a" => "4 spaces were used for indentation. Haml must be indented using two spaces.",
|
27
21
|
":a\n b" => ['Filter "a" is not defined.', 1],
|
28
22
|
":a= b" => 'Invalid filter name ":a= b".',
|
29
23
|
"." => "Illegal element: classes and ids must have values.",
|
30
24
|
".#" => "Illegal element: classes and ids must have values.",
|
31
25
|
".{} a" => "Illegal element: classes and ids must have values.",
|
26
|
+
".() a" => "Illegal element: classes and ids must have values.",
|
32
27
|
".= a" => "Illegal element: classes and ids must have values.",
|
33
28
|
"%p..a" => "Illegal element: classes and ids must have values.",
|
34
29
|
"%a/ b" => "Self-closing tags can't have content.",
|
30
|
+
"%p{:a => 'b',\n:c => 'd'}/ e" => ["Self-closing tags can't have content.", 2],
|
31
|
+
"%p{:a => 'b',\n:c => 'd'}=" => ["There's no Ruby code for = to evaluate.", 2],
|
32
|
+
"%p.{:a => 'b',\n:c => 'd'} e" => ["Illegal element: classes and ids must have values.", 1],
|
33
|
+
"%p{:a => 'b',\n:c => 'd',\n:e => 'f'}\n%p/ a" => ["Self-closing tags can't have content.", 4],
|
34
|
+
"%p{:a => 'b',\n:c => 'd',\n:e => 'f'}\n- raise 'foo'" => ["foo", 4],
|
35
|
+
"%p{:a => 'b',\n:c => raise('foo'),\n:e => 'f'}" => ["foo", 2],
|
36
|
+
"%p{:a => 'b',\n:c => 'd',\n:e => raise('foo')}" => ["foo", 3],
|
35
37
|
" %p foo" => "Indenting at the beginning of the document is illegal.",
|
36
38
|
" %p foo" => "Indenting at the beginning of the document is illegal.",
|
37
39
|
"- end" => "You don't need to use \"- end\" in Haml. Use indentation instead:\n- if foo?\n %strong Foo!\n- else\n Not foo.",
|
38
40
|
" \n\t\n %p foo" => ["Indenting at the beginning of the document is illegal.", 3],
|
41
|
+
"\n\n %p foo" => ["Indenting at the beginning of the document is illegal.", 3],
|
42
|
+
"%p\n foo\n foo" => ["Inconsistent indentation: 1 space was used for indentation, but the rest of the document was indented using 2 spaces.", 3],
|
43
|
+
"%p\n foo\n%p\n foo" => ["Inconsistent indentation: 1 space was used for indentation, but the rest of the document was indented using 2 spaces.", 4],
|
44
|
+
"%p\n\t\tfoo\n\tfoo" => ["Inconsistent indentation: 1 tab was used for indentation, but the rest of the document was indented using 2 tabs.", 3],
|
45
|
+
"%p\n foo\n foo" => ["Inconsistent indentation: 3 spaces were used for indentation, but the rest of the document was indented using 2 spaces.", 3],
|
46
|
+
"%p\n foo\n %p\n bar" => ["Inconsistent indentation: 3 spaces were used for indentation, but the rest of the document was indented using 2 spaces.", 4],
|
47
|
+
"%p\n :plain\n bar\n \t baz" => ['Inconsistent indentation: " \t " was used for indentation, but the rest of the document was indented using 2 spaces.', 4],
|
48
|
+
"%p\n foo\n%p\n bar" => ["The line was indented 2 levels deeper than the previous line.", 4],
|
49
|
+
"%p\n foo\n %p\n bar" => ["The line was indented 3 levels deeper than the previous line.", 4],
|
50
|
+
"%p\n \tfoo" => ["Indentation can't use both tabs and spaces.", 2],
|
51
|
+
"%p(" => "Invalid attribute list: \"(\".",
|
52
|
+
"%p(foo=\nbar)" => ["Invalid attribute list: \"(foo=\".", 1],
|
53
|
+
"%p(foo=)" => "Invalid attribute list: \"(foo=)\".",
|
54
|
+
"%p(foo 'bar')" => "Invalid attribute list: \"(foo 'bar')\".",
|
55
|
+
"%p(foo 'bar'\nbaz='bang')" => ["Invalid attribute list: \"(foo 'bar'\".", 1],
|
56
|
+
"%p(foo='bar'\nbaz 'bang'\nbip='bop')" => ["Invalid attribute list: \"(foo='bar' baz 'bang'\".", 2],
|
39
57
|
|
40
58
|
# Regression tests
|
41
59
|
"- raise 'foo'\n\n\n\nbar" => ["foo", 1],
|
42
60
|
"= 'foo'\n-raise 'foo'" => ["foo", 2],
|
43
61
|
"\n\n\n- raise 'foo'" => ["foo", 4],
|
62
|
+
"%p foo |\n bar |\n baz |\nbop\n- raise 'foo'" => ["foo", 5],
|
44
63
|
"foo\n\n\n bar" => ["Illegal nesting: nesting within plain text is illegal.", 4],
|
45
64
|
"%p/\n\n bar" => ["Illegal nesting: nesting within a self-closing tag is illegal.", 3],
|
46
65
|
"%p foo\n\n bar" => ["Illegal nesting: content can't be both given on the same line as %p and nested within it.", 3],
|
47
66
|
"/ foo\n\n bar" => ["Illegal nesting: nesting within a tag that already has content is illegal.", 3],
|
48
67
|
"!!!\n\n bar" => ["Illegal nesting: nesting within a header command is illegal.", 3],
|
49
|
-
"foo\n\n\n\tbar" => [<<END.strip, 4],
|
50
|
-
A tab character was used for indentation. Haml must be indented using two spaces.
|
51
|
-
Are you sure you have soft tabs enabled in your editor?
|
52
|
-
END
|
53
68
|
"foo\n:ruby\n 1\n 2\n 3\n- raise 'foo'" => ["foo", 6],
|
54
69
|
}
|
55
70
|
|
@@ -70,7 +85,20 @@ END
|
|
70
85
|
end
|
71
86
|
Haml::Engine.new(text, options)
|
72
87
|
end
|
73
|
-
|
88
|
+
|
89
|
+
def test_empty_render
|
90
|
+
assert_equal "", render("")
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_flexible_tabulation
|
94
|
+
assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
|
95
|
+
render("%p\n foo\n%q\n bar\n %a\n baz"))
|
96
|
+
assert_equal("<p>\n foo\n</p>\n<q>\n bar\n <a>\n baz\n </a>\n</q>\n",
|
97
|
+
render("%p\n\tfoo\n%q\n\tbar\n\t%a\n\t\tbaz"))
|
98
|
+
assert_equal("<p>\n \t \t bar\n baz\n</p>\n",
|
99
|
+
render("%p\n :plain\n \t \t bar\n baz"))
|
100
|
+
end
|
101
|
+
|
74
102
|
def test_empty_render_should_remain_empty
|
75
103
|
assert_equal('', render(''))
|
76
104
|
end
|
@@ -109,14 +137,19 @@ END
|
|
109
137
|
assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
|
110
138
|
end
|
111
139
|
|
112
|
-
def
|
113
|
-
assert_equal("<p>Hello World</p>\n", render('%p
|
114
|
-
assert_equal("<p>\n Hello World\n</p>\n", render("%p\n
|
140
|
+
def test_interpolation
|
141
|
+
assert_equal("<p>Hello World</p>\n", render('%p Hello #{who}', :locals => {:who => 'World'}))
|
142
|
+
assert_equal("<p>\n Hello World\n</p>\n", render("%p\n Hello \#{who}", :locals => {:who => 'World'}))
|
115
143
|
end
|
116
144
|
|
117
|
-
def
|
145
|
+
def test_interpolation_in_the_middle_of_a_string
|
118
146
|
assert_equal("\"title 'Title'. \"\n",
|
119
|
-
render("
|
147
|
+
render("\"title '\#{\"Title\"}'. \""))
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_interpolation_at_the_beginning_of_a_line
|
151
|
+
assert_equal("<p>2</p>\n", render('%p #{1 + 1}'))
|
152
|
+
assert_equal("<p>\n 2\n</p>\n", render("%p\n \#{1 + 1}"))
|
120
153
|
end
|
121
154
|
|
122
155
|
def test_nil_tag_value_should_render_as_empty
|
@@ -135,6 +168,13 @@ END
|
|
135
168
|
assert_equal("<img alt='' src='/foo.png' />\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}"))
|
136
169
|
end
|
137
170
|
|
171
|
+
def test_attribute_hash_with_newlines
|
172
|
+
assert_equal("<p a='b' c='d'>foop</p>\n", render("%p{:a => 'b',\n :c => 'd'} foop"))
|
173
|
+
assert_equal("<p a='b' c='d'>\n foop\n</p>\n", render("%p{:a => 'b',\n :c => 'd'}\n foop"))
|
174
|
+
assert_equal("<p a='b' c='d' />\n", render("%p{:a => 'b',\n :c => 'd'}/"))
|
175
|
+
assert_equal("<p a='b' c='d' e='f'></p>\n", render("%p{:a => 'b',\n :c => 'd',\n :e => 'f'}"))
|
176
|
+
end
|
177
|
+
|
138
178
|
def test_attr_hashes_not_modified
|
139
179
|
hash = {:color => 'red'}
|
140
180
|
assert_equal(<<HTML, render(<<HAML, :locals => {:hash => hash}))
|
@@ -188,8 +228,19 @@ SOURCE
|
|
188
228
|
render("%p{:foo => 'bar', :bar => false, :baz => 'false'}", :format => :xhtml))
|
189
229
|
end
|
190
230
|
|
231
|
+
def test_both_whitespace_nukes_work_together
|
232
|
+
assert_equal(<<RESULT, render(<<SOURCE))
|
233
|
+
<p><q>Foo
|
234
|
+
Bar</q></p>
|
235
|
+
RESULT
|
236
|
+
%p
|
237
|
+
%q><= "Foo\\nBar"
|
238
|
+
SOURCE
|
239
|
+
end
|
240
|
+
|
241
|
+
# Regression tests
|
242
|
+
|
191
243
|
def test_whitespace_nuke_with_both_newlines
|
192
|
-
# Regression test
|
193
244
|
assert_equal("<p>foo</p>\n", render('%p<= "\nfoo\n"'))
|
194
245
|
assert_equal(<<HTML, render(<<HAML))
|
195
246
|
<p>
|
@@ -201,17 +252,6 @@ HTML
|
|
201
252
|
HAML
|
202
253
|
end
|
203
254
|
|
204
|
-
def test_both_whitespace_nukes_work_together
|
205
|
-
assert_equal(<<RESULT, render(<<SOURCE))
|
206
|
-
<p><q>Foo
|
207
|
-
Bar</q></p>
|
208
|
-
RESULT
|
209
|
-
%p
|
210
|
-
%q><= "Foo\\nBar"
|
211
|
-
SOURCE
|
212
|
-
end
|
213
|
-
|
214
|
-
# Mostly a regression test
|
215
255
|
def test_both_case_indentation_work_with_deeply_nested_code
|
216
256
|
result = <<RESULT
|
217
257
|
<h2>
|
@@ -238,6 +278,87 @@ HAML
|
|
238
278
|
HAML
|
239
279
|
end
|
240
280
|
|
281
|
+
def test_equals_block_with_ugly
|
282
|
+
assert_equal("foo\n", render(<<HAML, :ugly => true))
|
283
|
+
= capture_haml do
|
284
|
+
foo
|
285
|
+
HAML
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_plain_equals_with_ugly
|
289
|
+
assert_equal("foo\nbar\n", render(<<HAML, :ugly => true))
|
290
|
+
= "foo"
|
291
|
+
bar
|
292
|
+
HAML
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_inline_if
|
296
|
+
assert_equal(<<HTML, render(<<HAML))
|
297
|
+
<p>One</p>
|
298
|
+
<p></p>
|
299
|
+
<p>Three</p>
|
300
|
+
HTML
|
301
|
+
- for name in ["One", "Two", "Three"]
|
302
|
+
%p= name unless name == "Two"
|
303
|
+
HAML
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_end_with_method_call
|
307
|
+
assert_equal(<<HTML, render(<<HAML))
|
308
|
+
2|3|4
|
309
|
+
b-a-r
|
310
|
+
HTML
|
311
|
+
= [1, 2, 3].map do |i|
|
312
|
+
- i + 1
|
313
|
+
- end.join("|")
|
314
|
+
= "bar".gsub(/./) do |s|
|
315
|
+
- s + "-"
|
316
|
+
- end.gsub(/-$/) do |s|
|
317
|
+
- ''
|
318
|
+
HAML
|
319
|
+
end
|
320
|
+
|
321
|
+
def test_multiline_with_colon_after_filter
|
322
|
+
assert_equal(<<HTML, render(<<HAML))
|
323
|
+
Foo
|
324
|
+
Bar
|
325
|
+
HTML
|
326
|
+
:plain
|
327
|
+
Foo
|
328
|
+
= { :a => "Bar", |
|
329
|
+
:b => "Baz" }[:a] |
|
330
|
+
HAML
|
331
|
+
assert_equal(<<HTML, render(<<HAML))
|
332
|
+
|
333
|
+
Bar
|
334
|
+
HTML
|
335
|
+
:plain
|
336
|
+
= { :a => "Bar", |
|
337
|
+
:b => "Baz" }[:a] |
|
338
|
+
HAML
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_multiline_in_filter
|
342
|
+
assert_equal(<<HTML, render(<<HAML))
|
343
|
+
Foo |
|
344
|
+
Bar |
|
345
|
+
Baz
|
346
|
+
HTML
|
347
|
+
:plain
|
348
|
+
Foo |
|
349
|
+
Bar |
|
350
|
+
Baz
|
351
|
+
HAML
|
352
|
+
end
|
353
|
+
|
354
|
+
def test_curly_brace
|
355
|
+
assert_equal(<<HTML, render(<<HAML))
|
356
|
+
Foo { Bar
|
357
|
+
HTML
|
358
|
+
== Foo { Bar
|
359
|
+
HAML
|
360
|
+
end
|
361
|
+
|
241
362
|
# HTML escaping tests
|
242
363
|
|
243
364
|
def test_ampersand_equals_should_escape
|
@@ -281,32 +402,57 @@ HAML
|
|
281
402
|
assert_equal("<img alt='' src='foo
.png' />\n",
|
282
403
|
render("%img{:width => nil, :src => \"foo\\n.png\", :alt => String.new}"))
|
283
404
|
end
|
284
|
-
|
285
|
-
def
|
405
|
+
|
406
|
+
def test_string_double_equals_should_be_esaped
|
286
407
|
assert_equal("<p>4&3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => true))
|
287
408
|
assert_equal("<p>4&3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => false))
|
288
409
|
end
|
289
410
|
|
290
|
-
def
|
411
|
+
def test_escaped_inline_string_double_equals
|
291
412
|
assert_equal("<p>4&3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => true))
|
292
413
|
assert_equal("<p>4&3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => false))
|
293
414
|
end
|
294
415
|
|
295
|
-
def
|
416
|
+
def test_unescaped_inline_string_double_equals
|
296
417
|
assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => true))
|
297
418
|
assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => false))
|
298
419
|
end
|
299
420
|
|
300
|
-
def
|
421
|
+
def test_escaped_string_double_equals
|
301
422
|
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => true))
|
302
423
|
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => false))
|
303
424
|
end
|
304
425
|
|
305
|
-
def
|
426
|
+
def test_unescaped_string_double_equals
|
306
427
|
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => true))
|
307
428
|
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => false))
|
308
429
|
end
|
309
430
|
|
431
|
+
def test_string_interpolation_should_be_esaped
|
432
|
+
assert_equal("<p>4&3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => true))
|
433
|
+
assert_equal("<p>4&3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => false))
|
434
|
+
end
|
435
|
+
|
436
|
+
def test_escaped_inline_string_interpolation
|
437
|
+
assert_equal("<p>4&3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => true))
|
438
|
+
assert_equal("<p>4&3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => false))
|
439
|
+
end
|
440
|
+
|
441
|
+
def test_unescaped_inline_string_interpolation
|
442
|
+
assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => true))
|
443
|
+
assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => false))
|
444
|
+
end
|
445
|
+
|
446
|
+
def test_escaped_string_interpolation
|
447
|
+
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => true))
|
448
|
+
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => false))
|
449
|
+
end
|
450
|
+
|
451
|
+
def test_unescaped_string_interpolation
|
452
|
+
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => true))
|
453
|
+
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => false))
|
454
|
+
end
|
455
|
+
|
310
456
|
def test_scripts_should_respect_escape_html_option
|
311
457
|
assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
|
312
458
|
assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
|
@@ -499,7 +645,7 @@ HAML
|
|
499
645
|
end
|
500
646
|
|
501
647
|
def test_unbalanced_brackets
|
502
|
-
render('
|
648
|
+
render('foo #{1 + 5} foo #{6 + 7 bar #{8 + 9}')
|
503
649
|
rescue Haml::SyntaxError => e
|
504
650
|
assert_equal("Unbalanced brackets.", e.message)
|
505
651
|
end
|
@@ -665,4 +811,153 @@ END
|
|
665
811
|
def test_html5_doctype
|
666
812
|
assert_equal %{<!DOCTYPE html>\n}, render('!!!', :format => :html5)
|
667
813
|
end
|
814
|
+
|
815
|
+
# New attributes
|
816
|
+
|
817
|
+
def test_basic_new_attributes
|
818
|
+
assert_equal("<a>bar</a>\n", render("%a() bar"))
|
819
|
+
assert_equal("<a href='foo'>bar</a>\n", render("%a(href='foo') bar"))
|
820
|
+
assert_equal("<a b='c' c='d' d='e'>baz</a>\n", render(%q{%a(b="c" c='d' d="e") baz}))
|
821
|
+
end
|
822
|
+
|
823
|
+
def test_new_attribute_ids
|
824
|
+
assert_equal("<div id='foo_bar'></div>\n", render("#foo(id='bar')"))
|
825
|
+
assert_equal("<div id='foo_bar_baz'></div>\n", render("#foo{:id => 'bar'}(id='baz')"))
|
826
|
+
assert_equal("<div id='foo_baz_bar'></div>\n", render("#foo(id='baz'){:id => 'bar'}"))
|
827
|
+
foo = User.new(42)
|
828
|
+
assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
|
829
|
+
render("#foo(id='baz'){:id => 'bar'}[foo]", :locals => {:foo => foo}))
|
830
|
+
assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
|
831
|
+
render("#foo(id='baz')[foo]{:id => 'bar'}", :locals => {:foo => foo}))
|
832
|
+
assert_equal("<div class='struct_user' id='foo_baz_bar_struct_user_42'></div>\n",
|
833
|
+
render("#foo[foo](id='baz'){:id => 'bar'}", :locals => {:foo => foo}))
|
834
|
+
assert_equal("<div class='struct_user' id='foo_bar_baz_struct_user_42'></div>\n",
|
835
|
+
render("#foo[foo]{:id => 'bar'}(id='baz')", :locals => {:foo => foo}))
|
836
|
+
end
|
837
|
+
|
838
|
+
def test_new_attribute_classes
|
839
|
+
assert_equal("<div class='bar foo'></div>\n", render(".foo(class='bar')"))
|
840
|
+
assert_equal("<div class='bar baz foo'></div>\n", render(".foo{:class => 'bar'}(class='baz')"))
|
841
|
+
assert_equal("<div class='bar baz foo'></div>\n", render(".foo(class='baz'){:class => 'bar'}"))
|
842
|
+
foo = User.new(42)
|
843
|
+
assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
|
844
|
+
render(".foo(class='baz'){:class => 'bar'}[foo]", :locals => {:foo => foo}))
|
845
|
+
assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
|
846
|
+
render(".foo[foo](class='baz'){:class => 'bar'}", :locals => {:foo => foo}))
|
847
|
+
assert_equal("<div class='bar baz foo struct_user' id='struct_user_42'></div>\n",
|
848
|
+
render(".foo[foo]{:class => 'bar'}(class='baz')", :locals => {:foo => foo}))
|
849
|
+
end
|
850
|
+
|
851
|
+
def test_dynamic_new_attributes
|
852
|
+
assert_equal("<a href='12'>bar</a>\n", render("%a(href=foo) bar", :locals => {:foo => 12}))
|
853
|
+
assert_equal("<a b='12' c='13' d='14'>bar</a>\n", render("%a(b=b c='13' d=d) bar", :locals => {:b => 12, :d => 14}))
|
854
|
+
end
|
855
|
+
|
856
|
+
def test_new_attribute_interpolation
|
857
|
+
assert_equal("<a href='12'>bar</a>\n", render('%a(href="1#{1 + 1}") bar'))
|
858
|
+
assert_equal("<a href='2: 2, 3: 3'>bar</a>\n", render(%q{%a(href='2: #{1 + 1}, 3: #{foo}') bar}, :locals => {:foo => 3}))
|
859
|
+
assert_equal(%Q{<a href='1\#{1 + 1}'>bar</a>\n}, render('%a(href="1\#{1 + 1}") bar'))
|
860
|
+
end
|
861
|
+
|
862
|
+
def test_truthy_new_attributes
|
863
|
+
assert_equal("<a href='href'>bar</a>\n", render("%a(href) bar"))
|
864
|
+
assert_equal("<a bar='baz' href>bar</a>\n", render("%a(href bar='baz') bar", :format => :html5))
|
865
|
+
assert_equal("<a href='href'>bar</a>\n", render("%a(href=true) bar"))
|
866
|
+
assert_equal("<a>bar</a>\n", render("%a(href=false) bar"))
|
867
|
+
end
|
868
|
+
|
869
|
+
def test_new_attribute_parsing
|
870
|
+
assert_equal("<a a2='b2'>bar</a>\n", render("%a(a2=b2) bar", :locals => {:b2 => 'b2'}))
|
871
|
+
assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a="#{'foo"bar'}") bar})) #'
|
872
|
+
assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="#{"foo'bar"}") bar})) #'
|
873
|
+
assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a='foo"bar') bar}))
|
874
|
+
assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="foo'bar") bar}))
|
875
|
+
assert_equal("<a a:b='foo'>bar</a>\n", render("%a(a:b='foo') bar"))
|
876
|
+
assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = 'foo' b = 'bar') bar"))
|
877
|
+
assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = foo b = bar) bar", :locals => {:foo => 'foo', :bar => 'bar'}))
|
878
|
+
assert_equal("<a a='foo'>(b='bar')</a>\n", render("%a(a='foo')(b='bar')"))
|
879
|
+
assert_equal("<a a='foo)bar'>baz</a>\n", render("%a(a='foo)bar') baz"))
|
880
|
+
assert_equal("<a a='foo'>baz</a>\n", render("%a( a = 'foo' ) baz"))
|
881
|
+
end
|
882
|
+
|
883
|
+
def test_new_attribute_escaping
|
884
|
+
assert_equal(%Q{<a a='foo " bar'>bar</a>\n}, render(%q{%a(a="foo \" bar") bar}))
|
885
|
+
assert_equal(%Q{<a a='foo \\" bar'>bar</a>\n}, render(%q{%a(a="foo \\\\\" bar") bar}))
|
886
|
+
|
887
|
+
assert_equal(%Q{<a a="foo ' bar">bar</a>\n}, render(%q{%a(a='foo \' bar') bar}))
|
888
|
+
assert_equal(%Q{<a a="foo \\' bar">bar</a>\n}, render(%q{%a(a='foo \\\\\' bar') bar}))
|
889
|
+
|
890
|
+
assert_equal(%Q{<a a='foo \\ bar'>bar</a>\n}, render(%q{%a(a="foo \\\\ bar") bar}))
|
891
|
+
assert_equal(%Q{<a a='foo \#{1 + 1} bar'>bar</a>\n}, render(%q{%a(a="foo \#{1 + 1} bar") bar}))
|
892
|
+
end
|
893
|
+
|
894
|
+
def test_multiline_new_attribute
|
895
|
+
assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(a='b'\n c='d') bar"))
|
896
|
+
assert_equal("<a a='b' b='c' c='d' d='e' e='f' f='j'>bar</a>\n",
|
897
|
+
render("%a(a='b' b='c'\n c='d' d=e\n e='f' f='j') bar", :locals => {:e => 'e'}))
|
898
|
+
end
|
899
|
+
|
900
|
+
def test_new_and_old_attributes
|
901
|
+
assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(a='b'){:c => 'd'} bar"))
|
902
|
+
assert_equal("<a a='b' c='d'>bar</a>\n", render("%a{:c => 'd'}(a='b') bar"))
|
903
|
+
assert_equal("<a a='b' c='d'>bar</a>\n", render("%a(c='d'){:a => 'b'} bar"))
|
904
|
+
assert_equal("<a a='b' c='d'>bar</a>\n", render("%a{:a => 'b'}(c='d') bar"))
|
905
|
+
|
906
|
+
assert_equal("<a a='d'>bar</a>\n", render("%a{:a => 'b'}(a='d') bar"))
|
907
|
+
assert_equal("<a a='b'>bar</a>\n", render("%a(a='d'){:a => 'b'} bar"))
|
908
|
+
|
909
|
+
assert_equal("<a a='b' b='c' c='d' d='e'>bar</a>\n",
|
910
|
+
render("%a{:a => 'b',\n:b => 'c'}(c='d'\nd='e') bar"))
|
911
|
+
end
|
912
|
+
|
913
|
+
# Encodings
|
914
|
+
|
915
|
+
unless Haml::Util.ruby1_8?
|
916
|
+
def test_default_encoding
|
917
|
+
assert_equal(Encoding.find("utf-8"), render(<<HAML.encode("us-ascii")).encoding)
|
918
|
+
HTML
|
919
|
+
%p bar
|
920
|
+
%p foo
|
921
|
+
HAML
|
922
|
+
end
|
923
|
+
|
924
|
+
def test_convert_template_render
|
925
|
+
assert_equal(<<HTML, render(<<HAML.encode("iso-8859-1"), :encoding => "utf-8"))
|
926
|
+
<p>bâr</p>
|
927
|
+
<p>föö</p>
|
928
|
+
HTML
|
929
|
+
%p bâr
|
930
|
+
%p föö
|
931
|
+
HAML
|
932
|
+
end
|
933
|
+
|
934
|
+
def test_convert_template_render_proc
|
935
|
+
assert_converts_template_properly {|e| e.render_proc.call}
|
936
|
+
end
|
937
|
+
|
938
|
+
def test_convert_template_render
|
939
|
+
assert_converts_template_properly {|e| e.render}
|
940
|
+
end
|
941
|
+
|
942
|
+
def test_convert_template_def_method
|
943
|
+
assert_converts_template_properly do |e|
|
944
|
+
o = Object.new
|
945
|
+
e.def_method(o, :render)
|
946
|
+
o.render
|
947
|
+
end
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
private
|
952
|
+
|
953
|
+
def assert_converts_template_properly
|
954
|
+
engine = Haml::Engine.new(<<HAML.encode("iso-8859-1"), :encoding => "utf-8")
|
955
|
+
%p bâr
|
956
|
+
%p föö
|
957
|
+
HAML
|
958
|
+
assert_equal(<<HTML, yield(engine))
|
959
|
+
<p>bâr</p>
|
960
|
+
<p>föö</p>
|
961
|
+
HTML
|
962
|
+
end
|
668
963
|
end
|