lib/sass/css.rb in haml-2.2.24 vs lib/sass/css.rb in haml-3.0.0.beta.1
- old
+ new
@@ -1,224 +1,69 @@
require File.dirname(__FILE__) + '/../sass'
require 'sass/tree/node'
+require 'sass/scss/css_parser'
require 'strscan'
module Sass
- module Tree
- class Node
- # Converts a node to Sass code that will generate it.
- #
- # @param tabs [Fixnum] The amount of tabulation to use for the Sass code
- # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
- # @return [String] The Sass code corresponding to the node
- def to_sass(tabs = 0, opts = {})
- result = ''
-
- children.each do |child|
- result << "#{' ' * tabs}#{child.to_sass(0, opts)}\n"
- end
-
- result
- end
- end
-
- class RuleNode
- # @see Node#to_sass
- def to_sass(tabs, opts = {})
- name = rules.first
- name = "\\" + name if name[0] == ?:
- str = "\n#{' ' * tabs}#{name}#{children.any? { |c| c.is_a? PropNode } ? "\n" : ''}"
-
- children.each do |child|
- str << "#{child.to_sass(tabs + 1, opts)}"
- end
-
- str
- end
- end
-
- class PropNode
- # @see Node#to_sass
- def to_sass(tabs, opts = {})
- "#{' ' * tabs}#{opts[:old] ? ':' : ''}#{name}#{opts[:old] ? '' : ':'} #{value}\n"
- end
- end
-
- class DirectiveNode
- # @see Node#to_sass
- def to_sass(tabs, opts = {})
- "#{' ' * tabs}#{value}#{children.map {|c| c.to_sass(tabs + 1, opts)}}\n"
- end
- end
- end
-
- # This class converts CSS documents into Sass templates.
+ # This class converts CSS documents into Sass or SCSS 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.
+ # to produce more concise and idiomatic Sass/SCSS.
#
# Example usage:
#
- # Sass::CSS.new("p { color: blue }").render #=> "p\n color: blue"
+ # Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
+ # Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
class CSS
# @param template [String] The CSS code
# @option options :old [Boolean] (false)
# Whether or not to output old property syntax
# (`:color blue` as opposed to `color: blue`).
+ # This is only meaningful when generating Sass code,
+ # rather than SCSS.
def initialize(template, options = {})
if template.is_a? IO
template = template.read
end
@options = options.dup
# Backwards compatibility
@options[:old] = true if @options[:alternate] == false
- @template = StringScanner.new(template)
+ @template = template
end
- # Converts the CSS template into Sass code.
+ # Converts the CSS template into Sass or SCSS code.
#
- # @return [String] The resulting Sass code
- def render
- begin
- build_tree.to_sass(0, @options).strip + "\n"
- rescue Exception => err
- line = @template.string[0...@template.pos].split("\n").size
-
- err.backtrace.unshift "(css):#{line}"
- raise err
+ # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
+ # @return [String] The resulting Sass or SCSS code
+ # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
+ def render(fmt = :sass)
+ Haml::Util.check_encoding(@template) do |msg, line|
+ raise Sass::SyntaxError.new(msg, :line => line)
end
+
+ build_tree.send("to_#{fmt}", @options).strip + "\n"
+ rescue Sass::SyntaxError => err
+ err.modify_backtrace(:filename => @options[:filename] || '(css)')
+ raise err
end
private
# Parses the CSS template and applies various transformations
#
# @return [Tree::Node] The root node of the parsed tree
def build_tree
- root = Tree::Node.new
- whitespace
- rules root
+ root = Sass::SCSS::CssParser.new(@template).parse
expand_commas root
parent_ref_rules root
remove_parent_refs root
flatten_rules root
fold_commas root
root
end
- # Parses a set of CSS rules.
- #
- # @param root [Tree::Node] The parent node of the rules
- def rules(root)
- while r = rule
- root << r
- whitespace
- end
- end
-
- # Parses a single CSS rule.
- #
- # @return [Tree::Node] The parsed rule
- def rule
- rule = ""
- loop do
- token = @template.scan(/(?:[^\{\};\/\s]|\/[^*])+/)
- if token.nil?
- return if rule.empty?
- break
- end
- rule << token
- break unless @template.match?(/\s|\/\*/)
- whitespace
- rule << " "
- end
-
- rule.strip!
- directive = rule[0] == ?@
-
- if directive
- node = Tree::DirectiveNode.new(rule)
- return node if @template.scan(/;/)
-
- assert_match /\{/
- whitespace
-
- rules(node)
- return node
- end
-
- assert_match /\{/
- node = Tree::RuleNode.new(rule)
- properties(node)
- return node
- end
-
- # Parses a set of CSS properties within a rule.
- #
- # @param rule [Tree::RuleNode] The parent node of the properties
- def properties(rule)
- while @template.scan(/[^:\}\s]+/)
- name = @template[0]
- whitespace
-
- assert_match /:/
-
- value = ''
- while @template.scan(/[^;\s\}]+/)
- value << @template[0] << whitespace
- end
-
- assert_match /(;|(?=\}))/
- rule << Tree::PropNode.new(name, value, nil)
- end
-
- assert_match /\}/
- end
-
- # Moves the scanner over a section of whitespace or comments.
- #
- # @return [String] The ignored whitespace
- def whitespace
- space = @template.scan(/\s*/) || ''
-
- # If we've hit a comment,
- # go past it and look for more whitespace
- if @template.scan(/\/\*/)
- @template.scan_until(/\*\//)
- return space + whitespace
- end
- return space
- end
-
- # Moves the scanner over a regular expression,
- # raising an exception if it doesn't match.
- #
- # @param re [Regexp] The regular expression to assert
- def assert_match(re)
- if @template.scan(re)
- whitespace
- return
- end
-
- line = @template.string[0..@template.pos].count "\n"
- pos = @template.pos
-
- after = @template.string[pos - 15...pos]
- after = "..." + after if pos >= 15
-
- # Display basic regexps as plain old strings
- expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect
-
- was = @template.rest[0...15]
- was += "..." if @template.rest.size >= 15
- raise Exception.new(<<MESSAGE)
-Invalid CSS on line #{line + 1} after #{after.inspect}:
- expected #{expected}, was #{was.inspect}
-MESSAGE
- end
-
# Transform
#
# foo, bar, baz
# color: blue
#
@@ -232,13 +77,13 @@
# color: blue
#
# @param root [Tree::Node] The parent node
def expand_commas(root)
root.children.map! do |child|
- next child unless Tree::RuleNode === child && child.rules.first.include?(',')
- child.rules.first.split(',').map do |rule|
- node = Tree::RuleNode.new(rule.strip)
+ next child unless Tree::RuleNode === child && child.rule.first.include?(',')
+ child.rule.first.split(',').map do |rule|
+ node = Tree::RuleNode.new([rule.strip])
node.children = child.children
node
end
end
root.children.flatten!
@@ -278,26 +123,30 @@
# color: blue
#
# @param root [Tree::Node] The parent node
def parent_ref_rules(root)
current_rule = nil
- root.children.select { |c| Tree::RuleNode === c }.each do |child|
- root.children.delete child
- first, rest = child.rules.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
+ root.children.map! do |child|
+ next child unless child.is_a?(Tree::RuleNode)
- if current_rule.nil? || current_rule.rules.first != first
- current_rule = Tree::RuleNode.new(first)
- root << current_rule
+ first, rest = child.rule.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
+
+ if current_rule.nil? || current_rule.rule.first != first
+ current_rule = Tree::RuleNode.new([first])
end
if rest
- child.rules = ["&" + rest]
+ child.rule = ["&" + rest]
current_rule << child
else
current_rule.children += child.children
end
+
+ current_rule
end
+ root.children.compact!
+ root.children.uniq!
root.children.each { |v| parent_ref_rules(v) }
end
# Remove useless parent refs so that
@@ -314,11 +163,11 @@
#
# @param root [Tree::Node] The parent node
def remove_parent_refs(root)
root.children.each do |child|
if child.is_a?(Tree::RuleNode)
- child.rules.first.gsub! /^& +/, ''
+ child.rule.first.gsub! /^& +/, ''
remove_parent_refs child
end
end
end
@@ -355,14 +204,14 @@
# @see #flatten_rules
def flatten_rule(rule)
while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
child = rule.children.first
- if child.rules.first[0] == ?&
- rule.rules = [child.rules.first.gsub(/^&/, rule.rules.first)]
+ if child.rule.first[0] == ?&
+ rule.rule = [child.rule.first.gsub(/^&/, rule.rule.first)]
else
- rule.rules = ["#{rule.rules.first} #{child.rules.first}"]
+ rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
end
rule.children = child.children
end
@@ -388,10 +237,10 @@
prev_rule = nil
root.children.map! do |child|
next child unless child.is_a?(Tree::RuleNode)
if prev_rule && prev_rule.children == child.children
- prev_rule.rules.first << ", #{child.rules.first}"
+ prev_rule.rule.first << ", #{child.rule.first}"
next nil
end
fold_commas(child)
prev_rule = child