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,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
@@ -0,0 +1,68 @@
1
+ # A visitor for performing selector inheritance on a static CSS tree.
2
+ #
3
+ # Destructively modifies the tree.
4
+ class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
5
+ # Performs the given extensions on the static CSS tree based in `root`, then
6
+ # validates that all extends matched some selector.
7
+ #
8
+ # @param root [Tree::Node] The root node of the tree to visit.
9
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
10
+ # Sass::Tree::Visitors::Cssize::Extend}]
11
+ # The extensions to perform on this tree.
12
+ # @return [Object] The return value of \{#visit} for the root node.
13
+ def self.visit(root, extends)
14
+ return if extends.empty?
15
+ new(extends).send(:visit, root)
16
+ check_extends_fired! extends
17
+ end
18
+
19
+ protected
20
+
21
+ def initialize(extends)
22
+ @parent_directives = []
23
+ @extends = extends
24
+ end
25
+
26
+ # If an exception is raised, this adds proper metadata to the backtrace.
27
+ def visit(node)
28
+ super(node)
29
+ rescue Sass::SyntaxError => e
30
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
31
+ raise e
32
+ end
33
+
34
+ # Keeps track of the current parent directives.
35
+ def visit_children(parent)
36
+ @parent_directives.push parent if parent.is_a?(Sass::Tree::DirectiveNode)
37
+ super
38
+ ensure
39
+ @parent_directives.pop if parent.is_a?(Sass::Tree::DirectiveNode)
40
+ end
41
+
42
+ # Applies the extend to a single rule's selector.
43
+ def visit_rule(node)
44
+ node.resolved_rules = node.resolved_rules.do_extend(@extends, @parent_directives)
45
+ end
46
+
47
+ private
48
+
49
+ def self.check_extends_fired!(extends)
50
+ extends.each_value do |ex|
51
+ next if ex.result == :succeeded || ex.node.optional?
52
+ warn = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
53
+ reason =
54
+ if ex.result == :not_found
55
+ "The selector \"#{ex.target.join}\" was not found."
56
+ else
57
+ "No selectors matching \"#{ex.target.join}\" could be unified with \"#{ex.extender}\"."
58
+ end
59
+
60
+ Sass::Util.sass_warn <<WARN
61
+ WARNING on line #{ex.node.line}#{" of #{ex.node.filename}" if ex.node.filename}: #{warn}
62
+ #{reason}
63
+ This will be an error in future releases of Sass.
64
+ Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
65
+ WARN
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,446 @@
1
+ # A visitor for converting a dynamic Sass tree into a static Sass tree.
2
+ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @param environment [Sass::Environment] The lexical environment.
5
+ # @return [Tree::Node] The resulting tree of static nodes.
6
+ def self.visit(root, environment = Sass::Environment.new)
7
+ new(environment).send(:visit, root)
8
+ end
9
+
10
+ # @api private
11
+ def self.perform_arguments(callable, args, keywords, splat)
12
+ desc = "#{callable.type.capitalize} #{callable.name}"
13
+ downcase_desc = "#{callable.type} #{callable.name}"
14
+
15
+ begin
16
+ unless keywords.empty?
17
+ unknown_args = Sass::Util.array_minus(keywords.keys,
18
+ callable.args.map {|var| var.first.underscored_name})
19
+ if callable.splat && unknown_args.include?(callable.splat.underscored_name)
20
+ raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} cannot be used as a named argument.")
21
+ elsif unknown_args.any?
22
+ description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
23
+ raise Sass::SyntaxError.new("#{desc} doesn't have #{description} #{unknown_args.map {|name| "$#{name}"}.join ', '}.")
24
+ end
25
+ end
26
+ rescue Sass::SyntaxError => keyword_exception
27
+ end
28
+
29
+ # If there's no splat, raise the keyword exception immediately. The actual
30
+ # raising happens in the ensure clause at the end of this function.
31
+ return if keyword_exception && !callable.splat
32
+
33
+ if args.size > callable.args.size && !callable.splat
34
+ takes = callable.args.size
35
+ passed = args.size
36
+ raise Sass::SyntaxError.new(
37
+ "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
38
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
39
+ end
40
+
41
+ splat_sep = :comma
42
+ if splat
43
+ args += splat.to_a
44
+ splat_sep = splat.separator if splat.is_a?(Sass::Script::List)
45
+ # If the splat argument exists, there won't be any keywords passed in
46
+ # manually, so we can safely overwrite rather than merge here.
47
+ keywords = splat.keywords if splat.is_a?(Sass::Script::ArgList)
48
+ end
49
+
50
+ keywords = keywords.dup
51
+ env = Sass::Environment.new(callable.environment)
52
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
53
+ if value && keywords.include?(var.underscored_name)
54
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} both by position and by name.")
55
+ end
56
+
57
+ value ||= keywords.delete(var.underscored_name)
58
+ value ||= default && default.perform(env)
59
+ raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
60
+ env.set_local_var(var.name, value)
61
+ end
62
+
63
+ if callable.splat
64
+ rest = args[callable.args.length..-1]
65
+ arg_list = Sass::Script::ArgList.new(rest, keywords.dup, splat_sep)
66
+ arg_list.options = env.options
67
+ env.set_local_var(callable.splat.name, arg_list)
68
+ end
69
+
70
+ yield env
71
+ rescue Exception => e
72
+ ensure
73
+ # If there's a keyword exception, we don't want to throw it immediately,
74
+ # because the invalid keywords may be part of a glob argument that should be
75
+ # passed on to another function. So we only raise it if we reach the end of
76
+ # this function *and* the keywords attached to the argument list glob object
77
+ # haven't been accessed.
78
+ #
79
+ # The keyword exception takes precedence over any Sass errors, but not over
80
+ # non-Sass exceptions.
81
+ if keyword_exception &&
82
+ !(arg_list && arg_list.keywords_accessed) &&
83
+ (e.nil? || e.is_a?(Sass::SyntaxError))
84
+ raise keyword_exception
85
+ elsif e
86
+ raise e
87
+ end
88
+ end
89
+
90
+ protected
91
+
92
+ def initialize(env)
93
+ @environment = env
94
+ # Stack trace information, including mixin includes and imports.
95
+ @stack = []
96
+ end
97
+
98
+ # If an exception is raised, this adds proper metadata to the backtrace.
99
+ def visit(node)
100
+ super(node.dup)
101
+ rescue Sass::SyntaxError => e
102
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
103
+ raise e
104
+ end
105
+
106
+ # Keeps track of the current environment.
107
+ def visit_children(parent)
108
+ with_environment Sass::Environment.new(@environment, parent.options) do
109
+ parent.children = super.flatten
110
+ parent
111
+ end
112
+ end
113
+
114
+ # Runs a block of code with the current environment replaced with the given one.
115
+ #
116
+ # @param env [Sass::Environment] The new environment for the duration of the block.
117
+ # @yield A block in which the environment is set to `env`.
118
+ # @return [Object] The return value of the block.
119
+ def with_environment(env)
120
+ old_env, @environment = @environment, env
121
+ yield
122
+ ensure
123
+ @environment = old_env
124
+ end
125
+
126
+ # Sets the options on the environment if this is the top-level root.
127
+ def visit_root(node)
128
+ yield
129
+ rescue Sass::SyntaxError => e
130
+ e.sass_template ||= node.template
131
+ raise e
132
+ end
133
+
134
+ # Removes this node from the tree if it's a silent comment.
135
+ def visit_comment(node)
136
+ return [] if node.invisible?
137
+ node.resolved_value = run_interp_no_strip(node.value)
138
+ node.resolved_value.gsub!(/\\([\\#])/, '\1')
139
+ node
140
+ end
141
+
142
+ # Prints the expression to STDERR.
143
+ def visit_debug(node)
144
+ res = node.expr.perform(@environment)
145
+ res = res.value if res.is_a?(Sass::Script::String)
146
+ if node.filename
147
+ Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}"
148
+ else
149
+ Sass::Util.sass_warn "Line #{node.line} DEBUG: #{res}"
150
+ end
151
+ []
152
+ end
153
+
154
+ # Runs the child nodes once for each value in the list.
155
+ def visit_each(node)
156
+ list = node.list.perform(@environment)
157
+
158
+ with_environment Sass::Environment.new(@environment) do
159
+ list.to_a.map do |v|
160
+ @environment.set_local_var(node.var, v)
161
+ node.children.map {|c| visit(c)}
162
+ end.flatten
163
+ end
164
+ end
165
+
166
+ # Runs SassScript interpolation in the selector,
167
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
168
+ def visit_extend(node)
169
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.selector), node.filename, node.line)
170
+ node.resolved_selector = parser.parse_selector
171
+ node
172
+ end
173
+
174
+ # Runs the child nodes once for each time through the loop, varying the variable each time.
175
+ def visit_for(node)
176
+ from = node.from.perform(@environment)
177
+ to = node.to.perform(@environment)
178
+ from.assert_int!
179
+ to.assert_int!
180
+
181
+ to = to.coerce(from.numerator_units, from.denominator_units)
182
+ range = Range.new(from.to_i, to.to_i, node.exclusive)
183
+
184
+ with_environment Sass::Environment.new(@environment) do
185
+ range.map do |i|
186
+ @environment.set_local_var(node.var,
187
+ Sass::Script::Number.new(i, from.numerator_units, from.denominator_units))
188
+ node.children.map {|c| visit(c)}
189
+ end.flatten
190
+ end
191
+ end
192
+
193
+ # Loads the function into the environment.
194
+ def visit_function(node)
195
+ env = Sass::Environment.new(@environment, node.options)
196
+ @environment.set_local_function(node.name,
197
+ Sass::Callable.new(node.name, node.args, node.splat, env, node.children, !:has_content, "function"))
198
+ []
199
+ end
200
+
201
+ # Runs the child nodes if the conditional expression is true;
202
+ # otherwise, tries the else nodes.
203
+ def visit_if(node)
204
+ if node.expr.nil? || node.expr.perform(@environment).to_bool
205
+ yield
206
+ node.children
207
+ elsif node.else
208
+ visit(node.else)
209
+ else
210
+ []
211
+ end
212
+ end
213
+
214
+ # Returns a static DirectiveNode if this is importing a CSS file,
215
+ # or parses and includes the imported Sass file.
216
+ def visit_import(node)
217
+ if path = node.css_import?
218
+ return Sass::Tree::CssImportNode.resolved("url(#{path})")
219
+ end
220
+ file = node.imported_file
221
+ handle_import_loop!(node) if @stack.any? {|e| e[:filename] == file.options[:filename]}
222
+
223
+ begin
224
+ @stack.push(:filename => node.filename, :line => node.line)
225
+ root = file.to_tree
226
+ Sass::Tree::Visitors::CheckNesting.visit(root)
227
+ node.children = root.children.map {|c| visit(c)}.flatten
228
+ node
229
+ rescue Sass::SyntaxError => e
230
+ e.modify_backtrace(:filename => node.imported_file.options[:filename])
231
+ e.add_backtrace(:filename => node.filename, :line => node.line)
232
+ raise e
233
+ end
234
+ ensure
235
+ @stack.pop unless path
236
+ end
237
+
238
+ # Loads a mixin into the environment.
239
+ def visit_mixindef(node)
240
+ env = Sass::Environment.new(@environment, node.options)
241
+ @environment.set_local_mixin(node.name,
242
+ Sass::Callable.new(node.name, node.args, node.splat, env, node.children, node.has_content, "mixin"))
243
+ []
244
+ end
245
+
246
+ # Runs a mixin.
247
+ def visit_mixin(node)
248
+ include_loop = true
249
+ handle_include_loop!(node) if @stack.any? {|e| e[:name] == node.name}
250
+ include_loop = false
251
+
252
+ @stack.push(:filename => node.filename, :line => node.line, :name => node.name)
253
+ raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
254
+
255
+ if node.children.any? && !mixin.has_content
256
+ raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
257
+ end
258
+
259
+ args = node.args.map {|a| a.perform(@environment)}
260
+ keywords = Sass::Util.map_hash(node.keywords) {|k, v| [k, v.perform(@environment)]}
261
+ splat = node.splat.perform(@environment) if node.splat
262
+
263
+ self.class.perform_arguments(mixin, args, keywords, splat) do |env|
264
+ env.caller = Sass::Environment.new(@environment)
265
+ env.content = node.children if node.has_children
266
+
267
+ trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
268
+ with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
269
+ trace_node
270
+ end
271
+ rescue Sass::SyntaxError => e
272
+ unless include_loop
273
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
274
+ e.add_backtrace(:line => node.line)
275
+ end
276
+ raise e
277
+ ensure
278
+ @stack.pop unless include_loop
279
+ end
280
+
281
+ def visit_content(node)
282
+ return [] unless content = @environment.content
283
+ @stack.push(:filename => node.filename, :line => node.line, :name => '@content')
284
+ trace_node = Sass::Tree::TraceNode.from_node('@content', node)
285
+ with_environment(@environment.caller) {trace_node.children = content.map {|c| visit(c.dup)}.flatten}
286
+ trace_node
287
+ rescue Sass::SyntaxError => e
288
+ e.modify_backtrace(:mixin => '@content', :line => node.line)
289
+ e.add_backtrace(:line => node.line)
290
+ raise e
291
+ ensure
292
+ @stack.pop if content
293
+ end
294
+
295
+ # Runs any SassScript that may be embedded in a property.
296
+ def visit_prop(node)
297
+ node.resolved_name = run_interp(node.name)
298
+ val = node.value.perform(@environment)
299
+ node.resolved_value = val.to_s
300
+ yield
301
+ end
302
+
303
+ # Returns the value of the expression.
304
+ def visit_return(node)
305
+ throw :_sass_return, node.expr.perform(@environment)
306
+ end
307
+
308
+ # Runs SassScript interpolation in the selector,
309
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
310
+ def visit_rule(node)
311
+ rule = node.rule
312
+ rule = rule.map {|e| e.is_a?(String) && e != ' ' ? e.strip : e} if node.style == :compressed
313
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
314
+ node.parsed_rules ||= parser.parse_selector
315
+ if node.options[:trace_selectors]
316
+ @stack.push(:filename => node.filename, :line => node.line)
317
+ node.stack_trace = stack_trace
318
+ @stack.pop
319
+ end
320
+ yield
321
+ end
322
+
323
+ # Loads the new variable value into the environment.
324
+ def visit_variable(node)
325
+ var = @environment.var(node.name)
326
+ return [] if node.guarded && var && !var.null?
327
+ val = node.expr.perform(@environment)
328
+ @environment.set_var(node.name, val)
329
+ []
330
+ end
331
+
332
+ # Prints the expression to STDERR with a stylesheet trace.
333
+ def visit_warn(node)
334
+ @stack.push(:filename => node.filename, :line => node.line)
335
+ res = node.expr.perform(@environment)
336
+ res = res.value if res.is_a?(Sass::Script::String)
337
+ msg = "WARNING: #{res}\n "
338
+ msg << stack_trace.join("\n ") << "\n"
339
+ Sass::Util.sass_warn msg
340
+ []
341
+ ensure
342
+ @stack.pop
343
+ end
344
+
345
+ # Runs the child nodes until the continuation expression becomes false.
346
+ def visit_while(node)
347
+ children = []
348
+ with_environment Sass::Environment.new(@environment) do
349
+ children += node.children.map {|c| visit(c)} while node.expr.perform(@environment).to_bool
350
+ end
351
+ children.flatten
352
+ end
353
+
354
+ def visit_directive(node)
355
+ node.resolved_value = run_interp(node.value)
356
+ yield
357
+ end
358
+
359
+ def visit_media(node)
360
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, node.line)
361
+ node.resolved_query ||= parser.parse_media_query_list
362
+ yield
363
+ end
364
+
365
+ def visit_supports(node)
366
+ node.condition = node.condition.deep_copy
367
+ node.condition.perform(@environment)
368
+ yield
369
+ end
370
+
371
+ def visit_cssimport(node)
372
+ node.resolved_uri = run_interp([node.uri])
373
+ if node.query
374
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, node.line)
375
+ node.resolved_query ||= parser.parse_media_query_list
376
+ end
377
+ yield
378
+ end
379
+
380
+ private
381
+
382
+ def stack_trace
383
+ trace = []
384
+ stack = @stack.map {|e| e.dup}.reverse
385
+ stack.each_cons(2) {|(e1, e2)| e1[:caller] = e2[:name]; [e1, e2]}
386
+ stack.each_with_index do |entry, i|
387
+ msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
388
+ msg << " of #{entry[:filename] || "an unknown file"}"
389
+ msg << ", in `#{entry[:caller]}'" if entry[:caller]
390
+ trace << msg
391
+ end
392
+ trace
393
+ end
394
+
395
+ def run_interp_no_strip(text)
396
+ text.map do |r|
397
+ next r if r.is_a?(String)
398
+ val = r.perform(@environment)
399
+ # Interpolated strings should never render with quotes
400
+ next val.value if val.is_a?(Sass::Script::String)
401
+ val.to_s
402
+ end.join
403
+ end
404
+
405
+ def run_interp(text)
406
+ run_interp_no_strip(text).strip
407
+ end
408
+
409
+ def handle_include_loop!(node)
410
+ msg = "An @include loop has been found:"
411
+ content_count = 0
412
+ mixins = @stack.reverse.map {|s| s[:name]}.compact.select do |s|
413
+ if s == '@content'
414
+ content_count += 1
415
+ false
416
+ elsif content_count > 0
417
+ content_count -= 1
418
+ false
419
+ else
420
+ true
421
+ end
422
+ end
423
+
424
+ return unless mixins.include?(node.name)
425
+ raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
426
+
427
+ msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
428
+ " #{m1} includes #{m2}"
429
+ end.join("\n")
430
+ raise Sass::SyntaxError.new(msg)
431
+ end
432
+
433
+ def handle_import_loop!(node)
434
+ msg = "An @import loop has been found:"
435
+ files = @stack.map {|s| s[:filename]}.compact
436
+ if node.filename == node.imported_file.options[:filename]
437
+ raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
438
+ end
439
+
440
+ files << node.filename << node.imported_file.options[:filename]
441
+ msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
442
+ " #{m1} imports #{m2}"
443
+ end.join("\n")
444
+ raise Sass::SyntaxError.new(msg)
445
+ end
446
+ end