PageRenderTime 87ms CodeModel.GetById 19ms app.highlight 64ms RepoModel.GetById 1ms app.codeStats 0ms

/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/tree/rule_node.rb

http://github.com/scalate/scalate
Ruby | 261 lines | 146 code | 33 blank | 82 comment | 27 complexity | 25896d0d288f5e5a10d745513c60e248 MD5 | raw file
  1require 'pathname'
  2require 'uri'
  3
  4module Sass::Tree
  5  # A static node reprenting a CSS rule.
  6  #
  7  # @see Sass::Tree
  8  class RuleNode < Node
  9    # The character used to include the parent selector
 10    PARENT = '&'
 11
 12    # The CSS selector for this rule,
 13    # interspersed with {Sass::Script::Node}s
 14    # representing `#{}`-interpolation.
 15    # Any adjacent strings will be merged together.
 16    #
 17    # @return [Array<String, Sass::Script::Node>]
 18    attr_accessor :rule
 19
 20    # The CSS selector for this rule,
 21    # without any unresolved interpolation
 22    # but with parent references still intact.
 23    # It's only set once {Tree::Node#perform} has been called.
 24    #
 25    # @return [Selector::CommaSequence]
 26    attr_accessor :parsed_rules
 27
 28    # The CSS selector for this rule,
 29    # without any unresolved interpolation or parent references.
 30    # It's only set once {Tree::Node#cssize} has been called.
 31    #
 32    # @return [Selector::CommaSequence]
 33    attr_accessor :resolved_rules
 34
 35    # How deep this rule is indented
 36    # relative to a base-level rule.
 37    # This is only greater than 0 in the case that:
 38    #
 39    # * This node is in a CSS tree
 40    # * The style is :nested
 41    # * This is a child rule of another rule
 42    # * The parent rule has properties, and thus will be rendered
 43    #
 44    # @return [Fixnum]
 45    attr_accessor :tabs
 46
 47    # Whether or not this rule is the last rule in a nested group.
 48    # This is only set in a CSS tree.
 49    #
 50    # @return [Boolean]
 51    attr_accessor :group_end
 52
 53    # @param rule [Array<String, Sass::Script::Node>]
 54    #   The CSS rule. See \{#rule}
 55    def initialize(rule)
 56      #p rule
 57      merged = Haml::Util.merge_adjacent_strings(rule)
 58      #p merged
 59      @rule = Haml::Util.strip_string_array(merged)
 60      #p @rule
 61      @tabs = 0
 62      super()
 63    end
 64
 65    # Compares the contents of two rules.
 66    #
 67    # @param other [Object] The object to compare with
 68    # @return [Boolean] Whether or not this node and the other object
 69    #   are the same
 70    def ==(other)
 71      self.class == other.class && rule == other.rule && super
 72    end
 73
 74    # Adds another {RuleNode}'s rules to this one's.
 75    #
 76    # @param node [RuleNode] The other node
 77    def add_rules(node)
 78      @rule = Haml::Util.strip_string_array(
 79        Haml::Util.merge_adjacent_strings(@rule + ["\n"] + node.rule))
 80    end
 81
 82    # @return [Boolean] Whether or not this rule is continued on the next line
 83    def continued?
 84      last = @rule.last
 85      last.is_a?(String) && last[-1] == ?,
 86    end
 87
 88    # @see Node#to_sass
 89    def to_sass(tabs, opts = {})
 90      name = selector_to_sass(rule, opts)
 91      name = "\\" + name if name[0] == ?:
 92      name.gsub(/^/, '  ' * tabs) + children_to_src(tabs, opts, :sass)
 93    end
 94
 95    # @see Node#to_scss
 96    def to_scss(tabs, opts = {})
 97      name = selector_to_scss(rule, tabs, opts)
 98      res = name + children_to_src(tabs, opts, :scss)
 99
