xass 0.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 (242) 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 +201 -0
  6. data/Rakefile +349 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/push +13 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/update_watch.rb +13 -0
  14. data/init.rb +18 -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 +64 -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/cache_stores.rb +15 -0
  21. data/lib/sass/callbacks.rb +66 -0
  22. data/lib/sass/css.rb +409 -0
  23. data/lib/sass/engine.rb +930 -0
  24. data/lib/sass/environment.rb +101 -0
  25. data/lib/sass/error.rb +201 -0
  26. data/lib/sass/exec.rb +707 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +186 -0
  29. data/lib/sass/importers.rb +22 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/logger.rb +15 -0
  33. data/lib/sass/media.rb +213 -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 +199 -0
  41. data/lib/sass/plugin.rb +133 -0
  42. data/lib/sass/railtie.rb +10 -0
  43. data/lib/sass/repl.rb +57 -0
  44. data/lib/sass/root.rb +7 -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 +245 -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 +345 -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 +502 -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/script.rb +39 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1180 -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/scss.rb +16 -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 +215 -0
  78. data/lib/sass/selector.rb +452 -0
  79. data/lib/sass/shared.rb +76 -0
  80. data/lib/sass/supports.rb +229 -0
  81. data/lib/sass/tree/charset_node.rb +22 -0
  82. data/lib/sass/tree/comment_node.rb +82 -0
  83. data/lib/sass/tree/content_node.rb +9 -0
  84. data/lib/sass/tree/css_import_node.rb +60 -0
  85. data/lib/sass/tree/debug_node.rb +18 -0
  86. data/lib/sass/tree/directive_node.rb +42 -0
  87. data/lib/sass/tree/each_node.rb +24 -0
  88. data/lib/sass/tree/extend_node.rb +36 -0
  89. data/lib/sass/tree/for_node.rb +36 -0
  90. data/lib/sass/tree/function_node.rb +34 -0
  91. data/lib/sass/tree/if_node.rb +52 -0
  92. data/lib/sass/tree/import_node.rb +75 -0
  93. data/lib/sass/tree/media_node.rb +58 -0
  94. data/lib/sass/tree/mixin_def_node.rb +38 -0
  95. data/lib/sass/tree/mixin_node.rb +39 -0
  96. data/lib/sass/tree/node.rb +196 -0
  97. data/lib/sass/tree/prop_node.rb +152 -0
  98. data/lib/sass/tree/return_node.rb +18 -0
  99. data/lib/sass/tree/root_node.rb +78 -0
  100. data/lib/sass/tree/rule_node.rb +132 -0
  101. data/lib/sass/tree/supports_node.rb +51 -0
  102. data/lib/sass/tree/trace_node.rb +32 -0
  103. data/lib/sass/tree/variable_node.rb +30 -0
  104. data/lib/sass/tree/visitors/base.rb +75 -0
  105. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  106. data/lib/sass/tree/visitors/convert.rb +316 -0
  107. data/lib/sass/tree/visitors/cssize.rb +241 -0
  108. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  109. data/lib/sass/tree/visitors/extend.rb +68 -0
  110. data/lib/sass/tree/visitors/perform.rb +446 -0
  111. data/lib/sass/tree/visitors/set_options.rb +125 -0
  112. data/lib/sass/tree/visitors/to_css.rb +228 -0
  113. data/lib/sass/tree/warn_node.rb +18 -0
  114. data/lib/sass/tree/while_node.rb +18 -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/util.rb +948 -0
  119. data/lib/sass/version.rb +126 -0
  120. data/lib/sass.rb +95 -0
  121. data/rails/init.rb +1 -0
  122. data/test/Gemfile +3 -0
  123. data/test/Gemfile.lock +10 -0
  124. data/test/sass/cache_test.rb +89 -0
  125. data/test/sass/callbacks_test.rb +61 -0
  126. data/test/sass/conversion_test.rb +1760 -0
  127. data/test/sass/css2sass_test.rb +458 -0
  128. data/test/sass/data/hsl-rgb.txt +319 -0
  129. data/test/sass/engine_test.rb +3244 -0
  130. data/test/sass/exec_test.rb +86 -0
  131. data/test/sass/extend_test.rb +1482 -0
  132. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  133. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  134. data/test/sass/functions_test.rb +1139 -0
  135. data/test/sass/importer_test.rb +192 -0
  136. data/test/sass/logger_test.rb +58 -0
  137. data/test/sass/mock_importer.rb +49 -0
  138. data/test/sass/more_results/more1.css +9 -0
  139. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  140. data/test/sass/more_results/more_import.css +29 -0
  141. data/test/sass/more_templates/_more_partial.sass +2 -0
  142. data/test/sass/more_templates/more1.sass +23 -0
  143. data/test/sass/more_templates/more_import.sass +11 -0
  144. data/test/sass/plugin_test.rb +564 -0
  145. data/test/sass/results/alt.css +4 -0
  146. data/test/sass/results/basic.css +9 -0
  147. data/test/sass/results/cached_import_option.css +3 -0
  148. data/test/sass/results/compact.css +5 -0
  149. data/test/sass/results/complex.css +86 -0
  150. data/test/sass/results/compressed.css +1 -0
  151. data/test/sass/results/expanded.css +19 -0
  152. data/test/sass/results/filename_fn.css +3 -0
  153. data/test/sass/results/if.css +3 -0
  154. data/test/sass/results/import.css +31 -0
  155. data/test/sass/results/import_charset.css +5 -0
  156. data/test/sass/results/import_charset_1_8.css +5 -0
  157. data/test/sass/results/import_charset_ibm866.css +5 -0
  158. data/test/sass/results/import_content.css +1 -0
  159. data/test/sass/results/line_numbers.css +49 -0
  160. data/test/sass/results/mixins.css +95 -0
  161. data/test/sass/results/multiline.css +24 -0
  162. data/test/sass/results/nested.css +22 -0
  163. data/test/sass/results/options.css +1 -0
  164. data/test/sass/results/parent_ref.css +13 -0
  165. data/test/sass/results/script.css +16 -0
  166. data/test/sass/results/scss_import.css +31 -0
  167. data/test/sass/results/scss_importee.css +2 -0
  168. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  169. data/test/sass/results/subdir/subdir.css +3 -0
  170. data/test/sass/results/units.css +11 -0
  171. data/test/sass/results/warn.css +0 -0
  172. data/test/sass/results/warn_imported.css +0 -0
  173. data/test/sass/script_conversion_test.rb +299 -0
  174. data/test/sass/script_test.rb +622 -0
  175. data/test/sass/scss/css_test.rb +1100 -0
  176. data/test/sass/scss/rx_test.rb +156 -0
  177. data/test/sass/scss/scss_test.rb +2106 -0
  178. data/test/sass/scss/test_helper.rb +37 -0
  179. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  180. data/test/sass/templates/_double_import_loop2.sass +1 -0
  181. data/test/sass/templates/_filename_fn_import.scss +11 -0
  182. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  183. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  184. data/test/sass/templates/_imported_content.sass +3 -0
  185. data/test/sass/templates/_partial.sass +2 -0
  186. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  187. data/test/sass/templates/alt.sass +16 -0
  188. data/test/sass/templates/basic.sass +23 -0
  189. data/test/sass/templates/bork1.sass +2 -0
  190. data/test/sass/templates/bork2.sass +2 -0
  191. data/test/sass/templates/bork3.sass +2 -0
  192. data/test/sass/templates/bork4.sass +2 -0
  193. data/test/sass/templates/bork5.sass +3 -0
  194. data/test/sass/templates/cached_import_option.scss +3 -0
  195. data/test/sass/templates/compact.sass +17 -0
  196. data/test/sass/templates/complex.sass +305 -0
  197. data/test/sass/templates/compressed.sass +15 -0
  198. data/test/sass/templates/double_import_loop1.sass +1 -0
  199. data/test/sass/templates/expanded.sass +17 -0
  200. data/test/sass/templates/filename_fn.scss +18 -0
  201. data/test/sass/templates/if.sass +11 -0
  202. data/test/sass/templates/import.sass +12 -0
  203. data/test/sass/templates/import_charset.sass +9 -0
  204. data/test/sass/templates/import_charset_1_8.sass +6 -0
  205. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  206. data/test/sass/templates/import_content.sass +4 -0
  207. data/test/sass/templates/importee.less +2 -0
  208. data/test/sass/templates/importee.sass +19 -0
  209. data/test/sass/templates/line_numbers.sass +13 -0
  210. data/test/sass/templates/mixin_bork.sass +5 -0
  211. data/test/sass/templates/mixins.sass +76 -0
  212. data/test/sass/templates/multiline.sass +20 -0
  213. data/test/sass/templates/nested.sass +25 -0
  214. data/test/sass/templates/nested_bork1.sass +2 -0
  215. data/test/sass/templates/nested_bork2.sass +2 -0
  216. data/test/sass/templates/nested_bork3.sass +2 -0
  217. data/test/sass/templates/nested_bork4.sass +2 -0
  218. data/test/sass/templates/nested_import.sass +2 -0
  219. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  220. data/test/sass/templates/options.sass +2 -0
  221. data/test/sass/templates/parent_ref.sass +25 -0
  222. data/test/sass/templates/same_name_different_ext.sass +2 -0
  223. data/test/sass/templates/same_name_different_ext.scss +1 -0
  224. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  225. data/test/sass/templates/script.sass +101 -0
  226. data/test/sass/templates/scss_import.scss +11 -0
  227. data/test/sass/templates/scss_importee.scss +1 -0
  228. data/test/sass/templates/single_import_loop.sass +1 -0
  229. data/test/sass/templates/subdir/import_up1.scss +1 -0
  230. data/test/sass/templates/subdir/import_up2.scss +1 -0
  231. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  232. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  233. data/test/sass/templates/subdir/subdir.sass +6 -0
  234. data/test/sass/templates/units.sass +11 -0
  235. data/test/sass/templates/warn.sass +3 -0
  236. data/test/sass/templates/warn_imported.sass +4 -0
  237. data/test/sass/test_helper.rb +8 -0
  238. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  239. data/test/sass/util/subset_map_test.rb +91 -0
  240. data/test/sass/util_test.rb +382 -0
  241. data/test/test_helper.rb +80 -0
  242. metadata +354 -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,241 @@
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_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
+ node.resolved_selector.members.each do |seq|
127
+ if seq.members.size > 1
128
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
129
+ end
130
+
131
+ sseq = seq.members.first
132
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
133
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
134
+ elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
135
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
136
+ end
137
+
138
+ sel = sseq.members
139
+ parent.resolved_rules.members.each do |member|
140
+ if !member.members.last.is_a?(Sass::Selector::SimpleSequence)
141
+ raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
142
+ end
143
+
144
+ @extends[sel] = Extend.new(member, sel, node, @parent_directives.dup, :not_found)
145
+ end
146
+ end
147
+
148
+ []
149
+ end
150
+
151
+ # Modifies exception backtraces to include the imported file.
152
+ def visit_import(node)
153
+ # Don't use #visit_children to avoid adding the import node to the list of parents.
154
+ node.children.map {|c| visit(c)}.flatten
155
+ rescue Sass::SyntaxError => e
156
+ e.modify_backtrace(:filename => node.children.first.filename)
157
+ e.add_backtrace(:filename => node.filename, :line => node.line)
158
+ raise e
159
+ end
160
+
161
+ # Bubbles the `@media` directive up through RuleNodes
162
+ # and merges it with other `@media` directives.
163
+ def visit_media(node)
164
+ yield unless bubble(node)
165
+ media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
166
+ node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
167
+ media = media.select {|n| n.resolved_query = n.resolved_query.merge(node.resolved_query)}
168
+ (node.children.empty? ? [] : [node]) + media
169
+ end
170
+
171
+ # Bubbles the `@supports` directive up through RuleNodes.
172
+ def visit_supports(node)
173
+ yield unless bubble(node)
174
+ node
175
+ end
176
+
177
+ # Asserts that all the traced children are valid in their new location.
178
+ def visit_trace(node)
179
+ # Don't use #visit_children to avoid adding the trace node to the list of parents.
180
+ node.children.map {|c| visit(c)}.flatten
181
+ rescue Sass::SyntaxError => e
182
+ e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
183
+ e.add_backtrace(:filename => node.filename, :line => node.line)
184
+ raise e
185
+ end
186
+
187
+ # Converts nested properties into flat properties
188
+ # and updates the indentation of the prop node based on the nesting level.
189
+ def visit_prop(node)
190
+ if parent.is_a?(Sass::Tree::PropNode)
191
+ node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
192
+ node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
193
+ end
194
+
195
+ yield
196
+
197
+ result = node.children.dup
198
+ if !node.resolved_value.empty? || node.children.empty?
199
+ node.send(:check!)
200
+ result.unshift(node)
201
+ end
202
+
203
+ result
204
+ end
205
+
206
+ # Resolves parent references and nested selectors,
207
+ # and updates the indentation of the rule node based on the nesting level.
208
+ def visit_rule(node)
209
+ parent_resolved_rules = parent.is_a?(Sass::Tree::RuleNode) ? parent.resolved_rules : nil
210
+ # It's possible for resolved_rules to be set if we've duplicated this node during @media bubbling
211
+ node.resolved_rules ||= node.parsed_rules.resolve_parent_refs(parent_resolved_rules)
212
+
213
+ yield
214
+
215
+ rules = node.children.select {|c| c.is_a?(Sass::Tree::RuleNode) || c.bubbles?}
216
+ props = node.children.reject {|c| c.is_a?(Sass::Tree::RuleNode) || c.bubbles? || c.invisible?}
217
+
218
+ unless props.empty?
219
+ node.children = props
220
+ rules.each {|r| r.tabs += 1} if node.style == :nested
221
+ rules.unshift(node)
222
+ end
223
+
224
+ rules.last.group_end = true unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty?
225
+
226
+ rules
227
+ end
228
+
229
+ private
230
+
231
+ def bubble(node)
232
+ return unless parent.is_a?(Sass::Tree::RuleNode)
233
+ new_rule = parent.dup
234
+ new_rule.children = node.children
235
+ node.children = with_parent(node) {Array(visit(new_rule))}
236
+ # If the last child is actually the end of the group,
237
+ # the parent's cssize will set it properly
238
+ node.children.last.group_end = false unless node.children.empty?
239
+ true
240
+ end
241
+ end
@@ -0,0 +1,102 @@
1
+ # A visitor for copying the full structure of a Sass tree.
2
+ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
3
+ protected
4
+
5
+ def visit(node)
6
+ super(node.dup)
7
+ end
8
+
9
+ def visit_children(parent)
10
+ parent.children = parent.children.map {|c| visit(c)}
11
+ parent
12
+ end
13
+
14
+ def visit_debug(node)
15
+ node.expr = node.expr.deep_copy
16
+ yield
17
+ end
18
+
19
+ def visit_each(node)
20
+ node.list = node.list.deep_copy
21
+ yield
22
+ end
23
+
24
+ def visit_extend(node)
25
+ node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
26
+ yield
27
+ end
28
+
29
+ def visit_for(node)
30
+ node.from = node.from.deep_copy
31
+ node.to = node.to.deep_copy
32
+ yield
33
+ end
34
+
35
+ def visit_function(node)
36
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
37
+ yield
38
+ end
39
+
40
+ def visit_if(node)
41
+ node.expr = node.expr.deep_copy if node.expr
42
+ node.else = visit(node.else) if node.else
43
+ yield
44
+ end
45
+
46
+ def visit_mixindef(node)
47
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
48
+ yield
49
+ end
50
+
51
+ def visit_mixin(node)
52
+ node.args = node.args.map {|a| a.deep_copy}
53
+ node.keywords = Hash[node.keywords.map {|k, v| [k, v.deep_copy]}]
54
+ yield
55
+ end
56
+
57
+ def visit_prop(node)
58
+ node.name = node.name.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
59
+ node.value = node.value.deep_copy
60
+ yield
61
+ end
62
+
63
+ def visit_return(node)
64
+ node.expr = node.expr.deep_copy
65
+ yield
66
+ end
67
+
68
+ def visit_rule(node)
69
+ node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
70
+ yield
71
+ end
72
+
73
+ def visit_variable(node)
74
+ node.expr = node.expr.deep_copy
75
+ yield
76
+ end
77
+
78
+ def visit_warn(node)
79
+ node.expr = node.expr.deep_copy
80
+ yield
81
+ end
82
+
83
+ def visit_while(node)
84
+ node.expr = node.expr.deep_copy
85
+ yield
86
+ end
87
+
88
+ def visit_directive(node)
89
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
90
+ yield
91
+ end
92
+
93
+ def visit_media(node)
94
+ node.query = node.query.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
95
+ yield
96
+ end
97
+
98
+ def visit_supports(node)
99
+ node.condition = node.condition.deep_copy
100
+ yield
101
+ end
102
+ end