oreorenasass 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +11 -0
  3. data/CONTRIBUTING +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +221 -0
  6. data/Rakefile +370 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/sass +13 -0
  10. data/bin/sass-convert +12 -0
  11. data/bin/scss +13 -0
  12. data/extra/update_watch.rb +13 -0
  13. data/init.rb +18 -0
  14. data/lib/sass/cache_stores/base.rb +88 -0
  15. data/lib/sass/cache_stores/chain.rb +34 -0
  16. data/lib/sass/cache_stores/filesystem.rb +60 -0
  17. data/lib/sass/cache_stores/memory.rb +47 -0
  18. data/lib/sass/cache_stores/null.rb +25 -0
  19. data/lib/sass/cache_stores.rb +15 -0
  20. data/lib/sass/callbacks.rb +67 -0
  21. data/lib/sass/css.rb +407 -0
  22. data/lib/sass/engine.rb +1181 -0
  23. data/lib/sass/environment.rb +191 -0
  24. data/lib/sass/error.rb +198 -0
  25. data/lib/sass/exec/base.rb +187 -0
  26. data/lib/sass/exec/sass_convert.rb +264 -0
  27. data/lib/sass/exec/sass_scss.rb +424 -0
  28. data/lib/sass/exec.rb +9 -0
  29. data/lib/sass/features.rb +47 -0
  30. data/lib/sass/importers/base.rb +182 -0
  31. data/lib/sass/importers/filesystem.rb +211 -0
  32. data/lib/sass/importers.rb +22 -0
  33. data/lib/sass/logger/base.rb +30 -0
  34. data/lib/sass/logger/log_level.rb +45 -0
  35. data/lib/sass/logger.rb +12 -0
  36. data/lib/sass/media.rb +210 -0
  37. data/lib/sass/plugin/compiler.rb +565 -0
  38. data/lib/sass/plugin/configuration.rb +118 -0
  39. data/lib/sass/plugin/generic.rb +15 -0
  40. data/lib/sass/plugin/merb.rb +48 -0
  41. data/lib/sass/plugin/rack.rb +60 -0
  42. data/lib/sass/plugin/rails.rb +47 -0
  43. data/lib/sass/plugin/staleness_checker.rb +199 -0
  44. data/lib/sass/plugin.rb +133 -0
  45. data/lib/sass/railtie.rb +10 -0
  46. data/lib/sass/repl.rb +57 -0
  47. data/lib/sass/root.rb +7 -0
  48. data/lib/sass/script/css_lexer.rb +33 -0
  49. data/lib/sass/script/css_parser.rb +34 -0
  50. data/lib/sass/script/functions.rb +2626 -0
  51. data/lib/sass/script/lexer.rb +449 -0
  52. data/lib/sass/script/parser.rb +637 -0
  53. data/lib/sass/script/tree/funcall.rb +306 -0
  54. data/lib/sass/script/tree/interpolation.rb +118 -0
  55. data/lib/sass/script/tree/list_literal.rb +77 -0
  56. data/lib/sass/script/tree/literal.rb +45 -0
  57. data/lib/sass/script/tree/map_literal.rb +64 -0
  58. data/lib/sass/script/tree/node.rb +109 -0
  59. data/lib/sass/script/tree/operation.rb +103 -0
  60. data/lib/sass/script/tree/selector.rb +26 -0
  61. data/lib/sass/script/tree/string_interpolation.rb +104 -0
  62. data/lib/sass/script/tree/unary_operation.rb +69 -0
  63. data/lib/sass/script/tree/variable.rb +57 -0
  64. data/lib/sass/script/tree.rb +16 -0
  65. data/lib/sass/script/value/arg_list.rb +36 -0
  66. data/lib/sass/script/value/base.rb +240 -0
  67. data/lib/sass/script/value/bool.rb +35 -0
  68. data/lib/sass/script/value/color.rb +680 -0
  69. data/lib/sass/script/value/helpers.rb +262 -0
  70. data/lib/sass/script/value/list.rb +113 -0
  71. data/lib/sass/script/value/map.rb +70 -0
  72. data/lib/sass/script/value/null.rb +44 -0
  73. data/lib/sass/script/value/number.rb +530 -0
  74. data/lib/sass/script/value/string.rb +97 -0
  75. data/lib/sass/script/value.rb +11 -0
  76. data/lib/sass/script.rb +66 -0
  77. data/lib/sass/scss/css_parser.rb +42 -0
  78. data/lib/sass/scss/parser.rb +1209 -0
  79. data/lib/sass/scss/rx.rb +141 -0
  80. data/lib/sass/scss/script_lexer.rb +15 -0
  81. data/lib/sass/scss/script_parser.rb +25 -0
  82. data/lib/sass/scss/static_parser.rb +368 -0
  83. data/lib/sass/scss.rb +16 -0
  84. data/lib/sass/selector/abstract_sequence.rb +109 -0
  85. data/lib/sass/selector/comma_sequence.rb +175 -0
  86. data/lib/sass/selector/pseudo.rb +256 -0
  87. data/lib/sass/selector/sequence.rb +600 -0
  88. data/lib/sass/selector/simple.rb +117 -0
  89. data/lib/sass/selector/simple_sequence.rb +325 -0
  90. data/lib/sass/selector.rb +326 -0
  91. data/lib/sass/shared.rb +76 -0
  92. data/lib/sass/source/map.rb +210 -0
  93. data/lib/sass/source/position.rb +39 -0
  94. data/lib/sass/source/range.rb +41 -0
  95. data/lib/sass/stack.rb +120 -0
  96. data/lib/sass/supports.rb +227 -0
  97. data/lib/sass/tree/at_root_node.rb +83 -0
  98. data/lib/sass/tree/charset_node.rb +22 -0
  99. data/lib/sass/tree/comment_node.rb +82 -0
  100. data/lib/sass/tree/content_node.rb +9 -0
  101. data/lib/sass/tree/css_import_node.rb +60 -0
  102. data/lib/sass/tree/debug_node.rb +18 -0
  103. data/lib/sass/tree/directive_node.rb +59 -0
  104. data/lib/sass/tree/each_node.rb +24 -0
  105. data/lib/sass/tree/error_node.rb +18 -0
  106. data/lib/sass/tree/extend_node.rb +43 -0
  107. data/lib/sass/tree/for_node.rb +36 -0
  108. data/lib/sass/tree/function_node.rb +39 -0
  109. data/lib/sass/tree/if_node.rb +52 -0
  110. data/lib/sass/tree/import_node.rb +74 -0
  111. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  112. data/lib/sass/tree/media_node.rb +48 -0
  113. data/lib/sass/tree/mixin_def_node.rb +38 -0
  114. data/lib/sass/tree/mixin_node.rb +52 -0
  115. data/lib/sass/tree/node.rb +238 -0
  116. data/lib/sass/tree/prop_node.rb +171 -0
  117. data/lib/sass/tree/return_node.rb +19 -0
  118. data/lib/sass/tree/root_node.rb +44 -0
  119. data/lib/sass/tree/rule_node.rb +145 -0
  120. data/lib/sass/tree/supports_node.rb +38 -0
  121. data/lib/sass/tree/trace_node.rb +33 -0
  122. data/lib/sass/tree/variable_node.rb +36 -0
  123. data/lib/sass/tree/visitors/base.rb +72 -0
  124. data/lib/sass/tree/visitors/check_nesting.rb +177 -0
  125. data/lib/sass/tree/visitors/convert.rb +334 -0
  126. data/lib/sass/tree/visitors/cssize.rb +369 -0
  127. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  128. data/lib/sass/tree/visitors/extend.rb +68 -0
  129. data/lib/sass/tree/visitors/perform.rb +539 -0
  130. data/lib/sass/tree/visitors/set_options.rb +139 -0
  131. data/lib/sass/tree/visitors/to_css.rb +381 -0
  132. data/lib/sass/tree/warn_node.rb +18 -0
  133. data/lib/sass/tree/while_node.rb +18 -0
  134. data/lib/sass/util/cross_platform_random.rb +19 -0
  135. data/lib/sass/util/multibyte_string_scanner.rb +157 -0
  136. data/lib/sass/util/normalized_map.rb +130 -0
  137. data/lib/sass/util/ordered_hash.rb +192 -0
  138. data/lib/sass/util/subset_map.rb +110 -0
  139. data/lib/sass/util/test.rb +9 -0
  140. data/lib/sass/util.rb +1318 -0
  141. data/lib/sass/version.rb +124 -0
  142. data/lib/sass.rb +102 -0
  143. data/rails/init.rb +1 -0
  144. data/test/sass/cache_test.rb +131 -0
  145. data/test/sass/callbacks_test.rb +61 -0
  146. data/test/sass/compiler_test.rb +232 -0
  147. data/test/sass/conversion_test.rb +2054 -0
  148. data/test/sass/css2sass_test.rb +477 -0
  149. data/test/sass/data/hsl-rgb.txt +319 -0
  150. data/test/sass/encoding_test.rb +219 -0
  151. data/test/sass/engine_test.rb +3301 -0
  152. data/test/sass/exec_test.rb +86 -0
  153. data/test/sass/extend_test.rb +1661 -0
  154. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  155. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  156. data/test/sass/functions_test.rb +1926 -0
  157. data/test/sass/importer_test.rb +412 -0
  158. data/test/sass/logger_test.rb +58 -0
  159. data/test/sass/mock_importer.rb +49 -0
  160. data/test/sass/more_results/more1.css +9 -0
  161. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  162. data/test/sass/more_results/more_import.css +29 -0
  163. data/test/sass/more_templates/_more_partial.sass +2 -0
  164. data/test/sass/more_templates/more1.sass +23 -0
  165. data/test/sass/more_templates/more_import.sass +11 -0
  166. data/test/sass/plugin_test.rb +554 -0
  167. data/test/sass/results/alt.css +4 -0
  168. data/test/sass/results/basic.css +9 -0
  169. data/test/sass/results/cached_import_option.css +3 -0
  170. data/test/sass/results/compact.css +5 -0
  171. data/test/sass/results/complex.css +86 -0
  172. data/test/sass/results/compressed.css +1 -0
  173. data/test/sass/results/expanded.css +19 -0
  174. data/test/sass/results/filename_fn.css +3 -0
  175. data/test/sass/results/if.css +3 -0
  176. data/test/sass/results/import.css +31 -0
  177. data/test/sass/results/import_charset.css +5 -0
  178. data/test/sass/results/import_charset_1_8.css +5 -0
  179. data/test/sass/results/import_charset_ibm866.css +5 -0
  180. data/test/sass/results/import_content.css +1 -0
  181. data/test/sass/results/line_numbers.css +49 -0
  182. data/test/sass/results/mixins.css +95 -0
  183. data/test/sass/results/multiline.css +24 -0
  184. data/test/sass/results/nested.css +22 -0
  185. data/test/sass/results/options.css +1 -0
  186. data/test/sass/results/parent_ref.css +13 -0
  187. data/test/sass/results/script.css +16 -0
  188. data/test/sass/results/scss_import.css +31 -0
  189. data/test/sass/results/scss_importee.css +2 -0
  190. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  191. data/test/sass/results/subdir/subdir.css +3 -0
  192. data/test/sass/results/units.css +11 -0
  193. data/test/sass/results/warn.css +0 -0
  194. data/test/sass/results/warn_imported.css +0 -0
  195. data/test/sass/script_conversion_test.rb +328 -0
  196. data/test/sass/script_test.rb +1054 -0
  197. data/test/sass/scss/css_test.rb +1215 -0
  198. data/test/sass/scss/rx_test.rb +156 -0
  199. data/test/sass/scss/scss_test.rb +3900 -0
  200. data/test/sass/scss/test_helper.rb +37 -0
  201. data/test/sass/source_map_test.rb +977 -0
  202. data/test/sass/superselector_test.rb +191 -0
  203. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  204. data/test/sass/templates/_double_import_loop2.sass +1 -0
  205. data/test/sass/templates/_filename_fn_import.scss +11 -0
  206. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  207. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  208. data/test/sass/templates/_imported_content.sass +3 -0
  209. data/test/sass/templates/_partial.sass +2 -0
  210. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  211. data/test/sass/templates/alt.sass +16 -0
  212. data/test/sass/templates/basic.sass +23 -0
  213. data/test/sass/templates/bork1.sass +2 -0
  214. data/test/sass/templates/bork2.sass +2 -0
  215. data/test/sass/templates/bork3.sass +2 -0
  216. data/test/sass/templates/bork4.sass +2 -0
  217. data/test/sass/templates/bork5.sass +3 -0
  218. data/test/sass/templates/cached_import_option.scss +3 -0
  219. data/test/sass/templates/compact.sass +17 -0
  220. data/test/sass/templates/complex.sass +305 -0
  221. data/test/sass/templates/compressed.sass +15 -0
  222. data/test/sass/templates/double_import_loop1.sass +1 -0
  223. data/test/sass/templates/expanded.sass +17 -0
  224. data/test/sass/templates/filename_fn.scss +18 -0
  225. data/test/sass/templates/if.sass +11 -0
  226. data/test/sass/templates/import.sass +12 -0
  227. data/test/sass/templates/import_charset.sass +9 -0
  228. data/test/sass/templates/import_charset_1_8.sass +6 -0
  229. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  230. data/test/sass/templates/import_content.sass +4 -0
  231. data/test/sass/templates/importee.less +2 -0
  232. data/test/sass/templates/importee.sass +19 -0
  233. data/test/sass/templates/line_numbers.sass +13 -0
  234. data/test/sass/templates/mixin_bork.sass +5 -0
  235. data/test/sass/templates/mixins.sass +76 -0
  236. data/test/sass/templates/multiline.sass +20 -0
  237. data/test/sass/templates/nested.sass +25 -0
  238. data/test/sass/templates/nested_bork1.sass +2 -0
  239. data/test/sass/templates/nested_bork2.sass +2 -0
  240. data/test/sass/templates/nested_bork3.sass +2 -0
  241. data/test/sass/templates/nested_bork4.sass +2 -0
  242. data/test/sass/templates/nested_import.sass +2 -0
  243. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  244. data/test/sass/templates/options.sass +2 -0
  245. data/test/sass/templates/parent_ref.sass +25 -0
  246. data/test/sass/templates/same_name_different_ext.sass +2 -0
  247. data/test/sass/templates/same_name_different_ext.scss +1 -0
  248. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  249. data/test/sass/templates/script.sass +101 -0
  250. data/test/sass/templates/scss_import.scss +12 -0
  251. data/test/sass/templates/scss_importee.scss +1 -0
  252. data/test/sass/templates/single_import_loop.sass +1 -0
  253. data/test/sass/templates/subdir/import_up1.scss +1 -0
  254. data/test/sass/templates/subdir/import_up2.scss +1 -0
  255. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  256. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  257. data/test/sass/templates/subdir/subdir.sass +6 -0
  258. data/test/sass/templates/units.sass +11 -0
  259. data/test/sass/templates/warn.sass +3 -0
  260. data/test/sass/templates/warn_imported.sass +4 -0
  261. data/test/sass/test_helper.rb +8 -0
  262. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  263. data/test/sass/util/normalized_map_test.rb +51 -0
  264. data/test/sass/util/subset_map_test.rb +91 -0
  265. data/test/sass/util_test.rb +467 -0
  266. data/test/sass/value_helpers_test.rb +179 -0
  267. data/test/test_helper.rb +109 -0
  268. metadata +386 -0