100      if children.last.is_a?(CommentNode) && children.last.silent
101        res.slice!(-3..-1)
102        res << "\n" << ('  ' * tabs) << "}\n"
103      end
104
105      res
106    end
107
108    # Extends this Rule's selector with the given `extends`.
109    #
110    # @see Node#do_extend
111    def do_extend(extends)
112      node = dup
113      node.resolved_rules = resolved_rules.do_extend(extends)
114      node
115    end
116
117    protected
118
119    # Computes the CSS for the rule.
120    #
121    # @param tabs [Fixnum] The level of indentation for the CSS
122    # @return [String] The resulting CSS
123    def _to_s(tabs)
124      tabs = tabs + self.tabs
125
126      rule_separator = style == :compressed ? ',' : ', '
127      line_separator =
128        case style
129          when :nested, :expanded; "\n"
130          when :compressed; ""
131          else; " "
132        end
133      rule_indent = '  ' * (tabs - 1)
134      per_rule_indent, total_indent = [:nested, :expanded].include?(style) ? [rule_indent, ''] : ['', rule_indent]
135
136      total_rule = total_indent + resolved_rules.members.
137        map {|seq| seq.to_a.join.gsub(/([^,])\n/m, style == :compressed ? '\1 ' : "\\1\n")}.
138        join(rule_separator).split("\n").map do |line|
139        per_rule_indent + line.strip
140      end.join(line_separator)
141
142      to_return = ''
143      old_spaces = '  ' * (tabs - 1)
144      spaces = '  ' * tabs
145      if style != :compressed
146        if @options[:debug_info]
147          to_return << debug_info_rule.to_s(tabs) << "\n"
148        elsif @options[:line_comments]
149          to_return << "#{old_spaces}/* line #{line}"
150
151          if filename
152            relative_filename = if @options[:css_filename]
153              begin
154                Pathname.new(filename).relative_path_from(
155                  Pathname.new(File.dirname(@options[:css_filename]))).to_s
156              rescue ArgumentError
157                nil
158              end
159            end
160            relative_filename ||= filename
161            to_return << ", #{relative_filename}"
162          end
163
164          to_return << " */\n"
165        end
166      end
167
168      if style == :compact
169        properties = children.map { |a| a.to_s(1) }.join(' ')
170        to_return << "#{total_rule} { #{properties} }#{"\n" if group_end}"
171      elsif style == :compressed
172        properties = children.map { |a| a.to_s(1) }.join(';')
173        to_return << "#{total_rule}{#{properties}}"
174      else
175        properties = children.map { |a| a.to_s(tabs + 1) }.join("\n")
176        end_props = (style == :expanded ? "\n" + old_spaces : ' ')
177        to_return << "#{total_rule} {\n#{properties}#{end_props}}#{"\n" if group_end}"
178      end
179
180      to_return
181    end
182
183    # Runs SassScript interpolation in the selector,
184    # and then parses the result into a {Sass::Selector::CommaSequence}.
185    #
186    # @param environment [Sass::Environment] The lexical environment containing
187    #   variable and mixin values
188    def perform!(environment)
189      @parsed_rules = Sass::SCSS::StaticParser.new(run_interp(@rule, environment), self.line).
190        parse_selector(self.filename)
191      super
192    end
193
194    # Converts nested rules into a flat list of rules.
195    #
196    # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
197    #   The extensions defined for this tree
198    # @param parent [RuleNode, nil] The parent node of this node,
199    #   or nil if the parent isn't a {RuleNode}
200    def _cssize(extends, parent)
201      node = super
202      rules = node.children.select {|c| c.is_a?(RuleNode)}
203      props = node.children.reject {|c| c.is_a?(RuleNode) || c.invisible?}
204
205      unless props.empty?
206        node.children = props
207        rules.each {|r| r.tabs += 1} if style == :nested
208        rules.unshift(node)
209      end
210
211      rules.last.group_end = true unless parent || rules.empty?
212
213      rules
214    end
215
216    # Resolves parent references and nested selectors,
217    # and updates the indentation based on the parent's indentation.
218    #
219    # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
220    #   The extensions defined for this tree
221    # @param parent [RuleNode, nil] The parent node of this node,
222    #   or nil if the parent isn't a {RuleNode}
223    # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
224    def cssize!(extends, parent)
225      self.resolved_rules = @parsed_rules.resolve_parent_refs(parent && parent.resolved_rules)
226      super
227    end
228
229    # A hash that will be associated with this rule in the CSS document
230    # if the {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} is enabled.
231    # This data is used by e.g. [the FireSass Firebug extension](https://addons.mozilla.org/en-US/firefox/addon/103988).
232    #
233    # @return [{#to_s => #to_s}]
234    def debug_info
235      {:filename => filename && ("file://" + URI.escape(File.expand_path(filename))),
236       :line => self.line}
237    end
238
239    private
240
241    def debug_info_rule
242      node = DirectiveNode.new("@media -sass-debug-info")
243      debug_info.map {|k, v| [k.to_s, v.to_s]}.sort.each do |k, v|
244        rule = RuleNode.new([""])
245        rule.resolved_rules = Sass::Selector::CommaSequence.new(
246          [Sass::Selector::Sequence.new(
247              [Sass::Selector::SimpleSequence.new(
248                  [Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)])
249              ])
250          ])
251        prop = PropNode.new([""], "", :new)
252        prop.resolved_name = "font-family"
253        prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
254        rule << prop
255        node << rule
256      end
257      node.options = @options.merge(:debug_info => false, :line_comments => false, :style => :compressed)
258      node
259    end
260  end
261end