Class | Sass::CSS |
In: |
lib/sass/css.rb
|
Parent: | Object |
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:
Sass::CSS.new("p { color: blue }").render #=> "p\n color: blue"
@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`).
# File lib/sass/css.rb, line 67 67: def initialize(template, options = {}) 68: if template.is_a? IO 69: template = template.read 70: end 71: 72: @options = options.dup 73: # Backwards compatibility 74: @options[:old] = true if @options[:alternate] == false 75: @template = StringScanner.new(template) 76: end
Converts the CSS template into Sass code.
@return [String] The resulting Sass code
# File lib/sass/css.rb, line 81 81: def render 82: begin 83: build_tree.to_sass(0, @options).strip + "\n" 84: rescue Exception => err 85: line = @template.string[0...@template.pos].split("\n").size 86: 87: err.backtrace.unshift "(css):#{line}" 88: raise err 89: end 90: end
Moves the scanner over a regular expression, raising an exception if it doesn‘t match.
@param re [Regexp] The regular expression to assert
# File lib/sass/css.rb, line 197 197: def assert_match(re) 198: if @template.scan(re) 199: whitespace 200: return 201: end 202: 203: line = @template.string[0..@template.pos].count "\n" 204: pos = @template.pos 205: 206: after = @template.string[pos - 15...pos] 207: after = "..." + after if pos >= 15 208: 209: # Display basic regexps as plain old strings 210: expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect 211: 212: was = @template.rest[0...15] 213: was += "..." if @template.rest.size >= 15 214: raise Exception.new("Invalid CSS on line \#{line + 1} after \#{after.inspect}:\n expected \#{expected}, was \#{was.inspect}\n") 215: end
Parses the CSS template and applies various transformations
@return [Tree::Node] The root node of the parsed tree
# File lib/sass/css.rb, line 97 97: def build_tree 98: root = Tree::Node.new 99: whitespace 100: rules root 101: expand_commas root 102: parent_ref_rules root 103: remove_parent_refs root 104: flatten_rules root 105: fold_commas root 106: root 107: end
Transform
foo, bar, baz color: blue
into
foo color: blue bar color: blue baz color: blue
@param root [Tree::Node] The parent node
# File lib/sass/css.rb, line 236 236: def expand_commas(root) 237: root.children.map! do |child| 238: next child unless Tree::RuleNode === child && child.rules.first.include?(',') 239: child.rules.first.split(',').map do |rule| 240: node = Tree::RuleNode.new(rule.strip) 241: node.children = child.children 242: node 243: end 244: end 245: root.children.flatten! 246: end
Flattens a single rule
@param rule [Tree::RuleNode] The candidate for flattening @see flatten_rules
# File lib/sass/css.rb, line 357 357: def flatten_rule(rule) 358: while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode) 359: child = rule.children.first 360: 361: if child.rules.first[0] == ?& 362: rule.rules = [child.rules.first.gsub(/^&/, rule.rules.first)] 363: else 364: rule.rules = ["#{rule.rules.first} #{child.rules.first}"] 365: end 366: 367: rule.children = child.children 368: end 369: 370: flatten_rules(rule) 371: end
Flatten rules so that
foo bar color: red
becomes
foo bar color: red
and
foo &.bar color: blue
becomes
foo.bar color: blue
@param root [Tree::Node] The parent node
# File lib/sass/css.rb, line 349 349: def flatten_rules(root) 350: root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) } 351: end
Transform
foo bar color: blue baz color: blue
into
foo bar, baz color: blue
@param rule [Tree::RuleNode] The candidate for flattening
# File lib/sass/css.rb, line 388 388: def fold_commas(root) 389: prev_rule = nil 390: root.children.map! do |child| 391: next child unless child.is_a?(Tree::RuleNode) 392: 393: if prev_rule && prev_rule.children == child.children 394: prev_rule.rules.first << ", #{child.rules.first}" 395: next nil 396: end 397: 398: fold_commas(child) 399: prev_rule = child 400: child 401: end 402: root.children.compact! 403: end
Make rules use parent refs so that
foo color: green foo.bar color: blue
becomes
foo color: green &.bar color: blue
This has the side effect of nesting rules, so that
foo color: green foo bar color: red foo baz color: blue
becomes
foo color: green & bar color: red & baz color: blue
@param root [Tree::Node] The parent node
# File lib/sass/css.rb, line 282 282: def parent_ref_rules(root) 283: current_rule = nil 284: root.children.select { |c| Tree::RuleNode === c }.each do |child| 285: root.children.delete child 286: first, rest = child.rules.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first 287: 288: if current_rule.nil? || current_rule.rules.first != first 289: current_rule = Tree::RuleNode.new(first) 290: root << current_rule 291: end 292: 293: if rest 294: child.rules = ["&" + rest] 295: current_rule << child 296: else 297: current_rule.children += child.children 298: end 299: end 300: 301: root.children.each { |v| parent_ref_rules(v) } 302: end
Parses a set of CSS properties within a rule.
@param rule [Tree::RuleNode] The parent node of the properties
# File lib/sass/css.rb, line 159 159: def properties(rule) 160: while @template.scan(/[^:\}\s]+/) 161: name = @template[0] 162: whitespace 163: 164: assert_match /:/ 165: 166: value = '' 167: while @template.scan(/[^;\s\}]+/) 168: value << @template[0] << whitespace 169: end 170: 171: assert_match /(;|(?=\}))/ 172: rule << Tree::PropNode.new(name, value, nil) 173: end 174: 175: assert_match /\}/ 176: end
Remove useless parent refs so that
foo & bar color: blue
becomes
foo bar color: blue
@param root [Tree::Node] The parent node
# File lib/sass/css.rb, line 317 317: def remove_parent_refs(root) 318: root.children.each do |child| 319: if child.is_a?(Tree::RuleNode) 320: child.rules.first.gsub! /^& +/, '' 321: remove_parent_refs child 322: end 323: end 324: end
@return [Tree::Node] The parsed rule
# File lib/sass/css.rb, line 122 122: def rule 123: rule = "" 124: loop do 125: token = @template.scan(/(?:[^\{\};\/\s]|\/[^*])+/) 126: if token.nil? 127: return if rule.empty? 128: break 129: end 130: rule << token 131: break unless @template.match?(/\s|\/\*/) 132: whitespace 133: rule << " " 134: end 135: 136: rule.strip! 137: directive = rule[0] == ?@ 138: 139: if directive 140: node = Tree::DirectiveNode.new(rule) 141: return node if @template.scan(/;/) 142: 143: assert_match /\{/ 144: whitespace 145: 146: rules(node) 147: return node 148: end 149: 150: assert_match /\{/ 151: node = Tree::RuleNode.new(rule) 152: properties(node) 153: return node 154: end
@param root [Tree::Node] The parent node of the rules
# File lib/sass/css.rb, line 112 112: def rules(root) 113: while r = rule 114: root << r 115: whitespace 116: end 117: end
Moves the scanner over a section of whitespace or comments.
@return [String] The ignored whitespace
# File lib/sass/css.rb, line 181 181: def whitespace 182: space = @template.scan(/\s*/) || '' 183: 184: # If we've hit a comment, 185: # go past it and look for more whitespace 186: if @template.scan(/\/\*/) 187: @template.scan_until(/\*\//) 188: return space + whitespace 189: end 190: return space 191: end