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

Mixin patch for Sass.

This commit is contained in:
Garry Hill 2008-04-09 10:21:49 +01:00 committed by Nathan Weizenbaum
parent f2388e7441
commit 917bd658c5
6 changed files with 337 additions and 10 deletions

View file

@ -228,6 +228,40 @@ becomes:
background-color: #79d645;
width: 15em; }
Taking the idea of constants a bit further are mixins.
These let you group whole swathes of CSS attributes into a single
directive and then include those anywhere you want:
-blue-border
:border
:color blue
:width 2px
:style dotted
.comment
+blue-border
:padding 2px
:margin 10px 0
.reply
+blue-border
becomes:
.comment {
border-color: blue;
border-width: 2px;
border-style: dotted;
padding: 2px;
margin: 10px 0;
}
.reply {
border-color: blue;
border-width: 2px;
border-style: dotted;
}
A comprehensive list of features is in
the documentation for the Sass module.

View file

@ -595,6 +595,82 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
# background-image: url(/images/pbj.png);
# color: red; }
#
# == Mixins
#
# Mixins enable you to define groups of CSS attributes and
# then include them inline in any number of selectors
# throughout the document.
#
# === Defining a Mixin
#
# To define a mixin you use a slightly modified form of selector syntax.
# For example the 'large-text' mixin is defined as follows:
#
# -large-text
# :font
# :family Arial
# :size 20px
# :weight bold
# :color #ff0000
#
# Anything you can put into a standard selector,
# you can put into a mixin definition. e.g.
#
# -clearfix
# display: inline-block
# &:after
# content: "."
# display: block
# height: 0
# clear: both
# visibility: hidden
# * html &
# height: 1px
#
#
# === Mixing it in
#
# Inlining a defined mixin is simple,
# just prepend a '+' symbol to the name of a mixin defined earlier in the document.
# So to inline the 'large-text' defined earlier,
# we include the statment '+large-text' in our selector definition thus:
#
# .page-title
# +large-text
# :padding 4px
# :margin
# :top 10px
#
#
# This will produce the following CSS output:
#
# .page-title {
# font-family: Arial;
# font-size: 20px;
# font-weight: bold;
# color: #ff0000;
# padding: 4px;
# margin-top: 10px;
# }
#
# Any number of mixins may be defined and there is no limit on
# the number that can be included in a particular selector.
#
# Mixin definitions can also include references to other mixins defined earlier in the file.
# E.g.
#
# -highlighted-background
# background:
# color: #fc0
# -header-text
# font:
# size: 20px
#
# -compound
# +highlighted-background
# +header-text
#
#
# == Output Style
#
# Although the default CSS style that Sass outputs is very nice,

View file

@ -43,6 +43,12 @@ module Sass
# Designates a non-parsed rule.
ESCAPE_CHAR = ?\\
# Designates block as mixin definition rather than CSS rules to output
MIXIN_DEFINITION_CHAR = ?-
# Includes named mixin declared using MIXIN_DEFINITION_CHAR
MIXIN_INCLUDE_CHAR = ?+
# The regex that matches and extracts data from
# attributes of the form <tt>:name attr</tt>.
ATTRIBUTE = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/
@ -74,6 +80,7 @@ module Sass
@template = template.split(/\n?\r|\r?\n/)
@lines = []
@constants = {"important" => "!important"}
@mixins = {}
end
# Processes the template and returns the result as a string.
@ -179,10 +186,16 @@ module Sass
if node == :constant
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath constants.", @line)
elsif node.is_a? Array
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.", @line)
# arrays can either be full of import statements
# or attributes from mixin includes
# in either case they shouldn't have children.
# Need to peek into the array in order to give meaningful errors
directive_type = (node.first.is_a?(Tree::DirectiveNode) ? "import" : "mixin")
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath #{directive_type} directives.", @line)
end
end
index = @line if node == :mixin
return node, index
end
@ -215,14 +228,7 @@ module Sass
while has_children
child, index = build_tree(index)
if child == :constant
raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
elsif child.is_a? Array
raise SyntaxError.new("Import directives may only be used at the root of a document.", @line)
elsif child.is_a? Tree::Node
child.line = @line
node << child
end
validate_and_append_child(node, child)
has_children = has_children?(index, tabs)
end
@ -230,6 +236,26 @@ module Sass
return node, index
end
def validate_and_append_child(parent, child)
case child
when :constant
raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
when :mixin
raise SyntaxError.new("Mixins may only be defined at the root of a document.", @line)
when Array
child.each do |c|
if c.is_a?(Tree::DirectiveNode)
raise SyntaxError.new("Import directives may only be used at the root of a document.", @line)
end
c.line = @line
parent << c
end
when Tree::Node
child.line = @line
parent << child
end
end
def has_children?(index, tabs)
next_line = ['//', 0]
while !next_line.nil? && next_line[0] == '//' && next_line[1] = 0
@ -255,6 +281,10 @@ module Sass
parse_directive(line)
when ESCAPE_CHAR
Tree::RuleNode.new(line[1..-1], @options[:style])
when MIXIN_DEFINITION_CHAR
parse_mixin_definition(line)
when MIXIN_INCLUDE_CHAR
parse_mixin_include(line)
else
if line =~ ATTRIBUTE_ALTERNATE_MATCHER
parse_attribute(line, ATTRIBUTE_ALTERNATE)
@ -324,6 +354,27 @@ module Sass
end
end
def parse_mixin_definition(line)
mixin_name = line[1..-1]
@mixins[mixin_name] = []
index = @line
line, tabs = @lines[index]
while !line.nil? && tabs > 0
child, index = build_tree(index)
validate_and_append_child(@mixins[mixin_name], child)
line, tabs = @lines[index]
end
:mixin
end
def parse_mixin_include(line)
mixin_name = line[1..-1]
unless @mixins.has_key?(mixin_name)
raise SyntaxError.new("Undefined mixin '#{mixin_name}'", @line)
end
@mixins[mixin_name]
end
def import(files)
nodes = []
@ -337,7 +388,7 @@ module Sass
end
if filename =~ /\.css$/
nodes << Tree::ValueNode.new("@import url(#{filename});", @options[:style])
nodes << Tree::DirectiveNode.new("@import url(#{filename})", @options[:style])
else
File.open(filename) do |file|
new_options = @options.dup

