aliddle-sass 1.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 (238) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +201 -0
  5. data/Rakefile +347 -0
  6. data/VERSION +1 -0
  7. data/VERSION_NAME +1 -0
  8. data/bin/sass +9 -0
  9. data/bin/sass-convert +8 -0
  10. data/bin/scss +9 -0
  11. data/extra/update_watch.rb +13 -0
  12. data/init.rb +18 -0
  13. data/lib/sass.rb +95 -0
  14. data/lib/sass/cache_stores.rb +15 -0
  15. data/lib/sass/cache_stores/base.rb +88 -0
  16. data/lib/sass/cache_stores/chain.rb +33 -0
  17. data/lib/sass/cache_stores/filesystem.rb +60 -0
  18. data/lib/sass/cache_stores/memory.rb +47 -0
  19. data/lib/sass/cache_stores/null.rb +25 -0
  20. data/lib/sass/callbacks.rb +66 -0
  21. data/lib/sass/css.rb +409 -0
  22. data/lib/sass/engine.rb +928 -0
  23. data/lib/sass/environment.rb +101 -0
  24. data/lib/sass/error.rb +201 -0
  25. data/lib/sass/exec.rb +707 -0
  26. data/lib/sass/importers.rb +22 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +190 -0
  29. data/lib/sass/logger.rb +15 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin.rb +132 -0
  34. data/lib/sass/plugin/compiler.rb +406 -0
  35. data/lib/sass/plugin/configuration.rb +123 -0
  36. data/lib/sass/plugin/generic.rb +15 -0
  37. data/lib/sass/plugin/merb.rb +48 -0
  38. data/lib/sass/plugin/rack.rb +60 -0
  39. data/lib/sass/plugin/rails.rb +47 -0
  40. data/lib/sass/plugin/staleness_checker.rb +183 -0
  41. data/lib/sass/railtie.rb +9 -0
  42. data/lib/sass/repl.rb +57 -0
  43. data/lib/sass/root.rb +7 -0
  44. data/lib/sass/script.rb +39 -0
  45. data/lib/sass/script/arg_list.rb +52 -0
  46. data/lib/sass/script/bool.rb +18 -0
  47. data/lib/sass/script/color.rb +606 -0
  48. data/lib/sass/script/css_lexer.rb +29 -0
  49. data/lib/sass/script/css_parser.rb +31 -0
  50. data/lib/sass/script/funcall.rb +237 -0
  51. data/lib/sass/script/functions.rb +1543 -0
  52. data/lib/sass/script/interpolation.rb +79 -0
  53. data/lib/sass/script/lexer.rb +348 -0
  54. data/lib/sass/script/list.rb +85 -0
  55. data/lib/sass/script/literal.rb +221 -0
  56. data/lib/sass/script/node.rb +99 -0
  57. data/lib/sass/script/null.rb +37 -0
  58. data/lib/sass/script/number.rb +453 -0
  59. data/lib/sass/script/operation.rb +110 -0
  60. data/lib/sass/script/parser.rb +495 -0
  61. data/lib/sass/script/string.rb +51 -0
  62. data/lib/sass/script/string_interpolation.rb +103 -0
  63. data/lib/sass/script/unary_operation.rb +69 -0
  64. data/lib/sass/script/variable.rb +58 -0
  65. data/lib/sass/scss.rb +16 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1179 -0
  68. data/lib/sass/scss/rx.rb +133 -0
  69. data/lib/sass/scss/script_lexer.rb +15 -0
  70. data/lib/sass/scss/script_parser.rb +25 -0
  71. data/lib/sass/scss/static_parser.rb +54 -0
  72. data/lib/sass/selector.rb +452 -0
  73. data/lib/sass/selector/abstract_sequence.rb +94 -0
  74. data/lib/sass/selector/comma_sequence.rb +92 -0
  75. data/lib/sass/selector/sequence.rb +507 -0
  76. data/lib/sass/selector/simple.rb +119 -0
  77. data/lib/sass/selector/simple_sequence.rb +212 -0
  78. data/lib/sass/shared.rb +76 -0
  79. data/lib/sass/supports.rb +229 -0
  80. data/lib/sass/tree/charset_node.rb +22 -0
  81. data/lib/sass/tree/comment_node.rb +82 -0
  82. data/lib/sass/tree/content_node.rb +9 -0
  83. data/lib/sass/tree/css_import_node.rb +60 -0
  84. data/lib/sass/tree/debug_node.rb +18 -0
  85. data/lib/sass/tree/directive_node.rb +42 -0
  86. data/lib/sass/tree/each_node.rb +24 -0
  87. data/lib/sass/tree/extend_node.rb +36 -0
  88. data/lib/sass/tree/for_node.rb +36 -0
  89. data/lib/sass/tree/function_node.rb +34 -0
  90. data/lib/sass/tree/if_node.rb +52 -0
  91. data/lib/sass/tree/import_node.rb +75 -0
  92. data/lib/sass/tree/media_node.rb +58 -0
  93. data/lib/sass/tree/mixin_def_node.rb +38 -0
  94. data/lib/sass/tree/mixin_node.rb +39 -0
  95. data/lib/sass/tree/node.rb +196 -0
  96. data/lib/sass/tree/prop_node.rb +152 -0
  97. data/lib/sass/tree/return_node.rb +18 -0
  98. data/lib/sass/tree/root_node.rb +28 -0
  99. data/lib/sass/tree/rule_node.rb +132 -0
  100. data/lib/sass/tree/supports_node.rb +51 -0
  101. data/lib/sass/tree/trace_node.rb +32 -0
  102. data/lib/sass/tree/variable_node.rb +30 -0
  103. data/lib/sass/tree/visitors/base.rb +75 -0
  104. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  105. data/lib/sass/tree/visitors/convert.rb +316 -0
  106. data/lib/sass/tree/visitors/cssize.rb +229 -0
  107. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  108. data/lib/sass/tree/visitors/extend.rb +68 -0
  109. data/lib/sass/tree/visitors/perform.rb +446 -0
  110. data/lib/sass/tree/visitors/set_options.rb +125 -0
  111. data/lib/sass/tree/visitors/to_css.rb +230 -0
  112. data/lib/sass/tree/warn_node.rb +18 -0
  113. data/lib/sass/tree/while_node.rb +18 -0
  114. data/lib/sass/util.rb +906 -0
  115. data/lib/sass/util/multibyte_string_scanner.rb +155 -0
  116. data/lib/sass/util/subset_map.rb +109 -0
  117. data/lib/sass/util/test.rb +10 -0
  118. data/lib/sass/version.rb +126 -0
  119. data/rails/init.rb +1 -0
  120. data/test/Gemfile +3 -0
  121. data/test/Gemfile.lock +10 -0
  122. data/test/sass/cache_test.rb +89 -0
  123. data/test/sass/callbacks_test.rb +61 -0
  124. data/test/sass/conversion_test.rb +1760 -0
  125. data/test/sass/css2sass_test.rb +439 -0
  126. data/test/sass/data/hsl-rgb.txt +319 -0
  127. data/test/sass/engine_test.rb +3243 -0
  128. data/test/sass/exec_test.rb +86 -0
  129. data/test/sass/extend_test.rb +1461 -0
  130. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  131. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  132. data/test/sass/functions_test.rb +1139 -0
  133. data/test/sass/importer_test.rb +192 -0
  134. data/test/sass/logger_test.rb +58 -0
  135. data/test/sass/mock_importer.rb +49 -0
  136. data/test/sass/more_results/more1.css +9 -0
  137. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  138. data/test/sass/more_results/more_import.css +29 -0
  139. data/test/sass/more_templates/_more_partial.sass +2 -0
  140. data/test/sass/more_templates/more1.sass +23 -0
  141. data/test/sass/more_templates/more_import.sass +11 -0
  142. data/test/sass/plugin_test.rb +550 -0
  143. data/test/sass/results/alt.css +4 -0
  144. data/test/sass/results/basic.css +9 -0
  145. data/test/sass/results/cached_import_option.css +3 -0
  146. data/test/sass/results/compact.css +5 -0
  147. data/test/sass/results/complex.css +86 -0
  148. data/test/sass/results/compressed.css +1 -0
  149. data/test/sass/results/expanded.css +19 -0
  150. data/test/sass/results/filename_fn.css +3 -0
  151. data/test/sass/results/if.css +3 -0
  152. data/test/sass/results/import.css +31 -0
  153. data/test/sass/results/import_charset.css +5 -0
  154. data/test/sass/results/import_charset_1_8.css +5 -0
  155. data/test/sass/results/import_charset_ibm866.css +5 -0
  156. data/test/sass/results/import_content.css +1 -0
  157. data/test/sass/results/line_numbers.css +49 -0
  158. data/test/sass/results/mixins.css +95 -0
  159. data/test/sass/results/multiline.css +24 -0
  160. data/test/sass/results/nested.css +22 -0
  161. data/test/sass/results/options.css +1 -0
  162. data/test/sass/results/parent_ref.css +13 -0
  163. data/test/sass/results/script.css +16 -0
  164. data/test/sass/results/scss_import.css +31 -0
  165. data/test/sass/results/scss_importee.css +2 -0
  166. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  167. data/test/sass/results/subdir/subdir.css +3 -0
  168. data/test/sass/results/units.css +11 -0
  169. data/test/sass/results/warn.css +0 -0
  170. data/test/sass/results/warn_imported.css +0 -0
  171. data/test/sass/script_conversion_test.rb +299 -0
  172. data/test/sass/script_test.rb +591 -0
  173. data/test/sass/scss/css_test.rb +1093 -0
  174. data/test/sass/scss/rx_test.rb +156 -0
  175. data/test/sass/scss/scss_test.rb +2043 -0
  176. data/test/sass/scss/test_helper.rb +37 -0
  177. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  178. data/test/sass/templates/_double_import_loop2.sass +1 -0
  179. data/test/sass/templates/_filename_fn_import.scss +11 -0
  180. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  181. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  182. data/test/sass/templates/_imported_content.sass +3 -0
  183. data/test/sass/templates/_partial.sass +2 -0
  184. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  185. data/test/sass/templates/alt.sass +16 -0
  186. data/test/sass/templates/basic.sass +23 -0
  187. data/test/sass/templates/bork1.sass +2 -0
  188. data/test/sass/templates/bork2.sass +2 -0
  189. data/test/sass/templates/bork3.sass +2 -0
  190. data/test/sass/templates/bork4.sass +2 -0
  191. data/test/sass/templates/bork5.sass +3 -0
  192. data/test/sass/templates/cached_import_option.scss +3 -0
  193. data/test/sass/templates/compact.sass +17 -0
  194. data/test/sass/templates/complex.sass +305 -0
  195. data/test/sass/templates/compressed.sass +15 -0
  196. data/test/sass/templates/double_import_loop1.sass +1 -0
  197. data/test/sass/templates/expanded.sass +17 -0
  198. data/test/sass/templates/filename_fn.scss +18 -0
  199. data/test/sass/templates/if.sass +11 -0
  200. data/test/sass/templates/import.sass +12 -0
  201. data/test/sass/templates/import_charset.sass +9 -0
  202. data/test/sass/templates/import_charset_1_8.sass +6 -0
  203. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  204. data/test/sass/templates/import_content.sass +4 -0
  205. data/test/sass/templates/importee.less +2 -0
  206. data/test/sass/templates/importee.sass +19 -0
  207. data/test/sass/templates/line_numbers.sass +13 -0
  208. data/test/sass/templates/mixin_bork.sass +5 -0
  209. data/test/sass/templates/mixins.sass +76 -0
  210. data/test/sass/templates/multiline.sass +20 -0
  211. data/test/sass/templates/nested.sass +25 -0
  212. data/test/sass/templates/nested_bork1.sass +2 -0
  213. data/test/sass/templates/nested_bork2.sass +2 -0
  214. data/test/sass/templates/nested_bork3.sass +2 -0
  215. data/test/sass/templates/nested_bork4.sass +2 -0
  216. data/test/sass/templates/nested_import.sass +2 -0
  217. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  218. data/test/sass/templates/options.sass +2 -0
  219. data/test/sass/templates/parent_ref.sass +25 -0
  220. data/test/sass/templates/same_name_different_ext.sass +2 -0
  221. data/test/sass/templates/same_name_different_ext.scss +1 -0
  222. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  223. data/test/sass/templates/script.sass +101 -0
  224. data/test/sass/templates/scss_import.scss +11 -0
  225. data/test/sass/templates/scss_importee.scss +1 -0
  226. data/test/sass/templates/single_import_loop.sass +1 -0
  227. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  228. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  229. data/test/sass/templates/subdir/subdir.sass +6 -0
  230. data/test/sass/templates/units.sass +11 -0
  231. data/test/sass/templates/warn.sass +3 -0
  232. data/test/sass/templates/warn_imported.sass +4 -0
  233. data/test/sass/test_helper.rb +8 -0
  234. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  235. data/test/sass/util/subset_map_test.rb +91 -0
  236. data/test/sass/util_test.rb +313 -0
  237. data/test/test_helper.rb +80 -0
  238. metadata +348 -0
