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.
- checksums.yaml +7 -0
- data/.yardopts +11 -0
- data/CONTRIBUTING +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +201 -0
- data/Rakefile +349 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/push +13 -0
- data/bin/sass +13 -0
- data/bin/sass-convert +12 -0
- data/bin/scss +13 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +18 -0
- data/lib/sass/cache_stores/base.rb +88 -0
- data/lib/sass/cache_stores/chain.rb +33 -0
- data/lib/sass/cache_stores/filesystem.rb +64 -0
- data/lib/sass/cache_stores/memory.rb +47 -0
- data/lib/sass/cache_stores/null.rb +25 -0
- data/lib/sass/cache_stores.rb +15 -0
- data/lib/sass/callbacks.rb +66 -0
- data/lib/sass/css.rb +409 -0
- data/lib/sass/engine.rb +930 -0
- data/lib/sass/environment.rb +101 -0
- data/lib/sass/error.rb +201 -0
- data/lib/sass/exec.rb +707 -0
- data/lib/sass/importers/base.rb +139 -0
- data/lib/sass/importers/filesystem.rb +186 -0
- data/lib/sass/importers.rb +22 -0
- data/lib/sass/logger/base.rb +32 -0
- data/lib/sass/logger/log_level.rb +49 -0
- data/lib/sass/logger.rb +15 -0
- data/lib/sass/media.rb +213 -0
- data/lib/sass/plugin/compiler.rb +406 -0
- data/lib/sass/plugin/configuration.rb +123 -0
- data/lib/sass/plugin/generic.rb +15 -0
- data/lib/sass/plugin/merb.rb +48 -0
- data/lib/sass/plugin/rack.rb +60 -0
- data/lib/sass/plugin/rails.rb +47 -0
- data/lib/sass/plugin/staleness_checker.rb +199 -0
- data/lib/sass/plugin.rb +133 -0
- data/lib/sass/railtie.rb +10 -0
- data/lib/sass/repl.rb +57 -0
- data/lib/sass/root.rb +7 -0
- data/lib/sass/script/arg_list.rb +52 -0
- data/lib/sass/script/bool.rb +18 -0
- data/lib/sass/script/color.rb +606 -0
- data/lib/sass/script/css_lexer.rb +29 -0
- data/lib/sass/script/css_parser.rb +31 -0
- data/lib/sass/script/funcall.rb +245 -0
- data/lib/sass/script/functions.rb +1543 -0
- data/lib/sass/script/interpolation.rb +79 -0
- data/lib/sass/script/lexer.rb +345 -0
- data/lib/sass/script/list.rb +85 -0
- data/lib/sass/script/literal.rb +221 -0
- data/lib/sass/script/node.rb +99 -0
- data/lib/sass/script/null.rb +37 -0
- data/lib/sass/script/number.rb +453 -0
- data/lib/sass/script/operation.rb +110 -0
- data/lib/sass/script/parser.rb +502 -0
- data/lib/sass/script/string.rb +51 -0
- data/lib/sass/script/string_interpolation.rb +103 -0
- data/lib/sass/script/unary_operation.rb +69 -0
- data/lib/sass/script/variable.rb +58 -0
- data/lib/sass/script.rb +39 -0
- data/lib/sass/scss/css_parser.rb +36 -0
- data/lib/sass/scss/parser.rb +1180 -0
- data/lib/sass/scss/rx.rb +133 -0
- data/lib/sass/scss/script_lexer.rb +15 -0
- data/lib/sass/scss/script_parser.rb +25 -0
- data/lib/sass/scss/static_parser.rb +54 -0
- data/lib/sass/scss.rb +16 -0
- data/lib/sass/selector/abstract_sequence.rb +94 -0
- data/lib/sass/selector/comma_sequence.rb +92 -0
- data/lib/sass/selector/sequence.rb +507 -0
- data/lib/sass/selector/simple.rb +119 -0
- data/lib/sass/selector/simple_sequence.rb +215 -0
- data/lib/sass/selector.rb +452 -0
- data/lib/sass/shared.rb +76 -0
- data/lib/sass/supports.rb +229 -0
- data/lib/sass/tree/charset_node.rb +22 -0
- data/lib/sass/tree/comment_node.rb +82 -0
- data/lib/sass/tree/content_node.rb +9 -0
- data/lib/sass/tree/css_import_node.rb +60 -0
- data/lib/sass/tree/debug_node.rb +18 -0
- data/lib/sass/tree/directive_node.rb +42 -0
- data/lib/sass/tree/each_node.rb +24 -0
- data/lib/sass/tree/extend_node.rb +36 -0
- data/lib/sass/tree/for_node.rb +36 -0
- data/lib/sass/tree/function_node.rb +34 -0
- data/lib/sass/tree/if_node.rb +52 -0
- data/lib/sass/tree/import_node.rb +75 -0
- data/lib/sass/tree/media_node.rb +58 -0
- data/lib/sass/tree/mixin_def_node.rb +38 -0
- data/lib/sass/tree/mixin_node.rb +39 -0
- data/lib/sass/tree/node.rb +196 -0
- data/lib/sass/tree/prop_node.rb +152 -0
- data/lib/sass/tree/return_node.rb +18 -0
- data/lib/sass/tree/root_node.rb +78 -0
- data/lib/sass/tree/rule_node.rb +132 -0
- data/lib/sass/tree/supports_node.rb +51 -0
- data/lib/sass/tree/trace_node.rb +32 -0
- data/lib/sass/tree/variable_node.rb +30 -0
- data/lib/sass/tree/visitors/base.rb +75 -0
- data/lib/sass/tree/visitors/check_nesting.rb +147 -0
- data/lib/sass/tree/visitors/convert.rb +316 -0
- data/lib/sass/tree/visitors/cssize.rb +241 -0
- data/lib/sass/tree/visitors/deep_copy.rb +102 -0
- data/lib/sass/tree/visitors/extend.rb +68 -0
- data/lib/sass/tree/visitors/perform.rb +446 -0
- data/lib/sass/tree/visitors/set_options.rb +125 -0
- data/lib/sass/tree/visitors/to_css.rb +228 -0
- data/lib/sass/tree/warn_node.rb +18 -0
- data/lib/sass/tree/while_node.rb +18 -0
- data/lib/sass/util/multibyte_string_scanner.rb +155 -0
- data/lib/sass/util/subset_map.rb +109 -0
- data/lib/sass/util/test.rb +10 -0
- data/lib/sass/util.rb +948 -0
- data/lib/sass/version.rb +126 -0
- data/lib/sass.rb +95 -0
- data/rails/init.rb +1 -0
- data/test/Gemfile +3 -0
- data/test/Gemfile.lock +10 -0
- data/test/sass/cache_test.rb +89 -0
- data/test/sass/callbacks_test.rb +61 -0
- data/test/sass/conversion_test.rb +1760 -0
- data/test/sass/css2sass_test.rb +458 -0
- data/test/sass/data/hsl-rgb.txt +319 -0
- data/test/sass/engine_test.rb +3244 -0
- data/test/sass/exec_test.rb +86 -0
- data/test/sass/extend_test.rb +1482 -0
- data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
- data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
- data/test/sass/functions_test.rb +1139 -0
- data/test/sass/importer_test.rb +192 -0
- data/test/sass/logger_test.rb +58 -0
- data/test/sass/mock_importer.rb +49 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +564 -0
- data/test/sass/results/alt.css +4 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/cached_import_option.css +3 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +86 -0
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/expanded.css +19 -0
- data/test/sass/results/filename_fn.css +3 -0
- data/test/sass/results/if.css +3 -0
- data/test/sass/results/import.css +31 -0
- data/test/sass/results/import_charset.css +5 -0
- data/test/sass/results/import_charset_1_8.css +5 -0
- data/test/sass/results/import_charset_ibm866.css +5 -0
- data/test/sass/results/import_content.css +1 -0
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/results/nested.css +22 -0
- data/test/sass/results/options.css +1 -0
- data/test/sass/results/parent_ref.css +13 -0
- data/test/sass/results/script.css +16 -0
- data/test/sass/results/scss_import.css +31 -0
- data/test/sass/results/scss_importee.css +2 -0
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/test/sass/results/subdir/subdir.css +3 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/results/warn.css +0 -0
- data/test/sass/results/warn_imported.css +0 -0
- data/test/sass/script_conversion_test.rb +299 -0
- data/test/sass/script_test.rb +622 -0
- data/test/sass/scss/css_test.rb +1100 -0
- data/test/sass/scss/rx_test.rb +156 -0
- data/test/sass/scss/scss_test.rb +2106 -0
- data/test/sass/scss/test_helper.rb +37 -0
- data/test/sass/templates/_cached_import_option_partial.scss +1 -0
- data/test/sass/templates/_double_import_loop2.sass +1 -0
- data/test/sass/templates/_filename_fn_import.scss +11 -0
- data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
- data/test/sass/templates/_imported_charset_utf8.sass +4 -0
- data/test/sass/templates/_imported_content.sass +3 -0
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/_same_name_different_partiality.scss +1 -0
- data/test/sass/templates/alt.sass +16 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork1.sass +2 -0
- data/test/sass/templates/bork2.sass +2 -0
- data/test/sass/templates/bork3.sass +2 -0
- data/test/sass/templates/bork4.sass +2 -0
- data/test/sass/templates/bork5.sass +3 -0
- data/test/sass/templates/cached_import_option.scss +3 -0
- data/test/sass/templates/compact.sass +17 -0
- data/test/sass/templates/complex.sass +305 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/double_import_loop1.sass +1 -0
- data/test/sass/templates/expanded.sass +17 -0
- data/test/sass/templates/filename_fn.scss +18 -0
- data/test/sass/templates/if.sass +11 -0
- data/test/sass/templates/import.sass +12 -0
- data/test/sass/templates/import_charset.sass +9 -0
- data/test/sass/templates/import_charset_1_8.sass +6 -0
- data/test/sass/templates/import_charset_ibm866.sass +11 -0
- data/test/sass/templates/import_content.sass +4 -0
- data/test/sass/templates/importee.less +2 -0
- data/test/sass/templates/importee.sass +19 -0
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/mixin_bork.sass +5 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/sass/templates/nested.sass +25 -0
- data/test/sass/templates/nested_bork1.sass +2 -0
- data/test/sass/templates/nested_bork2.sass +2 -0
- data/test/sass/templates/nested_bork3.sass +2 -0
- data/test/sass/templates/nested_bork4.sass +2 -0
- data/test/sass/templates/nested_import.sass +2 -0
- data/test/sass/templates/nested_mixin_bork.sass +6 -0
- data/test/sass/templates/options.sass +2 -0
- data/test/sass/templates/parent_ref.sass +25 -0
- data/test/sass/templates/same_name_different_ext.sass +2 -0
- data/test/sass/templates/same_name_different_ext.scss +1 -0
- data/test/sass/templates/same_name_different_partiality.scss +1 -0
- data/test/sass/templates/script.sass +101 -0
- data/test/sass/templates/scss_import.scss +11 -0
- data/test/sass/templates/scss_importee.scss +1 -0
- data/test/sass/templates/single_import_loop.sass +1 -0
- data/test/sass/templates/subdir/import_up1.scss +1 -0
- data/test/sass/templates/subdir/import_up2.scss +1 -0
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/test/sass/templates/subdir/subdir.sass +6 -0
- data/test/sass/templates/units.sass +11 -0
- data/test/sass/templates/warn.sass +3 -0
- data/test/sass/templates/warn_imported.sass +4 -0
- data/test/sass/test_helper.rb +8 -0
- data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
- data/test/sass/util/subset_map_test.rb +91 -0
- data/test/sass/util_test.rb +382 -0
- data/test/test_helper.rb +80 -0
- 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
|