/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

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