@@ -0,0 +1,316 @@
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
+ (@format == :sass ? "\n" : " {\n") + super.join.rstrip + (@format == :sass ? "\n" : "\n#{ @tab_chars * (@tabs-1)}}\n")
27
+ ensure
28
+ @tabs -= 1
29
+ end
30
+
31
+ # Ensures proper spacing between top-level nodes.
32
+ def visit_root(node)
33
+ Sass::Util.enum_cons(node.children + [nil], 2).map do |child, nxt|
34
+ visit(child) +
35
+ if nxt &&
36
+ (child.is_a?(Sass::Tree::CommentNode) &&
37
+ child.line + child.lines + 1 == nxt.line) ||
38
+ (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
39
+ child.line + 1 == nxt.line) ||
40
+ (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
41
+ child.line + 1 == nxt.line)
42
+ ""
43
+ else
44
+ "\n"
45
+ end
46
+ end.join.rstrip + "\n"
47
+ end
48
+
49
+ def visit_charset(node)
50
+ "#{tab_str}@charset \"#{node.name}\"#{semi}\n"
51
+ end
52
+
53
+ def visit_comment(node)
54
+ value = interp_to_src(node.value)
55
+ content = if @format == :sass
56
+ content = value.gsub(/\*\/$/, '').rstrip
57
+ if content =~ /\A[ \t]/
58
+ # Re-indent SCSS comments like this:
59
+ # /* foo
60
+ # bar
61
+ # baz */
62
+ content.gsub!(/^/, ' ')
63
+ content.sub!(/\A([ \t]*)\/\*/, '/*\1')
64
+ end
65
+
66
+ content =
67
+ unless content.include?("\n")
68
+ content
69
+ else
70
+ content.gsub!(/\n( \*|\/\/)/, "\n ")
71
+ spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
72
+ sep = node.type == :silent ? "\n//" : "\n *"
73
+ if spaces >= 2
74
+ content.gsub(/\n /, sep)
75
+ else
76
+ content.gsub(/\n#{' ' * spaces}/, sep)
77
+ end
78
+ end
79
+
80
+ content.gsub!(/\A\/\*/, '//') if node.type == :silent
81
+ content.gsub!(/^/, tab_str)
82
+ content.rstrip + "\n"
83
+ else
84
+ spaces = (@tab_chars * [@tabs - value[/^ */].size, 0].max)
85
+ content = if node.type == :silent
86
+ value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
87
+ else
88
+ value
89
+ end.gsub(/^/, spaces) + "\n"
90
+ content
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_directive(node)
100
+ res = "#{tab_str}#{interp_to_src(node.value)}"
101
+ res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2');
102
+ return res + "#{semi}\n" unless node.has_children
103
+ res + yield + "\n"
104
+ end
105
+
106
+ def visit_each(node)
107
+ "#{tab_str}@each $#{dasherize(node.var)} in #{node.list.to_sass(@options)}#{yield}"
108
+ end
109
+
110
+ def visit_extend(node)
111
+ "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}#{semi}#{" !optional" if node.optional?}\n"
112
+ end
113
+
114
+ def visit_for(node)
115
+ "#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
116
+ "#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
117
+ end
118
+
119
+ def visit_function(node)
120
+ args = node.args.map do |v, d|
121
+ d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
122
+ end.join(", ")
123
+ if node.splat
124
+ args << ", " unless node.args.empty?
125
+ args << node.splat.to_sass(@options) << "..."
126
+ end
127
+
128
+ "#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"
129
+ end
130
+
131
+ def visit_if(node)
132
+ name =
133
+ if !@is_else; "if"
134
+ elsif node.expr; "else if"
135
+ else; "else"
136
+ end
137
+ @is_else = false
138
+ str = "#{tab_str}@#{name}"
139
+ str << " #{node.expr.to_sass(@options)}" if node.expr
140
+ str << yield
141
+ @is_else = true
142
+ str << visit(node.else) if node.else
143
+ str
144
+ ensure
145
+ @is_else = false
146
+ end
147
+
148
+ def visit_import(node)
149
+ quote = @format == :scss ? '"' : ''
150
+ "#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n"
151
+ end
152
+
153
+ def visit_media(node)
154
+ "#{tab_str}@media #{media_interp_to_src(node.query)}#{yield}"
155
+ end
156
+
157
+ def visit_supports(node)
158
+ "#{tab_str}@#{node.name} #{node.condition.to_src(@options)}#{yield}"
159
+ end
160
+
161
+ def visit_cssimport(node)
162
+ if node.uri.is_a?(Sass::Script::Node)
163
+ str = "#{tab_str}@import #{node.uri.to_sass(@options)}"
164
+ else
165
+ str = "#{tab_str}@import #{node.uri}"
166
+ end
167
+ str << " #{interp_to_src(node.query)}" unless node.query.empty?
168
+ "#{str}#{semi}\n"
169
+ end
170
+
171
+ def visit_mixindef(node)
172
+ args =
173
+ if node.args.empty? && node.splat.nil?
174
+ ""
175
+ else
176
+ str = '('
177
+ str << node.args.map do |v, d|
178
+ if d
179
+ "#{v.to_sass(@options)}: #{d.to_sass(@options)}"
180
+ else
181
+ v.to_sass(@options)
182
+ end
183
+ end.join(", ")
184
+
185
+ if node.splat
186
+ str << ", " unless node.args.empty?
187
+ str << node.splat.to_sass(@options) << '...'
188
+ end
189
+
190
+ str << ')'
191
+ end
192
+
193
+ "#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
194
+ end
195
+
196
+ def visit_mixin(node)
197
+ arg_to_sass = lambda do |arg|
198
+ sass = arg.to_sass(@options)
199
+ sass = "(#{sass})" if arg.is_a?(Sass::Script::List) && arg.separator == :comma
200
+ sass
201
+ end
202
+
203
+ unless node.args.empty? && node.keywords.empty? && node.splat.nil?
204
+ args = node.args.map(&arg_to_sass).join(", ")
205
+ keywords = Sass::Util.hash_to_a(node.keywords).
206
+ map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"}.join(', ')
207
+ if node.splat
208
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
209
+ splat = "#{splat}#{arg_to_sass[node.splat]}..."
210
+ end
211
+ arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
212
+ end
213
+ "#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n"
214
+ end
215
+
216
+ def visit_content(node)
217
+ "#{tab_str}@content#{semi}\n"
218
+ end
219
+
220
+ def visit_prop(node)
221
+ res = tab_str + node.declaration(@options, @format)
222
+ return res + semi + "\n" if node.children.empty?
223
+ res + yield.rstrip + semi + "\n"
224
+ end
225
+
226
+ def visit_return(node)
227
+ "#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n"
228
+ end
229
+
230
+ def visit_rule(node)
231
+ if @format == :sass
232
+ name = selector_to_sass(node.rule)
233
+ name = "\\" + name if name[0] == ?:
234
+ name.gsub(/^/, tab_str) + yield
235
+ elsif @format == :scss
236
+ name = selector_to_scss(node.rule)
237
+ res = name + yield
238
+ if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent
239
+ res.slice!(-3..-1)
240
+ res << "\n" << tab_str << "}\n"
241
+ end
242
+ res
243
+ end
244
+ end
245
+
246
+ def visit_variable(node)
247
+ "#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}#{' !default' if node.guarded}#{semi}\n"
248
+ end
249
+
250
+ def visit_warn(node)
251
+ "#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n"
252
+ end
253
+
254
+ def visit_while(node)
255
+ "#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"
256
+ end
257
+
258
+ private
259
+
260
+ def interp_to_src(interp)
261
+ interp.map do |r|
262
+ next r if r.is_a?(String)
263
+ "\#{#{r.to_sass(@options)}}"
264
+ end.join
265
+ end
266
+
267
+ # Like interp_to_src, but removes the unnecessary `#{}` around the keys and
268
+ # values in media expressions.
269
+ def media_interp_to_src(interp)
270
+ Sass::Util.enum_with_index(interp).map do |r, i|
271
+ next r if r.is_a?(String)
272
+ before, after = interp[i-1], interp[i+1]
273
+ if before.is_a?(String) && after.is_a?(String) &&
274
+ ((before[-1] == ?( && after[0] == ?:) ||
275
+ (before =~ /:\s*/ && after[0] == ?)))
276
+ r.to_sass(@options)
277
+ else
278
+ "\#{#{r.to_sass(@options)}}"
279
+ end
280
+ end.join
281
+ end
282
+
283
+ def selector_to_src(sel)
284
+ @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
285
+ end
286
+
287
+ def selector_to_sass(sel)
288
+ sel.map do |r|
289
+ if r.is_a?(String)
290
+ r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
291
+ else
292
+ "\#{#{r.to_sass(@options)}}"
293
+ end
294
+ end.join
295
+ end
296
+
297
+ def selector_to_scss(sel)
298
+ interp_to_src(sel).gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
299
+ end
300
+
301
+ def semi
302
+ @format == :sass ? "" : ";"
303
+ end
304
+
305
+ def tab_str
306
+ @tab_chars * @tabs
307
+ end
308
+
309
+ def dasherize(s)
310
+ if @options[:dasherize]
311
+ s.gsub('_', '-')
312
+ else
313
+ s
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,229 @@
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
+ attr_reader :parent
13
+
14
+ def initialize
15
+ @parent_directives = []
16
+ @extends = Sass::Util::SubsetMap.new
17
+ end
18
+
19
+ # If an exception is raised, this adds proper metadata to the backtrace.
20
+ def visit(node)
21
+ super(node)
22
+ rescue Sass::SyntaxError => e
23
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
24
+ raise e
25
+ end
26
+
27
+ # Keeps track of the current parent node.
28
+ def visit_children(parent)
29
+ with_parent parent do
30
+ parent.children = super.flatten
31
+ parent
32
+ end
33
+ end
34
+
35
+ MERGEABLE_DIRECTIVES = [Sass::Tree::MediaNode]
36
+
37
+ # Runs a block of code with the current parent node
38
+ # replaced with the given node.
39
+ #
40
+ # @param parent [Tree::Node] The new parent for the duration of the block.
41
+ # @yield A block in which the parent is set to `parent`.
42
+ # @return [Object] The return value of the block.
43
+ def with_parent(parent)
44
+ if parent.is_a?(Sass::Tree::DirectiveNode)
45
+ if MERGEABLE_DIRECTIVES.any? {|klass| parent.is_a?(klass)}
46
+ old_parent_directive = @parent_directives.pop
47
+ end
48
+ @parent_directives.push parent
49
+ end
50
+
51
+ old_parent, @parent = @parent, parent
52
+ yield
53
+ ensure
54
+ @parent_directives.pop if parent.is_a?(Sass::Tree::DirectiveNode)
55
+ @parent_directives.push old_parent_directive if old_parent_directive
56
+ @parent = old_parent
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 = Sass::Util.extract!(node.children) do |c|
77
+ c.is_a?(Sass::Tree::DirectiveNode) && !c.is_a?(Sass::Tree::MediaNode) &&
78
+ c.resolved_value =~ /^@import /i
79
+ end
80
+ charset_and_index = Sass::Util.ruby1_8? &&
81
+ node.children.each_with_index.find {|c, _| c.is_a?(Sass::Tree::CharsetNode)}
82
+ if charset_and_index
83
+ index = charset_and_index.last
84
+ node.children = node.children[0..index] + imports + node.children[index+1..-1]
85
+ else
86
+ node.children = imports + node.children
87
+ end
88
+ end
89
+
90
+ return node, @extends
91
+ rescue Sass::SyntaxError => e
92
+ e.sass_template ||= node.template
93
+ raise e
94
+ end
95
+
96
+ # A simple struct wrapping up information about a single `@extend` instance. A
97
+ # single [ExtendNode] can have multiple Extends if either the parent node or
98
+ # the extended selector is a comma sequence.
99
+ #
100
+ # @attr extender [Sass::Selector::Sequence]
101
+ # The selector of the CSS rule containing the `@extend`.
102
+ # @attr target [Array<Sass::Selector::Simple>] The selector being `@extend`ed.
103
+ # @attr node [Sass::Tree::ExtendNode] The node that produced this extend.
104
+ # @attr directives [Array<Sass::Tree::DirectiveNode>]
105
+ # The directives containing the `@extend`.
106
+ # @attr result [Symbol]
107
+ # The result of this extend. One of `:not_found` (the target doesn't exist
108
+ # in the document), `:failed_to_unify` (the target exists but cannot be
109
+ # unified with the extender), or `:succeeded`.
110
+ Extend = Struct.new(:extender, :target, :node, :directives, :result)
111
+
112
+ # Registers an extension in the `@extends` subset map.
113
+ def visit_extend(node)
114
+ node.resolved_selector.members.each do |seq|
115
+ if seq.members.size > 1
116
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
117
+ end
118
+
119
+ sseq = seq.members.first
120
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
121
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
122
+ elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
123
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
124
+ end
125
+
126
+ sel = sseq.members
127
+ parent.resolved_rules.members.each do |member|
128
+ if !member.members.last.is_a?(Sass::Selector::SimpleSequence)
129
+ raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
130
+ end
131
+
132
+ @extends[sel] = Extend.new(member, sel, node, @parent_directives.dup, :not_found)
133
+ end
134
+ end
135
+
136
+ []
137
+ end
138
+
139
+ # Modifies exception backtraces to include the imported file.
140
+ def visit_import(node)
141
+ # Don't use #visit_children to avoid adding the import node to the list of parents.
142
+ node.children.map {|c| visit(c)}.flatten
143
+ rescue Sass::SyntaxError => e
144
+ e.modify_backtrace(:filename => node.children.first.filename)
145
+ e.add_backtrace(:filename => node.filename, :line => node.line)
146
+ raise e
147
+ end
148
+
149
+ # Bubbles the `@media` directive up through RuleNodes
150
+ # and merges it with other `@media` directives.
151
+ def visit_media(node)
152
+ yield unless bubble(node)
153
+ media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
154
+ node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
155
+ media = media.select {|n| n.resolved_query = n.resolved_query.merge(node.resolved_query)}
156
+ (node.children.empty? ? [] : [node]) + media
157
+ end
158
+
159
+ # Bubbles the `@supports` directive up through RuleNodes.
160
+ def visit_supports(node)
161
+ yield unless bubble(node)
162
+ node
163
+ end
164
+
165
+ # Asserts that all the traced children are valid in their new location.
166
+ def visit_trace(node)
167
+ # Don't use #visit_children to avoid adding the trace node to the list of parents.
168
+ node.children.map {|c| visit(c)}.flatten
169
+ rescue Sass::SyntaxError => e
170
+ e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
171
+ e.add_backtrace(:filename => node.filename, :line => node.line)
172
+ raise e
173
+ end
174
+
175
+ # Converts nested properties into flat properties
176
+ # and updates the indentation of the prop node based on the nesting level.
177
+ def visit_prop(node)
178
+ if parent.is_a?(Sass::Tree::PropNode)
179
+ node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
180
+ node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
181
+ end
182
+
183
+ yield
184
+
185
+ result = node.children.dup
186
+ if !node.resolved_value.empty? || node.children.empty?
187
+ node.send(:check!)
188
+ result.unshift(node)
189
+ end
190
+
191
+ result
192
+ end
193
+
194
+ # Resolves parent references and nested selectors,
195
+ # and updates the indentation of the rule node based on the nesting level.
196
+ def visit_rule(node)
197
+ parent_resolved_rules = parent.is_a?(Sass::Tree::RuleNode) ? parent.resolved_rules : nil
198
+ # It's possible for resolved_rules to be set if we've duplicated this node during @media bubbling
199
+ node.resolved_rules ||= node.parsed_rules.resolve_parent_refs(parent_resolved_rules)
200
+
201
+ yield
202
+
203
+ rules = node.children.select {|c| c.is_a?(Sass::Tree::RuleNode) || c.bubbles?}
204
+ props = node.children.reject {|c| c.is_a?(Sass::Tree::RuleNode) || c.bubbles? || c.invisible?}
205
+
206
+ unless props.empty?
207
+ node.children = props
208
+ rules.each {|r| r.tabs += 1} if node.style == :nested
209
+ rules.unshift(node)
210
+ end
211
+
212
+ rules.last.group_end = true unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty?
213
+
214
+ rules
215
+ end
216
+
217
+ private
218
+
219
+ def bubble(node)
220
+ return unless parent.is_a?(Sass::Tree::RuleNode)
221
+ new_rule = parent.dup
222
+ new_rule.children = node.children
223
+ node.children = with_parent(node) {Array(visit(new_rule))}
224
+ # If the last child is actually the end of the group,
225
+ # the parent's cssize will set it properly
226
+ node.children.last.group_end = false unless node.children.empty?
227
+ true
228
+ end
229
+ end