| 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