Class Sass::CSS
In: lib/sass/css.rb
Parent: Object
Haml::Util Engine Color SyntaxError UnitConversionError StandardError Node Operation Literal UnaryOperation Funcall Variable Number String Bool EvaluationContext Node\n[lib/sass/css.rb\nlib/sass/tree/node.rb] DebugNode IfNode CommentNode ForNode MixinNode VariableNode ImportNode WhileNode MixinDefNode Repl CSS Environment Lexer Parser PropNode\n[lib/sass/css.rb\nlib/sass/tree/prop_node.rb] DirectiveNode\n[lib/sass/css.rb\nlib/sass/tree/directive_node.rb] RuleNode\n[lib/sass/css.rb\nlib/sass/tree/rule_node.rb] Rack lib/sass/repl.rb lib/sass/css.rb lib/sass/environment.rb lib/sass/error.rb lib/sass/engine.rb lib/sass/script/lexer.rb lib/sass/script/color.rb lib/sass/script/string.rb lib/sass/script/unary_operation.rb lib/sass/script/variable.rb lib/sass/script/funcall.rb lib/sass/script/operation.rb lib/sass/script/bool.rb lib/sass/script/parser.rb lib/sass/script/literal.rb lib/sass/script/node.rb lib/sass/script/number.rb lib/sass/script/functions.rb Functions Script Files lib/sass/tree/while_node.rb lib/sass/tree/if_node.rb lib/sass/tree/mixin_def_node.rb lib/sass/tree/debug_node.rb lib/sass/tree/for_node.rb lib/sass/tree/import_node.rb lib/sass/tree/prop_node.rb lib/sass/tree/node.rb lib/sass/tree/comment_node.rb lib/sass/tree/mixin_node.rb lib/sass/tree/directive_node.rb lib/sass/tree/rule_node.rb lib/sass/tree/variable_node.rb Tree lib/sass/plugin/rack.rb Plugin Sass dot/m_54_0.png

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"

Methods

Public Class methods

@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`).

[Source]

    # 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

Public Instance methods

Converts the CSS template into Sass code.

@return [String] The resulting Sass code

[Source]

    # 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

Private Instance methods

Moves the scanner over a regular expression, raising an exception if it doesn‘t match.

@param re [Regexp] The regular expression to assert

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

Parses a single CSS rule.

@return [Tree::Node] The parsed rule

[Source]

     # 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

Parses a set of CSS rules.

@param root [Tree::Node] The parent node of the rules

[Source]

     # 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

[Source]

     # 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

[Validate]