@@ -0,0 +1,334 @@
1
+ # A visitor for converting a Sass tree into a source string.
2
+ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
3
+ # Runs the visitor on a tree.
4
+ #
5
+ # @param root [Tree::Node] The root node of the Sass tree.
6
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
7
+ # @param format [Symbol] `:sass` or `:scss`.
8
+ # @return [String] The Sass or SCSS source for the tree.
9
+ def self.visit(root, options, format)
10
+ new(options, format).send(:visit, root)
11
+ end
12
+
13
+ protected
14
+
15
+ def initialize(options, format)
16
+ @options = options
17
+ @format = format
18
+ @tabs = 0
19
+ # 2 spaces by default
20
+ @tab_chars = @options[:indent] || " "
21
+ end
22
+
23
+ def visit_children(parent)
24
+ @tabs += 1
25
+ return @format == :sass ? "\n" : " {}\n" if parent.children.empty?
26
+ if @format == :sass
27
+ "\n" + super.join.rstrip + "\n"
28
+ else
29
+ " {\n" + super.join.rstrip + "\n#{ @tab_chars * (@tabs - 1)}}\n"
30
+ end
31
+ ensure
32
+ @tabs -= 1
33
+ end
34
+
35
+ # Ensures proper spacing between top-level nodes.
36
+ def visit_root(node)
37
+ Sass::Util.enum_cons(node.children + [nil], 2).map do |child, nxt|
38
+ visit(child) +
39
+ if nxt &&
40
+ (child.is_a?(Sass::Tree::CommentNode) &&
41
+ child.line + child.lines + 1 == nxt.line) ||
42
+ (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
43
+ child.line + 1 == nxt.line) ||
44
+ (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
45
+ child.line + 1 == nxt.line)
46
+ ""
47
+ else
48
+ "\n"
49
+ end
50
+ end.join.rstrip + "\n"
51
+ end
52
+
53
+ def visit_charset(node)
54
+ "#{tab_str}@charset \"#{node.name}\"#{semi}\n"
55
+ end
56
+
57
+ def visit_comment(node)
58
+ value = interp_to_src(node.value)
59
+ if @format == :sass
60
+ content = value.gsub(/\*\/$/, '').rstrip
61
+ if content =~ /\A[ \t]/
62
+ # Re-indent SCSS comments like this:
63
+ # /* foo
64
+ # bar
65
+ # baz */
66
+ content.gsub!(/^/, ' ')
67
+ content.sub!(/\A([ \t]*)\/\*/, '/*\1')
68
+ end
69
+
70
+ if content.include?("\n")
71
+ content.gsub!(/\n \*/, "\n ")
72
+ spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
73
+ sep = node.type == :silent ? "\n//" : "\n *"
74
+ if spaces >= 2
75
+ content.gsub!(/\n /, sep)
76
+ else
77
+ content.gsub!(/\n#{' ' * spaces}/, sep)
78
+ end
79
+ end
80
+
81
+ content.gsub!(/\A\/\*/, '//') if node.type == :silent
82
+ content.gsub!(/^/, tab_str)
83
+ content = content.rstrip + "\n"
84
+ else
85
+ spaces = (@tab_chars * [@tabs - value[/^ */].size, 0].max)
86
+ content = if node.type == :silent
87
+ value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
88
+ else
89
+ value
90
+ end.gsub(/^/, spaces) + "\n"
91
+ end
92
+ content
93
+ end
94
+
95
+ def visit_debug(node)
96
+ "#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"
97
+ end
98
+
99
+ def visit_error(node)
100
+ "#{tab_str}@error #{node.expr.to_sass(@options)}#{semi}\n"
101
+ end
102
+
103
+ def visit_directive(node)
104
+ res = "#{tab_str}#{interp_to_src(node.value)}"
105
+ res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2')
106
+ return res + "#{semi}\n" unless node.has_children
107
+ res + yield + "\n"
108
+ end
109
+
110
+ def visit_each(node)
111
+ vars = node.vars.map {|var| "$#{dasherize(var)}"}.join(", ")
112
+ "#{tab_str}@each #{vars} in #{node.list.to_sass(@options)}#{yield}"
113
+ end
114
+
115
+ def visit_extend(node)
116
+ "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}#{semi}" +
117
+ "#{" !optional" if node.optional?}\n"
118
+ end
119
+
120
+ def visit_for(node)
121
+ "#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
122
+ "#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
123
+ end
124
+
125
+ def visit_function(node)
126
+ args = node.args.map do |v, d|
127
+ d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
128
+ end.join(", ")
129
+ if node.splat
130
+ args << ", " unless node.args.empty?
131
+ args << node.splat.to_sass(@options) << "..."
132
+ end
133
+
134
+ "#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"
135
+ end
136
+
137
+ def visit_if(node)
138
+ name =
139
+ if !@is_else
140
+ "if"
141
+ elsif node.expr
142
+ "else if"
143
+ else
144
+ "else"
145
+ end
146
+ @is_else = false
147
+ str = "#{tab_str}@#{name}"
148
+ str << " #{node.expr.to_sass(@options)}" if node.expr
149
+ str << yield
150
+ @is_else = true
151
+ str << visit(node.else) if node.else
152
+ str
153
+ ensure
154
+ @is_else = false
155
+ end
156
+
157
+ def visit_import(node)
158
+ quote = @format == :scss ? '"' : ''
159
+ "#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n"
160
+ end
161
+
162
+ def visit_media(node)
163
+ "#{tab_str}@media #{query_interp_to_src(node.query)}#{yield}"
164
+ end
165
+
166
+ def visit_supports(node)
167
+ "#{tab_str}@#{node.name} #{node.condition.to_src(@options)}#{yield}"
168
+ end
169
+
170
+ def visit_cssimport(node)
171
+ if node.uri.is_a?(Sass::Script::Tree::Node)
172
+ str = "#{tab_str}@import #{node.uri.to_sass(@options)}"
173
+ else
174
+ str = "#{tab_str}@import #{node.uri}"
175
+ end
176
+ str << " #{interp_to_src(node.query)}" unless node.query.empty?
177
+ "#{str}#{semi}\n"
178
+ end
179
+
180
+ def visit_mixindef(node)
181
+ args =
182
+ if node.args.empty? && node.splat.nil?
183
+ ""
184
+ else
185
+ str = '('
186
+ str << node.args.map do |v, d|
187
+ if d
188
+ "#{v.to_sass(@options)}: #{d.to_sass(@options)}"
189
+ else
190
+ v.to_sass(@options)
191
+ end
192
+ end.join(", ")
193
+
194
+ if node.splat
195
+ str << ", " unless node.args.empty?
196
+ str << node.splat.to_sass(@options) << '...'
197
+ end
198
+
199
+ str << ')'
200
+ end
201
+
202
+ "#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
203
+ end
204
+
205
+ def visit_mixin(node)
206
+ arg_to_sass = lambda do |arg|
207
+ sass = arg.to_sass(@options)
208
+ sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma
209
+ sass
210
+ end
211
+
212
+ unless node.args.empty? && node.keywords.empty? && node.splat.nil?
213
+ args = node.args.map(&arg_to_sass)
214
+ keywords = Sass::Util.hash_to_a(node.keywords.as_stored).
215
+ map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"}
216
+
217
+ if node.splat
218
+ splat = "#{arg_to_sass[node.splat]}..."
219
+ kwarg_splat = "#{arg_to_sass[node.kwarg_splat]}..." if node.kwarg_splat
220
+ end
221
+
222
+ arglist = "(#{[args, splat, keywords, kwarg_splat].flatten.compact.join(', ')})"
223
+ end
224
+ "#{tab_str}#{@format == :sass ? '+' : '@include '}" +
225
+ "#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n"
226
+ end
227
+
228
+ def visit_content(node)
229
+ "#{tab_str}@content#{semi}\n"
230
+ end
231
+
232
+ def visit_prop(node)
233
+ res = tab_str + node.declaration(@options, @format)
234
+ return res + semi + "\n" if node.children.empty?
235
+ res + yield.rstrip + semi + "\n"
236
+ end
237
+
238
+ def visit_return(node)
239
+ "#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n"
240
+ end
241
+
242
+ def visit_rule(node)
243
+ rule = node.parsed_rules ? [node.parsed_rules.to_s] : node.rule
244
+ if @format == :sass
245
+ name = selector_to_sass(rule)
246
+ name = "\\" + name if name[0] == ?:
247
+ name.gsub(/^/, tab_str) + yield
248
+ elsif @format == :scss
249
+ name = selector_to_scss(rule)
250
+ res = name + yield
251
+ if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent
252
+ res.slice!(-3..-1)
253
+ res << "\n" << tab_str << "}\n"
254
+ end
255
+ res
256
+ end
257
+ end
258
+
259
+ def visit_variable(node)
260
+ "#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}" +
261
+ "#{' !global' if node.global}#{' !default' if node.guarded}#{semi}\n"
262
+ end
263
+
264
+ def visit_warn(node)
265
+ "#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n"
266
+ end
267
+
268
+ def visit_while(node)
269
+ "#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"
270
+ end
271
+
272
+ def visit_atroot(node)
273
+ if node.query
274
+ "#{tab_str}@at-root #{query_interp_to_src(node.query)}#{yield}"
275
+ elsif node.children.length == 1 && node.children.first.is_a?(Sass::Tree::RuleNode)
276
+ rule = node.children.first
277
+ "#{tab_str}@at-root #{selector_to_src(rule.rule)}#{visit_children(rule)}"
278
+ else
279
+ "#{tab_str}@at-root#{yield}"
280
+ end
281
+ end
282
+
283
+ private
284
+
285
+ def interp_to_src(interp)
286
+ interp.map {|r| r.is_a?(String) ? r : r.to_sass(@options)}.join
287
+ end
288
+
289
+ # Like interp_to_src, but removes the unnecessary `#{}` around the keys and
290
+ # values in query expressions.
291
+ def query_interp_to_src(interp)
292
+ interp = interp.map do |e|
293
+ next e unless e.is_a?(Sass::Script::Tree::Literal)
294
+ next e unless e.value.is_a?(Sass::Script::Value::String)
295
+ e.value.value
296
+ end
297
+
298
+ interp_to_src(interp)
299
+ end
300
+
301
+ def selector_to_src(sel)
302
+ @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
303
+ end
304
+
305
+ def selector_to_sass(sel)
306
+ sel.map do |r|
307
+ if r.is_a?(String)
308
+ r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
309
+ else
310
+ r.to_sass(@options)
311
+ end
312
+ end.join
313
+ end
314
+
315
+ def selector_to_scss(sel)
316
+ interp_to_src(sel).gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
317
+ end
318
+
319
+ def semi
320
+ @format == :sass ? "" : ";"
321
+ end
322
+
323
+ def tab_str
324
+ @tab_chars * @tabs
325
+ end
326
+
327
+ def dasherize(s)
328
+ if @options[:dasherize]
329
+ s.gsub('_', '-')
330
+ else
331
+ s
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,369 @@
1
+ # A visitor for converting a static Sass tree into a static CSS tree.
2
+ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
5
+ # *and* the extensions defined for this tree
6
+ def self.visit(root); super; end
7
+
8
+ protected
9
+
10
+ # Returns the immediate parent of the current node.
11
+ # @return [Tree::Node]
12
+ def parent
13
+ @parents.last
14
+ end
15
+
16
+ def initialize
17
+ @parents = []
18
+ @extends = Sass::Util::SubsetMap.new
19
+ end
20
+
21
+ # If an exception is raised, this adds proper metadata to the backtrace.
22
+ def visit(node)
23
+ super(node)
24
+ rescue Sass::SyntaxError => e
25
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
26
+ raise e
27
+ end
28
+
29
+ # Keeps track of the current parent node.
30
+ def visit_children(parent)
31
+ with_parent parent do
32
+ parent.children = visit_children_without_parent(parent)
33
+ parent
34
+ end
35
+ end
36
+
37
+ # Like {#visit\_children}, but doesn't set {#parent}.
38
+ #
39
+ # @param node [Sass::Tree::Node]
40
+ # @return [Array<Sass::Tree::Node>] the flattened results of
41
+ # visiting all the children of `node`
42
+ def visit_children_without_parent(node)
43
+ node.children.map {|c| visit(c)}.flatten
44
+ end
45
+
46
+ # Runs a block of code with the current parent node
47
+ # replaced with the given node.
48
+ #
49
+ # @param parent [Tree::Node] The new parent for the duration of the block.
50
+ # @yield A block in which the parent is set to `parent`.
51
+ # @return [Object] The return value of the block.
52
+ def with_parent(parent)
53
+ @parents.push parent
54
+ yield
55
+ ensure
56
+ @parents.pop
57
+ end
58
+
59
+ # In Ruby 1.8, ensures that there's only one `@charset` directive
60
+ # and that it's at the top of the document.
61
+ #
62
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
63
+ # *and* the extensions defined for this tree
64
+ def visit_root(node)
65
+ yield
66
+
67
+ if parent.nil?
68
+ # In Ruby 1.9 we can make all @charset nodes invisible
69
+ # and infer the final @charset from the encoding of the final string.
70
+ if Sass::Util.ruby1_8?
71
+ charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
72
+ node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
73
+ node.children.unshift charset if charset
74
+ end
75
+
76
+ imports_to_move = []
77
+ import_limit = nil
78
+ i = -1
79
+ node.children.reject! do |n|
80
+ i += 1
81
+ if import_limit
82
+ next false unless n.is_a?(Sass::Tree::CssImportNode)
83
+ imports_to_move << n
84
+ next true
85
+ end
86
+
87
+ if !n.is_a?(Sass::Tree::CommentNode) &&
88
+ !n.is_a?(Sass::Tree::CharsetNode) &&
89
+ !n.is_a?(Sass::Tree::CssImportNode)
90
+ import_limit = i
91
+ end
92
+
93
+ false
94
+ end
95
+
96
+ if import_limit
97
+ node.children = node.children[0...import_limit] + imports_to_move +
98
+ node.children[import_limit..-1]
99
+ end
100
+ end
101
+
102
+ return node, @extends
103
+ rescue Sass::SyntaxError => e
104
+ e.sass_template ||= node.template
105
+ raise e
106
+ end
107
+
108
+ # A simple struct wrapping up information about a single `@extend` instance. A
109
+ # single {ExtendNode} can have multiple Extends if either the parent node or
110
+ # the extended selector is a comma sequence.
111
+ #
112
+ # @attr extender [Sass::Selector::Sequence]
113
+ # The selector of the CSS rule containing the `@extend`.
114
+ # @attr target [Array<Sass::Selector::Simple>] The selector being `@extend`ed.
115
+ # @attr node [Sass::Tree::ExtendNode] The node that produced this extend.
116
+ # @attr directives [Array<Sass::Tree::DirectiveNode>]
117
+ # The directives containing the `@extend`.
118
+ # @attr result [Symbol]
119
+ # The result of this extend. One of `:not_found` (the target doesn't exist
120
+ # in the document), `:failed_to_unify` (the target exists but cannot be
121
+ # unified with the extender), or `:succeeded`.
122
+ Extend = Struct.new(:extender, :target, :node, :directives, :result)
123
+
124
+ # Registers an extension in the `@extends` subset map.
125
+ def visit_extend(node)
126
+ parent.resolved_rules.populate_extends(@extends, node.resolved_selector, node,
127
+ @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)})
128
+ []
129
+ end
130
+
131
+ # Modifies exception backtraces to include the imported file.
132
+ def visit_import(node)
133
+ visit_children_without_parent(node)
134
+ rescue Sass::SyntaxError => e
135
+ e.modify_backtrace(:filename => node.children.first.filename)
136
+ e.add_backtrace(:filename => node.filename, :line => node.line)
137
+ raise e
138
+ end
139
+
140
+ # Asserts that all the traced children are valid in their new location.
141
+ def visit_trace(node)
142
+ visit_children_without_parent(node)
143
+ rescue Sass::SyntaxError => e
144
+ e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
145
+ e.add_backtrace(:filename => node.filename, :line => node.line)
146
+ raise e
147
+ end
148
+
149
+ # Converts nested properties into flat properties
150
+ # and updates the indentation of the prop node based on the nesting level.
151
+ def visit_prop(node)
152
+ if parent.is_a?(Sass::Tree::PropNode)
153
+ node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
154
+ node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
155
+ end
156
+
157
+ yield
158
+
159
+ result = node.children.dup
160
+ if !node.resolved_value.empty? || node.children.empty?
161
+ node.send(:check!)
162
+ result.unshift(node)
163
+ end
164
+
165
+ result
166
+ end
167
+
168
+ def visit_atroot(node)
169
+ # If there aren't any more directives or rules that this @at-root needs to
170
+ # exclude, we can get rid of it and just evaluate the children.
171
+ if @parents.none? {|n| node.exclude_node?(n)}
172
+ results = visit_children_without_parent(node)
173
+ results.each {|c| c.tabs += node.tabs if bubblable?(c)}
174
+ if !results.empty? && bubblable?(results.last)
175
+ results.last.group_end = node.group_end
176
+ end
177
+ return results
178
+ end
179
+
180
+ # If this @at-root excludes the immediate parent, return it as-is so that it
181
+ # can be bubbled up by the parent node.
182
+ return Bubble.new(node) if node.exclude_node?(parent)
183
+
184
+ # Otherwise, duplicate the current parent and move it into the @at-root
185
+ # node. As above, returning an @at-root node signals to the parent directive
186
+ # that it should be bubbled upwards.
187
+ bubble(node)
188
+ end
189
+
190
+ # The following directives are visible and have children. This means they need
191
+ # to be able to handle bubbling up nodes such as @at-root and @media.
192
+
193
+ # Updates the indentation of the rule node based on the nesting
194
+ # level. The selectors were resolved in {Perform}.
195
+ def visit_rule(node)
196
+ yield
197
+
198
+ rules = node.children.select {|c| bubblable?(c)}
199
+ props = node.children.reject {|c| bubblable?(c) || c.invisible?}
200
+
201
+ unless props.empty?
202
+ node.children = props
203
+ rules.each {|r| r.tabs += 1} if node.style == :nested
204
+ rules.unshift(node)
205
+ end
206
+
207
+ rules = debubble(rules)
208
+ unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty? || !bubblable?(rules.last)
209
+ rules.last.group_end = true
210
+ end
211
+ rules
212
+ end
213
+
214
+ def visit_keyframerule(node)
215
+ return node unless node.has_children
216
+
217
+ yield
218
+
219
+ debubble(node.children, node)
220
+ end
221
+
222
+ # Bubbles a directive up through RuleNodes.
223
+ def visit_directive(node)
224
+ return node unless node.has_children
225
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
226
+
227
+ yield
228
+
229
+ # Since we don't know if the mere presence of an unknown directive may be
230
+ # important, we should keep an empty version around even if all the contents
231
+ # are removed via @at-root. However, if the contents are just bubbled out,
232
+ # we don't need to do so.
233
+ directive_exists = node.children.any? do |child|
234
+ next true unless child.is_a?(Bubble)
235
+ next false unless child.node.is_a?(Sass::Tree::DirectiveNode)
236
+ child.node.resolved_value == node.resolved_value
237
+ end
238
+
239
+ # We know empty @keyframes directives do nothing.
240
+ if directive_exists || node.name == '@keyframes'
241
+ []
242
+ else
243
+ empty_node = node.dup
244
+ empty_node.children = []
245
+ [empty_node]
246
+ end + debubble(node.children, node)
247
+ end
248
+
249
+ # Bubbles the `@media` directive up through RuleNodes
250
+ # and merges it with other `@media` directives.
251
+ def visit_media(node)
252
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
253
+ return Bubble.new(node) if parent.is_a?(Sass::Tree::MediaNode)
254
+
255
+ yield
256
+
257
+ debubble(node.children, node) do |child|
258
+ next child unless child.is_a?(Sass::Tree::MediaNode)
259
+ # Copies of `node` can be bubbled, and we don't want to merge it with its
260
+ # own query.
261
+ next child if child.resolved_query == node.resolved_query
262
+ next child if child.resolved_query = child.resolved_query.merge(node.resolved_query)
263
+ end
264
+ end
265
+
266
+ # Bubbles the `@supports` directive up through RuleNodes.
267
+ def visit_supports(node)
268
+ return node unless node.has_children
269
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
270
+
271
+ yield
272
+
273
+ debubble(node.children, node)
274
+ end
275
+
276
+ private
277
+
278
+ # "Bubbles" `node` one level by copying the parent and wrapping `node`'s
279
+ # children with it.
280
+ #
281
+ # @param node [Sass::Tree::Node].
282
+ # @return [Bubble]
283
+ def bubble(node)
284
+ new_rule = parent.dup
285
+ new_rule.children = node.children
286
+ node.children = [new_rule]
287
+ Bubble.new(node)
288
+ end
289
+
290
+ # Pops all bubbles in `children` and intersperses the results with the other
291
+ # values.
292
+ #
293
+ # If `parent` is passed, it's copied and used as the parent node for the
294
+ # nested portions of `children`.
295
+ #
296
+ # @param children [List<Sass::Tree::Node, Bubble>]
297
+ # @param parent [Sass::Tree::Node]
298
+ # @yield [node] An optional block for processing bubbled nodes. Each bubbled
299
+ # node will be passed to this block.
300
+ # @yieldparam node [Sass::Tree::Node] A bubbled node.
301
+ # @yieldreturn [Sass::Tree::Node?] A node to use in place of the bubbled node.
302
+ # This can be the node itself, or `nil` to indicate that the node should be
303
+ # omitted.
304
+ # @return [List<Sass::Tree::Node, Bubble>]
305
+ def debubble(children, parent = nil)
306
+ # Keep track of the previous parent so that we don't divide `parent`
307
+ # unnecessarily if the `@at-root` doesn't produce any new nodes (e.g.
308
+ # `@at-root {@extend %foo}`).
309
+ previous_parent = nil
310
+
311
+ Sass::Util.slice_by(children) {|c| c.is_a?(Bubble)}.map do |(is_bubble, slice)|
312
+ unless is_bubble
313
+ next slice unless parent
314
+ if previous_parent
315
+ previous_parent.children.push(*slice)
316
+ next []
317
+ else
318
+ previous_parent = new_parent = parent.dup
319
+ new_parent.children = slice
320
+ next new_parent
321
+ end
322
+ end
323
+
324
+ slice.map do |bubble|
325
+ next unless (node = block_given? ? yield(bubble.node) : bubble.node)
326
+ node.tabs += bubble.tabs
327
+ node.group_end = bubble.group_end
328
+ results = [visit(node)].flatten
329
+ previous_parent = nil unless results.empty?
330
+ results
331
+ end.compact
332
+ end.flatten
333
+ end
334
+
335
+ # Returns whether or not a node can be bubbled up through the syntax tree.
336
+ #
337
+ # @param node [Sass::Tree::Node]
338
+ # @return [Boolean]
339
+ def bubblable?(node)
340
+ node.is_a?(Sass::Tree::RuleNode) || node.bubbles?
341
+ end
342
+
343
+ # A wrapper class for a node that indicates to the parent that it should
344
+ # treat the wrapped node as a sibling rather than a child.
345
+ #
346
+ # Nodes should be wrapped before they're passed to \{Cssize.visit}. They will
347
+ # be automatically visited upon calling \{#pop}.
348
+ #
349
+ # This duck types as a [Sass::Tree::Node] for the purposes of
350
+ # tree-manipulation operations.
351
+ class Bubble
352
+ attr_accessor :node
353
+ attr_accessor :tabs
354
+ attr_accessor :group_end
355
+
356
+ def initialize(node)
357
+ @node = node
358
+ @tabs = 0
359
+ end
360
+
361
+ def bubbles?
362
+ true
363
+ end
364
+
365
+ def inspect
366
+ "(Bubble #{node.inspect})"
367
+ end
368
+ end
369
+ end