View file

@ -48,6 +48,9 @@ class SassEngineTest < Test::Unit::TestCase
"foo\n @import templates/basic" => "Import directives may only be used at the root of a document.",
"!foo = bar baz !" => "Unterminated constant.",
"!foo = !(foo)" => "Invalid constant.",
"-foo\n :color red\n.bar\n +bang" => "Undefined mixin 'bang'",
".bar\n -foo\n :color red\n" => "Mixins may only be defined at the root of a document.",
"-foo\n :color red\n.bar\n +foo\n :color red" => "Illegal nesting: Nothing may be nested beneath mixin directives.",
}
def test_basic_render
@ -232,6 +235,10 @@ END
assert_equal("foo {\n a: b; }\n", render("!foo ||= b\nfoo\n a = !foo"))
end
def test_mixins
renders_correctly "mixins", { :style => :expanded }
end
private
def render(sass, options = {})

View file

@ -0,0 +1,88 @@
#main {
width: 15em;
color: #0000ff;
}
#main p {
border-top-width: 2px;
border-top-color: #ffcc00;
border-left-width: 1px;
border-left-color: #000;
border-style: dotted;
border-width: 2px;
}
#main .cool {
width: 100px;
}
#left {
border-top-width: 2px;
border-top-color: #ffcc00;
border-left-width: 1px;
border-left-color: #000;
font-size: 2em;
font-weight: bold;
float: left;
}
#right {
border-top-width: 2px;
border-top-color: #ffcc00;
border-left-width: 1px;
border-left-color: #000;
color: #f00;
font-size: 20px;
float: right;
}
.bordered {
border-top-width: 2px;
border-top-color: #ffcc00;
border-left-width: 1px;
border-left-color: #000;
}
.complex {
color: #f00;
font-size: 20px;
text-decoration: none;
}
.complex:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
* html .complex {
height: 1px;
color: #f00;
font-size: 20px;
}
.more-complex {
color: #f00;
font-size: 20px;
text-decoration: none;
display: inline;
}
.more-complex:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
* html .more-complex {
height: 1px;
color: #f00;
font-size: 20px;
}
.more-complex a:hover {
text-decoration: underline;
color: #f00;
font-size: 20px;
border-top-width: 2px;
border-top-color: #ffcc00;
border-left-width: 1px;
border-left-color: #000;
}

View file

@ -0,0 +1,71 @@
!yellow = #fc0
-bordered
:border
:top
:width 2px
:color = !yellow
:left
:width 1px
:color #000
-header-font
:color #f00
:font
:size 20px
-compound
+header-font
+bordered
-complex
+header-font
text:
decoration: none
&:after
content: "."
display: block
height: 0
clear: both
visibility: hidden
* html &
height: 1px
+header-font
-deep
a:hover
:text-decoration underline
+compound
#main
:width 15em
:color #0000ff
p
+bordered
:border
:style dotted
:width 2px
.cool
:width 100px
#left
+bordered
:font
:size 2em
:weight bold
:float left
#right
+bordered
+header-font
:float right
.bordered
+bordered
.complex
+complex
.more-complex
+complex
+deep
display: inline