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,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
@@ -0,0 +1,125 @@
1
+ # A visitor for setting options on the Sass tree
2
+ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @param options [{Symbol => Object}] The options has to set.
5
+ def self.visit(root, options); new(options).send(:visit, root); end
6
+
7
+ protected
8
+
9
+ def initialize(options)
10
+ @options = options
11
+ end
12
+
13
+ def visit(node)
14
+ node.instance_variable_set('@options', @options)
15
+ super
16
+ end
17
+
18
+ def visit_debug(node)
19
+ node.expr.options = @options
20
+ yield
21
+ end
22
+
23
+ def visit_each(node)
24
+ node.list.options = @options
25
+ yield
26
+ end
27
+
28
+ def visit_extend(node)
29
+ node.selector.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
30
+ yield
31
+ end
32
+
33
+ def visit_for(node)
34
+ node.from.options = @options
35
+ node.to.options = @options
36
+ yield
37
+ end
38
+
39
+ def visit_function(node)
40
+ node.args.each do |k, v|
41
+ k.options = @options
42
+ v.options = @options if v
43
+ end
44
+ yield
45
+ end
46
+
47
+ def visit_if(node)
48
+ node.expr.options = @options if node.expr
49
+ visit(node.else) if node.else
50
+ yield
51
+ end
52
+
53
+ def visit_import(node)
54
+ # We have no good way of propagating the new options through an Engine
55
+ # instance, so we just null it out. This also lets us avoid caching an
56
+ # imported Engine along with the importing source tree.
57
+ node.imported_file = nil
58
+ yield
59
+ end
60
+
61
+ def visit_mixindef(node)
62
+ node.args.each do |k, v|
63
+ k.options = @options
64
+ v.options = @options if v
65
+ end
66
+ yield
67
+ end
68
+
69
+ def visit_mixin(node)
70
+ node.args.each {|a| a.options = @options}
71
+ node.keywords.each {|k, v| v.options = @options}
72
+ yield
73
+ end
74
+
75
+ def visit_prop(node)
76
+ node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
77
+ node.value.options = @options
78
+ yield
79
+ end
80
+
81
+ def visit_return(node)
82
+ node.expr.options = @options
83
+ yield
84
+ end
85
+
86
+ def visit_rule(node)
87
+ node.rule.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
88
+ yield
89
+ end
90
+
91
+ def visit_variable(node)
92
+ node.expr.options = @options
93
+ yield
94
+ end
95
+
96
+ def visit_warn(node)
97
+ node.expr.options = @options
98
+ yield
99
+ end
100
+
101
+ def visit_while(node)
102
+ node.expr.options = @options
103
+ yield
104
+ end
105
+
106
+ def visit_directive(node)
107
+ node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
108
+ yield
109
+ end
110
+
111
+ def visit_media(node)
112
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
113
+ yield
114
+ end
115
+
116
+ def visit_cssimport(node)
117
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)} if node.query
118
+ yield
119
+ end
120
+
121
+ def visit_supports(node)
122
+ node.condition.options = @options
123
+ yield
124
+ end
125
+ end