# class Sass::CSS

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/SCSS.

Example usage:

    Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n  color: blue"
    Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n  color: blue; }"

### Public Class Methods

new(template, options = {}) click to toggle source

@param template [String] The CSS stylesheet.

    This stylesheet can be encoded using any encoding
    that can be converted to Unicode.
    If the stylesheet contains an `@charset` declaration,
    that overrides the Ruby encoding
    (see {file:SASS_REFERENCE.md#Encodings the encoding documentation})

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

@option options :indent [String] (“ ”)

    The string to use for indenting each line. Defaults to two spaces.

    # File lib/sass/css.rb, line 29
    def initialize(template, options = {})
      if template.is_a? IO
        template = template.read
      end

      @options = options.merge(:_convert => true)
      # Backwards compatibility
      @options[:old] = true if @options[:alternate] == false
      @template = template
      @checked_encoding = false
    end

### Public Instance Methods

render(fmt = :sass) click to toggle source

Converts the CSS template into Sass or SCSS code.

@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

    # File lib/sass/css.rb, line 46
    def render(fmt = :sass)
      check_encoding!
      build_tree.send("to_#{fmt}", @options).strip + "\n"
    rescue Sass::SyntaxError => err
      err.modify_backtrace(:filename => @options[:filename] || '(css)')
      raise err
    end

source_encoding() click to toggle source

Returns the original encoding of the document.

@return [Encoding, nil] @raise [Encoding::UndefinedConversionError] if the source encoding

    cannot be converted to UTF-8

@raise [ArgumentError] if the document uses an unknown encoding with `@charset`

    # File lib/sass/css.rb, line 60
    def source_encoding
      check_encoding!
      @original_encoding
    end

### Private Instance Methods

bubble_subject(root) click to toggle source

    # File lib/sass/css.rb, line 291
    def bubble_subject(root)
      root.children.each do |child|
        bubble_subject(child) if child.is_a?(Tree::RuleNode) || child.is_a?(Tree::DirectiveNode)
        next unless child.is_a?(Tree::RuleNode) && !child.children.empty?
        next unless child.children.all? do |c|
          next unless c.is_a?(Tree::RuleNode)
          first_simple_sel(c).is_a?(Sass::Selector::Parent) && first_sseq(c).subject?
        end
        first_sseq(child).subject = true
        child.children.each {|c| first_sseq(c).subject = false}
      end
    end

build_tree() click to toggle source

Parses the CSS template and applies various transformations

@return [Tree::Node] The root node of the parsed tree

    # File lib/sass/css.rb, line 76
    def build_tree
      root = Sass::SCSS::CssParser.new(@template, @options[:filename], nil).parse
      parse_selectors(root)
      expand_commas(root)
      nest_seqs(root)
      parent_ref_rules(root)
      flatten_rules(root)
      bubble_subject(root)
      fold_commas(root)
      dump_selectors(root)
      root
    end

check_encoding!() click to toggle source

    # File lib/sass/css.rb, line 67
    def check_encoding!
      return if @checked_encoding
      @checked_encoding = true
      @template, @original_encoding = Sass::Util.check_sass_encoding(@template)
    end

dump_selectors(root) click to toggle source

Dump all the parsed {Sass::Tree::RuleNode} selectors to strings.

@param root [Tree::Node] The parent node

    # File lib/sass/css.rb, line 342
    def dump_selectors(root)
      root.children.each do |child|
        next dump_selectors(child) if child.is_a?(Tree::DirectiveNode)
        next unless child.is_a?(Tree::RuleNode)
        child.rule = [child.parsed_rules.to_s]
        dump_selectors(child)
      end
    end

expand_commas(root) click to toggle source

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 117
    def expand_commas(root)
      root.children.map! do |child|
        # child.parsed_rules.members.size > 1 iff the rule contains a comma
        unless child.is_a?(Tree::RuleNode) && child.parsed_rules.members.size > 1
          expand_commas(child) if child.is_a?(Tree::DirectiveNode)
          next child
        end
        child.parsed_rules.members.map do |seq|
          node = Tree::RuleNode.new([])
          node.parsed_rules = make_cseq(seq)
          node.children = child.children
          node
        end
      end
      root.children.flatten!
    end

first_seq(rule) click to toggle source

Return the first {Sass::Selector::Sequence} in a {Sass::Tree::RuleNode}.

@param rule [Sass::Tree::RuleNode] @return [Sass::Selector::Sequence]

    # File lib/sass/css.rb, line 383
    def first_seq(rule)
      rule.parsed_rules.members.first
    end

first_simple_sel(rule) click to toggle source

Return the first {Sass::Selector::Simple} in a {Sass::Tree::RuleNode}, unless the rule begins with a combinator.

@param rule [Sass::Tree::RuleNode] @return [Sass::Selector::Simple?]

    # File lib/sass/css.rb, line 401
    def first_simple_sel(rule)
      sseq = first_sseq(rule)
      return unless sseq.is_a?(Sass::Selector::SimpleSequence)
      sseq.members.first
    end

first_sseq(rule) click to toggle source

Return the first {Sass::Selector::SimpleSequence} in a {Sass::Tree::RuleNode}.

@param rule [Sass::Tree::RuleNode] @return [Sass::Selector::SimpleSequence, String]

    # File lib/sass/css.rb, line 392
    def first_sseq(rule)
      first_seq(rule).members.first
    end

flatten_rule(rule) click to toggle source

Flattens a single rule.

@param rule [Tree::RuleNode] The candidate for flattening @see flatten_rules

    # File lib/sass/css.rb, line 275
    def flatten_rule(rule)
      while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
        child = rule.children.first

        if first_simple_sel(child).is_a?(Sass::Selector::Parent)
          rule.parsed_rules = child.parsed_rules.resolve_parent_refs(rule.parsed_rules)
        else
          rule.parsed_rules = make_seq(*(first_seq(rule).members + first_seq(child).members))
        end

        rule.children = child.children
      end

      flatten_rules(rule)
    end

flatten_rules(root) click to toggle source

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 260
    def flatten_rules(root)
      root.children.each do |child|
        case child
        when Tree::RuleNode
          flatten_rule(child)
        when Tree::DirectiveNode
          flatten_rules(child)
        end
      end
    end

fold_commas(root) click to toggle source

Transform

    foo
      bar
        color: blue
      baz
        color: blue

into

    foo
      bar, baz
        color: blue

@param root [Tree::Node] The parent node

    # File lib/sass/css.rb, line 319
    def fold_commas(root)
      prev_rule = nil
      root.children.map! do |child|
        unless child.is_a?(Tree::RuleNode)
          fold_commas(child) if child.is_a?(Tree::DirectiveNode)
          next child
        end

        if prev_rule && prev_rule.children.map {|c| c.to_sass} == child.children.map {|c| c.to_sass}
          prev_rule.parsed_rules.members << first_seq(child)
          next nil
        end

        fold_commas(child)
        prev_rule = child
        child
      end
      root.children.compact!
    end

make_cseq(*seqs) click to toggle source

Create a {Sass::Selector::CommaSequence}.

@param seqs [Array<Sass::Selector::Sequence>] @return [Sass::Selector::CommaSequence]

    # File lib/sass/css.rb, line 355
    def make_cseq(*seqs)
      Sass::Selector::CommaSequence.new(seqs)
    end

make_seq(*sseqs) click to toggle source

Create a {Sass::Selector::CommaSequence} containing only a single {Sass::Selector::Sequence}.

@param sseqs [Array<Sass::Selector::Sequence, String>] @return [Sass::Selector::CommaSequence]

    # File lib/sass/css.rb, line 364
    def make_seq(*sseqs)
      make_cseq(Sass::Selector::Sequence.new(sseqs))
    end

make_sseq(subject, *sseqs) click to toggle source

Create a {Sass::Selector::CommaSequence} containing only a single {Sass::Selector::Sequence} which in turn contains only a single {Sass::Selector::SimpleSequence}.

@param subject [Boolean] Whether this is a subject selector @param sseqs [Array<Sass::Selector::Sequence, String>] @return [Sass::Selector::CommaSequence]

    # File lib/sass/css.rb, line 375
    def make_sseq(subject, *sseqs)
      make_seq(Sass::Selector::SimpleSequence.new(sseqs, subject))
    end

nest_seqs(root) click to toggle source

Make rules use nesting 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 153
    def nest_seqs(root)
      current_rule = nil
      root.children.map! do |child|
        unless child.is_a?(Tree::RuleNode)
          nest_seqs(child) if child.is_a?(Tree::DirectiveNode)
          next child
        end

        seq = first_seq(child)
        seq.members.reject! {|sseq| sseq == "\n"}
        first, rest = seq.members.first, seq.members[1..-1]

        if current_rule.nil? || first_sseq(current_rule) != first
          current_rule = Tree::RuleNode.new([])
          current_rule.parsed_rules = make_seq(first)
        end

        if rest.empty?
          current_rule.children += child.children
        else
          child.parsed_rules = make_seq(*rest)
          current_rule << child
        end

        current_rule
      end
      root.children.compact!
      root.children.uniq!

      root.children.each {|v| nest_seqs(v)}
    end

parent_ref_rules(root) click to toggle source

Make rules use parent refs so that

    foo
      color: green
    foo.bar
      color: blue

becomes

    foo
      color: green
      &.bar
        color: blue

@param root [Tree::Node] The parent node

    # File lib/sass/css.rb, line 200
    def parent_ref_rules(root)
      current_rule = nil
      root.children.map! do |child|
        unless child.is_a?(Tree::RuleNode)
          parent_ref_rules(child) if child.is_a?(Tree::DirectiveNode)
          next child
        end

        sseq = first_sseq(child)
        next child unless sseq.is_a?(Sass::Selector::SimpleSequence)

        firsts, rest = [sseq.members.first], sseq.members[1..-1]
        firsts.push rest.shift if firsts.first.is_a?(Sass::Selector::Parent)

        last_simple_subject = rest.empty? && sseq.subject?
        if current_rule.nil? || first_sseq(current_rule).members != firsts ||
            !!first_sseq(current_rule).subject? != !!last_simple_subject
          current_rule = Tree::RuleNode.new([])
          current_rule.parsed_rules = make_sseq(last_simple_subject, *firsts)
        end

        if rest.empty?
          current_rule.children += child.children
        else
          rest.unshift Sass::Selector::Parent.new
          child.parsed_rules = make_sseq(sseq.subject?, *rest)
          current_rule << child
        end

        current_rule
      end
      root.children.compact!
      root.children.uniq!

      root.children.each {|v| parent_ref_rules(v)}
    end

parse_selectors(root) click to toggle source

Parse all the selectors in the document and assign them to {Sass::Tree::RuleNode#parsed_rules}.

@param root [Tree::Node] The parent node

    # File lib/sass/css.rb, line 93
    def parse_selectors(root)
      root.children.each do |child|
        next parse_selectors(child) if child.is_a?(Tree::DirectiveNode)
        next unless child.is_a?(Tree::RuleNode)
        parser = Sass::SCSS::CssParser.new(child.rule.first, child.filename, nil, child.line)
        child.parsed_rules = parser.parse_selector
      end
    end