2007-04-01 10:03:12 +00:00
|
|
|
require File.dirname(__FILE__) + '/../sass'
|
|
|
|
require 'sass/tree/node'
|
2010-01-06 22:42:13 -08:00
|
|
|
require 'sass/scss/css_parser'
|
2007-04-01 10:03:12 +00:00
|
|
|
require 'strscan'
|
|
|
|
|
|
|
|
module Sass
|
2009-04-11 03:50:24 -07:00
|
|
|
# This class converts CSS documents into Sass templates.
|
|
|
|
# It works by parsing the CSS document into a {Sass::Tree} structure,
|
|
|
|
# and then applying various transformations to the structure
|
|
|
|
# to produce more concise and idiomatic Sass.
|
|
|
|
#
|
|
|
|
# Example usage:
|
|
|
|
#
|
2009-06-19 03:20:48 -07:00
|
|
|
# Sass::CSS.new("p { color: blue }").render #=> "p\n color: blue"
|
2007-04-01 10:03:12 +00:00
|
|
|
class CSS
|
2009-05-13 20:54:25 -07:00
|
|
|
# @param template [String] The CSS code
|
2009-06-19 02:57:01 -07:00
|
|
|
# @option options :old [Boolean] (false)
|
2009-06-19 03:33:03 -07:00
|
|
|
# Whether or not to output old property syntax
|
2009-06-19 02:57:01 -07:00
|
|
|
# (`:color blue` as opposed to `color: blue`).
|
2009-09-23 14:22:24 -07:00
|
|
|
# @option options :filename [String]
|
|
|
|
# The filename of the CSS file being processed.
|
|
|
|
# Used for error reporting
|
2008-04-29 13:14:43 -07:00
|
|
|
def initialize(template, options = {})
|
2007-04-01 10:03:12 +00:00
|
|
|
if template.is_a? IO
|
|
|
|
template = template.read
|
|
|
|
end
|
|
|
|
|
2009-06-19 02:57:01 -07:00
|
|
|
@options = options.dup
|
|
|
|
# Backwards compatibility
|
|
|
|
@options[:old] = true if @options[:alternate] == false
|
2009-12-20 05:57:35 -08:00
|
|
|
@template = template
|
2007-04-01 10:03:12 +00:00
|
|
|
end
|
|
|
|
|
2009-04-11 03:50:24 -07:00
|
|
|
# Converts the CSS template into Sass code.
|
|
|
|
#
|
|
|
|
# @return [String] The resulting Sass code
|
2009-09-23 14:22:24 -07:00
|
|
|
# @raise [Sass::SyntaxError] if there's an error parsing the CSS template
|
2007-04-01 10:03:12 +00:00
|
|
|
def render
|
2009-12-20 05:57:35 -08:00
|
|
|
Haml::Util.check_encoding(@template) do |msg, line|
|
2009-09-23 15:54:46 -07:00
|
|
|
raise Sass::SyntaxError.new(msg, :line => line)
|
|
|
|
end
|
|
|
|
|
2009-09-23 14:22:24 -07:00
|
|
|
build_tree.to_sass(0, @options).strip + "\n"
|
|
|
|
rescue Sass::SyntaxError => err
|
2009-12-20 15:09:10 -08:00
|
|
|
err.modify_backtrace(:filename => @options[:filename] || '(css)')
|
2009-09-23 14:22:24 -07:00
|
|
|
raise err
|
2007-04-01 10:03:12 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2009-04-11 03:50:24 -07:00
|
|
|
# Parses the CSS template and applies various transformations
|
|
|
|
#
|
|
|
|
# @return [Tree::Node] The root node of the parsed tree
|
2007-04-01 10:03:12 +00:00
|
|
|
def build_tree
|
2010-01-06 22:42:13 -08:00
|
|
|
root = Sass::SCSS::CssParser.new(@template).parse
|
2008-04-29 12:41:53 -07:00
|
|
|
expand_commas root
|
|
|
|
parent_ref_rules root
|
|
|
|
remove_parent_refs root
|
|
|
|
flatten_rules root
|
|
|
|
fold_commas root
|
2007-04-01 22:33:08 +00:00
|
|
|
root
|
|
|
|
end
|
2007-04-01 10:03:12 +00:00
|
|
|
|
2007-12-16 22:22:56 +00:00
|
|
|
# Transform
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo, bar, baz
|
|
|
|
# color: blue
|
2007-12-16 22:22:56 +00:00
|
|
|
#
|
|
|
|
# into
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# color: blue
|
|
|
|
# bar
|
|
|
|
# color: blue
|
|
|
|
# baz
|
|
|
|
# color: blue
|
2007-12-16 22:22:56 +00:00
|
|
|
#
|
2009-05-13 20:54:25 -07:00
|
|
|
# @param root [Tree::Node] The parent node
|
2007-12-16 22:22:56 +00:00
|
|
|
def expand_commas(root)
|
|
|
|
root.children.map! do |child|
|
2010-01-05 20:49:05 -08:00
|
|
|
next child unless Tree::RuleNode === child && child.rule.first.include?(',')
|
|
|
|
child.rule.first.split(',').map do |rule|
|
|
|
|
node = Tree::RuleNode.new([rule.strip])
|
2007-12-16 22:22:56 +00:00
|
|
|
node.children = child.children
|
|
|
|
node
|
|
|
|
end
|
|
|
|
end
|
|
|
|
root.children.flatten!
|
|
|
|
end
|
|
|
|
|
2008-04-29 12:41:53 -07:00
|
|
|
# Make rules use parent refs so that
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# color: green
|
|
|
|
# foo.bar
|
|
|
|
# color: blue
|
2008-04-29 12:41:53 -07:00
|
|
|
#
|
|
|
|
# becomes
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# color: green
|
|
|
|
# &.bar
|
|
|
|
# color: blue
|
2008-04-29 12:41:53 -07:00
|
|
|
#
|
|
|
|
# This has the side effect of nesting rules,
|
|
|
|
# so that
|
2007-12-16 21:29:25 +00:00
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# color: green
|
|
|
|
# foo bar
|
|
|
|
# color: red
|
|
|
|
# foo baz
|
|
|
|
# color: blue
|
2007-12-16 21:29:25 +00:00
|
|
|
#
|
|
|
|
# becomes
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# color: green
|
|
|
|
# & bar
|
|
|
|
# color: red
|
|
|
|
# & baz
|
|
|
|
# color: blue
|
2008-04-07 23:09:17 -07:00
|
|
|
#
|
2009-05-13 20:54:25 -07:00
|
|
|
# @param root [Tree::Node] The parent node
|
2008-04-29 12:41:53 -07:00
|
|
|
def parent_ref_rules(root)
|
2009-05-10 14:08:06 -07:00
|
|
|
current_rule = nil
|
2007-12-16 22:22:56 +00:00
|
|
|
root.children.select { |c| Tree::RuleNode === c }.each do |child|
|
2007-12-16 22:05:54 +00:00
|
|
|
root.children.delete child
|
2010-01-05 20:49:05 -08:00
|
|
|
first, rest = child.rule.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
|
2009-05-10 14:08:06 -07:00
|
|
|
|
2010-01-05 20:49:05 -08:00
|
|
|
if current_rule.nil? || current_rule.rule.first != first
|
|
|
|
current_rule = Tree::RuleNode.new([first])
|
2009-05-10 14:08:06 -07:00
|
|
|
root << current_rule
|
|
|
|
end
|
|
|
|
|
2007-12-16 21:29:25 +00:00
|
|
|
if rest
|
2010-01-05 20:49:05 -08:00
|
|
|
child.rule = ["&" + rest]
|
2009-05-10 14:08:06 -07:00
|
|
|
current_rule << child
|
2007-04-03 08:12:39 +00:00
|
|
|
else
|
2009-05-10 14:08:06 -07:00
|
|
|
current_rule.children += child.children
|
2007-04-03 08:12:39 +00:00
|
|
|
end
|
2007-04-03 05:14:03 +00:00
|
|
|
end
|
|
|
|
|
2009-05-10 14:08:06 -07:00
|
|
|
root.children.each { |v| parent_ref_rules(v) }
|
2007-12-16 21:29:25 +00:00
|
|
|
end
|
|
|
|
|
2008-04-29 12:41:53 -07:00
|
|
|
# Remove useless parent refs so that
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# & bar
|
|
|
|
# color: blue
|
2008-04-29 12:41:53 -07:00
|
|
|
#
|
|
|
|
# becomes
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# bar
|
|
|
|
# color: blue
|
2008-04-29 12:41:53 -07:00
|
|
|
#
|
2009-05-13 20:54:25 -07:00
|
|
|
# @param root [Tree::Node] The parent node
|
2008-04-29 12:41:53 -07:00
|
|
|
def remove_parent_refs(root)
|
|
|
|
root.children.each do |child|
|
|
|
|
if child.is_a?(Tree::RuleNode)
|
2010-01-05 20:49:05 -08:00
|
|
|
child.rule.first.gsub! /^& +/, ''
|
2008-04-29 12:41:53 -07:00
|
|
|
remove_parent_refs child
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2007-12-16 21:29:25 +00:00
|
|
|
# Flatten rules so that
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# bar
|
2009-06-19 03:20:48 -07:00
|
|
|
# color: red
|
2007-12-16 21:29:25 +00:00
|
|
|
#
|
|
|
|
# becomes
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo bar
|
2009-06-19 03:20:48 -07:00
|
|
|
# color: red
|
2008-04-07 23:09:17 -07:00
|
|
|
#
|
2008-04-29 15:09:07 -07:00
|
|
|
# and
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# &.bar
|
|
|
|
# color: blue
|
2008-04-29 15:09:07 -07:00
|
|
|
#
|
|
|
|
# becomes
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo.bar
|
|
|
|
# color: blue
|
2008-04-29 15:09:07 -07:00
|
|
|
#
|
2009-05-13 20:54:25 -07:00
|
|
|
# @param root [Tree::Node] The parent node
|
2007-12-16 21:29:25 +00:00
|
|
|
def flatten_rules(root)
|
2007-12-16 22:20:47 +00:00
|
|
|
root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) }
|
2007-12-16 22:05:54 +00:00
|
|
|
end
|
|
|
|
|
2009-04-11 03:50:24 -07:00
|
|
|
# Flattens a single rule
|
|
|
|
#
|
2009-05-13 20:54:25 -07:00
|
|
|
# @param rule [Tree::RuleNode] The candidate for flattening
|
2009-04-11 03:50:24 -07:00
|
|
|
# @see #flatten_rules
|
2007-12-16 22:05:54 +00:00
|
|
|
def flatten_rule(rule)
|
|
|
|
while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
|
|
|
|
child = rule.children.first
|
2008-04-29 15:09:07 -07:00
|
|
|
|
2010-01-05 20:49:05 -08:00
|
|
|
if child.rule.first[0] == ?&
|
|
|
|
rule.rule = [child.rule.first.gsub(/^&/, rule.rule.first)]
|
2008-04-29 15:09:07 -07:00
|
|
|
else
|
2010-01-05 20:49:05 -08:00
|
|
|
rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
|
2008-04-29 15:09:07 -07:00
|
|
|
end
|
|
|
|
|
2007-12-16 22:05:54 +00:00
|
|
|
rule.children = child.children
|
2007-04-03 05:14:03 +00:00
|
|
|
end
|
2007-12-16 21:29:25 +00:00
|
|
|
|
2007-12-16 22:05:54 +00:00
|
|
|
flatten_rules(rule)
|
2007-04-03 05:14:03 +00:00
|
|
|
end
|
2007-12-16 22:50:59 +00:00
|
|
|
|
|
|
|
# Transform
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# bar
|
|
|
|
# color: blue
|
|
|
|
# baz
|
|
|
|
# color: blue
|
2007-12-16 22:50:59 +00:00
|
|
|
#
|
|
|
|
# into
|
|
|
|
#
|
2009-04-11 03:50:24 -07:00
|
|
|
# foo
|
|
|
|
# bar, baz
|
|
|
|
# color: blue
|
2007-12-16 22:50:59 +00:00
|
|
|
#
|
2009-05-13 20:54:25 -07:00
|
|
|
# @param rule [Tree::RuleNode] The candidate for flattening
|
2007-12-16 22:50:59 +00:00
|
|
|
def fold_commas(root)
|
|
|
|
prev_rule = nil
|
|
|
|
root.children.map! do |child|
|
2009-02-19 20:05:11 -08:00
|
|
|
next child unless child.is_a?(Tree::RuleNode)
|
2007-12-16 22:50:59 +00:00
|
|
|
|
|
|
|
if prev_rule && prev_rule.children == child.children
|
2010-01-05 20:49:05 -08:00
|
|
|
prev_rule.rule.first << ", #{child.rule.first}"
|
2007-12-16 22:50:59 +00:00
|
|
|
next nil
|
|
|
|
end
|
|
|
|
|
|
|
|
fold_commas(child)
|
|
|
|
prev_rule = child
|
|
|
|
child
|
|
|
|
end
|
|
|
|
root.children.compact!
|
|
|
|
end
|
2007-04-01 10:03:12 +00:00
|
|
|
end
|
|
|
|
end
|