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:
parent
f2388e7441
commit
917bd658c5
6 changed files with 337 additions and 10 deletions
34
README.rdoc
34
README.rdoc
|
@ -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.
|
||||
|
||||
|
|
76
lib/sass.rb
76
lib/sass.rb
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
88
test/sass/results/mixins.css
Normal file
88
test/sass/results/mixins.css
Normal 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;
|
||||
}
|
71
test/sass/templates/mixins.sass
Normal file
71
test/sass/templates/mixins.sass
Normal 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
|
Loading…
Add table
Reference in a new issue