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

http://github.com/scalate/scalate · Ruby · 294 lines · 131 code · 23 blank · 140 comment · 21 complexity · 97e9754b726f04f7424a9d6e69975225 MD5 · raw file

  1. require File.dirname(__FILE__) + '/../sass'
  2. require 'sass/tree/node'
  3. require 'sass/scss/css_parser'
  4. require 'strscan'
  5. module Sass
  6. # This class converts CSS documents into Sass or SCSS templates.
  7. # It works by parsing the CSS document into a {Sass::Tree} structure,
  8. # and then applying various transformations to the structure
  9. # to produce more concise and idiomatic Sass/SCSS.
  10. #
  11. # Example usage:
  12. #
  13. # Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
  14. # Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
  15. class CSS
  16. # @param template [String] The CSS stylesheet.
  17. # This stylesheet can be encoded using any encoding
  18. # that can be converted to Unicode.
  19. # If the stylesheet contains an `@charset` declaration,
  20. # that overrides the Ruby encoding
  21. # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
  22. # @option options :old [Boolean] (false)
  23. # Whether or not to output old property syntax
  24. # (`:color blue` as opposed to `color: blue`).
  25. # This is only meaningful when generating Sass code,
  26. # rather than SCSS.
  27. def initialize(template, options = {})
  28. if template.is_a? IO
  29. template = template.read
  30. end
  31. @options = options.dup
  32. # Backwards compatibility
  33. @options[:old] = true if @options[:alternate] == false
  34. @template = template
  35. end
  36. # Converts the CSS template into Sass or SCSS code.
  37. #
  38. # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
  39. # @return [String] The resulting Sass or SCSS code
  40. # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
  41. def render(fmt = :sass)
  42. check_encoding!
  43. build_tree.send("to_#{fmt}", @options).strip + "\n"
  44. rescue Sass::SyntaxError => err
  45. err.modify_backtrace(:filename => @options[:filename] || '(css)')
  46. raise err
  47. end
  48. # Returns the original encoding of the document,
  49. # or `nil` under Ruby 1.8.
  50. #
  51. # @return [Encoding, nil]
  52. # @raise [Encoding::UndefinedConversionError] if the source encoding
  53. # cannot be converted to UTF-8
  54. # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
  55. def source_encoding
  56. check_encoding!
  57. @original_encoding
  58. end
  59. private
  60. def check_encoding!
  61. return if @checked_encoding
  62. @checked_encoding = true
  63. @template, @original_encoding = Haml::Util.check_sass_encoding(@template) do |msg, line|
  64. raise Sass::SyntaxError.new(msg, :line => line)
  65. end
  66. end
  67. # Parses the CSS template and applies various transformations
  68. #
  69. # @return [Tree::Node] The root node of the parsed tree
  70. def build_tree
  71. root = Sass::SCSS::CssParser.new(@template).parse
  72. expand_commas root
  73. parent_ref_rules root
  74. remove_parent_refs root
  75. flatten_rules root
  76. fold_commas root
  77. root
  78. end
  79. # Transform
  80. #
  81. # foo, bar, baz
  82. # color: blue
  83. #
  84. # into
  85. #
  86. # foo
  87. # color: blue
  88. # bar
  89. # color: blue
  90. # baz
  91. # color: blue
  92. #
  93. # @param root [Tree::Node] The parent node
  94. def expand_commas(root)
  95. root.children.map! do |child|
  96. unless child.is_a?(Tree::RuleNode) && child.rule.first.include?(',')
  97. expand_commas(child) if child.is_a?(Tree::DirectiveNode)
  98. next child
  99. end
  100. child.rule.first.split(',').map do |rule|
  101. node = Tree::RuleNode.new([rule.strip])
  102. node.children = child.children
  103. node
  104. end
  105. end
  106. root.children.flatten!
  107. end
  108. # Make rules use parent refs so that
  109. #
  110. # foo
  111. # color: green
  112. # foo.bar
  113. # color: blue
  114. #
  115. # becomes
  116. #
  117. # foo
  118. # color: green
  119. # &.bar
  120. # color: blue
  121. #
  122. # This has the side effect of nesting rules,
  123. # so that
  124. #
  125. # foo
  126. # color: green
  127. # foo bar
  128. # color: red
  129. # foo baz
  130. # color: blue
  131. #
  132. # becomes
  133. #
  134. # foo
  135. # color: green
  136. # & bar
  137. # color: red
  138. # & baz
  139. # color: blue
  140. #
  141. # @param root [Tree::Node] The parent node
  142. def parent_ref_rules(root)
  143. current_rule = nil
  144. root.children.map! do |child|
  145. unless child.is_a?(Tree::RuleNode)
  146. parent_ref_rules(child) if child.is_a?(Tree::DirectiveNode)
  147. next child
  148. end
  149. first, rest = child.rule.first.scan(/\A(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?\Z/m).first
  150. if current_rule.nil? || current_rule.rule.first != first
  151. current_rule = Tree::RuleNode.new([first])
  152. end
  153. if rest
  154. child.rule = ["&" + rest]
  155. current_rule << child
  156. else
  157. current_rule.children += child.children
  158. end
  159. current_rule
  160. end
  161. root.children.compact!
  162. root.children.uniq!
  163. root.children.each { |v| parent_ref_rules(v) }
  164. end
  165. # Remove useless parent refs so that
  166. #
  167. # foo
  168. # & bar
  169. # color: blue
  170. #
  171. # becomes
  172. #
  173. # foo
  174. # bar
  175. # color: blue
  176. #
  177. # @param root [Tree::Node] The parent node
  178. def remove_parent_refs(root)
  179. root.children.each do |child|
  180. case child
  181. when Tree::RuleNode
  182. child.rule.first.gsub! /^& +/, ''
  183. remove_parent_refs child
  184. when Tree::DirectiveNode
  185. remove_parent_refs child
  186. end
  187. end
  188. end
  189. # Flatten rules so that
  190. #
  191. # foo
  192. # bar
  193. # color: red
  194. #
  195. # becomes
  196. #
  197. # foo bar
  198. # color: red
  199. #
  200. # and
  201. #
  202. # foo
  203. # &.bar
  204. # color: blue
  205. #
  206. # becomes
  207. #
  208. # foo.bar
  209. # color: blue
  210. #
  211. # @param root [Tree::Node] The parent node
  212. def flatten_rules(root)
  213. root.children.each do |child|
  214. case child
  215. when Tree::RuleNode
  216. flatten_rule(child)
  217. when Tree::DirectiveNode
  218. flatten_rules(child)
  219. end
  220. end
  221. end
  222. # Flattens a single rule
  223. #
  224. # @param rule [Tree::RuleNode] The candidate for flattening
  225. # @see #flatten_rules
  226. def flatten_rule(rule)
  227. while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
  228. child = rule.children.first
  229. if child.rule.first[0] == ?&
  230. rule.rule = [child.rule.first.gsub(/^&/, rule.rule.first)]
  231. else
  232. rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
  233. end
  234. rule.children = child.children
  235. end
  236. flatten_rules(rule)
  237. end
  238. # Transform
  239. #
  240. # foo
  241. # bar
  242. # color: blue
  243. # baz
  244. # color: blue
  245. #
  246. # into
  247. #
  248. # foo
  249. # bar, baz
  250. # color: blue
  251. #
  252. # @param rule [Tree::RuleNode] The candidate for flattening
  253. def fold_commas(root)
  254. prev_rule = nil
  255. root.children.map! do |child|
  256. unless child.is_a?(Tree::RuleNode)
  257. fold_commas(child) if child.is_a?(Tree::DirectiveNode)
  258. next child
  259. end
  260. if prev_rule && prev_rule.children == child.children
  261. prev_rule.rule.first << ", #{child.rule.first}"
  262. next nil
  263. end
  264. fold_commas(child)
  265. prev_rule = child
  266. child
  267. end
  268. root.children.compact!
  269. end
  270. end
  271. end