1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00

[Sass] [SCSS] Support interpolation in SCSS selectors.

This commit is contained in:
Nathan Weizenbaum 2010-01-05 21:58:30 -08:00
parent d7bbab7a24
commit 49f1fef36b
7 changed files with 166 additions and 57 deletions

View file

@ -134,6 +134,18 @@ module Haml
end
end
# Destructively strips whitespace from the beginning and end
# of the first and last elements, respectively,
# in the array (if those elements are strings).
#
# @param arr [Array]
# @return [Array] `arr`
def strip_string_array(arr)
arr.first.lstrip! if arr.first.is_a?(String)
arr.last.rstrip! if arr.last.is_a?(String)
arr
end
# Returns information about the caller of the previous method.
#
# @param entry [String] An entry in the `#caller` list, or a similarly formatted string

View file

@ -195,15 +195,16 @@ module Sass
end
def ruleset
rules = str do
return unless selector
rules = []
return unless v = selector
rules.concat v
while tok(/,/)
ss; expr!(:selector)
end
while tok(/,/)
rules << ',' << str {ss}
rules.concat expr!(:selector)
end
block(node(Sass::Tree::RuleNode.new([rules.strip])))
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)))
end
def block(node)
@ -271,9 +272,14 @@ module Sass
def selector
# The combinator here allows the "> E" hack
return unless combinator || simple_selector_sequence
simple_selector_sequence while combinator
true
return unless (comb = combinator) || (seq = simple_selector_sequence)
res = [comb] + (seq || [])
while v = combinator
res << v
res.concat(simple_selector_sequence || [])
end
res
end
def combinator
@ -281,47 +287,47 @@ module Sass
end
def simple_selector_sequence
unless element_name || tok(HASH) || class_expr ||
attrib || negation || pseudo || tok(/&/)
# This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
return expr
end
# This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
return expr unless e = element_name || tok(HASH) || class_expr ||
attrib || negation || pseudo || tok(/&/) || interpolation
res = [e]
# The tok(/\*/) allows the "E*" hack
nil while tok(HASH) || class_expr || attrib ||
negation || pseudo || tok(/\*/)
true
end
def class_expr
return unless tok(/\./)
tok! IDENT
end
def element_name
res = tok(IDENT) || tok(/\*/)
if tok(/\|/)
@expected = "element name or *"
res = tok(IDENT) || tok!(/\*/)
while v = element_name || tok(HASH) || class_expr ||
attrib || negation || pseudo || tok(/\*/) || interpolation
res << v
end
res
end
def class_expr
return unless tok(/\./)
'.' + tok!(IDENT)
end
def element_name
return unless name = tok(IDENT) || tok(/\*/) || tok?(/\|/)
if tok(/\|/)
@expected = "element name or *"
name << "|" << (tok(IDENT) || tok!(/\*/))
end
name
end
def attrib
return unless tok(/\[/)
ss
attrib_name!; ss
if tok(/=/) ||
res = ['[', str{ss}, str{attrib_name!}, str{ss}]
if m = tok(/=/) ||
tok(INCLUDES) ||
tok(DASHMATCH) ||
tok(PREFIXMATCH) ||
tok(SUFFIXMATCH) ||
tok(SUBSTRINGMATCH)
ss
@expected = "identifier or string"
tok(IDENT) || tok!(STRING); ss
res << m << str{ss} << (tok(IDENT) || expr!(:interp_string)) << str{ss}
end
tok!(/\]/)
res << tok!(/\]/)
end
def attrib_name!
@ -341,34 +347,34 @@ module Sass
end
def pseudo
return unless tok(/::?/)
return unless s = tok(/::?/)
@expected = "pseudoclass or pseudoelement"
functional_pseudo || tok!(IDENT)
[s, functional_pseudo || tok!(IDENT)]
end
def functional_pseudo
return unless tok FUNCTION
ss
expr! :pseudo_expr
tok!(/\)/)
return unless fn = tok(FUNCTION)
[fn, str{ss}, expr!(:pseudo_expr), tok!(/\)/)]
end
def pseudo_expr
return unless tok(PLUS) || tok(/-/) || tok(NUMBER) ||
tok(STRING) || tok(IDENT)
ss
ss while tok(PLUS) || tok(/-/) || tok(NUMBER) ||
tok(STRING) || tok(IDENT)
true
return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
interp_string || tok(IDENT) || interpolation
res = [e, str{ss}]
while e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
interp_string || tok(IDENT) || interpolation
res << e << str{ss}
end
res
end
def negation
return unless tok(NOT)
ss
res = [":not(", str{ss}]
@expected = "selector"
element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo)
tok!(/\)/)
res << (element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo))
res << tok!(/\)/)
end
def declaration
@ -425,9 +431,10 @@ MESSAGE
end
def expr
return unless term
nil while operator && term
true
[str do
return unless term
nil while operator && term
end]
end
def term
@ -460,6 +467,25 @@ MESSAGE
ss
end
def interpolation
return unless tok(/#\{/)
sass_script(:parse_interpolated)
end
def interp_string
_interp_string(:double) || _interp_string(:single)
end
def _interp_string(type)
return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
res = [start]
mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
# @scanner[2].empty? means we've started an interpolated section
res << expr!(:interpolation) << tok(mid_re) while @scanner[2].empty?
res
end
def str
@strs.push ""
yield

View file

@ -273,7 +273,7 @@ module Sass
text.map do |r|
next r if r.is_a?(String)
r.perform(environment).to_s
end.join
end.join.strip
end
# @see Haml::Shared.balance

View file

@ -50,8 +50,10 @@ module Sass::Tree
# @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
# `:old` if it uses `:a b`-style syntax
def initialize(name, value, prop_syntax)
@name = name
@value = value
@name = Haml::Util.strip_string_array(
Haml::Util.merge_adjacent_strings(name))
@value = Haml::Util.strip_string_array(
Haml::Util.merge_adjacent_strings(value))
@tabs = 0
@prop_syntax = prop_syntax
super()

View file

@ -77,7 +77,8 @@ module Sass::Tree
# @param rule [Array<String, Sass::Script::Node>]
# The CSS rule. See \{#rule}
def initialize(rule)
@rule = rule
@rule = Haml::Util.strip_string_array(
Haml::Util.merge_adjacent_strings(rule))
@tabs = 0
super()
end

View file

@ -66,6 +66,15 @@ class UtilTest < Test::Unit::TestCase
merge_adjacent_strings(["foo ", "bar ", "baz", :bang, "biz", " bop", 12]))
end
def test_strip_string_array
assert_equal(["foo ", " bar ", " baz"],
strip_string_array([" foo ", " bar ", " baz "]))
assert_equal([:foo, " bar ", " baz"],
strip_string_array([:foo, " bar ", " baz "]))
assert_equal(["foo ", " bar ", :baz],
strip_string_array([" foo ", " bar ", :baz]))
end
def test_silence_warnings
old_stderr, $stderr = $stderr, StringIO.new
warn "Out"

