class Sass::CSS
namely converting CSS documents to Sass templates.
This class contains the functionality used in the css2sass
utility,
def assert_match(re)
def assert_match(re) if !@template.scan(re) line = @template.string[0..@template.pos].count "\n" # Display basic regexps as plain old strings expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect raise Exception.new("Invalid CSS on line #{line}: expected #{expected}") end whitespace end
def attributes(rule)
def attributes(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::AttrNode.new(name, value, nil) end assert_match /\}/ end
def build_tree
def build_tree root = Tree::Node.new(nil) whitespace directives root rules root expand_commas root parent_ref_rules root remove_parent_refs root flatten_rules root fold_commas root root end
def directives(root)
def directives(root) while @template.scan(/@/) name = @template.scan /[^\s;]+/ whitespace value = @template.scan /[^;]+/ assert_match /;/ whitespace if name == "import" && value =~ /^(url\()?"?([^\s\(\)\"]+)\.css"?\)?$/ value = $2 end root << Tree::ValueNode.new("@#{name} #{value};", nil) end end
def expand_commas(root)
Yes, this expands the amount of code,
color: blue
baz
color: blue
bar
color: blue
foo
into
color: blue
foo, bar, baz
Transform
def expand_commas(root) root.children.map! do |child| next child unless Tree::RuleNode === child && child.rule.include?(',') child.rule.split(',').map do |rule| node = Tree::RuleNode.new(rule, nil) node.children = child.children node end end root.children.flatten! end
def flatten_rule(rule)
def flatten_rule(rule) while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode) child = rule.children.first if child.rule[0] == ?& rule.rule = child.rule.gsub /^&/, rule.rule else rule.rule = "#{rule.rule} #{child.rule}" end rule.children = child.children end flatten_rules(rule) end
def flatten_rules(root)
color: blue
foo.bar
becomes
color: blue
&.bar
foo
and
color: red
foo bar baz
becomes
color: red
baz
bar
foo
Flatten rules so that
def flatten_rules(root) root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) } end
def fold_commas(root)
color: blue
bar, baz
foo
into
color: blue
baz
color: blue
bar
foo
Transform
def fold_commas(root) prev_rule = nil root.children.map! do |child| next child unless Tree::RuleNode === child if prev_rule && prev_rule.children == child.children prev_rule.rule << ", #{child.rule}" next nil end fold_commas(child) prev_rule = child child end root.children.compact! end
def initialize(template, options = {})
Creates a new instance of Sass::CSS that will compile the given document
def initialize(template, options = {}) if template.is_a? IO template = template.read end @options = options @template = StringScanner.new(template) end
def parent_ref_rules(root)
color: blue
& baz
color: red
& bar
color: green
foo
becomes
color: blue
foo baz
color: red
foo bar
color: green
foo
so that
This has the side effect of nesting rules,
color: blue
&.bar
color: green
foo
becomes
color: blue
foo.bar
color: green
foo
Make rules use parent refs so that
def parent_ref_rules(root) rules = OrderedHash.new root.children.select { |c| Tree::RuleNode === c }.each do |child| root.children.delete child first, rest = child.rule.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first rules[first] ||= Tree::RuleNode.new(first, nil) if rest child.rule = "&" + rest rules[first] << child else rules[first].children += child.children end end rules.values.each { |v| parent_ref_rules(v) } root.children += rules.values end
def remove_parent_refs(root)
color: blue
bar
foo
becomes
color: blue
& bar
foo
Remove useless parent refs so that
def remove_parent_refs(root) root.children.each do |child| if child.is_a?(Tree::RuleNode) child.rule.gsub! /^& /, '' remove_parent_refs child end end end
def render
Processes the document and returns the result as a string
def render begin build_tree.to_sass(@options).lstrip rescue Exception => err line = @template.string[0...@template.pos].split("\n").size err.backtrace.unshift "(css):#{line}" raise err end end
def rules(root)
def rules(root) rules = [] while @template.scan(/[^\{\s]+/) rules << @template[0] whitespace if @template.scan(/\{/) result = Tree::RuleNode.new(rules.join(' '), nil) root << result rules = [] whitespace attributes(result) end end end
def 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