View file

@ -520,6 +520,65 @@ CSS
SCSS
end
## Interpolation
def test_basic_selector_interpolation
assert_equal <<CSS, render(<<SCSS)
foo 3 baz {
a: b; }
CSS
foo \#{1 + 2} baz {a: b}
SCSS
assert_equal <<CSS, render(<<SCSS)
foo.bar baz {
a: b; }
CSS
foo\#{".bar"} baz {a: b}
SCSS
assert_equal <<CSS, render(<<SCSS)
foo.bar baz {
a: b; }
CSS
\#{"foo"}.bar baz {a: b}
SCSS
end
def test_selector_only_interpolation
assert_equal <<CSS, render(<<SCSS)
foo bar {
a: b; }
CSS
\#{"foo" + " bar"} {a: b}
SCSS
end
def test_selector_interpolation_before_element_name
assert_equal <<CSS, render(<<SCSS)
foo barbaz {
a: b; }
CSS
\#{"foo" + " bar"}baz {a: b}
SCSS
end
def test_selector_interpolation_in_string
assert_equal <<CSS, render(<<SCSS)
foo[val="bar foo bar baz"] {
a: b; }
CSS
foo[val="bar \#{"foo" + " bar"} baz"] {a: b}
SCSS
end
def test_selector_interpolation_in_pseudoclass
assert_equal <<CSS, render(<<SCSS)
foo:nth-child(5n) {
a: b; }
CSS
foo:nth-child(\#{5 + "n"}) {a: b}
SCSS
end
## Errors
def test_mixin_defs_only_at_toplevel