[gnome-code-assistance] Update distributed sass to 3.4.9
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-code-assistance] Update distributed sass to 3.4.9
- Date: Sat, 10 Jan 2015 13:10:05 +0000 (UTC)
commit c11fed7f4766d826adbfb45492f498a2f5275c3e
Author: Jesse van den Kieboom <jessevdk gmail com>
Date: Sat Jan 10 13:46:34 2015 +0100
Update distributed sass to 3.4.9
backends/css/app.rb | 2 +-
backends/css/deps.mf | 901 +++---
backends/css/gems/sass-3.2.12/CONTRIBUTING | 3 -
backends/css/gems/sass-3.2.12/MIT-LICENSE | 20 -
backends/css/gems/sass-3.2.12/README.md | 201 -
backends/css/gems/sass-3.2.12/Rakefile | 347 --
backends/css/gems/sass-3.2.12/VERSION | 1 -
backends/css/gems/sass-3.2.12/VERSION_DATE | 1 -
backends/css/gems/sass-3.2.12/VERSION_NAME | 1 -
backends/css/gems/sass-3.2.12/bin/sass | 9 -
backends/css/gems/sass-3.2.12/bin/sass-convert | 8 -
backends/css/gems/sass-3.2.12/bin/scss | 9 -
backends/css/gems/sass-3.2.12/lib/sass.rb | 95 -
.../gems/sass-3.2.12/lib/sass/cache_stores/base.rb | 88 -
.../sass-3.2.12/lib/sass/cache_stores/chain.rb | 33 -
.../lib/sass/cache_stores/filesystem.rb | 60 -
.../sass-3.2.12/lib/sass/cache_stores/memory.rb | 47 -
.../gems/sass-3.2.12/lib/sass/cache_stores/null.rb | 25 -
.../css/gems/sass-3.2.12/lib/sass/callbacks.rb | 66 -
backends/css/gems/sass-3.2.12/lib/sass/css.rb | 409 --
backends/css/gems/sass-3.2.12/lib/sass/engine.rb | 928 -----
.../css/gems/sass-3.2.12/lib/sass/environment.rb | 101 -
backends/css/gems/sass-3.2.12/lib/sass/error.rb | 201 -
backends/css/gems/sass-3.2.12/lib/sass/exec.rb | 707 ----
.../gems/sass-3.2.12/lib/sass/importers/base.rb | 139 -
.../sass-3.2.12/lib/sass/importers/filesystem.rb | 190 -
backends/css/gems/sass-3.2.12/lib/sass/logger.rb | 15 -
.../css/gems/sass-3.2.12/lib/sass/logger/base.rb | 32 -
.../gems/sass-3.2.12/lib/sass/logger/log_level.rb | 49 -
backends/css/gems/sass-3.2.12/lib/sass/media.rb | 213 --
backends/css/gems/sass-3.2.12/lib/sass/plugin.rb | 132 -
.../gems/sass-3.2.12/lib/sass/plugin/compiler.rb | 406 --
.../sass-3.2.12/lib/sass/plugin/configuration.rb | 123 -
.../css/gems/sass-3.2.12/lib/sass/plugin/merb.rb | 48 -
.../lib/sass/plugin/staleness_checker.rb | 199 -
backends/css/gems/sass-3.2.12/lib/sass/railtie.rb | 9 -
backends/css/gems/sass-3.2.12/lib/sass/repl.rb | 57 -
backends/css/gems/sass-3.2.12/lib/sass/script.rb | 39 -
.../gems/sass-3.2.12/lib/sass/script/arg_list.rb | 52 -
.../css/gems/sass-3.2.12/lib/sass/script/bool.rb | 18 -
.../css/gems/sass-3.2.12/lib/sass/script/color.rb | 606 ---
.../gems/sass-3.2.12/lib/sass/script/css_lexer.rb | 29 -
.../gems/sass-3.2.12/lib/sass/script/css_parser.rb | 31 -
.../gems/sass-3.2.12/lib/sass/script/funcall.rb | 237 --
.../gems/sass-3.2.12/lib/sass/script/functions.rb | 1543 --------
.../sass-3.2.12/lib/sass/script/interpolation.rb | 79 -
.../css/gems/sass-3.2.12/lib/sass/script/lexer.rb | 348 --
.../css/gems/sass-3.2.12/lib/sass/script/list.rb | 85 -
.../gems/sass-3.2.12/lib/sass/script/literal.rb | 221 --
.../css/gems/sass-3.2.12/lib/sass/script/node.rb | 99 -
.../css/gems/sass-3.2.12/lib/sass/script/null.rb | 37 -
.../css/gems/sass-3.2.12/lib/sass/script/number.rb | 453 ---
.../gems/sass-3.2.12/lib/sass/script/operation.rb | 110 -
.../css/gems/sass-3.2.12/lib/sass/script/parser.rb | 495 ---
.../css/gems/sass-3.2.12/lib/sass/script/string.rb | 51 -
.../lib/sass/script/string_interpolation.rb | 103 -
.../sass-3.2.12/lib/sass/script/unary_operation.rb | 69 -
.../gems/sass-3.2.12/lib/sass/script/variable.rb | 58 -
.../gems/sass-3.2.12/lib/sass/scss/css_parser.rb | 36 -
.../css/gems/sass-3.2.12/lib/sass/scss/parser.rb | 1179 ------
backends/css/gems/sass-3.2.12/lib/sass/scss/rx.rb | 133 -
.../sass-3.2.12/lib/sass/scss/static_parser.rb | 54 -
backends/css/gems/sass-3.2.12/lib/sass/selector.rb | 452 ---
.../lib/sass/selector/abstract_sequence.rb | 94 -
.../lib/sass/selector/comma_sequence.rb | 92 -
.../gems/sass-3.2.12/lib/sass/selector/sequence.rb | 507 ---
.../gems/sass-3.2.12/lib/sass/selector/simple.rb | 119 -
.../lib/sass/selector/simple_sequence.rb | 212 --
backends/css/gems/sass-3.2.12/lib/sass/shared.rb | 76 -
backends/css/gems/sass-3.2.12/lib/sass/supports.rb | 229 --
.../gems/sass-3.2.12/lib/sass/tree/charset_node.rb | 22 -
.../gems/sass-3.2.12/lib/sass/tree/comment_node.rb | 82 -
.../sass-3.2.12/lib/sass/tree/css_import_node.rb | 60 -
.../gems/sass-3.2.12/lib/sass/tree/debug_node.rb | 18 -
.../sass-3.2.12/lib/sass/tree/directive_node.rb | 42 -
.../gems/sass-3.2.12/lib/sass/tree/each_node.rb | 24 -
.../gems/sass-3.2.12/lib/sass/tree/extend_node.rb | 36 -
.../css/gems/sass-3.2.12/lib/sass/tree/for_node.rb | 36 -
.../sass-3.2.12/lib/sass/tree/function_node.rb | 34 -
.../css/gems/sass-3.2.12/lib/sass/tree/if_node.rb | 52 -
.../gems/sass-3.2.12/lib/sass/tree/import_node.rb | 75 -
.../gems/sass-3.2.12/lib/sass/tree/media_node.rb | 58 -
.../sass-3.2.12/lib/sass/tree/mixin_def_node.rb | 38 -
.../gems/sass-3.2.12/lib/sass/tree/mixin_node.rb | 39 -
.../css/gems/sass-3.2.12/lib/sass/tree/node.rb | 196 -
.../gems/sass-3.2.12/lib/sass/tree/prop_node.rb | 152 -
.../gems/sass-3.2.12/lib/sass/tree/return_node.rb | 18 -
.../gems/sass-3.2.12/lib/sass/tree/root_node.rb | 28 -
.../gems/sass-3.2.12/lib/sass/tree/rule_node.rb | 132 -
.../sass-3.2.12/lib/sass/tree/supports_node.rb | 51 -
.../gems/sass-3.2.12/lib/sass/tree/trace_node.rb | 32 -
.../sass-3.2.12/lib/sass/tree/variable_node.rb | 30 -
.../sass-3.2.12/lib/sass/tree/visitors/base.rb | 75 -
.../lib/sass/tree/visitors/check_nesting.rb | 147 -
.../sass-3.2.12/lib/sass/tree/visitors/convert.rb | 316 --
.../sass-3.2.12/lib/sass/tree/visitors/cssize.rb | 229 --
.../lib/sass/tree/visitors/deep_copy.rb | 102 -
.../sass-3.2.12/lib/sass/tree/visitors/extend.rb | 68 -
.../sass-3.2.12/lib/sass/tree/visitors/perform.rb | 446 ---
.../lib/sass/tree/visitors/set_options.rb | 125 -
.../sass-3.2.12/lib/sass/tree/visitors/to_css.rb | 228 --
.../gems/sass-3.2.12/lib/sass/tree/warn_node.rb | 18 -
.../gems/sass-3.2.12/lib/sass/tree/while_node.rb | 18 -
backends/css/gems/sass-3.2.12/lib/sass/util.rb | 930 -----
.../lib/sass/util/multibyte_string_scanner.rb | 155 -
.../gems/sass-3.2.12/lib/sass/util/subset_map.rb | 109 -
.../css/gems/sass-3.2.12/lib/sass/util/test.rb | 10 -
backends/css/gems/sass-3.2.12/lib/sass/version.rb | 126 -
backends/css/gems/sass-3.2.12/test/Gemfile | 3 -
backends/css/gems/sass-3.2.12/test/Gemfile.lock | 10 -
.../css/gems/sass-3.2.12/test/sass/cache_test.rb | 89 -
.../gems/sass-3.2.12/test/sass/callbacks_test.rb | 61 -
.../gems/sass-3.2.12/test/sass/conversion_test.rb | 1760 ---------
.../gems/sass-3.2.12/test/sass/css2sass_test.rb | 439 ---
.../css/gems/sass-3.2.12/test/sass/engine_test.rb | 3243 ----------------
.../css/gems/sass-3.2.12/test/sass/exec_test.rb | 86 -
.../css/gems/sass-3.2.12/test/sass/extend_test.rb | 1482 --------
.../gems/sass-3.2.12/test/sass/functions_test.rb | 1139 ------
.../gems/sass-3.2.12/test/sass/importer_test.rb | 192 -
.../css/gems/sass-3.2.12/test/sass/logger_test.rb | 58 -
.../test/sass/more_results/more_import.css | 29 -
.../css/gems/sass-3.2.12/test/sass/plugin_test.rb | 550 ---
.../gems/sass-3.2.12/test/sass/results/compact.css | 5 -
.../gems/sass-3.2.12/test/sass/results/complex.css | 86 -
.../sass-3.2.12/test/sass/results/expanded.css | 19 -
.../gems/sass-3.2.12/test/sass/results/import.css | 31 -
.../test/sass/results/import_charset_ibm866.css | 5 -
.../gems/sass-3.2.12/test/sass/results/mixins.css | 95 -
.../gems/sass-3.2.12/test/sass/results/nested.css | 22 -
.../sass-3.2.12/test/sass/results/parent_ref.css | 13 -
.../gems/sass-3.2.12/test/sass/results/script.css | 16 -
.../sass-3.2.12/test/sass/results/scss_import.css | 31 -
.../test/sass/script_conversion_test.rb | 299 --
.../css/gems/sass-3.2.12/test/sass/script_test.rb | 591 ---
.../gems/sass-3.2.12/test/sass/scss/css_test.rb | 1093 ------
.../css/gems/sass-3.2.12/test/sass/scss/rx_test.rb | 156 -
.../gems/sass-3.2.12/test/sass/scss/scss_test.rb | 2043 ----------
.../test/sass/templates/scss_import.scss | 11 -
.../css/gems/sass-3.2.12/test/sass/test_helper.rb | 8 -
.../sass/util/multibyte_string_scanner_test.rb | 147 -
.../sass-3.2.12/test/sass/util/subset_map_test.rb | 91 -
.../css/gems/sass-3.2.12/test/sass/util_test.rb | 361 --
backends/css/gems/sass-3.2.12/test/test_helper.rb | 80 -
.../gems/sass-3.2.12/vendor/listen/CHANGELOG.md | 228 --
.../css/gems/sass-3.2.12/vendor/listen/Gemfile | 30 -
.../css/gems/sass-3.2.12/vendor/listen/README.md | 315 --
.../css/gems/sass-3.2.12/vendor/listen/Rakefile | 47 -
.../gems/sass-3.2.12/vendor/listen/lib/listen.rb | 40 -
.../vendor/listen/lib/listen/adapter.rb | 214 --
.../vendor/listen/lib/listen/adapters/bsd.rb | 112 -
.../vendor/listen/lib/listen/adapters/darwin.rb | 85 -
.../vendor/listen/lib/listen/adapters/linux.rb | 113 -
.../vendor/listen/lib/listen/adapters/polling.rb | 67 -
.../vendor/listen/lib/listen/adapters/windows.rb | 87 -
.../vendor/listen/lib/listen/dependency_manager.rb | 126 -
.../vendor/listen/lib/listen/directory_record.rb | 371 --
.../vendor/listen/lib/listen/listener.rb | 225 --
.../vendor/listen/lib/listen/multi_listener.rb | 143 -
.../vendor/listen/lib/listen/turnstile.rb | 28 -
.../vendor/listen/lib/listen/version.rb | 3 -
.../gems/sass-3.2.12/vendor/listen/listen.gemspec | 22 -
.../vendor/listen/spec/listen/adapter_spec.rb | 183 -
.../listen/spec/listen/adapters/polling_spec.rb | 68 -
.../listen/spec/listen/dependency_manager_spec.rb | 107 -
.../listen/spec/listen/directory_record_spec.rb | 1225 ------
.../vendor/listen/spec/listen/listener_spec.rb | 169 -
.../listen/spec/listen/multi_listener_spec.rb | 174 -
.../sass-3.2.12/vendor/listen/spec/listen_spec.rb | 73 -
.../sass-3.2.12/vendor/listen/spec/spec_helper.rb | 21 -
.../vendor/listen/spec/support/adapter_helper.rb | 629 ---
.../listen/spec/support/directory_record_helper.rb | 55 -
.../vendor/listen/spec/support/listeners_helper.rb | 156 -
.../css/gems/{sass-3.2.12 => sass-3.4.9}/.yardopts | 0
backends/css/gems/sass-3.4.9/CONTRIBUTING | 3 +
backends/css/gems/sass-3.4.9/MIT-LICENSE | 20 +
backends/css/gems/sass-3.4.9/README.md | 221 ++
.../css/gems/{sass-3.2.12 => sass-3.4.9}/REVISION | 0
backends/css/gems/sass-3.4.9/Rakefile | 370 ++
backends/css/gems/sass-3.4.9/VERSION | 1 +
backends/css/gems/sass-3.4.9/VERSION_DATE | 1 +
backends/css/gems/sass-3.4.9/VERSION_NAME | 1 +
backends/css/gems/sass-3.4.9/bin/sass | 13 +
backends/css/gems/sass-3.4.9/bin/sass-convert | 12 +
backends/css/gems/sass-3.4.9/bin/scss | 13 +
.../extra/update_watch.rb | 0
.../css/gems/{sass-3.2.12 => sass-3.4.9}/init.rb | 0
backends/css/gems/sass-3.4.9/lib/sass.rb | 102 +
.../lib/sass/cache_stores.rb | 0
.../gems/sass-3.4.9/lib/sass/cache_stores/base.rb | 88 +
.../gems/sass-3.4.9/lib/sass/cache_stores/chain.rb | 34 +
.../sass-3.4.9/lib/sass/cache_stores/filesystem.rb | 60 +
.../sass-3.4.9/lib/sass/cache_stores/memory.rb | 47 +
.../gems/sass-3.4.9/lib/sass/cache_stores/null.rb | 25 +
backends/css/gems/sass-3.4.9/lib/sass/callbacks.rb | 67 +
backends/css/gems/sass-3.4.9/lib/sass/css.rb | 407 ++
backends/css/gems/sass-3.4.9/lib/sass/engine.rb | 1182 ++++++
.../css/gems/sass-3.4.9/lib/sass/environment.rb | 208 +
backends/css/gems/sass-3.4.9/lib/sass/error.rb | 198 +
backends/css/gems/sass-3.4.9/lib/sass/exec.rb | 9 +
backends/css/gems/sass-3.4.9/lib/sass/exec/base.rb | 199 +
.../gems/sass-3.4.9/lib/sass/exec/sass_convert.rb | 269 ++
.../css/gems/sass-3.4.9/lib/sass/exec/sass_scss.rb | 444 +++
backends/css/gems/sass-3.4.9/lib/sass/features.rb | 47 +
.../lib/sass/importers.rb | 0
.../css/gems/sass-3.4.9/lib/sass/importers/base.rb | 182 +
.../sass-3.4.9/lib/sass/importers/filesystem.rb | 217 ++
backends/css/gems/sass-3.4.9/lib/sass/logger.rb | 12 +
.../css/gems/sass-3.4.9/lib/sass/logger/base.rb | 30 +
.../gems/sass-3.4.9/lib/sass/logger/log_level.rb | 45 +
backends/css/gems/sass-3.4.9/lib/sass/media.rb | 210 +
backends/css/gems/sass-3.4.9/lib/sass/plugin.rb | 133 +
.../gems/sass-3.4.9/lib/sass/plugin/compiler.rb | 571 +++
.../sass-3.4.9/lib/sass/plugin/configuration.rb | 118 +
.../lib/sass/plugin/generic.rb | 0
.../css/gems/sass-3.4.9/lib/sass/plugin/merb.rb | 48 +
.../lib/sass/plugin/rack.rb | 0
.../lib/sass/plugin/rails.rb | 0
.../lib/sass/plugin/staleness_checker.rb | 199 +
backends/css/gems/sass-3.4.9/lib/sass/railtie.rb | 10 +
backends/css/gems/sass-3.4.9/lib/sass/repl.rb | 57 +
.../{sass-3.2.12 => sass-3.4.9}/lib/sass/root.rb | 0
backends/css/gems/sass-3.4.9/lib/sass/script.rb | 66 +
.../gems/sass-3.4.9/lib/sass/script/css_lexer.rb | 33 +
.../gems/sass-3.4.9/lib/sass/script/css_parser.rb | 34 +
.../gems/sass-3.4.9/lib/sass/script/functions.rb | 2646 +++++++++++++
.../css/gems/sass-3.4.9/lib/sass/script/lexer.rb | 453 +++
.../css/gems/sass-3.4.9/lib/sass/script/parser.rb | 638 ++++
.../css/gems/sass-3.4.9/lib/sass/script/tree.rb | 16 +
.../sass-3.4.9/lib/sass/script/tree/funcall.rb | 306 ++
.../lib/sass/script/tree/interpolation.rb | 118 +
.../lib/sass/script/tree/list_literal.rb | 77 +
.../sass-3.4.9/lib/sass/script/tree/literal.rb | 45 +
.../sass-3.4.9/lib/sass/script/tree/map_literal.rb | 64 +
.../gems/sass-3.4.9/lib/sass/script/tree/node.rb | 109 +
.../sass-3.4.9/lib/sass/script/tree/operation.rb | 115 +
.../sass-3.4.9/lib/sass/script/tree/selector.rb | 26 +
.../lib/sass/script/tree/string_interpolation.rb | 104 +
.../lib/sass/script/tree/unary_operation.rb | 69 +
.../sass-3.4.9/lib/sass/script/tree/variable.rb | 57 +
.../css/gems/sass-3.4.9/lib/sass/script/value.rb | 11 +
.../sass-3.4.9/lib/sass/script/value/arg_list.rb | 36 +
.../gems/sass-3.4.9/lib/sass/script/value/base.rb | 240 ++
.../gems/sass-3.4.9/lib/sass/script/value/bool.rb | 35 +
.../gems/sass-3.4.9/lib/sass/script/value/color.rb | 680 ++++
.../sass-3.4.9/lib/sass/script/value/helpers.rb | 262 ++
.../gems/sass-3.4.9/lib/sass/script/value/list.rb | 113 +
.../gems/sass-3.4.9/lib/sass/script/value/map.rb | 70 +
.../gems/sass-3.4.9/lib/sass/script/value/null.rb | 44 +
.../sass-3.4.9/lib/sass/script/value/number.rb | 530 +++
.../sass-3.4.9/lib/sass/script/value/string.rb | 97 +
.../{sass-3.2.12 => sass-3.4.9}/lib/sass/scss.rb | 0
.../gems/sass-3.4.9/lib/sass/scss/css_parser.rb | 42 +
.../css/gems/sass-3.4.9/lib/sass/scss/parser.rb | 1211 ++++++
backends/css/gems/sass-3.4.9/lib/sass/scss/rx.rb | 141 +
.../lib/sass/scss/script_lexer.rb | 0
.../lib/sass/scss/script_parser.rb | 0
.../gems/sass-3.4.9/lib/sass/scss/static_parser.rb | 368 ++
backends/css/gems/sass-3.4.9/lib/sass/selector.rb | 326 ++
.../lib/sass/selector/abstract_sequence.rb | 109 +
.../sass-3.4.9/lib/sass/selector/comma_sequence.rb | 177 +
.../gems/sass-3.4.9/lib/sass/selector/pseudo.rb | 266 ++
.../gems/sass-3.4.9/lib/sass/selector/sequence.rb | 618 +++
.../gems/sass-3.4.9/lib/sass/selector/simple.rb | 117 +
.../lib/sass/selector/simple_sequence.rb | 344 ++
backends/css/gems/sass-3.4.9/lib/sass/shared.rb | 76 +
.../css/gems/sass-3.4.9/lib/sass/source/map.rb | 211 +
.../gems/sass-3.4.9/lib/sass/source/position.rb | 39 +
.../css/gems/sass-3.4.9/lib/sass/source/range.rb | 41 +
backends/css/gems/sass-3.4.9/lib/sass/stack.rb | 120 +
backends/css/gems/sass-3.4.9/lib/sass/supports.rb | 227 ++
.../gems/sass-3.4.9/lib/sass/tree/at_root_node.rb | 83 +
.../gems/sass-3.4.9/lib/sass/tree/charset_node.rb | 22 +
.../gems/sass-3.4.9/lib/sass/tree/comment_node.rb | 82 +
.../lib/sass/tree/content_node.rb | 0
.../sass-3.4.9/lib/sass/tree/css_import_node.rb | 60 +
.../gems/sass-3.4.9/lib/sass/tree/debug_node.rb | 18 +
.../sass-3.4.9/lib/sass/tree/directive_node.rb | 59 +
.../css/gems/sass-3.4.9/lib/sass/tree/each_node.rb | 24 +
.../gems/sass-3.4.9/lib/sass/tree/error_node.rb | 18 +
.../gems/sass-3.4.9/lib/sass/tree/extend_node.rb | 43 +
.../css/gems/sass-3.4.9/lib/sass/tree/for_node.rb | 36 +
.../gems/sass-3.4.9/lib/sass/tree/function_node.rb | 39 +
.../css/gems/sass-3.4.9/lib/sass/tree/if_node.rb | 52 +
.../gems/sass-3.4.9/lib/sass/tree/import_node.rb | 74 +
.../sass-3.4.9/lib/sass/tree/keyframe_rule_node.rb | 15 +
.../gems/sass-3.4.9/lib/sass/tree/media_node.rb | 48 +
.../sass-3.4.9/lib/sass/tree/mixin_def_node.rb | 38 +
.../gems/sass-3.4.9/lib/sass/tree/mixin_node.rb | 52 +
backends/css/gems/sass-3.4.9/lib/sass/tree/node.rb | 238 ++
.../css/gems/sass-3.4.9/lib/sass/tree/prop_node.rb | 171 +
.../gems/sass-3.4.9/lib/sass/tree/return_node.rb | 19 +
.../css/gems/sass-3.4.9/lib/sass/tree/root_node.rb | 44 +
.../css/gems/sass-3.4.9/lib/sass/tree/rule_node.rb | 145 +
.../gems/sass-3.4.9/lib/sass/tree/supports_node.rb | 38 +
.../gems/sass-3.4.9/lib/sass/tree/trace_node.rb | 33 +
.../gems/sass-3.4.9/lib/sass/tree/variable_node.rb | 36 +
.../gems/sass-3.4.9/lib/sass/tree/visitors/base.rb | 72 +
.../lib/sass/tree/visitors/check_nesting.rb | 177 +
.../sass-3.4.9/lib/sass/tree/visitors/convert.rb | 334 ++
.../sass-3.4.9/lib/sass/tree/visitors/cssize.rb | 373 ++
.../sass-3.4.9/lib/sass/tree/visitors/deep_copy.rb | 107 +
.../sass-3.4.9/lib/sass/tree/visitors/extend.rb | 68 +
.../sass-3.4.9/lib/sass/tree/visitors/perform.rb | 547 +++
.../lib/sass/tree/visitors/set_options.rb | 139 +
.../sass-3.4.9/lib/sass/tree/visitors/to_css.rb | 381 ++
.../css/gems/sass-3.4.9/lib/sass/tree/warn_node.rb | 18 +
.../gems/sass-3.4.9/lib/sass/tree/while_node.rb | 18 +
backends/css/gems/sass-3.4.9/lib/sass/util.rb | 1376 +++++++
.../lib/sass/util/cross_platform_random.rb | 19 +
.../lib/sass/util/multibyte_string_scanner.rb | 157 +
.../sass-3.4.9/lib/sass/util/normalized_map.rb | 130 +
.../gems/sass-3.4.9/lib/sass/util/ordered_hash.rb | 192 +
.../gems/sass-3.4.9/lib/sass/util/subset_map.rb | 110 +
backends/css/gems/sass-3.4.9/lib/sass/util/test.rb | 9 +
backends/css/gems/sass-3.4.9/lib/sass/version.rb | 124 +
.../gems/{sass-3.2.12 => sass-3.4.9}/rails/init.rb | 0
.../css/gems/sass-3.4.9/test/sass/cache_test.rb | 131 +
.../gems/sass-3.4.9/test/sass/callbacks_test.rb | 61 +
.../css/gems/sass-3.4.9/test/sass/compiler_test.rb | 236 ++
.../gems/sass-3.4.9/test/sass/conversion_test.rb | 2069 ++++++++++
.../css/gems/sass-3.4.9/test/sass/css2sass_test.rb | 477 +++
.../test/sass/data/hsl-rgb.txt | 0
.../css/gems/sass-3.4.9/test/sass/encoding_test.rb | 219 ++
.../css/gems/sass-3.4.9/test/sass/engine_test.rb | 3301 ++++++++++++++++
.../css/gems/sass-3.4.9/test/sass/exec_test.rb | 86 +
.../css/gems/sass-3.4.9/test/sass/extend_test.rb | 1687 ++++++++
.../test_staleness_check_across_importers.css | 0
.../test_staleness_check_across_importers.scss | 0
.../gems/sass-3.4.9/test/sass/functions_test.rb | 1954 ++++++++++
.../css/gems/sass-3.4.9/test/sass/importer_test.rb | 421 ++
.../css/gems/sass-3.4.9/test/sass/logger_test.rb | 58 +
.../test/sass/mock_importer.rb | 0
.../test/sass/more_results/more1.css | 0
.../sass/more_results/more1_with_line_comments.css | 0
.../test/sass/more_results/more_import.css | 29 +
.../test/sass/more_templates/_more_partial.sass | 0
.../test/sass/more_templates/more1.sass | 0
.../test/sass/more_templates/more_import.sass | 0
.../css/gems/sass-3.4.9/test/sass/plugin_test.rb | 556 +++
.../test/sass/results/alt.css | 0
.../test/sass/results/basic.css | 0
.../test/sass/results/cached_import_option.css | 0
.../gems/sass-3.4.9/test/sass/results/compact.css | 5 +
.../gems/sass-3.4.9/test/sass/results/complex.css | 86 +
.../test/sass/results/compressed.css | 0
.../gems/sass-3.4.9/test/sass/results/expanded.css | 19 +
.../test/sass/results/filename_fn.css | 0
.../test/sass/results/if.css | 0
.../gems/sass-3.4.9/test/sass/results/import.css | 31 +
.../test/sass/results/import_charset.css | 0
.../test/sass/results/import_charset_1_8.css | 0
.../test/sass/results/import_charset_ibm866.css} | 0
.../test/sass/results/import_content.css | 0
.../test/sass/results/line_numbers.css | 0
.../gems/sass-3.4.9/test/sass/results/mixins.css | 95 +
.../test/sass/results/multiline.css | 0
.../gems/sass-3.4.9/test/sass/results/nested.css | 22 +
.../test/sass/results/options.css | 0
.../sass-3.4.9/test/sass/results/parent_ref.css | 13 +
.../gems/sass-3.4.9/test/sass/results/script.css | 16 +
.../sass-3.4.9/test/sass/results/scss_import.css | 31 +
.../test/sass/results/scss_importee.css | 0
.../results/subdir/nested_subdir/nested_subdir.css | 0
.../test/sass/results/subdir/subdir.css | 0
.../test/sass/results/units.css | 0
.../test/sass/results/warn.css | 0
.../test/sass/results/warn_imported.css | 0
.../sass-3.4.9/test/sass/script_conversion_test.rb | 333 ++
.../css/gems/sass-3.4.9/test/sass/script_test.rb | 1170 ++++++
.../css/gems/sass-3.4.9/test/sass/scss/css_test.rb | 1256 ++++++
.../css/gems/sass-3.4.9/test/sass/scss/rx_test.rb | 156 +
.../gems/sass-3.4.9/test/sass/scss/scss_test.rb | 4008 ++++++++++++++++++++
.../test/sass/scss/test_helper.rb | 0
.../gems/sass-3.4.9/test/sass/source_map_test.rb | 977 +++++
.../sass-3.4.9/test/sass/superselector_test.rb | 210 +
.../templates/_cached_import_option_partial.scss | 0
.../test/sass/templates/_double_import_loop2.sass | 0
.../test/sass/templates/_filename_fn_import.scss | 0
.../sass/templates/_imported_charset_ibm866.sass | 0
.../sass/templates/_imported_charset_utf8.sass | 0
.../test/sass/templates/_imported_content.sass | 0
.../test/sass/templates/_partial.sass | 0
.../templates/_same_name_different_partiality.scss | 0
.../test/sass/templates/alt.sass | 0
.../test/sass/templates/basic.sass | 0
.../test/sass/templates/bork1.sass | 0
.../test/sass/templates/bork2.sass | 0
.../test/sass/templates/bork3.sass | 0
.../test/sass/templates/bork4.sass | 0
.../test/sass/templates/bork5.sass | 0
.../test/sass/templates/cached_import_option.scss | 0
.../test/sass/templates/compact.sass | 0
.../test/sass/templates/complex.sass | 0
.../test/sass/templates/compressed.sass | 0
.../test/sass/templates/double_import_loop1.sass | 0
.../test/sass/templates/expanded.sass | 0
.../test/sass/templates/filename_fn.scss | 0
.../test/sass/templates/if.sass | 0
.../test/sass/templates/import.sass | 0
.../test/sass/templates/import_charset.sass | 0
.../test/sass/templates/import_charset_1_8.sass | 0
.../test/sass/templates/import_charset_ibm866.sass | 0
.../test/sass/templates/import_content.sass | 0
.../test/sass/templates/importee.less | 0
.../test/sass/templates/importee.sass | 0
.../test/sass/templates/line_numbers.sass | 0
.../test/sass/templates/mixin_bork.sass | 0
.../test/sass/templates/mixins.sass | 0
.../test/sass/templates/multiline.sass | 0
.../test/sass/templates/nested.sass | 0
.../test/sass/templates/nested_bork1.sass | 0
.../test/sass/templates/nested_bork2.sass | 0
.../test/sass/templates/nested_bork3.sass | 0
.../test/sass/templates/nested_bork4.sass | 0
.../test/sass/templates/nested_import.sass | 0
.../test/sass/templates/nested_mixin_bork.sass | 0
.../test/sass/templates/options.sass | 0
.../test/sass/templates/parent_ref.sass | 0
.../sass/templates/same_name_different_ext.sass | 0
.../sass/templates/same_name_different_ext.scss | 0
.../templates/same_name_different_partiality.scss | 0
.../test/sass/templates/script.sass | 0
.../test/sass/templates/scss_import.scss | 12 +
.../test/sass/templates/scss_importee.scss | 0
.../test/sass/templates/single_import_loop.sass | 0
.../test/sass/templates/subdir/import_up1.scss | 1 +
.../test/sass/templates/subdir/import_up2.scss | 1 +
.../subdir/nested_subdir/_nested_partial.sass | 0
.../subdir/nested_subdir/nested_subdir.sass | 0
.../test/sass/templates/subdir/subdir.sass | 0
.../test/sass/templates/units.sass | 0
.../test/sass/templates/warn.sass | 0
.../test/sass/templates/warn_imported.sass | 0
.../css/gems/sass-3.4.9/test/sass/test_helper.rb | 8 +
.../sass/util/multibyte_string_scanner_test.rb | 147 +
.../test/sass/util/normalized_map_test.rb | 51 +
.../sass-3.4.9/test/sass/util/subset_map_test.rb | 91 +
.../css/gems/sass-3.4.9/test/sass/util_test.rb | 471 +++
.../sass-3.4.9/test/sass/value_helpers_test.rb | 179 +
backends/css/gems/sass-3.4.9/test/test_helper.rb | 109 +
.../css/gems/sass-3.4.9/vendor/listen/CHANGELOG.md | 1 +
.../vendor/listen/CONTRIBUTING.md | 0
backends/css/gems/sass-3.4.9/vendor/listen/Gemfile | 20 +
.../vendor/listen/Guardfile | 0
.../vendor/listen/LICENSE | 0
.../css/gems/sass-3.4.9/vendor/listen/README.md | 349 ++
.../css/gems/sass-3.4.9/vendor/listen/Rakefile | 5 +
.../vendor/listen/Vagrantfile | 0
.../gems/sass-3.4.9/vendor/listen/lib/listen.rb | 54 +
.../sass-3.4.9/vendor/listen/lib/listen/adapter.rb | 327 ++
.../vendor/listen/lib/listen/adapters/bsd.rb | 75 +
.../vendor/listen/lib/listen/adapters/darwin.rb | 48 +
.../vendor/listen/lib/listen/adapters/linux.rb | 81 +
.../vendor/listen/lib/listen/adapters/polling.rb | 58 +
.../vendor/listen/lib/listen/adapters/windows.rb | 91 +
.../vendor/listen/lib/listen/directory_record.rb | 406 ++
.../vendor/listen/lib/listen/listener.rb | 323 ++
.../vendor/listen/lib/listen/turnstile.rb | 32 +
.../sass-3.4.9/vendor/listen/lib/listen/version.rb | 3 +
.../gems/sass-3.4.9/vendor/listen/listen.gemspec | 28 +
.../vendor/listen/spec/listen/adapter_spec.rb | 149 +
.../vendor/listen/spec/listen/adapters/bsd_spec.rb | 0
.../listen/spec/listen/adapters/darwin_spec.rb | 0
.../listen/spec/listen/adapters/linux_spec.rb | 0
.../listen/spec/listen/adapters/polling_spec.rb | 68 +
.../listen/spec/listen/adapters/windows_spec.rb | 0
.../listen/spec/listen/directory_record_spec.rb | 1250 ++++++
.../vendor/listen/spec/listen/listener_spec.rb | 258 ++
.../vendor/listen/spec/listen/turnstile_spec.rb | 0
.../sass-3.4.9/vendor/listen/spec/listen_spec.rb | 67 +
.../sass-3.4.9/vendor/listen/spec/spec_helper.rb | 25 +
.../vendor/listen/spec/support/adapter_helper.rb | 666 ++++
.../listen/spec/support/directory_record_helper.rb | 57 +
.../vendor/listen/spec/support/fixtures_helper.rb | 0
.../vendor/listen/spec/support/listeners_helper.rb | 179 +
.../vendor/listen/spec/support/platform_helper.rb | 0
476 files changed, 49113 insertions(+), 37166 deletions(-)
---
diff --git a/backends/css/app.rb b/backends/css/app.rb
index 021cc79..fbc0292 100644
--- a/backends/css/app.rb
+++ b/backends/css/app.rb
@@ -17,7 +17,7 @@
require 'gnome/codeassistance/transport'
-oursass = File.join(File.dirname(__FILE__), 'gems', 'sass-3.2.12', 'init.rb')
+oursass = File.join(File.dirname(__FILE__), 'gems', 'sass-3.4.9', 'init.rb')
if FileTest.exist?(oursass)
require oursass
diff --git a/backends/css/deps.mf b/backends/css/deps.mf
index b0ee082..e2bad40 100644
--- a/backends/css/deps.mf
+++ b/backends/css/deps.mf
@@ -1,436 +1,477 @@
if RUBY_SASS
else
-csssass_gems_sass_3_2_12_test_sass_results_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/results/subdir
-csssass_gems_sass_3_2_12_test_sass_results_subdir_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/results/subdir/subdir.css
-
-csssass_gems_sass_3_2_12_lib_sass_tree_visitorsdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/tree/visitors
-csssass_gems_sass_3_2_12_lib_sass_tree_visitors_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/base.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/check_nesting.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/convert.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/cssize.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/deep_copy.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/extend.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/perform.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/set_options.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/to_css.rb
-
-csssass_gems_sass_3_2_12_test_sass_scssdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/scss
-csssass_gems_sass_3_2_12_test_sass_scss_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/scss/css_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/scss/rx_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/scss/scss_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/scss/test_helper.rb
-
-csssass_gems_sass_3_2_12_test_sassdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass
-csssass_gems_sass_3_2_12_test_sass_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/cache_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/callbacks_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/conversion_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/css2sass_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/engine_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/exec_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/extend_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/functions_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/importer_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/logger_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/mock_importer.rb \
- backends/css/gems/sass-3.2.12/test/sass/plugin_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/script_conversion_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/script_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/test_helper.rb \
- backends/css/gems/sass-3.2.12/test/sass/util_test.rb
-
-csssass_gems_sass_3_2_12_vendor_listen_spec_listen_adaptersdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters
-csssass_gems_sass_3_2_12_vendor_listen_spec_listen_adapters_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/bsd_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/darwin_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/linux_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/polling_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/windows_spec.rb
-
-csssass_gems_sass_3_2_12_test_sass_templates_subdir_nested_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir
-csssass_gems_sass_3_2_12_test_sass_templates_subdir_nested_subdir_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/_nested_partial.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
-
-csssass_gems_sass_3_2_12dir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12
-csssass_gems_sass_3_2_12_DATA = \
- backends/css/gems/sass-3.2.12/.yardopts \
- backends/css/gems/sass-3.2.12/CONTRIBUTING \
- backends/css/gems/sass-3.2.12/MIT-LICENSE \
- backends/css/gems/sass-3.2.12/README.md \
- backends/css/gems/sass-3.2.12/REVISION \
- backends/css/gems/sass-3.2.12/Rakefile \
- backends/css/gems/sass-3.2.12/VERSION \
- backends/css/gems/sass-3.2.12/VERSION_DATE \
- backends/css/gems/sass-3.2.12/VERSION_NAME \
- backends/css/gems/sass-3.2.12/init.rb
-
-csssass_gems_sass_3_2_12_test_sass_templates_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/templates/subdir
-csssass_gems_sass_3_2_12_test_sass_templates_subdir_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/templates/subdir/subdir.sass
-
-csssass_gems_sass_3_2_12_test_sass_datadir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/data
-csssass_gems_sass_3_2_12_test_sass_data_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/data/hsl-rgb.txt
-
-csssass_gems_sass_3_2_12_vendor_listen_spec_supportdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen/spec/support
-csssass_gems_sass_3_2_12_vendor_listen_spec_support_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/support/adapter_helper.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/support/directory_record_helper.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/support/fixtures_helper.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/support/listeners_helper.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/support/platform_helper.rb
-
-csssass_gems_sass_3_2_12_libdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib
-csssass_gems_sass_3_2_12_lib_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass.rb
-
-csssass_gems_sass_3_2_12_lib_sass_scriptdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/script
-csssass_gems_sass_3_2_12_lib_sass_script_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/script/arg_list.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/bool.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/color.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/css_lexer.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/css_parser.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/funcall.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/functions.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/interpolation.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/lexer.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/list.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/literal.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/null.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/number.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/operation.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/parser.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/string.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/string_interpolation.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/unary_operation.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script/variable.rb
-
-csssass_gems_sass_3_2_12_vendor_listen_lib_listendir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen/lib/listen
-csssass_gems_sass_3_2_12_vendor_listen_lib_listen_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapter.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/dependency_manager.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/directory_record.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/listener.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/multi_listener.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/turnstile.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/version.rb
-
-csssass_gems_sass_3_2_12_vendor_listendir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen
-csssass_gems_sass_3_2_12_vendor_listen_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/CHANGELOG.md \
- backends/css/gems/sass-3.2.12/vendor/listen/CONTRIBUTING.md \
- backends/css/gems/sass-3.2.12/vendor/listen/Gemfile \
- backends/css/gems/sass-3.2.12/vendor/listen/Guardfile \
- backends/css/gems/sass-3.2.12/vendor/listen/LICENSE \
- backends/css/gems/sass-3.2.12/vendor/listen/README.md \
- backends/css/gems/sass-3.2.12/vendor/listen/Rakefile \
- backends/css/gems/sass-3.2.12/vendor/listen/Vagrantfile \
- backends/css/gems/sass-3.2.12/vendor/listen/listen.gemspec
-
-csssass_gems_sass_3_2_12_test_sass_templatesdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/templates
-csssass_gems_sass_3_2_12_test_sass_templates_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/templates/_cached_import_option_partial.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/_double_import_loop2.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/_filename_fn_import.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_ibm866.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_utf8.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/_imported_content.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/_partial.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/_same_name_different_partiality.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/alt.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/basic.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/bork1.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/bork2.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/bork3.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/bork4.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/bork5.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/cached_import_option.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/compact.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/complex.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/compressed.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/double_import_loop1.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/expanded.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/filename_fn.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/if.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/import.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/import_charset.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_1_8.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_ibm866.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/import_content.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/importee.less \
- backends/css/gems/sass-3.2.12/test/sass/templates/importee.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/line_numbers.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/mixin_bork.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/mixins.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/multiline.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/nested.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork1.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork2.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork3.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork4.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/nested_import.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/nested_mixin_bork.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/options.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/parent_ref.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_partiality.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/script.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/scss_import.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/scss_importee.scss \
- backends/css/gems/sass-3.2.12/test/sass/templates/single_import_loop.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/units.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/warn.sass \
- backends/css/gems/sass-3.2.12/test/sass/templates/warn_imported.sass
-
-csssass_gems_sass_3_2_12_railsdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/rails
-csssass_gems_sass_3_2_12_rails_DATA = \
- backends/css/gems/sass-3.2.12/rails/init.rb
-
-csssass_gems_sass_3_2_12_vendor_listen_specdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen/spec
-csssass_gems_sass_3_2_12_vendor_listen_spec_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/spec_helper.rb
-
-csssass_gems_sass_3_2_12_extradir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/extra
-csssass_gems_sass_3_2_12_extra_DATA = \
- backends/css/gems/sass-3.2.12/extra/update_watch.rb
-
-csssass_gems_sass_3_2_12_test_sass_more_resultsdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/more_results
-csssass_gems_sass_3_2_12_test_sass_more_results_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/more_results/more1.css \
- backends/css/gems/sass-3.2.12/test/sass/more_results/more1_with_line_comments.css \
- backends/css/gems/sass-3.2.12/test/sass/more_results/more_import.css
-
-csssass_gems_sass_3_2_12_vendor_listen_spec_listendir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen/spec/listen
-csssass_gems_sass_3_2_12_vendor_listen_spec_listen_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapter_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/dependency_manager_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/directory_record_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/listener_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/multi_listener_spec.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/turnstile_spec.rb
-
-csssass_gems_sass_3_2_12_test_sass_utildir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/util
-csssass_gems_sass_3_2_12_test_sass_util_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/util/multibyte_string_scanner_test.rb \
- backends/css/gems/sass-3.2.12/test/sass/util/subset_map_test.rb
-
-csssass_gems_sass_3_2_12_test_sass_resultsdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/results
-csssass_gems_sass_3_2_12_test_sass_results_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/results/alt.css \
- backends/css/gems/sass-3.2.12/test/sass/results/basic.css \
- backends/css/gems/sass-3.2.12/test/sass/results/cached_import_option.css \
- backends/css/gems/sass-3.2.12/test/sass/results/compact.css \
- backends/css/gems/sass-3.2.12/test/sass/results/complex.css \
- backends/css/gems/sass-3.2.12/test/sass/results/compressed.css \
- backends/css/gems/sass-3.2.12/test/sass/results/expanded.css \
- backends/css/gems/sass-3.2.12/test/sass/results/filename_fn.css \
- backends/css/gems/sass-3.2.12/test/sass/results/if.css \
- backends/css/gems/sass-3.2.12/test/sass/results/import.css \
- backends/css/gems/sass-3.2.12/test/sass/results/import_charset.css \
- backends/css/gems/sass-3.2.12/test/sass/results/import_charset_1_8.css \
- backends/css/gems/sass-3.2.12/test/sass/results/import_charset_ibm866.css \
- backends/css/gems/sass-3.2.12/test/sass/results/import_content.css \
- backends/css/gems/sass-3.2.12/test/sass/results/line_numbers.css \
- backends/css/gems/sass-3.2.12/test/sass/results/mixins.css \
- backends/css/gems/sass-3.2.12/test/sass/results/multiline.css \
- backends/css/gems/sass-3.2.12/test/sass/results/nested.css \
- backends/css/gems/sass-3.2.12/test/sass/results/options.css \
- backends/css/gems/sass-3.2.12/test/sass/results/parent_ref.css \
- backends/css/gems/sass-3.2.12/test/sass/results/script.css \
- backends/css/gems/sass-3.2.12/test/sass/results/scss_import.css \
- backends/css/gems/sass-3.2.12/test/sass/results/scss_importee.css \
- backends/css/gems/sass-3.2.12/test/sass/results/units.css \
- backends/css/gems/sass-3.2.12/test/sass/results/warn.css \
- backends/css/gems/sass-3.2.12/test/sass/results/warn_imported.css
-
-csssass_gems_sass_3_2_12_lib_sass_treedir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/tree
-csssass_gems_sass_3_2_12_lib_sass_tree_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/tree/charset_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/comment_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/content_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/css_import_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/debug_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/directive_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/each_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/extend_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/for_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/function_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/if_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/import_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/media_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/mixin_def_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/mixin_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/prop_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/return_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/root_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/rule_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/supports_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/trace_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/variable_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/warn_node.rb \
- backends/css/gems/sass-3.2.12/lib/sass/tree/while_node.rb
-
-csssass_gems_sass_3_2_12_test_sass_results_subdir_nested_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/results/subdir/nested_subdir
-csssass_gems_sass_3_2_12_test_sass_results_subdir_nested_subdir_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/results/subdir/nested_subdir/nested_subdir.css
-
-csssass_gems_sass_3_2_12_lib_sass_cache_storesdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/cache_stores
-csssass_gems_sass_3_2_12_lib_sass_cache_stores_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/cache_stores/base.rb \
- backends/css/gems/sass-3.2.12/lib/sass/cache_stores/chain.rb \
- backends/css/gems/sass-3.2.12/lib/sass/cache_stores/filesystem.rb \
- backends/css/gems/sass-3.2.12/lib/sass/cache_stores/memory.rb \
- backends/css/gems/sass-3.2.12/lib/sass/cache_stores/null.rb
-
-csssass_gems_sass_3_2_12_lib_sass_utildir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/util
-csssass_gems_sass_3_2_12_lib_sass_util_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/util/multibyte_string_scanner.rb \
- backends/css/gems/sass-3.2.12/lib/sass/util/subset_map.rb \
- backends/css/gems/sass-3.2.12/lib/sass/util/test.rb
-
-csssass_gems_sass_3_2_12_lib_sass_loggerdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/logger
-csssass_gems_sass_3_2_12_lib_sass_logger_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/logger/base.rb \
- backends/css/gems/sass-3.2.12/lib/sass/logger/log_level.rb
-
-csssass_gems_sass_3_2_12_test_sass_more_templatesdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/more_templates
-csssass_gems_sass_3_2_12_test_sass_more_templates_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/more_templates/_more_partial.sass \
- backends/css/gems/sass-3.2.12/test/sass/more_templates/more1.sass \
- backends/css/gems/sass-3.2.12/test/sass/more_templates/more_import.sass
-
-csssass_gems_sass_3_2_12_bindir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/bin
-csssass_gems_sass_3_2_12_bin_DATA = \
- backends/css/gems/sass-3.2.12/bin/sass \
- backends/css/gems/sass-3.2.12/bin/sass-convert \
- backends/css/gems/sass-3.2.12/bin/scss
-
-csssass_gems_sass_3_2_12_lib_sass_importersdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/importers
-csssass_gems_sass_3_2_12_lib_sass_importers_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/importers/base.rb \
- backends/css/gems/sass-3.2.12/lib/sass/importers/filesystem.rb
-
-csssass_gems_sass_3_2_12_lib_sass_scssdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/scss
-csssass_gems_sass_3_2_12_lib_sass_scss_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/scss/css_parser.rb \
- backends/css/gems/sass-3.2.12/lib/sass/scss/parser.rb \
- backends/css/gems/sass-3.2.12/lib/sass/scss/rx.rb \
- backends/css/gems/sass-3.2.12/lib/sass/scss/script_lexer.rb \
- backends/css/gems/sass-3.2.12/lib/sass/scss/script_parser.rb \
- backends/css/gems/sass-3.2.12/lib/sass/scss/static_parser.rb
-
-csssass_gems_sass_3_2_12_lib_sass_plugindir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/plugin
-csssass_gems_sass_3_2_12_lib_sass_plugin_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/plugin/compiler.rb \
- backends/css/gems/sass-3.2.12/lib/sass/plugin/configuration.rb \
- backends/css/gems/sass-3.2.12/lib/sass/plugin/generic.rb \
- backends/css/gems/sass-3.2.12/lib/sass/plugin/merb.rb \
- backends/css/gems/sass-3.2.12/lib/sass/plugin/rack.rb \
- backends/css/gems/sass-3.2.12/lib/sass/plugin/rails.rb \
- backends/css/gems/sass-3.2.12/lib/sass/plugin/staleness_checker.rb
-
-csssass_gems_sass_3_2_12_vendor_listen_lib_listen_adaptersdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters
-csssass_gems_sass_3_2_12_vendor_listen_lib_listen_adapters_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/bsd.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/darwin.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/linux.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/polling.rb \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/windows.rb
-
-csssass_gems_sass_3_2_12_lib_sass_selectordir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass/selector
-csssass_gems_sass_3_2_12_lib_sass_selector_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/selector/abstract_sequence.rb \
- backends/css/gems/sass-3.2.12/lib/sass/selector/comma_sequence.rb \
- backends/css/gems/sass-3.2.12/lib/sass/selector/sequence.rb \
- backends/css/gems/sass-3.2.12/lib/sass/selector/simple.rb \
- backends/css/gems/sass-3.2.12/lib/sass/selector/simple_sequence.rb
-
-csssass_gems_sass_3_2_12_lib_sassdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/lib/sass
-csssass_gems_sass_3_2_12_lib_sass_DATA = \
- backends/css/gems/sass-3.2.12/lib/sass/cache_stores.rb \
- backends/css/gems/sass-3.2.12/lib/sass/callbacks.rb \
- backends/css/gems/sass-3.2.12/lib/sass/css.rb \
- backends/css/gems/sass-3.2.12/lib/sass/engine.rb \
- backends/css/gems/sass-3.2.12/lib/sass/environment.rb \
- backends/css/gems/sass-3.2.12/lib/sass/error.rb \
- backends/css/gems/sass-3.2.12/lib/sass/exec.rb \
- backends/css/gems/sass-3.2.12/lib/sass/importers.rb \
- backends/css/gems/sass-3.2.12/lib/sass/logger.rb \
- backends/css/gems/sass-3.2.12/lib/sass/media.rb \
- backends/css/gems/sass-3.2.12/lib/sass/plugin.rb \
- backends/css/gems/sass-3.2.12/lib/sass/railtie.rb \
- backends/css/gems/sass-3.2.12/lib/sass/repl.rb \
- backends/css/gems/sass-3.2.12/lib/sass/root.rb \
- backends/css/gems/sass-3.2.12/lib/sass/script.rb \
- backends/css/gems/sass-3.2.12/lib/sass/scss.rb \
- backends/css/gems/sass-3.2.12/lib/sass/selector.rb \
- backends/css/gems/sass-3.2.12/lib/sass/shared.rb \
- backends/css/gems/sass-3.2.12/lib/sass/supports.rb \
- backends/css/gems/sass-3.2.12/lib/sass/util.rb \
- backends/css/gems/sass-3.2.12/lib/sass/version.rb
-
-csssass_gems_sass_3_2_12_test_sass_fixturesdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test/sass/fixtures
-csssass_gems_sass_3_2_12_test_sass_fixtures_DATA = \
- backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.css \
- backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.scss
-
-csssass_gems_sass_3_2_12_testdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/test
-csssass_gems_sass_3_2_12_test_DATA = \
- backends/css/gems/sass-3.2.12/test/Gemfile \
- backends/css/gems/sass-3.2.12/test/Gemfile.lock \
- backends/css/gems/sass-3.2.12/test/test_helper.rb
-
-csssass_gems_sass_3_2_12_vendor_listen_libdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.2.12/vendor/listen/lib
-csssass_gems_sass_3_2_12_vendor_listen_lib_DATA = \
- backends/css/gems/sass-3.2.12/vendor/listen/lib/listen.rb
+csssass_gems_sass_3_4_9dir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9
+csssass_gems_sass_3_4_9_DATA = \
+ backends/css/gems/sass-3.4.9/.yardopts \
+ backends/css/gems/sass-3.4.9/CONTRIBUTING \
+ backends/css/gems/sass-3.4.9/MIT-LICENSE \
+ backends/css/gems/sass-3.4.9/README.md \
+ backends/css/gems/sass-3.4.9/REVISION \
+ backends/css/gems/sass-3.4.9/Rakefile \
+ backends/css/gems/sass-3.4.9/VERSION \
+ backends/css/gems/sass-3.4.9/VERSION_DATE \
+ backends/css/gems/sass-3.4.9/VERSION_NAME \
+ backends/css/gems/sass-3.4.9/init.rb
+
+csssass_gems_sass_3_4_9_test_sass_more_resultsdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/more_results
+csssass_gems_sass_3_4_9_test_sass_more_results_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/more_results/more1.css \
+ backends/css/gems/sass-3.4.9/test/sass/more_results/more1_with_line_comments.css \
+ backends/css/gems/sass-3.4.9/test/sass/more_results/more_import.css
+
+csssass_gems_sass_3_4_9_vendor_listen_spec_listen_adaptersdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters
+csssass_gems_sass_3_4_9_vendor_listen_spec_listen_adapters_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/bsd_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/darwin_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/linux_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/polling_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/windows_spec.rb
+
+csssass_gems_sass_3_4_9_lib_sass_loggerdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/logger
+csssass_gems_sass_3_4_9_lib_sass_logger_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/logger/base.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/logger/log_level.rb
+
+csssass_gems_sass_3_4_9_lib_sass_script_treedir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/script/tree
+csssass_gems_sass_3_4_9_lib_sass_script_tree_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/funcall.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/interpolation.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/list_literal.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/literal.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/map_literal.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/operation.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/selector.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/string_interpolation.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/unary_operation.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree/variable.rb
+
+csssass_gems_sass_3_4_9_lib_sass_script_valuedir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/script/value
+csssass_gems_sass_3_4_9_lib_sass_script_value_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/arg_list.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/base.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/bool.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/color.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/helpers.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/list.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/map.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/null.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/number.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value/string.rb
+
+csssass_gems_sass_3_4_9_lib_sass_plugindir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/plugin
+csssass_gems_sass_3_4_9_lib_sass_plugin_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin/compiler.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin/configuration.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin/generic.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin/merb.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin/rack.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin/rails.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin/staleness_checker.rb
+
+csssass_gems_sass_3_4_9_testdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test
+csssass_gems_sass_3_4_9_test_DATA = \
+ backends/css/gems/sass-3.4.9/test/test_helper.rb
+
+csssass_gems_sass_3_4_9_bindir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/bin
+csssass_gems_sass_3_4_9_bin_DATA = \
+ backends/css/gems/sass-3.4.9/bin/sass \
+ backends/css/gems/sass-3.4.9/bin/sass-convert \
+ backends/css/gems/sass-3.4.9/bin/scss
+
+csssass_gems_sass_3_4_9_test_sass_datadir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/data
+csssass_gems_sass_3_4_9_test_sass_data_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/data/hsl-rgb.txt
+
+csssass_gems_sass_3_4_9_lib_sass_scssdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/scss
+csssass_gems_sass_3_4_9_lib_sass_scss_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/scss/css_parser.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/scss/parser.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/scss/rx.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/scss/script_lexer.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/scss/script_parser.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/scss/static_parser.rb
+
+csssass_gems_sass_3_4_9_lib_sassdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass
+csssass_gems_sass_3_4_9_lib_sass_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/cache_stores.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/callbacks.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/css.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/engine.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/environment.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/error.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/exec.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/features.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/importers.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/logger.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/media.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/plugin.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/railtie.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/repl.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/root.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/scss.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/selector.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/shared.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/stack.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/supports.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/util.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/version.rb
+
+csssass_gems_sass_3_4_9_test_sass_templatesdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/templates
+csssass_gems_sass_3_4_9_test_sass_templates_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_cached_import_option_partial.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_double_import_loop2.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_filename_fn_import.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_imported_charset_ibm866.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_imported_charset_utf8.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_imported_content.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_partial.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/_same_name_different_partiality.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/alt.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/basic.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/bork1.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/bork2.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/bork3.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/bork4.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/bork5.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/cached_import_option.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/compact.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/complex.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/compressed.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/double_import_loop1.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/expanded.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/filename_fn.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/if.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/import.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/import_charset.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/import_charset_1_8.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/import_charset_ibm866.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/import_content.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/importee.less \
+ backends/css/gems/sass-3.4.9/test/sass/templates/importee.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/line_numbers.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/mixin_bork.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/mixins.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/multiline.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/nested.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork1.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork2.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork3.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork4.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/nested_import.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/nested_mixin_bork.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/options.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/parent_ref.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_ext.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_ext.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_partiality.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/script.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/scss_import.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/scss_importee.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/single_import_loop.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/units.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/warn.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/warn_imported.sass
+
+csssass_gems_sass_3_4_9_vendor_listen_lib_listendir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen/lib/listen
+csssass_gems_sass_3_4_9_vendor_listen_lib_listen_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapter.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/directory_record.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/listener.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/turnstile.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/version.rb
+
+csssass_gems_sass_3_4_9_lib_sass_cache_storesdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/cache_stores
+csssass_gems_sass_3_4_9_lib_sass_cache_stores_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/cache_stores/base.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/cache_stores/chain.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/cache_stores/filesystem.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/cache_stores/memory.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/cache_stores/null.rb
+
+csssass_gems_sass_3_4_9_lib_sass_tree_visitorsdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/tree/visitors
+csssass_gems_sass_3_4_9_lib_sass_tree_visitors_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/base.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/check_nesting.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/convert.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/cssize.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/deep_copy.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/extend.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/perform.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/set_options.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/to_css.rb
+
+csssass_gems_sass_3_4_9_test_sass_templates_subdir_nested_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/templates/subdir/nested_subdir
+csssass_gems_sass_3_4_9_test_sass_templates_subdir_nested_subdir_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/templates/subdir/nested_subdir/_nested_partial.sass \
+ backends/css/gems/sass-3.4.9/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
+
+csssass_gems_sass_3_4_9_test_sass_resultsdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/results
+csssass_gems_sass_3_4_9_test_sass_results_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/results/alt.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/basic.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/cached_import_option.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/compact.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/complex.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/compressed.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/expanded.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/filename_fn.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/if.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/import.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/import_charset.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/import_charset_1_8.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/import_charset_ibm866.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/import_content.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/line_numbers.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/mixins.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/multiline.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/nested.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/options.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/parent_ref.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/script.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/scss_import.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/scss_importee.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/units.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/warn.css \
+ backends/css/gems/sass-3.4.9/test/sass/results/warn_imported.css
+
+csssass_gems_sass_3_4_9_lib_sass_execdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/exec
+csssass_gems_sass_3_4_9_lib_sass_exec_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/exec/base.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/exec/sass_convert.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/exec/sass_scss.rb
+
+csssass_gems_sass_3_4_9_test_sass_scssdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/scss
+csssass_gems_sass_3_4_9_test_sass_scss_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/scss/css_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/scss/rx_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/scss/scss_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/scss/test_helper.rb
+
+csssass_gems_sass_3_4_9_railsdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/rails
+csssass_gems_sass_3_4_9_rails_DATA = \
+ backends/css/gems/sass-3.4.9/rails/init.rb
+
+csssass_gems_sass_3_4_9_vendor_listen_specdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen/spec
+csssass_gems_sass_3_4_9_vendor_listen_spec_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/spec_helper.rb
+
+csssass_gems_sass_3_4_9_lib_sass_utildir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/util
+csssass_gems_sass_3_4_9_lib_sass_util_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/util/cross_platform_random.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/util/multibyte_string_scanner.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/util/normalized_map.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/util/ordered_hash.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/util/subset_map.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/util/test.rb
+
+csssass_gems_sass_3_4_9_test_sassdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass
+csssass_gems_sass_3_4_9_test_sass_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/cache_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/callbacks_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/compiler_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/conversion_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/css2sass_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/encoding_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/engine_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/exec_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/extend_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/functions_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/importer_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/logger_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/mock_importer.rb \
+ backends/css/gems/sass-3.4.9/test/sass/plugin_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/script_conversion_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/script_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/source_map_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/superselector_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/test_helper.rb \
+ backends/css/gems/sass-3.4.9/test/sass/util_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/value_helpers_test.rb
+
+csssass_gems_sass_3_4_9_libdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib
+csssass_gems_sass_3_4_9_lib_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass.rb
+
+csssass_gems_sass_3_4_9_vendor_listendir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen
+csssass_gems_sass_3_4_9_vendor_listen_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/CHANGELOG.md \
+ backends/css/gems/sass-3.4.9/vendor/listen/CONTRIBUTING.md \
+ backends/css/gems/sass-3.4.9/vendor/listen/Gemfile \
+ backends/css/gems/sass-3.4.9/vendor/listen/Guardfile \
+ backends/css/gems/sass-3.4.9/vendor/listen/LICENSE \
+ backends/css/gems/sass-3.4.9/vendor/listen/README.md \
+ backends/css/gems/sass-3.4.9/vendor/listen/Rakefile \
+ backends/css/gems/sass-3.4.9/vendor/listen/Vagrantfile \
+ backends/css/gems/sass-3.4.9/vendor/listen/listen.gemspec
+
+csssass_gems_sass_3_4_9_lib_sass_treedir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/tree
+csssass_gems_sass_3_4_9_lib_sass_tree_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/at_root_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/charset_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/comment_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/content_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/css_import_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/debug_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/directive_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/each_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/error_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/extend_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/for_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/function_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/if_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/import_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/keyframe_rule_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/media_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_def_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/prop_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/return_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/root_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/rule_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/supports_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/trace_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/variable_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/warn_node.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/tree/while_node.rb
+
+csssass_gems_sass_3_4_9_lib_sass_sourcedir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/source
+csssass_gems_sass_3_4_9_lib_sass_source_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/source/map.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/source/position.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/source/range.rb
+
+csssass_gems_sass_3_4_9_test_sass_utildir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/util
+csssass_gems_sass_3_4_9_test_sass_util_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/util/multibyte_string_scanner_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/util/normalized_map_test.rb \
+ backends/css/gems/sass-3.4.9/test/sass/util/subset_map_test.rb
+
+csssass_gems_sass_3_4_9_test_sass_templates_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/templates/subdir
+csssass_gems_sass_3_4_9_test_sass_templates_subdir_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up1.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up2.scss \
+ backends/css/gems/sass-3.4.9/test/sass/templates/subdir/subdir.sass
+
+csssass_gems_sass_3_4_9_test_sass_fixturesdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/fixtures
+csssass_gems_sass_3_4_9_test_sass_fixtures_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/fixtures/test_staleness_check_across_importers.css \
+ backends/css/gems/sass-3.4.9/test/sass/fixtures/test_staleness_check_across_importers.scss
+
+csssass_gems_sass_3_4_9_extradir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/extra
+csssass_gems_sass_3_4_9_extra_DATA = \
+ backends/css/gems/sass-3.4.9/extra/update_watch.rb
+
+csssass_gems_sass_3_4_9_test_sass_more_templatesdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/more_templates
+csssass_gems_sass_3_4_9_test_sass_more_templates_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/more_templates/_more_partial.sass \
+ backends/css/gems/sass-3.4.9/test/sass/more_templates/more1.sass \
+ backends/css/gems/sass-3.4.9/test/sass/more_templates/more_import.sass
+
+csssass_gems_sass_3_4_9_vendor_listen_libdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen/lib
+csssass_gems_sass_3_4_9_vendor_listen_lib_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen.rb
+
+csssass_gems_sass_3_4_9_test_sass_results_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/results/subdir
+csssass_gems_sass_3_4_9_test_sass_results_subdir_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/results/subdir/subdir.css
+
+csssass_gems_sass_3_4_9_lib_sass_scriptdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/script
+csssass_gems_sass_3_4_9_lib_sass_script_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/script/css_lexer.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/css_parser.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/functions.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/lexer.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/parser.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/tree.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/script/value.rb
+
+csssass_gems_sass_3_4_9_lib_sass_importersdir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/importers
+csssass_gems_sass_3_4_9_lib_sass_importers_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/importers/base.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/importers/filesystem.rb
+
+csssass_gems_sass_3_4_9_vendor_listen_spec_listendir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen/spec/listen
+csssass_gems_sass_3_4_9_vendor_listen_spec_listen_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapter_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/directory_record_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/listener_spec.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/turnstile_spec.rb
+
+csssass_gems_sass_3_4_9_test_sass_results_subdir_nested_subdirdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/test/sass/results/subdir/nested_subdir
+csssass_gems_sass_3_4_9_test_sass_results_subdir_nested_subdir_DATA = \
+ backends/css/gems/sass-3.4.9/test/sass/results/subdir/nested_subdir/nested_subdir.css
+
+csssass_gems_sass_3_4_9_vendor_listen_spec_supportdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen/spec/support
+csssass_gems_sass_3_4_9_vendor_listen_spec_support_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/support/adapter_helper.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/support/directory_record_helper.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/support/fixtures_helper.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/support/listeners_helper.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/spec/support/platform_helper.rb
+
+csssass_gems_sass_3_4_9_lib_sass_selectordir = $(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/lib/sass/selector
+csssass_gems_sass_3_4_9_lib_sass_selector_DATA = \
+ backends/css/gems/sass-3.4.9/lib/sass/selector/abstract_sequence.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/selector/comma_sequence.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/selector/pseudo.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/selector/sequence.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/selector/simple.rb \
+ backends/css/gems/sass-3.4.9/lib/sass/selector/simple_sequence.rb
+
+csssass_gems_sass_3_4_9_vendor_listen_lib_listen_adaptersdir =
$(GCA_RBBACKENDS_DIR)/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters
+csssass_gems_sass_3_4_9_vendor_listen_lib_listen_adapters_DATA = \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/bsd.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/darwin.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/linux.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/polling.rb \
+ backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/windows.rb
endif
EXTRA_DIST += \
- $(csssass_gems_sass_3_2_12_test_sass_results_subdir_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_tree_visitors_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_scss_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_spec_listen_adapters_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_templates_subdir_nested_subdir_DATA) \
- $(csssass_gems_sass_3_2_12_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_templates_subdir_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_data_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_spec_support_DATA) \
- $(csssass_gems_sass_3_2_12_lib_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_script_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_lib_listen_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_templates_DATA) \
- $(csssass_gems_sass_3_2_12_rails_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_spec_DATA) \
- $(csssass_gems_sass_3_2_12_extra_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_more_results_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_spec_listen_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_util_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_results_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_tree_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_results_subdir_nested_subdir_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_cache_stores_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_util_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_logger_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_more_templates_DATA) \
- $(csssass_gems_sass_3_2_12_bin_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_importers_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_scss_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_plugin_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_lib_listen_adapters_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_selector_DATA) \
- $(csssass_gems_sass_3_2_12_lib_sass_DATA) \
- $(csssass_gems_sass_3_2_12_test_sass_fixtures_DATA) \
- $(csssass_gems_sass_3_2_12_test_DATA) \
- $(csssass_gems_sass_3_2_12_vendor_listen_lib_DATA)
+ $(csssass_gems_sass_3_4_9_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_more_results_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_spec_listen_adapters_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_logger_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_script_tree_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_script_value_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_plugin_DATA) \
+ $(csssass_gems_sass_3_4_9_test_DATA) \
+ $(csssass_gems_sass_3_4_9_bin_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_data_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_scss_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_templates_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_lib_listen_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_cache_stores_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_tree_visitors_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_templates_subdir_nested_subdir_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_results_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_exec_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_scss_DATA) \
+ $(csssass_gems_sass_3_4_9_rails_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_spec_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_util_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_tree_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_source_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_util_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_templates_subdir_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_fixtures_DATA) \
+ $(csssass_gems_sass_3_4_9_extra_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_more_templates_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_lib_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_results_subdir_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_script_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_importers_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_spec_listen_DATA) \
+ $(csssass_gems_sass_3_4_9_test_sass_results_subdir_nested_subdir_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_spec_support_DATA) \
+ $(csssass_gems_sass_3_4_9_lib_sass_selector_DATA) \
+ $(csssass_gems_sass_3_4_9_vendor_listen_lib_listen_adapters_DATA)
diff --git a/backends/css/gems/sass-3.2.12/.yardopts b/backends/css/gems/sass-3.4.9/.yardopts
similarity index 100%
rename from backends/css/gems/sass-3.2.12/.yardopts
rename to backends/css/gems/sass-3.4.9/.yardopts
diff --git a/backends/css/gems/sass-3.4.9/CONTRIBUTING b/backends/css/gems/sass-3.4.9/CONTRIBUTING
new file mode 100644
index 0000000..f76d92d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/CONTRIBUTING
@@ -0,0 +1,3 @@
+Contributions are welcomed. Please see the following sites for guidelines:
+
+ http://sass-lang.com/community#Contribute
diff --git a/backends/css/gems/sass-3.4.9/MIT-LICENSE b/backends/css/gems/sass-3.4.9/MIT-LICENSE
new file mode 100644
index 0000000..9854495
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2006-2014 Hampton Catlin, Natalie Weizenbaum, and Chris Eppstein
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/backends/css/gems/sass-3.4.9/README.md b/backends/css/gems/sass-3.4.9/README.md
new file mode 100644
index 0000000..c4fee13
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/README.md
@@ -0,0 +1,221 @@
+# Sass [![Gem Version](https://badge.fury.io/rb/sass.png)](http://badge.fury.io/rb/sass) [![Inline
docs](http://inch-ci.org/github/sass/sass.svg)](http://inch-ci.org/github/sass/sass)
+
+**Sass makes CSS fun again**. Sass is an extension of CSS3,
+adding nested rules, variables, mixins, selector inheritance, and more.
+It's translated to well-formatted, standard CSS
+using the command line tool or a web-framework plugin.
+
+Sass has two syntaxes. The new main syntax (as of Sass 3)
+is known as "SCSS" (for "Sassy CSS"),
+and is a superset of CSS3's syntax.
+This means that every valid CSS3 stylesheet is valid SCSS as well.
+SCSS files use the extension `.scss`.
+
+The second, older syntax is known as the indented syntax (or just "Sass").
+Inspired by Haml's terseness, it's intended for people
+who prefer conciseness over similarity to CSS.
+Instead of brackets and semicolons,
+it uses the indentation of lines to specify blocks.
+Although no longer the primary syntax,
+the indented syntax will continue to be supported.
+Files in the indented syntax use the extension `.sass`.
+
+## Using
+
+Sass can be used from the command line
+or as part of a web framework.
+The first step is to install the gem:
+
+ gem install sass
+
+After you convert some CSS to Sass, you can run
+
+ sass style.scss
+
+to compile it back to CSS.
+For more information on these commands, check out
+
+ sass --help
+
+To install Sass in Rails 2,
+just add `config.gem "sass"` to `config/environment.rb`.
+In Rails 3, add `gem "sass"` to your Gemfile instead.
+`.sass` or `.scss` files should be placed in `public/stylesheets/sass`,
+where they'll be automatically compiled
+to corresponding CSS files in `public/stylesheets` when needed
+(the Sass template directory is customizable...
+see [the Sass reference](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#template_location-option)
for details).
+
+Sass can also be used with any Rack-enabled web framework.
+To do so, just add
+
+```ruby
+require 'sass/plugin/rack'
+use Sass::Plugin::Rack
+```
+
+to `config.ru`.
+Then any Sass files in `public/stylesheets/sass`
+will be compiled into CSS files in `public/stylesheets` on every request.
+
+To use Sass programmatically,
+check out the [YARD documentation](http://sass-lang.com/documentation/file.SASS_REFERENCE.html#using_sass).
+
+## Formatting
+
+Sass is an extension of CSS
+that adds power and elegance to the basic language.
+It allows you to use [variables][vars], [nested rules][nested],
+[mixins][mixins], [inline imports][imports],
+and more, all with a fully CSS-compatible syntax.
+Sass helps keep large stylesheets well-organized,
+and get small stylesheets up and running quickly,
+particularly with the help of
+[the Compass style library](http://compass-style.org).
+
+[vars]: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#variables_
+[nested]: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#nested_rules
+[mixins]: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#mixins
+[imports]: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#import
+
+Sass has two syntaxes.
+The one presented here, known as "SCSS" (for "Sassy CSS"),
+is fully CSS-compatible.
+The other (older) syntax, known as the indented syntax or just "Sass",
+is whitespace-sensitive and indentation-based.
+For more information, see the [reference documentation][syntax].
+
+[syntax]: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#syntax
+
+To run the following examples and see the CSS they produce,
+put them in a file called `test.scss` and run `sass test.scss`.
+
+### Nesting
+
+Sass avoids repetition by nesting selectors within one another.
+The same thing works for properties.
+
+```scss
+table.hl {
+ margin: 2em 0;
+ td.ln { text-align: right; }
+}
+
+li {
+ font: {
+ family: serif;
+ weight: bold;
+ size: 1.2em;
+ }
+}
+```
+
+### Variables
+
+Use the same color all over the place?
+Need to do some math with height and width and text size?
+Sass supports variables, math operations, and many useful functions.
+
+```scss
+$blue: #3bbfce;
+$margin: 16px;
+
+.content_navigation {
+ border-color: $blue;
+ color: darken($blue, 10%);
+}
+
+.border {
+ padding: $margin / 2;
+ margin: $margin / 2;
+ border-color: $blue;
+}
+```
+
+### Mixins
+
+Even more powerful than variables,
+mixins allow you to re-use whole chunks of CSS,
+properties or selectors.
+You can even give them arguments.
+
+```scss
+ mixin table-scaffolding {
+ th {
+ text-align: center;
+ font-weight: bold;
+ }
+ td, th { padding: 2px; }
+}
+
+ mixin left($dist) {
+ float: left;
+ margin-left: $dist;
+}
+
+#data {
+ @include left(10px);
+ @include table-scaffolding;
+}
+```
+
+A comprehensive list of features is available
+in the [Sass reference](http://sass-lang.com/documentation/file.SASS_REFERENCE.html).
+
+## Executables
+
+The Sass gem includes several executables that are useful
+for dealing with Sass from the command line.
+
+### `sass`
+
+The `sass` executable transforms a source Sass file into CSS.
+See `sass --help` for further information and options.
+
+### `sass-convert`
+
+The `sass-convert` executable converts between CSS, Sass, and SCSS.
+When converting from CSS to Sass or SCSS,
+nesting is applied where appropriate.
+See `sass-convert --help` for further information and options.
+
+### Running locally
+
+To run the Sass executables from a source checkout instead of from rubygems:
+
+```
+$ cd <SASS_CHECKOUT_DIRECTORY>
+$ bundle
+$ bundle exec sass ...
+$ bundle exec scss ...
+$ bundle exec sass-convert ...
+```
+
+## Authors
+
+Sass was envisioned by [Hampton Catlin](http://www.hamptoncatlin.com)
+(@hcatlin). However, Hampton doesn't even know his way around the code anymore
+and now occasionally consults on the language issues. Hampton lives in San
+Francisco, California and works as VP of Technology
+at [Moovweb](http://www.moovweb.com/).
+
+[Natalie Weizenbaum](http://nex-3.com) is the primary developer and architect of
+Sass. Her hard work has kept the project alive by endlessly answering forum
+posts, fixing bugs, refactoring, finding speed improvements, writing
+documentation, implementing new features, and getting Hampton coffee (a fitting
+task for a girl genius). Natalie lives in Seattle, Washington and works on
+[Dart](http://dartlang.org) application libraries at Google.
+
+[Chris Eppstein](http://acts-as-architect.blogspot.com) is a core contributor to
+Sass and the creator of Compass, the first Sass-based framework. Chris focuses
+on making Sass more powerful, easy to use, and on ways to speed its adoption
+through the web development community. Chris lives in San Jose, California with
+his wife and daughter. He is an Engineer for
+[LinkedIn.com](http://linkedin.com), where one of his responsibilities is to
+maintain Sass & Compass.
+
+If you use this software, you must pay Hampton a compliment. And buy Natalie
+some candy. Maybe pet a kitten. Yeah. Pet that kitty.
+
+Beyond that, the implementation is licensed under the MIT License.
+Okay, fine, I guess that means compliments aren't __required__.
diff --git a/backends/css/gems/sass-3.2.12/REVISION b/backends/css/gems/sass-3.4.9/REVISION
similarity index 100%
rename from backends/css/gems/sass-3.2.12/REVISION
rename to backends/css/gems/sass-3.4.9/REVISION
diff --git a/backends/css/gems/sass-3.4.9/Rakefile b/backends/css/gems/sass-3.4.9/Rakefile
new file mode 100644
index 0000000..07d076f
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/Rakefile
@@ -0,0 +1,370 @@
+require 'rubygems/package'
+
+# ----- Utility Functions -----
+
+def scope(path)
+ File.join(File.dirname(__FILE__), path)
+end
+
+# ----- Default: Testing ------
+
+task :default => :test
+
+require 'rake/testtask'
+
+Rake::TestTask.new do |t|
+ t.libs << 'test'
+ test_files = FileList[scope('test/**/*_test.rb')]
+ test_files.exclude(scope('test/rails/*'))
+ test_files.exclude(scope('test/plugins/*'))
+ t.test_files = test_files
+ t.verbose = true
+end
+
+# ----- Code Style Enforcement -----
+
+if RUBY_VERSION !~ /^(1\.8)/ && (ENV.has_key?("RUBOCOP") && ENV["RUBOCOP"] == "true" ||
!(ENV.has_key?("RUBOCOP") || ENV.has_key?("TEST")))
+ require 'rubocop/rake_task'
+ Rubocop::RakeTask.new do |t|
+ t.patterns = FileList["lib/**/*"]
+ end
+else
+ task :rubocop do
+ puts "Skipping rubocop style check."
+ if !ENV.has_key?("RUBOCOP")
+ puts "Passing this check is required in order for your patch to be accepted."
+ puts "Use ruby 1.9 or greater and then run the style check with: rake rubocop"
+ end
+ end
+end
+
+task :test => :rubocop
+
+# ----- Packaging -----
+
+# Don't use Rake::GemPackageTast because we want prerequisites to run
+# before we load the gemspec.
+desc "Build all the packages."
+task :package => [:revision_file, :date_file, :submodules, :permissions] do
+ version = get_version
+ File.open(scope('VERSION'), 'w') {|f| f.puts(version)}
+ load scope('sass.gemspec')
+ Gem::Package.build(SASS_GEMSPEC)
+ sh %{git checkout VERSION}
+
+ pkg = "#{SASS_GEMSPEC.name}-#{SASS_GEMSPEC.version}"
+ mkdir_p "pkg"
+ verbose(true) {mv "#{pkg}.gem", "pkg/#{pkg}.gem"}
+
+ sh %{rm -f pkg/#{pkg}.tar.gz}
+ verbose(false) {SASS_GEMSPEC.files.each {|f| sh %{tar rf pkg/#{pkg}.tar #{f}}}}
+ sh %{gzip pkg/#{pkg}.tar}
+end
+
+task :permissions do
+ sh %{chmod -R a+rx bin}
+ sh %{chmod -R a+r .}
+ require 'shellwords'
+ Dir.glob('test/**/*_test.rb') do |file|
+ next if file =~ %r{^test/haml/spec/}
+ sh %{chmod a+rx #{file}}
+ end
+end
+
+task :revision_file do
+ require scope('lib/sass')
+
+ release = Rake.application.top_level_tasks.include?('release') || File.exist?(scope('EDGE_GEM_VERSION'))
+ if Sass.version[:rev] && !release
+ File.open(scope('REVISION'), 'w') { |f| f.puts Sass.version[:rev] }
+ elsif release
+ File.open(scope('REVISION'), 'w') { |f| f.puts "(release)" }
+ else
+ File.open(scope('REVISION'), 'w') { |f| f.puts "(unknown)" }
+ end
+end
+
+task :date_file do
+ File.open(scope('VERSION_DATE'), 'w') do |f|
+ f.puts Time.now.utc.strftime('%d %B %Y %T %Z')
+ end
+end
+
+# We also need to get rid of this file after packaging.
+at_exit do
+ File.delete(scope('REVISION')) rescue nil
+ File.delete(scope('VERSION_DATE')) rescue nil
+end
+
+desc "Install Sass as a gem. Use SUDO=1 to install with sudo."
+task :install => [:package] do
+ gem = RUBY_PLATFORM =~ /java/ ? 'jgem' : 'gem'
+ sh %{#{'sudo ' if ENV["SUDO"]}#{gem} install --no-ri pkg/sass-#{get_version}}
+end
+
+desc "Release a new Sass package to RubyGems.org."
+task :release => [:check_release, :package] do
+ name = File.read(scope("VERSION_NAME")).strip
+ version = File.read(scope("VERSION")).strip
+ sh %{gem push pkg/sass-#{version}.gem}
+end
+
+# Ensures that the VERSION file has been updated for a new release.
+task :check_release do
+ version = File.read(scope("VERSION")).strip
+ raise "There have been changes since current version (#{version})" if changed_since?(version)
+ raise "VERSION_NAME must not be 'Bleeding Edge'" if File.read(scope("VERSION_NAME")) == "Bleeding Edge"
+end
+
+# Reads a password from the command line.
+#
+# @param name [String] The prompt to use to read the password
+def read_password(prompt)
+ require 'readline'
+ system "stty -echo"
+ Readline.readline("#{prompt}: ").strip
+ensure
+ system "stty echo"
+ puts
+end
+
+# Returns whether or not the repository, or specific files,
+# has/have changed since a given revision.
+#
+# @param rev [String] The revision to check against
+# @param files [Array<String>] The files to check.
+# If this is empty, checks the entire repository
+def changed_since?(rev, *files)
+ IO.popen("git diff --exit-code #{rev} #{files.join(' ')}") {}
+ return !$?.success?
+end
+
+task :submodules do
+ if File.exist?(File.dirname(__FILE__) + "/.git")
+ sh %{git submodule sync}
+ sh %{git submodule update --init}
+ elsif !File.exist?(File.dirname(__FILE__) + "/vendor/listen/lib")
+ warn <<WARN
+WARNING: vendor/listen doesn't exist, and this isn't a git repository so
+I can't get it automatically!
+WARN
+ end
+end
+
+task :release_edge do
+ ensure_git_cleanup do
+ puts "#{'=' * 50} Running rake release_edge"
+
+ sh %{git checkout master}
+ sh %{git reset --hard origin/master}
+ sh %{rake package}
+ version = get_version
+ if version.include?('.rc.')
+ puts "#{'=' * 20} Not releasing edge gem for RC version"
+ next
+ end
+
+ sh %{gem push pkg/sass-#{version}.gem}
+ end
+end
+
+# Get the version string. If this is being installed from Git,
+# this includes the proper prerelease version.
+def get_version
+ written_version = File.read(scope('VERSION').strip)
+ return written_version unless File.exist?(scope('.git'))
+
+ # Get the current master branch version
+ version = written_version.split('.')
+ version.map! {|n| n =~ /^[0-9]+$/ ? n.to_i : n}
+ return written_version unless version.size == 5 && version[3] == "alpha" # prerelease
+
+ return written_version if (commit_count = `git log --pretty=oneline HEAD ^stable | wc -l`).empty?
+ version[4] = commit_count.strip
+ version.join('.')
+end
+
+task :watch_for_update do
+ sh %{ruby extra/update_watch.rb}
+end
+
+# ----- Documentation -----
+
+task :rdoc do
+ puts '=' * 100, <<END, '=' * 100
+Sass uses the YARD documentation system (http://github.com/lsegal/yard).
+Install the yard gem and then run "rake doc".
+END
+end
+
+begin
+ require 'yard'
+
+ namespace :doc do
+ task :sass do
+ require scope('lib/sass')
+ Dir[scope("yard/default/**/*.sass")].each do |sass|
+ File.open(sass.gsub(/sass$/, 'css'), 'w') do |f|
+ f.write(Sass::Engine.new(File.read(sass)).render)
+ end
+ end
+ end
+
+ desc "List all undocumented methods and classes."
+ task :undocumented do
+ opts = ENV["YARD_OPTS"] || ""
+ ENV["YARD_OPTS"] = opts.dup + <<OPTS
+ --list --tag comment --hide-tag comment --query "
+ object.docstring.blank? &&
+ !(object.type == :method && object.is_alias?)"
+OPTS
+ Rake::Task['yard'].execute
+ end
+ end
+
+ YARD::Rake::YardocTask.new do |t|
+ t.files = FileList.new(scope('lib/**/*.rb')) do |list|
+ list.exclude('lib/sass/plugin/merb.rb')
+ list.exclude('lib/sass/plugin/rails.rb')
+ end.to_a
+ t.options << '--incremental' if Rake.application.top_level_tasks.include?('redoc')
+ t.options += FileList.new(scope('yard/*.rb')).to_a.map {|f| ['-e', f]}.flatten
+ files = FileList.new(scope('doc-src/*')).to_a.sort_by {|s| s.size} + %w[MIT-LICENSE VERSION]
+ t.options << '--files' << files.join(',')
+ t.options << '--template-path' << scope('yard')
+ t.options << '--title' << ENV["YARD_TITLE"] if ENV["YARD_TITLE"]
+
+ t.before = lambda do
+ if ENV["YARD_OPTS"]
+ require 'shellwords'
+ t.options.concat(Shellwords.shellwords(ENV["YARD_OPTS"]))
+ end
+ end
+ end
+ Rake::Task['yard'].prerequisites.insert(0, 'doc:sass')
+ Rake::Task['yard'].instance_variable_set('@comment', nil)
+
+ desc "Generate Documentation"
+ task :doc => :yard
+ task :redoc => :yard
+rescue LoadError
+ desc "Generate Documentation"
+ task :doc => :rdoc
+ task :yard => :rdoc
+end
+
+task :pages do
+ ensure_git_cleanup do
+ puts "#{'=' * 50} Running rake pages"
+ sh %{git checkout sass-pages}
+ sh %{git reset --hard origin/sass-pages}
+
+ Dir.chdir("/var/www/sass-pages") do
+ sh %{git fetch origin}
+
+ sh %{git checkout stable}
+ sh %{git reset --hard origin/stable}
+
+ sh %{git checkout sass-pages}
+ sh %{git reset --hard origin/sass-pages}
+ sh %{rake build --trace}
+ sh %{mkdir -p tmp}
+ sh %{touch tmp/restart.txt}
+ end
+ end
+end
+
+# ----- Coverage -----
+
+begin
+ require 'rcov/rcovtask'
+
+ Rcov::RcovTask.new do |t|
+ t.test_files = FileList[scope('test/**/*_test.rb')]
+ t.rcov_opts << '-x' << '"^\/"'
+ if ENV['NON_NATIVE']
+ t.rcov_opts << "--no-rcovrt"
+ end
+ t.verbose = true
+ end
+rescue LoadError; end
+
+# ----- Profiling -----
+
+begin
+ require 'ruby-prof'
+
+ desc <<END
+Run a profile of sass.
+ TIMES=n sets the number of runs. Defaults to 1000.
+ FILE=str sets the file to profile. Defaults to 'complex'.
+ OUTPUT=str sets the ruby-prof output format.
+ Can be Flat, CallInfo, or Graph. Defaults to Flat. Defaults to Flat.
+END
+ task :profile do
+ times = (ENV['TIMES'] || '1000').to_i
+ file = ENV['FILE']
+
+ require 'lib/sass'
+
+ file = File.read(scope("test/sass/templates/#{file || 'complex'}.sass"))
+ result = RubyProf.profile { times.times { Sass::Engine.new(file).render } }
+
+ RubyProf.const_get("#{(ENV['OUTPUT'] || 'Flat').capitalize}Printer").new(result).print
+ end
+rescue LoadError; end
+
+# ----- Handling Updates -----
+
+def email_on_error
+ yield
+rescue Exception => e
+ IO.popen("sendmail nex342 gmail com", "w") do |sm|
+ sm << "From: nex3 nex-3 com\n" <<
+ "To: nex342 gmail com\n" <<
+ "Subject: Exception when running rake #{Rake.application.top_level_tasks.join(', ')}\n" <<
+ e.message << "\n\n" <<
+ e.backtrace.join("\n")
+ end
+ensure
+ raise e if e
+end
+
+def ensure_git_cleanup
+ email_on_error {yield}
+ensure
+ sh %{git reset --hard HEAD}
+ sh %{git clean -xdf}
+ sh %{git checkout master}
+end
+
+task :handle_update do
+ email_on_error do
+ unless ENV["REF"] =~ %r{^refs/heads/(master|stable|sass-pages)$}
+ puts "#{'=' * 20} Ignoring rake handle_update REF=#{ENV["REF"].inspect}"
+ next
+ end
+ branch = $1
+
+ puts
+ puts
+ puts '=' * 150
+ puts "Running rake handle_update REF=#{ENV["REF"].inspect}"
+
+ sh %{git fetch origin}
+ sh %{git checkout stable}
+ sh %{git reset --hard origin/stable}
+ sh %{git checkout master}
+ sh %{git reset --hard origin/master}
+
+ case branch
+ when "master"
+ sh %{rake release_edge --trace}
+ when "stable", "sass-pages"
+ sh %{rake pages --trace}
+ end
+
+ puts 'Done running handle_update'
+ puts '=' * 150
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/VERSION b/backends/css/gems/sass-3.4.9/VERSION
new file mode 100644
index 0000000..7bcbb38
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/VERSION
@@ -0,0 +1 @@
+3.4.9
diff --git a/backends/css/gems/sass-3.4.9/VERSION_DATE b/backends/css/gems/sass-3.4.9/VERSION_DATE
new file mode 100644
index 0000000..6df282b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/VERSION_DATE
@@ -0,0 +1 @@
+25 November 2014 01:40:45 UTC
diff --git a/backends/css/gems/sass-3.4.9/VERSION_NAME b/backends/css/gems/sass-3.4.9/VERSION_NAME
new file mode 100644
index 0000000..6dc9b26
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/VERSION_NAME
@@ -0,0 +1 @@
+Selective Steve
diff --git a/backends/css/gems/sass-3.4.9/bin/sass b/backends/css/gems/sass-3.4.9/bin/sass
new file mode 100755
index 0000000..62d6d0c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/bin/sass
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+# The command line Sass parser.
+
+THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+begin
+ require File.dirname(THIS_FILE) + '/../lib/sass'
+rescue LoadError
+ require 'sass'
+end
+require 'sass/exec'
+
+opts = Sass::Exec::SassScss.new(ARGV, :sass)
+opts.parse!
diff --git a/backends/css/gems/sass-3.4.9/bin/sass-convert b/backends/css/gems/sass-3.4.9/bin/sass-convert
new file mode 100755
index 0000000..b276253
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/bin/sass-convert
@@ -0,0 +1,12 @@
+#!/usr/bin/env ruby
+
+THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+begin
+ require File.dirname(THIS_FILE) + '/../lib/sass'
+rescue LoadError
+ require 'sass'
+end
+require 'sass/exec'
+
+opts = Sass::Exec::SassConvert.new(ARGV)
+opts.parse!
diff --git a/backends/css/gems/sass-3.4.9/bin/scss b/backends/css/gems/sass-3.4.9/bin/scss
new file mode 100755
index 0000000..ce3c4ad
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/bin/scss
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+# The command line Sass parser.
+
+THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+begin
+ require File.dirname(THIS_FILE) + '/../lib/sass'
+rescue LoadError
+ require 'sass'
+end
+require 'sass/exec'
+
+opts = Sass::Exec::SassScss.new(ARGV, :scss)
+opts.parse!
diff --git a/backends/css/gems/sass-3.2.12/extra/update_watch.rb
b/backends/css/gems/sass-3.4.9/extra/update_watch.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/extra/update_watch.rb
rename to backends/css/gems/sass-3.4.9/extra/update_watch.rb
diff --git a/backends/css/gems/sass-3.2.12/init.rb b/backends/css/gems/sass-3.4.9/init.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/init.rb
rename to backends/css/gems/sass-3.4.9/init.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass.rb b/backends/css/gems/sass-3.4.9/lib/sass.rb
new file mode 100644
index 0000000..c4def2d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass.rb
@@ -0,0 +1,102 @@
+dir = File.dirname(__FILE__)
+$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
+
+require 'sass/version'
+
+# The module that contains everything Sass-related:
+#
+# * {Sass::Engine} is the class used to render Sass/SCSS within Ruby code.
+# * {Sass::Plugin} is interfaces with web frameworks (Rails and Merb in particular).
+# * {Sass::SyntaxError} is raised when Sass encounters an error.
+# * {Sass::CSS} handles conversion of CSS to Sass.
+#
+# Also see the {file:SASS_REFERENCE.md full Sass reference}.
+module Sass
+ class << self
+ # @private
+ attr_accessor :tests_running
+ end
+
+ # The global load paths for Sass files. This is meant for plugins and
+ # libraries to register the paths to their Sass stylesheets to that they may
+ # be ` imported` This load path is used by every instance of {Sass::Engine}.
+ # They are lower-precedence than any load paths passed in via the
+ # {file:SASS_REFERENCE.md#load_paths-option `:load_paths` option}.
+ #
+ # If the `SASS_PATH` environment variable is set,
+ # the initial value of `load_paths` will be initialized based on that.
+ # The variable should be a colon-separated list of path names
+ # (semicolon-separated on Windows).
+ #
+ # Note that files on the global load path are never compiled to CSS
+ # themselves, even if they aren't partials. They exist only to be imported.
+ #
+ # @example
+ # Sass.load_paths << File.dirname(__FILE__ + '/sass')
+ # @return [Array<String, Pathname, Sass::Importers::Base>]
+ def self.load_paths
+ @load_paths ||= if ENV['SASS_PATH']
+ ENV['SASS_PATH'].split(Sass::Util.windows? ? ';' : ':')
+ else
+ []
+ end
+ end
+
+ # Compile a Sass or SCSS string to CSS.
+ # Defaults to SCSS.
+ #
+ # @param contents [String] The contents of the Sass file.
+ # @param options [{Symbol => Object}] An options hash;
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
+ # @raise [Sass::SyntaxError] if there's an error in the document
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
+ # cannot be converted to UTF-8
+ # @raise [ArgumentError] if the document uses an unknown encoding with ` charset`
+ def self.compile(contents, options = {})
+ options[:syntax] ||= :scss
+ Engine.new(contents, options).to_css
+ end
+
+ # Compile a file on disk to CSS.
+ #
+ # @raise [Sass::SyntaxError] if there's an error in the document
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
+ # cannot be converted to UTF-8
+ # @raise [ArgumentError] if the document uses an unknown encoding with ` charset`
+ #
+ # @overload compile_file(filename, options = {})
+ # Return the compiled CSS rather than writing it to a file.
+ #
+ # @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
+ # @param options [{Symbol => Object}] An options hash;
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
+ # @return [String] The compiled CSS.
+ #
+ # @overload compile_file(filename, css_filename, options = {})
+ # Write the compiled CSS to a file.
+ #
+ # @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
+ # @param options [{Symbol => Object}] An options hash;
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
+ # @param css_filename [String] The location to which to write the compiled CSS.
+ def self.compile_file(filename, *args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ css_filename = args.shift
+ result = Sass::Engine.for_file(filename, options).render
+ if css_filename
+ options[:css_filename] ||= css_filename
+ open(css_filename, "w") {|css_file| css_file.write(result)}
+ nil
+ else
+ result
+ end
+ end
+end
+
+require 'sass/logger'
+require 'sass/util'
+
+require 'sass/engine'
+require 'sass/plugin' if defined?(Merb::Plugins)
+require 'sass/railtie'
+require 'sass/features'
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/cache_stores.rb
b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/cache_stores.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/cache_stores.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/base.rb
b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/base.rb
new file mode 100644
index 0000000..e239666
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/base.rb
@@ -0,0 +1,88 @@
+module Sass
+ module CacheStores
+ # An abstract base class for backends for the Sass cache.
+ # Any key-value store can act as such a backend;
+ # it just needs to implement the
+ # \{#_store} and \{#_retrieve} methods.
+ #
+ # To use a cache store with Sass,
+ # use the {file:SASS_REFERENCE.md#cache_store-option `:cache_store` option}.
+ #
+ # @abstract
+ class Base
+ # Store cached contents for later retrieval
+ # Must be implemented by all CacheStore subclasses
+ #
+ # Note: cache contents contain binary data.
+ #
+ # @param key [String] The key to store the contents under
+ # @param version [String] The current sass version.
+ # Cached contents must not be retrieved across different versions of sass.
+ # @param sha [String] The sha of the sass source.
+ # Cached contents must not be retrieved if the sha has changed.
+ # @param contents [String] The contents to store.
+ def _store(key, version, sha, contents)
+ raise "#{self.class} must implement #_store."
+ end
+
+ # Retrieved cached contents.
+ # Must be implemented by all subclasses.
+ #
+ # Note: if the key exists but the sha or version have changed,
+ # then the key may be deleted by the cache store, if it wants to do so.
+ #
+ # @param key [String] The key to retrieve
+ # @param version [String] The current sass version.
+ # Cached contents must not be retrieved across different versions of sass.
+ # @param sha [String] The sha of the sass source.
+ # Cached contents must not be retrieved if the sha has changed.
+ # @return [String] The contents that were previously stored.
+ # @return [NilClass] when the cache key is not found or the version or sha have changed.
+ def _retrieve(key, version, sha)
+ raise "#{self.class} must implement #_retrieve."
+ end
+
+ # Store a {Sass::Tree::RootNode}.
+ #
+ # @param key [String] The key to store it under.
+ # @param sha [String] The checksum for the contents that are being stored.
+ # @param root [Object] The root node to cache.
+ def store(key, sha, root)
+ _store(key, Sass::VERSION, sha, Marshal.dump(root))
+ rescue TypeError, LoadError => e
+ Sass::Util.sass_warn "Warning. Error encountered while saving cache #{path_to(key)}: #{e}"
+ nil
+ end
+
+ # Retrieve a {Sass::Tree::RootNode}.
+ #
+ # @param key [String] The key the root element was stored under.
+ # @param sha [String] The checksum of the root element's content.
+ # @return [Object] The cached object.
+ def retrieve(key, sha)
+ contents = _retrieve(key, Sass::VERSION, sha)
+ Marshal.load(contents) if contents
+ rescue EOFError, TypeError, ArgumentError, LoadError => e
+ Sass::Util.sass_warn "Warning. Error encountered while reading cache #{path_to(key)}: #{e}"
+ nil
+ end
+
+ # Return the key for the sass file.
+ #
+ # The `(sass_dirname, sass_basename)` pair
+ # should uniquely identify the Sass document,
+ # but otherwise there are no restrictions on their content.
+ #
+ # @param sass_dirname [String]
+ # The fully-expanded location of the Sass file.
+ # This corresponds to the directory name on a filesystem.
+ # @param sass_basename [String] The name of the Sass file that is being referenced.
+ # This corresponds to the basename on a filesystem.
+ def key(sass_dirname, sass_basename)
+ dir = Digest::SHA1.hexdigest(sass_dirname)
+ filename = "#{sass_basename}c"
+ "#{dir}/#{filename}"
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/chain.rb
b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/chain.rb
new file mode 100644
index 0000000..914c111
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/chain.rb
@@ -0,0 +1,34 @@
+module Sass
+ module CacheStores
+ # A meta-cache that chains multiple caches together.
+ # Specifically:
+ #
+ # * All `#store`s are passed to all caches.
+ # * `#retrieve`s are passed to each cache until one has a hit.
+ # * When one cache has a hit, the value is `#store`d in all earlier caches.
+ class Chain < Base
+ # Create a new cache chaining the given caches.
+ #
+ # @param caches [Array<Sass::CacheStores::Base>] The caches to chain.
+ def initialize(*caches)
+ @caches = caches
+ end
+
+ # @see Base#store
+ def store(key, sha, obj)
+ @caches.each {|c| c.store(key, sha, obj)}
+ end
+
+ # @see Base#retrieve
+ def retrieve(key, sha)
+ @caches.each_with_index do |c, i|
+ obj = c.retrieve(key, sha)
+ next unless obj
+ @caches[0...i].each {|prev| prev.store(key, sha, obj)}
+ return obj
+ end
+ nil
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/filesystem.rb
b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/filesystem.rb
new file mode 100644
index 0000000..eea7c53
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/filesystem.rb
@@ -0,0 +1,60 @@
+require 'fileutils'
+
+module Sass
+ module CacheStores
+ # A backend for the Sass cache using the filesystem.
+ class Filesystem < Base
+ # The directory where the cached files will be stored.
+ #
+ # @return [String]
+ attr_accessor :cache_location
+
+ # @param cache_location [String] see \{#cache\_location}
+ def initialize(cache_location)
+ @cache_location = cache_location
+ end
+
+ # @see Base#\_retrieve
+ def _retrieve(key, version, sha)
+ return unless File.readable?(path_to(key))
+ File.open(path_to(key), "rb") do |f|
+ if f.readline("\n").strip == version && f.readline("\n").strip == sha
+ return f.read
+ end
+ end
+ begin
+ File.unlink path_to(key)
+ rescue Errno::ENOENT
+ # Already deleted. Race condition?
+ end
+ nil
+ rescue EOFError, TypeError, ArgumentError => e
+ Sass::Util.sass_warn "Warning. Error encountered while reading cache #{path_to(key)}: #{e}"
+ end
+
+ # @see Base#\_store
+ def _store(key, version, sha, contents)
+ compiled_filename = path_to(key)
+ FileUtils.mkdir_p(File.dirname(compiled_filename))
+ Sass::Util.atomic_create_and_write_file(compiled_filename, 0600) do |f|
+ f.puts(version)
+ f.puts(sha)
+ f.write(contents)
+ end
+ rescue Errno::EACCES
+ # pass
+ end
+
+ private
+
+ # Returns the path to a file for the given key.
+ #
+ # @param key [String]
+ # @return [String] The path to the cache file.
+ def path_to(key)
+ key = key.gsub(/[<>:\\|?*%]/) {|c| "%%%03d" % Sass::Util.ord(c)}
+ File.join(cache_location, key)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/memory.rb
b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/memory.rb
new file mode 100644
index 0000000..ac7eb3d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/memory.rb
@@ -0,0 +1,47 @@
+module Sass
+ module CacheStores
+ # A backend for the Sass cache using in-process memory.
+ class Memory < Base
+ # Since the {Memory} store is stored in the Sass tree's options hash,
+ # when the options get serialized as part of serializing the tree,
+ # you get crazy exponential growth in the size of the cached objects
+ # unless you don't dump the cache.
+ #
+ # @private
+ def _dump(depth)
+ ""
+ end
+
+ # If we deserialize this class, just make a new empty one.
+ #
+ # @private
+ def self._load(repr)
+ Memory.new
+ end
+
+ # Create a new, empty cache store.
+ def initialize
+ @contents = {}
+ end
+
+ # @see Base#retrieve
+ def retrieve(key, sha)
+ if @contents.has_key?(key)
+ return unless @contents[key][:sha] == sha
+ obj = @contents[key][:obj]
+ obj.respond_to?(:deep_copy) ? obj.deep_copy : obj.dup
+ end
+ end
+
+ # @see Base#store
+ def store(key, sha, obj)
+ @contents[key] = {:sha => sha, :obj => obj}
+ end
+
+ # Destructively clear the cache.
+ def reset!
+ @contents = {}
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/null.rb
b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/null.rb
new file mode 100644
index 0000000..f14f4c7
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/cache_stores/null.rb
@@ -0,0 +1,25 @@
+module Sass
+ module CacheStores
+ # Doesn't store anything, but records what things it should have stored.
+ # This doesn't currently have any use except for testing and debugging.
+ #
+ # @private
+ class Null < Base
+ def initialize
+ @keys = {}
+ end
+
+ def _retrieve(key, version, sha)
+ nil
+ end
+
+ def _store(key, version, sha, contents)
+ @keys[key] = true
+ end
+
+ def was_set?(key)
+ @keys[key]
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/callbacks.rb
b/backends/css/gems/sass-3.4.9/lib/sass/callbacks.rb
new file mode 100644
index 0000000..8d9cd77
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/callbacks.rb
@@ -0,0 +1,67 @@
+module Sass
+ # A lightweight infrastructure for defining and running callbacks.
+ # Callbacks are defined using \{#define\_callback\} at the class level,
+ # and called using `run_#{name}` at the instance level.
+ #
+ # Clients can add callbacks by calling the generated `on_#{name}` method,
+ # and passing in a block that's run when the callback is activated.
+ #
+ # @example Define a callback
+ # class Munger
+ # extend Sass::Callbacks
+ # define_callback :string_munged
+ #
+ # def munge(str)
+ # res = str.gsub(/[a-z]/, '\1\1')
+ # run_string_munged str, res
+ # res
+ # end
+ # end
+ #
+ # @example Use a callback
+ # m = Munger.new
+ # m.on_string_munged {|str, res| puts "#{str} was munged into #{res}!"}
+ # m.munge "bar" #=> bar was munged into bbaarr!
+ module Callbacks
+ # Automatically includes {InstanceMethods}
+ # when something extends this module.
+ #
+ # @param base [Module]
+ def self.extended(base)
+ base.send(:include, InstanceMethods)
+ end
+
+ protected
+
+ module InstanceMethods
+ # Removes all callbacks registered against this object.
+ def clear_callbacks!
+ @_sass_callbacks = {}
+ end
+ end
+
+ # Define a callback with the given name.
+ # This will define an `on_#{name}` method
+ # that registers a block,
+ # and a `run_#{name}` method that runs that block
+ # (optionall with some arguments).
+ #
+ # @param name [Symbol] The name of the callback
+ # @return [void]
+ def define_callback(name)
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
+def on_#{name}(&block)
+ @_sass_callbacks ||= {}
+ (@_sass_callbacks[#{name.inspect}] ||= []) << block
+end
+
+def run_#{name}(*args)
+ return unless @_sass_callbacks
+ return unless @_sass_callbacks[#{name.inspect}]
+ @_sass_callbacks[#{name.inspect}].each {|c| c[*args]}
+end
+private :run_#{name}
+RUBY
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/css.rb b/backends/css/gems/sass-3.4.9/lib/sass/css.rb
new file mode 100644
index 0000000..15aa0f4
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/css.rb
@@ -0,0 +1,407 @@
+require File.dirname(__FILE__) + '/../sass'
+require 'sass/tree/node'
+require 'sass/scss/css_parser'
+
+module Sass
+ # This class converts CSS documents into Sass or SCSS templates.
+ # It works by parsing the CSS document into a {Sass::Tree} structure,
+ # and then applying various transformations to the structure
+ # to produce more concise and idiomatic Sass/SCSS.
+ #
+ # Example usage:
+ #
+ # Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
+ # Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
+ class CSS
+ # @param template [String] The CSS stylesheet.
+ # This stylesheet can be encoded using any encoding
+ # that can be converted to Unicode.
+ # If the stylesheet contains an ` charset` declaration,
+ # that overrides the Ruby encoding
+ # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
+ # @option options :old [Boolean] (false)
+ # Whether or not to output old property syntax
+ # (`:color blue` as opposed to `color: blue`).
+ # This is only meaningful when generating Sass code,
+ # rather than SCSS.
+ # @option options :indent [String] (" ")
+ # The string to use for indenting each line. Defaults to two spaces.
+ def initialize(template, options = {})
+ if template.is_a? IO
+ template = template.read
+ end
+
+ @options = options.dup
+ # Backwards compatibility
+ @options[:old] = true if @options[:alternate] == false
+ @template = template
+ end
+
+ # Converts the CSS template into Sass or SCSS code.
+ #
+ # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
+ # @return [String] The resulting Sass or SCSS code
+ # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
+ def render(fmt = :sass)
+ check_encoding!
+ build_tree.send("to_#{fmt}", @options).strip + "\n"
+ rescue Sass::SyntaxError => err
+ err.modify_backtrace(:filename => @options[:filename] || '(css)')
+ raise err
+ end
+
+ # Returns the original encoding of the document,
+ # or `nil` under Ruby 1.8.
+ #
+ # @return [Encoding, nil]
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
+ # cannot be converted to UTF-8
+ # @raise [ArgumentError] if the document uses an unknown encoding with ` charset`
+ def source_encoding
+ check_encoding!
+ @original_encoding
+ end
+
+ private
+
+ def check_encoding!
+ return if @checked_encoding
+ @checked_encoding = true
+ @template, @original_encoding = Sass::Util.check_sass_encoding(@template)
+ end
+
+ # Parses the CSS template and applies various transformations
+ #
+ # @return [Tree::Node] The root node of the parsed tree
+ def build_tree
+ root = Sass::SCSS::CssParser.new(@template, @options[:filename], nil).parse
+ parse_selectors root
+ expand_commas root
+ nest_seqs root
+ parent_ref_rules root
+ flatten_rules root
+ bubble_subject root
+ fold_commas root
+ dump_selectors root
+ root
+ end
+
+ # Parse all the selectors in the document and assign them to
+ # {Sass::Tree::RuleNode#parsed_rules}.
+ #
+ # @param root [Tree::Node] The parent node
+ def parse_selectors(root)
+ root.children.each do |child|
+ next parse_selectors(child) if child.is_a?(Tree::DirectiveNode)
+ next unless child.is_a?(Tree::RuleNode)
+ parser = Sass::SCSS::CssParser.new(child.rule.first, child.filename, nil, child.line)
+ child.parsed_rules = parser.parse_selector
+ end
+ end
+
+ # Transform
+ #
+ # foo, bar, baz
+ # color: blue
+ #
+ # into
+ #
+ # foo
+ # color: blue
+ # bar
+ # color: blue
+ # baz
+ # color: blue
+ #
+ # @param root [Tree::Node] The parent node
+ def expand_commas(root)
+ root.children.map! do |child|
+ # child.parsed_rules.members.size > 1 iff the rule contains a comma
+ unless child.is_a?(Tree::RuleNode) && child.parsed_rules.members.size > 1
+ expand_commas(child) if child.is_a?(Tree::DirectiveNode)
+ next child
+ end
+ child.parsed_rules.members.map do |seq|
+ node = Tree::RuleNode.new([])
+ node.parsed_rules = make_cseq(seq)
+ node.children = child.children
+ node
+ end
+ end
+ root.children.flatten!
+ end
+
+ # Make rules use nesting so that
+ #
+ # foo
+ # color: green
+ # foo bar
+ # color: red
+ # foo baz
+ # color: blue
+ #
+ # becomes
+ #
+ # foo
+ # color: green
+ # bar
+ # color: red
+ # baz
+ # color: blue
+ #
+ # @param root [Tree::Node] The parent node
+ def nest_seqs(root)
+ current_rule = nil
+ root.children.map! do |child|
+ unless child.is_a?(Tree::RuleNode)
+ nest_seqs(child) if child.is_a?(Tree::DirectiveNode)
+ next child
+ end
+
+ seq = first_seq(child)
+ seq.members.reject! {|sseq| sseq == "\n"}
+ first, rest = seq.members.first, seq.members[1..-1]
+
+ if current_rule.nil? || first_sseq(current_rule) != first
+ current_rule = Tree::RuleNode.new([])
+ current_rule.parsed_rules = make_seq(first)
+ end
+
+ if rest.empty?
+ current_rule.children += child.children
+ else
+ child.parsed_rules = make_seq(*rest)
+ current_rule << child
+ end
+
+ current_rule
+ end
+ root.children.compact!
+ root.children.uniq!
+
+ root.children.each {|v| nest_seqs(v)}
+ end
+
+ # Make rules use parent refs so that
+ #
+ # foo
+ # color: green
+ # foo.bar
+ # color: blue
+ #
+ # becomes
+ #
+ # foo
+ # color: green
+ # &.bar
+ # color: blue
+ #
+ # @param root [Tree::Node] The parent node
+ def parent_ref_rules(root)
+ current_rule = nil
+ root.children.map! do |child|
+ unless child.is_a?(Tree::RuleNode)
+ parent_ref_rules(child) if child.is_a?(Tree::DirectiveNode)
+ next child
+ end
+
+ sseq = first_sseq(child)
+ next child unless sseq.is_a?(Sass::Selector::SimpleSequence)
+
+ firsts, rest = [sseq.members.first], sseq.members[1..-1]
+ firsts.push rest.shift if firsts.first.is_a?(Sass::Selector::Parent)
+
+ last_simple_subject = rest.empty? && sseq.subject?
+ if current_rule.nil? || first_sseq(current_rule).members != firsts ||
+ !!first_sseq(current_rule).subject? != !!last_simple_subject
+ current_rule = Tree::RuleNode.new([])
+ current_rule.parsed_rules = make_sseq(last_simple_subject, *firsts)
+ end
+
+ if rest.empty?
+ current_rule.children += child.children
+ else
+ rest.unshift Sass::Selector::Parent.new
+ child.parsed_rules = make_sseq(sseq.subject?, *rest)
+ current_rule << child
+ end
+
+ current_rule
+ end
+ root.children.compact!
+ root.children.uniq!
+
+ root.children.each {|v| parent_ref_rules(v)}
+ end
+
+ # Flatten rules so that
+ #
+ # foo
+ # bar
+ # color: red
+ #
+ # becomes
+ #
+ # foo bar
+ # color: red
+ #
+ # and
+ #
+ # foo
+ # &.bar
+ # color: blue
+ #
+ # becomes
+ #
+ # foo.bar
+ # color: blue
+ #
+ # @param root [Tree::Node] The parent node
+ def flatten_rules(root)
+ root.children.each do |child|
+ case child
+ when Tree::RuleNode
+ flatten_rule(child)
+ when Tree::DirectiveNode
+ flatten_rules(child)
+ end
+ end
+ end
+
+ # Flattens a single rule.
+ #
+ # @param rule [Tree::RuleNode] The candidate for flattening
+ # @see #flatten_rules
+ def flatten_rule(rule)
+ while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
+ child = rule.children.first
+
+ if first_simple_sel(child).is_a?(Sass::Selector::Parent)
+ rule.parsed_rules = child.parsed_rules.resolve_parent_refs(rule.parsed_rules)
+ else
+ rule.parsed_rules = make_seq(*(first_seq(rule).members + first_seq(child).members))
+ end
+
+ rule.children = child.children
+ end
+
+ flatten_rules(rule)
+ end
+
+ def bubble_subject(root)
+ root.children.each do |child|
+ bubble_subject(child) if child.is_a?(Tree::RuleNode) || child.is_a?(Tree::DirectiveNode)
+ next unless child.is_a?(Tree::RuleNode) && !child.children.empty?
+ next unless child.children.all? do |c|
+ next unless c.is_a?(Tree::RuleNode)
+ first_simple_sel(c).is_a?(Sass::Selector::Parent) && first_sseq(c).subject?
+ end
+ first_sseq(child).subject = true
+ child.children.each {|c| first_sseq(c).subject = false}
+ end
+ end
+
+ # Transform
+ #
+ # foo
+ # bar
+ # color: blue
+ # baz
+ # color: blue
+ #
+ # into
+ #
+ # foo
+ # bar, baz
+ # color: blue
+ #
+ # @param root [Tree::Node] The parent node
+ def fold_commas(root)
+ prev_rule = nil
+ root.children.map! do |child|
+ unless child.is_a?(Tree::RuleNode)
+ fold_commas(child) if child.is_a?(Tree::DirectiveNode)
+ next child
+ end
+
+ if prev_rule && prev_rule.children.map {|c| c.to_sass} == child.children.map {|c| c.to_sass}
+ prev_rule.parsed_rules.members << first_seq(child)
+ next nil
+ end
+
+ fold_commas(child)
+ prev_rule = child
+ child
+ end
+ root.children.compact!
+ end
+
+ # Dump all the parsed {Sass::Tree::RuleNode} selectors to strings.
+ #
+ # @param root [Tree::Node] The parent node
+ def dump_selectors(root)
+ root.children.each do |child|
+ next dump_selectors(child) if child.is_a?(Tree::DirectiveNode)
+ next unless child.is_a?(Tree::RuleNode)
+ child.rule = [child.parsed_rules.to_s]
+ dump_selectors(child)
+ end
+ end
+
+ # Create a {Sass::Selector::CommaSequence}.
+ #
+ # @param seqs [Array<Sass::Selector::Sequence>]
+ # @return [Sass::Selector::CommaSequence]
+ def make_cseq(*seqs)
+ Sass::Selector::CommaSequence.new(seqs)
+ end
+
+ # Create a {Sass::Selector::CommaSequence} containing only a single
+ # {Sass::Selector::Sequence}.
+ #
+ # @param sseqs [Array<Sass::Selector::Sequence, String>]
+ # @return [Sass::Selector::CommaSequence]
+ def make_seq(*sseqs)
+ make_cseq(Sass::Selector::Sequence.new(sseqs))
+ end
+
+ # Create a {Sass::Selector::CommaSequence} containing only a single
+ # {Sass::Selector::Sequence} which in turn contains only a single
+ # {Sass::Selector::SimpleSequence}.
+ #
+ # @param subject [Boolean] Whether this is a subject selector
+ # @param sseqs [Array<Sass::Selector::Sequence, String>]
+ # @return [Sass::Selector::CommaSequence]
+ def make_sseq(subject, *sseqs)
+ make_seq(Sass::Selector::SimpleSequence.new(sseqs, subject))
+ end
+
+ # Return the first {Sass::Selector::Sequence} in a {Sass::Tree::RuleNode}.
+ #
+ # @param rule [Sass::Tree::RuleNode]
+ # @return [Sass::Selector::Sequence]
+ def first_seq(rule)
+ rule.parsed_rules.members.first
+ end
+
+ # Return the first {Sass::Selector::SimpleSequence} in a
+ # {Sass::Tree::RuleNode}.
+ #
+ # @param rule [Sass::Tree::RuleNode]
+ # @return [Sass::Selector::SimpleSequence, String]
+ def first_sseq(rule)
+ first_seq(rule).members.first
+ end
+
+ # Return the first {Sass::Selector::Simple} in a {Sass::Tree::RuleNode},
+ # unless the rule begins with a combinator.
+ #
+ # @param rule [Sass::Tree::RuleNode]
+ # @return [Sass::Selector::Simple?]
+ def first_simple_sel(rule)
+ sseq = first_sseq(rule)
+ return unless sseq.is_a?(Sass::Selector::SimpleSequence)
+ sseq.members.first
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/engine.rb b/backends/css/gems/sass-3.4.9/lib/sass/engine.rb
new file mode 100644
index 0000000..3650e43
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/engine.rb
@@ -0,0 +1,1182 @@
+require 'set'
+require 'digest/sha1'
+require 'sass/cache_stores'
+require 'sass/source/position'
+require 'sass/source/range'
+require 'sass/source/map'
+require 'sass/tree/node'
+require 'sass/tree/root_node'
+require 'sass/tree/rule_node'
+require 'sass/tree/comment_node'
+require 'sass/tree/prop_node'
+require 'sass/tree/directive_node'
+require 'sass/tree/media_node'
+require 'sass/tree/supports_node'
+require 'sass/tree/css_import_node'
+require 'sass/tree/variable_node'
+require 'sass/tree/mixin_def_node'
+require 'sass/tree/mixin_node'
+require 'sass/tree/trace_node'
+require 'sass/tree/content_node'
+require 'sass/tree/function_node'
+require 'sass/tree/return_node'
+require 'sass/tree/extend_node'
+require 'sass/tree/if_node'
+require 'sass/tree/while_node'
+require 'sass/tree/for_node'
+require 'sass/tree/each_node'
+require 'sass/tree/debug_node'
+require 'sass/tree/warn_node'
+require 'sass/tree/import_node'
+require 'sass/tree/charset_node'
+require 'sass/tree/at_root_node'
+require 'sass/tree/keyframe_rule_node'
+require 'sass/tree/error_node'
+require 'sass/tree/visitors/base'
+require 'sass/tree/visitors/perform'
+require 'sass/tree/visitors/cssize'
+require 'sass/tree/visitors/extend'
+require 'sass/tree/visitors/convert'
+require 'sass/tree/visitors/to_css'
+require 'sass/tree/visitors/deep_copy'
+require 'sass/tree/visitors/set_options'
+require 'sass/tree/visitors/check_nesting'
+require 'sass/selector'
+require 'sass/environment'
+require 'sass/script'
+require 'sass/scss'
+require 'sass/stack'
+require 'sass/error'
+require 'sass/importers'
+require 'sass/shared'
+require 'sass/media'
+require 'sass/supports'
+
+module Sass
+ # A Sass mixin or function.
+ #
+ # `name`: `String`
+ # : The name of the mixin/function.
+ #
+ # `args`: `Array<(Script::Tree::Node, Script::Tree::Node)>`
+ # : The arguments for the mixin/function.
+ # Each element is a tuple containing the variable node of the argument
+ # and the parse tree for the default value of the argument.
+ #
+ # `splat`: `Script::Tree::Node?`
+ # : The variable node of the splat argument for this callable, or null.
+ #
+ # `environment`: {Sass::Environment}
+ # : The environment in which the mixin/function was defined.
+ # This is captured so that the mixin/function can have access
+ # to local variables defined in its scope.
+ #
+ # `tree`: `Array<Tree::Node>`
+ # : The parse tree for the mixin/function.
+ #
+ # `has_content`: `Boolean`
+ # : Whether the callable accepts a content block.
+ #
+ # `type`: `String`
+ # : The user-friendly name of the type of the callable.
+ Callable = Struct.new(:name, :args, :splat, :environment, :tree, :has_content, :type)
+
+ # This class handles the parsing and compilation of the Sass template.
+ # Example usage:
+ #
+ # template = File.load('stylesheets/sassy.sass')
+ # sass_engine = Sass::Engine.new(template)
+ # output = sass_engine.render
+ # puts output
+ class Engine
+ # A line of Sass code.
+ #
+ # `text`: `String`
+ # : The text in the line, without any whitespace at the beginning or end.
+ #
+ # `tabs`: `Fixnum`
+ # : The level of indentation of the line.
+ #
+ # `index`: `Fixnum`
+ # : The line number in the original document.
+ #
+ # `offset`: `Fixnum`
+ # : The number of bytes in on the line that the text begins.
+ # This ends up being the number of bytes of leading whitespace.
+ #
+ # `filename`: `String`
+ # : The name of the file in which this line appeared.
+ #
+ # `children`: `Array<Line>`
+ # : The lines nested below this one.
+ #
+ # `comment_tab_str`: `String?`
+ # : The prefix indentation for this comment, if it is a comment.
+ class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str)
+ def comment?
+ text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
+ end
+ end
+
+ # The character that begins a CSS property.
+ PROPERTY_CHAR = ?:
+
+ # The character that designates the beginning of a comment,
+ # either Sass or CSS.
+ COMMENT_CHAR = ?/
+
+ # The character that follows the general COMMENT_CHAR and designates a Sass comment,
+ # which is not output as a CSS comment.
+ SASS_COMMENT_CHAR = ?/
+
+ # The character that indicates that a comment allows interpolation
+ # and should be preserved even in `:compressed` mode.
+ SASS_LOUD_COMMENT_CHAR = ?!
+
+ # The character that follows the general COMMENT_CHAR and designates a CSS comment,
+ # which is embedded in the CSS document.
+ CSS_COMMENT_CHAR = ?*
+
+ # The character used to denote a compiler directive.
+ DIRECTIVE_CHAR = ?@
+
+ # Designates a non-parsed rule.
+ ESCAPE_CHAR = ?\\
+
+ # Designates block as mixin definition rather than CSS rules to output
+ MIXIN_DEFINITION_CHAR = ?=
+
+ # Includes named mixin declared using MIXIN_DEFINITION_CHAR
+ MIXIN_INCLUDE_CHAR = ?+
+
+ # The regex that matches and extracts data from
+ # properties of the form `:name prop`.
+ PROPERTY_OLD = /^:([^\s=:"]+)\s*(?:\s+|$)(.*)/
+
+ # The default options for Sass::Engine.
+ # @api public
+ DEFAULT_OPTIONS = {
+ :style => :nested,
+ :load_paths => ['.'],
+ :cache => true,
+ :cache_location => './.sass-cache',
+ :syntax => :sass,
+ :filesystem_importer => Sass::Importers::Filesystem
+ }.freeze
+
+ # Converts a Sass options hash into a standard form, filling in
+ # default values and resolving aliases.
+ #
+ # @param options [{Symbol => Object}] The options hash;
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
+ # @return [{Symbol => Object}] The normalized options hash.
+ # @private
+ def self.normalize_options(options)
+ options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
+
+ # If the `:filename` option is passed in without an importer,
+ # assume it's using the default filesystem importer.
+ options[:importer] ||= options[:filesystem_importer].new(".") if options[:filename]
+
+ # Tracks the original filename of the top-level Sass file
+ options[:original_filename] ||= options[:filename]
+
+ options[:cache_store] ||= Sass::CacheStores::Chain.new(
+ Sass::CacheStores::Memory.new, Sass::CacheStores::Filesystem.new(options[:cache_location]))
+ # Support both, because the docs said one and the other actually worked
+ # for quite a long time.
+ options[:line_comments] ||= options[:line_numbers]
+
+ options[:load_paths] = (options[:load_paths] + Sass.load_paths).map do |p|
+ next p unless p.is_a?(String) || (defined?(Pathname) && p.is_a?(Pathname))
+ options[:filesystem_importer].new(p.to_s)
+ end
+
+ # Backwards compatibility
+ options[:property_syntax] ||= options[:attribute_syntax]
+ case options[:property_syntax]
+ when :alternate; options[:property_syntax] = :new
+ when :normal; options[:property_syntax] = :old
+ end
+ options[:sourcemap] = :auto if options[:sourcemap] == true
+ options[:sourcemap] = :none if options[:sourcemap] == false
+
+ options
+ end
+
+ # Returns the {Sass::Engine} for the given file.
+ # This is preferable to Sass::Engine.new when reading from a file
+ # because it properly sets up the Engine's metadata,
+ # enables parse-tree caching,
+ # and infers the syntax from the filename.
+ #
+ # @param filename [String] The path to the Sass or SCSS file
+ # @param options [{Symbol => Object}] The options hash;
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ # @return [Sass::Engine] The Engine for the given Sass or SCSS file.
+ # @raise [Sass::SyntaxError] if there's an error in the document.
+ def self.for_file(filename, options)
+ had_syntax = options[:syntax]
+
+ if had_syntax
+ # Use what was explicitly specificed
+ elsif filename =~ /\.scss$/
+ options.merge!(:syntax => :scss)
+ elsif filename =~ /\.sass$/
+ options.merge!(:syntax => :sass)
+ end
+
+ Sass::Engine.new(File.read(filename), options.merge(:filename => filename))
+ end
+
+ # The options for the Sass engine.
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ #
+ # @return [{Symbol => Object}]
+ attr_reader :options
+
+ # Creates a new Engine. Note that Engine should only be used directly
+ # when compiling in-memory Sass code.
+ # If you're compiling a single Sass file from the filesystem,
+ # use \{Sass::Engine.for\_file}.
+ # If you're compiling multiple files from the filesystem,
+ # use {Sass::Plugin}.
+ #
+ # @param template [String] The Sass template.
+ # This template can be encoded using any encoding
+ # that can be converted to Unicode.
+ # If the template contains an ` charset` declaration,
+ # that overrides the Ruby encoding
+ # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
+ # @param options [{Symbol => Object}] An options hash.
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ # @see {Sass::Engine.for_file}
+ # @see {Sass::Plugin}
+ def initialize(template, options = {})
+ @options = self.class.normalize_options(options)
+ @template = template
+ end
+
+ # Render the template to CSS.
+ #
+ # @return [String] The CSS
+ # @raise [Sass::SyntaxError] if there's an error in the document
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
+ # cannot be converted to UTF-8
+ # @raise [ArgumentError] if the document uses an unknown encoding with ` charset`
+ def render
+ return _to_tree.render unless @options[:quiet]
+ Sass::Util.silence_sass_warnings {_to_tree.render}
+ end
+
+ # Render the template to CSS and return the source map.
+ #
+ # @param sourcemap_uri [String] The sourcemap URI to use in the
+ # ` sourceMappingURL` comment. If this is relative, it should be relative
+ # to the location of the CSS file.
+ # @return [(String, Sass::Source::Map)] The rendered CSS and the associated
+ # source map
+ # @raise [Sass::SyntaxError] if there's an error in the document, or if the
+ # public URL for this document couldn't be determined.
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
+ # cannot be converted to UTF-8
+ # @raise [ArgumentError] if the document uses an unknown encoding with ` charset`
+ def render_with_sourcemap(sourcemap_uri)
+ return _render_with_sourcemap(sourcemap_uri) unless @options[:quiet]
+ Sass::Util.silence_sass_warnings {_render_with_sourcemap(sourcemap_uri)}
+ end
+
+ alias_method :to_css, :render
+
+ # Parses the document into its parse tree. Memoized.
+ #
+ # @return [Sass::Tree::Node] The root of the parse tree.
+ # @raise [Sass::SyntaxError] if there's an error in the document
+ def to_tree
+ @tree ||= if @options[:quiet]
+ Sass::Util.silence_sass_warnings {_to_tree}
+ else
+ _to_tree
+ end
+ end
+
+ # Returns the original encoding of the document,
+ # or `nil` under Ruby 1.8.
+ #
+ # @return [Encoding, nil]
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
+ # cannot be converted to UTF-8
+ # @raise [ArgumentError] if the document uses an unknown encoding with ` charset`
+ def source_encoding
+ check_encoding!
+ @source_encoding
+ end
+
+ # Gets a set of all the documents
+ # that are (transitive) dependencies of this document,
+ # not including the document itself.
+ #
+ # @return [[Sass::Engine]] The dependency documents.
+ def dependencies
+ _dependencies(Set.new, engines = Set.new)
+ Sass::Util.array_minus(engines, [self])
+ end
+
+ # Helper for \{#dependencies}.
+ #
+ # @private
+ def _dependencies(seen, engines)
+ key = [ options[:filename], @options[:importer]]
+ return if seen.include?(key)
+ seen << key
+ engines << self
+ to_tree.grep(Tree::ImportNode) do |n|
+ next if n.css_import?
+ n.imported_file._dependencies(seen, engines)
+ end
+ end
+
+ private
+
+ def _render_with_sourcemap(sourcemap_uri)
+ filename = @options[:filename]
+ importer = @options[:importer]
+ sourcemap_dir = @options[:sourcemap_filename] &&
+ File.dirname(File.expand_path(@options[:sourcemap_filename]))
+ if filename.nil?
+ raise Sass::SyntaxError.new(<<ERR)
+Error generating source map: couldn't determine public URL for the source stylesheet.
+ No filename is available so there's nothing for the source map to link to.
+ERR
+ elsif importer.nil?
+ raise Sass::SyntaxError.new(<<ERR)
+Error generating source map: couldn't determine public URL for "#{filename}".
+ Without a public URL, there's nothing for the source map to link to.
+ An importer was not set for this file.
+ERR
+ elsif Sass::Util.silence_warnings do
+ sourcemap_dir = nil if @options[:sourcemap] == :file
+ importer.public_url(filename, sourcemap_dir).nil?
+ end
+ raise Sass::SyntaxError.new(<<ERR)
+Error generating source map: couldn't determine public URL for "#{filename}".
+ Without a public URL, there's nothing for the source map to link to.
+ Custom importers should define the #public_url method.
+ERR
+ end
+
+ rendered, sourcemap = _to_tree.render_with_sourcemap
+ compressed = @options[:style] == :compressed
+ rendered << "\n" if rendered[-1] != ?\n
+ rendered << "\n" unless compressed
+ rendered << "/*# sourceMappingURL="
+ rendered << Sass::Util.escape_uri(sourcemap_uri)
+ rendered << " */\n"
+ return rendered, sourcemap
+ end
+
+ def _to_tree
+ check_encoding!
+
+ if (@options[:cache] || @options[:read_cache]) &&
+ @options[:filename] && @options[:importer]
+ key = sassc_key
+ sha = Digest::SHA1.hexdigest(@template)
+
+ if (root = @options[:cache_store].retrieve(key, sha))
+ root.options = @options
+ return root
+ end
+ end
+
+ if @options[:syntax] == :scss
+ root = Sass::SCSS::Parser.new(@template, @options[:filename], @options[:importer]).parse
+ else
+ root = Tree::RootNode.new(@template)
+ append_children(root, tree(tabulate(@template)).first, true)
+ end
+
+ root.options = @options
+ if @options[:cache] && key && sha
+ begin
+ old_options = root.options
+ root.options = {}
+ @options[:cache_store].store(key, sha, root)
+ ensure
+ root.options = old_options
+ end
+ end
+ root
+ rescue SyntaxError => e
+ e.modify_backtrace(:filename => @options[:filename], :line => @line)
+ e.sass_template = @template
+ raise e
+ end
+
+ def sassc_key
+ @options[:cache_store].key(* options[:importer].key(@options[:filename], @options))
+ end
+
+ def check_encoding!
+ return if @checked_encoding
+ @checked_encoding = true
+ @template, @source_encoding = Sass::Util.check_sass_encoding(@template)
+ end
+
+ def tabulate(string)
+ tab_str = nil
+ comment_tab_str = nil
+ first = true
+ lines = []
+ string.scan(/^[^\n]*?$/).each_with_index do |line, index|
+ index += (@options[:line] || 1)
+ if line.strip.empty?
+ lines.last.text << "\n" if lines.last && lines.last.comment?
+ next
+ end
+
+ line_tab_str = line[/^\s*/]
+ unless line_tab_str.empty?
+ if tab_str.nil?
+ comment_tab_str ||= line_tab_str
+ next if try_comment(line, lines.last, "", comment_tab_str, index)
+ comment_tab_str = nil
+ end
+
+ tab_str ||= line_tab_str
+
+ raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
+ :line => index) if first
+
+ raise SyntaxError.new("Indentation can't use both tabs and spaces.",
+ :line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
+ end
+ first &&= !tab_str.nil?
+ if tab_str.nil?
+ lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
+ next
+ end
+
+ comment_tab_str ||= line_tab_str
+ if try_comment(line, lines.last, tab_str * lines.last.tabs, comment_tab_str, index)
+ next
+ else
+ comment_tab_str = nil
+ end
+
+ line_tabs = line_tab_str.scan(tab_str).size
+ if tab_str * line_tabs != line_tab_str
+ message = <<END.strip.gsub("\n", ' ')
+Inconsistent indentation: #{Sass::Shared.human_indentation line_tab_str, true} used for indentation,
+but the rest of the document was indented using #{Sass::Shared.human_indentation tab_str}.
+END
+ raise SyntaxError.new(message, :line => index)
+ end
+
+ lines << Line.new(line.strip, line_tabs, index, line_tab_str.size, @options[:filename], [])
+ end
+ lines
+ end
+
+ # @comment
+ # rubocop:disable ParameterLists
+ def try_comment(line, last, tab_str, comment_tab_str, index)
+ # rubocop:enable ParameterLists
+ return unless last && last.comment?
+ # Nested comment stuff must be at least one whitespace char deeper
+ # than the normal indentation
+ return unless line =~ /^#{tab_str}\s/
+ unless line =~ /^(?:#{comment_tab_str})(.*)$/
+ raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
+Inconsistent indentation:
+previous line was indented by #{Sass::Shared.human_indentation comment_tab_str},
+but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
+MSG
+ end
+
+ last.comment_tab_str ||= comment_tab_str
+ last.text << "\n" << line
+ true
+ end
+
+ def tree(arr, i = 0)
+ return [], i if arr[i].nil?
+
+ base = arr[i].tabs
+ nodes = []
+ while (line = arr[i]) && line.tabs >= base
+ if line.tabs > base
+ raise SyntaxError.new(
+ "The line was indented #{line.tabs - base} levels deeper than the previous line.",
+ :line => line.index) if line.tabs > base + 1
+
+ nodes.last.children, i = tree(arr, i)
+ else
+ nodes << line
+ i += 1
+ end
+ end
+ return nodes, i
+ end
+
+ def build_tree(parent, line, root = false)
+ @line = line.index
+ @offset = line.offset
+ node_or_nodes = parse_line(parent, line, root)
+
+ Array(node_or_nodes).each do |node|
+ # Node is a symbol if it's non-outputting, like a variable assignment
+ next unless node.is_a? Tree::Node
+
+ node.line = line.index
+ node.filename = line.filename
+
+ append_children(node, line.children, false)
+ end
+
+ node_or_nodes
+ end
+
+ def append_children(parent, children, root)
+ continued_rule = nil
+ continued_comment = nil
+ children.each do |line|
+ child = build_tree(parent, line, root)
+
+ if child.is_a?(Tree::RuleNode)
+ if child.continued? && child.children.empty?
+ if continued_rule
+ continued_rule.add_rules child
+ else
+ continued_rule = child
+ end
+ next
+ elsif continued_rule
+ continued_rule.add_rules child
+ continued_rule.children = child.children
+ continued_rule, child = nil, continued_rule
+ end
+ elsif continued_rule
+ continued_rule = nil
+ end
+
+ if child.is_a?(Tree::CommentNode) && child.type == :silent
+ if continued_comment &&
+ child.line == continued_comment.line +
+ continued_comment.lines + 1
+ continued_comment.value.last.sub!(/ \*\/\Z/, '')
+ child.value.first.gsub!(/\A\/\*/, ' *')
+ continued_comment.value += ["\n"] + child.value
+ next
+ end
+
+ continued_comment = child
+ end
+
+ check_for_no_children(child)
+ validate_and_append_child(parent, child, line, root)
+ end
+
+ parent
+ end
+
+ def validate_and_append_child(parent, child, line, root)
+ case child
+ when Array
+ child.each {|c| validate_and_append_child(parent, c, line, root)}
+ when Tree::Node
+ parent << child
+ end
+ end
+
+ def check_for_no_children(node)
+ return unless node.is_a?(Tree::RuleNode) && node.children.empty?
+ Sass::Util.sass_warn(<<WARNING.strip)
+WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
+This selector doesn't have any properties and will not be rendered.
+WARNING
+ end
+
+ def parse_line(parent, line, root)
+ case line.text[0]
+ when PROPERTY_CHAR
+ if line.text[1] == PROPERTY_CHAR ||
+ (@options[:property_syntax] == :new &&
+ line.text =~ PROPERTY_OLD && $2.empty?)
+ # Support CSS3-style pseudo-elements,
+ # which begin with ::,
+ # as well as pseudo-classes
+ # if we're using the new property syntax
+ Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
+ else
+ name_start_offset = line.offset + 1 # +1 for the leading ':'
+ name, value = line.text.scan(PROPERTY_OLD)[0]
+ raise SyntaxError.new("Invalid property: \"#{line.text}\".",
+ :line => @line) if name.nil? || value.nil?
+
+ value_start_offset = name_end_offset = name_start_offset + name.length
+ unless value.empty?
+ # +1 and -1 both compensate for the leading ':', which is part of line.text
+ value_start_offset = name_start_offset + line.text.index(value, name.length + 1) - 1
+ end
+
+ property = parse_property(name, parse_interp(name), value, :old, line, value_start_offset)
+ property.name_source_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(name_start_offset)),
+ Sass::Source::Position.new(@line, to_parser_offset(name_end_offset)),
+ @options[:filename], @options[:importer])
+ property
+ end
+ when ?$
+ parse_variable(line)
+ when COMMENT_CHAR
+ parse_comment(line)
+ when DIRECTIVE_CHAR
+ parse_directive(parent, line, root)
+ when ESCAPE_CHAR
+ Tree::RuleNode.new(parse_interp(line.text[1..-1]), full_line_range(line))
+ when MIXIN_DEFINITION_CHAR
+ parse_mixin_definition(line)
+ when MIXIN_INCLUDE_CHAR
+ if line.text[1].nil? || line.text[1] == ?\s
+ Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
+ else
+ parse_mixin_include(line, root)
+ end
+ else
+ parse_property_or_rule(line)
+ end
+ end
+
+ def parse_property_or_rule(line)
+ scanner = Sass::Util::MultibyteStringScanner.new(line.text)
+ hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
+ offset = line.offset
+ offset += hack_char.length if hack_char
+ parser = Sass::SCSS::Parser.new(scanner,
+ @options[:filename], @options[:importer],
+ @line, to_parser_offset(offset))
+
+ unless (res = parser.parse_interp_ident)
+ parsed = parse_interp(line.text, line.offset)
+ return Tree::RuleNode.new(parsed, full_line_range(line))
+ end
+
+ ident_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset)),
+ Sass::Source::Position.new(@line, parser.offset),
+ @options[:filename], @options[:importer])
+ offset = parser.offset - 1
+ res.unshift(hack_char) if hack_char
+
+ # Handle comments after a property name but before the colon.
+ if (comment = scanner.scan(Sass::SCSS::RX::COMMENT))
+ res << comment
+ offset += comment.length
+ end
+
+ name = line.text[0...scanner.pos]
+ if (scanned = scanner.scan(/\s*:(?:\s+|$)/)) # test for a property
+ offset += scanned.length
+ property = parse_property(name, res, scanner.rest, :new, line, offset)
+ property.name_source_range = ident_range
+ property
+ else
+ res.pop if comment
+
+ if (trailing = (scanner.scan(/\s*#{Sass::SCSS::RX::COMMENT}/) ||
+ scanner.scan(/\s*#{Sass::SCSS::RX::SINGLE_LINE_COMMENT}/)))
+ trailing.strip!
+ end
+ interp_parsed = parse_interp(scanner.rest)
+ selector_range = Sass::Source::Range.new(
+ ident_range.start_pos,
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
+ @options[:filename], @options[:importer])
+ rule = Tree::RuleNode.new(res + interp_parsed, selector_range)
+ rule << Tree::CommentNode.new([trailing], :silent) if trailing
+ rule
+ end
+ end
+
+ # @comment
+ # rubocop:disable ParameterLists
+ def parse_property(name, parsed_name, value, prop, line, start_offset)
+ # rubocop:enable ParameterLists
+ if value.strip.empty?
+ expr = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
+ end_offset = start_offset
+ else
+ expr = parse_script(value, :offset => to_parser_offset(start_offset))
+ end_offset = expr.source_range.end_pos.offset - 1
+ end
+ node = Tree::PropNode.new(parse_interp(name), expr, prop)
+ node.value_source_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(line.index, to_parser_offset(start_offset)),
+ Sass::Source::Position.new(line.index, to_parser_offset(end_offset)),
+ @options[:filename], @options[:importer])
+ if value.strip.empty? && line.children.empty?
+ raise SyntaxError.new(
+ "Invalid property: \"#{node.declaration}\" (no value)." +
+ node.pseudo_class_selector_message)
+ end
+
+ node
+ end
+
+ def parse_variable(line)
+ name, value, flags = line.text.scan(Script::MATCH)[0]
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
+ :line => @line + 1) unless line.children.empty?
+ raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
+ :line => @line) unless name && value
+ flags = flags ? flags.split(/\s+/) : []
+ if (invalid_flag = flags.find {|f| f != '!default' && f != '!global'})
+ raise SyntaxError.new("Invalid flag \"#{invalid_flag}\".", :line => @line)
+ end
+
+ # This workaround is needed for the case when the variable value is part of the identifier,
+ # otherwise we end up with the offset equal to the value index inside the name:
+ # $red_color: red;
+ var_lhs_length = 1 + name.length # 1 stands for '$'
+ index = line.text.index(value, line.offset + var_lhs_length) || 0
+ expr = parse_script(value, :offset => to_parser_offset(line.offset + index))
+
+ Tree::VariableNode.new(name, expr, flags.include?('!default'), flags.include?('!global'))
+ end
+
+ def parse_comment(line)
+ if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR
+ silent = line.text[1] == SASS_COMMENT_CHAR
+ loud = !silent && line.text[2] == SASS_LOUD_COMMENT_CHAR
+ if silent
+ value = [line.text]
+ else
+ value = self.class.parse_interp(
+ line.text, line.index, to_parser_offset(line.offset), :filename => @filename)
+ end
+ value = Sass::Util.with_extracted_values(value) do |str|
+ str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
+ format_comment_text(str, silent)
+ end
+ type = if silent
+ :silent
+ elsif loud
+ :loud
+ else
+ :normal
+ end
+ Tree::CommentNode.new(value, type)
+ else
+ Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
+ end
+ end
+
+ DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
+ :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
+ :at_root, :error]
+
+ # @comment
+ # rubocop:disable MethodLength
+ def parse_directive(parent, line, root)
+ directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
+ raise SyntaxError.new("Invalid directive: '@'.") unless directive
+ offset = directive.size + whitespace.size + 1 if whitespace
+
+ directive_name = directive.gsub('-', '_').to_sym
+ if DIRECTIVES.include?(directive_name)
+ return send("parse_#{directive_name}_directive", parent, line, root, value, offset)
+ end
+
+ unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '')
+ if unprefixed_directive == 'supports'
+ parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
+ return Tree::SupportsNode.new(directive, parser.parse_supports_condition)
+ end
+
+ Tree::DirectiveNode.new(
+ value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
+ end
+
+ def parse_while_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
+ Tree::WhileNode.new(parse_script(value, :offset => offset))
+ end
+
+ def parse_if_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
+ Tree::IfNode.new(parse_script(value, :offset => offset))
+ end
+
+ def parse_debug_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
+ :line => @line + 1) unless line.children.empty?
+ offset = line.offset + line.text.index(value).to_i
+ Tree::DebugNode.new(parse_script(value, :offset => offset))
+ end
+
+ def parse_error_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Invalid error directive '@error': expected expression.") unless value
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath error directives.",
+ :line => @line + 1) unless line.children.empty?
+ offset = line.offset + line.text.index(value).to_i
+ Tree::ErrorNode.new(parse_script(value, :offset => offset))
+ end
+
+ def parse_extend_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
+ :line => @line + 1) unless line.children.empty?
+ optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '')
+ offset = line.offset + line.text.index(value).to_i
+ interp_parsed = parse_interp(value, offset)
+ selector_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(offset)),
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
+ @options[:filename], @options[:importer]
+ )
+ Tree::ExtendNode.new(interp_parsed, optional, selector_range)
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ def parse_warn_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
+ :line => @line + 1) unless line.children.empty?
+ offset = line.offset + line.text.index(value).to_i
+ Tree::WarnNode.new(parse_script(value, :offset => offset))
+ end
+
+ def parse_return_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Invalid @return: expected expression.") unless value
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
+ :line => @line + 1) unless line.children.empty?
+ offset = line.offset + line.text.index(value).to_i
+ Tree::ReturnNode.new(parse_script(value, :offset => offset))
+ end
+
+ def parse_charset_directive(parent, line, root, value, offset)
+ name = value && value[/\A(["'])(.*)\1\Z/, 2] # "
+ raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
+ :line => @line + 1) unless line.children.empty?
+ Tree::CharsetNode.new(name)
+ end
+
+ def parse_media_directive(parent, line, root, value, offset)
+ parser = Sass::SCSS::Parser.new(value,
+ @options[:filename], @options[:importer],
+ @line, to_parser_offset(@offset))
+ offset = line.offset + line.text.index('media').to_i - 1
+ parsed_media_query_list = parser.parse_media_query_list.to_a
+ node = Tree::MediaNode.new(parsed_media_query_list)
+ node.source_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(offset)),
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
+ @options[:filename], @options[:importer])
+ node
+ end
+
+ def parse_at_root_directive(parent, line, root, value, offset)
+ return Sass::Tree::AtRootNode.new unless value
+
+ if value.start_with?('(')
+ parser = Sass::SCSS::Parser.new(value,
+ @options[:filename], @options[:importer],
+ @line, to_parser_offset(@offset))
+ offset = line.offset + line.text.index('at-root').to_i - 1
+ return Tree::AtRootNode.new(parser.parse_at_root_query)
+ end
+
+ at_root_node = Tree::AtRootNode.new
+ parsed = parse_interp(value, offset)
+ rule_node = Tree::RuleNode.new(parsed, full_line_range(line))
+
+ # The caller expects to automatically add children to the returned node
+ # and we want it to add children to the rule node instead, so we
+ # manually handle the wiring here and return nil so the caller doesn't
+ # duplicate our efforts.
+ append_children(rule_node, line.children, false)
+ at_root_node << rule_node
+ parent << at_root_node
+ nil
+ end
+
+ def parse_for_directive(parent, line, root, value, offset)
+ var, from_expr, to_name, to_expr =
+ value.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
+
+ if var.nil? # scan failed, try to figure out why for error message
+ if value !~ /^[^\s]+/
+ expected = "variable name"
+ elsif value !~ /^[^\s]+\s+from\s+.+/
+ expected = "'from <expr>'"
+ else
+ expected = "'to <expr>' or 'through <expr>'"
+ end
+ raise SyntaxError.new("Invalid for directive '@for #{value}': expected #{expected}.")
+ end
+ raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
+
+ var = var[1..-1]
+ parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
+ parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
+ Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
+ end
+
+ def parse_each_directive(parent, line, root, value, offset)
+ vars, list_expr = value.scan(/^([^\s]+(?:\s*,\s*[^\s]+)*)\s+in\s+(.+)$/).first
+
+ if vars.nil? # scan failed, try to figure out why for error message
+ if value !~ /^[^\s]+/
+ expected = "variable name"
+ elsif value !~ /^[^\s]+(?:\s*,\s*[^\s]+)*[^\s]+\s+from\s+.+/
+ expected = "'in <expr>'"
+ end
+ raise SyntaxError.new("Invalid each directive '@each #{value}': expected #{expected}.")
+ end
+
+ vars = vars.split(',').map do |var|
+ var.strip!
+ raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
+ var[1..-1]
+ end
+
+ parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr))
+ Tree::EachNode.new(vars, parsed_list)
+ end
+
+ def parse_else_directive(parent, line, root, value, offset)
+ previous = parent.children.last
+ raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
+
+ if value
+ if value !~ /^if\s+(.+)/
+ raise SyntaxError.new("Invalid else directive '@else #{value}': expected 'if <expr>'.")
+ end
+ expr = parse_script($1, :offset => line.offset + line.text.index($1))
+ end
+
+ node = Tree::IfNode.new(expr)
+ append_children(node, line.children, false)
+ previous.add_else node
+ nil
+ end
+
+ def parse_import_directive(parent, line, root, value, offset)
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
+ :line => @line + 1) unless line.children.empty?
+
+ scanner = Sass::Util::MultibyteStringScanner.new(value)
+ values = []
+
+ loop do
+ unless (node = parse_import_arg(scanner, offset + scanner.pos))
+ raise SyntaxError.new(
+ "Invalid @import: expected file to import, was #{scanner.rest.inspect}",
+ :line => @line)
+ end
+ values << node
+ break unless scanner.scan(/,\s*/)
+ end
+
+ if scanner.scan(/;/)
+ raise SyntaxError.new("Invalid @import: expected end of line, was \";\".",
+ :line => @line)
+ end
+
+ values
+ end
+
+ # @comment
+ # rubocop:disable MethodLength
+ def parse_import_arg(scanner, offset)
+ return if scanner.eos?
+
+ if scanner.match?(/url\(/i)
+ script_parser = Sass::Script::Parser.new(scanner, @line, to_parser_offset(offset), @options)
+ str = script_parser.parse_string
+
+ if scanner.eos?
+ end_pos = str.source_range.end_pos
+ node = Tree::CssImportNode.new(str)
+ else
+ media_parser = Sass::SCSS::Parser.new(scanner,
+ @options[:filename], @options[:importer],
+ @line, str.source_range.end_pos.offset)
+ media = media_parser.parse_media_query_list
+ end_pos = Sass::Source::Position.new(@line, media_parser.offset + 1)
+ node = Tree::CssImportNode.new(str, media.to_a)
+ end
+
+ node.source_range = Sass::Source::Range.new(
+ str.source_range.start_pos, end_pos,
+ @options[:filename], @options[:importer])
+ return node
+ end
+
+ unless (quoted_val = scanner.scan(Sass::SCSS::RX::STRING))
+ scanned = scanner.scan(/[^,;]+/)
+ node = Tree::ImportNode.new(scanned)
+ start_parser_offset = to_parser_offset(offset)
+ node.source_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, start_parser_offset),
+ Sass::Source::Position.new(@line, start_parser_offset + scanned.length),
+ @options[:filename], @options[:importer])
+ return node
+ end
+
+ start_offset = offset
+ offset += scanner.matched.length
+ val = Sass::Script::Value::String.value(scanner[1] || scanner[2])
+ scanned = scanner.scan(/\s*/)
+ if !scanner.match?(/[,;]|$/)
+ offset += scanned.length if scanned
+ media_parser = Sass::SCSS::Parser.new(scanner,
+ @options[:filename], @options[:importer], @line, offset)
+ media = media_parser.parse_media_query_list
+ node = Tree::CssImportNode.new(quoted_val, media.to_a)
+ node.source_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
+ Sass::Source::Position.new(@line, media_parser.offset),
+ @options[:filename], @options[:importer])
+ elsif val =~ %r{^(https?:)?//}
+ node = Tree::CssImportNode.new(quoted_val)
+ node.source_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
+ Sass::Source::Position.new(@line, to_parser_offset(offset)),
+ @options[:filename], @options[:importer])
+ else
+ node = Tree::ImportNode.new(val)
+ node.source_range = Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
+ Sass::Source::Position.new(@line, to_parser_offset(offset)),
+ @options[:filename], @options[:importer])
+ end
+ node
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ def parse_mixin_directive(parent, line, root, value, offset)
+ parse_mixin_definition(line)
+ end
+
+ MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
+ def parse_mixin_definition(line)
+ name, arg_string = line.text.scan(MIXIN_DEF_RE).first
+ raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
+
+ offset = line.offset + line.text.size - arg_string.size
+ args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
+ parse_mixin_definition_arglist
+ Tree::MixinDefNode.new(name, args, splat)
+ end
+
+ CONTENT_RE = /^ content\s*(.+)?$/
+ def parse_content_directive(parent, line, root, value, offset)
+ trailing = line.text.scan(CONTENT_RE).first.first
+ unless trailing.nil?
+ raise SyntaxError.new(
+ "Invalid content directive. Trailing characters found: \"#{trailing}\".")
+ end
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.",
+ :line => line.index + 1) unless line.children.empty?
+ Tree::ContentNode.new
+ end
+
+ def parse_include_directive(parent, line, root, value, offset)
+ parse_mixin_include(line, root)
+ end
+
+ MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
+ def parse_mixin_include(line, root)
+ name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
+ raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
+
+ offset = line.offset + line.text.size - arg_string.size
+ args, keywords, splat, kwarg_splat =
+ Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
+ parse_mixin_include_arglist
+ Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat)
+ end
+
+ FUNCTION_RE = /^ function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
+ def parse_function_directive(parent, line, root, value, offset)
+ name, arg_string = line.text.scan(FUNCTION_RE).first
+ raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
+
+ offset = line.offset + line.text.size - arg_string.size
+ args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
+ parse_function_definition_arglist
+ Tree::FunctionNode.new(name, args, splat)
+ end
+
+ def parse_script(script, options = {})
+ line = options[:line] || @line
+ offset = options[:offset] || @offset + 1
+ Script.parse(script, line, offset, @options)
+ end
+
+ def format_comment_text(text, silent)
+ content = text.split("\n")
+
+ if content.first && content.first.strip.empty?
+ removed_first = true
+ content.shift
+ end
+
+ return "/* */" if content.empty?
+ content.last.gsub!(/ ?\*\/ *$/, '')
+ first = content.shift unless removed_first
+ content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
+ content.unshift first unless removed_first
+ if silent
+ "/*" + content.join("\n *") + " */"
+ else
+ # The #gsub fixes the case of a trailing */
+ "/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
+ end
+ end
+
+ def parse_interp(text, offset = 0)
+ self.class.parse_interp(text, @line, offset, :filename => @filename)
+ end
+
+ # Parser tracks 1-based line and offset, so our offset should be converted.
+ def to_parser_offset(offset)
+ offset + 1
+ end
+
+ def full_line_range(line)
+ Sass::Source::Range.new(
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset)),
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
+ @options[:filename], @options[:importer])
+ end
+
+ # It's important that this have strings (at least)
+ # at the beginning, the end, and between each Script::Tree::Node.
+ #
+ # @private
+ def self.parse_interp(text, line, offset, options)
+ res = []
+ rest = Sass::Shared.handle_interpolation text do |scan|
+ escapes = scan[2].size
+ res << scan.matched[0...-2 - escapes]
+ if escapes.odd?
+ res << "\\" * (escapes - 1) << '#{'
+ else
+ res << "\\" * [0, escapes - 1].max
+ # Add 1 to emulate to_parser_offset.
+ res << Script::Parser.new(
+ scan, line, offset + scan.pos - scan.matched_size + 1, options).
+ parse_interpolated
+ end
+ end
+ res << rest
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/environment.rb
b/backends/css/gems/sass-3.4.9/lib/sass/environment.rb
new file mode 100644
index 0000000..f4c1231
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/environment.rb
@@ -0,0 +1,208 @@
+require 'set'
+
+module Sass
+ # The abstract base class for lexical environments for SassScript.
+ class BaseEnvironment
+ class << self
+ # Note: when updating this,
+ # update sass/yard/inherited_hash.rb as well.
+ def inherited_hash_accessor(name)
+ inherited_hash_reader(name)
+ inherited_hash_writer(name)
+ end
+
+ def inherited_hash_reader(name)
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{name}(name)
+ _#{name}(name.tr('_', '-'))
+ end
+
+ def _#{name}(name)
+ (@#{name}s && @#{name}s[name]) || @parent && @parent._#{name}(name)
+ end
+ protected :_#{name}
+
+ def is_#{name}_global?(name)
+ return ! parent if @#{name}s && @#{name}s.has_key?(name)
+ @parent && @parent.is_#{name}_global?(name)
+ end
+ RUBY
+ end
+
+ def inherited_hash_writer(name)
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def set_#{name}(name, value)
+ name = name.tr('_', '-')
+ @#{name}s[name] = value unless try_set_#{name}(name, value)
+ end
+
+ def try_set_#{name}(name, value)
+ @#{name}s ||= {}
+ if @#{name}s.include?(name)
+ @#{name}s[name] = value
+ true
+ elsif @parent && ! parent global?
+ @parent.try_set_#{name}(name, value)
+ else
+ false
+ end
+ end
+ protected :try_set_#{name}
+
+ def set_local_#{name}(name, value)
+ @#{name}s ||= {}
+ @#{name}s[name.tr('_', '-')] = value
+ end
+
+ def set_global_#{name}(name, value)
+ global_env.set_#{name}(name, value)
+ end
+ RUBY
+ end
+ end
+
+ # The options passed to the Sass Engine.
+ attr_reader :options
+
+ attr_writer :caller
+ attr_writer :content
+ attr_writer :selector
+
+ # variable
+ # Script::Value
+ inherited_hash_reader :var
+
+ # mixin
+ # Sass::Callable
+ inherited_hash_reader :mixin
+
+ # function
+ # Sass::Callable
+ inherited_hash_reader :function
+
+ # @param options [{Symbol => Object}] The options hash. See
+ # {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ # @param parent [Environment] See \{#parent}
+ def initialize(parent = nil, options = nil)
+ @parent = parent
+ @options = options || (parent && parent.options) || {}
+ @stack = Sass::Stack.new if @parent.nil?
+ end
+
+ # Returns whether this is the global environment.
+ #
+ # @return [Boolean]
+ def global?
+ @parent.nil?
+ end
+
+ # The environment of the caller of this environment's mixin or function.
+ # @return {Environment?}
+ def caller
+ @caller || (@parent && @parent.caller)
+ end
+
+ # The content passed to this environment. This is naturally only set
+ # for mixin body environments with content passed in.
+ #
+ # @return {[Array<Sass::Tree::Node>, Environment]?} The content nodes and
+ # the lexical environment of the content block.
+ def content
+ @content || (@parent && @parent.content)
+ end
+
+ # The selector for the current CSS rule, or nil if there is no
+ # current CSS rule.
+ #
+ # @return [Selector::CommaSequence?] The current selector, with any
+ # nesting fully resolved.
+ def selector
+ @selector || (@caller && @caller.selector) || (@parent && @parent.selector)
+ end
+
+ # The top-level Environment object.
+ #
+ # @return [Environment]
+ def global_env
+ @global_env ||= global? ? self : @parent.global_env
+ end
+
+ # The import/mixin stack.
+ #
+ # @return [Sass::Stack]
+ def stack
+ @stack || global_env.stack
+ end
+ end
+
+ # The lexical environment for SassScript.
+ # This keeps track of variable, mixin, and function definitions.
+ #
+ # A new environment is created for each level of Sass nesting.
+ # This allows variables to be lexically scoped.
+ # The new environment refers to the environment in the upper scope,
+ # so it has access to variables defined in enclosing scopes,
+ # but new variables are defined locally.
+ #
+ # Environment also keeps track of the {Engine} options
+ # so that they can be made available to {Sass::Script::Functions}.
+ class Environment < BaseEnvironment
+ # The enclosing environment,
+ # or nil if this is the global environment.
+ #
+ # @return [Environment]
+ attr_reader :parent
+
+ # variable
+ # Script::Value
+ inherited_hash_writer :var
+
+ # mixin
+ # Sass::Callable
+ inherited_hash_writer :mixin
+
+ # function
+ # Sass::Callable
+ inherited_hash_writer :function
+ end
+
+ # A read-only wrapper for a lexical environment for SassScript.
+ class ReadOnlyEnvironment < BaseEnvironment
+ # The read-only environment of the caller of this environment's mixin or function.
+ #
+ # @see BaseEnvironment#caller
+ # @return {ReadOnlyEnvironment}
+ def caller
+ return @caller if @caller
+ env = super
+ @caller ||= env.is_a?(ReadOnlyEnvironment) ? env : ReadOnlyEnvironment.new(env, env.options)
+ end
+
+ # The read-only content passed to this environment.
+ #
+ # @see BaseEnvironment#content
+ # @return {ReadOnlyEnvironment}
+ def content
+ return @content if @content
+ env = super
+ @content ||= env.is_a?(ReadOnlyEnvironment) ? env : ReadOnlyEnvironment.new(env, env.options)
+ end
+ end
+
+ # An environment that can write to in-scope global variables, but doesn't
+ # create new variables in the global scope. Useful for top-level control
+ # directives.
+ class SemiGlobalEnvironment < Environment
+ def try_set_var(name, value)
+ @vars ||= {}
+ if @vars.include?(name)
+ @vars[name] = value
+ true
+ elsif @parent
+ @parent.try_set_var(name, value)
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/error.rb b/backends/css/gems/sass-3.4.9/lib/sass/error.rb
new file mode 100644
index 0000000..79b2f90
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/error.rb
@@ -0,0 +1,198 @@
+module Sass
+ # An exception class that keeps track of
+ # the line of the Sass template it was raised on
+ # and the Sass file that was being parsed (if applicable).
+ #
+ # All Sass errors are raised as {Sass::SyntaxError}s.
+ #
+ # When dealing with SyntaxErrors,
+ # it's important to provide filename and line number information.
+ # This will be used in various error reports to users, including backtraces;
+ # see \{#sass\_backtrace} for details.
+ #
+ # Some of this information is usually provided as part of the constructor.
+ # New backtrace entries can be added with \{#add\_backtrace},
+ # which is called when an exception is raised between files (e.g. with ` import`).
+ #
+ # Often, a chunk of code will all have similar backtrace information -
+ # the same filename or even line.
+ # It may also be useful to have a default line number set.
+ # In those situations, the default values can be used
+ # by omitting the information on the original exception,
+ # and then calling \{#modify\_backtrace} in a wrapper `rescue`.
+ # When doing this, be sure that all exceptions ultimately end up
+ # with the information filled in.
+ class SyntaxError < StandardError
+ # The backtrace of the error within Sass files.
+ # This is an array of hashes containing information for a single entry.
+ # The hashes have the following keys:
+ #
+ # `:filename`
+ # : The name of the file in which the exception was raised,
+ # or `nil` if no filename is available.
+ #
+ # `:mixin`
+ # : The name of the mixin in which the exception was raised,
+ # or `nil` if it wasn't raised in a mixin.
+ #
+ # `:line`
+ # : The line of the file on which the error occurred. Never nil.
+ #
+ # This information is also included in standard backtrace format
+ # in the output of \{#backtrace}.
+ #
+ # @return [Aray<{Symbol => Object>}]
+ attr_accessor :sass_backtrace
+
+ # The text of the template where this error was raised.
+ #
+ # @return [String]
+ attr_accessor :sass_template
+
+ # @param msg [String] The error message
+ # @param attrs [{Symbol => Object}] The information in the backtrace entry.
+ # See \{#sass\_backtrace}
+ def initialize(msg, attrs = {})
+ @message = msg
+ @sass_backtrace = []
+ add_backtrace(attrs)
+ end
+
+ # The name of the file in which the exception was raised.
+ # This could be `nil` if no filename is available.
+ #
+ # @return [String, nil]
+ def sass_filename
+ sass_backtrace.first[:filename]
+ end
+
+ # The name of the mixin in which the error occurred.
+ # This could be `nil` if the error occurred outside a mixin.
+ #
+ # @return [Fixnum]
+ def sass_mixin
+ sass_backtrace.first[:mixin]
+ end
+
+ # The line of the Sass template on which the error occurred.
+ #
+ # @return [Fixnum]
+ def sass_line
+ sass_backtrace.first[:line]
+ end
+
+ # Adds an entry to the exception's Sass backtrace.
+ #
+ # @param attrs [{Symbol => Object}] The information in the backtrace entry.
+ # See \{#sass\_backtrace}
+ def add_backtrace(attrs)
+ sass_backtrace << attrs.reject {|k, v| v.nil?}
+ end
+
+ # Modify the top Sass backtrace entries
+ # (that is, the most deeply nested ones)
+ # to have the given attributes.
+ #
+ # Specifically, this goes through the backtrace entries
+ # from most deeply nested to least,
+ # setting the given attributes for each entry.
+ # If an entry already has one of the given attributes set,
+ # the pre-existing attribute takes precedence
+ # and is not used for less deeply-nested entries
+ # (even if they don't have that attribute set).
+ #
+ # @param attrs [{Symbol => Object}] The information to add to the backtrace entry.
+ # See \{#sass\_backtrace}
+ def modify_backtrace(attrs)
+ attrs = attrs.reject {|k, v| v.nil?}
+ # Move backwards through the backtrace
+ (0...sass_backtrace.size).to_a.reverse.each do |i|
+ entry = sass_backtrace[i]
+ sass_backtrace[i] = attrs.merge(entry)
+ attrs.reject! {|k, v| entry.include?(k)}
+ break if attrs.empty?
+ end
+ end
+
+ # @return [String] The error message
+ def to_s
+ @message
+ end
+
+ # Returns the standard exception backtrace,
+ # including the Sass backtrace.
+ #
+ # @return [Array<String>]
+ def backtrace
+ return nil if super.nil?
+ return super if sass_backtrace.all? {|h| h.empty?}
+ sass_backtrace.map do |h|
+ "#{h[:filename] || "(sass)"}:#{h[:line]}" +
+ (h[:mixin] ? ":in `#{h[:mixin]}'" : "")
+ end + super
+ end
+
+ # Returns a string representation of the Sass backtrace.
+ #
+ # @param default_filename [String] The filename to use for unknown files
+ # @see #sass_backtrace
+ # @return [String]
+ def sass_backtrace_str(default_filename = "an unknown file")
+ lines = message.split("\n")
+ msg = lines[0] + lines[1..-1].
+ map {|l| "\n" + (" " * "Error: ".size) + l}.join
+ "Error: #{msg}" +
+ Sass::Util.enum_with_index(sass_backtrace).map do |entry, i|
+ "\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
+ " of #{entry[:filename] || default_filename}" +
+ (entry[:mixin] ? ", in `#{entry[:mixin]}'" : "")
+ end.join
+ end
+
+ class << self
+ # Returns an error report for an exception in CSS format.
+ #
+ # @param e [Exception]
+ # @param line_offset [Fixnum] The number of the first line of the Sass template.
+ # @return [String] The error report
+ # @raise [Exception] `e`, if the
+ # {file:SASS_REFERENCE.md#full_exception-option `:full_exception`} option
+ # is set to false.
+ def exception_to_css(e, line_offset = 1)
+ header = header_string(e, line_offset)
+
+ <<END
+/*
+#{header.gsub("*/", "*\\/")}
+
+Backtrace:\n#{e.backtrace.join("\n").gsub("*/", "*\\/")}
+*/
+body:before {
+ white-space: pre;
+ font-family: monospace;
+ content: "#{header.gsub('"', '\"').gsub("\n", '\\A ')}"; }
+END
+ end
+
+ private
+
+ def header_string(e, line_offset)
+ unless e.is_a?(Sass::SyntaxError) && e.sass_line && e.sass_template
+ return "#{e.class}: #{e.message}"
+ end
+
+ line_num = e.sass_line + 1 - line_offset
+ min = [line_num - 6, 0].max
+ section = e.sass_template.rstrip.split("\n")[min ... line_num + 5]
+ return e.sass_backtrace_str if section.nil? || section.empty?
+
+ e.sass_backtrace_str + "\n\n" + Sass::Util.enum_with_index(section).
+ map {|line, i| "#{line_offset + min + i}: #{line}"}.join("\n")
+ end
+ end
+ end
+
+ # The class for Sass errors that are raised due to invalid unit conversions
+ # in SassScript.
+ class UnitConversionError < SyntaxError; end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/exec.rb b/backends/css/gems/sass-3.4.9/lib/sass/exec.rb
new file mode 100644
index 0000000..8add324
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/exec.rb
@@ -0,0 +1,9 @@
+module Sass
+ # This module handles the Sass executables (`sass` and `sass-convert`).
+ module Exec
+ end
+end
+
+require 'sass/exec/base'
+require 'sass/exec/sass_scss'
+require 'sass/exec/sass_convert'
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/exec/base.rb
b/backends/css/gems/sass-3.4.9/lib/sass/exec/base.rb
new file mode 100644
index 0000000..3b13e6a
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/exec/base.rb
@@ -0,0 +1,199 @@
+require 'optparse'
+
+module Sass::Exec
+ # The abstract base class for Sass executables.
+ class Base
+ # @param args [Array<String>] The command-line arguments
+ def initialize(args)
+ @args = args
+ @options = {}
+ end
+
+ # Parses the command-line arguments and runs the executable.
+ # Calls `Kernel#exit` at the end, so it never returns.
+ #
+ # @see #parse
+ def parse!
+ # rubocop:disable RescueException
+ begin
+ parse
+ rescue Exception => e
+ # Exit code 65 indicates invalid data per
+ # http://www.freebsd.org/cgi/man.cgi?query=sysexits. Setting it via
+ # at_exit is a bit of a hack, but it allows us to rethrow when --trace
+ # is active and get both the built-in exception formatting and the
+ # correct exit code.
+ at_exit {exit 65} if e.is_a?(Sass::SyntaxError)
+
+ raise e if @options[:trace] || e.is_a?(SystemExit)
+
+ if e.is_a?(Sass::SyntaxError)
+ $stderr.puts e.sass_backtrace_str("standard input")
+ else
+ $stderr.print "#{e.class}: " unless e.class == RuntimeError
+ $stderr.puts e.message.to_s
+ end
+ $stderr.puts " Use --trace for backtrace."
+
+ exit 1
+ end
+ exit 0
+ # rubocop:enable RescueException
+ end
+
+ # Parses the command-line arguments and runs the executable.
+ # This does not handle exceptions or exit the program.
+ #
+ # @see #parse!
+ def parse
+ @opts = OptionParser.new(&method(:set_opts))
+ @opts.parse!(@args)
+
+ process_result
+
+ @options
+ end
+
+ # @return [String] A description of the executable
+ def to_s
+ @opts.to_s
+ end
+
+ protected
+
+ # Finds the line of the source template
+ # on which an exception was raised.
+ #
+ # @param exception [Exception] The exception
+ # @return [String] The line number
+ def get_line(exception)
+ # SyntaxErrors have weird line reporting
+ # when there's trailing whitespace
+ if exception.is_a?(::SyntaxError)
+ return (exception.message.scan(/:(\d+)/).first || ["??"]).first
+ end
+ (exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
+ end
+
+ # Tells optparse how to parse the arguments
+ # available for all executables.
+ #
+ # This is meant to be overridden by subclasses
+ # so they can add their own options.
+ #
+ # @param opts [OptionParser]
+ def set_opts(opts)
+ Sass::Util.abstract(this)
+ end
+
+ # Set an option for specifying `Encoding.default_external`.
+ #
+ # @param opts [OptionParser]
+ def encoding_option(opts)
+ encoding_desc = if Sass::Util.ruby1_8?
+ 'Does not work in Ruby 1.8.'
+ else
+ 'Specify the default encoding for input files.'
+ end
+ opts.on('-E', '--default-encoding ENCODING', encoding_desc) do |encoding|
+ if Sass::Util.ruby1_8?
+ $stderr.puts "Specifying the encoding is not supported in ruby 1.8."
+ exit 1
+ else
+ Encoding.default_external = encoding
+ end
+ end
+ end
+
+ # Processes the options set by the command-line arguments. In particular,
+ # sets ` options[:input]` and ` options[:output]` to appropriate IO streams.
+ #
+ # This is meant to be overridden by subclasses
+ # so they can run their respective programs.
+ def process_result
+ input, output = @options[:input], @options[:output]
+ args = @args.dup
+ input ||=
+ begin
+ filename = args.shift
+ @options[:filename] = filename
+ open_file(filename) || $stdin
+ end
+ @options[:output_filename] = args.shift
+ output ||= @options[:output_filename] || $stdout
+ @options[:input], @options[:output] = input, output
+ end
+
+ COLORS = {:red => 31, :green => 32, :yellow => 33}
+
+ # Prints a status message about performing the given action,
+ # colored using the given color (via terminal escapes) if possible.
+ #
+ # @param name [#to_s] A short name for the action being performed.
+ # Shouldn't be longer than 11 characters.
+ # @param color [Symbol] The name of the color to use for this action.
+ # Can be `:red`, `:green`, or `:yellow`.
+ def puts_action(name, color, arg)
+ return if @options[:for_engine][:quiet]
+ printf color(color, "%11s %s\n"), name, arg
+ STDOUT.flush
+ end
+
+ # Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set.
+ #
+ # @param args [Array] Passed on to `Kernel.puts`
+ def puts(*args)
+ return if @options[:for_engine][:quiet]
+ Kernel.puts(*args)
+ end
+
+ # Wraps the given string in terminal escapes
+ # causing it to have the given color.
+ # If terminal esapes aren't supported on this platform,
+ # just returns the string instead.
+ #
+ # @param color [Symbol] The name of the color to use.
+ # Can be `:red`, `:green`, or `:yellow`.
+ # @param str [String] The string to wrap in the given color.
+ # @return [String] The wrapped string.
+ def color(color, str)
+ raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
+
+ # Almost any real Unix terminal will support color,
+ # so we just filter for Windows terms (which don't set TERM)
+ # and not-real terminals, which aren't ttys.
+ return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
+ "\e[#{COLORS[color]}m#{str}\e[0m"
+ end
+
+ def write_output(text, destination)
+ if destination.is_a?(String)
+ open_file(destination, 'w') {|file| file.write(text)}
+ else
+ destination.write(text)
+ end
+ end
+
+ private
+
+ def open_file(filename, flag = 'r')
+ return if filename.nil?
+ flag = 'wb' if @options[:unix_newlines] && flag == 'w'
+ file = File.open(filename, flag)
+ return file unless block_given?
+ yield file
+ file.close
+ end
+
+ def handle_load_error(err)
+ dep = err.message[/^no such file to load -- (.*)/, 1]
+ raise err if @options[:trace] || dep.nil? || dep.empty?
+ $stderr.puts <<MESSAGE
+Required dependency #{dep} not found!
+ Run "gem install #{dep}" to get it.
+ Use --trace for backtrace.
+MESSAGE
+ exit 1
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/exec/sass_convert.rb
b/backends/css/gems/sass-3.4.9/lib/sass/exec/sass_convert.rb
new file mode 100644
index 0000000..6ea43f2
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/exec/sass_convert.rb
@@ -0,0 +1,269 @@
+require 'optparse'
+require 'fileutils'
+
+module Sass::Exec
+ # The `sass-convert` executable.
+ class SassConvert < Base
+ # @param args [Array<String>] The command-line arguments
+ def initialize(args)
+ super
+ require 'sass'
+ @options[:for_tree] = {}
+ @options[:for_engine] = {:cache => false, :read_cache => true}
+ end
+
+ # Tells optparse how to parse the arguments.
+ #
+ # @param opts [OptionParser]
+ def set_opts(opts)
+ opts.banner = <<END
+Usage: sass-convert [options] [INPUT] [OUTPUT]
+
+Description:
+ Converts between CSS, indented syntax, and SCSS files. For example,
+ this can convert from the indented syntax to SCSS, or from CSS to
+ SCSS (adding appropriate nesting).
+END
+
+ common_options(opts)
+ style(opts)
+ input_and_output(opts)
+ miscellaneous(opts)
+ end
+
+ # Processes the options set by the command-line arguments,
+ # and runs the CSS compiler appropriately.
+ def process_result
+ require 'sass'
+
+ if @options[:recursive]
+ process_directory
+ return
+ end
+
+ super
+ input = @options[:input]
+ if File.directory?(input)
+ raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)"
+ end
+ output = @options[:output]
+ output = input if @options[:in_place]
+ process_file(input, output)
+ end
+
+ private
+
+ def common_options(opts)
+ opts.separator ''
+ opts.separator 'Common Options:'
+
+ opts.on('-F', '--from FORMAT',
+ 'The format to convert from. Can be css, scss, sass.',
+ 'By default, this is inferred from the input filename.',
+ 'If there is none, defaults to css.') do |name|
+ @options[:from] = name.downcase.to_sym
+ raise "sass-convert no longer supports LessCSS." if @options[:from] == :less
+ unless [:css, :scss, :sass].include?(@options[:from])
+ raise "Unknown format for sass-convert --from: #{name}"
+ end
+ end
+
+ opts.on('-T', '--to FORMAT',
+ 'The format to convert to. Can be scss or sass.',
+ 'By default, this is inferred from the output filename.',
+ 'If there is none, defaults to sass.') do |name|
+ @options[:to] = name.downcase.to_sym
+ unless [:scss, :sass].include?(@options[:to])
+ raise "Unknown format for sass-convert --to: #{name}"
+ end
+ end
+
+ opts.on('-i', '--in-place',
+ 'Convert a file to its own syntax.',
+ 'This can be used to update some deprecated syntax.') do
+ @options[:in_place] = true
+ end
+
+ opts.on('-R', '--recursive',
+ 'Convert all the files in a directory. Requires --from and --to.') do
+ @options[:recursive] = true
+ end
+
+ opts.on("-?", "-h", "--help", "Show this help message.") do
+ puts opts
+ exit
+ end
+
+ opts.on("-v", "--version", "Print the Sass version.") do
+ puts("Sass #{Sass.version[:string]}")
+ exit
+ end
+ end
+
+ def style(opts)
+ opts.separator ''
+ opts.separator 'Style:'
+
+ opts.on('--dasherize', 'Convert underscores to dashes.') do
+ @options[:for_tree][:dasherize] = true
+ end
+
+ opts.on('--indent NUM',
+ 'How many spaces to use for each level of indentation. Defaults to 2.',
+ '"t" means use hard tabs.') do |indent|
+
+ if indent == 't'
+ @options[:for_tree][:indent] = "\t"
+ else
+ @options[:for_tree][:indent] = " " * indent.to_i
+ end
+ end
+
+ opts.on('--old', 'Output the old-style ":prop val" property syntax.',
+ 'Only meaningful when generating Sass.') do
+ @options[:for_tree][:old] = true
+ end
+ end
+
+ def input_and_output(opts)
+ opts.separator ''
+ opts.separator 'Input and Output:'
+
+ opts.on('-s', '--stdin', :NONE,
+ 'Read input from standard input instead of an input file.',
+ 'This is the default if no input file is specified. Requires --from.') do
+ @options[:input] = $stdin
+ end
+
+ encoding_option(opts)
+
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.',
+ ('Always true on Unix.' unless Sass::Util.windows?)) do
+ @options[:unix_newlines] = true if Sass::Util.windows?
+ end
+ end
+
+ def miscellaneous(opts)
+ opts.separator ''
+ opts.separator 'Miscellaneous:'
+
+ opts.on('--cache-location PATH',
+ 'The path to save parsed Sass files. Defaults to .sass-cache.') do |loc|
+ @options[:for_engine][:cache_location] = loc
+ end
+
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
+ @options[:for_engine][:read_cache] = false
+ end
+
+ opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error') do
+ @options[:trace] = true
+ end
+ end
+
+ def process_directory
+ unless @options[:input] = @args.shift
+ raise "Error: directory required when using --recursive."
+ end
+
+ output = @options[:output] = @args.shift
+ raise "Error: --from required when using --recursive." unless @options[:from]
+ raise "Error: --to required when using --recursive." unless @options[:to]
+ unless File.directory?(@options[:input])
+ raise "Error: '#{ options[:input]}' is not a directory"
+ end
+ if @options[:output] && File.exist?(@options[:output]) &&
+ !File.directory?(@options[:output])
+ raise "Error: '#{ options[:output]}' is not a directory"
+ end
+ @options[:output] ||= @options[:input]
+
+ if @options[:to] == @options[:from] && ! options[:in_place]
+ fmt = @options[:from]
+ raise "Error: converting from #{fmt} to #{fmt} without --in-place"
+ end
+
+ ext = @options[:from]
+ Sass::Util.glob("#{ options[:input]}/**/*.#{ext}") do |f|
+ output =
+ if @options[:in_place]
+ f
+ elsif @options[:output]
+ output_name = f.gsub(/\.(c|sa|sc|le)ss$/, " #{ options[:to]}")
+ output_name[0 options[:input].size] = @options[:output]
+ output_name
+ else
+ f.gsub(/\.(c|sa|sc|le)ss$/, " #{ options[:to]}")
+ end
+
+ unless File.directory?(File.dirname(output))
+ puts_action :directory, :green, File.dirname(output)
+ FileUtils.mkdir_p(File.dirname(output))
+ end
+ puts_action :convert, :green, f
+ if File.exist?(output)
+ puts_action :overwrite, :yellow, output
+ else
+ puts_action :create, :green, output
+ end
+
+ process_file(f, output)
+ end
+ end
+
+ def process_file(input, output)
+ input_path, output_path = path_for(input), path_for(output)
+ if input_path
+ @options[:from] ||=
+ case input_path
+ when /\.scss$/; :scss
+ when /\.sass$/; :sass
+ when /\.less$/; raise "sass-convert no longer supports LessCSS."
+ when /\.css$/; :css
+ end
+ elsif @options[:in_place]
+ raise "Error: the --in-place option requires a filename."
+ end
+
+ if output_path
+ @options[:to] ||=
+ case output_path
+ when /\.scss$/; :scss
+ when /\.sass$/; :sass
+ end
+ end
+
+ @options[:from] ||= :css
+ @options[:to] ||= :sass
+ @options[:for_engine][:syntax] = @options[:from]
+
+ out =
+ Sass::Util.silence_sass_warnings do
+ if @options[:from] == :css
+ require 'sass/css'
+ Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
+ else
+ if input_path
+ Sass::Engine.for_file(input_path, @options[:for_engine])
+ else
+ Sass::Engine.new(input.read, @options[:for_engine])
+ end.to_tree.send("to_#{ options[:to]}", @options[:for_tree])
+ end
+ end
+
+ output = input_path if @options[:in_place]
+ write_output(out, output)
+ rescue Sass::SyntaxError => e
+ raise e if @options[:trace]
+ file = " of #{e.sass_filename}" if e.sass_filename
+ raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
+ rescue LoadError => err
+ handle_load_error(err)
+ end
+
+ def path_for(file)
+ return file.path if file.is_a?(File)
+ return file if file.is_a?(String)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/exec/sass_scss.rb
b/backends/css/gems/sass-3.4.9/lib/sass/exec/sass_scss.rb
new file mode 100644
index 0000000..9012f8d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/exec/sass_scss.rb
@@ -0,0 +1,444 @@
+module Sass::Exec
+ # The `sass` and `scss` executables.
+ class SassScss < Base
+ attr_reader :default_syntax
+
+ # @param args [Array<String>] The command-line arguments
+ def initialize(args, default_syntax)
+ super(args)
+ @options[:sourcemap] = :auto
+ @options[:for_engine] = {
+ :load_paths => default_sass_path
+ }
+ @default_syntax = default_syntax
+ end
+
+ protected
+
+ # Tells optparse how to parse the arguments.
+ #
+ # @param opts [OptionParser]
+ def set_opts(opts)
+ opts.banner = <<END
+Usage: #{default_syntax} [options] [INPUT] [OUTPUT]
+
+Description:
+ Converts SCSS or Sass files to CSS.
+END
+
+ common_options(opts)
+ watching_and_updating(opts)
+ input_and_output(opts)
+ miscellaneous(opts)
+ end
+
+ # Processes the options set by the command-line arguments,
+ # and runs the Sass compiler appropriately.
+ def process_result
+ require 'sass'
+
+ if ! options[:update] && ! options[:watch] &&
+ @args.first && colon_path?(@args.first)
+ if @args.size == 1
+ @args = split_colon_path(@args.first)
+ else
+ @options[:update] = true
+ end
+ end
+ load_compass if @options[:compass]
+ return interactive if @options[:interactive]
+ return watch_or_update if @options[:watch] || @options[:update]
+ super
+
+ if @options[:sourcemap] != :none && @options[:output_filename]
+ @options[:sourcemap_filename] = Sass::Util.sourcemap_name(@options[:output_filename])
+ end
+
+ @options[:for_engine][:filename] = @options[:filename]
+ @options[:for_engine][:css_filename] = @options[:output] if @options[:output].is_a?(String)
+ @options[:for_engine][:sourcemap_filename] = @options[:sourcemap_filename]
+ @options[:for_engine][:sourcemap] = @options[:sourcemap]
+
+ run
+ end
+
+ private
+
+ def common_options(opts)
+ opts.separator ''
+ opts.separator 'Common Options:'
+
+ opts.on('-I', '--load-path PATH', 'Specify a Sass import path.') do |path|
+ (@options[:for_engine][:load_paths] ||= []) << path
+ end
+
+ opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
+ require lib
+ end
+
+ opts.on('--compass', 'Make Compass imports available and load project configuration.') do
+ @options[:compass] = true
+ end
+
+ opts.on('-t', '--style NAME', 'Output style. Can be nested (default), compact, ' \
+ 'compressed, or expanded.') do |name|
+ @options[:for_engine][:style] = name.to_sym
+ end
+
+ opts.on("-?", "-h", "--help", "Show this help message.") do
+ puts opts
+ exit
+ end
+
+ opts.on("-v", "--version", "Print the Sass version.") do
+ puts("Sass #{Sass.version[:string]}")
+ exit
+ end
+ end
+
+ def watching_and_updating(opts)
+ opts.separator ''
+ opts.separator 'Watching and Updating:'
+
+ opts.on('--watch', 'Watch files or directories for changes.',
+ 'The location of the generated CSS can be set using a colon:',
+ " #{ default_syntax} --watch input #{ default_syntax}:output.css",
+ " #{ default_syntax} --watch input-dir:output-dir") do
+ @options[:watch] = true
+ end
+
+ # Polling is used by default on Windows.
+ unless Sass::Util.windows?
+ opts.on('--poll', 'Check for file changes manually, rather than relying on the OS.',
+ 'Only meaningful for --watch.') do
+ @options[:poll] = true
+ end
+ end
+
+ opts.on('--update', 'Compile files or directories to CSS.',
+ 'Locations are set like --watch.') do
+ @options[:update] = true
+ end
+
+ opts.on('-f', '--force', 'Recompile every Sass file, even if the CSS file is newer.',
+ 'Only meaningful for --update.') do
+ @options[:force] = true
+ end
+
+ opts.on('--stop-on-error', 'If a file fails to compile, exit immediately.',
+ 'Only meaningful for --watch and --update.') do
+ @options[:stop_on_error] = true
+ end
+ end
+
+ def input_and_output(opts)
+ opts.separator ''
+ opts.separator 'Input and Output:'
+
+ if @default_syntax == :sass
+ opts.on('--scss',
+ 'Use the CSS-superset SCSS syntax.') do
+ @options[:for_engine][:syntax] = :scss
+ end
+ else
+ opts.on('--sass',
+ 'Use the indented Sass syntax.') do
+ @options[:for_engine][:syntax] = :sass
+ end
+ end
+
+ # This is optional for backwards-compatibility with Sass 3.3, which didn't
+ # enable sourcemaps by default and instead used "--sourcemap" to do so.
+ opts.on(:OPTIONAL, '--sourcemap=TYPE',
+ 'How to link generated output to the source files.',
+ ' auto (default): relative paths where possible, file URIs elsewhere',
+ ' file: always absolute file URIs',
+ ' inline: include the source text in the sourcemap',
+ ' none: no sourcemaps') do |type|
+ if type && !%w[auto file inline none].include?(type)
+ $stderr.puts "Unknown sourcemap type #{type}.\n\n"
+ $stderr.puts opts
+ exit
+ elsif type.nil?
+ Sass::Util.sass_warn <<MESSAGE.rstrip
+DEPRECATION WARNING: Passing --sourcemap without a value is deprecated.
+Sourcemaps are now generated by default, so this flag has no effect.
+MESSAGE
+ end
+
+ @options[:sourcemap] = (type || :auto).to_sym
+ end
+
+ opts.on('-s', '--stdin', :NONE,
+ 'Read input from standard input instead of an input file.',
+ 'This is the default if no input file is specified.') do
+ @options[:input] = $stdin
+ end
+
+ encoding_option(opts)
+
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.',
+ ('Always true on Unix.' unless Sass::Util.windows?)) do
+ @options[:unix_newlines] = true if Sass::Util.windows?
+ end
+
+ opts.on('-g', '--debug-info',
+ 'Emit output that can be used by the FireSass Firebug plugin.') do
+ @options[:for_engine][:debug_info] = true
+ end
+
+ opts.on('-l', '--line-numbers', '--line-comments',
+ 'Emit comments in the generated CSS indicating the corresponding source line.') do
+ @options[:for_engine][:line_numbers] = true
+ end
+ end
+
+ def miscellaneous(opts)
+ opts.separator ''
+ opts.separator 'Miscellaneous:'
+
+ opts.on('-i', '--interactive',
+ 'Run an interactive SassScript shell.') do
+ @options[:interactive] = true
+ end
+
+ opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
+ require 'stringio'
+ @options[:check_syntax] = true
+ @options[:output] = StringIO.new
+ end
+
+ opts.on('--precision NUMBER_OF_DIGITS', Integer,
+ "How many digits of precision to use when outputting decimal numbers.",
+ "Defaults to #{Sass::Script::Value::Number.precision}.") do |precision|
+ Sass::Script::Value::Number.precision = precision
+ end
+
+ opts.on('--cache-location PATH',
+ 'The path to save parsed Sass files. Defaults to .sass-cache.') do |loc|
+ @options[:for_engine][:cache_location] = loc
+ end
+
+ opts.on('-C', '--no-cache', "Don't cache parsed Sass files.") do
+ @options[:for_engine][:cache] = false
+ end
+
+ opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error.') do
+ @options[:trace] = true
+ end
+
+ opts.on('-q', '--quiet', 'Silence warnings and status messages during compilation.') do
+ @options[:for_engine][:quiet] = true
+ end
+ end
+
+ def load_compass
+ begin
+ require 'compass'
+ rescue LoadError
+ require 'rubygems'
+ begin
+ require 'compass'
+ rescue LoadError
+ puts "ERROR: Cannot load compass."
+ exit 1
+ end
+ end
+ Compass.add_project_configuration
+ Compass.configuration.project_path ||= Dir.pwd
+ @options[:for_engine][:load_paths] ||= []
+ @options[:for_engine][:load_paths] += Compass.configuration.sass_load_paths
+ end
+
+ def interactive
+ require 'sass/repl'
+ Sass::Repl.new(@options).run
+ end
+
+ # @comment
+ # rubocop:disable MethodLength
+ def watch_or_update
+ require 'sass/plugin'
+ Sass::Plugin.options.merge! @options[:for_engine]
+ Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
+ Sass::Plugin.options[:poll] = @options[:poll]
+ Sass::Plugin.options[:sourcemap] = @options[:sourcemap]
+
+ if @options[:force]
+ raise "The --force flag may only be used with --update." unless @options[:update]
+ Sass::Plugin.options[:always_update] = true
+ end
+
+ raise <<MSG if @args.empty?
+What files should I watch? Did you mean something like:
+ #{ default_syntax} --watch input #{ default_syntax}:output.css
+ #{ default_syntax} --watch input-dir:output-dir
+MSG
+
+ if !colon_path?(@args[0]) && probably_dest_dir?(@args[1])
+ flag = @options[:update] ? "--update" : "--watch"
+ err =
+ if !File.exist?(@args[1])
+ "doesn't exist"
+ elsif @args[1] =~ /\.css$/
+ "is a CSS file"
+ end
+ raise <<MSG if err
+File #{ args[1]} #{err}.
+ Did you mean: #{ default_syntax} #{flag} #{ args[0]}:#{ args[1]}
+MSG
+ end
+
+ # Watch the working directory for changes without adding it to the load
+ # path. This preserves the pre-3.4 behavior when the working directory was
+ # on the load path. We should remove this when we can look for directories
+ # to watch by traversing the import graph.
+ class << Sass::Plugin.compiler
+ # We have to use a class var to make this visible to #watched_file? and
+ # #watched_paths.
+ # rubocop:disable ClassVars
+ @@working_directory = Sass::Util.realpath('.').to_s
+ # rubocop:ensable ClassVars
+
+ def watched_file?(file)
+ super(file) ||
+ (file =~ /\.s[ac]ss$/ && file.start_with?(@@working_directory + File::SEPARATOR))
+ end
+
+ def watched_paths
+ @watched_paths ||= super + [@@working_directory]
+ end
+ end
+
+ dirs, files = @args.map {|name| split_colon_path(name)}.
+ partition {|i, _| File.directory? i}
+ files.map! do |from, to|
+ to ||= from.gsub(/\.[^.]*?$/, '.css')
+ sourcemap = Sass::Util.sourcemap_name(to) if @options[:sourcemap]
+ [from, to, sourcemap]
+ end
+ dirs.map! {|from, to| [from, to || from]}
+ Sass::Plugin.options[:template_location] = dirs
+
+ Sass::Plugin.on_updated_stylesheet do |_, css, sourcemap|
+ [css, sourcemap].each do |file|
+ next unless file
+ puts_action :write, :green, file
+ end
+ end
+
+ had_error = false
+ Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
+ Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
+ Sass::Plugin.on_deleting_sourcemap {|filename| puts_action :delete, :yellow, filename}
+ Sass::Plugin.on_compilation_error do |error, _, _|
+ if error.is_a?(SystemCallError) && ! options[:stop_on_error]
+ had_error = true
+ puts_action :error, :red, error.message
+ STDOUT.flush
+ next
+ end
+
+ raise error unless error.is_a?(Sass::SyntaxError) && ! options[:stop_on_error]
+ had_error = true
+ puts_action :error, :red,
+ "#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
+ STDOUT.flush
+ end
+
+ if @options[:update]
+ Sass::Plugin.update_stylesheets(files)
+ exit 1 if had_error
+ return
+ end
+
+ puts ">>> Sass is watching for changes. Press Ctrl-C to stop."
+
+ Sass::Plugin.on_template_modified do |template|
+ puts ">>> Change detected to: #{template}"
+ STDOUT.flush
+ end
+ Sass::Plugin.on_template_created do |template|
+ puts ">>> New template detected: #{template}"
+ STDOUT.flush
+ end
+ Sass::Plugin.on_template_deleted do |template|
+ puts ">>> Deleted template detected: #{template}"
+ STDOUT.flush
+ end
+
+ Sass::Plugin.watch(files)
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ def run
+ input = @options[:input]
+ output = @options[:output]
+
+ @options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
+ @options[:for_engine][:syntax] ||= @default_syntax
+ engine =
+ if input.is_a?(File) && ! options[:check_syntax]
+ Sass::Engine.for_file(input.path, @options[:for_engine])
+ else
+ # We don't need to do any special handling of @options[:check_syntax] here,
+ # because the Sass syntax checking happens alongside evaluation
+ # and evaluation doesn't actually evaluate any code anyway.
+ Sass::Engine.new(input.read, @options[:for_engine])
+ end
+
+ input.close if input.is_a?(File)
+
+ if @options[:sourcemap] != :none && @options[:sourcemap_filename]
+ relative_sourcemap_path = Sass::Util.relative_path_from(
+ @options[:sourcemap_filename], Sass::Util.pathname(@options[:output_filename]).dirname)
+ rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s)
+ write_output(rendered, output)
+ write_output(mapping.to_json(
+ :type => @options[:sourcemap],
+ :css_path => @options[:output_filename],
+ :sourcemap_path => @options[:sourcemap_filename]) + "\n",
+ @options[:sourcemap_filename])
+ else
+ write_output(engine.render, output)
+ end
+ rescue Sass::SyntaxError => e
+ write_output(Sass::SyntaxError.exception_to_css(e), output) if output.is_a?(String)
+ raise e
+ ensure
+ output.close if output.is_a? File
+ end
+
+ def colon_path?(path)
+ !split_colon_path(path)[1].nil?
+ end
+
+ def split_colon_path(path)
+ one, two = path.split(':', 2)
+ if one && two && Sass::Util.windows? &&
+ one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
+ # If we're on Windows and we were passed a drive letter path,
+ # don't split on that colon.
+ one2, two = two.split(':', 2)
+ one = one + ':' + one2
+ end
+ return one, two
+ end
+
+ # Whether path is likely to be meant as the destination
+ # in a source:dest pair.
+ def probably_dest_dir?(path)
+ return false unless path
+ return false if colon_path?(path)
+ Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
+ end
+
+ def default_sass_path
+ return unless ENV['SASS_PATH']
+ # The select here prevents errors when the environment's
+ # load paths specified do not exist.
+ ENV['SASS_PATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)}
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/features.rb
b/backends/css/gems/sass-3.4.9/lib/sass/features.rb
new file mode 100644
index 0000000..f93031e
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/features.rb
@@ -0,0 +1,47 @@
+require 'set'
+module Sass
+ # Provides `Sass.has_feature?` which allows for simple feature detection
+ # by providing a feature name.
+ module Features
+ # This is the set of features that can be detected.
+ #
+ # When this is updated, the documentation of `feature-exists()` should be
+ # updated as well.
+ KNOWN_FEATURES = Set[*%w{
+ global-variable-shadowing
+ extend-selector-pseudoclass
+ units-level-3
+ at-error
+ }]
+
+ # Check if a feature exists by name. This is used to implement
+ # the Sass function `feature-exists($feature)`
+ #
+ # @param feature_name [String] The case sensitive name of the feature to
+ # check if it exists in this version of Sass.
+ # @return [Boolean] whether the feature of that name exists.
+ def has_feature?(feature_name)
+ KNOWN_FEATURES.include?(feature_name)
+ end
+
+ # Add a feature to Sass. Plugins can use this to easily expose their
+ # availability to end users. Plugins must prefix their feature
+ # names with a dash to distinguish them from official features.
+ #
+ # @example
+ # Sass.add_feature("-import-globbing")
+ # Sass.add_feature("-math-cos")
+ #
+ #
+ # @param feature_name [String] The case sensitive name of the feature to
+ # to add to Sass. Must begin with a dash.
+ def add_feature(feature_name)
+ unless feature_name[0] == ?-
+ raise ArgumentError.new("Plugin feature names must begin with a dash")
+ end
+ KNOWN_FEATURES << feature_name
+ end
+ end
+
+ extend Features
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/importers.rb
b/backends/css/gems/sass-3.4.9/lib/sass/importers.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/importers.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/importers.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/importers/base.rb
b/backends/css/gems/sass-3.4.9/lib/sass/importers/base.rb
new file mode 100644
index 0000000..62fb814
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/importers/base.rb
@@ -0,0 +1,182 @@
+module Sass
+ module Importers
+ # The abstract base class for Sass importers.
+ # All importers should inherit from this.
+ #
+ # At the most basic level, an importer is given a string
+ # and must return a {Sass::Engine} containing some Sass code.
+ # This string can be interpreted however the importer wants;
+ # however, subclasses are encouraged to use the URI format
+ # for pathnames.
+ #
+ # Importers that have some notion of "relative imports"
+ # should take a single load path in their constructor,
+ # and interpret paths as relative to that.
+ # They should also implement the \{#find\_relative} method.
+ #
+ # Importers should be serializable via `Marshal.dump`.
+ #
+ # @abstract
+ class Base
+ # Find a Sass file relative to another file.
+ # Importers without a notion of "relative paths"
+ # should just return nil here.
+ #
+ # If the importer does have a notion of "relative paths",
+ # it should ignore its load path during this method.
+ #
+ # See \{#find} for important information on how this method should behave.
+ #
+ # The `:filename` option passed to the returned {Sass::Engine}
+ # should be of a format that could be passed to \{#find}.
+ #
+ # @param uri [String] The URI to import. This is not necessarily relative,
+ # but this method should only return true if it is.
+ # @param base [String] The base filename. If `uri` is relative,
+ # it should be interpreted as relative to `base`.
+ # `base` is guaranteed to be in a format importable by this importer.
+ # @param options [{Symbol => Object}] Options for the Sass file
+ # containing the ` import` that's currently being resolved.
+ # @return [Sass::Engine, nil] An Engine containing the imported file,
+ # or nil if it couldn't be found or was in the wrong format.
+ def find_relative(uri, base, options)
+ Sass::Util.abstract(self)
+ end
+
+ # Find a Sass file, if it exists.
+ #
+ # This is the primary entry point of the Importer.
+ # It corresponds directly to an ` import` statement in Sass.
+ # It should do three basic things:
+ #
+ # * Determine if the URI is in this importer's format.
+ # If not, return nil.
+ # * Determine if the file indicated by the URI actually exists and is readable.
+ # If not, return nil.
+ # * Read the file and place the contents in a {Sass::Engine}.
+ # Return that engine.
+ #
+ # If this importer's format allows for file extensions,
+ # it should treat them the same way as the default {Filesystem} importer.
+ # If the URI explicitly has a `.sass` or `.scss` filename,
+ # the importer should look for that exact file
+ # and import it as the syntax indicated.
+ # If it doesn't exist, the importer should return nil.
+ #
+ # If the URI doesn't have either of these extensions,
+ # the importer should look for files with the extensions.
+ # If no such files exist, it should return nil.
+ #
+ # The {Sass::Engine} to be returned should be passed `options`,
+ # with a few modifications. `:syntax` should be set appropriately,
+ # `:filename` should be set to `uri`,
+ # and `:importer` should be set to this importer.
+ #
+ # @param uri [String] The URI to import.
+ # @param options [{Symbol => Object}] Options for the Sass file
+ # containing the ` import` that's currently being resolved.
+ # This is safe for subclasses to modify destructively.
+ # Callers should only pass in a value they don't mind being destructively modified.
+ # @return [Sass::Engine, nil] An Engine containing the imported file,
+ # or nil if it couldn't be found or was in the wrong format.
+ def find(uri, options)
+ Sass::Util.abstract(self)
+ end
+
+ # Returns the time the given Sass file was last modified.
+ #
+ # If the given file has been deleted or the time can't be accessed
+ # for some other reason, this should return nil.
+ #
+ # @param uri [String] The URI of the file to check.
+ # Comes from a `:filename` option set on an engine returned by this importer.
+ # @param options [{Symbol => Objet}] Options for the Sass file
+ # containing the ` import` currently being checked.
+ # @return [Time, nil]
+ def mtime(uri, options)
+ Sass::Util.abstract(self)
+ end
+
+ # Get the cache key pair for the given Sass URI.
+ # The URI need not be checked for validity.
+ #
+ # The only strict requirement is that the returned pair of strings
+ # uniquely identify the file at the given URI.
+ # However, the first component generally corresponds roughly to the directory,
+ # and the second to the basename, of the URI.
+ #
+ # Note that keys must be unique *across importers*.
+ # Thus it's probably a good idea to include the importer name
+ # at the beginning of the first component.
+ #
+ # @param uri [String] A URI known to be valid for this importer.
+ # @param options [{Symbol => Object}] Options for the Sass file
+ # containing the ` import` currently being checked.
+ # @return [(String, String)] The key pair which uniquely identifies
+ # the file at the given URI.
+ def key(uri, options)
+ Sass::Util.abstract(self)
+ end
+
+ # Get the publicly-visible URL for an imported file. This URL is used by
+ # source maps to link to the source stylesheet. This may return `nil` to
+ # indicate that no public URL is available; however, this will cause
+ # sourcemap generation to fail if any CSS is generated from files imported
+ # from this importer.
+ #
+ # If an absolute "file:" URI can be produced for an imported file, that
+ # should be preferred to returning `nil`. However, a URL relative to
+ # `sourcemap_directory` should be preferred over an absolute "file:" URI.
+ #
+ # @param uri [String] A URI known to be valid for this importer.
+ # @param sourcemap_directory [String, NilClass] The absolute path to a
+ # directory on disk where the sourcemap will be saved. If uri refers to
+ # a file on disk that's accessible relative to sourcemap_directory, this
+ # may return a relative URL. This may be `nil` if the sourcemap's
+ # eventual location is unknown.
+ # @return [String?] The publicly-visible URL for this file, or `nil`
+ # indicating that no publicly-visible URL exists. This should be
+ # appropriately URL-escaped.
+ def public_url(uri, sourcemap_directory)
+ return if @public_url_warning_issued
+ @public_url_warning_issued = true
+ Sass::Util.sass_warn <<WARNING
+WARNING: #{self.class.name} should define the #public_url method.
+WARNING
+ nil
+ end
+
+ # A string representation of the importer.
+ # Should be overridden by subclasses.
+ #
+ # This is used to help debugging,
+ # and should usually just show the load path encapsulated by this importer.
+ #
+ # @return [String]
+ def to_s
+ Sass::Util.abstract(self)
+ end
+
+ # If the importer is based on files on the local filesystem
+ # this method should return folders which should be watched
+ # for changes.
+ #
+ # @return [Array<String>] List of absolute paths of directories to watch
+ def directories_to_watch
+ []
+ end
+
+ # If this importer is based on files on the local filesystem This method
+ # should return true if the file, when changed, should trigger a
+ # recompile.
+ #
+ # It is acceptable for non-sass files to be watched and trigger a recompile.
+ #
+ # @param filename [String] The absolute filename for a file that has changed.
+ # @return [Boolean] When the file changed should cause a recompile.
+ def watched_file?(filename)
+ false
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/importers/filesystem.rb
b/backends/css/gems/sass-3.4.9/lib/sass/importers/filesystem.rb
new file mode 100644
index 0000000..aeff696
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/importers/filesystem.rb
@@ -0,0 +1,217 @@
+require 'set'
+
+module Sass
+ module Importers
+ # The default importer, used for any strings found in the load path.
+ # Simply loads Sass files from the filesystem using the default logic.
+ class Filesystem < Base
+ attr_accessor :root
+
+ # Creates a new filesystem importer that imports files relative to a given path.
+ #
+ # @param root [String] The root path.
+ # This importer will import files relative to this path.
+ def initialize(root)
+ @root = File.expand_path(root)
+ @real_root = Sass::Util.realpath(@root).to_s
+ @same_name_warnings = Set.new
+ end
+
+ # @see Base#find_relative
+ def find_relative(name, base, options)
+ _find(File.dirname(base), name, options)
+ end
+
+ # @see Base#find
+ def find(name, options)
+ _find(@root, name, options)
+ end
+
+ # @see Base#mtime
+ def mtime(name, options)
+ file, _ = Sass::Util.destructure(find_real_file(@root, name, options))
+ File.mtime(file) if file
+ rescue Errno::ENOENT
+ nil
+ end
+
+ # @see Base#key
+ def key(name, options)
+ [self.class.name + ":" + File.dirname(File.expand_path(name)),
+ File.basename(name)]
+ end
+
+ # @see Base#to_s
+ def to_s
+ @root
+ end
+
+ def hash
+ @root.hash
+ end
+
+ def eql?(other)
+ !other.nil? && other.respond_to?(:root) && root.eql?(other.root)
+ end
+
+ # @see Base#directories_to_watch
+ def directories_to_watch
+ [root]
+ end
+
+ # @see Base#watched_file?
+ def watched_file?(filename)
+ # Check against the root with symlinks resolved, since Listen
+ # returns fully-resolved paths.
+ filename =~ /\.s[ac]ss$/ && filename.start_with?(@real_root + File::SEPARATOR)
+ end
+
+ def public_url(name, sourcemap_directory)
+ file_pathname = Sass::Util.cleanpath(Sass::Util.absolute_path(name, @root))
+ return Sass::Util.file_uri_from_path(file_pathname) if sourcemap_directory.nil?
+
+ sourcemap_pathname = Sass::Util.cleanpath(sourcemap_directory)
+ begin
+ Sass::Util.file_uri_from_path(
+ Sass::Util.relative_path_from(file_pathname, sourcemap_pathname))
+ rescue ArgumentError # when a relative path cannot be constructed
+ Sass::Util.file_uri_from_path(file_pathname)
+ end
+ end
+
+ protected
+
+ # If a full uri is passed, this removes the root from it
+ # otherwise returns the name unchanged
+ def remove_root(name)
+ if name.index(@root + "/") == 0
+ name[(@root.length + 1)..-1]
+ else
+ name
+ end
+ end
+
+ # A hash from file extensions to the syntaxes for those extensions.
+ # The syntaxes must be `:sass` or `:scss`.
+ #
+ # This can be overridden by subclasses that want normal filesystem importing
+ # with unusual extensions.
+ #
+ # @return [{String => Symbol}]
+ def extensions
+ {'sass' => :sass, 'scss' => :scss}
+ end
+
+ # Given an ` import`ed path, returns an array of possible
+ # on-disk filenames and their corresponding syntaxes for that path.
+ #
+ # @param name [String] The filename.
+ # @return [Array(String, Symbol)] An array of pairs.
+ # The first element of each pair is a filename to look for;
+ # the second element is the syntax that file would be in (`:sass` or `:scss`).
+ def possible_files(name)
+ name = escape_glob_characters(name)
+ dirname, basename, extname = split(name)
+ sorted_exts = extensions.sort
+ syntax = extensions[extname]
+
+ if syntax
+ ret = [["#{dirname}/{_,}#{basename}.#{extensions.invert[syntax]}", syntax]]
+ else
+ ret = sorted_exts.map {|ext, syn| ["#{dirname}/{_,}#{basename}.#{ext}", syn]}
+ end
+
+ # JRuby chokes when trying to import files from JARs when the path starts with './'.
+ ret.map {|f, s| [f.sub(/^\.\//, ''), s]}
+ end
+
+ def escape_glob_characters(name)
+ name.gsub(/[\*\[\]\{\}\?]/) do |char|
+ "\\#{char}"
+ end
+ end
+
+ REDUNDANT_DIRECTORY = /#{Regexp.escape(File::SEPARATOR)}\.#{Regexp.escape(File::SEPARATOR)}/
+ # Given a base directory and an ` import`ed name,
+ # finds an existant file that matches the name.
+ #
+ # @param dir [String] The directory relative to which to search.
+ # @param name [String] The filename to search for.
+ # @return [(String, Symbol)] A filename-syntax pair.
+ def find_real_file(dir, name, options)
+ # On windows 'dir' or 'name' can be in native File::ALT_SEPARATOR form.
+ dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
+ name = name.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
+
+ found = possible_files(remove_root(name)).map do |f, s|
+ path = (dir == "." || Sass::Util.pathname(f).absolute?) ? f :
+ "#{escape_glob_characters(dir)}/#{f}"
+ Dir[path].map do |full_path|
+ full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR)
+ [Sass::Util.cleanpath(full_path).to_s, s]
+ end
+ end
+ found = Sass::Util.flatten(found, 1)
+ return if found.empty?
+
+ if found.size > 1 && ! same_name_warnings include?(found.first.first)
+ found.each {|(f, _)| @same_name_warnings << f}
+ relative_to = Sass::Util.pathname(dir)
+ if options[:_from_import_node]
+ # If _line exists, we're here due to an actual import in an
+ # import_node and we want to print a warning for a user writing an
+ # ambiguous import.
+ candidates = found.map do |(f, _)|
+ " " + Sass::Util.pathname(f).relative_path_from(relative_to).to_s
+ end.join("\n")
+ raise Sass::SyntaxError.new(<<MESSAGE)
+It's not clear which file to import for '@import "#{name}"'.
+Candidates:
+#{candidates}
+Please delete or rename all but one of these files.
+MESSAGE
+ else
+ # Otherwise, we're here via StalenessChecker, and we want to print a
+ # warning for a user running `sass --watch` with two ambiguous files.
+ candidates = found.map {|(f, _)| " " + File.basename(f)}.join("\n")
+ Sass::Util.sass_warn <<WARNING
+WARNING: In #{File.dirname(name)}:
+ There are multiple files that match the name "#{File.basename(name)}":
+#{candidates}
+WARNING
+ end
+ end
+ found.first
+ end
+
+ # Splits a filename into three parts, a directory part, a basename, and an extension
+ # Only the known extensions returned from the extensions method will be recognized as such.
+ def split(name)
+ extension = nil
+ dirname, basename = File.dirname(name), File.basename(name)
+ if basename =~ /^(.*)\.(#{extensions.keys.map {|e| Regexp.escape(e)}.join('|')})$/
+ basename = $1
+ extension = $2
+ end
+ [dirname, basename, extension]
+ end
+
+ private
+
+ def _find(dir, name, options)
+ full_filename, syntax = Sass::Util.destructure(find_real_file(dir, name, options))
+ return unless full_filename && File.readable?(full_filename)
+
+ # TODO: this preserves historical behavior, but it's possible
+ # :filename should be either normalized to the native format
+ # or consistently URI-format.
+ full_filename = full_filename.tr("\\", "/") if Sass::Util.windows?
+
+ options[:syntax] = syntax
+ options[:filename] = full_filename
+ options[:importer] = self
+ Sass::Engine.new(File.read(full_filename), options)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/logger.rb b/backends/css/gems/sass-3.4.9/lib/sass/logger.rb
new file mode 100644
index 0000000..f3f2045
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/logger.rb
@@ -0,0 +1,12 @@
+module Sass::Logger; end
+
+require "sass/logger/log_level"
+require "sass/logger/base"
+
+module Sass
+ class << self
+ attr_accessor :logger
+ end
+
+ self.logger = Sass::Logger::Base.new
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/logger/base.rb
b/backends/css/gems/sass-3.4.9/lib/sass/logger/base.rb
new file mode 100644
index 0000000..a9d3631
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/logger/base.rb
@@ -0,0 +1,30 @@
+require 'sass/logger/log_level'
+
+class Sass::Logger::Base
+ include Sass::Logger::LogLevel
+
+ attr_accessor :log_level
+ attr_accessor :disabled
+
+ log_level :trace
+ log_level :debug
+ log_level :info
+ log_level :warn
+ log_level :error
+
+ def initialize(log_level = :debug)
+ self.log_level = log_level
+ end
+
+ def logging_level?(level)
+ !disabled && self.class.log_level?(level, log_level)
+ end
+
+ def log(level, message)
+ _log(level, message) if logging_level?(level)
+ end
+
+ def _log(level, message)
+ Kernel.warn(message)
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/logger/log_level.rb
b/backends/css/gems/sass-3.4.9/lib/sass/logger/log_level.rb
new file mode 100644
index 0000000..39e7cec
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/logger/log_level.rb
@@ -0,0 +1,45 @@
+module Sass
+ module Logger
+ module LogLevel
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def inherited(subclass)
+ subclass.log_levels = subclass.superclass.log_levels.dup
+ end
+
+ attr_writer :log_levels
+
+ def log_levels
+ @log_levels ||= {}
+ end
+
+ def log_level?(level, min_level)
+ log_levels[level] >= log_levels[min_level]
+ end
+
+ def log_level(name, options = {})
+ if options[:prepend]
+ level = log_levels.values.min
+ level = level.nil? ? 0 : level - 1
+ else
+ level = log_levels.values.max
+ level = level.nil? ? 0 : level + 1
+ end
+ log_levels.update(name => level)
+ define_logger(name)
+ end
+
+ def define_logger(name, options = {})
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{name}(message)
+ #{options.fetch(:to, :log)}(#{name.inspect}, message)
+ end
+ RUBY
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/media.rb b/backends/css/gems/sass-3.4.9/lib/sass/media.rb
new file mode 100644
index 0000000..dc4542d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/media.rb
@@ -0,0 +1,210 @@
+# A namespace for the ` media` query parse tree.
+module Sass::Media
+ # A comma-separated list of queries.
+ #
+ # media_query [ ',' S* media_query ]*
+ class QueryList
+ # The queries contained in this list.
+ #
+ # @return [Array<Query>]
+ attr_accessor :queries
+
+ # @param queries [Array<Query>] See \{#queries}
+ def initialize(queries)
+ @queries = queries
+ end
+
+ # Merges this query list with another. The returned query list
+ # queries for the intersection between the two inputs.
+ #
+ # Both query lists should be resolved.
+ #
+ # @param other [QueryList]
+ # @return [QueryList?] The merged list, or nil if there is no intersection.
+ def merge(other)
+ new_queries = queries.map {|q1| other.queries.map {|q2| q1.merge(q2)}}.flatten.compact
+ return if new_queries.empty?
+ QueryList.new(new_queries)
+ end
+
+ # Returns the CSS for the media query list.
+ #
+ # @return [String]
+ def to_css
+ queries.map {|q| q.to_css}.join(', ')
+ end
+
+ # Returns the Sass/SCSS code for the media query list.
+ #
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
+ # @return [String]
+ def to_src(options)
+ queries.map {|q| q.to_src(options)}.join(', ')
+ end
+
+ # Returns a representation of the query as an array of strings and
+ # potentially {Sass::Script::Tree::Node}s (if there's interpolation in it).
+ # When the interpolation is resolved and the strings are joined together,
+ # this will be the string representation of this query.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ def to_a
+ Sass::Util.intersperse(queries.map {|q| q.to_a}, ', ').flatten
+ end
+
+ # Returns a deep copy of this query list and all its children.
+ #
+ # @return [QueryList]
+ def deep_copy
+ QueryList.new(queries.map {|q| q.deep_copy})
+ end
+ end
+
+ # A single media query.
+ #
+ # [ [ONLY | NOT]? S* media_type S* | expression ] [ AND S* expression ]*
+ class Query
+ # The modifier for the query.
+ #
+ # When parsed as Sass code, this contains strings and SassScript nodes. When
+ # parsed as CSS, it contains a single string (accessible via
+ # \{#resolved_modifier}).
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :modifier
+
+ # The type of the query (e.g. `"screen"` or `"print"`).
+ #
+ # When parsed as Sass code, this contains strings and SassScript nodes. When
+ # parsed as CSS, it contains a single string (accessible via
+ # \{#resolved_type}).
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :type
+
+ # The trailing expressions in the query.
+ #
+ # When parsed as Sass code, each expression contains strings and SassScript
+ # nodes. When parsed as CSS, each one contains a single string.
+ #
+ # @return [Array<Array<String, Sass::Script::Tree::Node>>]
+ attr_accessor :expressions
+
+ # @param modifier [Array<String, Sass::Script::Tree::Node>] See \{#modifier}
+ # @param type [Array<String, Sass::Script::Tree::Node>] See \{#type}
+ # @param expressions [Array<Array<String, Sass::Script::Tree::Node>>] See \{#expressions}
+ def initialize(modifier, type, expressions)
+ @modifier = modifier
+ @type = type
+ @expressions = expressions
+ end
+
+ # See \{#modifier}.
+ # @return [String]
+ def resolved_modifier
+ # modifier should contain only a single string
+ modifier.first || ''
+ end
+
+ # See \{#type}.
+ # @return [String]
+ def resolved_type
+ # type should contain only a single string
+ type.first || ''
+ end
+
+ # Merges this query with another. The returned query queries for
+ # the intersection between the two inputs.
+ #
+ # Both queries should be resolved.
+ #
+ # @param other [Query]
+ # @return [Query?] The merged query, or nil if there is no intersection.
+ def merge(other)
+ m1, t1 = resolved_modifier.downcase, resolved_type.downcase
+ m2, t2 = other.resolved_modifier.downcase, other.resolved_type.downcase
+ t1 = t2 if t1.empty?
+ t2 = t1 if t2.empty?
+ if (m1 == 'not') ^ (m2 == 'not')
+ return if t1 == t2
+ type = m1 == 'not' ? t2 : t1
+ mod = m1 == 'not' ? m2 : m1
+ elsif m1 == 'not' && m2 == 'not'
+ # CSS has no way of representing "neither screen nor print"
+ return unless t1 == t2
+ type = t1
+ mod = 'not'
+ elsif t1 != t2
+ return
+ else # t1 == t2, neither m1 nor m2 are "not"
+ type = t1
+ mod = m1.empty? ? m2 : m1
+ end
+ Query.new([mod], [type], other.expressions + expressions)
+ end
+
+ # Returns the CSS for the media query.
+ #
+ # @return [String]
+ def to_css
+ css = ''
+ css << resolved_modifier
+ css << ' ' unless resolved_modifier.empty?
+ css << resolved_type
+ css << ' and ' unless resolved_type.empty? || expressions.empty?
+ css << expressions.map do |e|
+ # It's possible for there to be script nodes in Expressions even when
+ # we're converting to CSS in the case where we parsed the document as
+ # CSS originally (as in css_test.rb).
+ e.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.to_sass : c.to_s}.join
+ end.join(' and ')
+ css
+ end
+
+ # Returns the Sass/SCSS code for the media query.
+ #
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
+ # @return [String]
+ def to_src(options)
+ src = ''
+ src << Sass::Media._interp_to_src(modifier, options)
+ src << ' ' unless modifier.empty?
+ src << Sass::Media._interp_to_src(type, options)
+ src << ' and ' unless type.empty? || expressions.empty?
+ src << expressions.map do |e|
+ Sass::Media._interp_to_src(e, options)
+ end.join(' and ')
+ src
+ end
+
+ # @see \{MediaQuery#to\_a}
+ def to_a
+ res = []
+ res += modifier
+ res << ' ' unless modifier.empty?
+ res += type
+ res << ' and ' unless type.empty? || expressions.empty?
+ res += Sass::Util.intersperse(expressions, ' and ').flatten
+ res
+ end
+
+ # Returns a deep copy of this query and all its children.
+ #
+ # @return [Query]
+ def deep_copy
+ Query.new(
+ modifier.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c},
+ type.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c},
+ expressions.map {|e| e.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}})
+ end
+ end
+
+ # Converts an interpolation array to source.
+ #
+ # @param interp [Array<String, Sass::Script::Tree::Node>] The interpolation array to convert.
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
+ # @return [String]
+ def self._interp_to_src(interp, options)
+ interp.map {|r| r.is_a?(String) ? r : r.to_sass(options)}.join
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/plugin.rb b/backends/css/gems/sass-3.4.9/lib/sass/plugin.rb
new file mode 100644
index 0000000..08bd9b1
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/plugin.rb
@@ -0,0 +1,133 @@
+require 'fileutils'
+
+require 'sass'
+require 'sass/plugin/compiler'
+
+module Sass
+ # This module provides a single interface to the compilation of Sass/SCSS files
+ # for an application. It provides global options and checks whether CSS files
+ # need to be updated.
+ #
+ # This module is used as the primary interface with Sass
+ # when it's used as a plugin for various frameworks.
+ # All Rack-enabled frameworks are supported out of the box.
+ # The plugin is
+ # {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}.
+ # Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}.
+ #
+ # This module has a large set of callbacks available
+ # to allow users to run code (such as logging) when certain things happen.
+ # All callback methods are of the form `on_#{name}`,
+ # and they all take a block that's called when the given action occurs.
+ #
+ # Note that this class proxies almost all methods to its {Sass::Plugin::Compiler} instance.
+ # See \{#compiler}.
+ #
+ # @example Using a callback
+ # Sass::Plugin.on_updating_stylesheet do |template, css|
+ # puts "Compiling #{template} to #{css}"
+ # end
+ # Sass::Plugin.update_stylesheets
+ # #=> Compiling app/sass/screen.scss to public/stylesheets/screen.css
+ # #=> Compiling app/sass/print.scss to public/stylesheets/print.css
+ # #=> Compiling app/sass/ie.scss to public/stylesheets/ie.css
+ # @see Sass::Plugin::Compiler
+ module Plugin
+ extend self
+
+ @checked_for_updates = false
+
+ # Whether or not Sass has **ever** checked if the stylesheets need to be updated
+ # (in this Ruby instance).
+ #
+ # @return [Boolean]
+ attr_accessor :checked_for_updates
+
+ # Same as \{#update\_stylesheets}, but respects \{#checked\_for\_updates}
+ # and the {file:SASS_REFERENCE.md#always_update-option `:always_update`}
+ # and {file:SASS_REFERENCE.md#always_check-option `:always_check`} options.
+ #
+ # @see #update_stylesheets
+ def check_for_updates
+ return unless !Sass::Plugin.checked_for_updates ||
+ Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
+ update_stylesheets
+ end
+
+ # Returns the singleton compiler instance.
+ # This compiler has been pre-configured according
+ # to the plugin configuration.
+ #
+ # @return [Sass::Plugin::Compiler]
+ def compiler
+ @compiler ||= Compiler.new
+ end
+
+ # Updates out-of-date stylesheets.
+ #
+ # Checks each Sass/SCSS file in
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`}
+ # to see if it's been modified more recently than the corresponding CSS file
+ # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
+ # If it has, it updates the CSS file.
+ #
+ # @param individual_files [Array<(String, String)>]
+ # A list of files to check for updates
+ # **in addition to those specified by the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
+ # The first string in each pair is the location of the Sass/SCSS file,
+ # the second is the location of the CSS file that it should be compiled to.
+ def update_stylesheets(individual_files = [])
+ return if options[:never_update]
+ compiler.update_stylesheets(individual_files)
+ end
+
+ # Updates all stylesheets, even those that aren't out-of-date.
+ # Ignores the cache.
+ #
+ # @param individual_files [Array<(String, String)>]
+ # A list of files to check for updates
+ # **in addition to those specified by the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
+ # The first string in each pair is the location of the Sass/SCSS file,
+ # the second is the location of the CSS file that it should be compiled to.
+ # @see #update_stylesheets
+ def force_update_stylesheets(individual_files = [])
+ Compiler.new(options.dup.merge(
+ :never_update => false,
+ :always_update => true,
+ :cache => false)).update_stylesheets(individual_files)
+ end
+
+ # All other method invocations are proxied to the \{#compiler}.
+ #
+ # @see #compiler
+ # @see Sass::Plugin::Compiler
+ def method_missing(method, *args, &block)
+ if compiler.respond_to?(method)
+ compiler.send(method, *args, &block)
+ else
+ super
+ end
+ end
+
+ # For parity with method_missing
+ def respond_to?(method)
+ super || compiler.respond_to?(method)
+ end
+
+ # There's a small speedup by not using method missing for frequently delegated methods.
+ def options
+ compiler.options
+ end
+ end
+end
+
+if defined?(ActionController)
+ # On Rails 3+ the rails plugin is loaded at the right time in railtie.rb
+ require 'sass/plugin/rails' unless Sass::Util.ap_geq_3?
+elsif defined?(Merb::Plugins)
+ require 'sass/plugin/merb'
+else
+ require 'sass/plugin/generic'
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/plugin/compiler.rb
b/backends/css/gems/sass-3.4.9/lib/sass/plugin/compiler.rb
new file mode 100644
index 0000000..d4c339b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/plugin/compiler.rb
@@ -0,0 +1,571 @@
+require 'fileutils'
+
+require 'sass'
+# XXX CE: is this still necessary now that we have the compiler class?
+require 'sass/callbacks'
+require 'sass/plugin/configuration'
+require 'sass/plugin/staleness_checker'
+
+module Sass::Plugin
+ # The Compiler class handles compilation of multiple files and/or directories,
+ # including checking which CSS files are out-of-date and need to be updated
+ # and calling Sass to perform the compilation on those files.
+ #
+ # {Sass::Plugin} uses this class to update stylesheets for a single application.
+ # Unlike {Sass::Plugin}, though, the Compiler class has no global state,
+ # and so multiple instances may be created and used independently.
+ #
+ # If you need to compile a Sass string into CSS,
+ # please see the {Sass::Engine} class.
+ #
+ # Unlike {Sass::Plugin}, this class doesn't keep track of
+ # whether or how many times a stylesheet should be updated.
+ # Therefore, the following `Sass::Plugin` options are ignored by the Compiler:
+ #
+ # * `:never_update`
+ # * `:always_check`
+ class Compiler
+ include Configuration
+ extend Sass::Callbacks
+
+ # Creates a new compiler.
+ #
+ # @param opts [{Symbol => Object}]
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ def initialize(opts = {})
+ @watched_files = Set.new
+ options.merge!(opts)
+ end
+
+ # Register a callback to be run before stylesheets are mass-updated.
+ # This is run whenever \{#update\_stylesheets} is called,
+ # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
+ # is enabled.
+ #
+ # @yield [files]
+ # @yieldparam files [<(String, String, String)>]
+ # Individual files to be updated. Files in directories specified are included in this list.
+ # The first element of each pair is the source file,
+ # the second is the target CSS file,
+ # the third is the target sourcemap file.
+ define_callback :updating_stylesheets
+
+ # Register a callback to be run after stylesheets are mass-updated.
+ # This is run whenever \{#update\_stylesheets} is called,
+ # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
+ # is enabled.
+ #
+ # @yield [updated_files]
+ # @yieldparam updated_files [<(String, String)>]
+ # Individual files that were updated.
+ # The first element of each pair is the source file, the second is the target CSS file.
+ define_callback :updated_stylesheets
+
+ # Register a callback to be run after a single stylesheet is updated.
+ # The callback is only run if the stylesheet is really updated;
+ # if the CSS file is fresh, this won't be run.
+ #
+ # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
+ # is enabled, this callback won't be run
+ # when an exception CSS file is being written.
+ # To run an action for those files, use \{#on\_compilation\_error}.
+ #
+ # @yield [template, css, sourcemap]
+ # @yieldparam template [String]
+ # The location of the Sass/SCSS file being updated.
+ # @yieldparam css [String]
+ # The location of the CSS file being generated.
+ # @yieldparam sourcemap [String]
+ # The location of the sourcemap being generated, if any.
+ define_callback :updated_stylesheet
+
+ # Register a callback to be run when compilation starts.
+ #
+ # In combination with on_updated_stylesheet, this could be used
+ # to collect compilation statistics like timing or to take a
+ # diff of the changes to the output file.
+ #
+ # @yield [template, css, sourcemap]
+ # @yieldparam template [String]
+ # The location of the Sass/SCSS file being updated.
+ # @yieldparam css [String]
+ # The location of the CSS file being generated.
+ # @yieldparam sourcemap [String]
+ # The location of the sourcemap being generated, if any.
+ define_callback :compilation_starting
+
+ # Register a callback to be run when Sass decides not to update a stylesheet.
+ # In particular, the callback is run when Sass finds that
+ # the template file and none of its dependencies
+ # have been modified since the last compilation.
+ #
+ # Note that this is **not** run when the
+ # \{file:SASS_REFERENCE.md#never-update_option `:never_update` option} is set,
+ # nor when Sass decides not to compile a partial.
+ #
+ # @yield [template, css]
+ # @yieldparam template [String]
+ # The location of the Sass/SCSS file not being updated.
+ # @yieldparam css [String]
+ # The location of the CSS file not being generated.
+ define_callback :not_updating_stylesheet
+
+ # Register a callback to be run when there's an error
+ # compiling a Sass file.
+ # This could include not only errors in the Sass document,
+ # but also errors accessing the file at all.
+ #
+ # @yield [error, template, css]
+ # @yieldparam error [Exception] The exception that was raised.
+ # @yieldparam template [String]
+ # The location of the Sass/SCSS file being updated.
+ # @yieldparam css [String]
+ # The location of the CSS file being generated.
+ define_callback :compilation_error
+
+ # Register a callback to be run when Sass creates a directory
+ # into which to put CSS files.
+ #
+ # Note that even if multiple levels of directories need to be created,
+ # the callback may only be run once.
+ # For example, if "foo/" exists and "foo/bar/baz/" needs to be created,
+ # this may only be run for "foo/bar/baz/".
+ # This is not a guarantee, however;
+ # it may also be run for "foo/bar/".
+ #
+ # @yield [dirname]
+ # @yieldparam dirname [String]
+ # The location of the directory that was created.
+ define_callback :creating_directory
+
+ # Register a callback to be run when Sass detects
+ # that a template has been modified.
+ # This is only run when using \{#watch}.
+ #
+ # @yield [template]
+ # @yieldparam template [String]
+ # The location of the template that was modified.
+ define_callback :template_modified
+
+ # Register a callback to be run when Sass detects
+ # that a new template has been created.
+ # This is only run when using \{#watch}.
+ #
+ # @yield [template]
+ # @yieldparam template [String]
+ # The location of the template that was created.
+ define_callback :template_created
+
+ # Register a callback to be run when Sass detects
+ # that a template has been deleted.
+ # This is only run when using \{#watch}.
+ #
+ # @yield [template]
+ # @yieldparam template [String]
+ # The location of the template that was deleted.
+ define_callback :template_deleted
+
+ # Register a callback to be run when Sass deletes a CSS file.
+ # This happens when the corresponding Sass/SCSS file has been deleted
+ # and when the compiler cleans the output files.
+ #
+ # @yield [filename]
+ # @yieldparam filename [String]
+ # The location of the CSS file that was deleted.
+ define_callback :deleting_css
+
+ # Register a callback to be run when Sass deletes a sourcemap file.
+ # This happens when the corresponding Sass/SCSS file has been deleted
+ # and when the compiler cleans the output files.
+ #
+ # @yield [filename]
+ # @yieldparam filename [String]
+ # The location of the sourcemap file that was deleted.
+ define_callback :deleting_sourcemap
+
+ # Updates out-of-date stylesheets.
+ #
+ # Checks each Sass/SCSS file in
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`}
+ # to see if it's been modified more recently than the corresponding CSS file
+ # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
+ # If it has, it updates the CSS file.
+ #
+ # @param individual_files [Array<(String, String[, String])>]
+ # A list of files to check for updates
+ # **in addition to those specified by the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
+ # The first string in each pair is the location of the Sass/SCSS file,
+ # the second is the location of the CSS file that it should be compiled to.
+ # The third string, if provided, is the location of the Sourcemap file.
+ def update_stylesheets(individual_files = [])
+ Sass::Plugin.checked_for_updates = true
+ staleness_checker = StalenessChecker.new(engine_options)
+
+ files = file_list(individual_files)
+ run_updating_stylesheets(files)
+
+ updated_stylesheets = []
+ files.each do |file, css, sourcemap|
+ # TODO: Does staleness_checker need to check the sourcemap file as well?
+ if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
+ # XXX For consistency, this should return the sourcemap too, but it would
+ # XXX be an API change.
+ updated_stylesheets << [file, css]
+ update_stylesheet(file, css, sourcemap)
+ else
+ run_not_updating_stylesheet(file, css, sourcemap)
+ end
+ end
+ run_updated_stylesheets(updated_stylesheets)
+ end
+
+ # Construct a list of files that might need to be compiled
+ # from the provided individual_files and the template_locations.
+ #
+ # Note: this method does not cache the results as they can change
+ # across invocations when sass files are added or removed.
+ #
+ # @param individual_files [Array<(String, String[, String])>]
+ # A list of files to check for updates
+ # **in addition to those specified by the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
+ # The first string in each pair is the location of the Sass/SCSS file,
+ # the second is the location of the CSS file that it should be compiled to.
+ # The third string, if provided, is the location of the Sourcemap file.
+ # @return [Array<(String, String, String)>]
+ # A list of [sass_file, css_file, sourcemap_file] tuples similar
+ # to what was passed in, but expanded to include the current state
+ # of the directories being updated.
+ def file_list(individual_files = [])
+ files = individual_files.map do |tuple|
+ if engine_options[:sourcemap] == :none
+ tuple[0..1]
+ elsif tuple.size < 3
+ [tuple[0], tuple[1], Sass::Util.sourcemap_name(tuple[1])]
+ else
+ tuple.dup
+ end
+ end
+
+ template_location_array.each do |template_location, css_location|
+ Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
+ # Get the relative path to the file
+ name = Sass::Util.relative_path_from(file, template_location).to_s
+ css = css_filename(name, css_location)
+ sourcemap = Sass::Util.sourcemap_name(css) unless engine_options[:sourcemap] == :none
+ files << [file, css, sourcemap]
+ end
+ end
+ files
+ end
+
+ # Watches the template directory (or directories)
+ # and updates the CSS files whenever the related Sass/SCSS files change.
+ # `watch` never returns.
+ #
+ # Whenever a change is detected to a Sass/SCSS file in
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`},
+ # the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
+ # will be recompiled.
+ # The CSS files of any Sass/SCSS files that import the changed file will also be recompiled.
+ #
+ # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
+ #
+ # Note that `watch` uses the [Listen](http://github.com/guard/listen) library
+ # to monitor the filesystem for changes.
+ # Listen isn't loaded until `watch` is run.
+ # The version of Listen distributed with Sass is loaded by default,
+ # but if another version has already been loaded that will be used instead.
+ #
+ # @param individual_files [Array<(String, String[, String])>]
+ # A list of files to check for updates
+ # **in addition to those specified by the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
+ # The first string in each pair is the location of the Sass/SCSS file,
+ # the second is the location of the CSS file that it should be compiled to.
+ # The third string, if provided, is the location of the Sourcemap file.
+ # @param options [Hash] The options that control how watching works.
+ # @option options [Boolean] :skip_initial_update
+ # Don't do an initial update when starting the watcher when true
+ def watch(individual_files = [], options = {})
+ options, individual_files = individual_files, [] if individual_files.is_a?(Hash)
+ update_stylesheets(individual_files) unless options[:skip_initial_update]
+
+ directories = watched_paths
+ individual_files.each do |(source, _, _)|
+ source = File.expand_path(source)
+ @watched_files << Sass::Util.realpath(source).to_s
+ directories << File.dirname(source)
+ end
+ directories = remove_redundant_directories(directories)
+
+ # A Listen version prior to 2.0 will write a test file to a directory to
+ # see if a watcher supports watching that directory. That breaks horribly
+ # on read-only directories, so we filter those out.
+ unless Sass::Util.listen_geq_2?
+ directories = directories.select {|d| File.directory?(d) && File.writable?(d)}
+ end
+
+ # TODO: Keep better track of what depends on what
+ # so we don't have to run a global update every time anything changes.
+ # XXX The :additional_watch_paths option exists for Compass to use until
+ # a deprecated feature is removed. It may be removed without warning.
+ listener_args = directories +
+ Array(options[:additional_watch_paths]) +
+ [{:relative_paths => false}]
+
+ # The native windows listener is much slower than the polling option, according to
+ # https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e
+ poll = @options[:poll] || Sass::Util.windows?
+ if poll && Sass::Util.listen_geq_2?
+ # In Listen 2.0.0 and on, :force_polling is an option. In earlier
+ # versions, it's a method on the listener (called below).
+ listener_args.last[:force_polling] = true
+ end
+
+ listener = create_listener(*listener_args) do |modified, added, removed|
+ on_file_changed(individual_files, modified, added, removed)
+ yield(modified, added, removed) if block_given?
+ end
+
+ if poll && !Sass::Util.listen_geq_2?
+ # In Listen 2.0.0 and on, :force_polling is an option (set above). In
+ # earlier versions, it's a method on the listener.
+ listener.force_polling(true)
+ end
+
+ listen_to(listener)
+ end
+
+ # Non-destructively modifies \{#options} so that default values are properly set,
+ # and returns the result.
+ #
+ # @param additional_options [{Symbol => Object}] An options hash with which to merge \{#options}
+ # @return [{Symbol => Object}] The modified options hash
+ def engine_options(additional_options = {})
+ opts = options.merge(additional_options)
+ opts[:load_paths] = load_paths(opts)
+ options[:sourcemap] = :auto if options[:sourcemap] == true
+ options[:sourcemap] = :none if options[:sourcemap] == false
+ opts
+ end
+
+ # Compass expects this to exist
+ def stylesheet_needs_update?(css_file, template_file)
+ StalenessChecker.stylesheet_needs_update?(css_file, template_file)
+ end
+
+ # Remove all output files that would be created by calling update_stylesheets, if they exist.
+ #
+ # This method runs the deleting_css and deleting_sourcemap callbacks for
+ # the files that are deleted.
+ #
+ # @param individual_files [Array<(String, String[, String])>]
+ # A list of files to check for updates
+ # **in addition to those specified by the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
+ # The first string in each pair is the location of the Sass/SCSS file,
+ # the second is the location of the CSS file that it should be compiled to.
+ # The third string, if provided, is the location of the Sourcemap file.
+ def clean(individual_files = [])
+ file_list(individual_files).each do |(_, css_file, sourcemap_file)|
+ if File.exist?(css_file)
+ run_deleting_css css_file
+ File.delete(css_file)
+ end
+ if sourcemap_file && File.exist?(sourcemap_file)
+ run_deleting_sourcemap sourcemap_file
+ File.delete(sourcemap_file)
+ end
+ end
+ nil
+ end
+
+ private
+
+ def create_listener(*args, &block)
+ Sass::Util.load_listen!
+ if Sass::Util.listen_geq_2?
+ # Work around guard/listen#243.
+ options = args.pop if args.last.is_a?(Hash)
+ args.map do |dir|
+ Listen.to(dir, options, &block)
+ end
+ else
+ Listen::Listener.new(*args, &block)
+ end
+ end
+
+ def listen_to(listener)
+ if Sass::Util.listen_geq_2?
+ listener.map {|l| l.start}
+ sleep
+ else
+ listener.start!
+ end
+ rescue Interrupt
+ # Squelch Interrupt for clean exit from Listen::Listener
+ end
+
+ def remove_redundant_directories(directories)
+ dedupped = []
+ directories.each do |new_directory|
+ # no need to add a directory that is already watched.
+ next if dedupped.any? do |existing_directory|
+ child_of_directory?(existing_directory, new_directory)
+ end
+ # get rid of any sub directories of this new directory
+ dedupped.reject! do |existing_directory|
+ child_of_directory?(new_directory, existing_directory)
+ end
+ dedupped << new_directory
+ end
+ dedupped
+ end
+
+ def on_file_changed(individual_files, modified, added, removed)
+ recompile_required = false
+
+ modified.uniq.each do |f|
+ next unless watched_file?(f)
+ recompile_required = true
+ run_template_modified(relative_to_pwd(f))
+ end
+
+ added.uniq.each do |f|
+ next unless watched_file?(f)
+ recompile_required = true
+ run_template_created(relative_to_pwd(f))
+ end
+
+ removed.uniq.each do |f|
+ next unless watched_file?(f)
+ run_template_deleted(relative_to_pwd(f))
+ if (files = individual_files.find {|(source, _, _)| File.expand_path(source) == f})
+ recompile_required = true
+ # This was a file we were watching explicitly and compiling to a particular location.
+ # Delete the corresponding file.
+ try_delete_css files[1]
+ else
+ next unless watched_file?(f)
+ recompile_required = true
+ # Look for the sass directory that contained the sass file
+ # And try to remove the css file that corresponds to it
+ template_location_array.each do |(sass_dir, css_dir)|
+ sass_dir = File.expand_path(sass_dir)
+ if child_of_directory?(sass_dir, f)
+ remainder = f[(sass_dir.size + 1)..-1]
+ try_delete_css(css_filename(remainder, css_dir))
+ break
+ end
+ end
+ end
+ end
+
+ if recompile_required
+ # In case a file we're watching is removed and then recreated we
+ # prune out the non-existant files here.
+ watched_files_remaining = individual_files.select {|(source, _, _)| File.exist?(source)}
+ update_stylesheets(watched_files_remaining)
+ end
+ end
+
+ def update_stylesheet(filename, css, sourcemap)
+ dir = File.dirname(css)
+ unless File.exist?(dir)
+ run_creating_directory dir
+ FileUtils.mkdir_p dir
+ end
+
+ begin
+ File.read(filename) unless File.readable?(filename) # triggers an error for handling
+ engine_opts = engine_options(:css_filename => css,
+ :filename => filename,
+ :sourcemap_filename => sourcemap)
+ mapping = nil
+ run_compilation_starting(filename, css, sourcemap)
+ engine = Sass::Engine.for_file(filename, engine_opts)
+ if sourcemap
+ rendered, mapping = engine.render_with_sourcemap(File.basename(sourcemap))
+ else
+ rendered = engine.render
+ end
+ rescue StandardError => e
+ compilation_error_occured = true
+ run_compilation_error e, filename, css, sourcemap
+ raise e unless options[:full_exception]
+ rendered = Sass::SyntaxError.exception_to_css(e, options[:line] || 1)
+ end
+
+ write_file(css, rendered)
+ if mapping
+ write_file(sourcemap, mapping.to_json(
+ :css_path => css, :sourcemap_path => sourcemap, :type => options[:sourcemap]))
+ end
+ run_updated_stylesheet(filename, css, sourcemap) unless compilation_error_occured
+ end
+
+ def write_file(fileName, content)
+ flag = 'w'
+ flag = 'wb' if Sass::Util.windows? && options[:unix_newlines]
+ File.open(fileName, flag) do |file|
+ file.set_encoding(content.encoding) unless Sass::Util.ruby1_8?
+ file.print(content)
+ end
+ end
+
+ def try_delete_css(css)
+ if File.exist?(css)
+ run_deleting_css css
+ File.delete css
+ end
+ map = Sass::Util.sourcemap_name(css)
+ if File.exist?(map)
+ run_deleting_sourcemap map
+ File.delete map
+ end
+ end
+
+ def watched_file?(file)
+ @watched_files.include?(file) || normalized_load_paths.any? {|lp| lp.watched_file?(file)}
+ end
+
+ def watched_paths
+ @watched_paths ||= normalized_load_paths.map {|lp| lp.directories_to_watch}.compact.flatten
+ end
+
+ def normalized_load_paths
+ @normalized_load_paths ||=
+ Sass::Engine.normalize_options(:load_paths => load_paths)[:load_paths]
+ end
+
+ def load_paths(opts = options)
+ (opts[:load_paths] || []) + template_locations
+ end
+
+ def template_locations
+ template_location_array.to_a.map {|l| l.first}
+ end
+
+ def css_locations
+ template_location_array.to_a.map {|l| l.last}
+ end
+
+ def css_filename(name, path)
+ "#{path}#{File::SEPARATOR unless path.end_with?(File::SEPARATOR)}#{name}".
+ gsub(/\.s[ac]ss$/, '.css')
+ end
+
+ def relative_to_pwd(f)
+ Sass::Util.relative_path_from(f, Dir.pwd).to_s
+ rescue ArgumentError # when a relative path cannot be computed
+ f
+ end
+
+ def child_of_directory?(parent, child)
+ parent_dir = parent.end_with?(File::SEPARATOR) ? parent : (parent + File::SEPARATOR)
+ child.start_with?(parent_dir) || parent == child
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/plugin/configuration.rb
b/backends/css/gems/sass-3.4.9/lib/sass/plugin/configuration.rb
new file mode 100644
index 0000000..b78c3ad
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/plugin/configuration.rb
@@ -0,0 +1,118 @@
+module Sass
+ module Plugin
+ # We keep configuration in its own self-contained file
+ # so that we can load it independently in Rails 3,
+ # where the full plugin stuff is lazy-loaded.
+ module Configuration
+ # Returns the default options for a {Sass::Plugin::Compiler}.
+ #
+ # @return [{Symbol => Object}]
+ def default_options
+ @default_options ||= {
+ :css_location => './public/stylesheets',
+ :always_update => false,
+ :always_check => true,
+ :full_exception => true,
+ :cache_location => ".sass-cache"
+ }.freeze
+ end
+
+ # Resets the options and
+ # {Sass::Callbacks::InstanceMethods#clear_callbacks! clears all callbacks}.
+ def reset!
+ @options = nil
+ clear_callbacks!
+ end
+
+ # An options hash.
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ #
+ # @return [{Symbol => Object}]
+ def options
+ @options ||= default_options.dup
+ end
+
+ # Adds a new template-location/css-location mapping.
+ # This means that Sass/SCSS files in `template_location`
+ # will be compiled to CSS files in `css_location`.
+ #
+ # This is preferred over manually manipulating the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}
+ # since the option can be in multiple formats.
+ #
+ # Note that this method will change `options[:template_location]`
+ # to be in the Array format.
+ # This means that even if `options[:template_location]`
+ # had previously been a Hash or a String,
+ # it will now be an Array.
+ #
+ # @param template_location [String] The location where Sass/SCSS files will be.
+ # @param css_location [String] The location where compiled CSS files will go.
+ def add_template_location(template_location, css_location = options[:css_location])
+ normalize_template_location!
+ template_location_array << [template_location, css_location]
+ end
+
+ # Removes a template-location/css-location mapping.
+ # This means that Sass/SCSS files in `template_location`
+ # will no longer be compiled to CSS files in `css_location`.
+ #
+ # This is preferred over manually manipulating the
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}
+ # since the option can be in multiple formats.
+ #
+ # Note that this method will change `options[:template_location]`
+ # to be in the Array format.
+ # This means that even if `options[:template_location]`
+ # had previously been a Hash or a String,
+ # it will now be an Array.
+ #
+ # @param template_location [String]
+ # The location where Sass/SCSS files were,
+ # which is now going to be ignored.
+ # @param css_location [String]
+ # The location where compiled CSS files went, but will no longer go.
+ # @return [Boolean]
+ # Non-`nil` if the given mapping already existed and was removed,
+ # or `nil` if nothing was changed.
+ def remove_template_location(template_location, css_location = options[:css_location])
+ normalize_template_location!
+ template_location_array.delete([template_location, css_location])
+ end
+
+ # Returns the template locations configured for Sass
+ # as an array of `[template_location, css_location]` pairs.
+ # See the {file:SASS_REFERENCE.md#template_location-option `:template_location` option}
+ # for details.
+ #
+ # @return [Array<(String, String)>]
+ # An array of `[template_location, css_location]` pairs.
+ def template_location_array
+ old_template_location = options[:template_location]
+ normalize_template_location!
+ options[:template_location]
+ ensure
+ options[:template_location] = old_template_location
+ end
+
+ private
+
+ def normalize_template_location!
+ return if options[:template_location].is_a?(Array)
+ options[:template_location] =
+ case options[:template_location]
+ when nil
+ if options[:css_location]
+ [[File.join(options[:css_location], 'sass'), options[:css_location]]]
+ else
+ []
+ end
+ when String
+ [[options[:template_location], options[:css_location]]]
+ else
+ options[:template_location].to_a
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/generic.rb
b/backends/css/gems/sass-3.4.9/lib/sass/plugin/generic.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/plugin/generic.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/plugin/generic.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/plugin/merb.rb
b/backends/css/gems/sass-3.4.9/lib/sass/plugin/merb.rb
new file mode 100644
index 0000000..074fd4e
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/plugin/merb.rb
@@ -0,0 +1,48 @@
+unless defined?(Sass::MERB_LOADED)
+ Sass::MERB_LOADED = true
+
+ module Sass::Plugin::Configuration
+ # Different default options in a m envirionment.
+ def default_options
+ @default_options ||= begin
+ version = Merb::VERSION.split('.').map {|n| n.to_i}
+ if version[0] <= 0 && version[1] < 5
+ root = MERB_ROOT
+ env = MERB_ENV
+ else
+ root = Merb.root.to_s
+ env = Merb.environment
+ end
+
+ {
+ :always_update => false,
+ :template_location => root + '/public/stylesheets/sass',
+ :css_location => root + '/public/stylesheets',
+ :cache_location => root + '/tmp/sass-cache',
+ :always_check => env != "production",
+ :quiet => env != "production",
+ :full_exception => env != "production"
+ }.freeze
+ end
+ end
+ end
+
+ config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {}
+
+ if defined? config.symbolize_keys!
+ config.symbolize_keys!
+ end
+
+ Sass::Plugin.options.merge!(config)
+
+ require 'sass/plugin/rack'
+ class Sass::Plugin::MerbBootLoader < Merb::BootLoader
+ after Merb::BootLoader::RackUpApplication
+
+ def self.run
+ # Apparently there's no better way than this to add Sass
+ # to Merb's Rack stack.
+ Merb::Config[:app] = Sass::Plugin::Rack.new(Merb::Config[:app])
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/rack.rb
b/backends/css/gems/sass-3.4.9/lib/sass/plugin/rack.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/plugin/rack.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/plugin/rack.rb
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/rails.rb
b/backends/css/gems/sass-3.4.9/lib/sass/plugin/rails.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/plugin/rails.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/plugin/rails.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/plugin/staleness_checker.rb
b/backends/css/gems/sass-3.4.9/lib/sass/plugin/staleness_checker.rb
new file mode 100644
index 0000000..23a9bf2
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/plugin/staleness_checker.rb
@@ -0,0 +1,199 @@
+require 'thread'
+
+module Sass
+ module Plugin
+ # The class handles `.s[ca]ss` file staleness checks via their mtime timestamps.
+ #
+ # To speed things up two level of caches are employed:
+ #
+ # * A class-level dependency cache which stores @import paths for each file.
+ # This is a long-lived cache that is reused by every StalenessChecker instance.
+ # * Three short-lived instance-level caches, one for file mtimes,
+ # one for whether a file is stale during this particular run.
+ # and one for the parse tree for a file.
+ # These are only used by a single StalenessChecker instance.
+ #
+ # Usage:
+ #
+ # * For a one-off staleness check of a single `.s[ca]ss` file,
+ # the class-level {stylesheet_needs_update?} method
+ # should be used.
+ # * For a series of staleness checks (e.g. checking all files for staleness)
+ # a StalenessChecker instance should be created,
+ # and the instance-level \{#stylesheet\_needs\_update?} method should be used.
+ # the caches should make the whole process significantly faster.
+ # *WARNING*: It is important not to retain the instance for too long,
+ # as its instance-level caches are never explicitly expired.
+ class StalenessChecker
+ @dependencies_cache = {}
+ @dependency_cache_mutex = Mutex.new
+
+ class << self
+ # TODO: attach this to a compiler instance.
+ # @private
+ attr_accessor :dependencies_cache
+ attr_reader :dependency_cache_mutex
+ end
+
+ # Creates a new StalenessChecker
+ # for checking the staleness of several stylesheets at once.
+ #
+ # @param options [{Symbol => Object}]
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ def initialize(options)
+ # URIs that are being actively checked for staleness. Protects against
+ # import loops.
+ @actively_checking = Set.new
+
+ # Entries in the following instance-level caches are never explicitly expired.
+ # Instead they are supposed to automatically go out of scope when a series of staleness
+ # checks (this instance of StalenessChecker was created for) is finished.
+ @mtimes, @dependencies_stale, @parse_trees = {}, {}, {}
+ @options = Sass::Engine.normalize_options(options)
+ end
+
+ # Returns whether or not a given CSS file is out of date
+ # and needs to be regenerated.
+ #
+ # @param css_file [String] The location of the CSS file to check.
+ # @param template_file [String] The location of the Sass or SCSS template
+ # that is compiled to `css_file`.
+ # @return [Boolean] Whether the stylesheet needs to be updated.
+ def stylesheet_needs_update?(css_file, template_file, importer = nil)
+ template_file = File.expand_path(template_file)
+ begin
+ css_mtime = File.mtime(css_file)
+ rescue Errno::ENOENT
+ return true
+ end
+ stylesheet_modified_since?(template_file, css_mtime, importer)
+ end
+
+ # Returns whether a Sass or SCSS stylesheet has been modified since a given time.
+ #
+ # @param template_file [String] The location of the Sass or SCSS template.
+ # @param mtime [Fixnum] The modification time to check against.
+ # @param importer [Sass::Importers::Base] The importer used to locate the stylesheet.
+ # Defaults to the filesystem importer.
+ # @return [Boolean] Whether the stylesheet has been modified.
+ def stylesheet_modified_since?(template_file, mtime, importer = nil)
+ importer ||= @options[:filesystem_importer].new(".")
+ dependency_updated?(mtime).call(template_file, importer)
+ end
+
+ # Returns whether or not a given CSS file is out of date
+ # and needs to be regenerated.
+ #
+ # The distinction between this method and the instance-level \{#stylesheet\_needs\_update?}
+ # is that the instance method preserves mtime and stale-dependency caches,
+ # so it's better to use when checking multiple stylesheets at once.
+ #
+ # @param css_file [String] The location of the CSS file to check.
+ # @param template_file [String] The location of the Sass or SCSS template
+ # that is compiled to `css_file`.
+ # @return [Boolean] Whether the stylesheet needs to be updated.
+ def self.stylesheet_needs_update?(css_file, template_file, importer = nil)
+ new(Plugin.engine_options).stylesheet_needs_update?(css_file, template_file, importer)
+ end
+
+ # Returns whether a Sass or SCSS stylesheet has been modified since a given time.
+ #
+ # The distinction between this method and the instance-level \{#stylesheet\_modified\_since?}
+ # is that the instance method preserves mtime and stale-dependency caches,
+ # so it's better to use when checking multiple stylesheets at once.
+ #
+ # @param template_file [String] The location of the Sass or SCSS template.
+ # @param mtime [Fixnum] The modification time to check against.
+ # @param importer [Sass::Importers::Base] The importer used to locate the stylesheet.
+ # Defaults to the filesystem importer.
+ # @return [Boolean] Whether the stylesheet has been modified.
+ def self.stylesheet_modified_since?(template_file, mtime, importer = nil)
+ new(Plugin.engine_options).stylesheet_modified_since?(template_file, mtime, importer)
+ end
+
+ private
+
+ def dependencies_stale?(uri, importer, css_mtime)
+ timestamps = @dependencies_stale[[uri, importer]] ||= {}
+ timestamps.each_pair do |checked_css_mtime, is_stale|
+ if checked_css_mtime <= css_mtime && !is_stale
+ return false
+ elsif checked_css_mtime > css_mtime && is_stale
+ return true
+ end
+ end
+ timestamps[css_mtime] = dependencies(uri, importer).any?(&dependency_updated?(css_mtime))
+ rescue Sass::SyntaxError
+ # If there's an error finding dependencies, default to recompiling.
+ true
+ end
+
+ def mtime(uri, importer)
+ @mtimes[[uri, importer]] ||=
+ begin
+ mtime = importer.mtime(uri, @options)
+ if mtime.nil?
+ with_dependency_cache {|cache| cache.delete([uri, importer])}
+ nil
+ else
+ mtime
+ end
+ end
+ end
+
+ def dependencies(uri, importer)
+ stored_mtime, dependencies =
+ with_dependency_cache {|cache| Sass::Util.destructure(cache[[uri, importer]])}
+
+ if !stored_mtime || stored_mtime < mtime(uri, importer)
+ dependencies = compute_dependencies(uri, importer)
+ with_dependency_cache do |cache|
+ cache[[uri, importer]] = [mtime(uri, importer), dependencies]
+ end
+ end
+
+ dependencies
+ end
+
+ def dependency_updated?(css_mtime)
+ proc do |uri, importer|
+ next true if @actively_checking.include?(uri)
+ begin
+ @actively_checking << uri
+ sass_mtime = mtime(uri, importer)
+ !sass_mtime ||
+ sass_mtime > css_mtime ||
+ dependencies_stale?(uri, importer, css_mtime)
+ ensure
+ @actively_checking.delete uri
+ end
+ end
+ end
+
+ def compute_dependencies(uri, importer)
+ tree(uri, importer).grep(Tree::ImportNode) do |n|
+ next if n.css_import?
+ file = n.imported_file
+ key = [file.options[:filename], file.options[:importer]]
+ @parse_trees[key] = file.to_tree
+ key
+ end.compact
+ end
+
+ def tree(uri, importer)
+ @parse_trees[[uri, importer]] ||= importer.find(uri, @options).to_tree
+ end
+
+ # Get access to the global dependency cache in a threadsafe manner.
+ # Inside the block, no other thread can access the dependency cache.
+ #
+ # @yieldparam cache [Hash] The hash that is the global dependency cache
+ # @return The value returned by the block to which this method yields
+ def with_dependency_cache
+ StalenessChecker.dependency_cache_mutex.synchronize do
+ yield StalenessChecker.dependencies_cache
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/railtie.rb
b/backends/css/gems/sass-3.4.9/lib/sass/railtie.rb
new file mode 100644
index 0000000..cf900b5
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/railtie.rb
@@ -0,0 +1,10 @@
+# Rails 3.0.0.beta.2+, < 3.1
+if defined?(ActiveSupport) && Sass::Util.has?(:public_method, ActiveSupport, :on_load) &&
+ !Sass::Util.ap_geq?('3.1.0.beta')
+ require 'sass/plugin/configuration'
+ ActiveSupport.on_load(:before_configuration) do
+ require 'sass'
+ require 'sass/plugin'
+ require 'sass/plugin/rails'
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/repl.rb b/backends/css/gems/sass-3.4.9/lib/sass/repl.rb
new file mode 100644
index 0000000..e9b9e6c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/repl.rb
@@ -0,0 +1,57 @@
+require 'readline'
+
+module Sass
+ # Runs a SassScript read-eval-print loop.
+ # It presents a prompt on the terminal,
+ # reads in SassScript expressions,
+ # evaluates them,
+ # and prints the result.
+ class Repl
+ # @param options [{Symbol => Object}] An options hash.
+ def initialize(options = {})
+ @options = options
+ end
+
+ # Starts the read-eval-print loop.
+ def run
+ environment = Environment.new
+ @line = 0
+ loop do
+ @line += 1
+ unless (text = Readline.readline('>> '))
+ puts
+ return
+ end
+
+ Readline::HISTORY << text
+ parse_input(environment, text)
+ end
+ end
+
+ private
+
+ def parse_input(environment, text)
+ case text
+ when Script::MATCH
+ name = $1
+ guarded = !!$3
+ val = Script::Parser.parse($2, @line, text.size - ($3 || '').size - $2.size)
+
+ unless guarded && environment.var(name)
+ environment.set_var(name, val.perform(environment))
+ end
+
+ p environment.var(name)
+ else
+ p Script::Parser.parse(text, @line, 0).perform(environment)
+ end
+ rescue Sass::SyntaxError => e
+ puts "SyntaxError: #{e.message}"
+ if @options[:trace]
+ e.backtrace.each do |line|
+ puts "\tfrom #{line}"
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/root.rb b/backends/css/gems/sass-3.4.9/lib/sass/root.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/root.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/root.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script.rb b/backends/css/gems/sass-3.4.9/lib/sass/script.rb
new file mode 100644
index 0000000..5ca639d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script.rb
@@ -0,0 +1,66 @@
+require 'sass/scss/rx'
+
+module Sass
+ # SassScript is code that's embedded in Sass documents
+ # to allow for property values to be computed from variables.
+ #
+ # This module contains code that handles the parsing and evaluation of SassScript.
+ module Script
+ # The regular expression used to parse variables.
+ MATCH = /^\$(#{Sass::SCSS::RX::IDENT})\s*:\s*(.+?)
+ (!#{Sass::SCSS::RX::IDENT}(?:\s+!#{Sass::SCSS::RX::IDENT})*)?$/x
+
+ # The regular expression used to validate variables without matching.
+ VALIDATE = /^\$#{Sass::SCSS::RX::IDENT}$/
+
+ # Parses a string of SassScript
+ #
+ # @param value [String] The SassScript
+ # @param line [Fixnum] The number of the line on which the SassScript appeared.
+ # Used for error reporting
+ # @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
+ # Used for error reporting
+ # @param options [{Symbol => Object}] An options hash;
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
+ # @return [Script::Tree::Node] The root node of the parse tree
+ def self.parse(value, line, offset, options = {})
+ Parser.parse(value, line, offset, options)
+ rescue Sass::SyntaxError => e
+ e.message << ": #{value.inspect}." if e.message == "SassScript error"
+ e.modify_backtrace(:line => line, :filename => options[:filename])
+ raise e
+ end
+
+ require 'sass/script/functions'
+ require 'sass/script/parser'
+ require 'sass/script/tree'
+ require 'sass/script/value'
+
+ # @private
+ CONST_RENAMES = {
+ :Literal => Sass::Script::Value::Base,
+ :ArgList => Sass::Script::Value::ArgList,
+ :Bool => Sass::Script::Value::Bool,
+ :Color => Sass::Script::Value::Color,
+ :List => Sass::Script::Value::List,
+ :Null => Sass::Script::Value::Null,
+ :Number => Sass::Script::Value::Number,
+ :String => Sass::Script::Value::String,
+ :Node => Sass::Script::Tree::Node,
+ :Funcall => Sass::Script::Tree::Funcall,
+ :Interpolation => Sass::Script::Tree::Interpolation,
+ :Operation => Sass::Script::Tree::Operation,
+ :StringInterpolation => Sass::Script::Tree::StringInterpolation,
+ :UnaryOperation => Sass::Script::Tree::UnaryOperation,
+ :Variable => Sass::Script::Tree::Variable,
+ }
+
+ # @private
+ def self.const_missing(name)
+ klass = CONST_RENAMES[name]
+ super unless klass
+ CONST_RENAMES.each {|n, k| const_set(n, k)}
+ klass
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/css_lexer.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/css_lexer.rb
new file mode 100644
index 0000000..6362a9d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/css_lexer.rb
@@ -0,0 +1,33 @@
+module Sass
+ module Script
+ # This is a subclass of {Lexer} for use in parsing plain CSS properties.
+ #
+ # @see Sass::SCSS::CssParser
+ class CssLexer < Lexer
+ private
+
+ def token
+ important || super
+ end
+
+ def string(re, *args)
+ if re == :uri
+ uri = scan(URI)
+ return unless uri
+ return [:string, Script::Value::String.new(uri)]
+ end
+
+ return unless scan(STRING)
+ string_value = Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
+ value = Script::Value::String.new(string_value, :string)
+ [:string, value]
+ end
+
+ def important
+ s = scan(IMPORTANT)
+ return unless s
+ [:raw, s]
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/css_parser.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/css_parser.rb
new file mode 100644
index 0000000..4399493
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/css_parser.rb
@@ -0,0 +1,34 @@
+require 'sass/script'
+require 'sass/script/css_lexer'
+
+module Sass
+ module Script
+ # This is a subclass of {Parser} for use in parsing plain CSS properties.
+ #
+ # @see Sass::SCSS::CssParser
+ class CssParser < Parser
+ private
+
+ # @private
+ def lexer_class; CssLexer; end
+
+ # We need a production that only does /,
+ # since * and % aren't allowed in plain CSS
+ production :div, :unary_plus, :div
+
+ def string
+ tok = try_tok(:string)
+ return number unless tok
+ unless @lexer.peek && @lexer.peek.type == :begin_interpolation
+ return literal_node(tok.value, tok.source_range)
+ end
+ end
+
+ # Short-circuit all the SassScript-only productions
+ alias_method :interpolation, :space
+ alias_method :or_expr, :div
+ alias_method :unary_div, :ident
+ alias_method :paren, :string
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/functions.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/functions.rb
new file mode 100644
index 0000000..11249d6
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/functions.rb
@@ -0,0 +1,2646 @@
+require 'sass/script/value/helpers'
+
+module Sass::Script
+ # @comment
+ # YARD can't handle some multiline tags, and we need really long tags for function declarations.
+ # rubocop:disable LineLength
+ # Methods in this module are accessible from the SassScript context.
+ # For example, you can write
+ #
+ # $color: hsl(120deg, 100%, 50%)
+ #
+ # and it will call {Functions#hsl}.
+ #
+ # The following functions are provided:
+ #
+ # *Note: These functions are described in more detail below.*
+ #
+ # ## RGB Functions
+ #
+ # \{#rgb rgb($red, $green, $blue)}
+ # : Creates a {Sass::Script::Value::Color Color} from red, green, and blue
+ # values.
+ #
+ # \{#rgba rgba($red, $green, $blue, $alpha)}
+ # : Creates a {Sass::Script::Value::Color Color} from red, green, blue, and
+ # alpha values.
+ #
+ # \{#red red($color)}
+ # : Gets the red component of a color.
+ #
+ # \{#green green($color)}
+ # : Gets the green component of a color.
+ #
+ # \{#blue blue($color)}
+ # : Gets the blue component of a color.
+ #
+ # \{#mix mix($color1, $color2, \[$weight\])}
+ # : Mixes two colors together.
+ #
+ # ## HSL Functions
+ #
+ # \{#hsl hsl($hue, $saturation, $lightness)}
+ # : Creates a {Sass::Script::Value::Color Color} from hue, saturation, and
+ # lightness values.
+ #
+ # \{#hsla hsla($hue, $saturation, $lightness, $alpha)}
+ # : Creates a {Sass::Script::Value::Color Color} from hue, saturation,
+ # lightness, and alpha values.
+ #
+ # \{#hue hue($color)}
+ # : Gets the hue component of a color.
+ #
+ # \{#saturation saturation($color)}
+ # : Gets the saturation component of a color.
+ #
+ # \{#lightness lightness($color)}
+ # : Gets the lightness component of a color.
+ #
+ # \{#adjust_hue adjust-hue($color, $degrees)}
+ # : Changes the hue of a color.
+ #
+ # \{#lighten lighten($color, $amount)}
+ # : Makes a color lighter.
+ #
+ # \{#darken darken($color, $amount)}
+ # : Makes a color darker.
+ #
+ # \{#saturate saturate($color, $amount)}
+ # : Makes a color more saturated.
+ #
+ # \{#desaturate desaturate($color, $amount)}
+ # : Makes a color less saturated.
+ #
+ # \{#grayscale grayscale($color)}
+ # : Converts a color to grayscale.
+ #
+ # \{#complement complement($color)}
+ # : Returns the complement of a color.
+ #
+ # \{#invert invert($color)}
+ # : Returns the inverse of a color.
+ #
+ # ## Opacity Functions
+ #
+ # \{#alpha alpha($color)} / \{#opacity opacity($color)}
+ # : Gets the alpha component (opacity) of a color.
+ #
+ # \{#rgba rgba($color, $alpha)}
+ # : Changes the alpha component for a color.
+ #
+ # \{#opacify opacify($color, $amount)} / \{#fade_in fade-in($color, $amount)}
+ # : Makes a color more opaque.
+ #
+ # \{#transparentize transparentize($color, $amount)} / \{#fade_out fade-out($color, $amount)}
+ # : Makes a color more transparent.
+ #
+ # ## Other Color Functions
+ #
+ # \{#adjust_color adjust-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\],
\[$lightness\], \[$alpha\])}
+ # : Increases or decreases one or more components of a color.
+ #
+ # \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$saturation\], \[$lightness\],
\[$alpha\])}
+ # : Fluidly scales one or more properties of a color.
+ #
+ # \{#change_color change-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\],
\[$lightness\], \[$alpha\])}
+ # : Changes one or more properties of a color.
+ #
+ # \{#ie_hex_str ie-hex-str($color)}
+ # : Converts a color into the format understood by IE filters.
+ #
+ # ## String Functions
+ #
+ # \{#unquote unquote($string)}
+ # : Removes quotes from a string.
+ #
+ # \{#quote quote($string)}
+ # : Adds quotes to a string.
+ #
+ # \{#str_length str-length($string)}
+ # : Returns the number of characters in a string.
+ #
+ # \{#str_insert str-insert($string, $insert, $index)}
+ # : Inserts `$insert` into `$string` at `$index`.
+ #
+ # \{#str_index str-index($string, $substring)}
+ # : Returns the index of the first occurance of `$substring` in `$string`.
+ #
+ # \{#str_slice str-slice($string, $start-at, [$end-at])}
+ # : Extracts a substring from `$string`.
+ #
+ # \{#to_upper_case to-upper-case($string)}
+ # : Converts a string to upper case.
+ #
+ # \{#to_lower_case to-lower-case($string)}
+ # : Converts a string to lower case.
+ #
+ # ## Number Functions
+ #
+ # \{#percentage percentage($number)}
+ # : Converts a unitless number to a percentage.
+ #
+ # \{#round round($number)}
+ # : Rounds a number to the nearest whole number.
+ #
+ # \{#ceil ceil($number)}
+ # : Rounds a number up to the next whole number.
+ #
+ # \{#floor floor($number)}
+ # : Rounds a number down to the previous whole number.
+ #
+ # \{#abs abs($number)}
+ # : Returns the absolute value of a number.
+ #
+ # \{#min min($numbers...)\}
+ # : Finds the minimum of several numbers.
+ #
+ # \{#max max($numbers...)\}
+ # : Finds the maximum of several numbers.
+ #
+ # \{#random random([$limit])\}
+ # : Returns a random number.
+ #
+ # ## List Functions {#list-functions}
+ #
+ # All list functions work for maps as well, treating them as lists of pairs.
+ #
+ # \{#length length($list)}
+ # : Returns the length of a list.
+ #
+ # \{#nth nth($list, $n)}
+ # : Returns a specific item in a list.
+ #
+ # \{#set-nth set-nth($list, $n, $value)}
+ # : Replaces the nth item in a list.
+ #
+ # \{#join join($list1, $list2, \[$separator\])}
+ # : Joins together two lists into one.
+ #
+ # \{#append append($list1, $val, \[$separator\])}
+ # : Appends a single value onto the end of a list.
+ #
+ # \{#zip zip($lists...)}
+ # : Combines several lists into a single multidimensional list.
+ #
+ # \{#index index($list, $value)}
+ # : Returns the position of a value within a list.
+ #
+ # \{#list_separator list-separator(#list)}
+ # : Returns the separator of a list.
+ #
+ # ## Map Functions {#map-functions}
+ #
+ # \{#map_get map-get($map, $key)}
+ # : Returns the value in a map associated with a given key.
+ #
+ # \{#map_merge map-merge($map1, $map2)}
+ # : Merges two maps together into a new map.
+ #
+ # \{#map_remove map-remove($map, $keys...)}
+ # : Returns a new map with keys removed.
+ #
+ # \{#map_keys map-keys($map)}
+ # : Returns a list of all keys in a map.
+ #
+ # \{#map_values map-values($map)}
+ # : Returns a list of all values in a map.
+ #
+ # \{#map_has_key map-has-key($map, $key)}
+ # : Returns whether a map has a value associated with a given key.
+ #
+ # \{#keywords keywords($args)}
+ # : Returns the keywords passed to a function that takes variable arguments.
+ #
+ # ## Selector Functions
+ #
+ # Selector functions are very liberal in the formats they support
+ # for selector arguments. They can take a plain string, a list of
+ # lists as returned by `&` or anything in between:
+ #
+ # * A plain sring, such as `".foo .bar, .baz .bang"`.
+ # * A space-separated list of strings such as `(".foo" ".bar")`.
+ # * A comma-separated list of strings such as `(".foo .bar", ".baz .bang")`.
+ # * A comma-separated list of space-separated lists of strings such
+ # as `((".foo" ".bar"), (".baz" ".bang"))`.
+ #
+ # In general, selector functions allow placeholder selectors
+ # (`%foo`) but disallow parent-reference selectors (`&`).
+ #
+ # \{#selector_nest selector-nest($selectors...)}
+ # : Nests selector beneath one another like they would be nested in the
+ # stylesheet.
+ #
+ # \{#selector_append selector-append($selectors...)}
+ # : Appends selectors to one another without spaces in between.
+ #
+ # \{#selector_extend selector-extend($selector, $extendee, $extender)}
+ # : Extends `$extendee` with `$extender` within `$selector`.
+ #
+ # \{#selector_replace selector-replace($selector, $original, $replacement)}
+ # : Replaces `$original` with `$replacement` within `$selector`.
+ #
+ # \{#selector_unify selector-unify($selector1, $selector2)}
+ # : Unifies two selectors to produce a selector that matches
+ # elements matched by both.
+ #
+ # \{#is_superselector is-superselector($super, $sub)}
+ # : Returns whether `$super` matches all the elements `$sub` does, and
+ # possibly more.
+ #
+ # \{#simple_selectors simple-selectors($selector)}
+ # : Returns the simple selectors that comprise a compound selector.
+ #
+ # \{#selector_parse selector-parse($selector)}
+ # : Parses a selector into the format returned by `&`.
+ #
+ # ## Introspection Functions
+ #
+ # \{#feature_exists feature-exists($feature)}
+ # : Returns whether a feature exists in the current Sass runtime.
+ #
+ # \{#variable_exists variable-exists($name)}
+ # : Returns whether a variable with the given name exists in the current scope.
+ #
+ # \{#global_variable_exists global-variable-exists($name)}
+ # : Returns whether a variable with the given name exists in the global scope.
+ #
+ # \{#function_exists function-exists($name)}
+ # : Returns whether a function with the given name exists.
+ #
+ # \{#mixin_exists mixin-exists($name)}
+ # : Returns whether a mixin with the given name exists.
+ #
+ # \{#inspect inspect($value)}
+ # : Returns the string representation of a value as it would be represented in Sass.
+ #
+ # \{#type_of type-of($value)}
+ # : Returns the type of a value.
+ #
+ # \{#unit unit($number)}
+ # : Returns the unit(s) associated with a number.
+ #
+ # \{#unitless unitless($number)}
+ # : Returns whether a number has units.
+ #
+ # \{#comparable comparable($number1, $number2)}
+ # : Returns whether two numbers can be added, subtracted, or compared.
+ #
+ # \{#call call($name, $args...)}
+ # : Dynamically calls a Sass function.
+ #
+ # ## Miscellaneous Functions
+ #
+ # \{#if if($condition, $if-true, $if-false)}
+ # : Returns one of two values, depending on whether or not `$condition` is
+ # true.
+ #
+ # \{#unique_id unique-id()}
+ # : Returns a unique CSS identifier.
+ #
+ # ## Adding Custom Functions
+ #
+ # New Sass functions can be added by adding Ruby methods to this module.
+ # For example:
+ #
+ # module Sass::Script::Functions
+ # def reverse(string)
+ # assert_type string, :String
+ # Sass::Script::Value::String.new(string.value.reverse)
+ # end
+ # declare :reverse, [:string]
+ # end
+ #
+ # Calling {declare} tells Sass the argument names for your function.
+ # If omitted, the function will still work, but will not be able to accept keyword arguments.
+ # {declare} can also allow your function to take arbitrary keyword arguments.
+ #
+ # There are a few things to keep in mind when modifying this module.
+ # First of all, the arguments passed are {Value} objects.
+ # Value objects are also expected to be returned.
+ # This means that Ruby values must be unwrapped and wrapped.
+ #
+ # Most Value objects support the {Value::Base#value value} accessor for getting
+ # their Ruby values. Color objects, though, must be accessed using
+ # {Sass::Script::Value::Color#rgb rgb}, {Sass::Script::Value::Color#red red},
+ # {Sass::Script::Value::Color#blue green}, or {Sass::Script::Value::Color#blue
+ # blue}.
+ #
+ # Second, making Ruby functions accessible from Sass introduces the temptation
+ # to do things like database access within stylesheets.
+ # This is generally a bad idea;
+ # since Sass files are by default only compiled once,
+ # dynamic code is not a great fit.
+ #
+ # If you really, really need to compile Sass on each request,
+ # first make sure you have adequate caching set up.
+ # Then you can use {Sass::Engine} to render the code,
+ # using the {file:SASS_REFERENCE.md#custom-option `options` parameter}
+ # to pass in data that {EvaluationContext#options can be accessed}
+ # from your Sass functions.
+ #
+ # Within one of the functions in this module,
+ # methods of {EvaluationContext} can be used.
+ #
+ # ### Caveats
+ #
+ # When creating new {Value} objects within functions, be aware that it's not
+ # safe to call {Value::Base#to_s #to_s} (or other methods that use the string
+ # representation) on those objects without first setting {Tree::Node#options=
+ # the #options attribute}.
+ #
+ # @comment
+ # rubocop:enable LineLength
+ module Functions
+ @signatures = {}
+
+ # A class representing a Sass function signature.
+ #
+ # @attr args [Array<String>] The names of the arguments to the function.
+ # @attr delayed_args [Array<String>] The names of the arguments whose evaluation should be
+ # delayed.
+ # @attr var_args [Boolean] Whether the function takes a variable number of arguments.
+ # @attr var_kwargs [Boolean] Whether the function takes an arbitrary set of keyword arguments.
+ Signature = Struct.new(:args, :delayed_args, :var_args, :var_kwargs, :deprecated)
+
+ # Declare a Sass signature for a Ruby-defined function.
+ # This includes the names of the arguments,
+ # whether the function takes a variable number of arguments,
+ # and whether the function takes an arbitrary set of keyword arguments.
+ #
+ # It's not necessary to declare a signature for a function.
+ # However, without a signature it won't support keyword arguments.
+ #
+ # A single function can have multiple signatures declared
+ # as long as each one takes a different number of arguments.
+ # It's also possible to declare multiple signatures
+ # that all take the same number of arguments,
+ # but none of them but the first will be used
+ # unless the user uses keyword arguments.
+ #
+ # @example
+ # declare :rgba, [:hex, :alpha]
+ # declare :rgba, [:red, :green, :blue, :alpha]
+ # declare :accepts_anything, [], :var_args => true, :var_kwargs => true
+ # declare :some_func, [:foo, :bar, :baz], :var_kwargs => true
+ #
+ # @param method_name [Symbol] The name of the method
+ # whose signature is being declared.
+ # @param args [Array<Symbol>] The names of the arguments for the function signature.
+ # @option options :var_args [Boolean] (false)
+ # Whether the function accepts a variable number of (unnamed) arguments
+ # in addition to the named arguments.
+ # @option options :var_kwargs [Boolean] (false)
+ # Whether the function accepts other keyword arguments
+ # in addition to those in `:args`.
+ # If this is true, the Ruby function will be passed a hash from strings
+ # to {Value}s as the last argument.
+ # In addition, if this is true and `:var_args` is not,
+ # Sass will ensure that the last argument passed is a hash.
+ def self.declare(method_name, args, options = {})
+ delayed_args = []
+ args = args.map do |a|
+ a = a.to_s
+ if a[0] == ?&
+ a = a[1..-1]
+ delayed_args << a
+ end
+ a
+ end
+ # We don't expose this functionality except to certain builtin methods.
+ if delayed_args.any? && method_name != :if
+ raise ArgumentError.new("Delayed arguments are not allowed for method #{method_name}")
+ end
+ @signatures[method_name] ||= []
+ @signatures[method_name] << Signature.new(
+ args,
+ delayed_args,
+ options[:var_args],
+ options[:var_kwargs],
+ options[:deprecated] && options[:deprecated].map {|a| a.to_s})
+ end
+
+ # Determine the correct signature for the number of arguments
+ # passed in for a given function.
+ # If no signatures match, the first signature is returned for error messaging.
+ #
+ # @param method_name [Symbol] The name of the Ruby function to be called.
+ # @param arg_arity [Fixnum] The number of unnamed arguments the function was passed.
+ # @param kwarg_arity [Fixnum] The number of keyword arguments the function was passed.
+ #
+ # @return [{Symbol => Object}, nil]
+ # The signature options for the matching signature,
+ # or nil if no signatures are declared for this function. See {declare}.
+ def self.signature(method_name, arg_arity, kwarg_arity)
+ return unless @signatures[method_name]
+ @signatures[method_name].each do |signature|
+ sig_arity = signature.args.size
+ return signature if sig_arity == arg_arity + kwarg_arity
+ next unless sig_arity < arg_arity + kwarg_arity
+
+ # We have enough args.
+ # Now we need to figure out which args are varargs
+ # and if the signature allows them.
+ t_arg_arity, t_kwarg_arity = arg_arity, kwarg_arity
+ if sig_arity > t_arg_arity
+ # we transfer some kwargs arity to args arity
+ # if it does not have enough args -- assuming the names will work out.
+ t_kwarg_arity -= (sig_arity - t_arg_arity)
+ t_arg_arity = sig_arity
+ end
+
+ if (t_arg_arity == sig_arity || t_arg_arity > sig_arity && signature.var_args) &&
+ (t_kwarg_arity == 0 || t_kwarg_arity > 0 && signature.var_kwargs)
+ return signature
+ end
+ end
+ @signatures[method_name].first
+ end
+
+ # Sets the random seed used by Sass's internal random number generator.
+ #
+ # This can be used to ensure consistent random number sequences which
+ # allows for consistent results when testing, etc.
+ #
+ # @param seed [Integer]
+ # @return [Integer] The same seed.
+ def self.random_seed=(seed)
+ @random_number_generator = Sass::Util::CrossPlatformRandom.new(seed)
+ end
+
+ # Get Sass's internal random number generator.
+ #
+ # @return [Random]
+ def self.random_number_generator
+ @random_number_generator ||= Sass::Util::CrossPlatformRandom.new
+ end
+
+ # The context in which methods in {Script::Functions} are evaluated.
+ # That means that all instance methods of {EvaluationContext}
+ # are available to use in functions.
+ class EvaluationContext
+ include Functions
+ include Value::Helpers
+
+ # The human-readable names for [Sass::Script::Value::Base]. The default is
+ # just the downcased name of the type.
+ TYPE_NAMES = {:ArgList => 'variable argument list'}
+
+ # The environment for this function. This environment's
+ # {Environment#parent} is the global environment, and its
+ # {Environment#caller} is a read-only view of the local environment of the
+ # caller of this function.
+ #
+ # @return [Environment]
+ attr_reader :environment
+
+ # The options hash for the {Sass::Engine} that is processing the function call
+ #
+ # @return [{Symbol => Object}]
+ attr_reader :options
+
+ # @param environment [Environment] See \{#environment}
+ def initialize(environment)
+ @environment = environment
+ @options = environment.options
+ end
+
+ # Asserts that the type of a given SassScript value
+ # is the expected type (designated by a symbol).
+ #
+ # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
+ # Note that `:String` will match both double-quoted strings
+ # and unquoted identifiers.
+ #
+ # @example
+ # assert_type value, :String
+ # assert_type value, :Number
+ # @param value [Sass::Script::Value::Base] A SassScript value
+ # @param type [Symbol] The name of the type the value is expected to be
+ # @param name [String, Symbol, nil] The name of the argument.
+ # @raise [ArgumentError] if value is not of the correct type.
+ def assert_type(value, type, name = nil)
+ klass = Sass::Script::Value.const_get(type)
+ return if value.is_a?(klass)
+ return if value.is_a?(Sass::Script::Value::List) && type == :Map && value.value.empty?
+ err = "#{value.inspect} is not a #{TYPE_NAMES[type] || type.to_s.downcase}"
+ err = "$#{name.to_s.gsub('_', '-')}: " + err if name
+ raise ArgumentError.new(err)
+ end
+
+ # Asserts that the unit of the number is as expected.
+ #
+ # @example
+ # assert_unit number, "px"
+ # assert_unit number, nil
+ # @param number [Sass::Script::Value::Number] The number to be validated.
+ # @param unit [::String]
+ # The unit that the number must have.
+ # If nil, the number must be unitless.
+ # @param name [::String] The name of the parameter being validated.
+ # @raise [ArgumentError] if number is not of the correct unit or is not a number.
+ def assert_unit(number, unit, name = nil)
+ assert_type number, :Number, name
+ return if number.is_unit?(unit)
+ expectation = unit ? "have a unit of #{unit}" : "be unitless"
+ if name
+ raise ArgumentError.new("Expected $#{name} to #{expectation} but got #{number}")
+ else
+ raise ArgumentError.new("Expected #{number} to #{expectation}")
+ end
+ end
+
+ # Asserts that the value is an integer.
+ #
+ # @example
+ # assert_integer 2px
+ # assert_integer 2.5px
+ # => SyntaxError: "Expected 2.5px to be an integer"
+ # assert_integer 2.5px, "width"
+ # => SyntaxError: "Expected width to be an integer but got 2.5px"
+ # @param number [Sass::Script::Value::Base] The value to be validated.
+ # @param name [::String] The name of the parameter being validated.
+ # @raise [ArgumentError] if number is not an integer or is not a number.
+ def assert_integer(number, name = nil)
+ assert_type number, :Number, name
+ return if number.int?
+ if name
+ raise ArgumentError.new("Expected $#{name} to be an integer but got #{number}")
+ else
+ raise ArgumentError.new("Expected #{number} to be an integer")
+ end
+ end
+
+ # Performs a node that has been delayed for execution.
+ #
+ # @private
+ # @param node [Sass::Script::Tree::Node,
+ # Sass::Script::Value::Base] When this is a tree node, it's
+ # performed in the caller's environment. When it's a value
+ # (which can happen when the value had to be performed already
+ # -- like for a splat), it's returned as-is.
+ # @param env [Sass::Environment] The environment within which to perform the node.
+ # Defaults to the (read-only) environment of the caller.
+ def perform(node, env = environment.caller)
+ if node.is_a?(Sass::Script::Value::Base)
+ node
+ else
+ node.perform(env)
+ end
+ end
+ end
+
+ class << self
+ # Returns whether user function with a given name exists.
+ #
+ # @param function_name [String]
+ # @return [Boolean]
+ alias_method :callable?, :public_method_defined?
+
+ private
+
+ def include(*args)
+ r = super
+ # We have to re-include ourselves into EvaluationContext to work around
+ # an icky Ruby restriction.
+ EvaluationContext.send :include, self
+ r
+ end
+ end
+
+ # Creates a {Sass::Script::Value::Color Color} object from red, green, and
+ # blue values.
+ #
+ # @see #rgba
+ # @overload rgb($red, $green, $blue)
+ # @param $red [Sass::Script::Value::Number] The amount of red in the color.
+ # Must be between 0 and 255 inclusive, or between `0%` and `100%`
+ # inclusive
+ # @param $green [Sass::Script::Value::Number] The amount of green in the
+ # color. Must be between 0 and 255 inclusive, or between `0%` and `100%`
+ # inclusive
+ # @param $blue [Sass::Script::Value::Number] The amount of blue in the
+ # color. Must be between 0 and 255 inclusive, or between `0%` and `100%`
+ # inclusive
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if any parameter is the wrong type or out of bounds
+ def rgb(red, green, blue)
+ assert_type red, :Number, :red
+ assert_type green, :Number, :green
+ assert_type blue, :Number, :blue
+
+ color_attrs = [[red, :red], [green, :green], [blue, :blue]].map do |(c, name)|
+ if c.is_unit?("%")
+ c.value * 255 / 100.0
+ elsif c.unitless?
+ c.value
+ else
+ raise ArgumentError.new("Expected #{c} to be unitless or have a unit of % but got #{c}")
+ end
+ end
+
+ # Don't store the string representation for function-created colors, both
+ # because it's not very useful and because some functions aren't supported
+ # on older browsers.
+ Sass::Script::Value::Color.new(color_attrs)
+ end
+ declare :rgb, [:red, :green, :blue]
+
+ # Creates a {Sass::Script::Value::Color Color} from red, green, blue, and
+ # alpha values.
+ # @see #rgb
+ #
+ # @overload rgba($red, $green, $blue, $alpha)
+ # @param $red [Sass::Script::Value::Number] The amount of red in the
+ # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive
+ # @param $green [Sass::Script::Value::Number] The amount of green in the
+ # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive
+ # @param $blue [Sass::Script::Value::Number] The amount of blue in the
+ # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive
+ # @param $alpha [Sass::Script::Value::Number] The opacity of the color.
+ # Must be between 0 and 1 inclusive
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if any parameter is the wrong type or out of
+ # bounds
+ #
+ # @overload rgba($color, $alpha)
+ # Sets the opacity of an existing color.
+ #
+ # @example
+ # rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
+ # rgba(blue, 0.2) => rgba(0, 0, 255, 0.2)
+ #
+ # @param $color [Sass::Script::Value::Color] The color whose opacity will
+ # be changed.
+ # @param $alpha [Sass::Script::Value::Number] The new opacity of the
+ # color. Must be between 0 and 1 inclusive
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$alpha` is out of bounds or either parameter
+ # is the wrong type
+ def rgba(*args)
+ case args.size
+ when 2
+ color, alpha = args
+
+ assert_type color, :Color, :color
+ assert_type alpha, :Number, :alpha
+ check_alpha_unit alpha, 'rgba'
+
+ color.with(:alpha => alpha.value)
+ when 4
+ red, green, blue, alpha = args
+ rgba(rgb(red, green, blue), alpha)
+ else
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
+ end
+ end
+ declare :rgba, [:red, :green, :blue, :alpha]
+ declare :rgba, [:color, :alpha]
+
+ # Creates a {Sass::Script::Value::Color Color} from hue, saturation, and
+ # lightness values. Uses the algorithm from the [CSS3 spec][].
+ #
+ # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color
+ #
+ # @see #hsla
+ # @overload hsl($hue, $saturation, $lightness)
+ # @param $hue [Sass::Script::Value::Number] The hue of the color. Should be
+ # between 0 and 360 degrees, inclusive
+ # @param $saturation [Sass::Script::Value::Number] The saturation of the
+ # color. Must be between `0%` and `100%`, inclusive
+ # @param $lightness [Sass::Script::Value::Number] The lightness of the
+ # color. Must be between `0%` and `100%`, inclusive
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$saturation` or `$lightness` are out of bounds
+ # or any parameter is the wrong type
+ def hsl(hue, saturation, lightness)
+ hsla(hue, saturation, lightness, number(1))
+ end
+ declare :hsl, [:hue, :saturation, :lightness]
+
+ # Creates a {Sass::Script::Value::Color Color} from hue,
+ # saturation, lightness, and alpha values. Uses the algorithm from
+ # the [CSS3 spec][].
+ #
+ # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color
+ #
+ # @see #hsl
+ # @overload hsla($hue, $saturation, $lightness, $alpha)
+ # @param $hue [Sass::Script::Value::Number] The hue of the color. Should be
+ # between 0 and 360 degrees, inclusive
+ # @param $saturation [Sass::Script::Value::Number] The saturation of the
+ # color. Must be between `0%` and `100%`, inclusive
+ # @param $lightness [Sass::Script::Value::Number] The lightness of the
+ # color. Must be between `0%` and `100%`, inclusive
+ # @param $alpha [Sass::Script::Value::Number] The opacity of the color. Must
+ # be between 0 and 1, inclusive
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$saturation`, `$lightness`, or `$alpha` are out
+ # of bounds or any parameter is the wrong type
+ def hsla(hue, saturation, lightness, alpha)
+ assert_type hue, :Number, :hue
+ assert_type saturation, :Number, :saturation
+ assert_type lightness, :Number, :lightness
+ assert_type alpha, :Number, :alpha
+ check_alpha_unit alpha, 'hsla'
+
+ h = hue.value
+ s = saturation.value
+ l = lightness.value
+
+ # Don't store the string representation for function-created colors, both
+ # because it's not very useful and because some functions aren't supported
+ # on older browsers.
+ Sass::Script::Value::Color.new(
+ :hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
+ end
+ declare :hsla, [:hue, :saturation, :lightness, :alpha]
+
+ # Gets the red component of a color. Calculated from HSL where necessary via
+ # [this algorithm][hsl-to-rgb].
+ #
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
+ #
+ # @overload red($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The red component, between 0 and 255
+ # inclusive
+ # @raise [ArgumentError] if `$color` isn't a color
+ def red(color)
+ assert_type color, :Color, :color
+ number(color.red)
+ end
+ declare :red, [:color]
+
+ # Gets the green component of a color. Calculated from HSL where necessary
+ # via [this algorithm][hsl-to-rgb].
+ #
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
+ #
+ # @overload green($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The green component, between 0 and
+ # 255 inclusive
+ # @raise [ArgumentError] if `$color` isn't a color
+ def green(color)
+ assert_type color, :Color, :color
+ number(color.green)
+ end
+ declare :green, [:color]
+
+ # Gets the blue component of a color. Calculated from HSL where necessary
+ # via [this algorithm][hsl-to-rgb].
+ #
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
+ #
+ # @overload blue($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The blue component, between 0 and
+ # 255 inclusive
+ # @raise [ArgumentError] if `$color` isn't a color
+ def blue(color)
+ assert_type color, :Color, :color
+ number(color.blue)
+ end
+ declare :blue, [:color]
+
+ # Returns the hue component of a color. See [the CSS3 HSL
+ # specification][hsl]. Calculated from RGB where necessary via [this
+ # algorithm][rgb-to-hsl].
+ #
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
+ #
+ # @overload hue($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The hue component, between 0deg and
+ # 360deg
+ # @raise [ArgumentError] if `$color` isn't a color
+ def hue(color)
+ assert_type color, :Color, :color
+ number(color.hue, "deg")
+ end
+ declare :hue, [:color]
+
+ # Returns the saturation component of a color. See [the CSS3 HSL
+ # specification][hsl]. Calculated from RGB where necessary via [this
+ # algorithm][rgb-to-hsl].
+ #
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
+ #
+ # @overload saturation($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The saturation component, between 0%
+ # and 100%
+ # @raise [ArgumentError] if `$color` isn't a color
+ def saturation(color)
+ assert_type color, :Color, :color
+ number(color.saturation, "%")
+ end
+ declare :saturation, [:color]
+
+ # Returns the lightness component of a color. See [the CSS3 HSL
+ # specification][hsl]. Calculated from RGB where necessary via [this
+ # algorithm][rgb-to-hsl].
+ #
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
+ #
+ # @overload lightness($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The lightness component, between 0%
+ # and 100%
+ # @raise [ArgumentError] if `$color` isn't a color
+ def lightness(color)
+ assert_type color, :Color, :color
+ number(color.lightness, "%")
+ end
+ declare :lightness, [:color]
+
+ # Returns the alpha component (opacity) of a color. This is 1 unless
+ # otherwise specified.
+ #
+ # This function also supports the proprietary Microsoft `alpha(opacity=20)`
+ # syntax as a special case.
+ #
+ # @overload alpha($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The alpha component, between 0 and 1
+ # @raise [ArgumentError] if `$color` isn't a color
+ def alpha(*args)
+ if args.all? do |a|
+ a.is_a?(Sass::Script::Value::String) && a.type == :identifier &&
+ a.value =~ /^[a-zA-Z]+\s*=/
+ end
+ # Support the proprietary MS alpha() function
+ return identifier("alpha(#{args.map {|a| a.to_s}.join(", ")})")
+ end
+
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 1)") if args.size != 1
+
+ assert_type args.first, :Color, :color
+ number(args.first.alpha)
+ end
+ declare :alpha, [:color]
+
+ # Returns the alpha component (opacity) of a color. This is 1 unless
+ # otherwise specified.
+ #
+ # @overload opacity($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Number] The alpha component, between 0 and 1
+ # @raise [ArgumentError] if `$color` isn't a color
+ def opacity(color)
+ if color.is_a?(Sass::Script::Value::Number)
+ return identifier("opacity(#{color})")
+ end
+ assert_type color, :Color, :color
+ number(color.alpha)
+ end
+ declare :opacity, [:color]
+
+ # Makes a color more opaque. Takes a color and a number between 0 and 1, and
+ # returns a color with the opacity increased by that amount.
+ #
+ # @see #transparentize
+ # @example
+ # opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
+ # opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
+ # @overload opacify($color, $amount)
+ # @param $color [Sass::Script::Value::Color]
+ # @param $amount [Sass::Script::Value::Number] The amount to increase the
+ # opacity by, between 0 and 1
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
+ # is the wrong type
+ def opacify(color, amount)
+ _adjust(color, amount, :alpha, 0..1, :+)
+ end
+ declare :opacify, [:color, :amount]
+
+ alias_method :fade_in, :opacify
+ declare :fade_in, [:color, :amount]
+
+ # Makes a color more transparent. Takes a color and a number between 0 and
+ # 1, and returns a color with the opacity decreased by that amount.
+ #
+ # @see #opacify
+ # @example
+ # transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
+ # transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
+ # @overload transparentize($color, $amount)
+ # @param $color [Sass::Script::Value::Color]
+ # @param $amount [Sass::Script::Value::Number] The amount to decrease the
+ # opacity by, between 0 and 1
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
+ # is the wrong type
+ def transparentize(color, amount)
+ _adjust(color, amount, :alpha, 0..1, :-)
+ end
+ declare :transparentize, [:color, :amount]
+
+ alias_method :fade_out, :transparentize
+ declare :fade_out, [:color, :amount]
+
+ # Makes a color lighter. Takes a color and a number between `0%` and `100%`,
+ # and returns a color with the lightness increased by that amount.
+ #
+ # @see #darken
+ # @example
+ # lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
+ # lighten(#800, 20%) => #e00
+ # @overload lighten($color, $amount)
+ # @param $color [Sass::Script::Value::Color]
+ # @param $amount [Sass::Script::Value::Number] The amount to increase the
+ # lightness by, between `0%` and `100%`
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
+ # is the wrong type
+ def lighten(color, amount)
+ _adjust(color, amount, :lightness, 0..100, :+, "%")
+ end
+ declare :lighten, [:color, :amount]
+
+ # Makes a color darker. Takes a color and a number between 0% and 100%, and
+ # returns a color with the lightness decreased by that amount.
+ #
+ # @see #lighten
+ # @example
+ # darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
+ # darken(#800, 20%) => #200
+ # @overload darken($color, $amount)
+ # @param $color [Sass::Script::Value::Color]
+ # @param $amount [Sass::Script::Value::Number] The amount to decrease the
+ # lightness by, between `0%` and `100%`
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
+ # is the wrong type
+ def darken(color, amount)
+ _adjust(color, amount, :lightness, 0..100, :-, "%")
+ end
+ declare :darken, [:color, :amount]
+
+ # Makes a color more saturated. Takes a color and a number between 0% and
+ # 100%, and returns a color with the saturation increased by that amount.
+ #
+ # @see #desaturate
+ # @example
+ # saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
+ # saturate(#855, 20%) => #9e3f3f
+ # @overload saturate($color, $amount)
+ # @param $color [Sass::Script::Value::Color]
+ # @param $amount [Sass::Script::Value::Number] The amount to increase the
+ # saturation by, between `0%` and `100%`
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
+ # is the wrong type
+ def saturate(color, amount = nil)
+ # Support the filter effects definition of saturate.
+ # https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
+ return identifier("saturate(#{color})") if amount.nil?
+ _adjust(color, amount, :saturation, 0..100, :+, "%")
+ end
+ declare :saturate, [:color, :amount]
+ declare :saturate, [:amount]
+
+ # Makes a color less saturated. Takes a color and a number between 0% and
+ # 100%, and returns a color with the saturation decreased by that value.
+ #
+ # @see #saturate
+ # @example
+ # desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
+ # desaturate(#855, 20%) => #726b6b
+ # @overload desaturate($color, $amount)
+ # @param $color [Sass::Script::Value::Color]
+ # @param $amount [Sass::Script::Value::Number] The amount to decrease the
+ # saturation by, between `0%` and `100%`
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
+ # is the wrong type
+ def desaturate(color, amount)
+ _adjust(color, amount, :saturation, 0..100, :-, "%")
+ end
+ declare :desaturate, [:color, :amount]
+
+ # Changes the hue of a color. Takes a color and a number of degrees (usually
+ # between `-360deg` and `360deg`), and returns a color with the hue rotated
+ # along the color wheel by that amount.
+ #
+ # @example
+ # adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
+ # adjust-hue(hsl(120, 30%, 90%), -60deg) => hsl(60, 30%, 90%)
+ # adjust-hue(#811, 45deg) => #886a11
+ # @overload adjust_hue($color, $degrees)
+ # @param $color [Sass::Script::Value::Color]
+ # @param $degrees [Sass::Script::Value::Number] The number of degrees to
+ # rotate the hue
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if either parameter is the wrong type
+ def adjust_hue(color, degrees)
+ assert_type color, :Color, :color
+ assert_type degrees, :Number, :degrees
+ color.with(:hue => color.hue + degrees.value)
+ end
+ declare :adjust_hue, [:color, :degrees]
+
+ # Converts a color into the format understood by IE filters.
+ #
+ # @example
+ # ie-hex-str(#abc) => #FFAABBCC
+ # ie-hex-str(#3322BB) => #FF3322BB
+ # ie-hex-str(rgba(0, 255, 0, 0.5)) => #8000FF00
+ # @overload ie_hex_str($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::String] The IE-formatted string
+ # representation of the color
+ # @raise [ArgumentError] if `$color` isn't a color
+ def ie_hex_str(color)
+ assert_type color, :Color, :color
+ alpha = (color.alpha * 255).round.to_s(16).rjust(2, '0')
+ identifier("##{alpha}#{color.send(:hex_str)[1..-1]}".upcase)
+ end
+ declare :ie_hex_str, [:color]
+
+ # Increases or decreases one or more properties of a color. This can change
+ # the red, green, blue, hue, saturation, value, and alpha properties. The
+ # properties are specified as keyword arguments, and are added to or
+ # subtracted from the color's current value for that property.
+ #
+ # All properties are optional. You can't specify both RGB properties
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`,
+ # `$value`) at the same time.
+ #
+ # @example
+ # adjust-color(#102030, $blue: 5) => #102035
+ # adjust-color(#102030, $red: -5, $blue: 5) => #0b2035
+ # adjust-color(hsl(25, 100%, 80%), $lightness: -30%, $alpha: -0.4) => hsla(25, 100%, 50%, 0.6)
+ # @comment
+ # rubocop:disable LineLength
+ # @overload adjust_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness],
[$alpha])
+ # @comment
+ # rubocop:disable LineLength
+ # @param $color [Sass::Script::Value::Color]
+ # @param $red [Sass::Script::Value::Number] The adjustment to make on the
+ # red component, between -255 and 255 inclusive
+ # @param $green [Sass::Script::Value::Number] The adjustment to make on the
+ # green component, between -255 and 255 inclusive
+ # @param $blue [Sass::Script::Value::Number] The adjustment to make on the
+ # blue component, between -255 and 255 inclusive
+ # @param $hue [Sass::Script::Value::Number] The adjustment to make on the
+ # hue component, in degrees
+ # @param $saturation [Sass::Script::Value::Number] The adjustment to make on
+ # the saturation component, between `-100%` and `100%` inclusive
+ # @param $lightness [Sass::Script::Value::Number] The adjustment to make on
+ # the lightness component, between `-100%` and `100%` inclusive
+ # @param $alpha [Sass::Script::Value::Number] The adjustment to make on the
+ # alpha component, between -1 and 1 inclusive
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
+ # bounds, or if RGB properties and HSL properties are adjusted at the
+ # same time
+ def adjust_color(color, kwargs)
+ assert_type color, :Color, :color
+ with = Sass::Util.map_hash(
+ "red" => [-255..255, ""],
+ "green" => [-255..255, ""],
+ "blue" => [-255..255, ""],
+ "hue" => nil,
+ "saturation" => [-100..100, "%"],
+ "lightness" => [-100..100, "%"],
+ "alpha" => [-1..1, ""]
+ ) do |name, (range, units)|
+
+ val = kwargs.delete(name)
+ next unless val
+ assert_type val, :Number, name
+ Sass::Util.check_range("$#{name}: Amount", range, val, units) if range
+ adjusted = color.send(name) + val.value
+ adjusted = [0, Sass::Util.restrict(adjusted, range)].max if range
+ [name.to_sym, adjusted]
+ end
+
+ unless kwargs.empty?
+ name, val = kwargs.to_a.first
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
+ end
+
+ color.with(with)
+ end
+ declare :adjust_color, [:color], :var_kwargs => true
+
+ # Fluidly scales one or more properties of a color. Unlike
+ # \{#adjust_color adjust-color}, which changes a color's properties by fixed
+ # amounts, \{#scale_color scale-color} fluidly changes them based on how
+ # high or low they already are. That means that lightening an already-light
+ # color with \{#scale_color scale-color} won't change the lightness much,
+ # but lightening a dark color by the same amount will change it more
+ # dramatically. This has the benefit of making `scale-color($color, ...)`
+ # have a similar effect regardless of what `$color` is.
+ #
+ # For example, the lightness of a color can be anywhere between `0%` and
+ # `100%`. If `scale-color($color, $lightness: 40%)` is called, the resulting
+ # color's lightness will be 40% of the way between its original lightness
+ # and 100. If `scale-color($color, $lightness: -40%)` is called instead, the
+ # lightness will be 40% of the way between the original and 0.
+ #
+ # This can change the red, green, blue, saturation, value, and alpha
+ # properties. The properties are specified as keyword arguments. All
+ # arguments should be percentages between `0%` and `100%`.
+ #
+ # All properties are optional. You can't specify both RGB properties
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$saturation`, `$value`)
+ # at the same time.
+ #
+ # @example
+ # scale-color(hsl(120, 70%, 80%), $lightness: 50%) => hsl(120, 70%, 90%)
+ # scale-color(rgb(200, 150%, 170%), $green: -40%, $blue: 70%) => rgb(200, 90, 229)
+ # scale-color(hsl(200, 70%, 80%), $saturation: -90%, $alpha: -30%) => hsla(200, 7%, 80%, 0.7)
+ # @comment
+ # rubocop:disable LineLength
+ # @overload scale_color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha])
+ # @comment
+ # rubocop:disable LineLength
+ # @param $color [Sass::Script::Value::Color]
+ # @param $red [Sass::Script::Value::Number]
+ # @param $green [Sass::Script::Value::Number]
+ # @param $blue [Sass::Script::Value::Number]
+ # @param $saturation [Sass::Script::Value::Number]
+ # @param $lightness [Sass::Script::Value::Number]
+ # @param $alpha [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
+ # bounds, or if RGB properties and HSL properties are adjusted at the
+ # same time
+ def scale_color(color, kwargs)
+ assert_type color, :Color, :color
+ with = Sass::Util.map_hash(
+ "red" => 255,
+ "green" => 255,
+ "blue" => 255,
+ "saturation" => 100,
+ "lightness" => 100,
+ "alpha" => 1
+ ) do |name, max|
+
+ val = kwargs.delete(name)
+ next unless val
+ assert_type val, :Number, name
+ assert_unit val, '%', name
+ Sass::Util.check_range("$#{name}: Amount", -100..100, val, '%')
+
+ current = color.send(name)
+ scale = val.value / 100.0
+ diff = scale > 0 ? max - current : current
+ [name.to_sym, current + diff * scale]
+ end
+
+ unless kwargs.empty?
+ name, val = kwargs.to_a.first
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
+ end
+
+ color.with(with)
+ end
+ declare :scale_color, [:color], :var_kwargs => true
+
+ # Changes one or more properties of a color. This can change the red, green,
+ # blue, hue, saturation, value, and alpha properties. The properties are
+ # specified as keyword arguments, and replace the color's current value for
+ # that property.
+ #
+ # All properties are optional. You can't specify both RGB properties
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`,
+ # `$value`) at the same time.
+ #
+ # @example
+ # change-color(#102030, $blue: 5) => #102005
+ # change-color(#102030, $red: 120, $blue: 5) => #782005
+ # change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8) => hsla(25, 100%, 40%, 0.8)
+ # @comment
+ # rubocop:disable LineLength
+ # @overload change_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness],
[$alpha])
+ # @comment
+ # rubocop:disable LineLength
+ # @param $color [Sass::Script::Value::Color]
+ # @param $red [Sass::Script::Value::Number] The new red component for the
+ # color, within 0 and 255 inclusive
+ # @param $green [Sass::Script::Value::Number] The new green component for
+ # the color, within 0 and 255 inclusive
+ # @param $blue [Sass::Script::Value::Number] The new blue component for the
+ # color, within 0 and 255 inclusive
+ # @param $hue [Sass::Script::Value::Number] The new hue component for the
+ # color, in degrees
+ # @param $saturation [Sass::Script::Value::Number] The new saturation
+ # component for the color, between `0%` and `100%` inclusive
+ # @param $lightness [Sass::Script::Value::Number] The new lightness
+ # component for the color, within `0%` and `100%` inclusive
+ # @param $alpha [Sass::Script::Value::Number] The new alpha component for
+ # the color, within 0 and 1 inclusive
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
+ # bounds, or if RGB properties and HSL properties are adjusted at the
+ # same time
+ def change_color(color, kwargs)
+ assert_type color, :Color, :color
+ with = Sass::Util.map_hash(
+ 'red' => ['Red value', 0..255],
+ 'green' => ['Green value', 0..255],
+ 'blue' => ['Blue value', 0..255],
+ 'hue' => [],
+ 'saturation' => ['Saturation', 0..100, '%'],
+ 'lightness' => ['Lightness', 0..100, '%'],
+ 'alpha' => ['Alpha channel', 0..1]
+ ) do |name, (desc, range, unit)|
+ val = kwargs.delete(name)
+ next unless val
+ assert_type val, :Number, name
+
+ if range
+ val = Sass::Util.check_range(desc, range, val, unit)
+ else
+ val = val.value
+ end
+
+ [name.to_sym, val]
+ end
+
+ unless kwargs.empty?
+ name, val = kwargs.to_a.first
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
+ end
+
+ color.with(with)
+ end
+ declare :change_color, [:color], :var_kwargs => true
+
+ # Mixes two colors together. Specifically, takes the average of each of the
+ # RGB components, optionally weighted by the given percentage. The opacity
+ # of the colors is also considered when weighting the components.
+ #
+ # The weight specifies the amount of the first color that should be included
+ # in the returned color. The default, `50%`, means that half the first color
+ # and half the second color should be used. `25%` means that a quarter of
+ # the first color and three quarters of the second color should be used.
+ #
+ # @example
+ # mix(#f00, #00f) => #7f007f
+ # mix(#f00, #00f, 25%) => #3f00bf
+ # mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
+ # @overload mix($color1, $color2, $weight: 50%)
+ # @param $color1 [Sass::Script::Value::Color]
+ # @param $color2 [Sass::Script::Value::Color]
+ # @param $weight [Sass::Script::Value::Number] The relative weight of each
+ # color. Closer to `0%` gives more weight to `$color1`, closer to `100%`
+ # gives more weight to `$color2`
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$weight` is out of bounds or any parameter is
+ # the wrong type
+ def mix(color1, color2, weight = number(50))
+ assert_type color1, :Color, :color1
+ assert_type color2, :Color, :color2
+ assert_type weight, :Number, :weight
+
+ Sass::Util.check_range("Weight", 0..100, weight, '%')
+
+ # This algorithm factors in both the user-provided weight (w) and the
+ # difference between the alpha values of the two colors (a) to decide how
+ # to perform the weighted average of the two RGB values.
+ #
+ # It works by first normalizing both parameters to be within [-1, 1],
+ # where 1 indicates "only use color1", -1 indicates "only use color2", and
+ # all values in between indicated a proportionately weighted average.
+ #
+ # Once we have the normalized variables w and a, we apply the formula
+ # (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1.
+ # This formula has two especially nice properties:
+ #
+ # * When either w or a are -1 or 1, the combined weight is also that number
+ # (cases where w * a == -1 are undefined, and handled as a special case).
+ #
+ # * When a is 0, the combined weight is w, and vice versa.
+ #
+ # Finally, the weight of color1 is renormalized to be within [0, 1]
+ # and the weight of color2 is given by 1 minus the weight of color1.
+ p = (weight.value / 100.0).to_f
+ w = p * 2 - 1
+ a = color1.alpha - color2.alpha
+
+ w1 = ((w * a == -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0
+ w2 = 1 - w1
+
+ rgba = color1.rgb.zip(color2.rgb).map {|v1, v2| v1 * w1 + v2 * w2}
+ rgba << color1.alpha * p + color2.alpha * (1 - p)
+ rgb_color(*rgba)
+ end
+ declare :mix, [:color1, :color2]
+ declare :mix, [:color1, :color2, :weight]
+
+ # Converts a color to grayscale. This is identical to `desaturate(color,
+ # 100%)`.
+ #
+ # @see #desaturate
+ # @overload grayscale($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$color` isn't a color
+ def grayscale(color)
+ if color.is_a?(Sass::Script::Value::Number)
+ return identifier("grayscale(#{color})")
+ end
+ desaturate color, number(100)
+ end
+ declare :grayscale, [:color]
+
+ # Returns the complement of a color. This is identical to `adjust-hue(color,
+ # 180deg)`.
+ #
+ # @see #adjust_hue #adjust-hue
+ # @overload complement($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$color` isn't a color
+ def complement(color)
+ adjust_hue color, number(180)
+ end
+ declare :complement, [:color]
+
+ # Returns the inverse (negative) of a color. The red, green, and blue values
+ # are inverted, while the opacity is left alone.
+ #
+ # @overload invert($color)
+ # @param $color [Sass::Script::Value::Color]
+ # @return [Sass::Script::Value::Color]
+ # @raise [ArgumentError] if `$color` isn't a color
+ def invert(color)
+ if color.is_a?(Sass::Script::Value::Number)
+ return identifier("invert(#{color})")
+ end
+
+ assert_type color, :Color, :color
+ color.with(
+ :red => (255 - color.red),
+ :green => (255 - color.green),
+ :blue => (255 - color.blue))
+ end
+ declare :invert, [:color]
+
+ # Removes quotes from a string. If the string is already unquoted, this will
+ # return it unmodified.
+ #
+ # @see #quote
+ # @example
+ # unquote("foo") => foo
+ # unquote(foo) => foo
+ # @overload unquote($string)
+ # @param $string [Sass::Script::Value::String]
+ # @return [Sass::Script::Value::String]
+ # @raise [ArgumentError] if `$string` isn't a string
+ def unquote(string)
+ if string.is_a?(Sass::Script::Value::String) && string.type != :identifier
+ identifier(string.value)
+ else
+ string
+ end
+ end
+ declare :unquote, [:string]
+
+ # Add quotes to a string if the string isn't quoted,
+ # or returns the same string if it is.
+ #
+ # @see #unquote
+ # @example
+ # quote("foo") => "foo"
+ # quote(foo) => "foo"
+ # @overload quote($string)
+ # @param $string [Sass::Script::Value::String]
+ # @return [Sass::Script::Value::String]
+ # @raise [ArgumentError] if `$string` isn't a string
+ def quote(string)
+ assert_type string, :String, :string
+ if string.type != :string
+ quoted_string(string.value)
+ else
+ string
+ end
+ end
+ declare :quote, [:string]
+
+ # Returns the number of characters in a string.
+ #
+ # @example
+ # str-length("foo") => 3
+ # @overload str_length($string)
+ # @param $string [Sass::Script::Value::String]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if `$string` isn't a string
+ def str_length(string)
+ assert_type string, :String, :string
+ number(string.value.size)
+ end
+ declare :str_length, [:string]
+
+ # Inserts `$insert` into `$string` at `$index`.
+ #
+ # Note that unlike some languages, the first character in a Sass string is
+ # number 1, the second number 2, and so forth.
+ #
+ # @example
+ # str-insert("abcd", "X", 1) => "Xabcd"
+ # str-insert("abcd", "X", 4) => "abcXd"
+ # str-insert("abcd", "X", 5) => "abcdX"
+ #
+ # @overload str_insert($string, $insert, $index)
+ # @param $string [Sass::Script::Value::String]
+ # @param $insert [Sass::Script::Value::String]
+ # @param $index [Sass::Script::Value::Number] The position at which
+ # `$insert` will be inserted. Negative indices count from the end of
+ # `$string`. An index that's outside the bounds of the string will insert
+ # `$insert` at the front or back of the string
+ # @return [Sass::Script::Value::String] The result string. This will be
+ # quoted if and only if `$string` was quoted
+ # @raise [ArgumentError] if any parameter is the wrong type
+ def str_insert(original, insert, index)
+ assert_type original, :String, :string
+ assert_type insert, :String, :insert
+ assert_integer index, :index
+ assert_unit index, nil, :index
+ insertion_point = if index.value > 0
+ [index.value - 1, original.value.size].min
+ else
+ [index.value, -original.value.size - 1].max
+ end
+ result = original.value.dup.insert(insertion_point, insert.value)
+ Sass::Script::Value::String.new(result, original.type)
+ end
+ declare :str_insert, [:string, :insert, :index]
+
+ # Returns the index of the first occurrence of `$substring` in `$string`. If
+ # there is no such occurrence, returns `null`.
+ #
+ # Note that unlike some languages, the first character in a Sass string is
+ # number 1, the second number 2, and so forth.
+ #
+ # @example
+ # str-index(abcd, a) => 1
+ # str-index(abcd, ab) => 1
+ # str-index(abcd, X) => null
+ # str-index(abcd, c) => 3
+ #
+ # @overload str_index($string, $substring)
+ # @param $string [Sass::Script::Value::String]
+ # @param $substring [Sass::Script::Value::String]
+ # @return [Sass::Script::Value::Number, Sass::Script::Value::Null]
+ # @raise [ArgumentError] if any parameter is the wrong type
+ def str_index(string, substring)
+ assert_type string, :String, :string
+ assert_type substring, :String, :substring
+ index = string.value.index(substring.value)
+ index ? number(index + 1) : null
+ end
+ declare :str_index, [:string, :substring]
+
+ # Extracts a substring from `$string`. The substring will begin at index
+ # `$start-at` and ends at index `$end-at`.
+ #
+ # Note that unlike some languages, the first character in a Sass string is
+ # number 1, the second number 2, and so forth.
+ #
+ # @example
+ # str-slice("abcd", 2, 3) => "bc"
+ # str-slice("abcd", 2) => "bcd"
+ # str-slice("abcd", -3, -2) => "bc"
+ # str-slice("abcd", 2, -2) => "bc"
+ #
+ # @overload str_slice($string, $start-at, $end-at: -1)
+ # @param $start-at [Sass::Script::Value::Number] The index of the first
+ # character of the substring. If this is negative, it counts from the end
+ # of `$string`
+ # @param $end-before [Sass::Script::Value::Number] The index of the last
+ # character of the substring. If this is negative, it counts from the end
+ # of `$string`. Defaults to -1
+ # @return [Sass::Script::Value::String] The substring. This will be quoted
+ # if and only if `$string` was quoted
+ # @raise [ArgumentError] if any parameter is the wrong type
+ def str_slice(string, start_at, end_at = nil)
+ assert_type string, :String, :string
+ assert_unit start_at, nil, "start-at"
+
+ end_at = number(-1) if end_at.nil?
+ assert_unit end_at, nil, "end-at"
+
+ return Sass::Script::Value::String.new("", string.type) if end_at.value == 0
+ s = start_at.value > 0 ? start_at.value - 1 : start_at.value
+ e = end_at.value > 0 ? end_at.value - 1 : end_at.value
+ s = string.value.length + s if s < 0
+ s = 0 if s < 0
+ e = string.value.length + e if e < 0
+ e = 0 if s < 0
+ extracted = string.value.slice(s..e)
+ Sass::Script::Value::String.new(extracted || "", string.type)
+ end
+ declare :str_slice, [:string, :start_at]
+ declare :str_slice, [:string, :start_at, :end_at]
+
+ # Converts a string to upper case.
+ #
+ # @example
+ # to-upper-case(abcd) => ABCD
+ #
+ # @overload to_upper_case($string)
+ # @param $string [Sass::Script::Value::String]
+ # @return [Sass::Script::Value::String]
+ # @raise [ArgumentError] if `$string` isn't a string
+ def to_upper_case(string)
+ assert_type string, :String, :string
+ Sass::Script::Value::String.new(string.value.upcase, string.type)
+ end
+ declare :to_upper_case, [:string]
+
+ # Convert a string to lower case,
+ #
+ # @example
+ # to-lower-case(ABCD) => abcd
+ #
+ # @overload to_lower_case($string)
+ # @param $string [Sass::Script::Value::String]
+ # @return [Sass::Script::Value::String]
+ # @raise [ArgumentError] if `$string` isn't a string
+ def to_lower_case(string)
+ assert_type string, :String, :string
+ Sass::Script::Value::String.new(string.value.downcase, string.type)
+ end
+ declare :to_lower_case, [:string]
+
+ # Returns the type of a value.
+ #
+ # @example
+ # type-of(100px) => number
+ # type-of(asdf) => string
+ # type-of("asdf") => string
+ # type-of(true) => bool
+ # type-of(#fff) => color
+ # type-of(blue) => color
+ # @overload type_of($value)
+ # @param $value [Sass::Script::Value::Base] The value to inspect
+ # @return [Sass::Script::Value::String] The unquoted string name of the
+ # value's type
+ def type_of(value)
+ identifier(value.class.name.gsub(/Sass::Script::Value::/, '').downcase)
+ end
+ declare :type_of, [:value]
+
+ # Returns whether a feature exists in the current Sass runtime.
+ #
+ # The following features are supported:
+ #
+ # * `global-variable-shadowing` indicates that a local variable will shadow
+ # a global variable unless `!global` is used.
+ #
+ # * `extend-selector-pseudoclass` indicates that ` extend` will reach into
+ # selector pseudoclasses like `:not`.
+ #
+ # * `units-level-3` indicates full support for unit arithmetic using units
+ # defined in the [Values and Units Level 3][] spec.
+ #
+ # [Values and Units Level 3]: http://www.w3.org/TR/css3-values/
+ #
+ # * `at-error` indicates that the Sass ` error` directive is supported.
+ #
+ # @example
+ # feature-exists(some-feature-that-exists) => true
+ # feature-exists(what-is-this-i-dont-know) => false
+ #
+ # @overload feature_exists($feature)
+ # @param $feature [Sass::Script::Value::String] The name of the feature
+ # @return [Sass::Script::Value::Bool] Whether the feature is supported in this version of Sass
+ # @raise [ArgumentError] if `$feature` isn't a string
+ def feature_exists(feature)
+ assert_type feature, :String, :feature
+ bool(Sass.has_feature?(feature.value))
+ end
+ declare :feature_exists, [:feature]
+
+ # Returns the unit(s) associated with a number. Complex units are sorted in
+ # alphabetical order by numerator and denominator.
+ #
+ # @example
+ # unit(100) => ""
+ # unit(100px) => "px"
+ # unit(3em) => "em"
+ # unit(10px * 5em) => "em*px"
+ # unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem"
+ # @overload unit($number)
+ # @param $number [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::String] The unit(s) of the number, as a
+ # quoted string
+ # @raise [ArgumentError] if `$number` isn't a number
+ def unit(number)
+ assert_type number, :Number, :number
+ quoted_string(number.unit_str)
+ end
+ declare :unit, [:number]
+
+ # Returns whether a number has units.
+ #
+ # @example
+ # unitless(100) => true
+ # unitless(100px) => false
+ # @overload unitless($number)
+ # @param $number [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Bool]
+ # @raise [ArgumentError] if `$number` isn't a number
+ def unitless(number)
+ assert_type number, :Number, :number
+ bool(number.unitless?)
+ end
+ declare :unitless, [:number]
+
+ # Returns whether two numbers can added, subtracted, or compared.
+ #
+ # @example
+ # comparable(2px, 1px) => true
+ # comparable(100px, 3em) => false
+ # comparable(10cm, 3mm) => true
+ # @overload comparable($number1, $number2)
+ # @param $number1 [Sass::Script::Value::Number]
+ # @param $number2 [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Bool]
+ # @raise [ArgumentError] if either parameter is the wrong type
+ def comparable(number1, number2)
+ assert_type number1, :Number, :number1
+ assert_type number2, :Number, :number2
+ bool(number1.comparable_to?(number2))
+ end
+ declare :comparable, [:number1, :number2]
+
+ # Converts a unitless number to a percentage.
+ #
+ # @example
+ # percentage(0.2) => 20%
+ # percentage(100px / 50px) => 200%
+ # @overload percentage($number)
+ # @param $number [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if `$number` isn't a unitless number
+ def percentage(number)
+ unless number.is_a?(Sass::Script::Value::Number) && number.unitless?
+ raise ArgumentError.new("$number: #{number.inspect} is not a unitless number")
+ end
+ number(number.value * 100, '%')
+ end
+ declare :percentage, [:number]
+
+ # Rounds a number to the nearest whole number.
+ #
+ # @example
+ # round(10.4px) => 10px
+ # round(10.6px) => 11px
+ # @overload round($number)
+ # @param $number [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if `$number` isn't a number
+ def round(number)
+ numeric_transformation(number) {|n| n.round}
+ end
+ declare :round, [:number]
+
+ # Rounds a number up to the next whole number.
+ #
+ # @example
+ # ceil(10.4px) => 11px
+ # ceil(10.6px) => 11px
+ # @overload ceil($number)
+ # @param $number [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if `$number` isn't a number
+ def ceil(number)
+ numeric_transformation(number) {|n| n.ceil}
+ end
+ declare :ceil, [:number]
+
+ # Rounds a number down to the previous whole number.
+ #
+ # @example
+ # floor(10.4px) => 10px
+ # floor(10.6px) => 10px
+ # @overload floor($number)
+ # @param $number [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if `$number` isn't a number
+ def floor(number)
+ numeric_transformation(number) {|n| n.floor}
+ end
+ declare :floor, [:number]
+
+ # Returns the absolute value of a number.
+ #
+ # @example
+ # abs(10px) => 10px
+ # abs(-10px) => 10px
+ # @overload abs($number)
+ # @param $number [Sass::Script::Value::Number]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if `$number` isn't a number
+ def abs(number)
+ numeric_transformation(number) {|n| n.abs}
+ end
+ declare :abs, [:number]
+
+ # Finds the minimum of several numbers. This function takes any number of
+ # arguments.
+ #
+ # @example
+ # min(1px, 4px) => 1px
+ # min(5em, 3em, 4em) => 3em
+ # @overload min($numbers...)
+ # @param $numbers [[Sass::Script::Value::Number]]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if any argument isn't a number, or if not all of
+ # the arguments have comparable units
+ def min(*numbers)
+ numbers.each {|n| assert_type n, :Number}
+ numbers.inject {|min, num| min.lt(num).to_bool ? min : num}
+ end
+ declare :min, [], :var_args => :true
+
+ # Finds the maximum of several numbers. This function takes any number of
+ # arguments.
+ #
+ # @example
+ # max(1px, 4px) => 4px
+ # max(5em, 3em, 4em) => 5em
+ # @overload max($numbers...)
+ # @param $numbers [[Sass::Script::Value::Number]]
+ # @return [Sass::Script::Value::Number]
+ # @raise [ArgumentError] if any argument isn't a number, or if not all of
+ # the arguments have comparable units
+ def max(*values)
+ values.each {|v| assert_type v, :Number}
+ values.inject {|max, val| max.gt(val).to_bool ? max : val}
+ end
+ declare :max, [], :var_args => :true
+
+ # Return the length of a list.
+ #
+ # This can return the number of pairs in a map as well.
+ #
+ # @example
+ # length(10px) => 1
+ # length(10px 20px 30px) => 3
+ # length((width: 10px, height: 20px)) => 2
+ # @overload length($list)
+ # @param $list [Sass::Script::Value::Base]
+ # @return [Sass::Script::Value::Number]
+ def length(list)
+ number(list.to_a.size)
+ end
+ declare :length, [:list]
+
+ # Return a new list, based on the list provided, but with the nth
+ # element changed to the value given.
+ #
+ # Note that unlike some languages, the first item in a Sass list is number
+ # 1, the second number 2, and so forth.
+ #
+ # Negative index values address elements in reverse order, starting with the last element
+ # in the list.
+ #
+ # @example
+ # set-nth($list: 10px 20px 30px, $n: 2, $value: -20px) => 10px -20px 30px
+ # @overload set-nth($list, $n, $value)
+ # @param $list [Sass::Script::Value::Base] The list that will be copied, having the element
+ # at index `$n` changed.
+ # @param $n [Sass::Script::Value::Number] The index of the item to set.
+ # Negative indices count from the end of the list.
+ # @param $value [Sass::Script::Value::Base] The new value at index `$n`.
+ # @return [Sass::Script::Value::List]
+ # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length
+ # of `$list`
+ def set_nth(list, n, value)
+ assert_type n, :Number, :n
+ Sass::Script::Value::List.assert_valid_index(list, n)
+ index = n.to_i > 0 ? n.to_i - 1 : n.to_i
+ new_list = list.to_a.dup
+ new_list[index] = value
+ Sass::Script::Value::List.new(new_list, list.separator)
+ end
+ declare :set_nth, [:list, :n, :value]
+
+ # Gets the nth item in a list.
+ #
+ # Note that unlike some languages, the first item in a Sass list is number
+ # 1, the second number 2, and so forth.
+ #
+ # This can return the nth pair in a map as well.
+ #
+ # Negative index values address elements in reverse order, starting with the last element in
+ # the list.
+ #
+ # @example
+ # nth(10px 20px 30px, 1) => 10px
+ # nth((Helvetica, Arial, sans-serif), 3) => sans-serif
+ # nth((width: 10px, length: 20px), 2) => length, 20px
+ # @overload nth($list, $n)
+ # @param $list [Sass::Script::Value::Base]
+ # @param $n [Sass::Script::Value::Number] The index of the item to get.
+ # Negative indices count from the end of the list.
+ # @return [Sass::Script::Value::Base]
+ # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length
+ # of `$list`
+ def nth(list, n)
+ assert_type n, :Number, :n
+ Sass::Script::Value::List.assert_valid_index(list, n)
+
+ index = n.to_i > 0 ? n.to_i - 1 : n.to_i
+ list.to_a[index]
+ end
+ declare :nth, [:list, :n]
+
+ # Joins together two lists into one.
+ #
+ # Unless `$separator` is passed, if one list is comma-separated and one is
+ # space-separated, the first parameter's separator is used for the resulting
+ # list. If both lists have fewer than two items, spaces are used for the
+ # resulting list.
+ #
+ # @example
+ # join(10px 20px, 30px 40px) => 10px 20px 30px 40px
+ # join((blue, red), (#abc, #def)) => blue, red, #abc, #def
+ # join(10px, 20px) => 10px 20px
+ # join(10px, 20px, comma) => 10px, 20px
+ # join((blue, red), (#abc, #def), space) => blue red #abc #def
+ # @overload join($list1, $list2, $separator: auto)
+ # @param $list1 [Sass::Script::Value::Base]
+ # @param $list2 [Sass::Script::Value::Base]
+ # @param $separator [Sass::Script::Value::String] The list separator to use.
+ # If this is `comma` or `space`, that separator will be used. If this is
+ # `auto` (the default), the separator is determined as explained above.
+ # @return [Sass::Script::Value::List]
+ def join(list1, list2, separator = identifier("auto"))
+ assert_type separator, :String, :separator
+ unless %w[auto space comma].include?(separator.value)
+ raise ArgumentError.new("Separator name must be space, comma, or auto")
+ end
+ sep = if separator.value == 'auto'
+ list1.separator || list2.separator || :space
+ else
+ separator.value.to_sym
+ end
+ list(list1.to_a + list2.to_a, sep)
+ end
+ declare :join, [:list1, :list2]
+ declare :join, [:list1, :list2, :separator]
+
+ # Appends a single value onto the end of a list.
+ #
+ # Unless the `$separator` argument is passed, if the list had only one item,
+ # the resulting list will be space-separated.
+ #
+ # @example
+ # append(10px 20px, 30px) => 10px 20px 30px
+ # append((blue, red), green) => blue, red, green
+ # append(10px 20px, 30px 40px) => 10px 20px (30px 40px)
+ # append(10px, 20px, comma) => 10px, 20px
+ # append((blue, red), green, space) => blue red green
+ # @overload append($list, $val, $separator: auto)
+ # @param $list [Sass::Script::Value::Base]
+ # @param $val [Sass::Script::Value::Base]
+ # @param $separator [Sass::Script::Value::String] The list separator to use.
+ # If this is `comma` or `space`, that separator will be used. If this is
+ # `auto` (the default), the separator is determined as explained above.
+ # @return [Sass::Script::Value::List]
+ def append(list, val, separator = identifier("auto"))
+ assert_type separator, :String, :separator
+ unless %w[auto space comma].include?(separator.value)
+ raise ArgumentError.new("Separator name must be space, comma, or auto")
+ end
+ sep = if separator.value == 'auto'
+ list.separator || :space
+ else
+ separator.value.to_sym
+ end
+ list(list.to_a + [val], sep)
+ end
+ declare :append, [:list, :val]
+ declare :append, [:list, :val, :separator]
+
+ # Combines several lists into a single multidimensional list. The nth value
+ # of the resulting list is a space separated list of the source lists' nth
+ # values.
+ #
+ # The length of the resulting list is the length of the
+ # shortest list.
+ #
+ # @example
+ # zip(1px 1px 3px, solid dashed solid, red green blue)
+ # => 1px solid red, 1px dashed green, 3px solid blue
+ # @overload zip($lists...)
+ # @param $lists [[Sass::Script::Value::Base]]
+ # @return [Sass::Script::Value::List]
+ def zip(*lists)
+ length = nil
+ values = []
+ lists.each do |list|
+ array = list.to_a
+ values << array.dup
+ length = length.nil? ? array.length : [length, array.length].min
+ end
+ values.each do |value|
+ value.slice!(length)
+ end
+ new_list_value = values.first.zip(*values[1..-1])
+ list(new_list_value.map {|list| list(list, :space)}, :comma)
+ end
+ declare :zip, [], :var_args => true
+
+ # Returns the position of a value within a list. If the value isn't found,
+ # returns `null` instead.
+ #
+ # Note that unlike some languages, the first item in a Sass list is number
+ # 1, the second number 2, and so forth.
+ #
+ # This can return the position of a pair in a map as well.
+ #
+ # @example
+ # index(1px solid red, solid) => 2
+ # index(1px solid red, dashed) => null
+ # index((width: 10px, height: 20px), (height 20px)) => 2
+ # @overload index($list, $value)
+ # @param $list [Sass::Script::Value::Base]
+ # @param $value [Sass::Script::Value::Base]
+ # @return [Sass::Script::Value::Number, Sass::Script::Value::Null] The
+ # 1-based index of `$value` in `$list`, or `null`
+ def index(list, value)
+ index = list.to_a.index {|e| e.eq(value).to_bool}
+ index ? number(index + 1) : null
+ end
+ declare :index, [:list, :value]
+
+ # Returns the separator of a list. If the list doesn't have a separator due
+ # to having fewer than two elements, returns `space`.
+ #
+ # @example
+ # list-separator(1px 2px 3px) => space
+ # list-separator(1px, 2px, 3px) => comma
+ # list-separator('foo') => space
+ # @overload list_separator($list)
+ # @param $list [Sass::Script::Value::Base]
+ # @return [Sass::Script::Value::String] `comma` or `space`
+ def list_separator(list)
+ identifier((list.separator || :space).to_s)
+ end
+ declare :separator, [:list]
+
+ # Returns the value in a map associated with the given key. If the map
+ # doesn't have such a key, returns `null`.
+ #
+ # @example
+ # map-get(("foo": 1, "bar": 2), "foo") => 1
+ # map-get(("foo": 1, "bar": 2), "bar") => 2
+ # map-get(("foo": 1, "bar": 2), "baz") => null
+ # @overload map_get($map, $key)
+ # @param $map [Sass::Script::Value::Map]
+ # @param $key [Sass::Script::Value::Base]
+ # @return [Sass::Script::Value::Base] The value indexed by `$key`, or `null`
+ # if the map doesn't contain the given key
+ # @raise [ArgumentError] if `$map` is not a map
+ def map_get(map, key)
+ assert_type map, :Map, :map
+ map.to_h[key] || null
+ end
+ declare :map_get, [:map, :key]
+
+ # Merges two maps together into a new map. Keys in `$map2` will take
+ # precedence over keys in `$map1`.
+ #
+ # This is the best way to add new values to a map.
+ #
+ # All keys in the returned map that also appear in `$map1` will have the
+ # same order as in `$map1`. New keys from `$map2` will be placed at the end
+ # of the map.
+ #
+ # @example
+ # map-merge(("foo": 1), ("bar": 2)) => ("foo": 1, "bar": 2)
+ # map-merge(("foo": 1, "bar": 2), ("bar": 3)) => ("foo": 1, "bar": 3)
+ # @overload map_merge($map1, $map2)
+ # @param $map1 [Sass::Script::Value::Map]
+ # @param $map2 [Sass::Script::Value::Map]
+ # @return [Sass::Script::Value::Map]
+ # @raise [ArgumentError] if either parameter is not a map
+ def map_merge(map1, map2)
+ assert_type map1, :Map, :map1
+ assert_type map2, :Map, :map2
+ map(map1.to_h.merge(map2.to_h))
+ end
+ declare :map_merge, [:map1, :map2]
+
+ # Returns a new map with keys removed.
+ #
+ # @example
+ # map-remove(("foo": 1, "bar": 2), "bar") => ("foo": 1)
+ # map-remove(("foo": 1, "bar": 2, "baz": 3), "bar", "baz") => ("foo": 1)
+ # map-remove(("foo": 1, "bar": 2), "baz") => ("foo": 1, "bar": 2)
+ # @overload map_remove($map, $keys...)
+ # @param $map [Sass::Script::Value::Map]
+ # @param $keys [[Sass::Script::Value::Base]]
+ # @return [Sass::Script::Value::Map]
+ # @raise [ArgumentError] if `$map` is not a map
+ def map_remove(map, *keys)
+ assert_type map, :Map, :map
+ hash = map.to_h.dup
+ hash.delete_if {|key, _| keys.include?(key)}
+ map(hash)
+ end
+ declare :map_remove, [:map, :key], :var_args => true
+
+ # Returns a list of all keys in a map.
+ #
+ # @example
+ # map-keys(("foo": 1, "bar": 2)) => "foo", "bar"
+ # @overload map_keys($map)
+ # @param $map [Map]
+ # @return [List] the list of keys, comma-separated
+ # @raise [ArgumentError] if `$map` is not a map
+ def map_keys(map)
+ assert_type map, :Map, :map
+ list(map.to_h.keys, :comma)
+ end
+ declare :map_keys, [:map]
+
+ # Returns a list of all values in a map. This list may include duplicate
+ # values, if multiple keys have the same value.
+ #
+ # @example
+ # map-values(("foo": 1, "bar": 2)) => 1, 2
+ # map-values(("foo": 1, "bar": 2, "baz": 1)) => 1, 2, 1
+ # @overload map_values($map)
+ # @param $map [Map]
+ # @return [List] the list of values, comma-separated
+ # @raise [ArgumentError] if `$map` is not a map
+ def map_values(map)
+ assert_type map, :Map, :map
+ list(map.to_h.values, :comma)
+ end
+ declare :map_values, [:map]
+
+ # Returns whether a map has a value associated with a given key.
+ #
+ # @example
+ # map-has-key(("foo": 1, "bar": 2), "foo") => true
+ # map-has-key(("foo": 1, "bar": 2), "baz") => false
+ # @overload map_has_key($map, $key)
+ # @param $map [Sass::Script::Value::Map]
+ # @param $key [Sass::Script::Value::Base]
+ # @return [Sass::Script::Value::Bool]
+ # @raise [ArgumentError] if `$map` is not a map
+ def map_has_key(map, key)
+ assert_type map, :Map, :map
+ bool(map.to_h.has_key?(key))
+ end
+ declare :map_has_key, [:map, :key]
+
+ # Returns the map of named arguments passed to a function or mixin that
+ # takes a variable argument list. The argument names are strings, and they
+ # do not contain the leading `$`.
+ #
+ # @example
+ # @mixin foo($args...) {
+ # @debug keywords($args); //=> (arg1: val, arg2: val)
+ # }
+ #
+ # @include foo($arg1: val, $arg2: val);
+ # @overload keywords($args)
+ # @param $args [Sass::Script::Value::ArgList]
+ # @return [Sass::Script::Value::Map]
+ # @raise [ArgumentError] if `$args` isn't a variable argument list
+ def keywords(args)
+ assert_type args, :ArgList, :args
+ map(Sass::Util.map_keys(args.keywords.as_stored) {|k| Sass::Script::Value::String.new(k)})
+ end
+ declare :keywords, [:args]
+
+ # Returns one of two values, depending on whether or not `$condition` is
+ # true. Just like in ` if`, all values other than `false` and `null` are
+ # considered to be true.
+ #
+ # @example
+ # if(true, 1px, 2px) => 1px
+ # if(false, 1px, 2px) => 2px
+ # @overload if($condition, $if-true, $if-false)
+ # @param $condition [Sass::Script::Value::Base] Whether the `$if-true` or
+ # `$if-false` will be returned
+ # @param $if-true [Sass::Script::Tree::Node]
+ # @param $if-false [Sass::Script::Tree::Node]
+ # @return [Sass::Script::Value::Base] `$if-true` or `$if-false`
+ def if(condition, if_true, if_false)
+ if condition.to_bool
+ perform(if_true)
+ else
+ perform(if_false)
+ end
+ end
+ declare :if, [:condition, :"&if_true", :"&if_false"]
+
+ # Returns a unique CSS identifier. The identifier is returned as an unquoted
+ # string. The identifier returned is only guaranteed to be unique within the
+ # scope of a single Sass run.
+ #
+ # @overload unique_id()
+ # @return [Sass::Script::Value::String]
+ def unique_id
+ generator = Sass::Script::Functions.random_number_generator
+ Thread.current[:sass_last_unique_id] ||= generator.rand(36**8)
+ # avoid the temptation of trying to guess the next unique value.
+ value = (Thread.current[:sass_last_unique_id] += (generator.rand(10) + 1))
+ # the u makes this a legal identifier if it would otherwise start with a number.
+ identifier("u" + value.to_s(36).rjust(8, '0'))
+ end
+ declare :unique_id, []
+
+ # Dynamically calls a function. This can call user-defined
+ # functions, built-in functions, or plain CSS functions. It will
+ # pass along all arguments, including keyword arguments, to the
+ # called function.
+ #
+ # @example
+ # call(rgb, 10, 100, 255) => #0a64ff
+ # call(scale-color, #0a64ff, $lightness: -10%) => #0058ef
+ #
+ # $fn: nth;
+ # call($fn, (a b c), 2) => b
+ #
+ # @overload call($name, $args...)
+ # @param $name [String] The name of the function to call.
+ def call(name, *args)
+ assert_type name, :String, :name
+ kwargs = args.last.is_a?(Hash) ? args.pop : {}
+ funcall = Sass::Script::Tree::Funcall.new(
+ name.value,
+ args.map {|a| Sass::Script::Tree::Literal.new(a)},
+ Sass::Util.map_vals(kwargs) {|v| Sass::Script::Tree::Literal.new(v)},
+ nil,
+ nil)
+ funcall.options = options
+ perform(funcall)
+ end
+ declare :call, [:name], :var_args => true, :var_kwargs => true
+
+ # This function only exists as a workaround for IE7's [`content:
+ # counter` bug](http://jes.st/2013/ie7s-css-breaking-content-counter-bug/).
+ # It works identically to any other plain-CSS function, except it
+ # avoids adding spaces between the argument commas.
+ #
+ # @example
+ # counter(item, ".") => counter(item,".")
+ # @overload counter($args...)
+ # @return [Sass::Script::Value::String]
+ def counter(*args)
+ identifier("counter(#{args.map {|a| a.to_s(options)}.join(',')})")
+ end
+ declare :counter, [], :var_args => true
+
+ # This function only exists as a workaround for IE7's [`content:
+ # counter` bug](http://jes.st/2013/ie7s-css-breaking-content-counter-bug/).
+ # It works identically to any other plain-CSS function, except it
+ # avoids adding spaces between the argument commas.
+ #
+ # @example
+ # counters(item, ".") => counters(item,".")
+ # @overload counters($args...)
+ # @return [Sass::Script::Value::String]
+ def counters(*args)
+ identifier("counters(#{args.map {|a| a.to_s(options)}.join(',')})")
+ end
+ declare :counters, [], :var_args => true
+
+ # Check whether a variable with the given name exists in the current
+ # scope or in the global scope.
+ #
+ # @example
+ # $a-false-value: false;
+ # variable-exists(a-false-value) => true
+ #
+ # variable-exists(nonexistent) => false
+ #
+ # @overload variable_exists($name)
+ # @param $name [Sass::Script::Value::String] The name of the variable to
+ # check. The name should not include the `$`.
+ # @return [Sass::Script::Value::Bool] Whether the variable is defined in
+ # the current scope.
+ def variable_exists(name)
+ assert_type name, :String, :name
+ bool(environment.caller.var(name.value))
+ end
+ declare :variable_exists, [:name]
+
+ # Check whether a variable with the given name exists in the global
+ # scope (at the top level of the file).
+ #
+ # @example
+ # $a-false-value: false;
+ # global-variable-exists(a-false-value) => true
+ #
+ # .foo {
+ # $some-var: false;
+ # @if global-variable-exists(some-var) { /* false, doesn't run */ }
+ # }
+ #
+ # @overload global_variable_exists($name)
+ # @param $name [Sass::Script::Value::String] The name of the variable to
+ # check. The name should not include the `$`.
+ # @return [Sass::Script::Value::Bool] Whether the variable is defined in
+ # the global scope.
+ def global_variable_exists(name)
+ assert_type name, :String, :name
+ bool(environment.global_env.var(name.value))
+ end
+ declare :global_variable_exists, [:name]
+
+ # Check whether a function with the given name exists.
+ #
+ # @example
+ # function-exists(lighten) => true
+ #
+ # @function myfunc { @return "something"; }
+ # function-exists(myfunc) => true
+ #
+ # @overload function_exists($name)
+ # @param name [Sass::Script::Value::String] The name of the function to
+ # check.
+ # @return [Sass::Script::Value::Bool] Whether the function is defined.
+ def function_exists(name)
+ assert_type name, :String, :name
+ exists = Sass::Script::Functions.callable?(name.value.tr("-", "_"))
+ exists ||= environment.function(name.value)
+ bool(exists)
+ end
+ declare :function_exists, [:name]
+
+ # Check whether a mixin with the given name exists.
+ #
+ # @example
+ # mixin-exists(nonexistent) => false
+ #
+ # @mixin red-text { color: red; }
+ # mixin-exists(red-text) => true
+ #
+ # @overload mixin_exists($name)
+ # @param name [Sass::Script::Value::String] The name of the mixin to
+ # check.
+ # @return [Sass::Script::Value::Bool] Whether the mixin is defined.
+ def mixin_exists(name)
+ assert_type name, :String, :name
+ bool(environment.mixin(name.value))
+ end
+ declare :mixin_exists, [:name]
+
+ # Return a string containing the value as its Sass representation.
+ #
+ # @overload inspect($value)
+ # @param $value [Sass::Script::Value::Base] The value to inspect.
+ # @return [Sass::Script::Value::String] A representation of the value as
+ # it would be written in Sass.
+ def inspect(value)
+ unquoted_string(value.to_sass)
+ end
+ declare :inspect, [:value]
+
+ # @overload random()
+ # Return a decimal between 0 and 1, inclusive of 0 but not 1.
+ # @return [Sass::Script::Value::Number] A decimal value.
+ # @overload random($limit)
+ # Return an integer between 1 and `$limit`, inclusive of 1 but not `$limit`.
+ # @param $limit [Sass::Script::Value::Number] The maximum of the random integer to be
+ # returned, a positive integer.
+ # @return [Sass::Script::Value::Number] An integer.
+ # @raise [ArgumentError] if the `$limit` is not 1 or greater
+ def random(limit = nil)
+ generator = Sass::Script::Functions.random_number_generator
+ if limit
+ assert_integer limit, "limit"
+ if limit.value < 1
+ raise ArgumentError.new("$limit #{limit} must be greater than or equal to 1")
+ end
+ number(1 + generator.rand(limit.value))
+ else
+ number(generator.rand)
+ end
+ end
+ declare :random, []
+ declare :random, [:limit]
+
+ # Parses a user-provided selector into a list of lists of strings
+ # as returned by `&`.
+ #
+ # @example
+ # selector-parse(".foo .bar, .baz .bang") => ('.foo' '.bar', '.baz' '.bang')
+ #
+ # @overload selector_parse($selector)
+ # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector to parse. This can be either a string, a list of
+ # strings, or a list of lists of strings as returned by `&`.
+ # @return [Sass::Script::Value::List]
+ # A list of lists of strings representing `$selector`. This is
+ # in the same format as a selector returned by `&`.
+ def selector_parse(selector)
+ parse_selector(selector, :selector).to_sass_script
+ end
+ declare :selector_parse, [:selector]
+
+ # Return a new selector with all selectors in `$selectors` nested beneath
+ # one another as though they had been nested in the stylesheet as
+ # `$selector1 { $selector2 { ... } }`.
+ #
+ # Unlike most selector functions, `selector-nest` allows the
+ # parent selector `&` to be used in any selector but the first.
+ #
+ # @example
+ # selector-nest(".foo", ".bar", ".baz") => .foo .bar .baz
+ # selector-nest(".a .foo", ".b .bar") => .a .foo .b .bar
+ # selector-nest(".foo", "&.bar") => .foo.bar
+ #
+ # @overload selector_nest($selectors...)
+ # @param $selectors [[Sass::Script::Value::String, Sass::Script::Value::List]]
+ # The selectors to nest. At least one selector must be passed. Each of
+ # these can be either a string, a list of strings, or a list of lists of
+ # strings as returned by `&`.
+ # @return [Sass::Script::Value::List]
+ # A list of lists of strings representing the result of nesting
+ # `$selectors`. This is in the same format as a selector returned by
+ # `&`.
+ def selector_nest(*selectors)
+ if selectors.empty?
+ raise ArgumentError.new("$selectors: At least one selector must be passed")
+ end
+
+ parsed = [parse_selector(selectors.first, :selectors)]
+ parsed += selectors[1..-1].map {|sel| parse_selector(sel, :selectors, !!:parse_parent_ref)}
+ parsed.inject {|result, child| child.resolve_parent_refs(result)}.to_sass_script
+ end
+ declare :selector_nest, [], :var_args => true
+
+ # Return a new selector with all selectors in `$selectors` appended one
+ # another as though they had been nested in the stylesheet as `$selector1 {
+ # &$selector2 { ... } }`.
+ #
+ # @example
+ # selector-append(".foo", ".bar", ".baz") => .foo.bar.baz
+ # selector-append(".a .foo", ".b .bar") => "a .foo.b .bar"
+ # selector-append(".foo", "-suffix") => ".foo-suffix"
+ #
+ # @overload selector_append($selectors...)
+ # @param $selectors [[Sass::Script::Value::String, Sass::Script::Value::List]]
+ # The selectors to append. At least one selector must be passed. Each of
+ # these can be either a string, a list of strings, or a list of lists of
+ # strings as returned by `&`.
+ # @return [Sass::Script::Value::List]
+ # A list of lists of strings representing the result of appending
+ # `$selectors`. This is in the same format as a selector returned by
+ # `&`.
+ # @raise [ArgumentError] if a selector could not be appended.
+ def selector_append(*selectors)
+ if selectors.empty?
+ raise ArgumentError.new("$selectors: At least one selector must be passed")
+ end
+
+ selectors.map {|sel| parse_selector(sel, :selectors)}.inject do |parent, child|
+ child.members.each do |seq|
+ sseq = seq.members.first
+ unless sseq.is_a?(Sass::Selector::SimpleSequence)
+ raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"")
+ end
+
+ base = sseq.base
+ case base
+ when Sass::Selector::Universal
+ raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"")
+ when Sass::Selector::Element
+ unless base.namespace.nil?
+ raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"")
+ end
+ sseq.members[0] = Sass::Selector::Parent.new(base.name)
+ else
+ sseq.members.unshift Sass::Selector::Parent.new
+ end
+ end
+ child.resolve_parent_refs(parent)
+ end.to_sass_script
+ end
+ declare :selector_append, [], :var_args => true
+
+ # Returns a new version of `$selector` with `$extendee` extended
+ # with `$extender`. This works just like the result of
+ #
+ # $selector { ... }
+ # $extender { @extend $extendee }
+ #
+ # @example
+ # selector-extend(".a .b", ".b", ".foo .bar") => .a .b, .a .foo .bar, .foo .a .bar
+ #
+ # @overload selector_extend($selector, $extendee, $extender)
+ # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector within which `$extendee` is extended with
+ # `$extender`. This can be either a string, a list of strings,
+ # or a list of lists of strings as returned by `&`.
+ # @param $extendee [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector being extended. This can be either a string, a
+ # list of strings, or a list of lists of strings as returned
+ # by `&`.
+ # @param $extender [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector being injected into `$selector`. This can be
+ # either a string, a list of strings, or a list of lists of
+ # strings as returned by `&`.
+ # @return [Sass::Script::Value::List]
+ # A list of lists of strings representing the result of the
+ # extension. This is in the same format as a selector returned
+ # by `&`.
+ # @raise [ArgumentError] if the extension fails
+ def selector_extend(selector, extendee, extender)
+ selector = parse_selector(selector, :selector)
+ extendee = parse_selector(extendee, :extendee)
+ extender = parse_selector(extender, :extender)
+
+ extends = Sass::Util::SubsetMap.new
+ begin
+ extender.populate_extends(extends, extendee)
+ selector.do_extend(extends).to_sass_script
+ rescue Sass::SyntaxError => e
+ raise ArgumentError.new(e.to_s)
+ end
+ end
+ declare :selector_extend, [:selector, :extendee, :extender]
+
+ # Replaces all instances of `$original` with `$replacement` in `$selector`
+ #
+ # This works by using ` extend` and throwing away the original
+ # selector. This means that it can be used to do very advanced
+ # replacements; see the examples below.
+ #
+ # @example
+ # selector-replace(".foo .bar", ".bar", ".baz") => ".foo .baz"
+ # selector-replace(".foo.bar.baz", ".foo.baz", ".qux") => ".bar.qux"
+ #
+ # @overload selector_replace($selector, $original, $replacement)
+ # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector within which `$original` is replaced with
+ # `$replacement`. This can be either a string, a list of
+ # strings, or a list of lists of strings as returned by `&`.
+ # @param $original [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector being replaced. This can be either a string, a
+ # list of strings, or a list of lists of strings as returned
+ # by `&`.
+ # @param $replacement [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector that `$original` is being replaced with. This
+ # can be either a string, a list of strings, or a list of
+ # lists of strings as returned by `&`.
+ # @return [Sass::Script::Value::List]
+ # A list of lists of strings representing the result of the
+ # extension. This is in the same format as a selector returned
+ # by `&`.
+ # @raise [ArgumentError] if the replacement fails
+ def selector_replace(selector, original, replacement)
+ selector = parse_selector(selector, :selector)
+ original = parse_selector(original, :original)
+ replacement = parse_selector(replacement, :replacement)
+
+ extends = Sass::Util::SubsetMap.new
+ begin
+ replacement.populate_extends(extends, original)
+ selector.do_extend(extends, [], !!:replace).to_sass_script
+ rescue Sass::SyntaxError => e
+ raise ArgumentError.new(e.to_s)
+ end
+ end
+ declare :selector_replace, [:selector, :original, :replacement]
+
+ # Unifies two selectors into a single selector that matches only
+ # elements matched by both input selectors. Returns `null` if
+ # there is no such selector.
+ #
+ # Like the selector unification done for ` extend`, this doesn't
+ # guarantee that the output selector will match *all* elements
+ # matched by both input selectors. For example, if `.a .b` is
+ # unified with `.x .y`, `.a .x .b.y, .x .a .b.y` will be returned,
+ # but `.a.x .b.y` will not. This avoids exponential output size
+ # while matching all elements that are likely to exist in
+ # practice.
+ #
+ # @example
+ # selector-unify(".a", ".b") => .a.b
+ # selector-unify(".a .b", ".x .y") => .a .x .b.y, .x .a .b.y
+ # selector-unify(".a.b", ".b.c") => .a.b.c
+ # selector-unify("#a", "#b") => null
+ #
+ # @overload selector_unify($selector1, $selector2)
+ # @param $selector1 [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The first selector to be unified. This can be either a
+ # string, a list of strings, or a list of lists of strings as
+ # returned by `&`.
+ # @param $selector2 [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The second selector to be unified. This can be either a
+ # string, a list of strings, or a list of lists of strings as
+ # returned by `&`.
+ # @return [Sass::Script::Value::List, Sass::Script::Value::Null]
+ # A list of lists of strings representing the result of the
+ # unification, or null if no unification exists. This is in
+ # the same format as a selector returned by `&`.
+ def selector_unify(selector1, selector2)
+ selector1 = parse_selector(selector1, :selector1)
+ selector2 = parse_selector(selector2, :selector2)
+ return null unless (unified = selector1.unify(selector2))
+ unified.to_sass_script
+ end
+ declare :selector_unify, [:selector1, :selector2]
+
+ # Returns the [simple
+ # selectors](http://dev.w3.org/csswg/selectors4/#simple) that
+ # comprise the compound selector `$selector`.
+ #
+ # Note that `$selector` **must be** a [compound
+ # selector](http://dev.w3.org/csswg/selectors4/#compound). That
+ # means it cannot contain commas or spaces. It also means that
+ # unlike other selector functions, this takes only strings, not
+ # lists.
+ #
+ # @example
+ # simple-selectors(".foo.bar") => ".foo", ".bar"
+ # simple-selectors(".foo.bar.baz") => ".foo", ".bar", ".baz"
+ #
+ # @overload simple_selectors($selector)
+ # @param $selector [Sass::Script::Value::String]
+ # The compound selector whose simple selectors will be extracted.
+ # @return [Sass::Script::Value::List]
+ # A list of simple selectors in the compound selector.
+ def simple_selectors(selector)
+ selector = parse_compound_selector(selector, :selector)
+ list(selector.members.map {|simple| unquoted_string(simple.to_s)}, :comma)
+ end
+ declare :simple_selectors, [:selector]
+
+ # Returns whether `$super` is a superselector of `$sub`. This means that
+ # `$super` matches all the elements that `$sub` matches, as well as possibly
+ # additional elements. In general, simpler selectors tend to be
+ # superselectors of more complex oned.
+ #
+ # @example
+ # is-superselector(".foo", ".foo.bar") => true
+ # is-superselector(".foo.bar", ".foo") => false
+ # is-superselector(".bar", ".foo .bar") => true
+ # is-superselector(".foo .bar", ".bar") => false
+ #
+ # @overload is_superselector($super, $sub)
+ # @param $super [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The potential superselector. This can be either a string, a list of
+ # strings, or a list of lists of strings as returned by `&`.
+ # @param $sub [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The potential subselector. This can be either a string, a list of
+ # strings, or a list of lists of strings as returned by `&`.
+ # @return [Sass::Script::Value::Bool]
+ # Whether `$selector1` is a superselector of `$selector2`.
+ def is_superselector(sup, sub)
+ sup = parse_selector(sup, :super)
+ sub = parse_selector(sub, :sub)
+ bool(sup.superselector?(sub))
+ end
+ declare :is_superselector, [:super, :sub]
+
+ private
+
+ # This method implements the pattern of transforming a numeric value into
+ # another numeric value with the same units.
+ # It yields a number to a block to perform the operation and return a number
+ def numeric_transformation(value)
+ assert_type value, :Number, :value
+ Sass::Script::Value::Number.new(
+ yield(value.value), value.numerator_units, value.denominator_units)
+ end
+
+ # @comment
+ # rubocop:disable ParameterLists
+ def _adjust(color, amount, attr, range, op, units = "")
+ # rubocop:enable ParameterLists
+ assert_type color, :Color, :color
+ assert_type amount, :Number, :amount
+ Sass::Util.check_range('Amount', range, amount, units)
+
+ color.with(attr => color.send(attr).send(op, amount.value))
+ end
+
+ def check_alpha_unit(alpha, function)
+ return if alpha.unitless?
+
+ if alpha.is_unit?("%")
+ Sass::Util.sass_warn(<<WARNING)
+DEPRECATION WARNING: Passing a percentage as the alpha value to #{function}() will be
+interpreted differently in future versions of Sass. For now, use #{alpha.value} instead.
+WARNING
+ else
+ Sass::Util.sass_warn(<<WARNING)
+DEPRECATION WARNING: Passing a number with units as the alpha value to #{function}() is
+deprecated and will be an error in future versions of Sass. Use #{alpha.value} instead.
+WARNING
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/lexer.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/lexer.rb
new file mode 100644
index 0000000..1686501
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/lexer.rb
@@ -0,0 +1,453 @@
+require 'sass/scss/rx'
+
+module Sass
+ module Script
+ # The lexical analyzer for SassScript.
+ # It takes a raw string and converts it to individual tokens
+ # that are easier to parse.
+ class Lexer
+ include Sass::SCSS::RX
+
+ # A struct containing information about an individual token.
+ #
+ # `type`: \[`Symbol`\]
+ # : The type of token.
+ #
+ # `value`: \[`Object`\]
+ # : The Ruby object corresponding to the value of the token.
+ #
+ # `source_range`: \[`Sass::Source::Range`\]
+ # : The range in the source file in which the token appeared.
+ #
+ # `pos`: \[`Fixnum`\]
+ # : The scanner position at which the SassScript token appeared.
+ Token = Struct.new(:type, :value, :source_range, :pos)
+
+ # The line number of the lexer's current position.
+ #
+ # @return [Fixnum]
+ def line
+ return @line unless @tok
+ @tok.source_range.start_pos.line
+ end
+
+ # The number of bytes into the current line
+ # of the lexer's current position (1-based).
+ #
+ # @return [Fixnum]
+ def offset
+ return @offset unless @tok
+ @tok.source_range.start_pos.offset
+ end
+
+ # A hash from operator strings to the corresponding token types.
+ OPERATORS = {
+ '+' => :plus,
+ '-' => :minus,
+ '*' => :times,
+ '/' => :div,
+ '%' => :mod,
+ '=' => :single_eq,
+ ':' => :colon,
+ '(' => :lparen,
+ ')' => :rparen,
+ ',' => :comma,
+ 'and' => :and,
+ 'or' => :or,
+ 'not' => :not,
+ '==' => :eq,
+ '!=' => :neq,
+ '>=' => :gte,
+ '<=' => :lte,
+ '>' => :gt,
+ '<' => :lt,
+ '#{' => :begin_interpolation,
+ '}' => :end_interpolation,
+ ';' => :semicolon,
+ '{' => :lcurly,
+ '...' => :splat,
+ }
+
+ OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
+
+ TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge(
+ :const => "variable (e.g. $foo)",
+ :ident => "identifier (e.g. middle)")
+
+ # A list of operator strings ordered with longer names first
+ # so that `>` and `<` don't clobber `>=` and `<=`.
+ OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
+
+ # A sub-list of {OP_NAMES} that only includes operators
+ # with identifier names.
+ IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
+
+ PARSEABLE_NUMBER = /(?:(\d*\.\d+)|(\d+))(?:[eE]([+-]?\d+))?(#{UNIT})?/
+
+ # A hash of regular expressions that are used for tokenizing.
+ REGULAR_EXPRESSIONS = {
+ :whitespace => /\s+/,
+ :comment => COMMENT,
+ :single_line_comment => SINGLE_LINE_COMMENT,
+ :variable => /(\$)(#{IDENT})/,
+ :ident => /(#{IDENT})(\()?/,
+ :number => PARSEABLE_NUMBER,
+ :unary_minus_number => /-#{PARSEABLE_NUMBER}/,
+ :color => HEXCOLOR,
+ :id => /##{IDENT}/,
+ :selector => /&/,
+ :ident_op => /(#{Regexp.union(*IDENT_OP_NAMES.map do |s|
+ Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")
+ end)})/,
+ :op => /(#{Regexp.union(*OP_NAMES)})/,
+ }
+
+ class << self
+ private
+
+ def string_re(open, close)
+ /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m
+ end
+ end
+
+ # A hash of regular expressions that are used for tokenizing strings.
+ #
+ # The key is a `[Symbol, Boolean]` pair.
+ # The symbol represents which style of quotation to use,
+ # while the boolean represents whether or not the string
+ # is following an interpolated segment.
+ STRING_REGULAR_EXPRESSIONS = {
+ :double => {
+ false => string_re('"', '"'),
+ true => string_re('', '"')
+ },
+ :single => {
+ false => string_re("'", "'"),
+ true => string_re('', "'")
+ },
+ :uri => {
+ false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
+ },
+ # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
+ # non-standard version of http://www.w3.org/TR/css3-conditional/
+ :url_prefix => {
+ false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
+ },
+ :domain => {
+ false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
+ }
+ }
+
+ # @param str [String, StringScanner] The source text to lex
+ # @param line [Fixnum] The 1-based line on which the SassScript appears.
+ # Used for error reporting and sourcemap building
+ # @param offset [Fixnum] The 1-based character (not byte) offset in the line in the source.
+ # Used for error reporting and sourcemap building
+ # @param options [{Symbol => Object}] An options hash;
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
+ def initialize(str, line, offset, options)
+ @scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
+ @line = line
+ @offset = offset
+ @options = options
+ @interpolation_stack = []
+ @prev = nil
+ end
+
+ # Moves the lexer forward one token.
+ #
+ # @return [Token] The token that was moved past
+ def next
+ @tok ||= read_token
+ @tok, tok = nil, @tok
+ @prev = tok
+ tok
+ end
+
+ # Returns whether or not there's whitespace before the next token.
+ #
+ # @return [Boolean]
+ def whitespace?(tok = @tok)
+ if tok
+ @scanner.string[0...tok.pos] =~ /\s\Z/
+ else
+ @scanner string[ scanner pos, 1] =~ /^\s/ ||
+ @scanner string[ scanner pos - 1, 1] =~ /\s\Z/
+ end
+ end
+
+ # Returns the next token without moving the lexer forward.
+ #
+ # @return [Token] The next token
+ def peek
+ @tok ||= read_token
+ end
+
+ # Rewinds the underlying StringScanner
+ # to before the token returned by \{#peek}.
+ def unpeek!
+ if @tok
+ @scanner.pos = @tok.pos
+ @line = @tok.source_range.start_pos.line
+ @offset = @tok.source_range.start_pos.offset
+ end
+ end
+
+ # @return [Boolean] Whether or not there's more source text to lex.
+ def done?
+ whitespace unless after_interpolation? && @interpolation_stack.last
+ @scanner.eos? && @tok.nil?
+ end
+
+ # @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
+ def after_interpolation?
+ @prev && @prev.type == :end_interpolation
+ end
+
+ # Raise an error to the effect that `name` was expected in the input stream
+ # and wasn't found.
+ #
+ # This calls \{#unpeek!} to rewind the scanner to immediately after
+ # the last returned token.
+ #
+ # @param name [String] The name of the entity that was expected but not found
+ # @raise [Sass::SyntaxError]
+ def expected!(name)
+ unpeek!
+ Sass::SCSS::Parser.expected(@scanner, name, @line)
+ end
+
+ # Records all non-comment text the lexer consumes within the block
+ # and returns it as a string.
+ #
+ # @yield A block in which text is recorded
+ # @return [String]
+ def str
+ old_pos = @tok ? @tok.pos : @scanner.pos
+ yield
+ new_pos = @tok ? @tok.pos : @scanner.pos
+ @scanner.string[old_pos...new_pos]
+ end
+
+ private
+
+ def read_token
+ return if done?
+ start_pos = source_position
+ value = token
+ return unless value
+ type, val = value
+ Token.new(type, val, range(start_pos), @scanner.pos - @scanner.matched_size)
+ end
+
+ def whitespace
+ nil while scan(REGULAR_EXPRESSIONS[:whitespace]) ||
+ scan(REGULAR_EXPRESSIONS[:comment]) ||
+ scan(REGULAR_EXPRESSIONS[:single_line_comment])
+ end
+
+ def token
+ if after_interpolation? && (interp = @interpolation_stack.pop)
+ interp_type, interp_value = interp
+ if interp_type == :special_fun
+ return special_fun_body(interp_value)
+ else
+ raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
+ return string(interp_value, true)
+ end
+ end
+
+ variable || string(:double, false) || string(:single, false) || number || id || color ||
+ selector || string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val ||
+ ident_op || ident || op
+ end
+
+ def variable
+ _variable(REGULAR_EXPRESSIONS[:variable])
+ end
+
+ def _variable(rx)
+ return unless scan(rx)
+
+ [:const, @scanner[2]]
+ end
+
+ def ident
+ return unless scan(REGULAR_EXPRESSIONS[:ident])
+ [ scanner[2] ? :funcall : :ident, @scanner[1]]
+ end
+
+ def string(re, open)
+ line, offset = @line, @offset
+ return unless scan(STRING_REGULAR_EXPRESSIONS[re][open])
+ if @scanner[0] =~ /([^\\]|^)\n/
+ filename = @options[:filename]
+ Sass::Util.sass_warn <<MESSAGE
+DEPRECATION WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
+Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
+To include a newline in a string, use "\\a" or "\\a " as in CSS.
+MESSAGE
+ end
+
+ if @scanner[2] == '#{' # '
+ @scanner.pos -= 2 # Don't actually consume the #{
+ @offset -= 2
+ @interpolation_stack << [:string, re]
+ end
+ str =
+ if re == :uri
+ url = "#{'url(' unless open}#{ scanner[1]}#{')' unless @scanner[2] == '#{'}"
+ Script::Value::String.new(url)
+ else
+ Script::Value::String.new(Sass::Script::Value::String.value(@scanner[1]), :string)
+ end
+ [:string, str]
+ end
+
+ def number
+ # Handling unary minus is complicated by the fact that whitespace is an
+ # operator in SassScript. We want "1-2" to be parsed as "1 - 2", but we
+ # want "1 -2" to be parsed as "1 (-2)". To accomplish this, we only
+ # parse a unary minus as part of a number literal if there's whitespace
+ # before and not after it. Cases like "(-2)" are handled by the unary
+ # minus logic in the parser instead.
+ if @scanner.peek(1) == '-'
+ return if @scanner.pos == 0
+ unary_minus_allowed =
+ case @scanner string[ scanner pos - 1, 1]
+ when /\s/; true
+ when '/'; @scanner.pos != 1 && @scanner string[ scanner pos - 2, 1] == '*'
+ else; false
+ end
+
+ return unless unary_minus_allowed
+ return unless scan(REGULAR_EXPRESSIONS[:unary_minus_number])
+ minus = true
+ else
+ return unless scan(REGULAR_EXPRESSIONS[:number])
+ minus = false
+ end
+
+ value = (@scanner[1] ? @scanner[1].to_f : @scanner[2].to_i) * (minus ? -1 : 1)
+ value *= 10** scanner[3] to_i if @scanner[3]
+ script_number = Script::Value::Number.new(value, Array(@scanner[4]))
+ [:number, script_number]
+ end
+
+ def id
+ # Colors and ids are tough to tell apart, because they overlap but
+ # neither is a superset of the other. "#xyz" is an id but not a color,
+ # "#000" is a color but not an id, "#abc" is both, and "#0" is neither.
+ # We need to handle all these cases correctly.
+ #
+ # To do so, we first try to parse something as an id. If this works and
+ # the id is also a valid color, we return the color. Otherwise, we
+ # return the id. If it didn't parse as an id, we then try to parse it as
+ # a color. If *this* works, we return the color, and if it doesn't we
+ # give up and throw an error.
+ #
+ # IDs in properties are used in the Basic User Interface Module
+ # (http://www.w3.org/TR/css3-ui/).
+ return unless scan(REGULAR_EXPRESSIONS[:id])
+ if @scanner[0] =~ /^\#[0-9a-fA-F]+$/ && (@scanner[0].length == 4 || @scanner[0].length == 7)
+ return [:color, Script::Value::Color.from_hex(@scanner[0])]
+ end
+ [:ident, @scanner[0]]
+ end
+
+ def color
+ return unless @scanner.match?(REGULAR_EXPRESSIONS[:color])
+ return unless @scanner[0].length == 4 || @scanner[0].length == 7
+ script_color = Script::Value::Color.from_hex(scan(REGULAR_EXPRESSIONS[:color]))
+ [:color, script_color]
+ end
+
+ def selector
+ start_pos = source_position
+ return unless scan(REGULAR_EXPRESSIONS[:selector])
+ script_selector = Script::Tree::Selector.new
+ script_selector.source_range = range(start_pos)
+ [:selector, script_selector]
+ end
+
+ def special_fun
+ prefix = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
+ return unless prefix
+ special_fun_body(1, prefix)
+ end
+
+ def special_fun_body(parens, prefix = nil)
+ str = prefix || ''
+ while (scanned = scan(/.*?([()]|\#\{)/m))
+ str << scanned
+ if scanned[-1] == ?(
+ parens += 1
+ next
+ elsif scanned[-1] == ?)
+ parens -= 1
+ next unless parens == 0
+ else
+ raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
+ str.slice!(-2..-1)
+ @scanner.pos -= 2 # Don't actually consume the #{
+ @offset -= 2
+ @interpolation_stack << [:special_fun, parens]
+ end
+
+ return [:special_fun, Sass::Script::Value::String.new(str)]
+ end
+
+ scan(/.*/)
+ expected!('")"')
+ end
+
+ def special_val
+ return unless scan(/!important/i)
+ [:string, Script::Value::String.new("!important")]
+ end
+
+ def ident_op
+ op = scan(REGULAR_EXPRESSIONS[:ident_op])
+ return unless op
+ [OPERATORS[op]]
+ end
+
+ def op
+ op = scan(REGULAR_EXPRESSIONS[:op])
+ return unless op
+ name = OPERATORS[op]
+ if name == :begin_interpolation && ! interpolation_stack empty?
+ [:string_interpolation]
+ else
+ [name]
+ end
+ end
+
+ def raw(rx)
+ val = scan(rx)
+ return unless val
+ [:raw, val]
+ end
+
+ def scan(re)
+ str = @scanner.scan(re)
+ return unless str
+ c = str.count("\n")
+ @line += c
+ @offset = (c == 0 ? @offset + str.size : str.size - str.rindex("\n"))
+ str
+ end
+
+ def range(start_pos, end_pos = source_position)
+ Sass::Source::Range.new(start_pos, end_pos, @options[:filename], @options[:importer])
+ end
+
+ def source_position
+ Sass::Source::Position.new(@line, @offset)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/parser.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/parser.rb
new file mode 100644
index 0000000..7c50c9a
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/parser.rb
@@ -0,0 +1,638 @@
+require 'sass/script/lexer'
+
+module Sass
+ module Script
+ # The parser for SassScript.
+ # It parses a string of code into a tree of {Script::Tree::Node}s.
+ class Parser
+ # The line number of the parser's current position.
+ #
+ # @return [Fixnum]
+ def line
+ @lexer.line
+ end
+
+ # The column number of the parser's current position.
+ #
+ # @return [Fixnum]
+ def offset
+ @lexer.offset
+ end
+
+ # @param str [String, StringScanner] The source text to parse
+ # @param line [Fixnum] The line on which the SassScript appears.
+ # Used for error reporting and sourcemap building
+ # @param offset [Fixnum] The character (not byte) offset where the script starts in the line.
+ # Used for error reporting and sourcemap building
+ # @param options [{Symbol => Object}] An options hash;
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
+ def initialize(str, line, offset, options = {})
+ @options = options
+ @lexer = lexer_class.new(str, line, offset, options)
+ end
+
+ # Parses a SassScript expression within an interpolated segment (`#{}`).
+ # This means that it stops when it comes across an unmatched `}`,
+ # which signals the end of an interpolated segment,
+ # it returns rather than throwing an error.
+ #
+ # @param warn_for_color [Boolean] Whether raw color values passed to
+ # interoplation should cause a warning.
+ # @return [Script::Tree::Node] The root node of the parse tree
+ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
+ def parse_interpolated(warn_for_color = false)
+ # Start two characters back to compensate for #{
+ start_pos = Sass::Source::Position.new(line, offset - 2)
+ expr = assert_expr :expr
+ assert_tok :end_interpolation
+ expr = Sass::Script::Tree::Interpolation.new(
+ nil, expr, nil, !:wb, !:wa, !:originally_text, warn_for_color)
+ expr.options = @options
+ node(expr, start_pos)
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parses a SassScript expression.
+ #
+ # @return [Script::Tree::Node] The root node of the parse tree
+ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
+ def parse
+ expr = assert_expr :expr
+ assert_done
+ expr.options = @options
+ expr
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parses a SassScript expression,
+ # ending it when it encounters one of the given identifier tokens.
+ #
+ # @param tokens [#include?(String)] A set of strings that delimit the expression.
+ # @return [Script::Tree::Node] The root node of the parse tree
+ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
+ def parse_until(tokens)
+ @stop_at = tokens
+ expr = assert_expr :expr
+ assert_done
+ expr.options = @options
+ expr
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parses the argument list for a mixin include.
+ #
+ # @return [(Array<Script::Tree::Node>,
+ # {String => Script::Tree::Node},
+ # Script::Tree::Node,
+ # Script::Tree::Node)]
+ # The root nodes of the positional arguments, keyword arguments, and
+ # splat argument(s). Keyword arguments are in a hash from names to values.
+ # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
+ def parse_mixin_include_arglist
+ args, keywords = [], {}
+ if try_tok(:lparen)
+ args, keywords, splat, kwarg_splat = mixin_arglist
+ assert_tok(:rparen)
+ end
+ assert_done
+
+ args.each {|a| a.options = @options}
+ keywords.each {|k, v| v.options = @options}
+ splat.options = @options if splat
+ kwarg_splat.options = @options if kwarg_splat
+ return args, keywords, splat, kwarg_splat
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parses the argument list for a mixin definition.
+ #
+ # @return [(Array<Script::Tree::Node>, Script::Tree::Node)]
+ # The root nodes of the arguments, and the splat argument.
+ # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
+ def parse_mixin_definition_arglist
+ args, splat = defn_arglist!(false)
+ assert_done
+
+ args.each do |k, v|
+ k.options = @options
+ v.options = @options if v
+ end
+ splat.options = @options if splat
+ return args, splat
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parses the argument list for a function definition.
+ #
+ # @return [(Array<Script::Tree::Node>, Script::Tree::Node)]
+ # The root nodes of the arguments, and the splat argument.
+ # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
+ def parse_function_definition_arglist
+ args, splat = defn_arglist!(true)
+ assert_done
+
+ args.each do |k, v|
+ k.options = @options
+ v.options = @options if v
+ end
+ splat.options = @options if splat
+ return args, splat
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parse a single string value, possibly containing interpolation.
+ # Doesn't assert that the scanner is finished after parsing.
+ #
+ # @return [Script::Tree::Node] The root node of the parse tree.
+ # @raise [Sass::SyntaxError] if the string isn't valid SassScript
+ def parse_string
+ unless (peek = @lexer.peek) &&
+ (peek.type == :string ||
+ (peek.type == :funcall && peek.value.downcase == 'url'))
+ lexer.expected!("string")
+ end
+
+ expr = assert_expr :funcall
+ expr.options = @options
+ @lexer.unpeek!
+ expr
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parses a SassScript expression.
+ #
+ # @overload parse(str, line, offset, filename = nil)
+ # @return [Script::Tree::Node] The root node of the parse tree
+ # @see Parser#initialize
+ # @see Parser#parse
+ def self.parse(*args)
+ new(*args).parse
+ end
+
+ PRECEDENCE = [
+ :comma, :single_eq, :space, :or, :and,
+ [:eq, :neq],
+ [:gt, :gte, :lt, :lte],
+ [:plus, :minus],
+ [:times, :div, :mod],
+ ]
+
+ ASSOCIATIVE = [:plus, :times]
+
+ class << self
+ # Returns an integer representing the precedence
+ # of the given operator.
+ # A lower integer indicates a looser binding.
+ #
+ # @private
+ def precedence_of(op)
+ PRECEDENCE.each_with_index do |e, i|
+ return i if Array(e).include?(op)
+ end
+ raise "[BUG] Unknown operator #{op.inspect}"
+ end
+
+ # Returns whether or not the given operation is associative.
+ #
+ # @private
+ def associative?(op)
+ ASSOCIATIVE.include?(op)
+ end
+
+ private
+
+ # Defines a simple left-associative production.
+ # name is the name of the production,
+ # sub is the name of the production beneath it,
+ # and ops is a list of operators for this precedence level
+ def production(name, sub, *ops)
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
+ def #{name}
+ interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect})
+ return interp if interp
+ return unless e = #{sub}
+ while tok = try_toks(#{ops.map {|o| o.inspect}.join(', ')})
+ if interp = try_op_before_interp(tok, e)
+ other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
+ return interp unless other_interp
+ return other_interp
+ end
+
+ e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type),
+ e.source_range.start_pos)
+ end
+ e
+ end
+RUBY
+ end
+
+ def unary(op, sub)
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
+ def unary_#{op}
+ return #{sub} unless tok = try_tok(:#{op})
+ interp = try_op_before_interp(tok)
+ return interp if interp
+ start_pos = source_position
+ node(Tree::UnaryOperation.new(assert_expr(:unary_#{op}), :#{op}), start_pos)
+ end
+RUBY
+ end
+ end
+
+ private
+
+ def source_position
+ Sass::Source::Position.new(line, offset)
+ end
+
+ def range(start_pos, end_pos = source_position)
+ Sass::Source::Range.new(start_pos, end_pos, @options[:filename], @options[:importer])
+ end
+
+ # @private
+ def lexer_class; Lexer; end
+
+ def map
+ start_pos = source_position
+ e = interpolation
+ return unless e
+ return list e, start_pos unless @lexer.peek && @lexer.peek.type == :colon
+
+ pair = map_pair(e)
+ map = node(Sass::Script::Tree::MapLiteral.new([pair]), start_pos)
+ while try_tok(:comma)
+ pair = map_pair
+ return map unless pair
+ map.pairs << pair
+ end
+ map
+ end
+
+ def map_pair(key = nil)
+ return unless key ||= interpolation
+ assert_tok :colon
+ return key, assert_expr(:interpolation)
+ end
+
+ def expr
+ start_pos = source_position
+ e = interpolation
+ return unless e
+ list e, start_pos
+ end
+
+ def list(first, start_pos)
+ return first unless @lexer.peek && @lexer.peek.type == :comma
+
+ list = node(Sass::Script::Tree::ListLiteral.new([first], :comma), start_pos)
+ while (tok = try_tok(:comma))
+ element_before_interp = list.elements.length == 1 ? list.elements.first : list
+ if (interp = try_op_before_interp(tok, element_before_interp))
+ other_interp = try_ops_after_interp([:comma], :expr, interp)
+ return interp unless other_interp
+ return other_interp
+ end
+ return list unless (e = interpolation)
+ list.elements << e
+ end
+ list
+ end
+
+ production :equals, :interpolation, :single_eq
+
+ def try_op_before_interp(op, prev = nil)
+ return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
+ wb = @lexer.whitespace?(op)
+ str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
+ op.source_range)
+ interp = node(
+ Script::Tree::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text),
+ (prev || str).source_range.start_pos)
+ interpolation(interp)
+ end
+
+ def try_ops_after_interp(ops, name, prev = nil)
+ return unless @lexer.after_interpolation?
+ op = try_toks(*ops)
+ return unless op
+ interp = try_op_before_interp(op, prev)
+ return interp if interp
+
+ wa = @lexer.whitespace?
+ str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
+ op.source_range)
+ str.line = @lexer.line
+ interp = node(
+ Script::Tree::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text),
+ (prev || str).source_range.start_pos)
+ interp
+ end
+
+ def interpolation(first = space)
+ e = first
+ while (interp = try_tok(:begin_interpolation))
+ wb = @lexer.whitespace?(interp)
+ mid = assert_expr :expr
+ assert_tok :end_interpolation
+ wa = @lexer.whitespace?
+ e = node(
+ Script::Tree::Interpolation.new(e, mid, space, wb, wa),
+ (e || mid).source_range.start_pos)
+ end
+ e
+ end
+
+ def space
+ start_pos = source_position
+ e = or_expr
+ return unless e
+ arr = [e]
+ while (e = or_expr)
+ arr << e
+ end
+ if arr.size == 1
+ arr.first
+ else
+ node(Sass::Script::Tree::ListLiteral.new(arr, :space), start_pos)
+ end
+ end
+
+ production :or_expr, :and_expr, :or
+ production :and_expr, :eq_or_neq, :and
+ production :eq_or_neq, :relational, :eq, :neq
+ production :relational, :plus_or_minus, :gt, :gte, :lt, :lte
+ production :plus_or_minus, :times_div_or_mod, :plus, :minus
+ production :times_div_or_mod, :unary_plus, :times, :div, :mod
+
+ unary :plus, :unary_minus
+ unary :minus, :unary_div
+ unary :div, :unary_not # For strings, so /foo/bar works
+ unary :not, :ident
+
+ def ident
+ return funcall unless @lexer.peek && @lexer.peek.type == :ident
+ return if @stop_at && @stop_at.include?(@lexer.peek.value)
+
+ name = @lexer.next
+ if (color = Sass::Script::Value::Color::COLOR_NAMES[name.value.downcase])
+ literal_node(Sass::Script::Value::Color.new(color, name.value), name.source_range)
+ elsif name.value == "true"
+ literal_node(Sass::Script::Value::Bool.new(true), name.source_range)
+ elsif name.value == "false"
+ literal_node(Sass::Script::Value::Bool.new(false), name.source_range)
+ elsif name.value == "null"
+ literal_node(Sass::Script::Value::Null.new, name.source_range)
+ else
+ literal_node(Sass::Script::Value::String.new(name.value, :identifier), name.source_range)
+ end
+ end
+
+ def funcall
+ tok = try_tok(:funcall)
+ return raw unless tok
+ args, keywords, splat, kwarg_splat = fn_arglist
+ assert_tok(:rparen)
+ node(Script::Tree::Funcall.new(tok.value, args, keywords, splat, kwarg_splat),
+ tok.source_range.start_pos, source_position)
+ end
+
+ def defn_arglist!(must_have_parens)
+ if must_have_parens
+ assert_tok(:lparen)
+ else
+ return [], nil unless try_tok(:lparen)
+ end
+ return [], nil if try_tok(:rparen)
+
+ res = []
+ splat = nil
+ must_have_default = false
+ loop do
+ c = assert_tok(:const)
+ var = node(Script::Tree::Variable.new(c.value), c.source_range)
+ if try_tok(:colon)
+ val = assert_expr(:space)
+ must_have_default = true
+ elsif try_tok(:splat)
+ splat = var
+ break
+ elsif must_have_default
+ raise SyntaxError.new(
+ "Required argument #{var.inspect} must come before any optional arguments.")
+ end
+ res << [var, val]
+ break unless try_tok(:comma)
+ end
+ assert_tok(:rparen)
+ return res, splat
+ end
+
+ def fn_arglist
+ arglist(:equals, "function argument")
+ end
+
+ def mixin_arglist
+ arglist(:interpolation, "mixin argument")
+ end
+
+ def arglist(subexpr, description)
+ args = []
+ keywords = Sass::Util::NormalizedMap.new
+ e = send(subexpr)
+
+ return [args, keywords] unless e
+
+ splat = nil
+ loop do
+ if @lexer.peek && @lexer.peek.type == :colon
+ name = e
+ @lexer.expected!("comma") unless name.is_a?(Tree::Variable)
+ assert_tok(:colon)
+ value = assert_expr(subexpr, description)
+
+ if keywords[name.name]
+ raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
+ end
+
+ keywords[name.name] = value
+ else
+ if try_tok(:splat)
+ return args, keywords, splat, e if splat
+ splat, e = e, nil
+ elsif splat
+ raise SyntaxError.new("Only keyword arguments may follow variable arguments (...).")
+ elsif !keywords.empty?
+ raise SyntaxError.new("Positional arguments must come before keyword arguments.")
+ end
+
+ args << e if e
+ end
+
+ return args, keywords, splat unless try_tok(:comma)
+ e = assert_expr(subexpr, description)
+ end
+ end
+
+ def raw
+ tok = try_tok(:raw)
+ return special_fun unless tok
+ literal_node(Script::Value::String.new(tok.value), tok.source_range)
+ end
+
+ def special_fun
+ first = try_tok(:special_fun)
+ return paren unless first
+ str = literal_node(first.value, first.source_range)
+ return str unless try_tok(:string_interpolation)
+ mid = assert_expr :expr
+ assert_tok :end_interpolation
+ last = assert_expr(:special_fun)
+ node(Tree::Interpolation.new(str, mid, last, false, false),
+ first.source_range.start_pos)
+ end
+
+ def paren
+ return variable unless try_tok(:lparen)
+ was_in_parens = @in_parens
+ @in_parens = true
+ start_pos = source_position
+ e = map
+ end_pos = source_position
+ assert_tok(:rparen)
+ return e || node(Sass::Script::Tree::ListLiteral.new([], nil), start_pos, end_pos)
+ ensure
+ @in_parens = was_in_parens
+ end
+
+ def variable
+ start_pos = source_position
+ c = try_tok(:const)
+ return string unless c
+ node(Tree::Variable.new(*c.value), start_pos)
+ end
+
+ def string
+ first = try_tok(:string)
+ return number unless first
+ str = literal_node(first.value, first.source_range)
+ return str unless try_tok(:string_interpolation)
+ mid = assert_expr :expr
+ assert_tok :end_interpolation
+ last = assert_expr(:string)
+ node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
+ end
+
+ def number
+ tok = try_tok(:number)
+ return selector unless tok
+ num = tok.value
+ num.original = num.to_s unless @in_parens
+ literal_node(num, tok.source_range.start_pos)
+ end
+
+ def selector
+ tok = try_tok(:selector)
+ return literal unless tok
+ node(tok.value, tok.source_range.start_pos)
+ end
+
+ def literal
+ t = try_tok(:color)
+ return literal_node(t.value, t.source_range) if t
+ end
+
+ # It would be possible to have unified #assert and #try methods,
+ # but detecting the method/token difference turns out to be quite expensive.
+
+ EXPR_NAMES = {
+ :string => "string",
+ :default => "expression (e.g. 1px, bold)",
+ :mixin_arglist => "mixin argument",
+ :fn_arglist => "function argument",
+ :splat => "...",
+ :special_fun => '")"',
+ }
+
+ def assert_expr(name, expected = nil)
+ e = send(name)
+ return e if e
+ @lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
+ end
+
+ def assert_tok(name)
+ # Avoids an array allocation caused by argument globbing in assert_toks.
+ t = try_tok(name)
+ return t if t
+ @lexer.expected!(Lexer::TOKEN_NAMES[name] || name.to_s)
+ end
+
+ def assert_toks(*names)
+ t = try_toks(*names)
+ return t if t
+ @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
+ end
+
+ def try_tok(name)
+ # Avoids an array allocation caused by argument globbing in the try_toks method.
+ peeked = @lexer.peek
+ peeked && name == peeked.type && @lexer.next
+ end
+
+ def try_toks(*names)
+ peeked = @lexer.peek
+ peeked && names.include?(peeked.type) && @lexer.next
+ end
+
+ def assert_done
+ return if @lexer.done?
+ @lexer.expected!(EXPR_NAMES[:default])
+ end
+
+ # @overload node(value, source_range)
+ # @param value [Sass::Script::Value::Base]
+ # @param source_range [Sass::Source::Range]
+ # @overload node(value, start_pos, end_pos = source_position)
+ # @param value [Sass::Script::Value::Base]
+ # @param start_pos [Sass::Source::Position]
+ # @param end_pos [Sass::Source::Position]
+ def literal_node(value, source_range_or_start_pos, end_pos = source_position)
+ node(Sass::Script::Tree::Literal.new(value), source_range_or_start_pos, end_pos)
+ end
+
+ # @overload node(node, source_range)
+ # @param node [Sass::Script::Tree::Node]
+ # @param source_range [Sass::Source::Range]
+ # @overload node(node, start_pos, end_pos = source_position)
+ # @param node [Sass::Script::Tree::Node]
+ # @param start_pos [Sass::Source::Position]
+ # @param end_pos [Sass::Source::Position]
+ def node(node, source_range_or_start_pos, end_pos = source_position)
+ source_range =
+ if source_range_or_start_pos.is_a?(Sass::Source::Range)
+ source_range_or_start_pos
+ else
+ range(source_range_or_start_pos, end_pos)
+ end
+
+ node.line = source_range.start_pos.line
+ node.source_range = source_range
+ node.filename = @options[:filename]
+ node
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree.rb
new file mode 100644
index 0000000..8bd4686
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree.rb
@@ -0,0 +1,16 @@
+# The module containing nodes in the SassScript parse tree. These nodes are
+# all subclasses of {Sass::Script::Tree::Node}.
+module Sass::Script::Tree
+end
+
+require 'sass/script/tree/node'
+require 'sass/script/tree/variable'
+require 'sass/script/tree/funcall'
+require 'sass/script/tree/operation'
+require 'sass/script/tree/unary_operation'
+require 'sass/script/tree/interpolation'
+require 'sass/script/tree/string_interpolation'
+require 'sass/script/tree/literal'
+require 'sass/script/tree/list_literal'
+require 'sass/script/tree/map_literal'
+require 'sass/script/tree/selector'
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/funcall.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/funcall.rb
new file mode 100644
index 0000000..8a590dc
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/funcall.rb
@@ -0,0 +1,306 @@
+require 'sass/script/functions'
+require 'sass/util/normalized_map'
+
+module Sass::Script::Tree
+ # A SassScript parse node representing a function call.
+ #
+ # A function call either calls one of the functions in
+ # {Sass::Script::Functions}, or if no function with the given name exists it
+ # returns a string representation of the function call.
+ class Funcall < Node
+ # The name of the function.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # The arguments to the function.
+ #
+ # @return [Array<Node>]
+ attr_reader :args
+
+ # The keyword arguments to the function.
+ #
+ # @return [Sass::Util::NormalizedMap<Node>]
+ attr_reader :keywords
+
+ # The first splat argument for this function, if one exists.
+ #
+ # This could be a list of positional arguments, a map of keyword
+ # arguments, or an arglist containing both.
+ #
+ # @return [Node?]
+ attr_accessor :splat
+
+ # The second splat argument for this function, if one exists.
+ #
+ # If this exists, it's always a map of keyword arguments, and
+ # \{#splat} is always either a list or an arglist.
+ #
+ # @return [Node?]
+ attr_accessor :kwarg_splat
+
+ # @param name [String] See \{#name}
+ # @param args [Array<Node>] See \{#args}
+ # @param keywords [Sass::Util::NormalizedMap<Node>] See \{#keywords}
+ # @param splat [Node] See \{#splat}
+ # @param kwarg_splat [Node] See \{#kwarg_splat}
+ def initialize(name, args, keywords, splat, kwarg_splat)
+ @name = name
+ @args = args
+ @keywords = keywords
+ @splat = splat
+ @kwarg_splat = kwarg_splat
+ super()
+ end
+
+ # @return [String] A string representation of the function call
+ def inspect
+ args = @args.map {|a| a.inspect}.join(', ')
+ keywords = Sass::Util.hash_to_a(@keywords.as_stored).
+ map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
+ # rubocop:disable RedundantSelf
+ if self.splat
+ splat = args.empty? && keywords.empty? ? "" : ", "
+ splat = "#{splat}#{self.splat.inspect}..."
+ splat = "#{splat}, #{kwarg_splat.inspect}..." if kwarg_splat
+ end
+ # rubocop:enable RedundantSelf
+ "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
+ end
+
+ # @see Node#to_sass
+ def to_sass(opts = {})
+ arg_to_sass = lambda do |arg|
+ sass = arg.to_sass(opts)
+ sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma
+ sass
+ end
+
+ args = @args.map(&arg_to_sass)
+ keywords = Sass::Util.hash_to_a(@keywords.as_stored).
+ map {|k, v| "$#{dasherize(k, opts)}: #{arg_to_sass[v]}"}
+
+ # rubocop:disable RedundantSelf
+ if self.splat
+ splat = "#{arg_to_sass[self.splat]}..."
+ kwarg_splat = "#{arg_to_sass[self.kwarg_splat]}..." if self.kwarg_splat
+ end
+ # rubocop:enable RedundantSelf
+
+ arglist = [args, splat, keywords, kwarg_splat].flatten.compact.join(', ')
+ "#{dasherize(name, opts)}(#{arglist})"
+ end
+
+ # Returns the arguments to the function.
+ #
+ # @return [Array<Node>]
+ # @see Node#children
+ def children
+ res = @args + @keywords.values
+ res << @splat if @splat
+ res << @kwarg_splat if @kwarg_splat
+ res
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@args', args.map {|a| a.deep_copy})
+ copied_keywords = Sass::Util::NormalizedMap.new
+ @keywords.as_stored.each {|k, v| copied_keywords[k] = v.deep_copy}
+ node.instance_variable_set('@keywords', copied_keywords)
+ node
+ end
+
+ protected
+
+ # Evaluates the function call.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value] The SassScript object that is the value of the function call
+ # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
+ def _perform(environment)
+ args = Sass::Util.enum_with_index(@args).
+ map {|a, i| perform_arg(a, environment, signature && signature.args[i])}
+ keywords = Sass::Util.map_hash(@keywords) do |k, v|
+ [k, perform_arg(v, environment, k.tr('-', '_'))]
+ end
+ splat = Sass::Tree::Visitors::Perform.perform_splat(
+ @splat, keywords, @kwarg_splat, environment)
+ if (fn = environment.function(@name))
+ return without_original(perform_sass_fn(fn, args, splat, environment))
+ end
+
+ args = construct_ruby_args(ruby_name, args, splat, environment)
+
+ if Sass::Script::Functions.callable?(ruby_name)
+ local_environment = Sass::Environment.new(environment.global_env, environment.options)
+ local_environment.caller = Sass::ReadOnlyEnvironment.new(environment, environment.options)
+ result = opts(Sass::Script::Functions::EvaluationContext.new(
+ local_environment).send(ruby_name, *args))
+ without_original(result)
+ else
+ opts(to_literal(args))
+ end
+ rescue ArgumentError => e
+ reformat_argument_error(e)
+ end
+
+ # Compass historically overrode this before it changed name to {Funcall#to_value}.
+ # We should get rid of it in the future.
+ def to_literal(args)
+ to_value(args)
+ end
+
+ # This method is factored out from `_perform` so that compass can override
+ # it with a cross-browser implementation for functions that require vendor prefixes
+ # in the generated css.
+ def to_value(args)
+ Sass::Script::Value::String.new("#{name}(#{args.join(', ')})")
+ end
+
+ private
+
+ def ruby_name
+ @ruby_name ||= @name.tr('-', '_')
+ end
+
+ def perform_arg(argument, environment, name)
+ return argument if signature && signature.delayed_args.include?(name)
+ argument.perform(environment)
+ end
+
+ def signature
+ @signature ||= Sass::Script::Functions.signature(name.to_sym, @args.size, @keywords.size)
+ end
+
+ def without_original(value)
+ return value unless value.is_a?(Sass::Script::Value::Number)
+ value = value.dup
+ value.original = nil
+ value
+ end
+
+ def construct_ruby_args(name, args, splat, environment)
+ args += splat.to_a if splat
+
+ # All keywords are contained in splat.keywords for consistency,
+ # even if there were no splats passed in.
+ old_keywords_accessed = splat.keywords_accessed
+ keywords = splat.keywords
+ splat.keywords_accessed = old_keywords_accessed
+
+ unless (signature = Sass::Script::Functions.signature(name.to_sym, args.size, keywords.size))
+ return args if keywords.empty?
+ raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
+ end
+
+ # If the user passes more non-keyword args than the function expects,
+ # but it does expect keyword args, Ruby's arg handling won't raise an error.
+ # Since we don't want to make functions think about this,
+ # we'll handle it for them here.
+ if signature.var_kwargs && !signature.var_args && args.size > signature.args.size
+ raise Sass::SyntaxError.new(
+ "#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'")
+ elsif keywords.empty?
+ return args
+ end
+
+ argnames = signature.args[args.size..-1] || []
+ deprecated_argnames = (signature.deprecated && signature.deprecated[args.size..-1]) || []
+ args = args + argnames.zip(deprecated_argnames).map do |(argname, deprecated_argname)|
+ if keywords.has_key?(argname)
+ keywords.delete(argname)
+ elsif deprecated_argname && keywords.has_key?(deprecated_argname)
+ deprecated_argname = keywords.denormalize(deprecated_argname)
+ Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " +
+ "`#{ name}()' has been renamed to `$#{argname}'.")
+ keywords.delete(deprecated_argname)
+ else
+ raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
+ end
+ end
+
+ if keywords.size > 0
+ if signature.var_kwargs
+ # Don't pass a NormalizedMap to a Ruby function.
+ args << keywords.to_hash
+ else
+ argname = keywords.keys.sort.first
+ if signature.args.include?(argname)
+ raise Sass::SyntaxError.new(
+ "Function #{name} was passed argument $#{argname} both by position and by name")
+ else
+ raise Sass::SyntaxError.new(
+ "Function #{name} doesn't have an argument named $#{argname}")
+ end
+ end
+ end
+
+ args
+ end
+
+ def perform_sass_fn(function, args, splat, environment)
+ Sass::Tree::Visitors::Perform.perform_arguments(function, args, splat, environment) do |env|
+ env.caller = Sass::Environment.new(environment)
+
+ val = catch :_sass_return do
+ function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)}
+ raise Sass::SyntaxError.new("Function #{ name} finished without @return")
+ end
+ val
+ end
+ end
+
+ def reformat_argument_error(e)
+ message = e.message
+
+ # If this is a legitimate Ruby-raised argument error, re-raise it.
+ # Otherwise, it's an error in the user's stylesheet, so wrap it.
+ if Sass::Util.rbx?
+ # Rubinius has a different error report string than vanilla Ruby. It
+ # also doesn't put the actual method for which the argument error was
+ # thrown in the backtrace, nor does it include `send`, so we look for
+ # `_perform`.
+ if e.message =~ /^method '([^']+)': given (\d+), expected (\d+)/
+ error_name, given, expected = $1, $2, $3
+ raise e if error_name != ruby_name || e.backtrace[0] !~ /:in `_perform'$/
+ message = "wrong number of arguments (#{given} for #{expected})"
+ end
+ elsif Sass::Util.jruby?
+ if Sass::Util.jruby1_6?
+ should_maybe_raise = e.message =~ /^wrong number of arguments \((\d+) for (\d+)\)/ &&
+ # The one case where JRuby does include the Ruby name of the function
+ # is manually-thrown ArgumentErrors, which are indistinguishable from
+ # legitimate ArgumentErrors. We treat both of these as
+ # Sass::SyntaxErrors even though it can hide Ruby errors.
+ e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
+ else
+ should_maybe_raise =
+ e.message =~ /^wrong number of arguments calling `[^`]+` \((\d+) for (\d+)\)/
+ given, expected = $1, $2
+ end
+
+ if should_maybe_raise
+ # JRuby 1.7 includes __send__ before send and _perform.
+ trace = e.backtrace.dup
+ raise e if !Sass::Util.jruby1_6? && trace.shift !~ /:in `__send__'$/
+
+ # JRuby (as of 1.7.2) doesn't put the actual method
+ # for which the argument error was thrown in the backtrace, so we
+ # detect whether our send threw an argument error.
+ if !(trace[0] =~ /:in `send'$/ && trace[1] =~ /:in `_perform'$/)
+ raise e
+ elsif !Sass::Util.jruby1_6?
+ # JRuby 1.7 doesn't use standard formatting for its ArgumentErrors.
+ message = "wrong number of arguments (#{given} for #{expected})"
+ end
+ end
+ elsif e.message =~ /^wrong number of arguments \(\d+ for \d+\)/ &&
+ e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
+ raise e
+ end
+ raise Sass::SyntaxError.new("#{message} for `#{name}'")
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/interpolation.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/interpolation.rb
new file mode 100644
index 0000000..e619df4
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/interpolation.rb
@@ -0,0 +1,118 @@
+module Sass::Script::Tree
+ # A SassScript object representing `#{}` interpolation outside a string.
+ #
+ # @see StringInterpolation
+ class Interpolation < Node
+ # @return [Node] The SassScript before the interpolation
+ attr_reader :before
+
+ # @return [Node] The SassScript within the interpolation
+ attr_reader :mid
+
+ # @return [Node] The SassScript after the interpolation
+ attr_reader :after
+
+ # @return [Boolean] Whether there was whitespace between `before` and `#{`
+ attr_reader :whitespace_before
+
+ # @return [Boolean] Whether there was whitespace between `}` and `after`
+ attr_reader :whitespace_after
+
+ # @return [Boolean] Whether the original format of the interpolation was
+ # plain text, not an interpolation. This is used when converting back to
+ # SassScript.
+ attr_reader :originally_text
+
+ # @return [Boolean] Whether a color value passed to the interpolation should
+ # generate a warning.
+ attr_reader :warn_for_color
+
+ # Interpolation in a property is of the form `before #{mid} after`.
+ #
+ # @param before [Node] See {Interpolation#before}
+ # @param mid [Node] See {Interpolation#mid}
+ # @param after [Node] See {Interpolation#after}
+ # @param wb [Boolean] See {Interpolation#whitespace_before}
+ # @param wa [Boolean] See {Interpolation#whitespace_after}
+ # @param originally_text [Boolean] See {Interpolation#originally_text}
+ # @param warn_for_color [Boolean] See {Interpolation#warn_for_color}
+ # @comment
+ # rubocop:disable ParameterLists
+ def initialize(before, mid, after, wb, wa, originally_text = false, warn_for_color = false)
+ # rubocop:enable ParameterLists
+ @before = before
+ @mid = mid
+ @after = after
+ @whitespace_before = wb
+ @whitespace_after = wa
+ @originally_text = originally_text
+ @warn_for_color = warn_for_color
+ end
+
+ # @return [String] A human-readable s-expression representation of the interpolation
+ def inspect
+ "(interpolation #{ before inspect} #{ mid inspect} #{ after inspect})"
+ end
+
+ # @see Node#to_sass
+ def to_sass(opts = {})
+ res = ""
+ res << @before.to_sass(opts) if @before
+ res << ' ' if @before && @whitespace_before
+ res << '#{' unless @originally_text
+ res << @mid.to_sass(opts)
+ res << '}' unless @originally_text
+ res << ' ' if @after && @whitespace_after
+ res << @after.to_sass(opts) if @after
+ res
+ end
+
+ # Returns the three components of the interpolation, `before`, `mid`, and `after`.
+ #
+ # @return [Array<Node>]
+ # @see #initialize
+ # @see Node#children
+ def children
+ [ before, @mid, @after].compact
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@before', @before.deep_copy) if @before
+ node.instance_variable_set('@mid', @mid.deep_copy)
+ node.instance_variable_set('@after', @after.deep_copy) if @after
+ node
+ end
+
+ protected
+
+ # Evaluates the interpolation.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value::String]
+ # The SassScript string that is the value of the interpolation
+ def _perform(environment)
+ res = ""
+ res << @before.perform(environment).to_s if @before
+ res << " " if @before && @whitespace_before
+
+ val = @mid.perform(environment)
+ if @warn_for_color && val.is_a?(Sass::Script::Value::Color) && val.name
+ alternative = Operation.new(Sass::Script::Value::String.new("", :string), @mid, :plus)
+ Sass::Util.sass_warn <<MESSAGE
+WARNING on line #{line}, column #{source_range.start_pos.offset}#{" of #{filename}" if filename}:
+You probably don't mean to use the color value `#{val}' in interpolation here.
+It may end up represented as #{val.inspect}, which will likely produce invalid CSS.
+Always quote color names when using them as strings (for example, "#{val}").
+If you really want to use the color value here, use `#{alternative.to_sass}'.
+MESSAGE
+ end
+
+ res << val.to_s(:quote => :none)
+ res << " " if @after && @whitespace_after
+ res << @after.perform(environment).to_s if @after
+ opts(Sass::Script::Value::String.new(res))
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/list_literal.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/list_literal.rb
new file mode 100644
index 0000000..292e719
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/list_literal.rb
@@ -0,0 +1,77 @@
+module Sass::Script::Tree
+ # A parse tree node representing a list literal. When resolved, this returns a
+ # {Sass::Tree::Value::List}.
+ class ListLiteral < Node
+ # The parse nodes for members of this list.
+ #
+ # @return [Array<Node>]
+ attr_reader :elements
+
+ # The operator separating the values of the list. Either `:comma` or
+ # `:space`.
+ #
+ # @return [Symbol]
+ attr_reader :separator
+
+ # Creates a new list literal.
+ #
+ # @param elements [Array<Node>] See \{#elements}
+ # @param separator [Symbol] See \{#separator}
+ def initialize(elements, separator)
+ @elements = elements
+ @separator = separator
+ end
+
+ # @see Node#children
+ def children; elements; end
+
+ # @see Value#to_sass
+ def to_sass(opts = {})
+ return "()" if elements.empty?
+ precedence = Sass::Script::Parser.precedence_of(separator)
+ members = elements.map do |v|
+ if v.is_a?(ListLiteral) && Sass::Script::Parser.precedence_of(v.separator) <= precedence ||
+ separator == :space && v.is_a?(UnaryOperation) &&
+ (v.operator == :minus || v.operator == :plus)
+ "(#{v.to_sass(opts)})"
+ else
+ v.to_sass(opts)
+ end
+ end
+
+ return "(#{members.first},)" if separator == :comma && members.length == 1
+
+ members.join(sep_str(nil))
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@elements', elements.map {|e| e.deep_copy})
+ node
+ end
+
+ def inspect
+ "(#{elements.map {|e| e.inspect}.join(separator == :space ? ' ' : ', ')})"
+ end
+
+ protected
+
+ def _perform(environment)
+ list = Sass::Script::Value::List.new(
+ elements.map {|e| e.perform(environment)},
+ separator)
+ list.source_range = source_range
+ list.options = options
+ list
+ end
+
+ private
+
+ def sep_str(opts = options)
+ return ' ' if separator == :space
+ return ',' if opts && opts[:style] == :compressed
+ ', '
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/literal.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/literal.rb
new file mode 100644
index 0000000..4dd3ca0
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/literal.rb
@@ -0,0 +1,45 @@
+module Sass::Script::Tree
+ # The parse tree node for a literal scalar value. This wraps an instance of
+ # {Sass::Script::Value::Base}.
+ #
+ # List literals should use {ListLiteral} instead.
+ class Literal < Node
+ # The wrapped value.
+ #
+ # @return [Sass::Script::Value::Base]
+ attr_reader :value
+
+ # Creates a new literal value.
+ #
+ # @param value [Sass::Script::Value::Base]
+ # @see #value
+ def initialize(value)
+ @value = value
+ end
+
+ # @see Node#children
+ def children; []; end
+
+ # @see Node#to_sass
+ def to_sass(opts = {}); value.to_sass(opts); end
+
+ # @see Node#deep_copy
+ def deep_copy; dup; end
+
+ # @see Node#options=
+ def options=(options)
+ value.options = options
+ end
+
+ def inspect
+ value.inspect
+ end
+
+ protected
+
+ def _perform(environment)
+ value.source_range = source_range
+ value
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/map_literal.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/map_literal.rb
new file mode 100644
index 0000000..a2f8856
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/map_literal.rb
@@ -0,0 +1,64 @@
+module Sass::Script::Tree
+ # A class representing a map literal. When resolved, this returns a
+ # {Sass::Script::Node::Map}.
+ class MapLiteral < Node
+ # The key/value pairs that make up this map node. This isn't a Hash so that
+ # we can detect key collisions once all the keys have been performed.
+ #
+ # @return [Array<(Node, Node)>]
+ attr_reader :pairs
+
+ # Creates a new map literal.
+ #
+ # @param pairs [Array<(Node, Node)>] See \{#pairs}
+ def initialize(pairs)
+ @pairs = pairs
+ end
+
+ # @see Node#children
+ def children
+ @pairs.flatten
+ end
+
+ # @see Node#to_sass
+ def to_sass(opts = {})
+ return "()" if pairs.empty?
+
+ to_sass = lambda do |value|
+ if value.is_a?(ListLiteral) && value.separator == :comma
+ "(#{value.to_sass(opts)})"
+ else
+ value.to_sass(opts)
+ end
+ end
+
+ "(" + pairs.map {|(k, v)| "#{to_sass[k]}: #{to_sass[v]}"}.join(', ') + ")"
+ end
+ alias_method :inspect, :to_sass
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@pairs',
+ pairs.map {|(k, v)| [k.deep_copy, v.deep_copy]})
+ node
+ end
+
+ protected
+
+ # @see Node#_perform
+ def _perform(environment)
+ keys = Set.new
+ map = Sass::Script::Value::Map.new(Sass::Util.to_hash(pairs.map do |(k, v)|
+ k, v = k.perform(environment), v.perform(environment)
+ if keys.include?(k)
+ raise Sass::SyntaxError.new("Duplicate key #{k.inspect} in map #{to_sass}.")
+ end
+ keys << k
+ [k, v]
+ end))
+ map.options = options
+ map
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/node.rb
new file mode 100644
index 0000000..953e121
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/node.rb
@@ -0,0 +1,109 @@
+module Sass::Script::Tree
+ # The abstract superclass for SassScript parse tree nodes.
+ #
+ # Use \{#perform} to evaluate a parse tree.
+ class Node
+ # The options hash for this node.
+ #
+ # @return [{Symbol => Object}]
+ attr_reader :options
+
+ # The line of the document on which this node appeared.
+ #
+ # @return [Fixnum]
+ attr_accessor :line
+
+ # The source range in the document on which this node appeared.
+ #
+ # @return [Sass::Source::Range]
+ attr_accessor :source_range
+
+ # The file name of the document on which this node appeared.
+ #
+ # @return [String]
+ attr_accessor :filename
+
+ # Sets the options hash for this node,
+ # as well as for all child nodes.
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ #
+ # @param options [{Symbol => Object}] The options
+ def options=(options)
+ @options = options
+ children.each do |c|
+ if c.is_a? Hash
+ c.values.each {|v| v.options = options}
+ else
+ c.options = options
+ end
+ end
+ end
+
+ # Evaluates the node.
+ #
+ # \{#perform} shouldn't be overridden directly;
+ # instead, override \{#\_perform}.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value] The SassScript object that is the value of the SassScript
+ def perform(environment)
+ _perform(environment)
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:line => line)
+ raise e
+ end
+
+ # Returns all child nodes of this node.
+ #
+ # @return [Array<Node>]
+ def children
+ Sass::Util.abstract(self)
+ end
+
+ # Returns the text of this SassScript expression.
+ #
+ # @return [String]
+ def to_sass(opts = {})
+ Sass::Util.abstract(self)
+ end
+
+ # Returns a deep clone of this node.
+ # The child nodes are cloned, but options are not.
+ #
+ # @return [Node]
+ def deep_copy
+ Sass::Util.abstract(self)
+ end
+
+ protected
+
+ # Converts underscores to dashes if the :dasherize option is set.
+ def dasherize(s, opts)
+ if opts[:dasherize]
+ s.gsub(/_/, '-')
+ else
+ s
+ end
+ end
+
+ # Evaluates this node.
+ # Note that all {Sass::Script::Value} objects created within this method
+ # should have their \{#options} attribute set, probably via \{#opts}.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value] The SassScript object that is the value of the SassScript
+ # @see #perform
+ def _perform(environment)
+ Sass::Util.abstract(self)
+ end
+
+ # Sets the \{#options} field on the given value and returns it.
+ #
+ # @param value [Sass::Script::Value]
+ # @return [Sass::Script::Value]
+ def opts(value)
+ value.options = options
+ value
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/operation.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/operation.rb
new file mode 100644
index 0000000..859bb45
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/operation.rb
@@ -0,0 +1,115 @@
+module Sass::Script::Tree
+ # A SassScript parse node representing a binary operation,
+ # such as `$a + $b` or `"foo" + 1`.
+ class Operation < Node
+ attr_reader :operand1
+ attr_reader :operand2
+ attr_reader :operator
+
+ # @param operand1 [Sass::Script::Tree::Node] The parse-tree node
+ # for the right-hand side of the operator
+ # @param operand2 [Sass::Script::Tree::Node] The parse-tree node
+ # for the left-hand side of the operator
+ # @param operator [Symbol] The operator to perform.
+ # This should be one of the binary operator names in {Sass::Script::Lexer::OPERATORS}
+ def initialize(operand1, operand2, operator)
+ @operand1 = operand1
+ @operand2 = operand2
+ @operator = operator
+ super()
+ end
+
+ # @return [String] A human-readable s-expression representation of the operation
+ def inspect
+ "(#{ operator inspect} #{ operand1 inspect} #{ operand2 inspect})"
+ end
+
+ # @see Node#to_sass
+ def to_sass(opts = {})
+ o1 = operand_to_sass @operand1, :left, opts
+ o2 = operand_to_sass @operand2, :right, opts
+ sep =
+ case @operator
+ when :comma; ", "
+ when :space; " "
+ else; " #{Sass::Script::Lexer::OPERATORS_REVERSE[ operator]} "
+ end
+ "#{o1}#{sep}#{o2}"
+ end
+
+ # Returns the operands for this operation.
+ #
+ # @return [Array<Node>]
+ # @see Node#children
+ def children
+ [ operand1, @operand2]
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@operand1', @operand1.deep_copy)
+ node.instance_variable_set('@operand2', @operand2.deep_copy)
+ node
+ end
+
+ protected
+
+ # Evaluates the operation.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value] The SassScript object that is the value of the operation
+ # @raise [Sass::SyntaxError] if the operation is undefined for the operands
+ def _perform(environment)
+ value1 = @operand1.perform(environment)
+
+ # Special-case :and and :or to support short-circuiting.
+ if @operator == :and
+ return value1.to_bool ? @operand2.perform(environment) : value1
+ elsif @operator == :or
+ return value1.to_bool ? value1 : @operand2.perform(environment)
+ end
+
+ value2 = @operand2.perform(environment)
+
+ if (value1.is_a?(Sass::Script::Value::Null) || value2.is_a?(Sass::Script::Value::Null)) &&
+ @operator != :eq && @operator != :neq
+ raise Sass::SyntaxError.new(
+ "Invalid null operation: \"#{value1.inspect} #{ operator} #{value2.inspect}\".")
+ end
+
+ begin
+ result = opts(value1.send(@operator, value2))
+ rescue NoMethodError => e
+ raise e unless e.name.to_s == @operator.to_s
+ raise Sass::SyntaxError.new("Undefined operation: \"#{value1} #{ operator} #{value2}\".")
+ end
+
+ if @operator == :eq && value1.is_a?(Sass::Script::Value::Number) &&
+ value2.is_a?(Sass::Script::Value::Number) && result == Sass::Script::Value::Bool::TRUE &&
+ value1.unitless? != value2.unitless?
+ Sass::Util.sass_warn <<WARNING
+DEPRECATION WARNING on line #{line}#{" of #{filename}" if filename}:
+The result of `#{value1} == #{value2}` will be `false` in future releases of Sass.
+Unitless numbers will no longer be equal to the same numbers with units.
+WARNING
+ end
+
+ result
+ end
+
+ private
+
+ def operand_to_sass(op, side, opts)
+ return "(#{op.to_sass(opts)})" if op.is_a?(Sass::Script::Tree::ListLiteral)
+ return op.to_sass(opts) unless op.is_a?(Operation)
+
+ pred = Sass::Script::Parser.precedence_of(@operator)
+ sub_pred = Sass::Script::Parser.precedence_of(op.operator)
+ assoc = Sass::Script::Parser.associative?(@operator)
+ return "(#{op.to_sass(opts)})" if sub_pred < pred ||
+ (side == :right && sub_pred == pred && !assoc)
+ op.to_sass(opts)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/selector.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/selector.rb
new file mode 100644
index 0000000..4a852cd
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/selector.rb
@@ -0,0 +1,26 @@
+module Sass::Script::Tree
+ # A SassScript node that will resolve to the current selector.
+ class Selector < Node
+ def initialize; end
+
+ def children
+ []
+ end
+
+ def to_sass(opts = {})
+ '&'
+ end
+
+ def deep_copy
+ dup
+ end
+
+ protected
+
+ def _perform(environment)
+ selector = environment.selector
+ return opts(Sass::Script::Value::Null.new) unless selector
+ opts(selector.to_sass_script)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/string_interpolation.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/string_interpolation.rb
new file mode 100644
index 0000000..6c7575d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/string_interpolation.rb
@@ -0,0 +1,104 @@
+module Sass::Script::Tree
+ # A SassScript object representing `#{}` interpolation within a string.
+ #
+ # @see Interpolation
+ class StringInterpolation < Node
+ # Interpolation in a string is of the form `"before #{mid} after"`,
+ # where `before` and `after` may include more interpolation.
+ #
+ # @param before [Node] The string before the interpolation
+ # @param mid [Node] The SassScript within the interpolation
+ # @param after [Node] The string after the interpolation
+ def initialize(before, mid, after)
+ @before = before
+ @mid = mid
+ @after = after
+ end
+
+ # @return [String] A human-readable s-expression representation of the interpolation
+ def inspect
+ "(string_interpolation #{ before inspect} #{ mid inspect} #{ after inspect})"
+ end
+
+ # @see Node#to_sass
+ def to_sass(opts = {})
+ # We can get rid of all of this when we remove the deprecated :equals context
+ # XXX CE: It's gone now but I'm not sure what can be removed now.
+ before_unquote, before_quote_char, before_str = parse_str(@before.to_sass(opts))
+ after_unquote, after_quote_char, after_str = parse_str(@after.to_sass(opts))
+ unquote = before_unquote || after_unquote ||
+ (before_quote_char && !after_quote_char && !after_str.empty?) ||
+ (!before_quote_char && after_quote_char && !before_str.empty?)
+ quote_char =
+ if before_quote_char && after_quote_char && before_quote_char != after_quote_char
+ before_str.gsub!("\\'", "'")
+ before_str.gsub!('"', "\\\"")
+ after_str.gsub!("\\'", "'")
+ after_str.gsub!('"', "\\\"")
+ '"'
+ else
+ before_quote_char || after_quote_char
+ end
+
+ res = ""
+ res << 'unquote(' if unquote
+ res << quote_char if quote_char
+ res << before_str
+ res << '#{' << @mid.to_sass(opts) << '}'
+ res << after_str
+ res << quote_char if quote_char
+ res << ')' if unquote
+ res
+ end
+
+ # Returns the three components of the interpolation, `before`, `mid`, and `after`.
+ #
+ # @return [Array<Node>]
+ # @see #initialize
+ # @see Node#children
+ def children
+ [ before, @mid, @after].compact
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@before', @before.deep_copy) if @before
+ node.instance_variable_set('@mid', @mid.deep_copy)
+ node.instance_variable_set('@after', @after.deep_copy) if @after
+ node
+ end
+
+ protected
+
+ # Evaluates the interpolation.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value::String]
+ # The SassScript string that is the value of the interpolation
+ def _perform(environment)
+ res = ""
+ before = @before.perform(environment)
+ res << before.value
+ mid = @mid.perform(environment)
+ res << (mid.is_a?(Sass::Script::Value::String) ? mid.value : mid.to_s(:quote => :none))
+ res << @after.perform(environment).value
+ opts(Sass::Script::Value::String.new(res, before.type))
+ end
+
+ private
+
+ def parse_str(str)
+ case str
+ when /^unquote\((["'])(.*)\1\)$/
+ return true, $1, $2
+ when '""'
+ return false, nil, ""
+ when /^(["'])(.*)\1$/
+ return false, $1, $2
+ else
+ return false, nil, str
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/unary_operation.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/unary_operation.rb
new file mode 100644
index 0000000..b32da08
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/unary_operation.rb
@@ -0,0 +1,69 @@
+module Sass::Script::Tree
+ # A SassScript parse node representing a unary operation,
+ # such as `-$b` or `not true`.
+ #
+ # Currently only `-`, `/`, and `not` are unary operators.
+ class UnaryOperation < Node
+ # @return [Symbol] The operation to perform
+ attr_reader :operator
+
+ # @return [Script::Node] The parse-tree node for the object of the operator
+ attr_reader :operand
+
+ # @param operand [Script::Node] See \{#operand}
+ # @param operator [Symbol] See \{#operator}
+ def initialize(operand, operator)
+ @operand = operand
+ @operator = operator
+ super()
+ end
+
+ # @return [String] A human-readable s-expression representation of the operation
+ def inspect
+ "(#{ operator inspect} #{ operand inspect})"
+ end
+
+ # @see Node#to_sass
+ def to_sass(opts = {})
+ operand = @operand.to_sass(opts)
+ if @operand.is_a?(Operation) ||
+ (@operator == :minus &&
+ (operand =~ Sass::SCSS::RX::IDENT) == 0)
+ operand = "(#{ operand to_sass(opts)})"
+ end
+ op = Sass::Script::Lexer::OPERATORS_REVERSE[ operator]
+ op + (op =~ /[a-z]/ ? " " : "") + operand
+ end
+
+ # Returns the operand of the operation.
+ #
+ # @return [Array<Node>]
+ # @see Node#children
+ def children
+ [ operand]
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@operand', @operand.deep_copy)
+ node
+ end
+
+ protected
+
+ # Evaluates the operation.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value] The SassScript object that is the value of the operation
+ # @raise [Sass::SyntaxError] if the operation is undefined for the operand
+ def _perform(environment)
+ operator = "unary_#{ operator}"
+ value = @operand.perform(environment)
+ value.send(operator)
+ rescue NoMethodError => e
+ raise e unless e.name.to_s == operator.to_s
+ raise Sass::SyntaxError.new("Undefined unary operation: \"#{ operator} #{value}\".")
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/tree/variable.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/variable.rb
new file mode 100644
index 0000000..3480db5
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/tree/variable.rb
@@ -0,0 +1,57 @@
+module Sass::Script::Tree
+ # A SassScript parse node representing a variable.
+ class Variable < Node
+ # The name of the variable.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # The underscored name of the variable.
+ #
+ # @return [String]
+ attr_reader :underscored_name
+
+ # @param name [String] See \{#name}
+ def initialize(name)
+ @name = name
+ @underscored_name = name.gsub(/-/, "_")
+ super()
+ end
+
+ # @return [String] A string representation of the variable
+ def inspect(opts = {})
+ "$#{dasherize(name, opts)}"
+ end
+ alias_method :to_sass, :inspect
+
+ # Returns an empty array.
+ #
+ # @return [Array<Node>] empty
+ # @see Node#children
+ def children
+ []
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ dup
+ end
+
+ protected
+
+ # Evaluates the variable.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Sass::Script::Value] The SassScript object that is the value of the variable
+ # @raise [Sass::SyntaxError] if the variable is undefined
+ def _perform(environment)
+ val = environment.var(name)
+ raise Sass::SyntaxError.new("Undefined variable: \"$#{name}\".") unless val
+ if val.is_a?(Sass::Script::Value::Number) && val.original
+ val = val.dup
+ val.original = nil
+ end
+ val
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value.rb
new file mode 100644
index 0000000..0842b58
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value.rb
@@ -0,0 +1,11 @@
+module Sass::Script::Value; end
+
+require 'sass/script/value/base'
+require 'sass/script/value/string'
+require 'sass/script/value/number'
+require 'sass/script/value/color'
+require 'sass/script/value/bool'
+require 'sass/script/value/null'
+require 'sass/script/value/list'
+require 'sass/script/value/arg_list'
+require 'sass/script/value/map'
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/arg_list.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/arg_list.rb
new file mode 100644
index 0000000..2b09882
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/arg_list.rb
@@ -0,0 +1,36 @@
+module Sass::Script::Value
+ # A SassScript object representing a variable argument list. This works just
+ # like a normal list, but can also contain keyword arguments.
+ #
+ # The keyword arguments attached to this list are unused except when this is
+ # passed as a glob argument to a function or mixin.
+ class ArgList < List
+ # Whether \{#keywords} has been accessed. If so, we assume that all keywords
+ # were valid for the function that created this ArgList.
+ #
+ # @return [Boolean]
+ attr_accessor :keywords_accessed
+
+ # Creates a new argument list.
+ #
+ # @param value [Array<Value>] See \{List#value}.
+ # @param keywords [Hash<String, Value>, NormalizedMap<Value>] See \{#keywords}
+ # @param separator [String] See \{List#separator}.
+ def initialize(value, keywords, separator)
+ super(value, separator)
+ if keywords.is_a?(Sass::Util::NormalizedMap)
+ @keywords = keywords
+ else
+ @keywords = Sass::Util::NormalizedMap.new(keywords)
+ end
+ end
+
+ # The keyword arguments attached to this list.
+ #
+ # @return [NormalizedMap<Value>]
+ def keywords
+ @keywords_accessed = true
+ @keywords
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/base.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/base.rb
new file mode 100644
index 0000000..4cdcbed
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/base.rb
@@ -0,0 +1,240 @@
+module Sass::Script::Value
+ # The abstract superclass for SassScript objects.
+ #
+ # Many of these methods, especially the ones that correspond to SassScript operations,
+ # are designed to be overridden by subclasses which may change the semantics somewhat.
+ # The operations listed here are just the defaults.
+ class Base
+ # Returns the Ruby value of the value.
+ # The type of this value varies based on the subclass.
+ #
+ # @return [Object]
+ attr_reader :value
+
+ # The source range in the document on which this node appeared.
+ #
+ # @return [Sass::Source::Range]
+ attr_accessor :source_range
+
+ # Creates a new value.
+ #
+ # @param value [Object] The object for \{#value}
+ def initialize(value = nil)
+ value.freeze unless value.nil? || value == true || value == false
+ @value = value
+ end
+
+ # Sets the options hash for this node,
+ # as well as for all child nodes.
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ #
+ # @param options [{Symbol => Object}] The options
+ attr_writer :options
+
+ # Returns the options hash for this node.
+ #
+ # @return [{Symbol => Object}]
+ # @raise [Sass::SyntaxError] if the options hash hasn't been set.
+ # This should only happen when the value was created
+ # outside of the parser and \{#to\_s} was called on it
+ def options
+ return @options if @options
+ raise Sass::SyntaxError.new(<<MSG)
+The #options attribute is not set on this #{self.class}.
+ This error is probably occurring because #to_s was called
+ on this value within a custom Sass function without first
+ setting the #options attribute.
+MSG
+ end
+
+ # The SassScript `==` operation.
+ # **Note that this returns a {Sass::Script::Value::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Sass::Script::Value::Bool] True if this value is the same as the other,
+ # false otherwise
+ def eq(other)
+ Sass::Script::Value::Bool.new(self.class == other.class && value == other.value)
+ end
+
+ # The SassScript `!=` operation.
+ # **Note that this returns a {Sass::Script::Value::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Sass::Script::Value::Bool] False if this value is the same as the other,
+ # true otherwise
+ def neq(other)
+ Sass::Script::Value::Bool.new(!eq(other).to_bool)
+ end
+
+ # The SassScript `==` operation.
+ # **Note that this returns a {Sass::Script::Value::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Sass::Script::Value::Bool] True if this value is the same as the other,
+ # false otherwise
+ def unary_not
+ Sass::Script::Value::Bool.new(!to_bool)
+ end
+
+ # The SassScript `=` operation
+ # (used for proprietary MS syntax like `alpha(opacity=20)`).
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Script::Value::String] A string containing both values
+ # separated by `"="`
+ def single_eq(other)
+ Sass::Script::Value::String.new("#{to_s}=#{other.to_s}")
+ end
+
+ # The SassScript `+` operation.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Script::Value::String] A string containing both values
+ # without any separation
+ def plus(other)
+ type = other.is_a?(Sass::Script::Value::String) ? other.type : :identifier
+ Sass::Script::Value::String.new(to_s(:quote => :none) + other.to_s(:quote => :none), type)
+ end
+
+ # The SassScript `-` operation.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Script::Value::String] A string containing both values
+ # separated by `"-"`
+ def minus(other)
+ Sass::Script::Value::String.new("#{to_s}-#{other.to_s}")
+ end
+
+ # The SassScript `/` operation.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Script::Value::String] A string containing both values
+ # separated by `"/"`
+ def div(other)
+ Sass::Script::Value::String.new("#{to_s}/#{other.to_s}")
+ end
+
+ # The SassScript unary `+` operation (e.g. `+$a`).
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Script::Value::String] A string containing the value
+ # preceded by `"+"`
+ def unary_plus
+ Sass::Script::Value::String.new("+#{to_s}")
+ end
+
+ # The SassScript unary `-` operation (e.g. `-$a`).
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Script::Value::String] A string containing the value
+ # preceded by `"-"`
+ def unary_minus
+ Sass::Script::Value::String.new("-#{to_s}")
+ end
+
+ # The SassScript unary `/` operation (e.g. `/$a`).
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Script::Value::String] A string containing the value
+ # preceded by `"/"`
+ def unary_div
+ Sass::Script::Value::String.new("/#{to_s}")
+ end
+
+ # Returns the hash code of this value. Two objects' hash codes should be
+ # equal if the objects are equal.
+ #
+ # @return [Fixnum] The hash code.
+ def hash
+ value.hash
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ # @return [String] A readable representation of the value
+ def inspect
+ value.inspect
+ end
+
+ # @return [Boolean] `true` (the Ruby boolean value)
+ def to_bool
+ true
+ end
+
+ # Compares this object with another.
+ #
+ # @param other [Object] The object to compare with
+ # @return [Boolean] Whether or not this value is equivalent to `other`
+ def ==(other)
+ eq(other).to_bool
+ end
+
+ # @return [Fixnum] The integer value of this value
+ # @raise [Sass::SyntaxError] if this value isn't an integer
+ def to_i
+ raise Sass::SyntaxError.new("#{inspect} is not an integer.")
+ end
+
+ # @raise [Sass::SyntaxError] if this value isn't an integer
+ def assert_int!; to_i; end
+
+ # Returns the separator for this value. For non-list-like values or the
+ # empty list, this will be `nil`. For lists or maps, it will be `:space` or
+ # `:comma`.
+ #
+ # @return [Symbol]
+ def separator; nil; end
+
+ # Returns the value of this value as a list.
+ # Single values are considered the same as single-element lists.
+ #
+ # @return [Array<Value>] This value as a list
+ def to_a
+ [self]
+ end
+
+ # Returns the value of this value as a hash. Most values don't have hash
+ # representations, but [Map]s and empty [List]s do.
+ #
+ # @return [Hash<Value, Value>] This value as a hash
+ # @raise [Sass::SyntaxError] if this value doesn't have a hash representation
+ def to_h
+ raise Sass::SyntaxError.new("#{inspect} is not a map.")
+ end
+
+ # Returns the string representation of this value
+ # as it would be output to the CSS document.
+ #
+ # @options opts :quote [String]
+ # The preferred quote style for quoted strings. If `:none`, strings are
+ # always emitted unquoted.
+ # @return [String]
+ def to_s(opts = {})
+ Sass::Util.abstract(self)
+ end
+ alias_method :to_sass, :to_s
+
+ # Returns whether or not this object is null.
+ #
+ # @return [Boolean] `false`
+ def null?
+ false
+ end
+
+ protected
+
+ # Evaluates the value.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Value] This value
+ def _perform(environment)
+ self
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/bool.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/bool.rb
new file mode 100644
index 0000000..fd1789b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/bool.rb
@@ -0,0 +1,35 @@
+module Sass::Script::Value
+ # A SassScript object representing a boolean (true or false) value.
+ class Bool < Base
+ # The true value in SassScript.
+ #
+ # This is assigned before new is overridden below so that we use the default implementation.
+ TRUE = new(true)
+
+ # The false value in SassScript.
+ #
+ # This is assigned before new is overridden below so that we use the default implementation.
+ FALSE = new(false)
+
+ # We override object creation so that users of the core API
+ # will not need to know that booleans are specific constants.
+ #
+ # @param value A ruby value that will be tested for truthiness.
+ # @return [Bool] TRUE if value is truthy, FALSE if value is falsey
+ def self.new(value)
+ value ? TRUE : FALSE
+ end
+
+ # The Ruby value of the boolean.
+ #
+ # @return [Boolean]
+ attr_reader :value
+ alias_method :to_bool, :value
+
+ # @return [String] "true" or "false"
+ def to_s(opts = {})
+ @value.to_s
+ end
+ alias_method :to_sass, :to_s
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/color.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/color.rb
new file mode 100644
index 0000000..884936a
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/color.rb
@@ -0,0 +1,680 @@
+module Sass::Script::Value
+ # A SassScript object representing a CSS color.
+ #
+ # A color may be represented internally as RGBA, HSLA, or both.
+ # It's originally represented as whatever its input is;
+ # if it's created with RGB values, it's represented as RGBA,
+ # and if it's created with HSL values, it's represented as HSLA.
+ # Once a property is accessed that requires the other representation --
+ # for example, \{#red} for an HSL color --
+ # that component is calculated and cached.
+ #
+ # The alpha channel of a color is independent of its RGB or HSL representation.
+ # It's always stored, as 1 if nothing else is specified.
+ # If only the alpha channel is modified using \{#with},
+ # the cached RGB and HSL values are retained.
+ class Color < Base
+ # @private
+ #
+ # Convert a ruby integer to a rgba components
+ # @param color [Fixnum]
+ # @return [Array<Fixnum>] Array of 4 numbers representing r,g,b and alpha
+ def self.int_to_rgba(color)
+ rgba = (0..3).map {|n| color >> (n << 3) & 0xff}.reverse
+ rgba[-1] = rgba[-1] / 255.0
+ rgba
+ end
+
+ ALTERNATE_COLOR_NAMES = Sass::Util.map_vals({
+ 'aqua' => 0x00FFFFFF,
+ 'darkgrey' => 0xA9A9A9FF,
+ 'darkslategrey' => 0x2F4F4FFF,
+ 'dimgrey' => 0x696969FF,
+ 'fuchsia' => 0xFF00FFFF,
+ 'grey' => 0x808080FF,
+ 'lightgrey' => 0xD3D3D3FF,
+ 'lightslategrey' => 0x778899FF,
+ 'slategrey' => 0x708090FF,
+ }, &method(:int_to_rgba))
+
+ # A hash from color names to `[red, green, blue]` value arrays.
+ COLOR_NAMES = Sass::Util.map_vals({
+ 'aliceblue' => 0xF0F8FFFF,
+ 'antiquewhite' => 0xFAEBD7FF,
+ 'aquamarine' => 0x7FFFD4FF,
+ 'azure' => 0xF0FFFFFF,
+ 'beige' => 0xF5F5DCFF,
+ 'bisque' => 0xFFE4C4FF,
+ 'black' => 0x000000FF,
+ 'blanchedalmond' => 0xFFEBCDFF,
+ 'blue' => 0x0000FFFF,
+ 'blueviolet' => 0x8A2BE2FF,
+ 'brown' => 0xA52A2AFF,
+ 'burlywood' => 0xDEB887FF,
+ 'cadetblue' => 0x5F9EA0FF,
+ 'chartreuse' => 0x7FFF00FF,
+ 'chocolate' => 0xD2691EFF,
+ 'coral' => 0xFF7F50FF,
+ 'cornflowerblue' => 0x6495EDFF,
+ 'cornsilk' => 0xFFF8DCFF,
+ 'crimson' => 0xDC143CFF,
+ 'cyan' => 0x00FFFFFF,
+ 'darkblue' => 0x00008BFF,
+ 'darkcyan' => 0x008B8BFF,
+ 'darkgoldenrod' => 0xB8860BFF,
+ 'darkgray' => 0xA9A9A9FF,
+ 'darkgreen' => 0x006400FF,
+ 'darkkhaki' => 0xBDB76BFF,
+ 'darkmagenta' => 0x8B008BFF,
+ 'darkolivegreen' => 0x556B2FFF,
+ 'darkorange' => 0xFF8C00FF,
+ 'darkorchid' => 0x9932CCFF,
+ 'darkred' => 0x8B0000FF,
+ 'darksalmon' => 0xE9967AFF,
+ 'darkseagreen' => 0x8FBC8FFF,
+ 'darkslateblue' => 0x483D8BFF,
+ 'darkslategray' => 0x2F4F4FFF,
+ 'darkturquoise' => 0x00CED1FF,
+ 'darkviolet' => 0x9400D3FF,
+ 'deeppink' => 0xFF1493FF,
+ 'deepskyblue' => 0x00BFFFFF,
+ 'dimgray' => 0x696969FF,
+ 'dodgerblue' => 0x1E90FFFF,
+ 'firebrick' => 0xB22222FF,
+ 'floralwhite' => 0xFFFAF0FF,
+ 'forestgreen' => 0x228B22FF,
+ 'gainsboro' => 0xDCDCDCFF,
+ 'ghostwhite' => 0xF8F8FFFF,
+ 'gold' => 0xFFD700FF,
+ 'goldenrod' => 0xDAA520FF,
+ 'gray' => 0x808080FF,
+ 'green' => 0x008000FF,
+ 'greenyellow' => 0xADFF2FFF,
+ 'honeydew' => 0xF0FFF0FF,
+ 'hotpink' => 0xFF69B4FF,
+ 'indianred' => 0xCD5C5CFF,
+ 'indigo' => 0x4B0082FF,
+ 'ivory' => 0xFFFFF0FF,
+ 'khaki' => 0xF0E68CFF,
+ 'lavender' => 0xE6E6FAFF,
+ 'lavenderblush' => 0xFFF0F5FF,
+ 'lawngreen' => 0x7CFC00FF,
+ 'lemonchiffon' => 0xFFFACDFF,
+ 'lightblue' => 0xADD8E6FF,
+ 'lightcoral' => 0xF08080FF,
+ 'lightcyan' => 0xE0FFFFFF,
+ 'lightgoldenrodyellow' => 0xFAFAD2FF,
+ 'lightgreen' => 0x90EE90FF,
+ 'lightgray' => 0xD3D3D3FF,
+ 'lightpink' => 0xFFB6C1FF,
+ 'lightsalmon' => 0xFFA07AFF,
+ 'lightseagreen' => 0x20B2AAFF,
+ 'lightskyblue' => 0x87CEFAFF,
+ 'lightslategray' => 0x778899FF,
+ 'lightsteelblue' => 0xB0C4DEFF,
+ 'lightyellow' => 0xFFFFE0FF,
+ 'lime' => 0x00FF00FF,
+ 'limegreen' => 0x32CD32FF,
+ 'linen' => 0xFAF0E6FF,
+ 'magenta' => 0xFF00FFFF,
+ 'maroon' => 0x800000FF,
+ 'mediumaquamarine' => 0x66CDAAFF,
+ 'mediumblue' => 0x0000CDFF,
+ 'mediumorchid' => 0xBA55D3FF,
+ 'mediumpurple' => 0x9370DBFF,
+ 'mediumseagreen' => 0x3CB371FF,
+ 'mediumslateblue' => 0x7B68EEFF,
+ 'mediumspringgreen' => 0x00FA9AFF,
+ 'mediumturquoise' => 0x48D1CCFF,
+ 'mediumvioletred' => 0xC71585FF,
+ 'midnightblue' => 0x191970FF,
+ 'mintcream' => 0xF5FFFAFF,
+ 'mistyrose' => 0xFFE4E1FF,
+ 'moccasin' => 0xFFE4B5FF,
+ 'navajowhite' => 0xFFDEADFF,
+ 'navy' => 0x000080FF,
+ 'oldlace' => 0xFDF5E6FF,
+ 'olive' => 0x808000FF,
+ 'olivedrab' => 0x6B8E23FF,
+ 'orange' => 0xFFA500FF,
+ 'orangered' => 0xFF4500FF,
+ 'orchid' => 0xDA70D6FF,
+ 'palegoldenrod' => 0xEEE8AAFF,
+ 'palegreen' => 0x98FB98FF,
+ 'paleturquoise' => 0xAFEEEEFF,
+ 'palevioletred' => 0xDB7093FF,
+ 'papayawhip' => 0xFFEFD5FF,
+ 'peachpuff' => 0xFFDAB9FF,
+ 'peru' => 0xCD853FFF,
+ 'pink' => 0xFFC0CBFF,
+ 'plum' => 0xDDA0DDFF,
+ 'powderblue' => 0xB0E0E6FF,
+ 'purple' => 0x800080FF,
+ 'red' => 0xFF0000FF,
+ 'rebeccapurple' => 0x663399FF,
+ 'rosybrown' => 0xBC8F8FFF,
+ 'royalblue' => 0x4169E1FF,
+ 'saddlebrown' => 0x8B4513FF,
+ 'salmon' => 0xFA8072FF,
+ 'sandybrown' => 0xF4A460FF,
+ 'seagreen' => 0x2E8B57FF,
+ 'seashell' => 0xFFF5EEFF,
+ 'sienna' => 0xA0522DFF,
+ 'silver' => 0xC0C0C0FF,
+ 'skyblue' => 0x87CEEBFF,
+ 'slateblue' => 0x6A5ACDFF,
+ 'slategray' => 0x708090FF,
+ 'snow' => 0xFFFAFAFF,
+ 'springgreen' => 0x00FF7FFF,
+ 'steelblue' => 0x4682B4FF,
+ 'tan' => 0xD2B48CFF,
+ 'teal' => 0x008080FF,
+ 'thistle' => 0xD8BFD8FF,
+ 'tomato' => 0xFF6347FF,
+ 'transparent' => 0x00000000,
+ 'turquoise' => 0x40E0D0FF,
+ 'violet' => 0xEE82EEFF,
+ 'wheat' => 0xF5DEB3FF,
+ 'white' => 0xFFFFFFFF,
+ 'whitesmoke' => 0xF5F5F5FF,
+ 'yellow' => 0xFFFF00FF,
+ 'yellowgreen' => 0x9ACD32FF
+ }, &method(:int_to_rgba))
+
+ # A hash from `[red, green, blue, alpha]` value arrays to color names.
+ COLOR_NAMES_REVERSE = COLOR_NAMES.invert.freeze
+
+ # We add the alternate color names after inverting because
+ # different ruby implementations and versions vary on the ordering of the result of invert.
+ COLOR_NAMES.update(ALTERNATE_COLOR_NAMES).freeze
+
+ # The user's original representation of the color.
+ #
+ # @return [String]
+ attr_reader :representation
+
+ # Constructs an RGB or HSL color object,
+ # optionally with an alpha channel.
+ #
+ # RGB values are clipped within 0 and 255.
+ # Saturation and lightness values are clipped within 0 and 100.
+ # The alpha value is clipped within 0 and 1.
+ #
+ # @raise [Sass::SyntaxError] if any color value isn't in the specified range
+ #
+ # @overload initialize(attrs)
+ # The attributes are specified as a hash. This hash must contain either
+ # `:hue`, `:saturation`, and `:value` keys, or `:red`, `:green`, and
+ # `:blue` keys. It cannot contain both HSL and RGB keys. It may also
+ # optionally contain an `:alpha` key, and a `:representation` key
+ # indicating the original representation of the color that the user wrote
+ # in their stylesheet.
+ #
+ # @param attrs [{Symbol => Numeric}] A hash of color attributes to values
+ # @raise [ArgumentError] if not enough attributes are specified,
+ # or both RGB and HSL attributes are specified
+ #
+ # @overload initialize(rgba, [representation])
+ # The attributes are specified as an array.
+ # This overload only supports RGB or RGBA colors.
+ #
+ # @param rgba [Array<Numeric>] A three- or four-element array
+ # of the red, green, blue, and optionally alpha values (respectively)
+ # of the color
+ # @param representation [String] The original representation of the color
+ # that the user wrote in their stylesheet.
+ # @raise [ArgumentError] if not enough attributes are specified
+ def initialize(attrs, representation = nil, allow_both_rgb_and_hsl = false)
+ super(nil)
+
+ if attrs.is_a?(Array)
+ unless (3..4).include?(attrs.size)
+ raise ArgumentError.new("Color.new(array) expects a three- or four-element array")
+ end
+
+ red, green, blue = attrs[0...3].map {|c| c.to_i}
+ @attrs = {:red => red, :green => green, :blue => blue}
+ @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
+ @representation = representation
+ else
+ attrs = attrs.reject {|k, v| v.nil?}
+ hsl = [:hue, :saturation, :lightness] & attrs.keys
+ rgb = [:red, :green, :blue] & attrs.keys
+ if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty?
+ raise ArgumentError.new("Color.new(hash) may not have both HSL and RGB keys specified")
+ elsif hsl.empty? && rgb.empty?
+ raise ArgumentError.new("Color.new(hash) must have either HSL or RGB keys specified")
+ elsif !hsl.empty? && hsl.size != 3
+ raise ArgumentError.new("Color.new(hash) must have all three HSL values specified")
+ elsif !rgb.empty? && rgb.size != 3
+ raise ArgumentError.new("Color.new(hash) must have all three RGB values specified")
+ end
+
+ @attrs = attrs
+ @attrs[:hue] %= 360 if @attrs[:hue]
+ @attrs[:alpha] ||= 1
+ @representation = @attrs.delete(:representation)
+ end
+
+ [:red, :green, :blue].each do |k|
+ next if @attrs[k].nil?
+ @attrs[k] = Sass::Util.restrict(@attrs[k].to_i, 0..255)
+ end
+
+ [:saturation, :lightness].each do |k|
+ next if @attrs[k].nil?
+ @attrs[k] = Sass::Util.restrict(@attrs[k], 0..100)
+ end
+
+ @attrs[:alpha] = Sass::Util.restrict(@attrs[:alpha], 0..1)
+ end
+
+ # Create a new color from a valid CSS hex string.
+ #
+ # The leading hash is optional.
+ #
+ # @return [Color]
+ def self.from_hex(hex_string, alpha = nil)
+ unless hex_string =~ /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i ||
+ hex_string =~ /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i
+ raise ArgumentError.new("#{hex_string.inspect} is not a valid hex color.")
+ end
+ red = $1.ljust(2, $1).to_i(16)
+ green = $2.ljust(2, $2).to_i(16)
+ blue = $3.ljust(2, $3).to_i(16)
+
+ hex_string = '##{hex_string}' unless hex_string[0] == ?#
+ attrs = {:red => red, :green => green, :blue => blue, :representation => hex_string}
+ attrs[:alpha] = alpha if alpha
+ new(attrs)
+ end
+
+ # The red component of the color.
+ #
+ # @return [Fixnum]
+ def red
+ hsl_to_rgb!
+ @attrs[:red]
+ end
+
+ # The green component of the color.
+ #
+ # @return [Fixnum]
+ def green
+ hsl_to_rgb!
+ @attrs[:green]
+ end
+
+ # The blue component of the color.
+ #
+ # @return [Fixnum]
+ def blue
+ hsl_to_rgb!
+ @attrs[:blue]
+ end
+
+ # The hue component of the color.
+ #
+ # @return [Numeric]
+ def hue
+ rgb_to_hsl!
+ @attrs[:hue]
+ end
+
+ # The saturation component of the color.
+ #
+ # @return [Numeric]
+ def saturation
+ rgb_to_hsl!
+ @attrs[:saturation]
+ end
+
+ # The lightness component of the color.
+ #
+ # @return [Numeric]
+ def lightness
+ rgb_to_hsl!
+ @attrs[:lightness]
+ end
+
+ # The alpha channel (opacity) of the color.
+ # This is 1 unless otherwise defined.
+ #
+ # @return [Fixnum]
+ def alpha
+ @attrs[:alpha].to_f
+ end
+
+ # Returns whether this color object is translucent;
+ # that is, whether the alpha channel is non-1.
+ #
+ # @return [Boolean]
+ def alpha?
+ alpha < 1
+ end
+
+ # Returns the red, green, and blue components of the color.
+ #
+ # @return [Array<Fixnum>] A frozen three-element array of the red, green, and blue
+ # values (respectively) of the color
+ def rgb
+ [red, green, blue].freeze
+ end
+
+ # Returns the red, green, blue, and alpha components of the color.
+ #
+ # @return [Array<Fixnum>] A frozen four-element array of the red, green,
+ # blue, and alpha values (respectively) of the color
+ def rgba
+ [red, green, blue, alpha].freeze
+ end
+
+ # Returns the hue, saturation, and lightness components of the color.
+ #
+ # @return [Array<Fixnum>] A frozen three-element array of the
+ # hue, saturation, and lightness values (respectively) of the color
+ def hsl
+ [hue, saturation, lightness].freeze
+ end
+
+ # Returns the hue, saturation, lightness, and alpha components of the color.
+ #
+ # @return [Array<Fixnum>] A frozen four-element array of the hue,
+ # saturation, lightness, and alpha values (respectively) of the color
+ def hsla
+ [hue, saturation, lightness].freeze
+ end
+
+ # The SassScript `==` operation.
+ # **Note that this returns a {Sass::Script::Value::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Bool] True if this value is the same as the other,
+ # false otherwise
+ def eq(other)
+ Sass::Script::Value::Bool.new(
+ other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha)
+ end
+
+ def hash
+ [rgb, alpha].hash
+ end
+
+ # Returns a copy of this color with one or more channels changed.
+ # RGB or HSL colors may be changed, but not both at once.
+ #
+ # For example:
+ #
+ # Color.new([10, 20, 30]).with(:blue => 40)
+ # #=> rgb(10, 40, 30)
+ # Color.new([126, 126, 126]).with(:red => 0, :green => 255)
+ # #=> rgb(0, 255, 126)
+ # Color.new([255, 0, 127]).with(:saturation => 60)
+ # #=> rgb(204, 51, 127)
+ # Color.new([1, 2, 3]).with(:alpha => 0.4)
+ # #=> rgba(1, 2, 3, 0.4)
+ #
+ # @param attrs [{Symbol => Numeric}]
+ # A map of channel names (`:red`, `:green`, `:blue`,
+ # `:hue`, `:saturation`, `:lightness`, or `:alpha`) to values
+ # @return [Color] The new Color object
+ # @raise [ArgumentError] if both RGB and HSL keys are specified
+ def with(attrs)
+ attrs = attrs.reject {|k, v| v.nil?}
+ hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty?
+ rgb = !([:red, :green, :blue] & attrs.keys).empty?
+ if hsl && rgb
+ raise ArgumentError.new("Cannot specify HSL and RGB values for a color at the same time")
+ end
+
+ if hsl
+ [:hue, :saturation, :lightness].each {|k| attrs[k] ||= send(k)}
+ elsif rgb
+ [:red, :green, :blue].each {|k| attrs[k] ||= send(k)}
+ else
+ # If we're just changing the alpha channel,
+ # keep all the HSL/RGB stuff we've calculated
+ attrs = @attrs.merge(attrs)
+ end
+ attrs[:alpha] ||= alpha
+
+ Color.new(attrs, nil, :allow_both_rgb_and_hsl)
+ end
+
+ # The SassScript `+` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Adds the number to each of the RGB color channels.
+ #
+ # {Color}
+ # : Adds each of the RGB color channels together.
+ #
+ # {Value}
+ # : See {Value::Base#plus}.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Color] The resulting color
+ # @raise [Sass::SyntaxError] if `other` is a number with units
+ def plus(other)
+ if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
+ piecewise(other, :+)
+ else
+ super
+ end
+ end
+
+ # The SassScript `-` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Subtracts the number from each of the RGB color channels.
+ #
+ # {Color}
+ # : Subtracts each of the other color's RGB color channels from this color's.
+ #
+ # {Value}
+ # : See {Value::Base#minus}.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Color] The resulting color
+ # @raise [Sass::SyntaxError] if `other` is a number with units
+ def minus(other)
+ if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
+ piecewise(other, :-)
+ else
+ super
+ end
+ end
+
+ # The SassScript `*` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Multiplies the number by each of the RGB color channels.
+ #
+ # {Color}
+ # : Multiplies each of the RGB color channels together.
+ #
+ # @param other [Number, Color] The right-hand side of the operator
+ # @return [Color] The resulting color
+ # @raise [Sass::SyntaxError] if `other` is a number with units
+ def times(other)
+ if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
+ piecewise(other, :*)
+ else
+ raise NoMethodError.new(nil, :times)
+ end
+ end
+
+ # The SassScript `/` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Divides each of the RGB color channels by the number.
+ #
+ # {Color}
+ # : Divides each of this color's RGB color channels by the other color's.
+ #
+ # {Value}
+ # : See {Value::Base#div}.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Color] The resulting color
+ # @raise [Sass::SyntaxError] if `other` is a number with units
+ def div(other)
+ if other.is_a?(Sass::Script::Value::Number) ||
+ other.is_a?(Sass::Script::Value::Color)
+ piecewise(other, :/)
+ else
+ super
+ end
+ end
+
+ # The SassScript `%` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Takes each of the RGB color channels module the number.
+ #
+ # {Color}
+ # : Takes each of this color's RGB color channels modulo the other color's.
+ #
+ # @param other [Number, Color] The right-hand side of the operator
+ # @return [Color] The resulting color
+ # @raise [Sass::SyntaxError] if `other` is a number with units
+ def mod(other)
+ if other.is_a?(Sass::Script::Value::Number) ||
+ other.is_a?(Sass::Script::Value::Color)
+ piecewise(other, :%)
+ else
+ raise NoMethodError.new(nil, :mod)
+ end
+ end
+
+ # Returns a string representation of the color.
+ # This is usually the color's hex value,
+ # but if the color has a name that's used instead.
+ #
+ # @return [String] The string representation
+ def to_s(opts = {})
+ return smallest if options[:style] == :compressed
+ return representation if representation
+ return name if name
+ alpha? ? rgba_str : hex_str
+ end
+ alias_method :to_sass, :to_s
+
+ # Returns a string representation of the color.
+ #
+ # @return [String] The hex value
+ def inspect
+ alpha? ? rgba_str : hex_str
+ end
+
+ # Returns the color's name, if it has one.
+ #
+ # @return [String, nil]
+ def name
+ COLOR_NAMES_REVERSE[rgba]
+ end
+
+ private
+
+ def smallest
+ small_explicit_str = alpha? ? rgba_str : hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
+ [representation, COLOR_NAMES_REVERSE[rgba], small_explicit_str].
+ compact.min_by {|str| str.size}
+ end
+
+ def rgba_str
+ split = options[:style] == :compressed ? ',' : ', '
+ "rgba(#{rgb.join(split)}#{split}#{Number.round(alpha)})"
+ end
+
+ def hex_str
+ red, green, blue = rgb.map {|num| num.to_s(16).rjust(2, '0')}
+ "##{red}#{green}#{blue}"
+ end
+
+ def piecewise(other, operation)
+ other_num = other.is_a? Number
+ if other_num && !other.unitless?
+ raise Sass::SyntaxError.new(
+ "Cannot add a number with units (#{other}) to a color (#{self}).")
+ end
+
+ result = []
+ (0...3).each do |i|
+ res = rgb[i].send(operation, other_num ? other.value : other.rgb[i])
+ result[i] = [[res, 255].min, 0].max
+ end
+
+ if !other_num && other.alpha != alpha
+ raise Sass::SyntaxError.new("Alpha channels must be equal: #{self} #{operation} #{other}")
+ end
+
+ with(:red => result[0], :green => result[1], :blue => result[2])
+ end
+
+ def hsl_to_rgb!
+ return if @attrs[:red] && @attrs[:blue] && @attrs[:green]
+
+ h = @attrs[:hue] / 360.0
+ s = @attrs[:saturation] / 100.0
+ l = @attrs[:lightness] / 100.0
+
+ # Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.
+ m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
+ m1 = l * 2 - m2
+ @attrs[:red], @attrs[:green], @attrs[:blue] = [
+ hue_to_rgb(m1, m2, h + 1.0 / 3),
+ hue_to_rgb(m1, m2, h),
+ hue_to_rgb(m1, m2, h - 1.0 / 3)
+ ].map {|c| (c * 0xff).round}
+ end
+
+ def hue_to_rgb(m1, m2, h)
+ h += 1 if h < 0
+ h -= 1 if h > 1
+ return m1 + (m2 - m1) * h * 6 if h * 6 < 1
+ return m2 if h * 2 < 1
+ return m1 + (m2 - m1) * (2.0 / 3 - h) * 6 if h * 3 < 2
+ m1
+ end
+
+ def rgb_to_hsl!
+ return if @attrs[:hue] && @attrs[:saturation] && @attrs[:lightness]
+ r, g, b = [:red, :green, :blue].map {|k| @attrs[k] / 255.0}
+
+ # Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
+ max = [r, g, b].max
+ min = [r, g, b].min
+ d = max - min
+
+ h =
+ case max
+ when min; 0
+ when r; 60 * (g - b) / d
+ when g; 60 * (b - r) / d + 120
+ when b; 60 * (r - g) / d + 240
+ end
+
+ l = (max + min) / 2.0
+
+ s =
+ if max == min
+ 0
+ elsif l < 0.5
+ d / (2 * l)
+ else
+ d / (2 - 2 * l)
+ end
+
+ @attrs[:hue] = h % 360
+ @attrs[:saturation] = s * 100
+ @attrs[:lightness] = l * 100
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/helpers.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/helpers.rb
new file mode 100644
index 0000000..d1a6376
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/helpers.rb
@@ -0,0 +1,262 @@
+module Sass::Script::Value
+ # Provides helper functions for creating sass values from within ruby methods.
+ # @since `3.3.0`
+ module Helpers
+ # Construct a Sass Boolean.
+ #
+ # @param value [Object] A ruby object that will be tested for truthiness.
+ # @return [Sass::Script::Value::Bool] whether the ruby value is truthy.
+ def bool(value)
+ Bool.new(value)
+ end
+
+ # Construct a Sass Color from a hex color string.
+ #
+ # @param value [::String] A string representing a hex color.
+ # The leading hash ("#") is optional.
+ # @param alpha [::Number] The alpha channel. A number between 0 and 1.
+ # @return [Sass::Script::Value::Color] the color object
+ def hex_color(value, alpha = nil)
+ Color.from_hex(value, alpha)
+ end
+
+ # Construct a Sass Color from hsl values.
+ #
+ # @param hue [::Number] The hue of the color in degrees.
+ # A non-negative number, usually less than 360.
+ # @param saturation [::Number] The saturation of the color.
+ # Must be between 0 and 100 inclusive.
+ # @param lightness [::Number] The lightness of the color.
+ # Must be between 0 and 100 inclusive.
+ # @param alpha [::Number] The alpha channel. A number between 0 and 1.
+ #
+ # @return [Sass::Script::Value::Color] the color object
+ def hsl_color(hue, saturation, lightness, alpha = nil)
+ attrs = {:hue => hue, :saturation => saturation, :lightness => lightness}
+ attrs[:alpha] = alpha if alpha
+ Color.new(attrs)
+ end
+
+ # Construct a Sass Color from rgb values.
+ #
+ # @param red [::Number] The red component. Must be between 0 and 255 inclusive.
+ # @param green [::Number] The green component. Must be between 0 and 255 inclusive.
+ # @param blue [::Number] The blue component. Must be between 0 and 255 inclusive.
+ # @param alpha [::Number] The alpha channel. A number between 0 and 1.
+ #
+ # @return [Sass::Script::Value::Color] the color object
+ def rgb_color(red, green, blue, alpha = nil)
+ attrs = {:red => red, :green => green, :blue => blue}
+ attrs[:alpha] = alpha if alpha
+ Color.new(attrs)
+ end
+
+ # Construct a Sass Number from a ruby number.
+ #
+ # @param number [::Number] A numeric value.
+ # @param unit_string [::String] A unit string of the form
+ # `numeral_unit1 * numeral_unit2 ... / denominator_unit1 * denominator_unit2 ...`
+ # this is the same format that is returned by
+ # {Sass::Script::Value::Number#unit_str the `unit_str` method}
+ #
+ # @see Sass::Script::Value::Number#unit_str
+ #
+ # @return [Sass::Script::Value::Number] The sass number representing the given ruby number.
+ def number(number, unit_string = nil)
+ Number.new(number, *parse_unit_string(unit_string))
+ end
+
+ # @overload list(*elements, separator)
+ # Create a space-separated list from the arguments given.
+ # @param elements [Array<Sass::Script::Value::Base>] Each argument will be a list element.
+ # @param separator [Symbol] Either :space or :comma.
+ # @return [Sass::Script::Value::List] The space separated list.
+ #
+ # @overload list(array, separator)
+ # Create a space-separated list from the array given.
+ # @param array [Array<Sass::Script::Value::Base>] A ruby array of Sass values
+ # to make into a list.
+ # @return [Sass::Script::Value::List] The space separated list.
+ def list(*elements)
+ unless elements.last.is_a?(Symbol)
+ raise ArgumentError.new("A list type of :space or :comma must be specified.")
+ end
+ separator = elements.pop
+ if elements.size == 1 && elements.first.is_a?(Array)
+ elements = elements.first
+ end
+ Sass::Script::Value::List.new(elements, separator)
+ end
+
+ # Construct a Sass map.
+ #
+ # @param hash [Hash<Sass::Script::Value::Base,
+ # Sass::Script::Value::Base>] A Ruby map to convert to a Sass map.
+ # @return [Sass::Script::Value::Map] The map.
+ def map(hash)
+ Map.new(hash)
+ end
+
+ # Create a sass null value.
+ #
+ # @return [Sass::Script::Value::Null]
+ def null
+ Sass::Script::Value::Null.new
+ end
+
+ # Create a quoted string.
+ #
+ # @param str [::String] A ruby string.
+ # @return [Sass::Script::Value::String] A quoted string.
+ def quoted_string(str)
+ Sass::Script::String.new(str, :string)
+ end
+
+ # Create an unquoted string.
+ #
+ # @param str [::String] A ruby string.
+ # @return [Sass::Script::Value::String] An unquoted string.
+ def unquoted_string(str)
+ Sass::Script::String.new(str, :identifier)
+ end
+ alias_method :identifier, :unquoted_string
+
+ # Parses a user-provided selector.
+ #
+ # @param value [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector to parse. This can be either a string, a list of
+ # strings, or a list of lists of strings as returned by `&`.
+ # @param name [Symbol, nil]
+ # If provided, the name of the selector argument. This is used
+ # for error reporting.
+ # @param allow_parent_ref [Boolean]
+ # Whether the parsed selector should allow parent references.
+ # @return [Sass::Selector::CommaSequence] The parsed selector.
+ # @throw [ArgumentError] if the parse failed for any reason.
+ def parse_selector(value, name = nil, allow_parent_ref = false)
+ str = normalize_selector(value, name)
+ begin
+ Sass::SCSS::StaticParser.new(str, nil, nil, 1, 1, allow_parent_ref).parse_selector
+ rescue Sass::SyntaxError => e
+ err = "#{value.inspect} is not a valid selector: #{e}"
+ err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
+ raise ArgumentError.new(err)
+ end
+ end
+
+ # Parses a user-provided complex selector.
+ #
+ # A complex selector can contain combinators but cannot contain commas.
+ #
+ # @param value [Sass::Script::Value::String, Sass::Script::Value::List]
+ # The selector to parse. This can be either a string or a list of
+ # strings.
+ # @param name [Symbol, nil]
+ # If provided, the name of the selector argument. This is used
+ # for error reporting.
+ # @param allow_parent_ref [Boolean]
+ # Whether the parsed selector should allow parent references.
+ # @return [Sass::Selector::Sequence] The parsed selector.
+ # @throw [ArgumentError] if the parse failed for any reason.
+ def parse_complex_selector(value, name = nil, allow_parent_ref = false)
+ selector = parse_selector(value, name, allow_parent_ref)
+ return seq if selector.members.length == 1
+
+ err = "#{value.inspect} is not a complex selector"
+ err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
+ raise ArgumentError.new(err)
+ end
+
+ # Parses a user-provided compound selector.
+ #
+ # A compound selector cannot contain combinators or commas.
+ #
+ # @param value [Sass::Script::Value::String] The selector to parse.
+ # @param name [Symbol, nil]
+ # If provided, the name of the selector argument. This is used
+ # for error reporting.
+ # @param allow_parent_ref [Boolean]
+ # Whether the parsed selector should allow parent references.
+ # @return [Sass::Selector::SimpleSequence] The parsed selector.
+ # @throw [ArgumentError] if the parse failed for any reason.
+ def parse_compound_selector(value, name = nil, allow_parent_ref = false)
+ assert_type value, :String, name
+ selector = parse_selector(value, name, allow_parent_ref)
+ seq = selector.members.first
+ sseq = seq.members.first
+ if selector.members.length == 1 && seq.members.length == 1 &&
+ sseq.is_a?(Sass::Selector::SimpleSequence)
+ return sseq
+ end
+
+ err = "#{value.inspect} is not a compound selector"
+ err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
+ raise ArgumentError.new(err)
+ end
+
+ private
+
+ # Converts a user-provided selector into string form or throws an
+ # ArgumentError if it's in an invalid format.
+ def normalize_selector(value, name)
+ if (str = selector_to_str(value))
+ return str
+ end
+
+ err = "#{value.inspect} is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings"
+ err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
+ raise ArgumentError.new(err)
+ end
+
+ # Converts a user-provided selector into string form or returns
+ # `nil` if it's in an invalid format.
+ def selector_to_str(value)
+ return value.value if value.is_a?(Sass::Script::String)
+ return unless value.is_a?(Sass::Script::List)
+
+ if value.separator == :comma
+ return value.to_a.map do |complex|
+ next complex.value if complex.is_a?(Sass::Script::String)
+ return unless complex.is_a?(Sass::Script::List) && complex.separator == :space
+ return unless (str = selector_to_str(complex))
+ str
+ end.join(', ')
+ end
+
+ value.to_a.map do |compound|
+ return unless compound.is_a?(Sass::Script::String)
+ compound.value
+ end.join(' ')
+ end
+
+ # @private
+ VALID_UNIT = /#{Sass::SCSS::RX::NMSTART}#{Sass::SCSS::RX::NMCHAR}|%*/
+
+ # @example
+ # parse_unit_string("em*px/in*%") # => [["em", "px], ["in", "%"]]
+ #
+ # @param unit_string [String] A string adhering to the output of a number with complex
+ # units. E.g. "em*px/in*%"
+ # @return [Array<Array<String>>] A list of numerator units and a list of denominator units.
+ def parse_unit_string(unit_string)
+ denominator_units = numerator_units = Sass::Script::Value::Number::NO_UNITS
+ return numerator_units, denominator_units unless unit_string && unit_string.length > 0
+ num_over_denominator = unit_string.split(/ *\/ */)
+ unless (1..2).include?(num_over_denominator.size)
+ raise ArgumentError.new("Malformed unit string: #{unit_string}")
+ end
+ numerator_units = num_over_denominator[0].split(/ *\* */)
+ denominator_units = (num_over_denominator[1] || "").split(/ *\* */)
+ [[numerator_units, "numerator"], [denominator_units, "denominator"]].each do |units, name|
+ if unit_string =~ /\// && units.size == 0
+ raise ArgumentError.new("Malformed unit string: #{unit_string}")
+ end
+ if units.any? {|unit| unit !~ VALID_UNIT}
+ raise ArgumentError.new("Malformed #{name} in unit string: #{unit_string}")
+ end
+ end
+ [numerator_units, denominator_units]
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/list.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/list.rb
new file mode 100644
index 0000000..648b830
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/list.rb
@@ -0,0 +1,113 @@
+module Sass::Script::Value
+ # A SassScript object representing a CSS list.
+ # This includes both comma-separated lists and space-separated lists.
+ class List < Base
+ # The Ruby array containing the contents of the list.
+ #
+ # @return [Array<Value>]
+ attr_reader :value
+ alias_method :to_a, :value
+
+ # The operator separating the values of the list.
+ # Either `:comma` or `:space`.
+ #
+ # @return [Symbol]
+ attr_reader :separator
+
+ # Creates a new list.
+ #
+ # @param value [Array<Value>] See \{#value}
+ # @param separator [Symbol] See \{#separator}
+ def initialize(value, separator)
+ super(value)
+ @separator = separator
+ end
+
+ # @see Value#options=
+ def options=(options)
+ super
+ value.each {|v| v.options = options}
+ end
+
+ # @see Value#eq
+ def eq(other)
+ Sass::Script::Value::Bool.new(
+ other.is_a?(List) && value == other.value &&
+ separator == other.separator)
+ end
+
+ def hash
+ @hash ||= [value, separator].hash
+ end
+
+ # @see Value#to_s
+ def to_s(opts = {})
+ raise Sass::SyntaxError.new("() isn't a valid CSS value.") if value.empty?
+ value.
+ reject {|e| e.is_a?(Null) || e.is_a?(List) && e.value.empty?}.
+ map {|e| e.to_s(opts)}.join(sep_str)
+ end
+
+ # @see Value#to_sass
+ def to_sass(opts = {})
+ return "()" if value.empty?
+ members = value.map do |v|
+ if element_needs_parens?(v)
+ "(#{v.to_sass(opts)})"
+ else
+ v.to_sass(opts)
+ end
+ end
+ return "(#{members.first},)" if members.length == 1 && separator == :comma
+ members.join(sep_str(nil))
+ end
+
+ # @see Value#to_h
+ def to_h
+ return Sass::Util.ordered_hash if value.empty?
+ super
+ end
+
+ # @see Value#inspect
+ def inspect
+ "(#{value.map {|e| e.inspect}.join(sep_str(nil))})"
+ end
+
+ # Asserts an index is within the list.
+ #
+ # @private
+ #
+ # @param list [Sass::Script::Value::List] The list for which the index should be checked.
+ # @param n [Sass::Script::Value::Number] The index being checked.
+ def self.assert_valid_index(list, n)
+ if !n.int? || n.to_i == 0
+ raise ArgumentError.new("List index #{n} must be a non-zero integer")
+ elsif list.to_a.size == 0
+ raise ArgumentError.new("List index is #{n} but list has no items")
+ elsif n.to_i.abs > (size = list.to_a.size)
+ raise ArgumentError.new(
+ "List index is #{n} but list is only #{size} item#{'s' if size != 1} long")
+ end
+ end
+
+ private
+
+ def element_needs_parens?(element)
+ if element.is_a?(List)
+ return false if element.value.empty?
+ precedence = Sass::Script::Parser.precedence_of(separator)
+ return Sass::Script::Parser.precedence_of(element.separator) <= precedence
+ end
+
+ return false unless separator == :space
+ return false unless element.is_a?(Sass::Script::Tree::UnaryOperation)
+ element.operator == :minus || element.operator == :plus
+ end
+
+ def sep_str(opts = options)
+ return ' ' if separator == :space
+ return ',' if opts && opts[:style] == :compressed
+ ', '
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/map.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/map.rb
new file mode 100644
index 0000000..9946fea
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/map.rb
@@ -0,0 +1,70 @@
+module Sass::Script::Value
+ # A SassScript object representing a map from keys to values. Both keys and
+ # values can be any SassScript object.
+ class Map < Base
+ # The Ruby hash containing the contents of this map.
+ #
+ # @return [Hash<Node, Node>]
+ attr_reader :value
+ alias_method :to_h, :value
+
+ # Creates a new map.
+ #
+ # @param hash [Hash<Node, Node>]
+ def initialize(hash)
+ super(Sass::Util.ordered_hash(hash))
+ end
+
+ # @see Value#options=
+ def options=(options)
+ super
+ value.each do |k, v|
+ k.options = options
+ v.options = options
+ end
+ end
+
+ # @see Value#separator
+ def separator
+ :comma unless value.empty?
+ end
+
+ # @see Value#to_a
+ def to_a
+ value.map do |k, v|
+ list = List.new([k, v], :space)
+ list.options = options
+ list
+ end
+ end
+
+ # @see Value#eq
+ def eq(other)
+ Bool.new(other.is_a?(Map) && value == other.value)
+ end
+
+ def hash
+ @hash ||= value.hash
+ end
+
+ # @see Value#to_s
+ def to_s(opts = {})
+ raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.")
+ end
+
+ def to_sass(opts = {})
+ return "()" if value.empty?
+
+ to_sass = lambda do |value|
+ if value.is_a?(Map) || (value.is_a?(List) && value.separator == :comma)
+ "(#{value.to_sass(opts)})"
+ else
+ value.to_sass(opts)
+ end
+ end
+
+ "(#{value.map {|(k, v)| "#{to_sass[k]}: #{to_sass[v]}"}.join(', ')})"
+ end
+ alias_method :inspect, :to_sass
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/null.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/null.rb
new file mode 100644
index 0000000..f6d573b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/null.rb
@@ -0,0 +1,44 @@
+module Sass::Script::Value
+ # A SassScript object representing a null value.
+ class Null < Base
+ # The null value in SassScript.
+ #
+ # This is assigned before new is overridden below so that we use the default implementation.
+ NULL = new(nil)
+
+ # We override object creation so that users of the core API
+ # will not need to know that null is a specific constant.
+ #
+ # @private
+ # @return [Null] the {NULL} constant.
+ def self.new
+ NULL
+ end
+
+ # @return [Boolean] `false` (the Ruby boolean value)
+ def to_bool
+ false
+ end
+
+ # @return [Boolean] `true`
+ def null?
+ true
+ end
+
+ # @return [String] '' (An empty string)
+ def to_s(opts = {})
+ ''
+ end
+
+ def to_sass(opts = {})
+ 'null'
+ end
+
+ # Returns a string representing a null value.
+ #
+ # @return [String]
+ def inspect
+ 'null'
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/number.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/number.rb
new file mode 100644
index 0000000..031a6e7
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/number.rb
@@ -0,0 +1,530 @@
+module Sass::Script::Value
+ # A SassScript object representing a number.
+ # SassScript numbers can have decimal values,
+ # and can also have units.
+ # For example, `12`, `1px`, and `10.45em`
+ # are all valid values.
+ #
+ # Numbers can also have more complex units, such as `1px*em/in`.
+ # These cannot be inputted directly in Sass code at the moment.
+ class Number < Base
+ # The Ruby value of the number.
+ #
+ # @return [Numeric]
+ attr_reader :value
+
+ # A list of units in the numerator of the number.
+ # For example, `1px*em/in*cm` would return `["px", "em"]`
+ # @return [Array<String>]
+ attr_reader :numerator_units
+
+ # A list of units in the denominator of the number.
+ # For example, `1px*em/in*cm` would return `["in", "cm"]`
+ # @return [Array<String>]
+ attr_reader :denominator_units
+
+ # The original representation of this number.
+ # For example, although the result of `1px/2px` is `0.5`,
+ # the value of `#original` is `"1px/2px"`.
+ #
+ # This is only non-nil when the original value should be used as the CSS value,
+ # as in `font: 1px/2px`.
+ #
+ # @return [Boolean, nil]
+ attr_accessor :original
+
+ def self.precision
+ @precision ||= 5
+ end
+
+ # Sets the number of digits of precision
+ # For example, if this is `3`,
+ # `3.1415926` will be printed as `3.142`.
+ def self.precision=(digits)
+ @precision = digits.round
+ @precision_factor = 10 0** precision
+ end
+
+ # the precision factor used in numeric output
+ # it is derived from the `precision` method.
+ def self.precision_factor
+ @precision_factor ||= 10.0**precision
+ end
+
+ # Used so we don't allocate two new arrays for each new number.
+ NO_UNITS = []
+
+ # @param value [Numeric] The value of the number
+ # @param numerator_units [::String, Array<::String>] See \{#numerator\_units}
+ # @param denominator_units [::String, Array<::String>] See \{#denominator\_units}
+ def initialize(value, numerator_units = NO_UNITS, denominator_units = NO_UNITS)
+ numerator_units = [numerator_units] if numerator_units.is_a?(::String)
+ denominator_units = [denominator_units] if denominator_units.is_a?(::String)
+ super(value)
+ @numerator_units = numerator_units
+ @denominator_units = denominator_units
+ normalize!
+ end
+
+ # The SassScript `+` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Adds the two numbers together, converting units if possible.
+ #
+ # {Color}
+ # : Adds this number to each of the RGB color channels.
+ #
+ # {Value}
+ # : See {Value::Base#plus}.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Value] The result of the operation
+ # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
+ def plus(other)
+ if other.is_a? Number
+ operate(other, :+)
+ elsif other.is_a?(Color)
+ other.plus(self)
+ else
+ super
+ end
+ end
+
+ # The SassScript binary `-` operation (e.g. `$a - $b`).
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Subtracts this number from the other, converting units if possible.
+ #
+ # {Value}
+ # : See {Value::Base#minus}.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Value] The result of the operation
+ # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
+ def minus(other)
+ if other.is_a? Number
+ operate(other, :-)
+ else
+ super
+ end
+ end
+
+ # The SassScript unary `+` operation (e.g. `+$a`).
+ #
+ # @return [Number] The value of this number
+ def unary_plus
+ self
+ end
+
+ # The SassScript unary `-` operation (e.g. `-$a`).
+ #
+ # @return [Number] The negative value of this number
+ def unary_minus
+ Number.new(-value, @numerator_units, @denominator_units)
+ end
+
+ # The SassScript `*` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Multiplies the two numbers together, converting units appropriately.
+ #
+ # {Color}
+ # : Multiplies each of the RGB color channels by this number.
+ #
+ # @param other [Number, Color] The right-hand side of the operator
+ # @return [Number, Color] The result of the operation
+ # @raise [NoMethodError] if `other` is an invalid type
+ def times(other)
+ if other.is_a? Number
+ operate(other, :*)
+ elsif other.is_a? Color
+ other.times(self)
+ else
+ raise NoMethodError.new(nil, :times)
+ end
+ end
+
+ # The SassScript `/` operation.
+ # Its functionality depends on the type of its argument:
+ #
+ # {Number}
+ # : Divides this number by the other, converting units appropriately.
+ #
+ # {Value}
+ # : See {Value::Base#div}.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Value] The result of the operation
+ def div(other)
+ if other.is_a? Number
+ res = operate(other, :/)
+ if original && other.original
+ res.original = "#{original}/#{other.original}"
+ end
+ res
+ else
+ super
+ end
+ end
+
+ # The SassScript `%` operation.
+ #
+ # @param other [Number] The right-hand side of the operator
+ # @return [Number] This number modulo the other
+ # @raise [NoMethodError] if `other` is an invalid type
+ # @raise [Sass::UnitConversionError] if `other` has incompatible units
+ def mod(other)
+ if other.is_a?(Number)
+ operate(other, :%)
+ else
+ raise NoMethodError.new(nil, :mod)
+ end
+ end
+
+ # The SassScript `==` operation.
+ #
+ # @param other [Value] The right-hand side of the operator
+ # @return [Boolean] Whether this number is equal to the other object
+ def eq(other)
+ return Bool::FALSE unless other.is_a?(Sass::Script::Value::Number)
+ this = self
+ begin
+ if unitless?
+ this = this.coerce(other.numerator_units, other.denominator_units)
+ else
+ other = other.coerce(@numerator_units, @denominator_units)
+ end
+ rescue Sass::UnitConversionError
+ return Bool::FALSE
+ end
+ Bool.new(this.value == other.value)
+ end
+
+ def hash
+ [value, numerator_units, denominator_units].hash
+ end
+
+ # Hash-equality works differently than `==` equality for numbers.
+ # Hash-equality must be transitive, so it just compares the exact value,
+ # numerator units, and denominator units.
+ def eql?(other)
+ value == other.value && numerator_units == other.numerator_units &&
+ denominator_units == other.denominator_units
+ end
+
+ # The SassScript `>` operation.
+ #
+ # @param other [Number] The right-hand side of the operator
+ # @return [Boolean] Whether this number is greater than the other
+ # @raise [NoMethodError] if `other` is an invalid type
+ def gt(other)
+ raise NoMethodError.new(nil, :gt) unless other.is_a?(Number)
+ operate(other, :>)
+ end
+
+ # The SassScript `>=` operation.
+ #
+ # @param other [Number] The right-hand side of the operator
+ # @return [Boolean] Whether this number is greater than or equal to the other
+ # @raise [NoMethodError] if `other` is an invalid type
+ def gte(other)
+ raise NoMethodError.new(nil, :gte) unless other.is_a?(Number)
+ operate(other, :>=)
+ end
+
+ # The SassScript `<` operation.
+ #
+ # @param other [Number] The right-hand side of the operator
+ # @return [Boolean] Whether this number is less than the other
+ # @raise [NoMethodError] if `other` is an invalid type
+ def lt(other)
+ raise NoMethodError.new(nil, :lt) unless other.is_a?(Number)
+ operate(other, :<)
+ end
+
+ # The SassScript `<=` operation.
+ #
+ # @param other [Number] The right-hand side of the operator
+ # @return [Boolean] Whether this number is less than or equal to the other
+ # @raise [NoMethodError] if `other` is an invalid type
+ def lte(other)
+ raise NoMethodError.new(nil, :lte) unless other.is_a?(Number)
+ operate(other, :<=)
+ end
+
+ # @return [String] The CSS representation of this number
+ # @raise [Sass::SyntaxError] if this number has units that can't be used in CSS
+ # (e.g. `px*in`)
+ def to_s(opts = {})
+ return original if original
+ raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units?
+ inspect
+ end
+
+ # Returns a readable representation of this number.
+ #
+ # This representation is valid CSS (and valid SassScript)
+ # as long as there is only one unit.
+ #
+ # @return [String] The representation
+ def inspect(opts = {})
+ return original if original
+
+ value = self.class.round(self.value)
+ str = value.to_s
+
+ # Ruby will occasionally print in scientific notation if the number is
+ # small enough. That's technically valid CSS, but it's not well-supported
+ # and confusing.
+ str = ("%0.#{self.class.precision}f" % value).gsub(/0*$/, '') if str.include?('e')
+
+ unitless? ? str : "#{str}#{unit_str}"
+ end
+ alias_method :to_sass, :inspect
+
+ # @return [Fixnum] The integer value of the number
+ # @raise [Sass::SyntaxError] if the number isn't an integer
+ def to_i
+ super unless int?
+ value.to_i
+ end
+
+ # @return [Boolean] Whether or not this number is an integer.
+ def int?
+ value % 1 == 0.0
+ end
+
+ # @return [Boolean] Whether or not this number has no units.
+ def unitless?
+ @numerator_units.empty? && @denominator_units.empty?
+ end
+
+ # Checks whether the number has the numerator unit specified.
+ #
+ # @example
+ # number = Sass::Script::Value::Number.new(10, "px")
+ # number.is_unit?("px") => true
+ # number.is_unit?(nil) => false
+ #
+ # @param unit [::String, nil] The unit the number should have or nil if the number
+ # should be unitless.
+ # @see Number#unitless? The unitless? method may be more readable.
+ def is_unit?(unit)
+ if unit
+ denominator_units.size == 0 && numerator_units.size == 1 && numerator_units.first == unit
+ else
+ unitless?
+ end
+ end
+
+ # @return [Boolean] Whether or not this number has units that can be represented in CSS
+ # (that is, zero or one \{#numerator\_units}).
+ def legal_units?
+ (@numerator_units.empty? || @numerator_units.size == 1) && @denominator_units.empty?
+ end
+
+ # Returns this number converted to other units.
+ # The conversion takes into account the relationship between e.g. mm and cm,
+ # as well as between e.g. in and cm.
+ #
+ # If this number has no units, it will simply return itself
+ # with the given units.
+ #
+ # An incompatible coercion, e.g. between px and cm, will raise an error.
+ #
+ # @param num_units [Array<String>] The numerator units to coerce this number into.
+ # See {\#numerator\_units}
+ # @param den_units [Array<String>] The denominator units to coerce this number into.
+ # See {\#denominator\_units}
+ # @return [Number] The number with the new units
+ # @raise [Sass::UnitConversionError] if the given units are incompatible with the number's
+ # current units
+ def coerce(num_units, den_units)
+ Number.new(if unitless?
+ value
+ else
+ value * coercion_factor(@numerator_units, num_units) /
+ coercion_factor(@denominator_units, den_units)
+ end, num_units, den_units)
+ end
+
+ # @param other [Number] A number to decide if it can be compared with this number.
+ # @return [Boolean] Whether or not this number can be compared with the other.
+ def comparable_to?(other)
+ operate(other, :+)
+ true
+ rescue Sass::UnitConversionError
+ false
+ end
+
+ # Returns a human readable representation of the units in this number.
+ # For complex units this takes the form of:
+ # numerator_unit1 * numerator_unit2 / denominator_unit1 * denominator_unit2
+ # @return [String] a string that represents the units in this number
+ def unit_str
+ rv = @numerator_units.sort.join("*")
+ if @denominator_units.any?
+ rv << "/"
+ rv << @denominator_units.sort.join("*")
+ end
+ rv
+ end
+
+ private
+
+ # @private
+ def self.round(num)
+ if num.is_a?(Float) && (num.infinite? || num.nan?)
+ num
+ elsif num % 1 == 0.0
+ num.to_i
+ else
+ ((num * precision_factor).round / precision_factor).to_f
+ end
+ end
+
+ OPERATIONS = [:+, :-, :<=, :<, :>, :>=, :%]
+
+ def operate(other, operation)
+ this = self
+ if OPERATIONS.include?(operation)
+ if unitless?
+ this = this.coerce(other.numerator_units, other.denominator_units)
+ else
+ other = other.coerce(@numerator_units, @denominator_units)
+ end
+ end
+ # avoid integer division
+ value = :/ == operation ? this.value.to_f : this.value
+ result = value.send(operation, other.value)
+
+ if result.is_a?(Numeric)
+ Number.new(result, *compute_units(this, other, operation))
+ else # Boolean op
+ Bool.new(result)
+ end
+ end
+
+ def coercion_factor(from_units, to_units)
+ # get a list of unmatched units
+ from_units, to_units = sans_common_units(from_units, to_units)
+
+ if from_units.size != to_units.size || !convertable?(from_units | to_units)
+ raise Sass::UnitConversionError.new(
+ "Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.")
+ end
+
+ from_units.zip(to_units).inject(1) {|m, p| m * conversion_factor(p[0], p[1])}
+ end
+
+ def compute_units(this, other, operation)
+ case operation
+ when :*
+ [this.numerator_units + other.numerator_units,
+ this.denominator_units + other.denominator_units]
+ when :/
+ [this.numerator_units + other.denominator_units,
+ this.denominator_units + other.numerator_units]
+ else
+ [this.numerator_units, this.denominator_units]
+ end
+ end
+
+ def normalize!
+ return if unitless?
+ @numerator_units, @denominator_units =
+ sans_common_units(@numerator_units, @denominator_units)
+
+ @denominator_units.each_with_index do |d, i|
+ if convertable?(d) && (u = @numerator_units.find(&method(:convertable?)))
+ @value /= conversion_factor(d, u)
+ @denominator_units.delete_at(i)
+ @numerator_units.delete_at(@numerator_units.index(u))
+ end
+ end
+ end
+
+ # This is the source data for all the unit logic. It's pre-processed to make
+ # it efficient to figure out whether a set of units is mutually compatible
+ # and what the conversion ratio is between two units.
+ #
+ # These come from http://www.w3.org/TR/2012/WD-css3-values-20120308/.
+ relative_sizes = [
+ {
+ 'in' => Rational(1),
+ 'cm' => Rational(1, 2.54),
+ 'pc' => Rational(1, 6),
+ 'mm' => Rational(1, 25.4),
+ 'pt' => Rational(1, 72),
+ 'px' => Rational(1, 96)
+ },
+ {
+ 'deg' => Rational(1, 360),
+ 'grad' => Rational(1, 400),
+ 'rad' => Rational(1, 2 * Math::PI),
+ 'turn' => Rational(1)
+ },
+ {
+ 's' => Rational(1),
+ 'ms' => Rational(1, 1000)
+ },
+ {
+ 'Hz' => Rational(1),
+ 'kHz' => Rational(1000)
+ },
+ {
+ 'dpi' => Rational(1),
+ 'dpcm' => Rational(1, 2.54),
+ 'dppx' => Rational(1, 96)
+ }
+ ]
+
+ # A hash from each known unit to the set of units that it's mutually
+ # convertible with.
+ MUTUALLY_CONVERTIBLE = {}
+ relative_sizes.map do |values|
+ set = values.keys.to_set
+ values.keys.each {|name| MUTUALLY_CONVERTIBLE[name] = set}
+ end
+
+ # A two-dimensional hash from two units to the conversion ratio between
+ # them. Multiply `X` by `CONVERSION_TABLE[X][Y]` to convert it to `Y`.
+ CONVERSION_TABLE = {}
+ relative_sizes.each do |values|
+ values.each do |(name1, value1)|
+ CONVERSION_TABLE[name1] ||= {}
+ values.each do |(name2, value2)|
+ value = value1 / value2
+ CONVERSION_TABLE[name1][name2] = value.denominator == 1 ? value.to_i : value.to_f
+ end
+ end
+ end
+
+ def conversion_factor(from_unit, to_unit)
+ CONVERSION_TABLE[from_unit][to_unit]
+ end
+
+ def convertable?(units)
+ units = Array(units).to_set
+ return true if units.empty?
+ return false unless (mutually_convertible = MUTUALLY_CONVERTIBLE[units.first])
+ units.subset?(mutually_convertible)
+ end
+
+ def sans_common_units(units1, units2)
+ units2 = units2.dup
+ # Can't just use -, because we want px*px to coerce properly to px*mm
+ units1 = units1.map do |u|
+ j = units2.index(u)
+ next u unless j
+ units2.delete_at(j)
+ nil
+ end
+ units1.compact!
+ return units1, units2
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/script/value/string.rb
b/backends/css/gems/sass-3.4.9/lib/sass/script/value/string.rb
new file mode 100644
index 0000000..1d47980
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/script/value/string.rb
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+module Sass::Script::Value
+ # A SassScript object representing a CSS string *or* a CSS identifier.
+ class String < Base
+ # The Ruby value of the string.
+ #
+ # @return [String]
+ attr_reader :value
+
+ # Whether this is a CSS string or a CSS identifier.
+ # The difference is that strings are written with double-quotes,
+ # while identifiers aren't.
+ #
+ # @return [Symbol] `:string` or `:identifier`
+ attr_reader :type
+
+ def self.value(contents)
+ contents.gsub("\\\n", "").gsub(/\\(?:([0-9a-fA-F]{1,6})\s?|(.))/) do
+ next $2 if $2
+ # Handle unicode escapes as per CSS Syntax Level 3 section 4.3.8.
+ code_point = $1.to_i(16)
+ if code_point == 0 || code_point > 0x10FFFF ||
+ (code_point >= 0xD800 && code_point <= 0xDFFF)
+ '�'
+ else
+ [code_point].pack("U")
+ end
+ end
+ end
+
+ def self.quote(contents, quote = nil)
+ # Short-circuit if there are no characters that need quoting.
+ unless contents =~ /[\n\\"']/
+ quote ||= '"'
+ return "#{quote}#{contents}#{quote}"
+ end
+
+ if quote.nil?
+ if contents.include?('"')
+ if contents.include?("'")
+ quote = '"'
+ else
+ quote = "'"
+ end
+ else
+ quote = '"'
+ end
+ end
+
+ # Replace single backslashes with multiples.
+ contents = contents.gsub("\\", "\\\\\\\\")
+
+ if quote == '"'
+ contents = contents.gsub('"', "\\\"")
+ else
+ contents = contents.gsub("'", "\\'")
+ end
+
+ contents = contents.gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ")
+ "#{quote}#{contents}#{quote}"
+ end
+
+ # Creates a new string.
+ #
+ # @param value [String] See \{#value}
+ # @param type [Symbol] See \{#type}
+ def initialize(value, type = :identifier)
+ super(value)
+ @type = type
+ end
+
+ # @see Value#plus
+ def plus(other)
+ other_value = if other.is_a?(Sass::Script::Value::String)
+ other.value
+ else
+ other.to_s(:quote => :none)
+ end
+ Sass::Script::Value::String.new(value + other_value, type)
+ end
+
+ # @see Value#to_s
+ def to_s(opts = {})
+ return @value.gsub(/\n\s*/, ' ') if opts[:quote] == :none || @type == :identifier
+ Sass::Script::Value::String.quote(value, opts[:quote])
+ end
+
+ # @see Value#to_sass
+ def to_sass(opts = {})
+ to_s
+ end
+
+ def inspect
+ String.quote(value)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss.rb b/backends/css/gems/sass-3.4.9/lib/sass/scss.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/scss.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/scss.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/scss/css_parser.rb
b/backends/css/gems/sass-3.4.9/lib/sass/scss/css_parser.rb
new file mode 100644
index 0000000..d655816
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/scss/css_parser.rb
@@ -0,0 +1,42 @@
+require 'sass/script/css_parser'
+
+module Sass
+ module SCSS
+ # This is a subclass of {Parser} which only parses plain CSS.
+ # It doesn't support any Sass extensions, such as interpolation,
+ # parent references, nested selectors, and so forth.
+ # It does support all the same CSS hacks as the SCSS parser, though.
+ class CssParser < StaticParser
+ private
+
+ def placeholder_selector; nil; end
+ def parent_selector; nil; end
+ def interpolation(warn_for_color = false); nil; end
+ def use_css_import?; true; end
+
+ def block_child(context)
+ case context
+ when :ruleset
+ declaration
+ when :stylesheet
+ directive || ruleset
+ when :directive
+ directive || declaration_or_ruleset
+ end
+ end
+
+ def nested_properties!(node)
+ expected('expression (e.g. 1px, bold)')
+ end
+
+ def ruleset
+ start_pos = source_position
+ return unless (selector = selector_comma_sequence)
+ block(node(Sass::Tree::RuleNode.new(selector, range(start_pos)), start_pos), :ruleset)
+ end
+
+ @sass_script_parser = Class.new(Sass::Script::CssParser)
+ @sass_script_parser.send(:include, ScriptParser)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/scss/parser.rb
b/backends/css/gems/sass-3.4.9/lib/sass/scss/parser.rb
new file mode 100644
index 0000000..65378ea
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/scss/parser.rb
@@ -0,0 +1,1211 @@
+# -*- coding: utf-8 -*-
+require 'set'
+
+module Sass
+ module SCSS
+ # The parser for SCSS.
+ # It parses a string of code into a tree of {Sass::Tree::Node}s.
+ class Parser
+ # Expose for the SASS parser.
+ attr_accessor :offset
+
+ # @param str [String, StringScanner] The source document to parse.
+ # Note that `Parser` *won't* raise a nice error message if this isn't properly parsed;
+ # for that, you should use the higher-level {Sass::Engine} or {Sass::CSS}.
+ # @param filename [String] The name of the file being parsed. Used for
+ # warnings and source maps.
+ # @param importer [Sass::Importers::Base] The importer used to import the
+ # file being parsed. Used for source maps.
+ # @param line [Fixnum] The 1-based line on which the source string appeared,
+ # if it's part of another document.
+ # @param offset [Fixnum] The 1-based character (not byte) offset in the line on
+ # which the source string starts. Used for error reporting and sourcemap
+ # building.
+ # @comment
+ # rubocop:disable ParameterLists
+ def initialize(str, filename, importer, line = 1, offset = 1)
+ # rubocop:enable ParameterLists
+ @template = str
+ @filename = filename
+ @importer = importer
+ @line = line
+ @offset = offset
+ @strs = []
+ end
+
+ # Parses an SCSS document.
+ #
+ # @return [Sass::Tree::RootNode] The root node of the document tree
+ # @raise [Sass::SyntaxError] if there's a syntax error in the document
+ def parse
+ init_scanner!
+ root = stylesheet
+ expected("selector or at-rule") unless root && @scanner.eos?
+ root
+ end
+
+ # Parses an identifier with interpolation.
+ # Note that this won't assert that the identifier takes up the entire input string;
+ # it's meant to be used with `StringScanner`s as part of other parsers.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>, nil]
+ # The interpolated identifier, or nil if none could be parsed
+ def parse_interp_ident
+ init_scanner!
+ interp_ident
+ end
+
+ # Parses a media query list.
+ #
+ # @return [Sass::Media::QueryList] The parsed query list
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query list,
+ # or if it doesn't take up the entire input string.
+ def parse_media_query_list
+ init_scanner!
+ ql = media_query_list
+ expected("media query list") unless ql && @scanner.eos?
+ ql
+ end
+
+ # Parses an at-root query.
+ #
+ # @return [Array<String, Sass::Script;:Tree::Node>] The interpolated query.
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query,
+ # or if it doesn't take up the entire input string.
+ def parse_at_root_query
+ init_scanner!
+ query = at_root_query
+ expected("@at-root query list") unless query && @scanner.eos?
+ query
+ end
+
+ # Parses a supports query condition.
+ #
+ # @return [Sass::Supports::Condition] The parsed condition
+ # @raise [Sass::SyntaxError] if there's a syntax error in the condition,
+ # or if it doesn't take up the entire input string.
+ def parse_supports_condition
+ init_scanner!
+ condition = supports_condition
+ expected("supports condition") unless condition && @scanner.eos?
+ condition
+ end
+
+ private
+
+ include Sass::SCSS::RX
+
+ def source_position
+ Sass::Source::Position.new(@line, @offset)
+ end
+
+ def range(start_pos, end_pos = source_position)
+ Sass::Source::Range.new(start_pos, end_pos, @filename, @importer)
+ end
+
+ def init_scanner!
+ @scanner =
+ if @template.is_a?(StringScanner)
+ @template
+ else
+ Sass::Util::MultibyteStringScanner.new(@template.gsub("\r", ""))
+ end
+ end
+
+ def stylesheet
+ node = node(Sass::Tree::RootNode.new(@scanner.string), source_position)
+ block_contents(node, :stylesheet) {s(node)}
+ end
+
+ def s(node)
+ while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
+ next unless c
+ process_comment c, node
+ c = nil
+ end
+ true
+ end
+
+ def ss
+ nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
+ true
+ end
+
+ def ss_comments(node)
+ while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
+ next unless c
+ process_comment c, node
+ c = nil
+ end
+
+ true
+ end
+
+ def whitespace
+ return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
+ ss
+ end
+
+ def process_comment(text, node)
+ silent = text =~ %r{\A//}
+ loud = !silent && text =~ %r{\A/[/*]!}
+ line = @line - text.count("\n")
+
+ if silent
+ value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */']
+ else
+ value = Sass::Engine.parse_interp(
+ text, line, @scanner.pos - text.size, :filename => @filename)
+ string_before_comment = @scanner string[0 scanner pos - text.length]
+ newline_before_comment = string_before_comment.rindex("\n")
+ last_line_before_comment =
+ if newline_before_comment
+ string_before_comment[newline_before_comment + 1..-1]
+ else
+ string_before_comment
+ end
+ value.unshift(last_line_before_comment.gsub(/[^\s]/, ' '))
+ end
+
+ type = if silent
+ :silent
+ elsif loud
+ :loud
+ else
+ :normal
+ end
+ comment = Sass::Tree::CommentNode.new(value, type)
+ comment.line = line
+ node << comment
+ end
+
+ DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
+ :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
+ :_moz_document, :at_root, :error]
+
+ PREFIXED_DIRECTIVES = Set[:supports]
+
+ def directive
+ start_pos = source_position
+ return unless tok(/@/)
+ name = tok!(IDENT)
+ ss
+
+ if (dir = special_directive(name, start_pos))
+ return dir
+ elsif (dir = prefixed_directive(name, start_pos))
+ return dir
+ end
+
+ val = almost_any_value
+ val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
+ directive_body(val, start_pos)
+ end
+
+ def directive_body(value, start_pos)
+ node = Sass::Tree::DirectiveNode.new(value)
+
+ if tok(/\{/)
+ node.has_children = true
+ block_contents(node, :directive)
+ tok!(/\}/)
+ end
+
+ node(node, start_pos)
+ end
+
+ def special_directive(name, start_pos)
+ sym = name.gsub('-', '_').to_sym
+ DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos)
+ end
+
+ def prefixed_directive(name, start_pos)
+ sym = deprefix(name).gsub('-', '_').to_sym
+ PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos)
+ end
+
+ def mixin_directive(start_pos)
+ name = tok! IDENT
+ args, splat = sass_script(:parse_mixin_definition_arglist)
+ ss
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive)
+ end
+
+ def include_directive(start_pos)
+ name = tok! IDENT
+ args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist)
+ ss
+ include_node = node(
+ Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos)
+ if tok?(/\{/)
+ include_node.has_children = true
+ block(include_node, :directive)
+ else
+ include_node
+ end
+ end
+
+ def content_directive(start_pos)
+ ss
+ node(Sass::Tree::ContentNode.new, start_pos)
+ end
+
+ def function_directive(start_pos)
+ name = tok! IDENT
+ args, splat = sass_script(:parse_function_definition_arglist)
+ ss
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function)
+ end
+
+ def return_directive(start_pos)
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos)
+ end
+
+ def debug_directive(start_pos)
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos)
+ end
+
+ def warn_directive(start_pos)
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos)
+ end
+
+ def for_directive(start_pos)
+ tok!(/\$/)
+ var = tok! IDENT
+ ss
+
+ tok!(/from/)
+ from = sass_script(:parse_until, Set["to", "through"])
+ ss
+
+ @expected = '"to" or "through"'
+ exclusive = (tok(/to/) || tok!(/through/)) == 'to'
+ to = sass_script(:parse)
+ ss
+
+ block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive)
+ end
+
+ def each_directive(start_pos)
+ tok!(/\$/)
+ vars = [tok!(IDENT)]
+ ss
+ while tok(/,/)
+ ss
+ tok!(/\$/)
+ vars << tok!(IDENT)
+ ss
+ end
+
+ tok!(/in/)
+ list = sass_script(:parse)
+ ss
+
+ block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
+ end
+
+ def while_directive(start_pos)
+ expr = sass_script(:parse)
+ ss
+ block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive)
+ end
+
+ def if_directive(start_pos)
+ expr = sass_script(:parse)
+ ss
+ node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive)
+ pos = @scanner.pos
+ line = @line
+ ss
+
+ else_block(node) ||
+ begin
+ # Backtrack in case there are any comments we want to parse
+ @scanner.pos = pos
+ @line = line
+ node
+ end
+ end
+
+ def else_block(node)
+ start_pos = source_position
+ return unless tok(/@else/)
+ ss
+ else_node = block(
+ node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos),
+ :directive)
+ node.add_else(else_node)
+ pos = @scanner.pos
+ line = @line
+ ss
+
+ else_block(node) ||
+ begin
+ # Backtrack in case there are any comments we want to parse
+ @scanner.pos = pos
+ @line = line
+ node
+ end
+ end
+
+ def else_directive(start_pos)
+ err("Invalid CSS: @else must come after @if")
+ end
+
+ def extend_directive(start_pos)
+ selector_start_pos = source_position
+ @expected = "selector"
+ selector = Sass::Util.strip_string_array(expr!(:almost_any_value))
+ optional = tok(OPTIONAL)
+ ss
+ node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos)
+ end
+
+ def import_directive(start_pos)
+ values = []
+
+ loop do
+ values << expr!(:import_arg)
+ break if use_css_import?
+ break unless tok(/,/)
+ ss
+ end
+
+ values
+ end
+
+ def import_arg
+ start_pos = source_position
+ return unless (str = string) || (uri = tok?(/url\(/i))
+ if uri
+ str = sass_script(:parse_string)
+ ss
+ media = media_query_list
+ ss
+ return node(Tree::CssImportNode.new(str, media.to_a), start_pos)
+ end
+ ss
+
+ media = media_query_list
+ if str =~ %r{^(https?:)?//} || media || use_css_import?
+ return node(Sass::Tree::CssImportNode.new(
+ Sass::Script::Value::String.quote(str), media.to_a), start_pos)
+ end
+
+ node(Sass::Tree::ImportNode.new(str.strip), start_pos)
+ end
+
+ def use_css_import?; false; end
+
+ def media_directive(start_pos)
+ block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive)
+ end
+
+ # http://www.w3.org/TR/css3-mediaqueries/#syntax
+ def media_query_list
+ query = media_query
+ return unless query
+ queries = [query]
+
+ ss
+ while tok(/,/)
+ ss; queries << expr!(:media_query)
+ end
+ ss
+
+ Sass::Media::QueryList.new(queries)
+ end
+
+ def media_query
+ if (ident1 = interp_ident)
+ ss
+ ident2 = interp_ident
+ ss
+ if ident2 && ident2.length == 1 && ident2[0].is_a?(String) && ident2[0].downcase == 'and'
+ query = Sass::Media::Query.new([], ident1, [])
+ else
+ if ident2
+ query = Sass::Media::Query.new(ident1, ident2, [])
+ else
+ query = Sass::Media::Query.new([], ident1, [])
+ end
+ return query unless tok(/and/i)
+ ss
+ end
+ end
+
+ if query
+ expr = expr!(:media_expr)
+ else
+ expr = media_expr
+ return unless expr
+ end
+ query ||= Sass::Media::Query.new([], [], [])
+ query.expressions << expr
+
+ ss
+ while tok(/and/i)
+ ss; query.expressions << expr!(:media_expr)
+ end
+
+ query
+ end
+
+ def query_expr
+ interp = interpolation
+ return interp if interp
+ return unless tok(/\(/)
+ res = ['(']
+ ss
+ res << sass_script(:parse)
+
+ if tok(/:/)
+ res << ': '
+ ss
+ res << sass_script(:parse)
+ end
+ res << tok!(/\)/)
+ ss
+ res
+ end
+
+ # Aliases allow us to use different descriptions if the same
+ # expression fails in different contexts.
+ alias_method :media_expr, :query_expr
+ alias_method :at_root_query, :query_expr
+
+ def charset_directive(start_pos)
+ name = expr!(:string)
+ ss
+ node(Sass::Tree::CharsetNode.new(name), start_pos)
+ end
+
+ # The document directive is specified in
+ # http://www.w3.org/TR/css3-conditional/, but Gecko allows the
+ # `url-prefix` and `domain` functions to omit quotation marks, contrary to
+ # the standard.
+ #
+ # We could parse all document directives according to Mozilla's syntax,
+ # but if someone's using e.g. @-webkit-document we don't want them to
+ # think WebKit works sans quotes.
+ def _moz_document_directive(start_pos)
+ res = ["@-moz-document "]
+ loop do
+ res << str {ss} << expr!(:moz_document_function)
+ if (c = tok(/,/))
+ res << c
+ else
+ break
+ end
+ end
+ directive_body(res.flatten, start_pos)
+ end
+
+ def moz_document_function
+ val = interp_uri || _interp_string(:url_prefix) ||
+ _interp_string(:domain) || function(!:allow_var) || interpolation
+ return unless val
+ ss
+ val
+ end
+
+ def at_root_directive(start_pos)
+ if tok?(/\(/) && (expr = at_root_query)
+ return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive)
+ end
+
+ at_root_node = node(Sass::Tree::AtRootNode.new, start_pos)
+ rule_node = ruleset
+ return block(at_root_node, :stylesheet) unless rule_node
+ at_root_node << rule_node
+ at_root_node
+ end
+
+ def at_root_directive_list
+ return unless (first = tok(IDENT))
+ arr = [first]
+ ss
+ while (e = tok(IDENT))
+ arr << e
+ ss
+ end
+ arr
+ end
+
+ def error_directive(start_pos)
+ node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos)
+ end
+
+ # http://www.w3.org/TR/css3-conditional/
+ def supports_directive(name, start_pos)
+ condition = expr!(:supports_condition)
+ node = Sass::Tree::SupportsNode.new(name, condition)
+
+ tok!(/\{/)
+ node.has_children = true
+ block_contents(node, :directive)
+ tok!(/\}/)
+
+ node(node, start_pos)
+ end
+
+ def supports_condition
+ supports_negation || supports_operator || supports_interpolation
+ end
+
+ def supports_negation
+ return unless tok(/not/i)
+ ss
+ Sass::Supports::Negation.new(expr!(:supports_condition_in_parens))
+ end
+
+ def supports_operator
+ cond = supports_condition_in_parens
+ return unless cond
+ while (op = tok(/and|or/i))
+ ss
+ cond = Sass::Supports::Operator.new(
+ cond, expr!(:supports_condition_in_parens), op)
+ end
+ cond
+ end
+
+ def supports_condition_in_parens
+ interp = supports_interpolation
+ return interp if interp
+ return unless tok(/\(/); ss
+ if (cond = supports_condition)
+ tok!(/\)/); ss
+ cond
+ else
+ name = sass_script(:parse)
+ tok!(/:/); ss
+ value = sass_script(:parse)
+ tok!(/\)/); ss
+ Sass::Supports::Declaration.new(name, value)
+ end
+ end
+
+ def supports_declaration_condition
+ return unless tok(/\(/); ss
+ supports_declaration_body
+ end
+
+ def supports_interpolation
+ interp = interpolation
+ return unless interp
+ ss
+ Sass::Supports::Interpolation.new(interp)
+ end
+
+ def variable
+ return unless tok(/\$/)
+ start_pos = source_position
+ name = tok!(IDENT)
+ ss; tok!(/:/); ss
+
+ expr = sass_script(:parse)
+ while tok(/!/)
+ flag_name = tok!(IDENT)
+ if flag_name == 'default'
+ guarded ||= true
+ elsif flag_name == 'global'
+ global ||= true
+ else
+ raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line)
+ end
+ ss
+ end
+
+ result = Sass::Tree::VariableNode.new(name, expr, guarded, global)
+ node(result, start_pos)
+ end
+
+ def operator
+ # Many of these operators (all except / and ,)
+ # are disallowed by the CSS spec,
+ # but they're included here for compatibility
+ # with some proprietary MS properties
+ str {ss if tok(/[\/,:.=]/)}
+ end
+
+ def ruleset
+ start_pos = source_position
+ return unless (rules = almost_any_value)
+ block(node(
+ Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset)
+ end
+
+ def block(node, context)
+ node.has_children = true
+ tok!(/\{/)
+ block_contents(node, context)
+ tok!(/\}/)
+ node
+ end
+
+ # A block may contain declarations and/or rulesets
+ def block_contents(node, context)
+ block_given? ? yield : ss_comments(node)
+ node << (child = block_child(context))
+ while tok(/;/) || has_children?(child)
+ block_given? ? yield : ss_comments(node)
+ node << (child = block_child(context))
+ end
+ node
+ end
+
+ def block_child(context)
+ return variable || directive if context == :function
+ return variable || directive || ruleset if context == :stylesheet
+ variable || directive || declaration_or_ruleset
+ end
+
+ def has_children?(child_or_array)
+ return false unless child_or_array
+ return child_or_array.last.has_children if child_or_array.is_a?(Array)
+ child_or_array.has_children
+ end
+
+ # When parsing the contents of a ruleset, it can be difficult to tell
+ # declarations apart from nested rulesets. Since we don't thoroughly parse
+ # selectors until after resolving interpolation, we can share a bunch of
+ # the parsing of the two, but we need to disambiguate them first. We use
+ # the following criteria:
+ #
+ # * If the entity doesn't start with an identifier followed by a colon,
+ # it's a selector. There are some additional mostly-unimportant cases
+ # here to support various declaration hacks.
+ #
+ # * If the colon is followed by another colon, it's a selector.
+ #
+ # * Otherwise, if the colon is followed by anything other than
+ # interpolation or a character that's valid as the beginning of an
+ # identifier, it's a declaration.
+ #
+ # * If the colon is followed by interpolation or a valid identifier, try
+ # parsing it as a declaration value. If this fails, backtrack and parse
+ # it as a selector.
+ #
+ # * If the declaration value value valid but is followed by "{", backtrack
+ # and parse it as a selector anyway. This ensures that ".foo:bar {" is
+ # always parsed as a selector and never as a property with nested
+ # properties beneath it.
+ def declaration_or_ruleset
+ start_pos = source_position
+ declaration = try_declaration
+
+ if declaration.nil?
+ return unless (selector = almost_any_value)
+ elsif declaration.is_a?(Array)
+ selector = declaration
+ else
+ # Declaration should be a PropNode.
+ return declaration
+ end
+
+ if (additional_selector = almost_any_value)
+ selector << additional_selector
+ end
+
+ block(node(
+ Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset)
+ end
+
+ # Tries to parse a declaration, and returns the value parsed so far if it
+ # fails.
+ #
+ # This has three possible return types. It can return `nil`, indicating
+ # that parsing failed completely and the scanner hasn't moved forward at
+ # all. It can return an Array, indicating that parsing failed after
+ # consuming some text (possibly containing interpolation), which is
+ # returned. Or it can return a PropNode, indicating that parsing
+ # succeeded.
+ def try_declaration
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
+ # val" hacks.
+ name_start_pos = source_position
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
+ name = [s, str {ss}]
+ return name unless (ident = interp_ident)
+ name << ident
+ else
+ return unless (name = interp_ident)
+ name = Array(name)
+ end
+
+ if (comment = tok(COMMENT))
+ name << comment
+ end
+ name_end_pos = source_position
+
+ mid = [str {ss}]
+ return name + mid unless tok(/:/)
+ mid << ':'
+ return name + mid + [':'] if tok(/:/)
+ mid << str {ss}
+ post_colon_whitespace = !mid.last.empty?
+ could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START))
+
+ value_start_pos = source_position
+ value = nil
+ error = catch_error do
+ value = value!
+ if tok?(/\{/)
+ # Properties that are ambiguous with selectors can't have additional
+ # properties nested beneath them.
+ tok!(/;/) if could_be_selector
+ elsif !tok?(/[;{}]/)
+ # We want an exception if there's no valid end-of-property character
+ # exists, but we don't want to consume it if it does.
+ tok!(/[;{}]/)
+ end
+ end
+
+ if error
+ rethrow error unless could_be_selector
+
+ # If the value would be followed by a semicolon, it's definitely
+ # supposed to be a property, not a selector.
+ additional_selector = almost_any_value
+ rethrow error if tok?(/;/)
+
+ return name + mid + (additional_selector || [])
+ end
+
+ value_end_pos = source_position
+ ss
+ require_block = tok?(/\{/)
+
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
+ name_start_pos, value_end_pos)
+ node.name_source_range = range(name_start_pos, name_end_pos)
+ node.value_source_range = range(value_start_pos, value_end_pos)
+
+ return node unless require_block
+ nested_properties! node
+ end
+
+ # This production is similar to the CSS [`<any-value>`][any-value]
+ # production, but as the name implies, not quite the same. It's meant to
+ # consume values that could be a selector, an expression, or a combination
+ # of both. It respects strings and comments and supports interpolation. It
+ # will consume up to "{", "}", ";", or "!".
+ #
+ # [any-value]: http://dev.w3.org/csswg/css-variables/#typedef-any-value
+ #
+ # Values consumed by this production will usually be parsed more
+ # thoroughly once interpolation has been resolved.
+ def almost_any_value
+ return unless (tok = almost_any_value_token)
+ sel = [tok]
+ while (tok = almost_any_value_token)
+ sel << tok
+ end
+ merge(sel)
+ end
+
+ def almost_any_value_token
+ tok(%r{
+ (
+ \\.
+ |
+ (?!url\()
+ [^"'/\#!;\{\}] # "
+ |
+ /(?![/*])
+ |
+ \#(?!\{)
+ |
+ !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed.
+ )+
+ }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri ||
+ interpolation(:warn_for_color)
+ end
+
+ def declaration
+ # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop:
+ # val" hacks.
+ name_start_pos = source_position
+ if (s = tok(/[:\*\.]|\#(?!\{)/))
+ name = [s, str {ss}, *expr!(:interp_ident)]
+ else
+ return unless (name = interp_ident)
+ name = Array(name)
+ end
+
+ if (comment = tok(COMMENT))
+ name << comment
+ end
+ name_end_pos = source_position
+ ss
+
+ tok!(/:/)
+ ss
+ value_start_pos = source_position
+ value = value!
+ value_end_pos = source_position
+ ss
+ require_block = tok?(/\{/)
+
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new),
+ name_start_pos, value_end_pos)
+ node.name_source_range = range(name_start_pos, name_end_pos)
+ node.value_source_range = range(value_start_pos, value_end_pos)
+
+ return node unless require_block
+ nested_properties! node
+ end
+
+ def value!
+ if tok?(/\{/)
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
+ str.line = source_position.line
+ str.source_range = range(source_position)
+ return str
+ end
+
+ start_pos = source_position
+ # This is a bit of a dirty trick:
+ # if the value is completely static,
+ # we don't parse it at all, and instead return a plain old string
+ # containing the value.
+ # This results in a dramatic speed increase.
+ if (val = tok(STATIC_VALUE, true))
+ str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(val.strip))
+ str.line = start_pos.line
+ str.source_range = range(start_pos)
+ return str
+ end
+ sass_script(:parse)
+ end
+
+ def nested_properties!(node)
+ @expected = 'expression (e.g. 1px, bold) or "{"'
+ block(node, :property)
+ end
+
+ def expr(allow_var = true)
+ t = term(allow_var)
+ return unless t
+ res = [t, str {ss}]
+
+ while (o = operator) && (t = term(allow_var))
+ res << o << t << str {ss}
+ end
+
+ res.flatten
+ end
+
+ def term(allow_var)
+ e = tok(NUMBER) ||
+ interp_uri ||
+ function(allow_var) ||
+ interp_string ||
+ tok(UNICODERANGE) ||
+ interp_ident ||
+ tok(HEXCOLOR) ||
+ (allow_var && var_expr)
+ return e if e
+
+ op = tok(/[+-]/)
+ return unless op
+ @expected = "number or function"
+ [op,
+ tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)]
+ end
+
+ def function(allow_var)
+ name = tok(FUNCTION)
+ return unless name
+ if name == "expression(" || name == "calc("
+ str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
+ [name, str]
+ else
+ [name, str {ss}, expr(allow_var), tok!(/\)/)]
+ end
+ end
+
+ def var_expr
+ return unless tok(/\$/)
+ line = @line
+ var = Sass::Script::Tree::Variable.new(tok!(IDENT))
+ var.line = line
+ var
+ end
+
+ def interpolation(warn_for_color = false)
+ return unless tok(INTERP_START)
+ sass_script(:parse_interpolated, warn_for_color)
+ end
+
+ def string
+ return unless tok(STRING)
+ Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
+ end
+
+ def interp_string
+ _interp_string(:double) || _interp_string(:single)
+ end
+
+ def interp_uri
+ _interp_string(:uri)
+ end
+
+ def _interp_string(type)
+ start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false])
+ return unless start
+ res = [start]
+
+ mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true]
+ # @scanner[2].empty? means we've started an interpolated section
+ while @scanner[2] == '#{'
+ @scanner.pos -= 2 # Don't consume the #{
+ res.last.slice!(-2..-1)
+ res << expr!(:interpolation) << tok(mid_re)
+ end
+ res
+ end
+
+ def interp_ident(start = IDENT)
+ val = tok(start) || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP, true)
+ return unless val
+ res = [val]
+ while (val = tok(NAME) || interpolation(:warn_for_color))
+ res << val
+ end
+ res
+ end
+
+ def interp_ident_or_var
+ id = interp_ident
+ return id if id
+ var = var_expr
+ return [var] if var
+ end
+
+ def str
+ @strs.push ""
+ yield
+ @strs.last
+ ensure
+ @strs.pop
+ end
+
+ def str?
+ pos = @scanner.pos
+ line = @line
+ offset = @offset
+ @strs.push ""
+ throw_error {yield} && @strs.last
+ rescue Sass::SyntaxError
+ @scanner.pos = pos
+ @line = line
+ @offset = offset
+ nil
+ ensure
+ @strs.pop
+ end
+
+ def node(node, start_pos, end_pos = source_position)
+ node.line = start_pos.line
+ node.source_range = range(start_pos, end_pos)
+ node
+ end
+
+ @sass_script_parser = Class.new(Sass::Script::Parser)
+ @sass_script_parser.send(:include, ScriptParser)
+
+ class << self
+ # @private
+ attr_accessor :sass_script_parser
+ end
+
+ def sass_script(*args)
+ parser = self.class.sass_script_parser.new(@scanner, @line, @offset,
+ :filename => @filename, :importer => @importer)
+ result = parser.send(*args)
+ unless @strs.empty?
+ # Convert to CSS manually so that comments are ignored.
+ src = result.to_sass
+ @strs.each {|s| s << src}
+ end
+ @line = parser.line
+ @offset = parser.offset
+ result
+ rescue Sass::SyntaxError => e
+ throw(:_sass_parser_error, true) if @throw_error
+ raise e
+ end
+
+ def merge(arr)
+ arr && Sass::Util.merge_adjacent_strings([arr].flatten)
+ end
+
+ EXPR_NAMES = {
+ :media_query => "media query (e.g. print, screen, print and screen)",
+ :media_query_list => "media query (e.g. print, screen, print and screen)",
+ :media_expr => "media expression (e.g. (min-device-width: 800px))",
+ :at_root_query => "@at-root query (e.g. (without: media))",
+ :at_root_directive_list => '* or identifier',
+ :pseudo_args => "expression (e.g. fr, 2n+1)",
+ :interp_ident => "identifier",
+ :qualified_name => "identifier",
+ :expr => "expression (e.g. 1px, bold)",
+ :selector_comma_sequence => "selector",
+ :string => "string",
+ :import_arg => "file to import (string or url())",
+ :moz_document_function => "matching function (e.g. url-prefix(), domain())",
+ :supports_condition => "@supports condition (e.g. (display: flexbox))",
+ :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))",
+ :a_n_plus_b => "An+B expression",
+ :keyframes_selector_component => "from, to, or a percentage",
+ :keyframes_selector => "keyframes selector (e.g. 10%)"
+ }
+
+ TOK_NAMES = Sass::Util.to_hash(Sass::SCSS::RX.constants.map do |c|
+ [Sass::SCSS::RX.const_get(c), c.downcase]
+ end).merge(
+ IDENT => "identifier",
+ /[;{}]/ => '";"',
+ /\b(without|with)\b/ => '"with" or "without"'
+ )
+
+ def tok?(rx)
+ @scanner.match?(rx)
+ end
+
+ def expr!(name)
+ e = send(name)
+ return e if e
+ expected(EXPR_NAMES[name] || name.to_s)
+ end
+
+ def tok!(rx)
+ t = tok(rx)
+ return t if t
+ name = TOK_NAMES[rx]
+
+ unless name
+ # Display basic regexps as plain old strings
+ source = rx.source.gsub(/\\\//, '/')
+ string = rx.source.gsub(/\\(.)/, '\1')
+ name = source == Regexp.escape(string) ? string.inspect : rx.inspect
+ end
+
+ expected(name)
+ end
+
+ def expected(name)
+ throw(:_sass_parser_error, true) if @throw_error
+ self.class.expected(@scanner, @expected || name, @line)
+ end
+
+ def err(msg)
+ throw(:_sass_parser_error, true) if @throw_error
+ raise Sass::SyntaxError.new(msg, :line => @line)
+ end
+
+ def throw_error
+ old_throw_error, @throw_error = @throw_error, false
+ yield
+ ensure
+ @throw_error = old_throw_error
+ end
+
+ def catch_error(&block)
+ old_throw_error, @throw_error = @throw_error, true
+ pos = @scanner.pos
+ line = @line
+ offset = @offset
+ expected = @expected
+ if catch(:_sass_parser_error) {yield; false}
+ @scanner.pos = pos
+ @line = line
+ @offset = offset
+ @expected = expected
+ {:pos => pos, :line => line, :expected => @expected, :block => block}
+ end
+ ensure
+ @throw_error = old_throw_error
+ end
+
+ def rethrow(err)
+ if @throw_error
+ throw :_sass_parser_error, err
+ else
+ @scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string)
+ @scanner.pos = err[:pos]
+ @line = err[:line]
+ @expected = err[:expected]
+ err[:block].call
+ end
+ end
+
+ # @private
+ def self.expected(scanner, expected, line)
+ pos = scanner.pos
+
+ after = scanner.string[0...pos]
+ # Get rid of whitespace between pos and the last token,
+ # but only if there's a newline in there
+ after.gsub!(/\s*\n\s*$/, '')
+ # Also get rid of stuff before the last newline
+ after.gsub!(/.*\n/, '')
+ after = "..." + after[-15..-1] if after.size > 18
+
+ was = scanner.rest.dup
+ # Get rid of whitespace between pos and the next token,
+ # but only if there's a newline in there
+ was.gsub!(/^\s*\n\s*/, '')
+ # Also get rid of stuff after the next newline
+ was.gsub!(/\n.*/, '')
+ was = was[0...15] + "..." if was.size > 18
+
+ raise Sass::SyntaxError.new(
+ "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"",
+ :line => line)
+ end
+
+ # Avoid allocating lots of new strings for `#tok`.
+ # This is important because `#tok` is called all the time.
+ NEWLINE = "\n"
+
+ def tok(rx, last_group_lookahead = false)
+ res = @scanner.scan(rx)
+ if res
+ # This fixes https://github.com/nex3/sass/issues/104, which affects
+ # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
+ # positive lookahead operator in the Regexp (which matches without
+ # consuming the matched group), with a match that does consume the
+ # group, but then rewinds the scanner and removes the group from the
+ # end of the matched string. This fix makes the assumption that the
+ # matched group will always occur at the end of the match.
+ if last_group_lookahead && @scanner[-1]
+ @scanner.pos -= @scanner[-1].length
+ res.slice!(- scanner[-1] length -1)
+ end
+
+ newline_count = res.count(NEWLINE)
+ if newline_count > 0
+ @line += newline_count
+ @offset = res[res.rindex(NEWLINE)..-1].size
+ else
+ @offset += res.size
+ end
+
+ @expected = nil
+ if ! strs empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
+ @strs.each {|s| s << res}
+ end
+ res
+ end
+ end
+
+ # Remove a vendor prefix from `str`.
+ def deprefix(str)
+ str.gsub(/^-[a-zA-Z0-9]+-/, '')
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/scss/rx.rb
b/backends/css/gems/sass-3.4.9/lib/sass/scss/rx.rb
new file mode 100644
index 0000000..2226157
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/scss/rx.rb
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+module Sass
+ module SCSS
+ # A module containing regular expressions used
+ # for lexing tokens in an SCSS document.
+ # Most of these are taken from [the CSS3 spec](http://www.w3.org/TR/css3-syntax/#lexical),
+ # although some have been modified for various reasons.
+ module RX
+ # Takes a string and returns a CSS identifier
+ # that will have the value of the given string.
+ #
+ # @param str [String] The string to escape
+ # @return [String] The escaped string
+ def self.escape_ident(str)
+ return "" if str.empty?
+ return "\\#{str}" if str == '-' || str == '_'
+ out = ""
+ value = str.dup
+ out << value.slice!(0...1) if value =~ /^[-_]/
+ if value[0...1] =~ NMSTART
+ out << value.slice!(0...1)
+ else
+ out << escape_char(value.slice!(0...1))
+ end
+ out << value.gsub(/[^a-zA-Z0-9_-]/) {|c| escape_char c}
+ out
+ end
+
+ # Escapes a single character for a CSS identifier.
+ #
+ # @param c [String] The character to escape. Should have length 1
+ # @return [String] The escaped character
+ # @private
+ def self.escape_char(c)
+ return "\\%06x" % Sass::Util.ord(c) unless c =~ /[ -\/:-~]/
+ "\\#{c}"
+ end
+
+ # Creates a Regexp from a plain text string,
+ # escaping all significant characters.
+ #
+ # @param str [String] The text of the regexp
+ # @param flags [Fixnum] Flags for the created regular expression
+ # @return [Regexp]
+ # @private
+ def self.quote(str, flags = 0)
+ Regexp.new(Regexp.quote(str), flags)
+ end
+
+ H = /[0-9a-fA-F]/
+ NL = /\n|\r\n|\r|\f/
+ UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
+ s = if Sass::Util.ruby1_8?
+ '\200-\377'
+ elsif Sass::Util.macruby?
+ '\u0080-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF'
+ else
+ '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
+ end
+ NONASCII = /[#{s}]/
+ ESCAPE = /#{UNICODE}|\\[ -~#{s}]/
+ NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/
+ NMCHAR = /[a-zA-Z0-9_-]|#{NONASCII}|#{ESCAPE}/
+ STRING1 = /\"((?:[^\n\r\f\\"]|\\#{NL}|#{ESCAPE})*)\"/
+ STRING2 = /\'((?:[^\n\r\f\\']|\\#{NL}|#{ESCAPE})*)\'/
+
+ IDENT = /-*#{NMSTART}#{NMCHAR}*/
+ NAME = /#{NMCHAR}+/
+ NUM = //
+ STRING = /#{STRING1}|#{STRING2}/
+ URLCHAR = /[#%&*-~]|#{NONASCII}|#{ESCAPE}/
+ URL = /(#{URLCHAR}*)/
+ W = /[ \t\r\n\f]*/
+ VARIABLE = /(\$)(#{Sass::SCSS::RX::IDENT})/
+
+ # This is more liberal than the spec's definition,
+ # but that definition didn't work well with the greediness rules
+ RANGE = /(?:#{H}|\?){1,6}/
+
+ ##
+
+ S = /[ \t\r\n\f]+/
+
+ COMMENT = %r{/\*([^*]|\*+[^/*])*\**\*/}
+ SINGLE_LINE_COMMENT = %r{//.*(\n[ \t]*//.*)*}
+
+ CDO = quote("<!--")
+ CDC = quote("-->")
+ INCLUDES = quote("~=")
+ DASHMATCH = quote("|=")
+ PREFIXMATCH = quote("^=")
+ SUFFIXMATCH = quote("$=")
+ SUBSTRINGMATCH = quote("*=")
+
+ HASH = /##{NAME}/
+
+ IMPORTANT = /!#{W}important/i
+
+ UNITLESS_NUMBER = /(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?\d+)?/
+ NUMBER = /#{UNITLESS_NUMBER}(?:#{IDENT}|%)?/
+ PERCENTAGE = /#{UNITLESS_NUMBER}%/
+
+ URI = /url\(#{W}(?:#{STRING}|#{URL})#{W}\)/i
+ FUNCTION = /#{IDENT}\(/
+
+ UNICODERANGE = /u\+(?:#{H}{1,6}-#{H}{1,6}|#{RANGE})/i
+
+ # Defined in http://www.w3.org/TR/css3-selectors/#lex
+ PLUS = /#{W}\+/
+ GREATER = /#{W}>/
+ TILDE = /#{W}~/
+ NOT = quote(":not(", Regexp::IGNORECASE)
+
+ # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
+ # non-standard version of http://www.w3.org/TR/css3-conditional/
+ URL_PREFIX = /url-prefix\(#{W}(?:#{STRING}|#{URL})#{W}\)/i
+ DOMAIN = /domain\(#{W}(?:#{STRING}|#{URL})#{W}\)/i
+
+ # Custom
+ HEXCOLOR = /\#[0-9a-fA-F]+/
+ INTERP_START = /#\{/
+ ANY = /:(-[-\w]+-)?any\(/i
+ OPTIONAL = /!#{W}optional/i
+ IDENT_START = /-|#{NMSTART}/
+
+ # A unit is like an IDENT, but disallows a hyphen followed by a digit.
+ # This allows "1px-2px" to be interpreted as subtraction rather than "1"
+ # with the unit "px-2px". It also allows "%".
+ UNIT = /-?#{NMSTART}(?:[a-zA-Z0-9_]|#{NONASCII}|#{ESCAPE}|-(?!\d))*|%/
+
+ IDENT_HYPHEN_INTERP = /-(#\{)/
+ STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|#{ESCAPE})*)\"/
+ STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|#{ESCAPE})*)\'/
+ STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
+
+ STATIC_COMPONENT = /#{IDENT}|#{STRING_NOINTERP}|#{HEXCOLOR}|[+-]?#{NUMBER}|\!important/i
+ STATIC_VALUE = /#{STATIC_COMPONENT}(\s*[\s,\/]\s*#{STATIC_COMPONENT})*([;}])/i
+ STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){1,50}([{])/i
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss/script_lexer.rb
b/backends/css/gems/sass-3.4.9/lib/sass/scss/script_lexer.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/scss/script_lexer.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/scss/script_lexer.rb
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss/script_parser.rb
b/backends/css/gems/sass-3.4.9/lib/sass/scss/script_parser.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/scss/script_parser.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/scss/script_parser.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/scss/static_parser.rb
b/backends/css/gems/sass-3.4.9/lib/sass/scss/static_parser.rb
new file mode 100644
index 0000000..013fcab
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/scss/static_parser.rb
@@ -0,0 +1,368 @@
+require 'sass/script/css_parser'
+
+module Sass
+ module SCSS
+ # A parser for a static SCSS tree.
+ # Parses with SCSS extensions, like nested rules and parent selectors,
+ # but without dynamic SassScript.
+ # This is useful for e.g. \{#parse\_selector parsing selectors}
+ # after resolving the interpolation.
+ class StaticParser < Parser
+ # Parses the text as a selector.
+ #
+ # @param filename [String, nil] The file in which the selector appears,
+ # or nil if there is no such file.
+ # Used for error reporting.
+ # @return [Selector::CommaSequence] The parsed selector
+ # @raise [Sass::SyntaxError] if there's a syntax error in the selector
+ def parse_selector
+ init_scanner!
+ seq = expr!(:selector_comma_sequence)
+ expected("selector") unless @scanner.eos?
+ seq.line = @line
+ seq.filename = @filename
+ seq
+ end
+
+ # Parses a static at-root query.
+ #
+ # @return [(Symbol, Array<String>)] The type of the query
+ # (`:with` or `:without`) and the values that are being filtered.
+ # @raise [Sass::SyntaxError] if there's a syntax error in the query,
+ # or if it doesn't take up the entire input string.
+ def parse_static_at_root_query
+ init_scanner!
+ tok!(/\(/); ss
+ type = tok!(/\b(without|with)\b/).to_sym; ss
+ tok!(/:/); ss
+ directives = expr!(:at_root_directive_list); ss
+ tok!(/\)/)
+ expected("@at-root query list") unless @scanner.eos?
+ return type, directives
+ end
+
+ def parse_keyframes_selector
+ init_scanner!
+ sel = expr!(:keyframes_selector)
+ expected("keyframes selector") unless @scanner.eos?
+ sel
+ end
+
+ # @see Parser#initialize
+ # @param allow_parent_ref [Boolean] Whether to allow the
+ # parent-reference selector, `&`, when parsing the document.
+ # @comment
+ # rubocop:disable ParameterLists
+ def initialize(str, filename, importer, line = 1, offset = 1, allow_parent_ref = true)
+ # rubocop:enable ParameterLists
+ super(str, filename, importer, line, offset)
+ @allow_parent_ref = allow_parent_ref
+ end
+
+ private
+
+ def moz_document_function
+ val = tok(URI) || tok(URL_PREFIX) || tok(DOMAIN) || function(!:allow_var)
+ return unless val
+ ss
+ [val]
+ end
+
+ def variable; nil; end
+ def script_value; nil; end
+ def interpolation(warn_for_color = false); nil; end
+ def var_expr; nil; end
+ def interp_string; (s = tok(STRING)) && [s]; end
+ def interp_uri; (s = tok(URI)) && [s]; end
+ def interp_ident(ident = IDENT); (s = tok(ident)) && [s]; end
+ def use_css_import?; true; end
+
+ def special_directive(name, start_pos)
+ return unless %w[media import charset -moz-document].include?(name)
+ super
+ end
+
+ def selector_comma_sequence
+ sel = selector
+ return unless sel
+ selectors = [sel]
+ ws = ''
+ while tok(/,/)
+ ws << str {ss}
+ if (sel = selector)
+ selectors << sel
+ if ws.include?("\n")
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members)
+ end
+ ws = ''
+ end
+ end
+ Selector::CommaSequence.new(selectors)
+ end
+
+ def selector_string
+ sel = selector
+ return unless sel
+ sel.to_s
+ end
+
+ def selector
+ start_pos = source_position
+ # The combinator here allows the "> E" hack
+ val = combinator || simple_selector_sequence
+ return unless val
+ nl = str {ss}.include?("\n")
+ res = []
+ res << val
+ res << "\n" if nl
+
+ while (val = combinator || simple_selector_sequence)
+ res << val
+ res << "\n" if str {ss}.include?("\n")
+ end
+ seq = Selector::Sequence.new(res.compact)
+
+ if seq.members.any? {|sseq| sseq.is_a?(Selector::SimpleSequence) && sseq.subject?}
+ location = " of #{ filename}" if @filename
+ Sass::Util.sass_warn <<MESSAGE
+DEPRECATION WARNING on line #{start_pos.line}, column #{start_pos.offset}#{location}:
+The subject selector operator "!" is deprecated and will be removed in a future release.
+This operator has been replaced by ":has()" in the CSS spec.
+For example: #{seq.subjectless}
+MESSAGE
+ end
+
+ seq
+ end
+
+ def combinator
+ tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
+ end
+
+ def reference_combinator
+ return unless tok(/\//)
+ res = '/'
+ ns, name = expr!(:qualified_name)
+ res << ns << '|' if ns
+ res << name << tok!(/\//)
+ res
+ end
+
+ def simple_selector_sequence
+ start_pos = source_position
+ e = element_name || id_selector || class_selector || placeholder_selector || attrib ||
+ pseudo || parent_selector
+ return unless e
+ res = [e]
+
+ # The tok(/\*/) allows the "E*" hack
+ while (v = id_selector || class_selector || placeholder_selector ||
+ attrib || pseudo || (tok(/\*/) && Selector::Universal.new(nil)))
+ res << v
+ end
+
+ pos = @scanner.pos
+ line = @line
+ if (sel = str? {simple_selector_sequence})
+ @scanner.pos = pos
+ @line = line
+ begin
+ # If we see "*E", don't force a throw because this could be the
+ # "*prop: val" hack.
+ expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
+ throw_error {expected('"{"')}
+ rescue Sass::SyntaxError => e
+ e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector."
+ raise e
+ end
+ end
+
+ Selector::SimpleSequence.new(res, tok(/!/), range(start_pos))
+ end
+
+ def parent_selector
+ return unless @allow_parent_ref && tok(/&/)
+ Selector::Parent.new(tok(NAME))
+ end
+
+ def class_selector
+ return unless tok(/\./)
+ @expected = "class name"
+ Selector::Class.new(tok!(IDENT))
+ end
+
+ def id_selector
+ return unless tok(/#(?!\{)/)
+ @expected = "id name"
+ Selector::Id.new(tok!(NAME))
+ end
+
+ def placeholder_selector
+ return unless tok(/%/)
+ @expected = "placeholder name"
+ Selector::Placeholder.new(tok!(IDENT))
+ end
+
+ def element_name
+ ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
+ return unless ns || name
+
+ if name == '*'
+ Selector::Universal.new(ns)
+ else
+ Selector::Element.new(name, ns)
+ end
+ end
+
+ def qualified_name(allow_star_name = false)
+ name = tok(IDENT) || tok(/\*/) || (tok?(/\|/) && "")
+ return unless name
+ return nil, name unless tok(/\|/)
+
+ return name, tok!(IDENT) unless allow_star_name
+ @expected = "identifier or *"
+ return name, tok(IDENT) || tok!(/\*/)
+ end
+
+ def attrib
+ return unless tok(/\[/)
+ ss
+ ns, name = attrib_name!
+ ss
+
+ op = tok(/=/) ||
+ tok(INCLUDES) ||
+ tok(DASHMATCH) ||
+ tok(PREFIXMATCH) ||
+ tok(SUFFIXMATCH) ||
+ tok(SUBSTRINGMATCH)
+ if op
+ @expected = "identifier or string"
+ ss
+ val = tok(IDENT) || tok!(STRING)
+ ss
+ end
+ flags = tok(IDENT) || tok(STRING)
+ tok!(/\]/)
+
+ Selector::Attribute.new(name, ns, op, val, flags)
+ end
+
+ def attrib_name!
+ if (name_or_ns = tok(IDENT))
+ # E, E|E
+ if tok(/\|(?!=)/)
+ ns = name_or_ns
+ name = tok(IDENT)
+ else
+ name = name_or_ns
+ end
+ else
+ # *|E or |E
+ ns = tok(/\*/) || ""
+ tok!(/\|/)
+ name = tok!(IDENT)
+ end
+ return ns, name
+ end
+
+ SELECTOR_PSEUDO_CLASSES = %w[not matches current any has host host-context].to_set
+
+ PREFIXED_SELECTOR_PSEUDO_CLASSES = %w[nth-child nth-last-child].to_set
+
+ def pseudo
+ s = tok(/::?/)
+ return unless s
+ @expected = "pseudoclass or pseudoelement"
+ name = tok!(IDENT)
+ if tok(/\(/)
+ ss
+ deprefixed = deprefix(name)
+ if s == ':' && SELECTOR_PSEUDO_CLASSES.include?(deprefixed)
+ sel = selector_comma_sequence
+ elsif s == ':' && PREFIXED_SELECTOR_PSEUDO_CLASSES.include?(deprefixed)
+ arg, sel = prefixed_selector_pseudo
+ else
+ arg = expr!(:pseudo_args)
+ end
+
+ tok!(/\)/)
+ end
+ Selector::Pseudo.new(s == ':' ? :class : :element, name, arg, sel)
+ end
+
+ def pseudo_args
+ arg = expr!(:pseudo_expr)
+ while tok(/,/)
+ arg << ',' << str {ss}
+ arg.concat expr!(:pseudo_expr)
+ end
+ arg
+ end
+
+ def pseudo_expr
+ res = pseudo_expr_token
+ return unless res
+ res << str {ss}
+ while (e = pseudo_expr_token)
+ res << e << str {ss}
+ end
+ res
+ end
+
+ def pseudo_expr_token
+ tok(PLUS) || tok(/[-*]/) || tok(NUMBER) || tok(STRING) || tok(IDENT)
+ end
+
+ def prefixed_selector_pseudo
+ prefix = str do
+ expr = str {expr!(:a_n_plus_b)}
+ ss
+ return expr, nil unless tok(/of/)
+ ss
+ end
+ return prefix, expr!(:selector_comma_sequence)
+ end
+
+ def a_n_plus_b
+ if (parity = tok(/even|odd/i))
+ return parity
+ end
+
+ if tok(/[+-]?[0-9]+/)
+ ss
+ return true unless tok(/n/)
+ else
+ return unless tok(/[+-]?n/i)
+ end
+ ss
+
+ return true unless tok(/[+-]/)
+ ss
+ @expected = "number"
+ tok!(/[0-9]+/)
+ true
+ end
+
+ def keyframes_selector
+ ss
+ str do
+ return unless keyframes_selector_component
+ ss
+ while tok(/,/)
+ ss
+ expr!(:keyframes_selector_component)
+ ss
+ end
+ end
+ end
+
+ def keyframes_selector_component
+ tok(IDENT) || tok(PERCENTAGE)
+ end
+
+ @sass_script_parser = Class.new(Sass::Script::CssParser)
+ @sass_script_parser.send(:include, ScriptParser)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/selector.rb
b/backends/css/gems/sass-3.4.9/lib/sass/selector.rb
new file mode 100644
index 0000000..752491b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/selector.rb
@@ -0,0 +1,326 @@
+require 'sass/selector/simple'
+require 'sass/selector/abstract_sequence'
+require 'sass/selector/comma_sequence'
+require 'sass/selector/pseudo'
+require 'sass/selector/sequence'
+require 'sass/selector/simple_sequence'
+
+module Sass
+ # A namespace for nodes in the parse tree for selectors.
+ #
+ # {CommaSequence} is the toplevel selector,
+ # representing a comma-separated sequence of {Sequence}s,
+ # such as `foo bar, baz bang`.
+ # {Sequence} is the next level,
+ # representing {SimpleSequence}s separated by combinators (e.g. descendant or child),
+ # such as `foo bar` or `foo > bar baz`.
+ # {SimpleSequence} is a sequence of selectors that all apply to a single element,
+ # such as `foo.bar[attr=val]`.
+ # Finally, {Simple} is the superclass of the simplest selectors,
+ # such as `.foo` or `#bar`.
+ module Selector
+ # The base used for calculating selector specificity. The spec says this
+ # should be "sufficiently high"; it's extremely unlikely that any single
+ # selector sequence will contain 1,000 simple selectors.
+ SPECIFICITY_BASE = 1_000
+
+ # A parent-referencing selector (`&` in Sass).
+ # The function of this is to be replaced by the parent selector
+ # in the nested hierarchy.
+ class Parent < Simple
+ # The identifier following the `&`. `nil` indicates no suffix.
+ #
+ # @return [String, nil]
+ attr_reader :suffix
+
+ # @param name [String, nil] See \{#suffix}
+ def initialize(suffix = nil)
+ @suffix = suffix
+ end
+
+ # @see Selector#to_s
+ def to_s
+ "&" + (@suffix || '')
+ end
+
+ # Always raises an exception.
+ #
+ # @raise [Sass::SyntaxError] Parent selectors should be resolved before unification
+ # @see Selector#unify
+ def unify(sels)
+ raise Sass::SyntaxError.new("[BUG] Cannot unify parent selectors.")
+ end
+ end
+
+ # A class selector (e.g. `.foo`).
+ class Class < Simple
+ # The class name.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # @param name [String] The class name
+ def initialize(name)
+ @name = name
+ end
+
+ # @see Selector#to_s
+ def to_s
+ "." + @name
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ SPECIFICITY_BASE
+ end
+ end
+
+ # An id selector (e.g. `#foo`).
+ class Id < Simple
+ # The id name.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # @param name [String] The id name
+ def initialize(name)
+ @name = name
+ end
+
+ # @see Selector#to_s
+ def to_s
+ "#" + @name
+ end
+
+ # Returns `nil` if `sels` contains an {Id} selector
+ # with a different name than this one.
+ #
+ # @see Selector#unify
+ def unify(sels)
+ return if sels.any? {|sel2| sel2.is_a?(Id) && name != sel2.name}
+ super
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ SPECIFICITY_BASE**2
+ end
+ end
+
+ # A placeholder selector (e.g. `%foo`).
+ # This exists to be replaced via ` extend`
+ # Rulesets using this selector will not be printed, but can be extended.
+ # Otherwise, this acts just like a class selector.
+ class Placeholder < Simple
+ # The placeholder name.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # @param name [String] The placeholder name
+ def initialize(name)
+ @name = name
+ end
+
+ # @see Selector#to_s
+ def to_s
+ "%" + @name
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ SPECIFICITY_BASE
+ end
+ end
+
+ # A universal selector (`*` in CSS).
+ class Universal < Simple
+ # The selector namespace. `nil` means the default namespace, `""` means no
+ # namespace, `"*"` means any namespace.
+ #
+ # @return [String, nil]
+ attr_reader :namespace
+
+ # @param namespace [String, nil] See \{#namespace}
+ def initialize(namespace)
+ @namespace = namespace
+ end
+
+ # @see Selector#to_s
+ def to_s
+ @namespace ? "#{ namespace}|*" : "*"
+ end
+
+ # Unification of a universal selector is somewhat complicated,
+ # especially when a namespace is specified.
+ # If there is no namespace specified
+ # or any namespace is specified (namespace `"*"`),
+ # then `sel` is returned without change
+ # (unless it's empty, in which case `"*"` is required).
+ #
+ # If a namespace is specified
+ # but `sel` does not specify a namespace,
+ # then the given namespace is applied to `sel`,
+ # either by adding this {Universal} selector
+ # or applying this namespace to an existing {Element} selector.
+ #
+ # If both this selector *and* `sel` specify namespaces,
+ # those namespaces are unified via {Simple#unify_namespaces}
+ # and the unified namespace is used, if possible.
+ #
+ # @todo There are lots of cases that this documentation specifies;
+ # make sure we thoroughly test **all of them**.
+ # @todo Keep track of whether a default namespace has been declared
+ # and handle namespace-unspecified selectors accordingly.
+ # @todo If any branch of a CommaSequence ends up being just `"*"`,
+ # then all other branches should be eliminated
+ #
+ # @see Selector#unify
+ def unify(sels)
+ name =
+ case sels.first
+ when Universal; :universal
+ when Element; sels.first.name
+ else
+ return [self] + sels unless namespace.nil? || namespace == '*'
+ return sels unless sels.empty?
+ return [self]
+ end
+
+ ns, accept = unify_namespaces(namespace, sels.first.namespace)
+ return unless accept
+ [name == :universal ? Universal.new(ns) : Element.new(name, ns)] + sels[1..-1]
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ 0
+ end
+ end
+
+ # An element selector (e.g. `h1`).
+ class Element < Simple
+ # The element name.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # The selector namespace. `nil` means the default namespace, `""` means no
+ # namespace, `"*"` means any namespace.
+ #
+ # @return [String, nil]
+ attr_reader :namespace
+
+ # @param name [String] The element name
+ # @param namespace [String, nil] See \{#namespace}
+ def initialize(name, namespace)
+ @name = name
+ @namespace = namespace
+ end
+
+ # @see Selector#to_s
+ def to_s
+ @namespace ? "#{ namespace}|#{ name}" : @name
+ end
+
+ # Unification of an element selector is somewhat complicated,
+ # especially when a namespace is specified.
+ # First, if `sel` contains another {Element} with a different \{#name},
+ # then the selectors can't be unified and `nil` is returned.
+ #
+ # Otherwise, if `sel` doesn't specify a namespace,
+ # or it specifies any namespace (via `"*"`),
+ # then it's returned with this element selector
+ # (e.g. `.foo` becomes `a.foo` or `svg|a.foo`).
+ # Similarly, if this selector doesn't specify a namespace,
+ # the namespace from `sel` is used.
+ #
+ # If both this selector *and* `sel` specify namespaces,
+ # those namespaces are unified via {Simple#unify_namespaces}
+ # and the unified namespace is used, if possible.
+ #
+ # @todo There are lots of cases that this documentation specifies;
+ # make sure we thoroughly test **all of them**.
+ # @todo Keep track of whether a default namespace has been declared
+ # and handle namespace-unspecified selectors accordingly.
+ #
+ # @see Selector#unify
+ def unify(sels)
+ case sels.first
+ when Universal;
+ when Element; return unless name == sels.first.name
+ else return [self] + sels
+ end
+
+ ns, accept = unify_namespaces(namespace, sels.first.namespace)
+ return unless accept
+ [Element.new(name, ns)] + sels[1..-1]
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ 1
+ end
+ end
+
+ # An attribute selector (e.g. `[href^="http://"]`).
+ class Attribute < Simple
+ # The attribute name.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_reader :name
+
+ # The attribute namespace. `nil` means the default namespace, `""` means
+ # no namespace, `"*"` means any namespace.
+ #
+ # @return [String, nil]
+ attr_reader :namespace
+
+ # The matching operator, e.g. `"="` or `"^="`.
+ #
+ # @return [String]
+ attr_reader :operator
+
+ # The right-hand side of the operator.
+ #
+ # @return [String]
+ attr_reader :value
+
+ # Flags for the attribute selector (e.g. `i`).
+ #
+ # @return [String]
+ attr_reader :flags
+
+ # @param name [String] The attribute name
+ # @param namespace [String, nil] See \{#namespace}
+ # @param operator [String] The matching operator, e.g. `"="` or `"^="`
+ # @param value [String] See \{#value}
+ # @param flags [String] See \{#flags}
+ # @comment
+ # rubocop:disable ParameterLists
+ def initialize(name, namespace, operator, value, flags)
+ # rubocop:enable ParameterLists
+ @name = name
+ @namespace = namespace
+ @operator = operator
+ @value = value
+ @flags = flags
+ end
+
+ # @see Selector#to_s
+ def to_s
+ res = "["
+ res << @namespace << "|" if @namespace
+ res << @name
+ res << @operator << @value if @value
+ res << " " << @flags if @flags
+ res << "]"
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ SPECIFICITY_BASE
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/selector/abstract_sequence.rb
b/backends/css/gems/sass-3.4.9/lib/sass/selector/abstract_sequence.rb
new file mode 100644
index 0000000..2761715
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/selector/abstract_sequence.rb
@@ -0,0 +1,109 @@
+module Sass
+ module Selector
+ # The abstract parent class of the various selector sequence classes.
+ #
+ # All subclasses should implement a `members` method that returns an array
+ # of object that respond to `#line=` and `#filename=`, as well as a `to_s`
+ # method that returns the string representation of the selector.
+ class AbstractSequence
+ # The line of the Sass template on which this selector was declared.
+ #
+ # @return [Fixnum]
+ attr_reader :line
+
+ # The name of the file in which this selector was declared.
+ #
+ # @return [String, nil]
+ attr_reader :filename
+
+ # Sets the line of the Sass template on which this selector was declared.
+ # This also sets the line for all child selectors.
+ #
+ # @param line [Fixnum]
+ # @return [Fixnum]
+ def line=(line)
+ members.each {|m| m.line = line}
+ @line = line
+ end
+
+ # Sets the name of the file in which this selector was declared,
+ # or `nil` if it was not declared in a file (e.g. on stdin).
+ # This also sets the filename for all child selectors.
+ #
+ # @param filename [String, nil]
+ # @return [String, nil]
+ def filename=(filename)
+ members.each {|m| m.filename = filename}
+ @filename = filename
+ end
+
+ # Returns a hash code for this sequence.
+ #
+ # Subclasses should define `#_hash` rather than overriding this method,
+ # which automatically handles memoizing the result.
+ #
+ # @return [Fixnum]
+ def hash
+ @_hash ||= _hash
+ end
+
+ # Checks equality between this and another object.
+ #
+ # Subclasses should define `#_eql?` rather than overriding this method,
+ # which handles checking class equality and hash equality.
+ #
+ # @param other [Object] The object to test equality against
+ # @return [Boolean] Whether or not this is equal to `other`
+ def eql?(other)
+ other.class == self.class && other.hash == hash && _eql?(other)
+ end
+ alias_method :==, :eql?
+
+ # Whether or not this selector sequence contains a placeholder selector.
+ # Checks recursively.
+ def has_placeholder?
+ @has_placeholder ||= members.any? do |m|
+ next m.has_placeholder? if m.is_a?(AbstractSequence)
+ next m.selector && m.selector.has_placeholder? if m.is_a?(Pseudo)
+ m.is_a?(Placeholder)
+ end
+ end
+
+ # Returns the selector string.
+ #
+ # @return [String]
+ def to_s
+ Sass::Util.abstract(self)
+ end
+
+ # Returns the specificity of the selector.
+ #
+ # The base is given by {Sass::Selector::SPECIFICITY_BASE}. This can be a
+ # number or a range representing possible specificities.
+ #
+ # @return [Fixnum, Range]
+ def specificity
+ _specificity(members)
+ end
+
+ protected
+
+ def _specificity(arr)
+ min = 0
+ max = 0
+ arr.each do |m|
+ next if m.is_a?(String)
+ spec = m.specificity
+ if spec.is_a?(Range)
+ min += spec.begin
+ max += spec.end
+ else
+ min += spec
+ max += spec
+ end
+ end
+ min == max ? min : (min..max)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/selector/comma_sequence.rb
b/backends/css/gems/sass-3.4.9/lib/sass/selector/comma_sequence.rb
new file mode 100644
index 0000000..506af86
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/selector/comma_sequence.rb
@@ -0,0 +1,177 @@
+module Sass
+ module Selector
+ # A comma-separated sequence of selectors.
+ class CommaSequence < AbstractSequence
+ # The comma-separated selector sequences
+ # represented by this class.
+ #
+ # @return [Array<Sequence>]
+ attr_reader :members
+
+ # @param seqs [Array<Sequence>] See \{#members}
+ def initialize(seqs)
+ @members = seqs
+ end
+
+ # Resolves the {Parent} selectors within this selector
+ # by replacing them with the given parent selector,
+ # handling commas appropriately.
+ #
+ # @param super_cseq [CommaSequence] The parent selector
+ # @param implicit_parent [Boolean] Whether the the parent
+ # selector should automatically be prepended to the resolved
+ # selector if it contains no parent refs.
+ # @return [CommaSequence] This selector, with parent references resolved
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
+ def resolve_parent_refs(super_cseq, implicit_parent = true)
+ if super_cseq.nil?
+ if contains_parent_ref?
+ raise Sass::SyntaxError.new(
+ "Base-level rules cannot contain the parent-selector-referencing character '&'.")
+ end
+ return self
+ end
+
+ CommaSequence.new(Sass::Util.flatten_vertically(@members.map do |seq|
+ seq.resolve_parent_refs(super_cseq, implicit_parent).members
+ end))
+ end
+
+ # Returns whether there's a {Parent} selector anywhere in this sequence.
+ #
+ # @return [Boolean]
+ def contains_parent_ref?
+ @members.any? {|sel| sel.contains_parent_ref?}
+ end
+
+ # Non-destrucively extends this selector with the extensions specified in a hash
+ # (which should come from {Sass::Tree::Visitors::Cssize}).
+ #
+ # @todo Link this to the reference documentation on ` extend`
+ # when such a thing exists.
+ #
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
+ # Sass::Tree::Visitors::Cssize::Extend}]
+ # The extensions to perform on this selector
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
+ # The directives containing this selector.
+ # @param replace [Boolean]
+ # Whether to replace the original selector entirely or include
+ # it in the result.
+ # @param seen [Set<Array<Selector::Simple>>]
+ # The set of simple sequences that are currently being replaced.
+ # @param original [Boolean]
+ # Whether this is the original selector being extended, as opposed to
+ # the result of a previous extension that's being re-extended.
+ # @return [CommaSequence] A copy of this selector,
+ # with extensions made according to `extends`
+ def do_extend(extends, parent_directives = [], replace = false, seen = Set.new,
+ original = true)
+ CommaSequence.new(members.map do |seq|
+ seq.do_extend(extends, parent_directives, replace, seen, original)
+ end.flatten)
+ end
+
+ # Returns whether or not this selector matches all elements
+ # that the given selector matches (as well as possibly more).
+ #
+ # @example
+ # (.foo).superselector?(.foo.bar) #=> true
+ # (.foo).superselector?(.bar) #=> false
+ # @param cseq [CommaSequence]
+ # @return [Boolean]
+ def superselector?(cseq)
+ cseq.members.all? {|seq1| members.any? {|seq2| seq2.superselector?(seq1)}}
+ end
+
+ # Populates a subset map that can then be used to extend
+ # selectors. This registers an extension with this selector as
+ # the extender and `extendee` as the extendee.
+ #
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
+ # Sass::Tree::Visitors::Cssize::Extend}]
+ # The subset map representing the extensions to perform.
+ # @param extendee [CommaSequence] The selector being extended.
+ # @param extend_node [Sass::Tree::ExtendNode]
+ # The node that caused this extension.
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
+ # The parent directives containing `extend_node`.
+ # @raise [Sass::SyntaxError] if this extension is invalid.
+ def populate_extends(extends, extendee, extend_node = nil, parent_directives = [])
+ extendee.members.each do |seq|
+ if seq.members.size > 1
+ raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend nested selectors")
+ end
+
+ sseq = seq.members.first
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
+ raise Sass::SyntaxError.new("Can't extend #{seq}: invalid selector")
+ elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
+ raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend parent selectors")
+ end
+
+ sel = sseq.members
+ members.each do |member|
+ unless member.members.last.is_a?(Sass::Selector::SimpleSequence)
+ raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
+ end
+
+ extends[sel] = Sass::Tree::Visitors::Cssize::Extend.new(
+ member, sel, extend_node, parent_directives, :not_found)
+ end
+ end
+ end
+
+ # Unifies this with another comma selector to produce a selector
+ # that matches (a subset of) the intersection of the two inputs.
+ #
+ # @param other [CommaSequence]
+ # @return [CommaSequence, nil] The unified selector, or nil if unification failed.
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
+ # This will only ever occur when a dynamic selector,
+ # such as {Parent} or {Interpolation}, is used in unification.
+ # Since these selectors should be resolved
+ # by the time extension and unification happen,
+ # this exception will only ever be raised as a result of programmer error
+ def unify(other)
+ results = members.map {|seq1| other.members.map {|seq2| seq1.unify(seq2)}}.flatten.compact
+ results.empty? ? nil : CommaSequence.new(results.map {|cseq| cseq.members}.flatten)
+ end
+
+ # Returns a SassScript representation of this selector.
+ #
+ # @return [Sass::Script::Value::List]
+ def to_sass_script
+ Sass::Script::Value::List.new(members.map do |seq|
+ Sass::Script::Value::List.new(seq.members.map do |component|
+ next if component == "\n"
+ Sass::Script::Value::String.new(component.to_s)
+ end.compact, :space)
+ end, :comma)
+ end
+
+ # Returns a string representation of the sequence.
+ # This is basically the selector string.
+ #
+ # @return [String]
+ def inspect
+ members.map {|m| m.inspect}.join(", ")
+ end
+
+ # @see AbstractSequence#to_s
+ def to_s
+ @members.join(", ").gsub(", \n", ",\n")
+ end
+
+ private
+
+ def _hash
+ members.hash
+ end
+
+ def _eql?(other)
+ other.class == self.class && other.members.eql?(members)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/selector/pseudo.rb
b/backends/css/gems/sass-3.4.9/lib/sass/selector/pseudo.rb
new file mode 100644
index 0000000..047fa4b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/selector/pseudo.rb
@@ -0,0 +1,266 @@
+module Sass
+ module Selector
+ # A pseudoclass (e.g. `:visited`) or pseudoelement (e.g. `::first-line`)
+ # selector. It can have arguments (e.g. `:nth-child(2n+1)`) which can
+ # contain selectors (e.g. `:nth-child(2n+1 of .foo)`).
+ class Pseudo < Simple
+ # Some pseudo-class-syntax selectors are actually considered
+ # pseudo-elements and must be treated differently. This is a list of such
+ # selectors.
+ #
+ # @return [Set<String>]
+ ACTUALLY_ELEMENTS = %w[after before first-line first-letter].to_set
+
+ # Like \{#type}, but returns the type of selector this looks like, rather
+ # than the type it is semantically. This only differs from type for
+ # selectors in \{ACTUALLY\_ELEMENTS}.
+ #
+ # @return [Symbol]
+ attr_reader :syntactic_type
+
+ # The name of the selector.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # The argument to the selector,
+ # or `nil` if no argument was given.
+ #
+ # @return [String, nil]
+ attr_reader :arg
+
+ # The selector argument, or `nil` if no selector exists.
+ #
+ # If this and \{#arg\} are both set, \{#arg\} is considered a non-selector
+ # prefix.
+ #
+ # @return [CommaSequence]
+ attr_reader :selector
+
+ # @param syntactic_type [Symbol] See \{#syntactic_type}
+ # @param name [String] See \{#name}
+ # @param arg [nil, String] See \{#arg}
+ # @param selector [nil, CommaSequence] See \{#selector}
+ def initialize(syntactic_type, name, arg, selector)
+ @syntactic_type = syntactic_type
+ @name = name
+ @arg = arg
+ @selector = selector
+ end
+
+ # Returns a copy of this with \{#selector} set to \{#new\_selector}.
+ #
+ # @param new_selector [CommaSequence]
+ # @return [Array<Simple>]
+ def with_selector(new_selector)
+ result = Pseudo.new(syntactic_type, name, arg,
+ CommaSequence.new(new_selector.members.map do |seq|
+ next seq unless seq.members.length == 1
+ sseq = seq.members.first
+ next seq unless sseq.is_a?(SimpleSequence) && sseq.members.length == 1
+ sel = sseq.members.first
+ next seq unless sel.is_a?(Pseudo) && sel.selector
+
+ case normalized_name
+ when 'not'
+ # In theory, if there's a nested :not its contents should be
+ # unified with the return value. For example, if :not(.foo)
+ # extends .bar, :not(.bar) should become .foo:not(.bar). However,
+ # this is a narrow edge case and supporting it properly would make
+ # this code and the code calling it a lot more complicated, so
+ # it's not supported for now.
+ next [] unless sel.normalized_name == 'matches'
+ sel.selector.members
+ when 'matches', 'any', 'current', 'nth-child', 'nth-last-child'
+ # As above, we could theoretically support :not within :matches, but
+ # doing so would require this method and its callers to handle much
+ # more complex cases that likely aren't worth the pain.
+ next [] unless sel.name == name && sel.arg == arg
+ sel.selector.members
+ when 'has', 'host', 'host-context'
+ # We can't expand nested selectors here, because each layer adds an
+ # additional layer of semantics. For example, `:has(:has(img))`
+ # doesn't match `<div><img></div>` but `:has(img)` does.
+ sel
+ else
+ []
+ end
+ end.flatten))
+
+ # Older browsers support :not but only with a single complex selector.
+ # In order to support those browsers, we break up the contents of a :not
+ # unless it originally contained a selector list.
+ return [result] unless normalized_name == 'not'
+ return [result] if selector.members.length > 1
+ result.selector.members.map do |seq|
+ Pseudo.new(syntactic_type, name, arg, CommaSequence.new([seq]))
+ end
+ end
+
+ # The type of the selector. `:class` if this is a pseudoclass selector,
+ # `:element` if it's a pseudoelement.
+ #
+ # @return [Symbol]
+ def type
+ ACTUALLY_ELEMENTS.include?(normalized_name) ? :element : syntactic_type
+ end
+
+ # Like \{#name\}, but without any vendor prefix.
+ #
+ # @return [String]
+ def normalized_name
+ @normalized_name ||= name.gsub(/^-[a-zA-Z0-9]+-/, '')
+ end
+
+ # @see Selector#to_s
+ def to_s
+ res = (syntactic_type == :class ? ":" : "::") + @name
+ if @arg || @selector
+ res << "("
+ res << @arg.strip if @arg
+ res << " " if @arg && @selector
+ res << @selector.to_s if @selector
+ res << ")"
+ end
+ res
+ end
+
+ # Returns `nil` if this is a pseudoelement selector
+ # and `sels` contains a pseudoelement selector different than this one.
+ #
+ # @see SimpleSequence#unify
+ def unify(sels)
+ return if type == :element && sels.any? do |sel|
+ sel.is_a?(Pseudo) && sel.type == :element &&
+ (sel.name != name || sel.arg != arg || sel.selector != selector)
+ end
+ super
+ end
+
+ # Returns whether or not this selector matches all elements
+ # that the given selector matches (as well as possibly more).
+ #
+ # @example
+ # (.foo).superselector?(.foo.bar) #=> true
+ # (.foo).superselector?(.bar) #=> false
+ # @param their_sseq [SimpleSequence]
+ # @param parents [Array<SimpleSequence, String>] The parent selectors of `their_sseq`, if any.
+ # @return [Boolean]
+ def superselector?(their_sseq, parents = [])
+ case normalized_name
+ when 'matches', 'any'
+ # :matches can be a superselector of another selector in one of two
+ # ways. Either its constituent selectors can be a superset of those of
+ # another :matches in the other selector, or any of its constituent
+ # selectors can individually be a superselector of the other selector.
+ (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
+ next false unless their_sel.is_a?(Pseudo)
+ next false unless their_sel.name == name
+ selector.superselector?(their_sel.selector)
+ end || selector.members.any? do |our_seq|
+ their_seq = Sequence.new(parents + [their_sseq])
+ our_seq.superselector?(their_seq)
+ end
+ when 'has', 'host', 'host-context'
+ # Like :matches, :has (et al) can be a superselector of another
+ # selector if its constituent selectors are a superset of those of
+ # another :has in the other selector. However, the :matches other case
+ # doesn't work, because :has refers to nested elements.
+ (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
+ next false unless their_sel.is_a?(Pseudo)
+ next false unless their_sel.name == name
+ selector.superselector?(their_sel.selector)
+ end
+ when 'not'
+ selector.members.all? do |our_seq|
+ their_sseq.members.any? do |their_sel|
+ if their_sel.is_a?(Element) || their_sel.is_a?(Id)
+ # `:not(a)` is a superselector of `h1` and `:not(#foo)` is a
+ # superselector of `#bar`.
+ our_sseq = our_seq.members.last
+ next false unless our_sseq.is_a?(SimpleSequence)
+ our_sseq.members.any? do |our_sel|
+ our_sel.class == their_sel.class && our_sel != their_sel
+ end
+ else
+ next false unless their_sel.is_a?(Pseudo)
+ next false unless their_sel.name == name
+ # :not(X) is a superselector of :not(Y) exactly when Y is a
+ # superselector of X.
+ their_sel.selector.superselector?(CommaSequence.new([our_seq]))
+ end
+ end
+ end
+ when 'current'
+ (their_sseq.selector_pseudo_classes['current'] || []).any? do |their_current|
+ next false if their_current.name != name
+ # Explicitly don't check for nested superselector relationships
+ # here. :current(.foo) isn't always a superselector of
+ # :current(.foo.bar), since it matches the *innermost* ancestor of
+ # the current element that matches the selector. For example:
+ #
+ # <div class="foo bar">
+ # <p class="foo">
+ # <span>current element</span>
+ # </p>
+ # </div>
+ #
+ # Here :current(.foo) would match the p element and *not* the div
+ # element, whereas :current(.foo.bar) would match the div and not
+ # the p.
+ selector == their_current.selector
+ end
+ when 'nth-child', 'nth-last-child'
+ their_sseq.members.any? do |their_sel|
+ # This misses a few edge cases. For example, `:nth-child(n of X)`
+ # is a superselector of `X`, and `:nth-child(2n of X)` is a
+ # superselector of `:nth-child(4n of X)`. These seem rare enough
+ # not to be worth worrying about, though.
+ next false unless their_sel.is_a?(Pseudo)
+ next false unless their_sel.name == name
+ next false unless their_sel.arg == arg
+ selector.superselector?(their_sel.selector)
+ end
+ else
+ throw "[BUG] Unknown selector pseudo class #{name}"
+ end
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ return 1 if type == :element
+ return SPECIFICITY_BASE unless selector
+ @specificity ||=
+ if normalized_name == 'not'
+ min = 0
+ max = 0
+ selector.members.each do |seq|
+ spec = seq.specificity
+ if spec.is_a?(Range)
+ min = Sass::Util.max(spec.begin, min)
+ max = Sass::Util.max(spec.end, max)
+ else
+ min = Sass::Util.max(spec, min)
+ max = Sass::Util.max(spec, max)
+ end
+ end
+ min == max ? max : (min..max)
+ else
+ min = 0
+ max = 0
+ selector.members.each do |seq|
+ spec = seq.specificity
+ if spec.is_a?(Range)
+ min = Sass::Util.min(spec.begin, min)
+ max = Sass::Util.max(spec.end, max)
+ else
+ min = Sass::Util.min(spec, min)
+ max = Sass::Util.max(spec, max)
+ end
+ end
+ min == max ? max : (min..max)
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/selector/sequence.rb
b/backends/css/gems/sass-3.4.9/lib/sass/selector/sequence.rb
new file mode 100644
index 0000000..a2de06c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/selector/sequence.rb
@@ -0,0 +1,618 @@
+module Sass
+ module Selector
+ # An operator-separated sequence of
+ # {SimpleSequence simple selector sequences}.
+ class Sequence < AbstractSequence
+ # Sets the line of the Sass template on which this selector was declared.
+ # This also sets the line for all child selectors.
+ #
+ # @param line [Fixnum]
+ # @return [Fixnum]
+ def line=(line)
+ members.each {|m| m.line = line if m.is_a?(SimpleSequence)}
+ line
+ end
+
+ # Sets the name of the file in which this selector was declared,
+ # or `nil` if it was not declared in a file (e.g. on stdin).
+ # This also sets the filename for all child selectors.
+ #
+ # @param filename [String, nil]
+ # @return [String, nil]
+ def filename=(filename)
+ members.each {|m| m.filename = filename if m.is_a?(SimpleSequence)}
+ filename
+ end
+
+ # The array of {SimpleSequence simple selector sequences}, operators, and
+ # newlines. The operators are strings such as `"+"` and `">"` representing
+ # the corresponding CSS operators, or interpolated SassScript. Newlines
+ # are also newline strings; these aren't semantically relevant, but they
+ # do affect formatting.
+ #
+ # @return [Array<SimpleSequence, String|Array<Sass::Tree::Node, String>>]
+ attr_reader :members
+
+ # @param seqs_and_ops [Array<SimpleSequence, String|Array<Sass::Tree::Node, String>>]
+ # See \{#members}
+ def initialize(seqs_and_ops)
+ @members = seqs_and_ops
+ end
+
+ # Resolves the {Parent} selectors within this selector
+ # by replacing them with the given parent selector,
+ # handling commas appropriately.
+ #
+ # @param super_cseq [CommaSequence] The parent selector
+ # @param implicit_parent [Boolean] Whether the the parent
+ # selector should automatically be prepended to the resolved
+ # selector if it contains no parent refs.
+ # @return [CommaSequence] This selector, with parent references resolved
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
+ def resolve_parent_refs(super_cseq, implicit_parent)
+ members = @members.dup
+ nl = (members.first == "\n" && members.shift)
+ contains_parent_ref = contains_parent_ref?
+ return CommaSequence.new([self]) if !implicit_parent && !contains_parent_ref
+
+ unless contains_parent_ref
+ old_members, members = members, []
+ members << nl if nl
+ members << SimpleSequence.new([Parent.new], false)
+ members += old_members
+ end
+
+ CommaSequence.new(Sass::Util.paths(members.map do |sseq_or_op|
+ next [sseq_or_op] unless sseq_or_op.is_a?(SimpleSequence)
+ sseq_or_op.resolve_parent_refs(super_cseq).members
+ end).map do |path|
+ Sequence.new(path.map do |seq_or_op|
+ next seq_or_op unless seq_or_op.is_a?(Sequence)
+ seq_or_op.members
+ end.flatten)
+ end)
+ end
+
+ # Returns whether there's a {Parent} selector anywhere in this sequence.
+ #
+ # @return [Boolean]
+ def contains_parent_ref?
+ members.any? do |sseq_or_op|
+ next false unless sseq_or_op.is_a?(SimpleSequence)
+ next true if sseq_or_op.members.first.is_a?(Parent)
+ sseq_or_op.members.any? do |sel|
+ sel.is_a?(Pseudo) && sel.selector && sel.selector.contains_parent_ref?
+ end
+ end
+ end
+
+ # Non-destructively extends this selector with the extensions specified in a hash
+ # (which should come from {Sass::Tree::Visitors::Cssize}).
+ #
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
+ # Sass::Tree::Visitors::Cssize::Extend}]
+ # The extensions to perform on this selector
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
+ # The directives containing this selector.
+ # @param replace [Boolean]
+ # Whether to replace the original selector entirely or include
+ # it in the result.
+ # @param seen [Set<Array<Selector::Simple>>]
+ # The set of simple sequences that are currently being replaced.
+ # @param original [Boolean]
+ # Whether this is the original selector being extended, as opposed to
+ # the result of a previous extension that's being re-extended.
+ # @return [Array<Sequence>] A list of selectors generated
+ # by extending this selector with `extends`.
+ # These correspond to a {CommaSequence}'s {CommaSequence#members members array}.
+ # @see CommaSequence#do_extend
+ def do_extend(extends, parent_directives, replace, seen, original)
+ extended_not_expanded = members.map do |sseq_or_op|
+ next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
+ extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen)
+
+ # The First Law of Extend says that the generated selector should have
+ # specificity greater than or equal to that of the original selector.
+ # In order to ensure that, we record the original selector's
+ # (`extended.first`) original specificity.
+ extended.first.add_sources!([self]) if original && !has_placeholder?
+
+ extended.map {|seq| seq.members}
+ end
+ weaves = Sass::Util.paths(extended_not_expanded).map {|path| weave(path)}
+ trim(weaves).map {|p| Sequence.new(p)}
+ end
+
+ # Unifies this with another selector sequence to produce a selector
+ # that matches (a subset of) the intersection of the two inputs.
+ #
+ # @param other [Sequence]
+ # @return [CommaSequence, nil] The unified selector, or nil if unification failed.
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
+ # This will only ever occur when a dynamic selector,
+ # such as {Parent} or {Interpolation}, is used in unification.
+ # Since these selectors should be resolved
+ # by the time extension and unification happen,
+ # this exception will only ever be raised as a result of programmer error
+ def unify(other)
+ base = members.last
+ other_base = other.members.last
+ return unless base.is_a?(SimpleSequence) && other_base.is_a?(SimpleSequence)
+ return unless (unified = other_base.unify(base))
+
+ woven = weave([members[0...-1], other.members[0...-1] + [unified]])
+ CommaSequence.new(woven.map {|w| Sequence.new(w)})
+ end
+
+ # Returns whether or not this selector matches all elements
+ # that the given selector matches (as well as possibly more).
+ #
+ # @example
+ # (.foo).superselector?(.foo.bar) #=> true
+ # (.foo).superselector?(.bar) #=> false
+ # @param cseq [Sequence]
+ # @return [Boolean]
+ def superselector?(seq)
+ _superselector?(members, seq.members)
+ end
+
+ # @see AbstractSequence#to_s
+ def to_s
+ @members.join(" ").gsub(/ ?\n ?/, "\n")
+ end
+
+ # Returns a string representation of the sequence.
+ # This is basically the selector string.
+ #
+ # @return [String]
+ def inspect
+ members.map {|m| m.inspect}.join(" ")
+ end
+
+ # Add to the {SimpleSequence#sources} sets of the child simple sequences.
+ # This destructively modifies this sequence's members array, but not the
+ # child simple sequences.
+ #
+ # @param sources [Set<Sequence>]
+ def add_sources!(sources)
+ members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
+ end
+
+ # Converts the subject operator "!", if it exists, into a ":has()"
+ # selector.
+ #
+ # @retur [Sequence]
+ def subjectless
+ pre_subject = []
+ has = []
+ subject = nil
+ members.each do |sseq_or_op|
+ if subject
+ has << sseq_or_op
+ elsif sseq_or_op.is_a?(String) || !sseq_or_op.subject?
+ pre_subject << sseq_or_op
+ else
+ subject = sseq_or_op.dup
+ subject.members = sseq_or_op.members.dup
+ subject.subject = false
+ has = []
+ end
+ end
+
+ return self unless subject
+
+ unless has.empty?
+ subject.members << Pseudo.new(:class, 'has', nil, CommaSequence.new([Sequence.new(has)]))
+ end
+ Sequence.new(pre_subject + [subject])
+ end
+
+ private
+
+ # Conceptually, this expands "parenthesized selectors". That is, if we
+ # have `.A .B { extend .C}` and `.D .C {...}`, this conceptually expands
+ # into `.D .C, .D (.A .B)`, and this function translates `.D (.A .B)` into
+ # `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` would also be
+ # required, but including merged selectors results in exponential output
+ # for very little gain.
+ #
+ # @param path [Array<Array<SimpleSequence or String>>]
+ # A list of parenthesized selector groups.
+ # @return [Array<Array<SimpleSequence or String>>] A list of fully-expanded selectors.
+ def weave(path)
+ # This function works by moving through the selector path left-to-right,
+ # building all possible prefixes simultaneously.
+ prefixes = [[]]
+
+ path.each do |current|
+ next if current.empty?
+ current = current.dup
+ last_current = [current.pop]
+ prefixes = Sass::Util.flatten(prefixes.map do |prefix|
+ sub = subweave(prefix, current)
+ next [] unless sub
+ sub.map {|seqs| seqs + last_current}
+ end, 1)
+ end
+ prefixes
+ end
+
+ # This interweaves two lists of selectors,
+ # returning all possible orderings of them (including using unification)
+ # that maintain the relative ordering of the input arrays.
+ #
+ # For example, given `.foo .bar` and `.baz .bang`,
+ # this would return `.foo .bar .baz .bang`, `.foo .bar.baz .bang`,
+ # `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`,
+ # and so on until `.baz .bang .foo .bar`.
+ #
+ # Semantically, for selectors A and B, this returns all selectors `AB_i`
+ # such that the union over all i of elements matched by `AB_i X` is
+ # identical to the intersection of all elements matched by `A X` and all
+ # elements matched by `B X`. Some `AB_i` are elided to reduce the size of
+ # the output.
+ #
+ # @param seq1 [Array<SimpleSequence or String>]
+ # @param seq2 [Array<SimpleSequence or String>]
+ # @return [Array<Array<SimpleSequence or String>>]
+ def subweave(seq1, seq2)
+ return [seq2] if seq1.empty?
+ return [seq1] if seq2.empty?
+
+ seq1, seq2 = seq1.dup, seq2.dup
+ init = merge_initial_ops(seq1, seq2)
+ return unless init
+ fin = merge_final_ops(seq1, seq2)
+ return unless fin
+ seq1 = group_selectors(seq1)
+ seq2 = group_selectors(seq2)
+ lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|
+ next s1 if s1 == s2
+ next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
+ next s2 if parent_superselector?(s1, s2)
+ next s1 if parent_superselector?(s2, s1)
+ end
+
+ diff = [[init]]
+ until lcs.empty?
+ diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift]
+ seq1.shift
+ seq2.shift
+ end
+ diff << chunks(seq1, seq2) {|s| s.empty?}
+ diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}
+ diff.reject! {|c| c.empty?}
+
+ Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)}
+ end
+
+ # Extracts initial selector combinators (`"+"`, `">"`, `"~"`, and `"\n"`)
+ # from two sequences and merges them together into a single array of
+ # selector combinators.
+ #
+ # @param seq1 [Array<SimpleSequence or String>]
+ # @param seq2 [Array<SimpleSequence or String>]
+ # @return [Array<String>, nil] If there are no operators in the merged
+ # sequence, this will be the empty array. If the operators cannot be
+ # merged, this will be nil.
+ def merge_initial_ops(seq1, seq2)
+ ops1, ops2 = [], []
+ ops1 << seq1.shift while seq1.first.is_a?(String)
+ ops2 << seq2.shift while seq2.first.is_a?(String)
+
+ newline = false
+ newline ||= !!ops1.shift if ops1.first == "\n"
+ newline ||= !!ops2.shift if ops2.first == "\n"
+
+ # If neither sequence is a subsequence of the other, they cannot be
+ # merged successfully
+ lcs = Sass::Util.lcs(ops1, ops2)
+ return unless lcs == ops1 || lcs == ops2
+ (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
+ end
+
+ # Extracts final selector combinators (`"+"`, `">"`, `"~"`) and the
+ # selectors to which they apply from two sequences and merges them
+ # together into a single array.
+ #
+ # @param seq1 [Array<SimpleSequence or String>]
+ # @param seq2 [Array<SimpleSequence or String>]
+ # @return [Array<SimpleSequence or String or
+ # Array<Array<SimpleSequence or String>>]
+ # If there are no trailing combinators to be merged, this will be the
+ # empty array. If the trailing combinators cannot be merged, this will
+ # be nil. Otherwise, this will contained the merged selector. Array
+ # elements are [Sass::Util#paths]-style options; conceptually, an "or"
+ # of multiple selectors.
+ # @comment
+ # rubocop:disable MethodLength
+ def merge_final_ops(seq1, seq2, res = [])
+ ops1, ops2 = [], []
+ ops1 << seq1.pop while seq1.last.is_a?(String)
+ ops2 << seq2.pop while seq2.last.is_a?(String)
+
+ # Not worth the headache of trying to preserve newlines here. The most
+ # important use of newlines is at the beginning of the selector to wrap
+ # across lines anyway.
+ ops1.reject! {|o| o == "\n"}
+ ops2.reject! {|o| o == "\n"}
+
+ return res if ops1.empty? && ops2.empty?
+ if ops1.size > 1 || ops2.size > 1
+ # If there are multiple operators, something hacky's going on. If one
+ # is a supersequence of the other, use that, otherwise give up.
+ lcs = Sass::Util.lcs(ops1, ops2)
+ return unless lcs == ops1 || lcs == ops2
+ res.unshift(*(ops1.size > ops2.size ? ops1 : ops2).reverse)
+ return res
+ end
+
+ # This code looks complicated, but it's actually just a bunch of special
+ # cases for interactions between different combinators.
+ op1, op2 = ops1.first, ops2.first
+ if op1 && op2
+ sel1 = seq1.pop
+ sel2 = seq2.pop
+ if op1 == '~' && op2 == '~'
+ if sel1.superselector?(sel2)
+ res.unshift sel2, '~'
+ elsif sel2.superselector?(sel1)
+ res.unshift sel1, '~'
+ else
+ merged = sel1.unify(sel2)
+ res.unshift [
+ [sel1, '~', sel2, '~'],
+ [sel2, '~', sel1, '~'],
+ ([merged, '~'] if merged)
+ ].compact
+ end
+ elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~')
+ if op1 == '~'
+ tilde_sel, plus_sel = sel1, sel2
+ else
+ tilde_sel, plus_sel = sel2, sel1
+ end
+
+ if tilde_sel.superselector?(plus_sel)
+ res.unshift plus_sel, '+'
+ else
+ merged = plus_sel.unify(tilde_sel)
+ res.unshift [
+ [tilde_sel, '~', plus_sel, '+'],
+ ([merged, '+'] if merged)
+ ].compact
+ end
+ elsif op1 == '>' && %w[~ +].include?(op2)
+ res.unshift sel2, op2
+ seq1.push sel1, op1
+ elsif op2 == '>' && %w[~ +].include?(op1)
+ res.unshift sel1, op1
+ seq2.push sel2, op2
+ elsif op1 == op2
+ merged = sel1.unify(sel2)
+ return unless merged
+ res.unshift merged, op1
+ else
+ # Unknown selector combinators can't be unified
+ return
+ end
+ return merge_final_ops(seq1, seq2, res)
+ elsif op1
+ seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last)
+ res.unshift seq1.pop, op1
+ return merge_final_ops(seq1, seq2, res)
+ else # op2
+ seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last)
+ res.unshift seq2.pop, op2
+ return merge_final_ops(seq1, seq2, res)
+ end
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ # Takes initial subsequences of `seq1` and `seq2` and returns all
+ # orderings of those subsequences. The initial subsequences are determined
+ # by a block.
+ #
+ # Destructively removes the initial subsequences of `seq1` and `seq2`.
+ #
+ # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`
+ # denoting the boundary of the initial subsequence), this would return
+ # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and
+ # `(3 4 5)`.
+ #
+ # @param seq1 [Array]
+ # @param seq2 [Array]
+ # @yield [a] Used to determine when to cut off the initial subsequences.
+ # Called repeatedly for each sequence until it returns true.
+ # @yieldparam a [Array] A final subsequence of one input sequence after
+ # cutting off some initial subsequence.
+ # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
+ # here.
+ # @return [Array<Array>] All possible orderings of the initial subsequences.
+ def chunks(seq1, seq2)
+ chunk1 = []
+ chunk1 << seq1.shift until yield seq1
+ chunk2 = []
+ chunk2 << seq2.shift until yield seq2
+ return [] if chunk1.empty? && chunk2.empty?
+ return [chunk2] if chunk1.empty?
+ return [chunk1] if chunk2.empty?
+ [chunk1 + chunk2, chunk2 + chunk1]
+ end
+
+ # Groups a sequence into subsequences. The subsequences are determined by
+ # strings; adjacent non-string elements will be put into separate groups,
+ # but any element adjacent to a string will be grouped with that string.
+ #
+ # For example, `(A B "C" D E "F" G "H" "I" J)` will become `[(A) (B "C" D)
+ # (E "F" G "H" "I" J)]`.
+ #
+ # @param seq [Array]
+ # @return [Array<Array>]
+ def group_selectors(seq)
+ newseq = []
+ tail = seq.dup
+ until tail.empty?
+ head = []
+ begin
+ head << tail.shift
+ end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String)
+ newseq << head
+ end
+ newseq
+ end
+
+ # Given two selector sequences, returns whether `seq1` is a
+ # superselector of `seq2`; that is, whether `seq1` matches every
+ # element `seq2` matches.
+ #
+ # @param seq1 [Array<SimpleSequence or String>]
+ # @param seq2 [Array<SimpleSequence or String>]
+ # @return [Boolean]
+ def _superselector?(seq1, seq2)
+ seq1 = seq1.reject {|e| e == "\n"}
+ seq2 = seq2.reject {|e| e == "\n"}
+ # Selectors with leading or trailing operators are neither
+ # superselectors nor subselectors.
+ return if seq1.last.is_a?(String) || seq2.last.is_a?(String) ||
+ seq1.first.is_a?(String) || seq2.first.is_a?(String)
+ # More complex selectors are never superselectors of less complex ones
+ return if seq1.size > seq2.size
+ return seq1.first.superselector?(seq2.last, seq2[0...-1]) if seq1.size == 1
+
+ _, si = Sass::Util.enum_with_index(seq2).find do |e, i|
+ return if i == seq2.size - 1
+ next if e.is_a?(String)
+ seq1.first.superselector?(e, seq2[0...i])
+ end
+ return unless si
+
+ if seq1[1].is_a?(String)
+ return unless seq2[si + 1].is_a?(String)
+
+ # .foo ~ .bar is a superselector of .foo + .bar
+ return unless seq1[1] == "~" ? seq2[si + 1] != ">" : seq1[1] == seq2[si + 1]
+
+ # .foo > .baz is not a superselector of .foo > .bar > .baz or .foo >
+ # .bar .baz, despite the fact that .baz is a superselector of .bar >
+ # .baz and .bar .baz. Same goes for + and ~.
+ return if seq1.length == 3 && seq2.length > 3
+
+ return _superselector?(seq1[2..-1], seq2[si + 2..-1])
+ elsif seq2[si + 1].is_a?(String)
+ return unless seq2[si + 1] == ">"
+ return _superselector?(seq1[1..-1], seq2[si + 2..-1])
+ else
+ return _superselector?(seq1[1..-1], seq2[si + 1..-1])
+ end
+ end
+
+ # Like \{#_superselector?}, but compares the selectors in the
+ # context of parent selectors, as though they shared an implicit
+ # base simple selector. For example, `B` is not normally a
+ # superselector of `B A`, since it doesn't match `A` elements.
+ # However, it is a parent superselector, since `B X` is a
+ # superselector of `B A X`.
+ #
+ # @param seq1 [Array<SimpleSequence or String>]
+ # @param seq2 [Array<SimpleSequence or String>]
+ # @return [Boolean]
+ def parent_superselector?(seq1, seq2)
+ base = Sass::Selector::SimpleSequence.new([Sass::Selector::Placeholder.new('<temp>')],
+ false)
+ _superselector?(seq1 + [base], seq2 + [base])
+ end
+
+ # Removes redundant selectors from between multiple lists of
+ # selectors. This takes a list of lists of selector sequences;
+ # each individual list is assumed to have no redundancy within
+ # itself. A selector is only removed if it's redundant with a
+ # selector in another list.
+ #
+ # "Redundant" here means that one selector is a superselector of
+ # the other. The more specific selector is removed.
+ #
+ # @param seqses [Array<Array<Array<SimpleSequence or String>>>]
+ # @return [Array<Array<SimpleSequence or String>>]
+ def trim(seqses)
+ # Avoid truly horrific quadratic behavior. TODO: I think there
+ # may be a way to get perfect trimming without going quadratic.
+ return Sass::Util.flatten(seqses, 1) if seqses.size > 100
+
+ # Keep the results in a separate array so we can be sure we aren't
+ # comparing against an already-trimmed selector. This ensures that two
+ # identical selectors don't mutually trim one another.
+ result = seqses.dup
+
+ # This is n^2 on the sequences, but only comparing between
+ # separate sequences should limit the quadratic behavior.
+ seqses.each_with_index do |seqs1, i|
+ result[i] = seqs1.reject do |seq1|
+ # The maximum specificity of the sources that caused [seq1] to be
+ # generated. In order for [seq1] to be removed, there must be
+ # another selector that's a superselector of it *and* that has
+ # specificity greater or equal to this.
+ max_spec = _sources(seq1).map do |seq|
+ spec = seq.specificity
+ spec.is_a?(Range) ? spec.max : spec
+ end.max || 0
+
+ result.any? do |seqs2|
+ next if seqs1.equal?(seqs2)
+ # Second Law of Extend: the specificity of a generated selector
+ # should never be less than the specificity of the extending
+ # selector.
+ #
+ # See https://github.com/nex3/sass/issues/324.
+ seqs2.any? do |seq2|
+ spec2 = _specificity(seq2)
+ spec2 = spec2.begin if spec2.is_a?(Range)
+ spec2 >= max_spec && _superselector?(seq2, seq1)
+ end
+ end
+ end
+ end
+ Sass::Util.flatten(result, 1)
+ end
+
+ def _hash
+ members.reject {|m| m == "\n"}.hash
+ end
+
+ def _eql?(other)
+ other.members.reject {|m| m == "\n"}.eql?(members.reject {|m| m == "\n"})
+ end
+
+ private
+
+ def path_has_two_subjects?(path)
+ subject = false
+ path.each do |sseq_or_op|
+ next unless sseq_or_op.is_a?(SimpleSequence)
+ next unless sseq_or_op.subject?
+ return true if subject
+ subject = true
+ end
+ false
+ end
+
+ def _sources(seq)
+ s = Set.new
+ seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)}
+ s
+ end
+
+ def extended_not_expanded_to_s(extended_not_expanded)
+ extended_not_expanded.map do |choices|
+ choices = choices.map do |sel|
+ next sel.first.to_s if sel.size == 1
+ "#{sel.join ' '}"
+ end
+ next choices.first if choices.size == 1 && !choices.include?(' ')
+ "(#{choices.join ', '})"
+ end.join ' '
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/selector/simple.rb
b/backends/css/gems/sass-3.4.9/lib/sass/selector/simple.rb
new file mode 100644
index 0000000..bb18e8d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/selector/simple.rb
@@ -0,0 +1,117 @@
+module Sass
+ module Selector
+ # The abstract superclass for simple selectors
+ # (that is, those that don't compose multiple selectors).
+ class Simple
+ # The line of the Sass template on which this selector was declared.
+ #
+ # @return [Fixnum]
+ attr_accessor :line
+
+ # The name of the file in which this selector was declared,
+ # or `nil` if it was not declared in a file (e.g. on stdin).
+ #
+ # @return [String, nil]
+ attr_accessor :filename
+
+ # @see #to_s
+ #
+ # @return [String]
+ def inspect
+ to_s
+ end
+
+ # Returns the selector string.
+ #
+ # @return [String]
+ def to_s
+ Sass::Util.abstract(self)
+ end
+
+ # Returns a hash code for this selector object.
+ #
+ # By default, this is based on the value of \{#to\_a},
+ # so if that contains information irrelevant to the identity of the selector,
+ # this should be overridden.
+ #
+ # @return [Fixnum]
+ def hash
+ @_hash ||= equality_key.hash
+ end
+
+ # Checks equality between this and another object.
+ #
+ # By default, this is based on the value of \{#to\_a},
+ # so if that contains information irrelevant to the identity of the selector,
+ # this should be overridden.
+ #
+ # @param other [Object] The object to test equality against
+ # @return [Boolean] Whether or not this is equal to `other`
+ def eql?(other)
+ other.class == self.class && other.hash == hash && other.equality_key == equality_key
+ end
+ alias_method :==, :eql?
+
+ # Unifies this selector with a {SimpleSequence}'s {SimpleSequence#members members array},
+ # returning another `SimpleSequence` members array
+ # that matches both this selector and the input selector.
+ #
+ # By default, this just appends this selector to the end of the array
+ # (or returns the original array if this selector already exists in it).
+ #
+ # @param sels [Array<Simple>] A {SimpleSequence}'s {SimpleSequence#members members array}
+ # @return [Array<Simple>, nil] A {SimpleSequence} {SimpleSequence#members members array}
+ # matching both `sels` and this selector,
+ # or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`)
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
+ # This will only ever occur when a dynamic selector,
+ # such as {Parent} or {Interpolation}, is used in unification.
+ # Since these selectors should be resolved
+ # by the time extension and unification happen,
+ # this exception will only ever be raised as a result of programmer error
+ def unify(sels)
+ return sels if sels.any? {|sel2| eql?(sel2)}
+ sels_with_ix = Sass::Util.enum_with_index(sels)
+ _, i =
+ if is_a?(Pseudo)
+ sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && (sels.last.type == :element)}
+ else
+ sels_with_ix.find {|sel, _| sel.is_a?(Pseudo)}
+ end
+ return sels + [self] unless i
+ sels[0...i] + [self] + sels[i..-1]
+ end
+
+ protected
+
+ # Returns the key used for testing whether selectors are equal.
+ #
+ # This is a cached version of \{#to\_s}.
+ #
+ # @return [String]
+ def equality_key
+ @equality_key ||= to_s
+ end
+
+ # Unifies two namespaces,
+ # returning a namespace that works for both of them if possible.
+ #
+ # @param ns1 [String, nil] The first namespace.
+ # `nil` means none specified, e.g. `foo`.
+ # The empty string means no namespace specified, e.g. `|foo`.
+ # `"*"` means any namespace is allowed, e.g. `*|foo`.
+ # @param ns2 [String, nil] The second namespace. See `ns1`.
+ # @return [Array(String or nil, Boolean)]
+ # The first value is the unified namespace, or `nil` for no namespace.
+ # The second value is whether or not a namespace that works for both inputs
+ # could be found at all.
+ # If the second value is `false`, the first should be ignored.
+ def unify_namespaces(ns1, ns2)
+ return nil, false unless ns1 == ns2 || ns1.nil? || ns1 == '*' || ns2.nil? || ns2 == '*'
+ return ns2, true if ns1 == '*'
+ return ns1, true if ns2 == '*'
+ [ns1 || ns2, true]
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/selector/simple_sequence.rb
b/backends/css/gems/sass-3.4.9/lib/sass/selector/simple_sequence.rb
new file mode 100644
index 0000000..bfcda10
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/selector/simple_sequence.rb
@@ -0,0 +1,344 @@
+module Sass
+ module Selector
+ # A unseparated sequence of selectors
+ # that all apply to a single element.
+ # For example, `.foo#bar[attr=baz]` is a simple sequence
+ # of the selectors `.foo`, `#bar`, and `[attr=baz]`.
+ class SimpleSequence < AbstractSequence
+ # The array of individual selectors.
+ #
+ # @return [Array<Simple>]
+ attr_accessor :members
+
+ # The extending selectors that caused this selector sequence to be
+ # generated. For example:
+ #
+ # a.foo { ... }
+ # b.bar { extend a}
+ # c.baz { extend b}
+ #
+ # The generated selector `b.foo.bar` has `{b.bar}` as its `sources` set,
+ # and the generated selector `c.foo.bar.baz` has `{b.bar, c.baz}` as its
+ # `sources` set.
+ #
+ # This is populated during the {Sequence#do_extend} process.
+ #
+ # @return {Set<Sequence>}
+ attr_accessor :sources
+
+ # This sequence source range.
+ #
+ # @return [Sass::Source::Range]
+ attr_accessor :source_range
+
+ # @see \{#subject?}
+ attr_writer :subject
+
+ # Returns the element or universal selector in this sequence,
+ # if it exists.
+ #
+ # @return [Element, Universal, nil]
+ def base
+ @base ||= (members.first if members.first.is_a?(Element) || members.first.is_a?(Universal))
+ end
+
+ def pseudo_elements
+ @pseudo_elements ||= members.select {|sel| sel.is_a?(Pseudo) && sel.type == :element}
+ end
+
+ def selector_pseudo_classes
+ @selector_pseudo_classes ||= members.
+ select {|sel| sel.is_a?(Pseudo) && sel.type == :class && sel.selector}.
+ group_by {|sel| sel.normalized_name}
+ end
+
+ # Returns the non-base, non-pseudo-element selectors in this sequence.
+ #
+ # @return [Set<Simple>]
+ def rest
+ @rest ||= Set.new(members - [base] - pseudo_elements)
+ end
+
+ # Whether or not this compound selector is the subject of the parent
+ # selector; that is, whether it is prepended with `$` and represents the
+ # actual element that will be selected.
+ #
+ # @return [Boolean]
+ def subject?
+ @subject
+ end
+
+ # @param selectors [Array<Simple>] See \{#members}
+ # @param subject [Boolean] See \{#subject?}
+ # @param source_range [Sass::Source::Range]
+ def initialize(selectors, subject, source_range = nil)
+ @members = selectors
+ @subject = subject
+ @sources = Set.new
+ @source_range = source_range
+ end
+
+ # Resolves the {Parent} selectors within this selector
+ # by replacing them with the given parent selector,
+ # handling commas appropriately.
+ #
+ # @param super_cseq [CommaSequence] The parent selector
+ # @return [CommaSequence] This selector, with parent references resolved
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
+ def resolve_parent_refs(super_cseq)
+ resolved_members = @members.map do |sel|
+ next sel unless sel.is_a?(Pseudo) && sel.selector
+ sel.with_selector(sel.selector.resolve_parent_refs(super_cseq, !:implicit_parent))
+ end.flatten
+
+ # Parent selector only appears as the first selector in the sequence
+ unless (parent = resolved_members.first).is_a?(Parent)
+ return CommaSequence.new([Sequence.new([SimpleSequence.new(resolved_members, subject?)])])
+ end
+
+ return super_cseq if @members.size == 1 && parent.suffix.nil?
+
+ CommaSequence.new(super_cseq.members.map do |super_seq|
+ members = super_seq.members.dup
+ newline = members.pop if members.last == "\n"
+ unless members.last.is_a?(SimpleSequence)
+ raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
+ super_seq.to_s + '"')
+ end
+
+ parent_sub = members.last.members
+ unless parent.suffix.nil?
+ parent_sub = parent_sub.dup
+ parent_sub[-1] = parent_sub.last.dup
+ case parent_sub.last
+ when Sass::Selector::Class, Sass::Selector::Id, Sass::Selector::Placeholder
+ parent_sub[-1] = parent_sub.last.class.new(parent_sub.last.name + parent.suffix)
+ when Sass::Selector::Element
+ parent_sub[-1] = parent_sub.last.class.new(
+ parent_sub.last.name + parent.suffix,
+ parent_sub.last.namespace)
+ when Sass::Selector::Pseudo
+ if parent_sub.last.arg || parent_sub.last.selector
+ raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
+ super_seq.to_s + '"')
+ end
+ parent_sub[-1] = Sass::Selector::Pseudo.new(
+ parent_sub.last.type,
+ parent_sub.last.name + parent.suffix,
+ nil, nil)
+ else
+ raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
+ super_seq.to_s + '"')
+ end
+ end
+
+ Sequence.new(members[0...-1] +
+ [SimpleSequence.new(parent_sub + @members[1..-1], subject?)] +
+ [newline].compact)
+ end)
+ end
+
+ # Non-destructively extends this selector with the extensions specified in a hash
+ # (which should come from {Sass::Tree::Visitors::Cssize}).
+ #
+ # @param extends [{Selector::Simple =>
+ # Sass::Tree::Visitors::Cssize::Extend}]
+ # The extensions to perform on this selector
+ # @param parent_directives [Array<Sass::Tree::DirectiveNode>]
+ # The directives containing this selector.
+ # @param seen [Set<Array<Selector::Simple>>]
+ # The set of simple sequences that are currently being replaced.
+ # @param original [Boolean]
+ # Whether this is the original selector being extended, as opposed to
+ # the result of a previous extension that's being re-extended.
+ # @return [Array<Sequence>] A list of selectors generated
+ # by extending this selector with `extends`.
+ # @see CommaSequence#do_extend
+ def do_extend(extends, parent_directives, replace, seen)
+ seen_with_pseudo_selectors = seen.dup
+
+ modified_original = false
+ members = Sass::Util.enum_with_index(self.members).map do |sel, i|
+ next sel unless sel.is_a?(Pseudo) && sel.selector
+ next sel if seen.include?([sel])
+ extended = sel.selector.do_extend(extends, parent_directives, replace, seen, !:original)
+ next sel if extended == sel.selector
+ extended.members.reject! {|seq| seq.has_placeholder?}
+
+ # For `:not()`, we usually want to get rid of any complex
+ # selectors becuase that will cause the selector to fail to
+ # parse on all browsers at time of writing. We can keep them
+ # if either the original selector had a complex selector, or
+ # the result of extending has only complex selectors,
+ # because either way we aren't breaking anything that isn't
+ # already broken.
+ if sel.normalized_name == 'not' &&
+ (sel.selector.members.none? {|seq| seq.members.length > 1} &&
+ extended.members.any? {|seq| seq.members.length == 1})
+ extended.members.reject! {|seq| seq.members.length > 1}
+ end
+
+ modified_original = true
+ result = sel.with_selector(extended)
+ result.each {|new_sel| seen_with_pseudo_selectors << [new_sel]}
+ result
+ end.flatten
+
+ groups = Sass::Util.group_by_to_a(extends[members.to_set]) {|ex| ex.extender}
+ groups.map! do |seq, group|
+ sels = group.map {|e| e.target}.flatten
+ # If A { extend B} and C {...},
+ # seq is A, sels is B, and self is C
+
+ self_without_sel = Sass::Util.array_minus(members, sels)
+ group.each {|e| e.result = :failed_to_unify unless e.result == :succeeded}
+ unified = seq.members.last.unify(SimpleSequence.new(self_without_sel, subject?))
+ next unless unified
+ group.each {|e| e.result = :succeeded}
+ group.each {|e| check_directives_match!(e, parent_directives)}
+ new_seq = Sequence.new(seq.members[0...-1] + [unified])
+ new_seq.add_sources!(sources + [seq])
+ [sels, new_seq]
+ end
+ groups.compact!
+ groups.map! do |sels, seq|
+ next [] if seen.include?(sels)
+ seq.do_extend(
+ extends, parent_directives, !:replace, seen_with_pseudo_selectors + [sels], !:original)
+ end
+ groups.flatten!
+
+ if modified_original || !replace || groups.empty?
+ # First Law of Extend: the result of extending a selector should
+ # (almost) always contain the base selector.
+ #
+ # See https://github.com/nex3/sass/issues/324.
+ original = Sequence.new([SimpleSequence.new(members, @subject, source_range)])
+ original.add_sources! sources
+ groups.unshift original
+ end
+ groups.uniq!
+ groups
+ end
+
+ # Unifies this selector with another {SimpleSequence}, returning
+ # another `SimpleSequence` that is a subselector of both input
+ # selectors.
+ #
+ # @param other [SimpleSequence]
+ # @return [SimpleSequence, nil] A {SimpleSequence} matching both `sels` and this selector,
+ # or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`)
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
+ # This will only ever occur when a dynamic selector,
+ # such as {Parent} or {Interpolation}, is used in unification.
+ # Since these selectors should be resolved
+ # by the time extension and unification happen,
+ # this exception will only ever be raised as a result of programmer error
+ def unify(other)
+ sseq = members.inject(other.members) do |member, sel|
+ return unless member
+ sel.unify(member)
+ end
+ return unless sseq
+ SimpleSequence.new(sseq, other.subject? || subject?)
+ end
+
+ # Returns whether or not this selector matches all elements
+ # that the given selector matches (as well as possibly more).
+ #
+ # @example
+ # (.foo).superselector?(.foo.bar) #=> true
+ # (.foo).superselector?(.bar) #=> false
+ # @param their_sseq [SimpleSequence]
+ # @param parents [Array<SimpleSequence, String>] The parent selectors of `their_sseq`, if any.
+ # @return [Boolean]
+ def superselector?(their_sseq, parents = [])
+ return false unless base.nil? || base.eql?(their_sseq.base)
+ return false unless pseudo_elements.eql?(their_sseq.pseudo_elements)
+ our_spcs = selector_pseudo_classes
+ their_spcs = their_sseq.selector_pseudo_classes
+
+ # Some psuedo-selectors can be subselectors of non-pseudo selectors.
+ # Pull those out here so we can efficiently check against them below.
+ their_subselector_pseudos = %w[matches any nth-child nth-last-child].
+ map {|name| their_spcs[name] || []}.flatten
+
+ # If `self`'s non-pseudo simple selectors aren't a subset of `their_sseq`'s,
+ # it's definitely not a superselector. This also considers being matched
+ # by `:matches` or `:any`.
+ return false unless rest.all? do |our_sel|
+ next true if our_sel.is_a?(Pseudo) && our_sel.selector
+ next true if their_sseq.rest.include?(our_sel)
+ their_subselector_pseudos.any? do |their_pseudo|
+ their_pseudo.selector.members.all? do |their_seq|
+ next false unless their_seq.members.length == 1
+ their_sseq = their_seq.members.first
+ next false unless their_sseq.is_a?(SimpleSequence)
+ their_sseq.rest.include?(our_sel)
+ end
+ end
+ end
+
+ our_spcs.all? do |name, pseudos|
+ pseudos.all? {|pseudo| pseudo.superselector?(their_sseq, parents)}
+ end
+ end
+
+ # @see Simple#to_s
+ def to_s
+ res = @members.join
+ res << '!' if subject?
+ res
+ end
+
+ # Returns a string representation of the sequence.
+ # This is basically the selector string.
+ #
+ # @return [String]
+ def inspect
+ res = members.map {|m| m.inspect}.join
+ res << '!' if subject?
+ res
+ end
+
+ # Return a copy of this simple sequence with `sources` merged into the
+ # {SimpleSequence#sources} set.
+ #
+ # @param sources [Set<Sequence>]
+ # @return [SimpleSequence]
+ def with_more_sources(sources)
+ sseq = dup
+ sseq.members = members.dup
+ sseq.sources = self.sources | sources
+ sseq
+ end
+
+ private
+
+ def check_directives_match!(extend, parent_directives)
+ dirs1 = extend.directives.map {|d| d.resolved_value}
+ dirs2 = parent_directives.map {|d| d.resolved_value}
+ return if Sass::Util.subsequence?(dirs1, dirs2)
+ line = extend.node.line
+ filename = extend.node.filename
+
+ # TODO(nweiz): this should use the Sass stack trace of the extend node,
+ # not the selector.
+ raise Sass::SyntaxError.new(<<MESSAGE)
+You may not @extend an outer selector from within #{extend.directives.last.name}.
+You may only @extend selectors within the same directive.
+From "@extend #{extend.target.join(', ')}" on line #{line}#{" of #{filename}" if filename}.
+MESSAGE
+ end
+
+ def _hash
+ [base, Sass::Util.set_hash(rest)].hash
+ end
+
+ def _eql?(other)
+ other.base.eql?(base) && other.pseudo_elements == pseudo_elements &&
+ Sass::Util.set_eql?(other.rest, rest) && other.subject? == subject?
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/shared.rb b/backends/css/gems/sass-3.4.9/lib/sass/shared.rb
new file mode 100644
index 0000000..9e9189c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/shared.rb
@@ -0,0 +1,76 @@
+module Sass
+ # This module contains functionality that's shared between Haml and Sass.
+ module Shared
+ extend self
+
+ # Scans through a string looking for the interoplation-opening `#{`
+ # and, when it's found, yields the scanner to the calling code
+ # so it can handle it properly.
+ #
+ # The scanner will have any backslashes immediately in front of the `#{`
+ # as the second capture group (`scan[2]`),
+ # and the text prior to that as the first (`scan[1]`).
+ #
+ # @yieldparam scan [StringScanner] The scanner scanning through the string
+ # @return [String] The text remaining in the scanner after all `#{`s have been processed
+ def handle_interpolation(str)
+ scan = Sass::Util::MultibyteStringScanner.new(str)
+ yield scan while scan.scan(/(.*?)(\\*)\#\{/m)
+ scan.rest
+ end
+
+ # Moves a scanner through a balanced pair of characters.
+ # For example:
+ #
+ # Foo (Bar (Baz bang) bop) (Bang (bop bip))
+ # ^ ^
+ # from to
+ #
+ # @param scanner [StringScanner] The string scanner to move
+ # @param start [Character] The character opening the balanced pair.
+ # A `Fixnum` in 1.8, a `String` in 1.9
+ # @param finish [Character] The character closing the balanced pair.
+ # A `Fixnum` in 1.8, a `String` in 1.9
+ # @param count [Fixnum] The number of opening characters matched
+ # before calling this method
+ # @return [(String, String)] The string matched within the balanced pair
+ # and the rest of the string.
+ # `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
+ def balance(scanner, start, finish, count = 0)
+ str = ''
+ scanner = Sass::Util::MultibyteStringScanner.new(scanner) unless scanner.is_a? StringScanner
+ regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
+ while scanner.scan(regexp)
+ str << scanner.matched
+ count += 1 if scanner.matched[-1] == start
+ count -= 1 if scanner.matched[-1] == finish
+ return [str, scanner.rest] if count == 0
+ end
+ end
+
+ # Formats a string for use in error messages about indentation.
+ #
+ # @param indentation [String] The string used for indentation
+ # @param was [Boolean] Whether or not to add `"was"` or `"were"`
+ # (depending on how many characters were in `indentation`)
+ # @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`)
+ def human_indentation(indentation, was = false)
+ if !indentation.include?(?\t)
+ noun = 'space'
+ elsif !indentation.include?(?\s)
+ noun = 'tab'
+ else
+ return indentation.inspect + (was ? ' was' : '')
+ end
+
+ singular = indentation.length == 1
+ if was
+ was = singular ? ' was' : ' were'
+ else
+ was = ''
+ end
+
+ "#{indentation.length} #{noun}#{'s' unless singular}#{was}"
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/source/map.rb
b/backends/css/gems/sass-3.4.9/lib/sass/source/map.rb
new file mode 100644
index 0000000..fdad539
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/source/map.rb
@@ -0,0 +1,211 @@
+module Sass::Source
+ class Map
+ # A mapping from one source range to another. Indicates that `input` was
+ # compiled to `output`.
+ #
+ # @!attribute input
+ # @return [Sass::Source::Range] The source range in the input document.
+ #
+ # @!attribute output
+ # @return [Sass::Source::Range] The source range in the output document.
+ class Mapping < Struct.new(:input, :output)
+ # @return [String] A string representation of the mapping.
+ def inspect
+ "#{input.inspect} => #{output.inspect}"
+ end
+ end
+
+ # The mapping data ordered by the location in the target.
+ #
+ # @return [Array<Mapping>]
+ attr_reader :data
+
+ def initialize
+ @data = []
+ end
+
+ # Adds a new mapping from one source range to another. Multiple invocations
+ # of this method should have each `output` range come after all previous ranges.
+ #
+ # @param input [Sass::Source::Range]
+ # The source range in the input document.
+ # @param output [Sass::Source::Range]
+ # The source range in the output document.
+ def add(input, output)
+ @data.push(Mapping.new(input, output))
+ end
+
+ # Shifts all output source ranges forward one or more lines.
+ #
+ # @param delta [Fixnum] The number of lines to shift the ranges forward.
+ def shift_output_lines(delta)
+ return if delta == 0
+ @data.each do |m|
+ m.output.start_pos.line += delta
+ m.output.end_pos.line += delta
+ end
+ end
+
+ # Shifts any output source ranges that lie on the first line forward one or
+ # more characters on that line.
+ #
+ # @param delta [Fixnum] The number of characters to shift the ranges
+ # forward.
+ def shift_output_offsets(delta)
+ return if delta == 0
+ @data.each do |m|
+ break if m.output.start_pos.line > 1
+ m.output.start_pos.offset += delta
+ m.output.end_pos.offset += delta if m.output.end_pos.line > 1
+ end
+ end
+
+ # Returns the standard JSON representation of the source map.
+ #
+ # If the `:css_uri` option isn't specified, the `:css_path` and
+ # `:sourcemap_path` options must both be specified. Any options may also be
+ # specified alongside the `:css_uri` option. If `:css_uri` isn't specified,
+ # it will be inferred from `:css_path` and `:sourcemap_path` using the
+ # assumption that the local file system has the same layout as the server.
+ #
+ # Regardless of which options are passed to this method, source stylesheets
+ # that are imported using a non-default importer will only be linked to in
+ # the source map if their importers implement
+ # \{Sass::Importers::Base#public\_url\}.
+ #
+ # @option options :css_uri [String]
+ # The publicly-visible URI of the CSS output file.
+ # @option options :css_path [String]
+ # The local path of the CSS output file.
+ # @option options :sourcemap_path [String]
+ # The (eventual) local path of the sourcemap file.
+ # @option options :type [Symbol]
+ # `:auto` (default), `:file`, or `:inline`.
+ # @return [String] The JSON string.
+ # @raise [ArgumentError] If neither `:css_uri` nor `:css_path` and
+ # `:sourcemap_path` are specified.
+ # @comment
+ # rubocop:disable MethodLength
+ def to_json(options)
+ css_uri, css_path, sourcemap_path =
+ options[:css_uri], options[:css_path], options[:sourcemap_path]
+ unless css_uri || (css_path && sourcemap_path)
+ raise ArgumentError.new("Sass::Source::Map#to_json requires either " \
+ "the :css_uri option or both the :css_path and :soucemap_path options.")
+ end
+ css_path &&= Sass::Util.pathname(Sass::Util.absolute_path(css_path))
+ sourcemap_path &&= Sass::Util.pathname(Sass::Util.absolute_path(sourcemap_path))
+ css_uri ||= Sass::Util.file_uri_from_path(
+ Sass::Util.relative_path_from(css_path, sourcemap_path.dirname))
+
+ result = "{\n"
+ write_json_field(result, "version", 3, true)
+
+ source_uri_to_id = {}
+ id_to_source_uri = {}
+ id_to_contents = {} if options[:type] == :inline
+ next_source_id = 0
+ line_data = []
+ segment_data_for_line = []
+
+ # These track data necessary for the delta coding.
+ previous_target_line = nil
+ previous_target_offset = 1
+ previous_source_line = 1
+ previous_source_offset = 1
+ previous_source_id = 0
+
+ @data.each do |m|
+ file, importer = m.input.file, m.input.importer
+
+ if options[:type] == :inline
+ source_uri = file
+ else
+ sourcemap_dir = sourcemap_path && sourcemap_path.dirname.to_s
+ sourcemap_dir = nil if options[:type] == :file
+ source_uri = importer && importer.public_url(file, sourcemap_dir)
+ next unless source_uri
+ end
+
+ current_source_id = source_uri_to_id[source_uri]
+ unless current_source_id
+ current_source_id = next_source_id
+ next_source_id += 1
+
+ source_uri_to_id[source_uri] = current_source_id
+ id_to_source_uri[current_source_id] = source_uri
+
+ if options[:type] == :inline
+ id_to_contents[current_source_id] =
+ importer.find(file, {}).instance_variable_get('@template')
+ end
+ end
+
+ [
+ [m.input.start_pos, m.output.start_pos],
+ [m.input.end_pos, m.output.end_pos]
+ ].each do |source_pos, target_pos|
+ if previous_target_line != target_pos.line
+ line_data.push(segment_data_for_line.join(",")) unless segment_data_for_line.empty?
+ (target_pos.line - 1 - (previous_target_line || 0)).times {line_data.push("")}
+ previous_target_line = target_pos.line
+ previous_target_offset = 1
+ segment_data_for_line = []
+ end
+
+ # `segment` is a data chunk for a single position mapping.
+ segment = ""
+
+ # Field 1: zero-based starting offset.
+ segment << Sass::Util.encode_vlq(target_pos.offset - previous_target_offset)
+ previous_target_offset = target_pos.offset
+
+ # Field 2: zero-based index into the "sources" list.
+ segment << Sass::Util.encode_vlq(current_source_id - previous_source_id)
+ previous_source_id = current_source_id
+
+ # Field 3: zero-based starting line in the original source.
+ segment << Sass::Util.encode_vlq(source_pos.line - previous_source_line)
+ previous_source_line = source_pos.line
+
+ # Field 4: zero-based starting offset in the original source.
+ segment << Sass::Util.encode_vlq(source_pos.offset - previous_source_offset)
+ previous_source_offset = source_pos.offset
+
+ segment_data_for_line.push(segment)
+
+ previous_target_line = target_pos.line
+ end
+ end
+ line_data.push(segment_data_for_line.join(","))
+ write_json_field(result, "mappings", line_data.join(";"))
+
+ source_names = []
+ (0...next_source_id).each {|id| source_names.push(id_to_source_uri[id].to_s)}
+ write_json_field(result, "sources", source_names)
+
+ if options[:type] == :inline
+ write_json_field(result, "sourcesContent",
+ (0...next_source_id).map {|id| id_to_contents[id]})
+ end
+
+ write_json_field(result, "names", [])
+ write_json_field(result, "file", css_uri)
+
+ result << "\n}"
+ result
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ private
+
+ def write_json_field(out, name, value, is_first = false)
+ out << (is_first ? "" : ",\n") <<
+ "\"" <<
+ Sass::Util.json_escape_string(name) <<
+ "\": " <<
+ Sass::Util.json_value_of(value)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/source/position.rb
b/backends/css/gems/sass-3.4.9/lib/sass/source/position.rb
new file mode 100644
index 0000000..df39bd3
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/source/position.rb
@@ -0,0 +1,39 @@
+module Sass::Source
+ class Position
+ # The one-based line of the document associated with the position.
+ #
+ # @return [Fixnum]
+ attr_accessor :line
+
+ # The one-based offset in the line of the document associated with the
+ # position.
+ #
+ # @return [Fixnum]
+ attr_accessor :offset
+
+ # @param line [Fixnum] The source line
+ # @param offset [Fixnum] The source offset
+ def initialize(line, offset)
+ @line = line
+ @offset = offset
+ end
+
+ # @return [String] A string representation of the source position.
+ def inspect
+ "#{line.inspect}:#{offset.inspect}"
+ end
+
+ # @param str [String] The string to move through.
+ # @return [Position] The source position after proceeding forward through
+ # `str`.
+ def after(str)
+ newlines = str.count("\n")
+ Position.new(line + newlines,
+ if newlines == 0
+ offset + str.length
+ else
+ str.length - str.rindex("\n") - 1
+ end)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/source/range.rb
b/backends/css/gems/sass-3.4.9/lib/sass/source/range.rb
new file mode 100644
index 0000000..de687f9
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/source/range.rb
@@ -0,0 +1,41 @@
+module Sass::Source
+ class Range
+ # The starting position of the range in the document (inclusive).
+ #
+ # @return [Sass::Source::Position]
+ attr_accessor :start_pos
+
+ # The ending position of the range in the document (exclusive).
+ #
+ # @return [Sass::Source::Position]
+ attr_accessor :end_pos
+
+ # The file in which this source range appears. This can be nil if the file
+ # is unknown or not yet generated.
+ #
+ # @return [String]
+ attr_accessor :file
+
+ # The importer that imported the file in which this source range appears.
+ # This is nil for target ranges.
+ #
+ # @return [Sass::Importers::Base]
+ attr_accessor :importer
+
+ # @param start_pos [Sass::Source::Position] See \{#start_pos}
+ # @param end_pos [Sass::Source::Position] See \{#end_pos}
+ # @param file [String] See \{#file}
+ # @param importer [Sass::Importers::Base] See \{#importer}
+ def initialize(start_pos, end_pos, file, importer = nil)
+ @start_pos = start_pos
+ @end_pos = end_pos
+ @file = file
+ @importer = importer
+ end
+
+ # @return [String] A string representation of the source range.
+ def inspect
+ "(#{start_pos.inspect} to #{end_pos.inspect}#{" in #{ file}" if @file})"
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/stack.rb b/backends/css/gems/sass-3.4.9/lib/sass/stack.rb
new file mode 100644
index 0000000..3036d42
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/stack.rb
@@ -0,0 +1,120 @@
+module Sass
+ # A class representing the stack when compiling a Sass file.
+ class Stack
+ # TODO: use this to generate stack information for Sass::SyntaxErrors.
+
+ # A single stack frame.
+ class Frame
+ # The filename of the file in which this stack frame was created.
+ #
+ # @return [String]
+ attr_reader :filename
+
+ # The line number on which this stack frame was created.
+ #
+ # @return [String]
+ attr_reader :line
+
+ # The type of this stack frame. This can be `:import`, `:mixin`, or
+ # `:base`.
+ #
+ # `:base` indicates that this is the bottom-most frame, meaning that it
+ # represents a single line of code rather than a nested context. The stack
+ # will only ever have one base frame, and it will always be the most
+ # deeply-nested frame.
+ #
+ # @return [Symbol?]
+ attr_reader :type
+
+ # The name of the stack frame. For mixin frames, this is the mixin name;
+ # otherwise, it's `nil`.
+ #
+ # @return [String?]
+ attr_reader :name
+
+ def initialize(filename, line, type, name = nil)
+ @filename = filename
+ @line = line
+ @type = type
+ @name = name
+ end
+
+ # Whether this frame represents an import.
+ #
+ # @return [Boolean]
+ def is_import?
+ type == :import
+ end
+
+ # Whether this frame represents a mixin.
+ #
+ # @return [Boolean]
+ def is_mixin?
+ type == :mixin
+ end
+
+ # Whether this is the base frame.
+ #
+ # @return [Boolean]
+ def is_base?
+ type == :base
+ end
+ end
+
+ # The stack frames. The last frame is the most deeply-nested.
+ #
+ # @return [Array<Frame>]
+ attr_reader :frames
+
+ def initialize
+ @frames = []
+ end
+
+ # Pushes a base frame onto the stack.
+ #
+ # @param filename [String] See \{Frame#filename}.
+ # @param line [String] See \{Frame#line}.
+ # @yield [] A block in which the new frame is on the stack.
+ def with_base(filename, line)
+ with_frame(filename, line, :base) {yield}
+ end
+
+ # Pushes an import frame onto the stack.
+ #
+ # @param filename [String] See \{Frame#filename}.
+ # @param line [String] See \{Frame#line}.
+ # @yield [] A block in which the new frame is on the stack.
+ def with_import(filename, line)
+ with_frame(filename, line, :import) {yield}
+ end
+
+ # Pushes a mixin frame onto the stack.
+ #
+ # @param filename [String] See \{Frame#filename}.
+ # @param line [String] See \{Frame#line}.
+ # @param name [String] See \{Frame#name}.
+ # @yield [] A block in which the new frame is on the stack.
+ def with_mixin(filename, line, name)
+ with_frame(filename, line, :mixin, name) {yield}
+ end
+
+ def to_s
+ Sass::Util.enum_with_index(Sass::Util.enum_cons(frames.reverse + [nil], 2)).
+ map do |(frame, caller), i|
+ "#{i == 0 ? "on" : "from"} line #{frame.line}" +
+ " of #{frame.filename || "an unknown file"}" +
+ (caller && caller.name ? ", in `#{caller.name}'" : "")
+ end.join("\n")
+ end
+
+ private
+
+ def with_frame(filename, line, type, name = nil)
+ @frames.pop if @frames.last && @frames.last.type == :base
+ @frames.push(Frame.new(filename, line, type, name))
+ yield
+ ensure
+ @frames.pop unless type == :base && @frames.last && @frames.last.type != :base
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/supports.rb
b/backends/css/gems/sass-3.4.9/lib/sass/supports.rb
new file mode 100644
index 0000000..ee67254
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/supports.rb
@@ -0,0 +1,227 @@
+# A namespace for the ` supports` condition parse tree.
+module Sass::Supports
+ # The abstract superclass of all Supports conditions.
+ class Condition
+ # Runs the SassScript in the supports condition.
+ #
+ # @param environment [Sass::Environment] The environment in which to run the script.
+ def perform(environment); Sass::Util.abstract(self); end
+
+ # Returns the CSS for this condition.
+ #
+ # @return [String]
+ def to_css; Sass::Util.abstract(self); end
+
+ # Returns the Sass/CSS code for this condition.
+ #
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
+ # @return [String]
+ def to_src(options); Sass::Util.abstract(self); end
+
+ # Returns a deep copy of this condition and all its children.
+ #
+ # @return [Condition]
+ def deep_copy; Sass::Util.abstract(self); end
+
+ # Sets the options hash for the script nodes in the supports condition.
+ #
+ # @param options [{Symbol => Object}] The options has to set.
+ def options=(options); Sass::Util.abstract(self); end
+ end
+
+ # An operator condition (e.g. `CONDITION1 and CONDITION2`).
+ class Operator < Condition
+ # The left-hand condition.
+ #
+ # @return [Sass::Supports::Condition]
+ attr_accessor :left
+
+ # The right-hand condition.
+ #
+ # @return [Sass::Supports::Condition]
+ attr_accessor :right
+
+ # The operator ("and" or "or").
+ #
+ # @return [String]
+ attr_accessor :op
+
+ def initialize(left, right, op)
+ @left = left
+ @right = right
+ @op = op
+ end
+
+ def perform(env)
+ @left.perform(env)
+ @right.perform(env)
+ end
+
+ def to_css
+ "#{left_parens @left.to_css} #{op} #{right_parens @right.to_css}"
+ end
+
+ def to_src(options)
+ "#{left_parens @left.to_src(options)} #{op} #{right_parens @right.to_src(options)}"
+ end
+
+ def deep_copy
+ copy = dup
+ copy.left = @left.deep_copy
+ copy.right = @right.deep_copy
+ copy
+ end
+
+ def options=(options)
+ @left.options = options
+ @right.options = options
+ end
+
+ private
+
+ def left_parens(str)
+ return "(#{str})" if @left.is_a?(Negation)
+ str
+ end
+
+ def right_parens(str)
+ return "(#{str})" if @right.is_a?(Negation) || @right.is_a?(Operator)
+ str
+ end
+ end
+
+ # A negation condition (`not CONDITION`).
+ class Negation < Condition
+ # The condition being negated.
+ #
+ # @return [Sass::Supports::Condition]
+ attr_accessor :condition
+
+ def initialize(condition)
+ @condition = condition
+ end
+
+ def perform(env)
+ @condition.perform(env)
+ end
+
+ def to_css
+ "not #{parens @condition.to_css}"
+ end
+
+ def to_src(options)
+ "not #{parens @condition.to_src(options)}"
+ end
+
+ def deep_copy
+ copy = dup
+ copy.condition = condition.deep_copy
+ copy
+ end
+
+ def options=(options)
+ condition.options = options
+ end
+
+ private
+
+ def parens(str)
+ return "(#{str})" if @condition.is_a?(Negation) || @condition.is_a?(Operator)
+ str
+ end
+ end
+
+ # A declaration condition (e.g. `(feature: value)`).
+ class Declaration < Condition
+ # @return [Sass::Script::Tree::Node] The feature name.
+ attr_accessor :name
+
+ # @!attribute resolved_name
+ # The name of the feature after any SassScript has been resolved.
+ # Only set once \{Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_name
+
+ # The feature value.
+ #
+ # @return [Sass::Script::Tree::Node]
+ attr_accessor :value
+
+ # The value of the feature after any SassScript has been resolved.
+ # Only set once \{Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_value
+
+ def initialize(name, value)
+ @name = name
+ @value = value
+ end
+
+ def perform(env)
+ @resolved_name = name.perform(env)
+ @resolved_value = value.perform(env)
+ end
+
+ def to_css
+ "(#{ resolved_name}: #{ resolved_value})"
+ end
+
+ def to_src(options)
+ "(#{ name to_sass(options)}: #{ value to_sass(options)})"
+ end
+
+ def deep_copy
+ copy = dup
+ copy.name = @name.deep_copy
+ copy.value = @value.deep_copy
+ copy
+ end
+
+ def options=(options)
+ @name.options = options
+ @value.options = options
+ end
+ end
+
+ # An interpolation condition (e.g. `#{$var}`).
+ class Interpolation < Condition
+ # The SassScript expression in the interpolation.
+ #
+ # @return [Sass::Script::Tree::Node]
+ attr_accessor :value
+
+ # The value of the expression after it's been resolved.
+ # Only set once \{Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def perform(env)
+ @resolved_value = value.perform(env).to_s(:quote => :none)
+ end
+
+ def to_css
+ @resolved_value
+ end
+
+ def to_src(options)
+ @value.to_sass(options)
+ end
+
+ def deep_copy
+ copy = dup
+ copy.value = @value.deep_copy
+ copy
+ end
+
+ def options=(options)
+ @value.options = options
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/at_root_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/at_root_node.rb
new file mode 100644
index 0000000..e44d7aa
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/at_root_node.rb
@@ -0,0 +1,83 @@
+module Sass
+ module Tree
+ # A dynamic node representing an ` at-root` directive.
+ #
+ # An ` at-root` directive with a selector is converted to an \{AtRootNode}
+ # containing a \{RuleNode} at parse time.
+ #
+ # @see Sass::Tree
+ class AtRootNode < Node
+ # The query for this node (e.g. `(without: media)`),
+ # interspersed with {Sass::Script::Tree::Node}s representing
+ # `#{}`-interpolation. Any adjacent strings will be merged
+ # together.
+ #
+ # This will be nil if the directive didn't have a query. In this
+ # case, {#resolved\_type} will automatically be set to
+ # `:without` and {#resolved\_rule} will automatically be set to `["rule"]`.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :query
+
+ # The resolved type of this directive. `:with` or `:without`.
+ #
+ # @return [Symbol]
+ attr_accessor :resolved_type
+
+ # The resolved value of this directive -- a list of directives
+ # to either include or exclude.
+ #
+ # @return [Array<String>]
+ attr_accessor :resolved_value
+
+ # The number of additional tabs that the contents of this node
+ # should be indented.
+ #
+ # @return [Number]
+ attr_accessor :tabs
+
+ # Whether the last child of this node should be considered the
+ # end of a group.
+ #
+ # @return [Boolean]
+ attr_accessor :group_end
+
+ def initialize(query = nil)
+ super()
+ @query = Sass::Util.strip_string_array(Sass::Util.merge_adjacent_strings(query)) if query
+ @tabs = 0
+ end
+
+ # Returns whether or not the given directive is excluded by this
+ # node. `directive` may be "rule", which indicates whether
+ # normal CSS rules should be excluded.
+ #
+ # @param directive [String]
+ # @return [Boolean]
+ def exclude?(directive)
+ if resolved_type == :with
+ return false if resolved_value.include?('all')
+ !resolved_value.include?(directive)
+ else # resolved_type == :without
+ return true if resolved_value.include?('all')
+ resolved_value.include?(directive)
+ end
+ end
+
+ # Returns whether the given node is excluded by this node.
+ #
+ # @param node [Sass::Tree::Node]
+ # @return [Boolean]
+ def exclude_node?(node)
+ return exclude?(node.name.gsub(/^@/, '')) if node.is_a?(Sass::Tree::DirectiveNode)
+ return exclude?('keyframes') if node.is_a?(Sass::Tree::KeyframeRuleNode)
+ exclude?('rule') && node.is_a?(Sass::Tree::RuleNode)
+ end
+
+ # @see Node#bubbles?
+ def bubbles?
+ true
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/charset_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/charset_node.rb
new file mode 100644
index 0000000..5e0eeba
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/charset_node.rb
@@ -0,0 +1,22 @@
+module Sass::Tree
+ # A static node representing an unprocessed Sass ` charset` directive.
+ #
+ # @see Sass::Tree
+ class CharsetNode < Node
+ # The name of the charset.
+ #
+ # @return [String]
+ attr_accessor :name
+
+ # @param name [String] see \{#name}
+ def initialize(name)
+ @name = name
+ super()
+ end
+
+ # @see Node#invisible?
+ def invisible?
+ !Sass::Util.ruby1_8?
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/comment_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/comment_node.rb
new file mode 100644
index 0000000..8b7cdf0
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/comment_node.rb
@@ -0,0 +1,82 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A static node representing a Sass comment (silent or loud).
+ #
+ # @see Sass::Tree
+ class CommentNode < Node
+ # The text of the comment, not including `/*` and `*/`.
+ # Interspersed with {Sass::Script::Tree::Node}s representing `#{}`-interpolation
+ # if this is a loud comment.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :value
+
+ # The text of the comment
+ # after any interpolated SassScript has been resolved.
+ # Only set once \{Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_value
+
+ # The type of the comment. `:silent` means it's never output to CSS,
+ # `:normal` means it's output in every compile mode except `:compressed`,
+ # and `:loud` means it's output even in `:compressed`.
+ #
+ # @return [Symbol]
+ attr_accessor :type
+
+ # @param value [Array<String, Sass::Script::Tree::Node>] See \{#value}
+ # @param type [Symbol] See \{#type}
+ def initialize(value, type)
+ @value = Sass::Util.with_extracted_values(value) {|str| normalize_indentation str}
+ @type = type
+ super()
+ end
+
+ # Compares the contents of two comments.
+ #
+ # @param other [Object] The object to compare with
+ # @return [Boolean] Whether or not this node and the other object
+ # are the same
+ def ==(other)
+ self.class == other.class && value == other.value && type == other.type
+ end
+
+ # Returns `true` if this is a silent comment
+ # or the current style doesn't render comments.
+ #
+ # Comments starting with ! are never invisible (and the ! is removed from the output.)
+ #
+ # @return [Boolean]
+ def invisible?
+ case @type
+ when :loud; false
+ when :silent; true
+ else; style == :compressed
+ end
+ end
+
+ # Returns the number of lines in the comment.
+ #
+ # @return [Fixnum]
+ def lines
+ @value.inject(0) do |s, e|
+ next s + e.count("\n") if e.is_a?(String)
+ next s
+ end
+ end
+
+ private
+
+ def normalize_indentation(str)
+ ind = str.split("\n").inject(str[/^[ \t]*/].split("")) do |pre, line|
+ line[/^[ \t]*/].split("").zip(pre).inject([]) do |arr, (a, b)|
+ break arr if a != b
+ arr << a
+ end
+ end.join
+ str.gsub(/^#{ind}/, '')
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/content_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/content_node.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/lib/sass/tree/content_node.rb
rename to backends/css/gems/sass-3.4.9/lib/sass/tree/content_node.rb
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/css_import_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/css_import_node.rb
new file mode 100644
index 0000000..125ca13
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/css_import_node.rb
@@ -0,0 +1,60 @@
+module Sass::Tree
+ # A node representing an ` import` rule that's importing plain CSS.
+ #
+ # @see Sass::Tree
+ class CssImportNode < DirectiveNode
+ # The URI being imported, either as a plain string or an interpolated
+ # script string.
+ #
+ # @return [String, Sass::Script::Tree::Node]
+ attr_accessor :uri
+
+ # The text of the URI being imported after any interpolated SassScript has
+ # been resolved. Only set once {Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_uri
+
+ # The media query for this rule, interspersed with
+ # {Sass::Script::Tree::Node}s representing `#{}`-interpolation. Any adjacent
+ # strings will be merged together.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :query
+
+ # The media query for this rule, without any unresolved interpolation.
+ # It's only set once {Tree::Visitors::Perform} has been run.
+ #
+ # @return [Sass::Media::QueryList]
+ attr_accessor :resolved_query
+
+ # @param uri [String, Sass::Script::Tree::Node] See \{#uri}
+ # @param query [Array<String, Sass::Script::Tree::Node>] See \{#query}
+ def initialize(uri, query = [])
+ @uri = uri
+ @query = query
+ super('')
+ end
+
+ # @param uri [String] See \{#resolved_uri}
+ # @return [CssImportNode]
+ def self.resolved(uri)
+ node = new(uri)
+ node.resolved_uri = uri
+ node
+ end
+
+ # @see DirectiveNode#value
+ def value; raise NotImplementedError; end
+
+ # @see DirectiveNode#resolved_value
+ def resolved_value
+ @resolved_value ||=
+ begin
+ str = "@import #{resolved_uri}"
+ str << " #{resolved_query.to_css}" if resolved_query
+ str
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/debug_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/debug_node.rb
new file mode 100644
index 0000000..5cc2842
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/debug_node.rb
@@ -0,0 +1,18 @@
+module Sass
+ module Tree
+ # A dynamic node representing a Sass ` debug` statement.
+ #
+ # @see Sass::Tree
+ class DebugNode < Node
+ # The expression to print.
+ # @return [Script::Tree::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Tree::Node] The expression to print
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/directive_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/directive_node.rb
new file mode 100644
index 0000000..315bb70
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/directive_node.rb
@@ -0,0 +1,59 @@
+module Sass::Tree
+ # A static node representing an unprocessed Sass ` `-directive
+ # Directives known to Sass, like ` for` and ` debug`,
+ # are handled by their own nodes;
+ # only CSS directives like ` media` and ` font-face` become {DirectiveNode}s.
+ #
+ # ` import` and ` charset` are special cases;
+ # they become {ImportNode}s and {CharsetNode}s, respectively.
+ #
+ # @see Sass::Tree
+ class DirectiveNode < Node
+ # The text of the directive, ` ` and all, with interpolation included.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :value
+
+ # The text of the directive after any interpolated SassScript has been resolved.
+ # Only set once \{Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_value
+
+ # @see RuleNode#tabs
+ attr_accessor :tabs
+
+ # @see RuleNode#group_end
+ attr_accessor :group_end
+
+ # @param value [Array<String, Sass::Script::Tree::Node>] See \{#value}
+ def initialize(value)
+ @value = value
+ @tabs = 0
+ super()
+ end
+
+ # @param value [String] See \{#resolved_value}
+ # @return [DirectiveNode]
+ def self.resolved(value)
+ node = new([value])
+ node.resolved_value = value
+ node
+ end
+
+ # @return [String] The name of the directive, including ` `
+ def name
+ @name ||= value.first.gsub(/ .*$/, '')
+ end
+
+ # Strips out any vendor prefixes and downcases the directive name.
+ # @return [String] The normalized name of the directive.
+ def normalized_name
+ @normalized_name ||= name.gsub(/^(@)(?:-[a-zA-Z0-9]+-)?/, '\1').downcase
+ end
+
+ def bubbles?
+ has_children
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/each_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/each_node.rb
new file mode 100644
index 0000000..586cfa7
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/each_node.rb
@@ -0,0 +1,24 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A dynamic node representing a Sass ` each` loop.
+ #
+ # @see Sass::Tree
+ class EachNode < Node
+ # The names of the loop variables.
+ # @return [Array<String>]
+ attr_reader :vars
+
+ # The parse tree for the list.
+ # @return [Script::Tree::Node]
+ attr_accessor :list
+
+ # @param vars [Array<String>] The names of the loop variables
+ # @param list [Script::Tree::Node] The parse tree for the list
+ def initialize(vars, list)
+ @vars = vars
+ @list = list
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/error_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/error_node.rb
new file mode 100644
index 0000000..203fd62
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/error_node.rb
@@ -0,0 +1,18 @@
+module Sass
+ module Tree
+ # A dynamic node representing a Sass ` error` statement.
+ #
+ # @see Sass::Tree
+ class ErrorNode < Node
+ # The expression to print.
+ # @return [Script::Tree::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Tree::Node] The expression to print
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/extend_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/extend_node.rb
new file mode 100644
index 0000000..817c20c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/extend_node.rb
@@ -0,0 +1,43 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A static node representing an ` extend` directive.
+ #
+ # @see Sass::Tree
+ class ExtendNode < Node
+ # The parsed selector after interpolation has been resolved.
+ # Only set once {Tree::Visitors::Perform} has been run.
+ #
+ # @return [Selector::CommaSequence]
+ attr_accessor :resolved_selector
+
+ # The CSS selector to extend, interspersed with {Sass::Script::Tree::Node}s
+ # representing `#{}`-interpolation.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :selector
+
+ # The extended selector source range.
+ #
+ # @return [Sass::Source::Range]
+ attr_accessor :selector_source_range
+
+ # Whether the ` extend` is allowed to match no selectors or not.
+ #
+ # @return [Boolean]
+ def optional?; @optional; end
+
+ # @param selector [Array<String, Sass::Script::Tree::Node>]
+ # The CSS selector to extend,
+ # interspersed with {Sass::Script::Tree::Node}s
+ # representing `#{}`-interpolation.
+ # @param optional [Boolean] See \{ExtendNode#optional?}
+ # @param selector_source_range [Sass::Source::Range] The extended selector source range.
+ def initialize(selector, optional, selector_source_range)
+ @selector = selector
+ @optional = optional
+ @selector_source_range = selector_source_range
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/for_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/for_node.rb
new file mode 100644
index 0000000..da3f655
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/for_node.rb
@@ -0,0 +1,36 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A dynamic node representing a Sass ` for` loop.
+ #
+ # @see Sass::Tree
+ class ForNode < Node
+ # The name of the loop variable.
+ # @return [String]
+ attr_reader :var
+
+ # The parse tree for the initial expression.
+ # @return [Script::Tree::Node]
+ attr_accessor :from
+
+ # The parse tree for the final expression.
+ # @return [Script::Tree::Node]
+ attr_accessor :to
+
+ # Whether to include `to` in the loop or stop just before.
+ # @return [Boolean]
+ attr_reader :exclusive
+
+ # @param var [String] See \{#var}
+ # @param from [Script::Tree::Node] See \{#from}
+ # @param to [Script::Tree::Node] See \{#to}
+ # @param exclusive [Boolean] See \{#exclusive}
+ def initialize(var, from, to, exclusive)
+ @var = var
+ @from = from
+ @to = to
+ @exclusive = exclusive
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/function_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/function_node.rb
new file mode 100644
index 0000000..2ce47b7
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/function_node.rb
@@ -0,0 +1,39 @@
+module Sass
+ module Tree
+ # A dynamic node representing a function definition.
+ #
+ # @see Sass::Tree
+ class FunctionNode < Node
+ # The name of the function.
+ # @return [String]
+ attr_reader :name
+
+ # The arguments to the function. Each element is a tuple
+ # containing the variable for argument and the parse tree for
+ # the default value of the argument
+ #
+ # @return [Array<Script::Tree::Node>]
+ attr_accessor :args
+
+ # The splat argument for this function, if one exists.
+ #
+ # @return [Script::Tree::Node?]
+ attr_accessor :splat
+
+ # @param name [String] The function name
+ # @param args [Array<(Script::Tree::Node, Script::Tree::Node)>]
+ # The arguments for the function.
+ # @param splat [Script::Tree::Node] See \{#splat}
+ def initialize(name, args, splat)
+ @name = name
+ @args = args
+ @splat = splat
+ super()
+
+ if %w[and or not].include?(name)
+ raise Sass::SyntaxError.new("Invalid function name \"#{name}\".")
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/if_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/if_node.rb
new file mode 100644
index 0000000..ebfec7c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/if_node.rb
@@ -0,0 +1,52 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A dynamic node representing a Sass ` if` statement.
+ #
+ # {IfNode}s are a little odd, in that they also represent ` else` and ` else if`s.
+ # This is done as a linked list:
+ # each {IfNode} has a link (\{#else}) to the next {IfNode}.
+ #
+ # @see Sass::Tree
+ class IfNode < Node
+ # The conditional expression.
+ # If this is nil, this is an ` else` node, not an ` else if`.
+ #
+ # @return [Script::Expr]
+ attr_accessor :expr
+
+ # The next {IfNode} in the if-else list, or `nil`.
+ #
+ # @return [IfNode]
+ attr_accessor :else
+
+ # @param expr [Script::Expr] See \{#expr}
+ def initialize(expr)
+ @expr = expr
+ @last_else = self
+ super()
+ end
+
+ # Append an ` else` node to the end of the list.
+ #
+ # @param node [IfNode] The ` else` node to append
+ def add_else(node)
+ @last_else.else = node
+ @last_else = node
+ end
+
+ def _dump(f)
+ Marshal.dump([expr, self.else, children])
+ end
+
+ def self._load(data)
+ expr, else_, children = Marshal.load(data)
+ node = IfNode.new(expr)
+ node.else = else_
+ node.children = children
+ node.instance_variable_set('@last_else',
+ node.else ? node.else.instance_variable_get('@last_else') : node)
+ node
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/import_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/import_node.rb
new file mode 100644
index 0000000..01dc0a9
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/import_node.rb
@@ -0,0 +1,74 @@
+module Sass
+ module Tree
+ # A static node that wraps the {Sass::Tree} for an ` import`ed file.
+ # It doesn't have a functional purpose other than to add the ` import`ed file
+ # to the backtrace if an error occurs.
+ class ImportNode < RootNode
+ # The name of the imported file as it appears in the Sass document.
+ #
+ # @return [String]
+ attr_reader :imported_filename
+
+ # Sets the imported file.
+ attr_writer :imported_file
+
+ # @param imported_filename [String] The name of the imported file
+ def initialize(imported_filename)
+ @imported_filename = imported_filename
+ super(nil)
+ end
+
+ def invisible?; to_s.empty?; end
+
+ # Returns the imported file.
+ #
+ # @return [Sass::Engine]
+ # @raise [Sass::SyntaxError] If no file could be found to import.
+ def imported_file
+ @imported_file ||= import
+ end
+
+ # Returns whether or not this import should emit a CSS @import declaration
+ #
+ # @return [Boolean] Whether or not this is a simple CSS @import declaration.
+ def css_import?
+ if @imported_filename =~ /\.css$/
+ @imported_filename
+ elsif imported_file.is_a?(String) && imported_file =~ /\.css$/
+ imported_file
+ end
+ end
+
+ private
+
+ def import
+ paths = @options[:load_paths]
+
+ if @options[:importer]
+ f = @options[:importer].find_relative(
+ @imported_filename, @options[:filename], options_for_importer)
+ return f if f
+ end
+
+ paths.each do |p|
+ f = p.find(@imported_filename, options_for_importer)
+ return f if f
+ end
+
+ message = "File to import not found or unreadable: #{ imported_filename} \n"
+ if paths.size == 1
+ message << "Load path: #{paths.first}"
+ else
+ message << "Load paths:\n " << paths.join("\n ")
+ end
+ raise SyntaxError.new(message)
+ rescue SyntaxError => e
+ raise SyntaxError.new(e.message, :line => line, :filename => @filename)
+ end
+
+ def options_for_importer
+ @options.merge(:_from_import_node => true)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/keyframe_rule_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/keyframe_rule_node.rb
new file mode 100644
index 0000000..9f75f94
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/keyframe_rule_node.rb
@@ -0,0 +1,15 @@
+module Sass::Tree
+ class KeyframeRuleNode < Node
+ # The text of the directive after any interpolated SassScript has been resolved.
+ # Since this is only a static node, this is the only value property.
+ #
+ # @return [String]
+ attr_accessor :resolved_value
+
+ # @param resolved_value [String] See \{#resolved_value}
+ def initialize(resolved_value)
+ @resolved_value = resolved_value
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/media_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/media_node.rb
new file mode 100644
index 0000000..3178de0
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/media_node.rb
@@ -0,0 +1,48 @@
+module Sass::Tree
+ # A static node representing a ` media` rule.
+ # ` media` rules behave differently from other directives
+ # in that when they're nested within rules,
+ # they bubble up to top-level.
+ #
+ # @see Sass::Tree
+ class MediaNode < DirectiveNode
+ # TODO: parse and cache the query immediately if it has no dynamic elements
+
+ # The media query for this rule, interspersed with {Sass::Script::Tree::Node}s
+ # representing `#{}`-interpolation. Any adjacent strings will be merged
+ # together.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :query
+
+ # The media query for this rule, without any unresolved interpolation. It's
+ # only set once {Tree::Visitors::Perform} has been run.
+ #
+ # @return [Sass::Media::QueryList]
+ attr_accessor :resolved_query
+
+ # @param query [Array<String, Sass::Script::Tree::Node>] See \{#query}
+ def initialize(query)
+ @query = query
+ super('')
+ end
+
+ # @see DirectiveNode#value
+ def value; raise NotImplementedError; end
+
+ # @see DirectiveNode#name
+ def name; '@media'; end
+
+ # @see DirectiveNode#resolved_value
+ def resolved_value
+ @resolved_value ||= "@media #{resolved_query.to_css}"
+ end
+
+ # True when the directive has no visible children.
+ #
+ # @return [Boolean]
+ def invisible?
+ children.all? {|c| c.invisible?}
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_def_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_def_node.rb
new file mode 100644
index 0000000..9ed8bfb
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_def_node.rb
@@ -0,0 +1,38 @@
+module Sass
+ module Tree
+ # A dynamic node representing a mixin definition.
+ #
+ # @see Sass::Tree
+ class MixinDefNode < Node
+ # The mixin name.
+ # @return [String]
+ attr_reader :name
+
+ # The arguments for the mixin.
+ # Each element is a tuple containing the variable for argument
+ # and the parse tree for the default value of the argument.
+ #
+ # @return [Array<(Script::Tree::Node, Script::Tree::Node)>]
+ attr_accessor :args
+
+ # The splat argument for this mixin, if one exists.
+ #
+ # @return [Script::Tree::Node?]
+ attr_accessor :splat
+
+ # Whether the mixin uses ` content` Set during the nesting check phase.
+ # @return [Boolean]
+ attr_accessor :has_content
+
+ # @param name [String] The mixin name
+ # @param args [Array<(Script::Tree::Node, Script::Tree::Node)>] See \{#args}
+ # @param splat [Script::Tree::Node] See \{#splat}
+ def initialize(name, args, splat)
+ @name = name
+ @args = args
+ @splat = splat
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_node.rb
new file mode 100644
index 0000000..48592c1
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/mixin_node.rb
@@ -0,0 +1,52 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A static node representing a mixin include.
+ # When in a static tree, the sole purpose is to wrap exceptions
+ # to add the mixin to the backtrace.
+ #
+ # @see Sass::Tree
+ class MixinNode < Node
+ # The name of the mixin.
+ # @return [String]
+ attr_reader :name
+
+ # The arguments to the mixin.
+ # @return [Array<Script::Tree::Node>]
+ attr_accessor :args
+
+ # A hash from keyword argument names to values.
+ # @return [Sass::Util::NormalizedMap<Script::Tree::Node>]
+ attr_accessor :keywords
+
+ # The first splat argument for this mixin, if one exists.
+ #
+ # This could be a list of positional arguments, a map of keyword
+ # arguments, or an arglist containing both.
+ #
+ # @return [Node?]
+ attr_accessor :splat
+
+ # The second splat argument for this mixin, if one exists.
+ #
+ # If this exists, it's always a map of keyword arguments, and
+ # \{#splat} is always either a list or an arglist.
+ #
+ # @return [Node?]
+ attr_accessor :kwarg_splat
+
+ # @param name [String] The name of the mixin
+ # @param args [Array<Script::Tree::Node>] See \{#args}
+ # @param splat [Script::Tree::Node] See \{#splat}
+ # @param kwarg_splat [Script::Tree::Node] See \{#kwarg_splat}
+ # @param keywords [Sass::Util::NormalizedMap<Script::Tree::Node>] See \{#keywords}
+ def initialize(name, args, keywords, splat, kwarg_splat)
+ @name = name
+ @args = args
+ @keywords = keywords
+ @splat = splat
+ @kwarg_splat = kwarg_splat
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/node.rb
new file mode 100644
index 0000000..3ebb5e8
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/node.rb
@@ -0,0 +1,238 @@
+module Sass
+ # A namespace for nodes in the Sass parse tree.
+ #
+ # The Sass parse tree has three states: dynamic, static Sass, and static CSS.
+ #
+ # When it's first parsed, a Sass document is in the dynamic state.
+ # It has nodes for mixin definitions and ` for` loops and so forth,
+ # in addition to nodes for CSS rules and properties.
+ # Nodes that only appear in this state are called **dynamic nodes**.
+ #
+ # {Tree::Visitors::Perform} creates a static Sass tree, which is
+ # different. It still has nodes for CSS rules and properties but it
+ # doesn't have any dynamic-generation-related nodes. The nodes in
+ # this state are in a similar structure to the Sass document: rules
+ # and properties are nested beneath one another, although the
+ # {Tree::RuleNode} selectors are already in their final state. Nodes
+ # that can be in this state or in the dynamic state are called
+ # **static nodes**; nodes that can only be in this state are called
+ # **solely static nodes**.
+ #
+ # {Tree::Visitors::Cssize} is then used to create a static CSS tree.
+ # This is like a static Sass tree,
+ # but the structure exactly mirrors that of the generated CSS.
+ # Rules and properties can't be nested beneath one another in this state.
+ #
+ # Finally, {Tree::Visitors::ToCss} can be called on a static CSS tree
+ # to get the actual CSS code as a string.
+ module Tree
+ # The abstract superclass of all parse-tree nodes.
+ class Node
+ include Enumerable
+
+ def self.inherited(base)
+ node_name = base.name.gsub(/.*::(.*?)Node$/, '\\1').downcase
+ base.instance_eval <<-METHODS
+ # @return [Symbol] The name that is used for this node when visiting.
+ def node_name
+ :#{node_name}
+ end
+
+ # @return [Symbol] The method that is used on the visitor to visit nodes of this type.
+ def visit_method
+ :visit_#{node_name}
+ end
+
+ # @return [Symbol] The method name that determines if the parent is invalid.
+ def invalid_child_method_name
+ :"invalid_#{node_name}_child?"
+ end
+
+ # @return [Symbol] The method name that determines if the node is an invalid parent.
+ def invalid_parent_method_name
+ :"invalid_#{node_name}_parent?"
+ end
+ METHODS
+ end
+
+ # The child nodes of this node.
+ #
+ # @return [Array<Tree::Node>]
+ attr_reader :children
+
+ # Whether or not this node has child nodes.
+ # This may be true even when \{#children} is empty,
+ # in which case this node has an empty block (e.g. `{}`).
+ #
+ # @return [Boolean]
+ attr_accessor :has_children
+
+ # The line of the document on which this node appeared.
+ #
+ # @return [Fixnum]
+ attr_accessor :line
+
+ # The source range in the document on which this node appeared.
+ #
+ # @return [Sass::Source::Range]
+ attr_accessor :source_range
+
+ # The name of the document on which this node appeared.
+ #
+ # @return [String]
+ attr_writer :filename
+
+ # The options hash for the node.
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ #
+ # @return [{Symbol => Object}]
+ attr_reader :options
+
+ def initialize
+ @children = []
+ end
+
+ # Sets the options hash for the node and all its children.
+ #
+ # @param options [{Symbol => Object}] The options
+ # @see #options
+ def options=(options)
+ Sass::Tree::Visitors::SetOptions.visit(self, options)
+ end
+
+ # @private
+ def children=(children)
+ self.has_children ||= !children.empty?
+ @children = children
+ end
+
+ # The name of the document on which this node appeared.
+ #
+ # @return [String]
+ def filename
+ @filename || (@options && @options[:filename])
+ end
+
+ # Appends a child to the node.
+ #
+ # @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
+ # @raise [Sass::SyntaxError] if `child` is invalid
+ def <<(child)
+ return if child.nil?
+ if child.is_a?(Array)
+ child.each {|c| self << c}
+ else
+ self.has_children = true
+ @children << child
+ end
+ end
+
+ # Compares this node and another object (only other {Tree::Node}s will be equal).
+ # This does a structural comparison;
+ # if the contents of the nodes and all the child nodes are equivalent,
+ # then the nodes are as well.
+ #
+ # Only static nodes need to override this.
+ #
+ # @param other [Object] The object to compare with
+ # @return [Boolean] Whether or not this node and the other object
+ # are the same
+ # @see Sass::Tree
+ def ==(other)
+ self.class == other.class && other.children == children
+ end
+
+ # True if \{#to\_s} will return `nil`;
+ # that is, if the node shouldn't be rendered.
+ # Should only be called in a static tree.
+ #
+ # @return [Boolean]
+ def invisible?; false; end
+
+ # The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ #
+ # @return [Symbol]
+ def style
+ @options[:style]
+ end
+
+ # Computes the CSS corresponding to this static CSS tree.
+ #
+ # @return [String] The resulting CSS
+ # @see Sass::Tree
+ def css
+ Sass::Tree::Visitors::ToCss.new.visit(self)
+ end
+
+ # Computes the CSS corresponding to this static CSS tree, along with
+ # the respective source map.
+ #
+ # @return [(String, Sass::Source::Map)] The resulting CSS and the source map
+ # @see Sass::Tree
+ def css_with_sourcemap
+ visitor = Sass::Tree::Visitors::ToCss.new(:build_source_mapping)
+ result = visitor.visit(self)
+ return result, visitor.source_mapping
+ end
+
+ # Returns a representation of the node for debugging purposes.
+ #
+ # @return [String]
+ def inspect
+ return self.class.to_s unless has_children
+ "(#{self.class} #{children.map {|c| c.inspect}.join(' ')})"
+ end
+
+ # Iterates through each node in the tree rooted at this node
+ # in a pre-order walk.
+ #
+ # @yield node
+ # @yieldparam node [Node] a node in the tree
+ def each
+ yield self
+ children.each {|c| c.each {|n| yield n}}
+ end
+
+ # Converts a node to Sass code that will generate it.
+ #
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
+ # @return [String] The Sass code corresponding to the node
+ def to_sass(options = {})
+ Sass::Tree::Visitors::Convert.visit(self, options, :sass)
+ end
+
+ # Converts a node to SCSS code that will generate it.
+ #
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
+ # @return [String] The Sass code corresponding to the node
+ def to_scss(options = {})
+ Sass::Tree::Visitors::Convert.visit(self, options, :scss)
+ end
+
+ # Return a deep clone of this node.
+ # The child nodes are cloned, but options are not.
+ #
+ # @return [Node]
+ def deep_copy
+ Sass::Tree::Visitors::DeepCopy.visit(self)
+ end
+
+ # Whether or not this node bubbles up through RuleNodes.
+ #
+ # @return [Boolean]
+ def bubbles?
+ false
+ end
+
+ protected
+
+ # @see Sass::Shared.balance
+ # @raise [Sass::SyntaxError] if the brackets aren't balanced
+ def balance(*args)
+ res = Sass::Shared.balance(*args)
+ return res if res
+ raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/prop_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/prop_node.rb
new file mode 100644
index 0000000..f157e41
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/prop_node.rb
@@ -0,0 +1,171 @@
+module Sass::Tree
+ # A static node representing a CSS property.
+ #
+ # @see Sass::Tree
+ class PropNode < Node
+ # The name of the property,
+ # interspersed with {Sass::Script::Tree::Node}s
+ # representing `#{}`-interpolation.
+ # Any adjacent strings will be merged together.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :name
+
+ # The name of the property
+ # after any interpolated SassScript has been resolved.
+ # Only set once \{Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_name
+
+ # The value of the property.
+ #
+ # @return [Sass::Script::Tree::Node]
+ attr_accessor :value
+
+ # The value of the property
+ # after any interpolated SassScript has been resolved.
+ # Only set once \{Tree::Visitors::Perform} has been run.
+ #
+ # @return [String]
+ attr_accessor :resolved_value
+
+ # How deep this property is indented
+ # relative to a normal property.
+ # This is only greater than 0 in the case that:
+ #
+ # * This node is in a CSS tree
+ # * The style is :nested
+ # * This is a child property of another property
+ # * The parent property has a value, and thus will be rendered
+ #
+ # @return [Fixnum]
+ attr_accessor :tabs
+
+ # The source range in which the property name appears.
+ #
+ # @return [Sass::Source::Range]
+ attr_accessor :name_source_range
+
+ # The source range in which the property value appears.
+ #
+ # @return [Sass::Source::Range]
+ attr_accessor :value_source_range
+
+ # @param name [Array<String, Sass::Script::Tree::Node>] See \{#name}
+ # @param value [Sass::Script::Tree::Node] See \{#value}
+ # @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
+ # `:old` if it uses `:a b`-style syntax
+ def initialize(name, value, prop_syntax)
+ @name = Sass::Util.strip_string_array(
+ Sass::Util.merge_adjacent_strings(name))
+ @value = value
+ @tabs = 0
+ @prop_syntax = prop_syntax
+ super()
+ end
+
+ # Compares the names and values of two properties.
+ #
+ # @param other [Object] The object to compare with
+ # @return [Boolean] Whether or not this node and the other object
+ # are the same
+ def ==(other)
+ self.class == other.class && name == other.name && value == other.value && super
+ end
+
+ # Returns a appropriate message indicating how to escape pseudo-class selectors.
+ # This only applies for old-style properties with no value,
+ # so returns the empty string if this is new-style.
+ #
+ # @return [String] The message
+ def pseudo_class_selector_message
+ if @prop_syntax == :new ||
+ !value.is_a?(Sass::Script::Tree::Literal) ||
+ !value.value.is_a?(Sass::Script::Value::String) ||
+ !value.value.value.empty?
+ return ""
+ end
+
+ "\nIf #{declaration.dump} should be a selector, use \"\\#{declaration}\" instead."
+ end
+
+ # Computes the Sass or SCSS code for the variable declaration.
+ # This is like \{#to\_scss} or \{#to\_sass},
+ # except it doesn't print any child properties or a trailing semicolon.
+ #
+ # @param opts [{Symbol => Object}] The options hash for the tree.
+ # @param fmt [Symbol] `:scss` or `:sass`.
+ def declaration(opts = {:old => @prop_syntax == :old}, fmt = :sass)
+ name = self.name.map {|n| n.is_a?(String) ? n : n.to_sass(opts)}.join
+ if name[0] == ?:
+ raise Sass::SyntaxError.new("The \"#{name}: #{self.class.val_to_sass(value, opts)}\"" +
+ " hack is not allowed in the Sass indented syntax")
+ end
+
+ old = opts[:old] && fmt == :sass
+ initial = old ? ':' : ''
+ mid = old ? '' : ':'
+ "#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip
+ end
+
+ # A property node is invisible if its value is empty.
+ #
+ # @return [Boolean]
+ def invisible?
+ resolved_value.empty?
+ end
+
+ private
+
+ def check!
+ if @options[:property_syntax] && @options[:property_syntax] != @prop_syntax
+ raise Sass::SyntaxError.new(
+ "Illegal property syntax: can't use #{ prop_syntax} syntax when " +
+ ":property_syntax => #{ options[:property_syntax].inspect} is set.")
+ end
+ end
+
+ class << self
+ # @private
+ def val_to_sass(value, opts)
+ val_to_sass_comma(value, opts).to_sass(opts)
+ end
+
+ private
+
+ def val_to_sass_comma(node, opts)
+ return node unless node.is_a?(Sass::Script::Tree::Operation)
+ return val_to_sass_concat(node, opts) unless node.operator == :comma
+
+ Sass::Script::Tree::Operation.new(
+ val_to_sass_concat(node.operand1, opts),
+ val_to_sass_comma(node.operand2, opts),
+ node.operator)
+ end
+
+ def val_to_sass_concat(node, opts)
+ return node unless node.is_a?(Sass::Script::Tree::Operation)
+ return val_to_sass_div(node, opts) unless node.operator == :space
+
+ Sass::Script::Tree::Operation.new(
+ val_to_sass_div(node.operand1, opts),
+ val_to_sass_concat(node.operand2, opts),
+ node.operator)
+ end
+
+ def val_to_sass_div(node, opts)
+ unless node.is_a?(Sass::Script::Tree::Operation) && node.operator == :div &&
+ node.operand1.is_a?(Sass::Script::Tree::Literal) &&
+ node.operand1.value.is_a?(Sass::Script::Value::Number) &&
+ node.operand2.is_a?(Sass::Script::Tree::Literal) &&
+ node.operand2.value.is_a?(Sass::Script::Value::Number) &&
+ (!node.operand1.value.original || !node.operand2.value.original)
+ return node
+ end
+
+ Sass::Script::Value::String.new("(#{node.to_sass(opts)})")
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/return_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/return_node.rb
new file mode 100644
index 0000000..3056406
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/return_node.rb
@@ -0,0 +1,19 @@
+module Sass
+ module Tree
+ # A dynamic node representing returning from a function.
+ #
+ # @see Sass::Tree
+ class ReturnNode < Node
+ # The expression to return.
+ #
+ # @return [Script::Tree::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Tree::Node] The expression to return
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/root_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/root_node.rb
new file mode 100644
index 0000000..1f02cbd
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/root_node.rb
@@ -0,0 +1,44 @@
+module Sass
+ module Tree
+ # A static node that is the root node of the Sass document.
+ class RootNode < Node
+ # The Sass template from which this node was created
+ #
+ # @param template [String]
+ attr_reader :template
+
+ # @param template [String] The Sass template from which this node was created
+ def initialize(template)
+ super()
+ @template = template
+ end
+
+ # Runs the dynamic Sass code and computes the CSS for the tree.
+ #
+ # @return [String] The compiled CSS.
+ def render
+ css_tree.css
+ end
+
+ # Runs the dynamic Sass code and computes the CSS for the tree, along with
+ # the sourcemap.
+ #
+ # @return [(String, Sass::Source::Map)] The compiled CSS, as well as
+ # the source map. @see #render
+ def render_with_sourcemap
+ css_tree.css_with_sourcemap
+ end
+
+ private
+
+ def css_tree
+ Visitors::CheckNesting.visit(self)
+ result = Visitors::Perform.visit(self)
+ Visitors::CheckNesting.visit(result) # Check again to validate mixins
+ result, extends = Visitors::Cssize.visit(result)
+ Visitors::Extend.visit(result, extends)
+ result
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/rule_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/rule_node.rb
new file mode 100644
index 0000000..5e0883b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/rule_node.rb
@@ -0,0 +1,145 @@
+require 'pathname'
+
+module Sass::Tree
+ # A static node representing a CSS rule.
+ #
+ # @see Sass::Tree
+ class RuleNode < Node
+ # The character used to include the parent selector
+ PARENT = '&'
+
+ # The CSS selector for this rule,
+ # interspersed with {Sass::Script::Tree::Node}s
+ # representing `#{}`-interpolation.
+ # Any adjacent strings will be merged together.
+ #
+ # @return [Array<String, Sass::Script::Tree::Node>]
+ attr_accessor :rule
+
+ # The CSS selector for this rule, without any unresolved
+ # interpolation but with parent references still intact. It's only
+ # guaranteed to be set once {Tree::Visitors::Perform} has been
+ # run, but it may be set before then for optimization reasons.
+ #
+ # @return [Selector::CommaSequence]
+ attr_accessor :parsed_rules
+
+ # The CSS selector for this rule, without any unresolved
+ # interpolation or parent references. It's only set once
+ # {Tree::Visitors::Perform} has been run.
+ #
+ # @return [Selector::CommaSequence]
+ attr_accessor :resolved_rules
+
+ # How deep this rule is indented
+ # relative to a base-level rule.
+ # This is only greater than 0 in the case that:
+ #
+ # * This node is in a CSS tree
+ # * The style is :nested
+ # * This is a child rule of another rule
+ # * The parent rule has properties, and thus will be rendered
+ #
+ # @return [Fixnum]
+ attr_accessor :tabs
+
+ # The entire selector source range for this rule.
+ # @return [Sass::Source::Range]
+ attr_accessor :selector_source_range
+
+ # Whether or not this rule is the last rule in a nested group.
+ # This is only set in a CSS tree.
+ #
+ # @return [Boolean]
+ attr_accessor :group_end
+
+ # The stack trace.
+ # This is only readable in a CSS tree as it is written during the perform step
+ # and only when the :trace_selectors option is set.
+ #
+ # @return [String]
+ attr_accessor :stack_trace
+
+ # @param rule [Array<String, Sass::Script::Tree::Node>, Sass::Selector::CommaSequence]
+ # The CSS rule, either unparsed or parsed.
+ # @param selector_source_range [Sass::Source::Range]
+ def initialize(rule, selector_source_range = nil)
+ if rule.is_a?(Sass::Selector::CommaSequence)
+ @rule = [rule.to_s]
+ @parsed_rules = rule
+ else
+ merged = Sass::Util.merge_adjacent_strings(rule)
+ @rule = Sass::Util.strip_string_array(merged)
+ try_to_parse_non_interpolated_rules
+ end
+ @selector_source_range = selector_source_range
+ @tabs = 0
+ super()
+ end
+
+ # If we've precached the parsed selector, set the line on it, too.
+ def line=(line)
+ @parsed_rules.line = line if @parsed_rules
+ super
+ end
+
+ # If we've precached the parsed selector, set the filename on it, too.
+ def filename=(filename)
+ @parsed_rules.filename = filename if @parsed_rules
+ super
+ end
+
+ # Compares the contents of two rules.
+ #
+ # @param other [Object] The object to compare with
+ # @return [Boolean] Whether or not this node and the other object
+ # are the same
+ def ==(other)
+ self.class == other.class && rule == other.rule && super
+ end
+
+ # Adds another {RuleNode}'s rules to this one's.
+ #
+ # @param node [RuleNode] The other node
+ def add_rules(node)
+ @rule = Sass::Util.strip_string_array(
+ Sass::Util.merge_adjacent_strings(@rule + ["\n"] + node.rule))
+ try_to_parse_non_interpolated_rules
+ end
+
+ # @return [Boolean] Whether or not this rule is continued on the next line
+ def continued?
+ last = @rule.last
+ last.is_a?(String) && last[-1] == ?,
+ end
+
+ # A hash that will be associated with this rule in the CSS document
+ # if the {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} is enabled.
+ # This data is used by e.g. [the FireSass Firebug
+ # extension](https://addons.mozilla.org/en-US/firefox/addon/103988).
+ #
+ # @return [{#to_s => #to_s}]
+ def debug_info
+ {:filename => filename && ("file://" + Sass::Util.escape_uri(File.expand_path(filename))),
+ :line => line}
+ end
+
+ # A rule node is invisible if it has only placeholder selectors.
+ def invisible?
+ resolved_rules.members.all? {|seq| seq.has_placeholder?}
+ end
+
+ private
+
+ def try_to_parse_non_interpolated_rules
+ if @rule.all? {|t| t.kind_of?(String)}
+ # We don't use real filename/line info because we don't have it yet.
+ # When we get it, we'll set it on the parsed rules if possible.
+ parser = Sass::SCSS::StaticParser.new(@rule.join.strip, nil, nil, 1)
+ # rubocop:disable RescueModifier
+ @parsed_rules = parser.parse_selector rescue nil
+ # rubocop:enable RescueModifier
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/supports_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/supports_node.rb
new file mode 100644
index 0000000..1a2f04b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/supports_node.rb
@@ -0,0 +1,38 @@
+module Sass::Tree
+ # A static node representing a ` supports` rule.
+ #
+ # @see Sass::Tree
+ class SupportsNode < DirectiveNode
+ # The name, which may include a browser prefix.
+ #
+ # @return [String]
+ attr_accessor :name
+
+ # The supports condition.
+ #
+ # @return [Sass::Supports::Condition]
+ attr_accessor :condition
+
+ # @param condition [Sass::Supports::Condition] See \{#condition}
+ def initialize(name, condition)
+ @name = name
+ @condition = condition
+ super('')
+ end
+
+ # @see DirectiveNode#value
+ def value; raise NotImplementedError; end
+
+ # @see DirectiveNode#resolved_value
+ def resolved_value
+ @resolved_value ||= "@#{name} #{condition.to_css}"
+ end
+
+ # True when the directive has no visible children.
+ #
+ # @return [Boolean]
+ def invisible?
+ children.all? {|c| c.invisible?}
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/trace_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/trace_node.rb
new file mode 100644
index 0000000..2c71e88
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/trace_node.rb
@@ -0,0 +1,33 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A solely static node left over after a mixin include or @content has been performed.
+ # Its sole purpose is to wrap exceptions to add to the backtrace.
+ #
+ # @see Sass::Tree
+ class TraceNode < Node
+ # The name of the trace entry to add.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # @param name [String] The name of the trace entry to add.
+ def initialize(name)
+ @name = name
+ self.has_children = true
+ super()
+ end
+
+ # Initializes this node from an existing node.
+ # @param name [String] The name of the trace entry to add.
+ # @param node [Node] The node to copy information from.
+ # @return [TraceNode]
+ def self.from_node(name, node)
+ trace = new(name)
+ trace.line = node.line
+ trace.filename = node.filename
+ trace.options = node.options
+ trace
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/variable_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/variable_node.rb
new file mode 100644
index 0000000..2c0ed55
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/variable_node.rb
@@ -0,0 +1,36 @@
+module Sass
+ module Tree
+ # A dynamic node representing a variable definition.
+ #
+ # @see Sass::Tree
+ class VariableNode < Node
+ # The name of the variable.
+ # @return [String]
+ attr_reader :name
+
+ # The parse tree for the variable value.
+ # @return [Script::Tree::Node]
+ attr_accessor :expr
+
+ # Whether this is a guarded variable assignment (`!default`).
+ # @return [Boolean]
+ attr_reader :guarded
+
+ # Whether this is a global variable assignment (`!global`).
+ # @return [Boolean]
+ attr_reader :global
+
+ # @param name [String] The name of the variable
+ # @param expr [Script::Tree::Node] See \{#expr}
+ # @param guarded [Boolean] See \{#guarded}
+ # @param global [Boolean] See \{#global}
+ def initialize(name, expr, guarded, global)
+ @name = name
+ @expr = expr
+ @guarded = guarded
+ @global = global
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/base.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/base.rb
new file mode 100644
index 0000000..2c8e134
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/base.rb
@@ -0,0 +1,72 @@
+# Visitors are used to traverse the Sass parse tree.
+# Visitors should extend {Visitors::Base},
+# which provides a small amount of scaffolding for traversal.
+module Sass::Tree::Visitors
+ # The abstract base class for Sass visitors.
+ # Visitors should extend this class,
+ # then implement `visit_*` methods for each node they care about
+ # (e.g. `visit_rule` for {RuleNode} or `visit_for` for {ForNode}).
+ # These methods take the node in question as argument.
+ # They may `yield` to visit the child nodes of the current node.
+ #
+ # *Note*: due to the unusual nature of {Sass::Tree::IfNode},
+ # special care must be taken to ensure that it is properly handled.
+ # In particular, there is no built-in scaffolding
+ # for dealing with the return value of ` else` nodes.
+ #
+ # @abstract
+ class Base
+ # Runs the visitor on a tree.
+ #
+ # @param root [Tree::Node] The root node of the Sass tree.
+ # @return [Object] The return value of \{#visit} for the root node.
+ def self.visit(root)
+ new.send(:visit, root)
+ end
+
+ protected
+
+ # Runs the visitor on the given node.
+ # This can be overridden by subclasses that need to do something for each node.
+ #
+ # @param node [Tree::Node] The node to visit.
+ # @return [Object] The return value of the `visit_*` method for this node.
+ def visit(node)
+ if respond_to?(node.class.visit_method, true)
+ send(node.class.visit_method, node) {visit_children(node)}
+ else
+ visit_children(node)
+ end
+ end
+
+ # Visit the child nodes for a given node.
+ # This can be overridden by subclasses that need to do something
+ # with the child nodes' return values.
+ #
+ # This method is run when `visit_*` methods `yield`,
+ # and its return value is returned from the `yield`.
+ #
+ # @param parent [Tree::Node] The parent node of the children to visit.
+ # @return [Array<Object>] The return values of the `visit_*` methods for the children.
+ def visit_children(parent)
+ parent.children.map {|c| visit(c)}
+ end
+
+ # Returns the name of a node as used in the `visit_*` method.
+ #
+ # @param [Tree::Node] node The node.
+ # @return [String] The name.
+ def self.node_name(node)
+ Sass::Util.deprecated(self, "Call node.class.node_name instead.")
+ node.class.node_name
+ end
+
+ # `yield`s, then runs the visitor on the ` else` clause if the node has one.
+ # This exists to ensure that the contents of the ` else` clause get visited.
+ def visit_if(node)
+ yield
+ visit(node.else) if node.else
+ node
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/check_nesting.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/check_nesting.rb
new file mode 100644
index 0000000..cbead20
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/check_nesting.rb
@@ -0,0 +1,177 @@
+# A visitor for checking that all nodes are properly nested.
+class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base
+ protected
+
+ def initialize
+ @parents = []
+ end
+
+ def visit(node)
+ if (error = @parent && (
+ try_send(@parent.class.invalid_child_method_name, @parent, node) ||
+ try_send(node.class.invalid_parent_method_name, @parent, node)))
+ raise Sass::SyntaxError.new(error)
+ end
+ super
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ CONTROL_NODES = [Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode,
+ Sass::Tree::WhileNode, Sass::Tree::TraceNode]
+ SCRIPT_NODES = [Sass::Tree::ImportNode] + CONTROL_NODES
+ def visit_children(parent)
+ old_parent = @parent
+
+ # When checking a static tree, resolve at-roots to be sure they won't send
+ # nodes where they don't belong.
+ if parent.is_a?(Sass::Tree::AtRootNode) && parent.resolved_value
+ old_parents = @parents
+ @parents = @parents.reject {|p| parent.exclude_node?(p)}
+ @parent = Sass::Util.enum_with_index(@parents.reverse).
+ find {|p, i| !transparent_parent?(p, @parents[-i - 2])}.first
+
+ begin
+ return super
+ ensure
+ @parents = old_parents
+ @parent = old_parent
+ end
+ end
+
+ unless transparent_parent?(parent, old_parent)
+ @parent = parent
+ end
+
+ @parents.push parent
+ begin
+ super
+ ensure
+ @parent = old_parent
+ @parents.pop
+ end
+ end
+
+ def visit_root(node)
+ yield
+ rescue Sass::SyntaxError => e
+ e.sass_template ||= node.template
+ raise e
+ end
+
+ def visit_import(node)
+ yield
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.children.first.filename)
+ e.add_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ def visit_mixindef(node)
+ @current_mixin_def, old_mixin_def = node, @current_mixin_def
+ yield
+ ensure
+ @current_mixin_def = old_mixin_def
+ end
+
+ def invalid_content_parent?(parent, child)
+ if @current_mixin_def
+ @current_mixin_def.has_content = true
+ nil
+ else
+ "@content may only be used within a mixin."
+ end
+ end
+
+ def invalid_charset_parent?(parent, child)
+ "@charset may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode)
+ end
+
+ VALID_EXTEND_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
+ def invalid_extend_parent?(parent, child)
+ unless is_any_of?(parent, VALID_EXTEND_PARENTS)
+ return "Extend directives may only be used within rules."
+ end
+ end
+
+ INVALID_IMPORT_PARENTS = CONTROL_NODES +
+ [Sass::Tree::MixinDefNode, Sass::Tree::MixinNode]
+ def invalid_import_parent?(parent, child)
+ unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
+ return "Import directives may not be used within control directives or mixins."
+ end
+ return if parent.is_a?(Sass::Tree::RootNode)
+ return "CSS import directives may only be used at the root of a document." if child.css_import?
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => child.imported_file.options[:filename])
+ e.add_backtrace(:filename => child.filename, :line => child.line)
+ raise e
+ end
+
+ def invalid_mixindef_parent?(parent, child)
+ unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
+ return "Mixins may not be defined within control directives or other mixins."
+ end
+ end
+
+ def invalid_function_parent?(parent, child)
+ unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty?
+ return "Functions may not be defined within control directives or other mixins."
+ end
+ end
+
+ VALID_FUNCTION_CHILDREN = [
+ Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode,
+ Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::ErrorNode
+ ] + CONTROL_NODES
+ def invalid_function_child?(parent, child)
+ unless is_any_of?(child, VALID_FUNCTION_CHILDREN)
+ "Functions can only contain variable declarations and control directives."
+ end
+ end
+
+ VALID_PROP_CHILDREN = CONTROL_NODES + [Sass::Tree::CommentNode,
+ Sass::Tree::PropNode,
+ Sass::Tree::MixinNode]
+ def invalid_prop_child?(parent, child)
+ unless is_any_of?(child, VALID_PROP_CHILDREN)
+ "Illegal nesting: Only properties may be nested beneath properties."
+ end
+ end
+
+ VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::KeyframeRuleNode, Sass::Tree::PropNode,
+ Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode, Sass::Tree::MixinNode]
+ def invalid_prop_parent?(parent, child)
+ unless is_any_of?(parent, VALID_PROP_PARENTS)
+ "Properties are only allowed within rules, directives, mixin includes, or other properties." +
+ child.pseudo_class_selector_message
+ end
+ end
+
+ def invalid_return_parent?(parent, child)
+ "@return may only be used within a function." unless parent.is_a?(Sass::Tree::FunctionNode)
+ end
+
+ private
+
+ # Whether `parent` should be assigned to ` parent`
+ def transparent_parent?(parent, grandparent)
+ is_any_of?(parent, SCRIPT_NODES) ||
+ (parent.bubbles? &&
+ !grandparent.is_a?(Sass::Tree::RootNode) &&
+ !grandparent.is_a?(Sass::Tree::AtRootNode))
+ end
+
+ def is_any_of?(val, classes)
+ classes.each do |c|
+ return true if val.is_a?(c)
+ end
+ false
+ end
+
+ def try_send(method, *args)
+ return unless respond_to?(method, true)
+ send(method, *args)
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/convert.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/convert.rb
new file mode 100644
index 0000000..ecbf0c9
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/convert.rb
@@ -0,0 +1,334 @@
+# A visitor for converting a Sass tree into a source string.
+class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
+ # Runs the visitor on a tree.
+ #
+ # @param root [Tree::Node] The root node of the Sass tree.
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
+ # @param format [Symbol] `:sass` or `:scss`.
+ # @return [String] The Sass or SCSS source for the tree.
+ def self.visit(root, options, format)
+ new(options, format).send(:visit, root)
+ end
+
+ protected
+
+ def initialize(options, format)
+ @options = options
+ @format = format
+ @tabs = 0
+ # 2 spaces by default
+ @tab_chars = @options[:indent] || " "
+ end
+
+ def visit_children(parent)
+ @tabs += 1
+ return @format == :sass ? "\n" : " {}\n" if parent.children.empty?
+ if @format == :sass
+ "\n" + super.join.rstrip + "\n"
+ else
+ " {\n" + super.join.rstrip + "\n#{ @tab_chars * (@tabs - 1)}}\n"
+ end
+ ensure
+ @tabs -= 1
+ end
+
+ # Ensures proper spacing between top-level nodes.
+ def visit_root(node)
+ Sass::Util.enum_cons(node.children + [nil], 2).map do |child, nxt|
+ visit(child) +
+ if nxt &&
+ (child.is_a?(Sass::Tree::CommentNode) &&
+ child.line + child.lines + 1 == nxt.line) ||
+ (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
+ child.line + 1 == nxt.line) ||
+ (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
+ child.line + 1 == nxt.line)
+ ""
+ else
+ "\n"
+ end
+ end.join.rstrip + "\n"
+ end
+
+ def visit_charset(node)
+ "#{tab_str} charset \"#{node.name}\"#{semi}\n"
+ end
+
+ def visit_comment(node)
+ value = interp_to_src(node.value)
+ if @format == :sass
+ content = value.gsub(/\*\/$/, '').rstrip
+ if content =~ /\A[ \t]/
+ # Re-indent SCSS comments like this:
+ # /* foo
+ # bar
+ # baz */
+ content.gsub!(/^/, ' ')
+ content.sub!(/\A([ \t]*)\/\*/, '/*\1')
+ end
+
+ if content.include?("\n")
+ content.gsub!(/\n \*/, "\n ")
+ spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
+ sep = node.type == :silent ? "\n//" : "\n *"
+ if spaces >= 2
+ content.gsub!(/\n /, sep)
+ else
+ content.gsub!(/\n#{' ' * spaces}/, sep)
+ end
+ end
+
+ content.gsub!(/\A\/\*/, '//') if node.type == :silent
+ content.gsub!(/^/, tab_str)
+ content = content.rstrip + "\n"
+ else
+ spaces = (@tab_chars * [ tabs - value[/^ */].size, 0].max)
+ content = if node.type == :silent
+ value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
+ else
+ value
+ end.gsub(/^/, spaces) + "\n"
+ end
+ content
+ end
+
+ def visit_debug(node)
+ "#{tab_str} debug #{node.expr.to_sass(@options)}#{semi}\n"
+ end
+
+ def visit_error(node)
+ "#{tab_str} error #{node.expr.to_sass(@options)}#{semi}\n"
+ end
+
+ def visit_directive(node)
+ res = "#{tab_str}#{interp_to_src(node.value)}"
+ res.gsub!(/^ import \#\{(.*)\}([^}]*)$/, '@import \1\2')
+ return res + "#{semi}\n" unless node.has_children
+ res + yield + "\n"
+ end
+
+ def visit_each(node)
+ vars = node.vars.map {|var| "$#{dasherize(var)}"}.join(", ")
+ "#{tab_str} each #{vars} in #{node.list.to_sass(@options)}#{yield}"
+ end
+
+ def visit_extend(node)
+ "#{tab_str} extend #{selector_to_src(node.selector).lstrip}" +
+ "#{" !optional" if node.optional?}#{semi}\n"
+ end
+
+ def visit_for(node)
+ "#{tab_str} for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
+ "#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
+ end
+
+ def visit_function(node)
+ args = node.args.map do |v, d|
+ d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options)
+ end.join(", ")
+ if node.splat
+ args << ", " unless node.args.empty?
+ args << node.splat.to_sass(@options) << "..."
+ end
+
+ "#{tab_str} function #{dasherize(node.name)}(#{args})#{yield}"
+ end
+
+ def visit_if(node)
+ name =
+ if ! is_else
+ "if"
+ elsif node.expr
+ "else if"
+ else
+ "else"
+ end
+ @is_else = false
+ str = "#{tab_str} #{name}"
+ str << " #{node.expr.to_sass(@options)}" if node.expr
+ str << yield
+ @is_else = true
+ str << visit(node.else) if node.else
+ str
+ ensure
+ @is_else = false
+ end
+
+ def visit_import(node)
+ quote = @format == :scss ? '"' : ''
+ "#{tab_str} import #{quote}#{node.imported_filename}#{quote}#{semi}\n"
+ end
+
+ def visit_media(node)
+ "#{tab_str} media #{query_interp_to_src(node.query)}#{yield}"
+ end
+
+ def visit_supports(node)
+ "#{tab_str} #{node name} #{node.condition.to_src(@options)}#{yield}"
+ end
+
+ def visit_cssimport(node)
+ if node.uri.is_a?(Sass::Script::Tree::Node)
+ str = "#{tab_str} import #{node.uri.to_sass(@options)}"
+ else
+ str = "#{tab_str} import #{node.uri}"
+ end
+ str << " #{interp_to_src(node.query)}" unless node.query.empty?
+ "#{str}#{semi}\n"
+ end
+
+ def visit_mixindef(node)
+ args =
+ if node.args.empty? && node.splat.nil?
+ ""
+ else
+ str = '('
+ str << node.args.map do |v, d|
+ if d
+ "#{v.to_sass(@options)}: #{d.to_sass(@options)}"
+ else
+ v.to_sass(@options)
+ end
+ end.join(", ")
+
+ if node.splat
+ str << ", " unless node.args.empty?
+ str << node.splat.to_sass(@options) << '...'
+ end
+
+ str << ')'
+ end
+
+ "#{tab_str}#{ format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
+ end
+
+ def visit_mixin(node)
+ arg_to_sass = lambda do |arg|
+ sass = arg.to_sass(@options)
+ sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma
+ sass
+ end
+
+ unless node.args.empty? && node.keywords.empty? && node.splat.nil?
+ args = node.args.map(&arg_to_sass)
+ keywords = Sass::Util.hash_to_a(node.keywords.as_stored).
+ map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"}
+
+ if node.splat
+ splat = "#{arg_to_sass[node.splat]}..."
+ kwarg_splat = "#{arg_to_sass[node.kwarg_splat]}..." if node.kwarg_splat
+ end
+
+ arglist = "(#{[args, splat, keywords, kwarg_splat].flatten.compact.join(', ')})"
+ end
+ "#{tab_str}#{ format == :sass ? '+' : '@include '}" +
+ "#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n"
+ end
+
+ def visit_content(node)
+ "#{tab_str} content#{semi}\n"
+ end
+
+ def visit_prop(node)
+ res = tab_str + node.declaration(@options, @format)
+ return res + semi + "\n" if node.children.empty?
+ res + yield.rstrip + semi + "\n"
+ end
+
+ def visit_return(node)
+ "#{tab_str} return #{node.expr.to_sass(@options)}#{semi}\n"
+ end
+
+ def visit_rule(node)
+ rule = node.parsed_rules ? [node.parsed_rules.to_s] : node.rule
+ if @format == :sass
+ name = selector_to_sass(rule)
+ name = "\\" + name if name[0] == ?:
+ name.gsub(/^/, tab_str) + yield
+ elsif @format == :scss
+ name = selector_to_scss(rule)
+ res = name + yield
+ if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent
+ res.slice!(-3..-1)
+ res << "\n" << tab_str << "}\n"
+ end
+ res
+ end
+ end
+
+ def visit_variable(node)
+ "#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}" +
+ "#{' !global' if node.global}#{' !default' if node.guarded}#{semi}\n"
+ end
+
+ def visit_warn(node)
+ "#{tab_str} warn #{node.expr.to_sass(@options)}#{semi}\n"
+ end
+
+ def visit_while(node)
+ "#{tab_str} while #{node.expr.to_sass(@options)}#{yield}"
+ end
+
+ def visit_atroot(node)
+ if node.query
+ "#{tab_str} at-root #{query_interp_to_src(node.query)}#{yield}"
+ elsif node.children.length == 1 && node.children.first.is_a?(Sass::Tree::RuleNode)
+ rule = node.children.first
+ "#{tab_str} at-root #{selector_to_src(rule.rule).lstrip}#{visit_children(rule)}"
+ else
+ "#{tab_str} at-root#{yield}"
+ end
+ end
+
+ private
+
+ def interp_to_src(interp)
+ interp.map {|r| r.is_a?(String) ? r : r.to_sass(@options)}.join
+ end
+
+ # Like interp_to_src, but removes the unnecessary `#{}` around the keys and
+ # values in query expressions.
+ def query_interp_to_src(interp)
+ interp = interp.map do |e|
+ next e unless e.is_a?(Sass::Script::Tree::Literal)
+ next e unless e.value.is_a?(Sass::Script::Value::String)
+ e.value.value
+ end
+
+ interp_to_src(interp)
+ end
+
+ def selector_to_src(sel)
+ @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
+ end
+
+ def selector_to_sass(sel)
+ sel.map do |r|
+ if r.is_a?(String)
+ r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
+ else
+ r.to_sass(@options)
+ end
+ end.join
+ end
+
+ def selector_to_scss(sel)
+ interp_to_src(sel).gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
+ end
+
+ def semi
+ @format == :sass ? "" : ";"
+ end
+
+ def tab_str
+ @tab_chars * @tabs
+ end
+
+ def dasherize(s)
+ if @options[:dasherize]
+ s.gsub('_', '-')
+ else
+ s
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/cssize.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/cssize.rb
new file mode 100644
index 0000000..ff18dde
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/cssize.rb
@@ -0,0 +1,373 @@
+# A visitor for converting a static Sass tree into a static CSS tree.
+class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
+ # @param root [Tree::Node] The root node of the tree to visit.
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
+ # *and* the extensions defined for this tree
+ def self.visit(root); super; end
+
+ protected
+
+ # Returns the immediate parent of the current node.
+ # @return [Tree::Node]
+ def parent
+ @parents.last
+ end
+
+ def initialize
+ @parents = []
+ @extends = Sass::Util::SubsetMap.new
+ end
+
+ # If an exception is raised, this adds proper metadata to the backtrace.
+ def visit(node)
+ super(node)
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ # Keeps track of the current parent node.
+ def visit_children(parent)
+ with_parent parent do
+ parent.children = visit_children_without_parent(parent)
+ parent
+ end
+ end
+
+ # Like {#visit\_children}, but doesn't set {#parent}.
+ #
+ # @param node [Sass::Tree::Node]
+ # @return [Array<Sass::Tree::Node>] the flattened results of
+ # visiting all the children of `node`
+ def visit_children_without_parent(node)
+ node.children.map {|c| visit(c)}.flatten
+ end
+
+ # Runs a block of code with the current parent node
+ # replaced with the given node.
+ #
+ # @param parent [Tree::Node] The new parent for the duration of the block.
+ # @yield A block in which the parent is set to `parent`.
+ # @return [Object] The return value of the block.
+ def with_parent(parent)
+ @parents.push parent
+ yield
+ ensure
+ @parents.pop
+ end
+
+ # In Ruby 1.8, ensures that there's only one ` charset` directive
+ # and that it's at the top of the document.
+ #
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
+ # *and* the extensions defined for this tree
+ def visit_root(node)
+ yield
+
+ if parent.nil?
+ # In Ruby 1.9 we can make all @charset nodes invisible
+ # and infer the final @charset from the encoding of the final string.
+ if Sass::Util.ruby1_8?
+ charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
+ node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
+ node.children.unshift charset if charset
+ end
+
+ imports_to_move = []
+ import_limit = nil
+ i = -1
+ node.children.reject! do |n|
+ i += 1
+ if import_limit
+ next false unless n.is_a?(Sass::Tree::CssImportNode)
+ imports_to_move << n
+ next true
+ end
+
+ if !n.is_a?(Sass::Tree::CommentNode) &&
+ !n.is_a?(Sass::Tree::CharsetNode) &&
+ !n.is_a?(Sass::Tree::CssImportNode)
+ import_limit = i
+ end
+
+ false
+ end
+
+ if import_limit
+ node.children = node.children[0...import_limit] + imports_to_move +
+ node.children[import_limit..-1]
+ end
+ end
+
+ return node, @extends
+ rescue Sass::SyntaxError => e
+ e.sass_template ||= node.template
+ raise e
+ end
+
+ # A simple struct wrapping up information about a single ` extend` instance. A
+ # single {ExtendNode} can have multiple Extends if either the parent node or
+ # the extended selector is a comma sequence.
+ #
+ # @attr extender [Sass::Selector::Sequence]
+ # The selector of the CSS rule containing the ` extend`
+ # @attr target [Array<Sass::Selector::Simple>] The selector being ` extend`ed
+ # @attr node [Sass::Tree::ExtendNode] The node that produced this extend.
+ # @attr directives [Array<Sass::Tree::DirectiveNode>]
+ # The directives containing the ` extend`
+ # @attr result [Symbol]
+ # The result of this extend. One of `:not_found` (the target doesn't exist
+ # in the document), `:failed_to_unify` (the target exists but cannot be
+ # unified with the extender), or `:succeeded`.
+ Extend = Struct.new(:extender, :target, :node, :directives, :result)
+
+ # Registers an extension in the ` extends` subset map.
+ def visit_extend(node)
+ parent.resolved_rules.populate_extends(@extends, node.resolved_selector, node,
+ @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)})
+ []
+ end
+
+ # Modifies exception backtraces to include the imported file.
+ def visit_import(node)
+ visit_children_without_parent(node)
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.children.first.filename)
+ e.add_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ # Asserts that all the traced children are valid in their new location.
+ def visit_trace(node)
+ visit_children_without_parent(node)
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
+ e.add_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ # Converts nested properties into flat properties
+ # and updates the indentation of the prop node based on the nesting level.
+ def visit_prop(node)
+ if parent.is_a?(Sass::Tree::PropNode)
+ node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
+ node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
+ end
+
+ yield
+
+ result = node.children.dup
+ if !node.resolved_value.empty? || node.children.empty?
+ node.send(:check!)
+ result.unshift(node)
+ end
+
+ result
+ end
+
+ def visit_atroot(node)
+ # If there aren't any more directives or rules that this @at-root needs to
+ # exclude, we can get rid of it and just evaluate the children.
+ if @parents.none? {|n| node.exclude_node?(n)}
+ results = visit_children_without_parent(node)
+ results.each {|c| c.tabs += node.tabs if bubblable?(c)}
+ if !results.empty? && bubblable?(results.last)
+ results.last.group_end = node.group_end
+ end
+ return results
+ end
+
+ # If this @at-root excludes the immediate parent, return it as-is so that it
+ # can be bubbled up by the parent node.
+ return Bubble.new(node) if node.exclude_node?(parent)
+
+ # Otherwise, duplicate the current parent and move it into the @at-root
+ # node. As above, returning an @at-root node signals to the parent directive
+ # that it should be bubbled upwards.
+ bubble(node)
+ end
+
+ # The following directives are visible and have children. This means they need
+ # to be able to handle bubbling up nodes such as @at-root and @media.
+
+ # Updates the indentation of the rule node based on the nesting
+ # level. The selectors were resolved in {Perform}.
+ def visit_rule(node)
+ yield
+
+ rules = node.children.select {|c| bubblable?(c)}
+ props = node.children.reject {|c| bubblable?(c) || c.invisible?}
+
+ unless props.empty?
+ node.children = props
+ rules.each {|r| r.tabs += 1} if node.style == :nested
+ rules.unshift(node)
+ end
+
+ rules = debubble(rules)
+ unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty? || !bubblable?(rules.last)
+ rules.last.group_end = true
+ end
+ rules
+ end
+
+ def visit_keyframerule(node)
+ return node unless node.has_children
+
+ yield
+
+ debubble(node.children, node)
+ end
+
+ # Bubbles a directive up through RuleNodes.
+ def visit_directive(node)
+ return node unless node.has_children
+ if parent.is_a?(Sass::Tree::RuleNode)
+ # @keyframes shouldn't include the rule nodes, so we manually create a
+ # bubble that doesn't have the parent's contents for them.
+ return node.normalized_name == '@keyframes' ? Bubble.new(node) : bubble(node)
+ end
+
+ yield
+
+ # Since we don't know if the mere presence of an unknown directive may be
+ # important, we should keep an empty version around even if all the contents
+ # are removed via @at-root. However, if the contents are just bubbled out,
+ # we don't need to do so.
+ directive_exists = node.children.any? do |child|
+ next true unless child.is_a?(Bubble)
+ next false unless child.node.is_a?(Sass::Tree::DirectiveNode)
+ child.node.resolved_value == node.resolved_value
+ end
+
+ # We know empty @keyframes directives do nothing.
+ if directive_exists || node.name == '@keyframes'
+ []
+ else
+ empty_node = node.dup
+ empty_node.children = []
+ [empty_node]
+ end + debubble(node.children, node)
+ end
+
+ # Bubbles the ` media` directive up through RuleNodes
+ # and merges it with other ` media` directives.
+ def visit_media(node)
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
+ return Bubble.new(node) if parent.is_a?(Sass::Tree::MediaNode)
+
+ yield
+
+ debubble(node.children, node) do |child|
+ next child unless child.is_a?(Sass::Tree::MediaNode)
+ # Copies of `node` can be bubbled, and we don't want to merge it with its
+ # own query.
+ next child if child.resolved_query == node.resolved_query
+ next child if child.resolved_query = child.resolved_query.merge(node.resolved_query)
+ end
+ end
+
+ # Bubbles the ` supports` directive up through RuleNodes.
+ def visit_supports(node)
+ return node unless node.has_children
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
+
+ yield
+
+ debubble(node.children, node)
+ end
+
+ private
+
+ # "Bubbles" `node` one level by copying the parent and wrapping `node`'s
+ # children with it.
+ #
+ # @param node [Sass::Tree::Node].
+ # @return [Bubble]
+ def bubble(node)
+ new_rule = parent.dup
+ new_rule.children = node.children
+ node.children = [new_rule]
+ Bubble.new(node)
+ end
+
+ # Pops all bubbles in `children` and intersperses the results with the other
+ # values.
+ #
+ # If `parent` is passed, it's copied and used as the parent node for the
+ # nested portions of `children`.
+ #
+ # @param children [List<Sass::Tree::Node, Bubble>]
+ # @param parent [Sass::Tree::Node]
+ # @yield [node] An optional block for processing bubbled nodes. Each bubbled
+ # node will be passed to this block.
+ # @yieldparam node [Sass::Tree::Node] A bubbled node.
+ # @yieldreturn [Sass::Tree::Node?] A node to use in place of the bubbled node.
+ # This can be the node itself, or `nil` to indicate that the node should be
+ # omitted.
+ # @return [List<Sass::Tree::Node, Bubble>]
+ def debubble(children, parent = nil)
+ # Keep track of the previous parent so that we don't divide `parent`
+ # unnecessarily if the ` at-root` doesn't produce any new nodes (e.g.
+ # ` at-root { extend %foo}`).
+ previous_parent = nil
+
+ Sass::Util.slice_by(children) {|c| c.is_a?(Bubble)}.map do |(is_bubble, slice)|
+ unless is_bubble
+ next slice unless parent
+ if previous_parent
+ previous_parent.children.push(*slice)
+ next []
+ else
+ previous_parent = new_parent = parent.dup
+ new_parent.children = slice
+ next new_parent
+ end
+ end
+
+ slice.map do |bubble|
+ next unless (node = block_given? ? yield(bubble.node) : bubble.node)
+ node.tabs += bubble.tabs
+ node.group_end = bubble.group_end
+ results = [visit(node)].flatten
+ previous_parent = nil unless results.empty?
+ results
+ end.compact
+ end.flatten
+ end
+
+ # Returns whether or not a node can be bubbled up through the syntax tree.
+ #
+ # @param node [Sass::Tree::Node]
+ # @return [Boolean]
+ def bubblable?(node)
+ node.is_a?(Sass::Tree::RuleNode) || node.bubbles?
+ end
+
+ # A wrapper class for a node that indicates to the parent that it should
+ # treat the wrapped node as a sibling rather than a child.
+ #
+ # Nodes should be wrapped before they're passed to \{Cssize.visit}. They will
+ # be automatically visited upon calling \{#pop}.
+ #
+ # This duck types as a [Sass::Tree::Node] for the purposes of
+ # tree-manipulation operations.
+ class Bubble
+ attr_accessor :node
+ attr_accessor :tabs
+ attr_accessor :group_end
+
+ def initialize(node)
+ @node = node
+ @tabs = 0
+ end
+
+ def bubbles?
+ true
+ end
+
+ def inspect
+ "(Bubble #{node.inspect})"
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/deep_copy.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/deep_copy.rb
new file mode 100644
index 0000000..2a7036d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/deep_copy.rb
@@ -0,0 +1,107 @@
+# A visitor for copying the full structure of a Sass tree.
+class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
+ protected
+
+ def visit(node)
+ super(node.dup)
+ end
+
+ def visit_children(parent)
+ parent.children = parent.children.map {|c| visit(c)}
+ parent
+ end
+
+ def visit_debug(node)
+ node.expr = node.expr.deep_copy
+ yield
+ end
+
+ def visit_error(node)
+ node.expr = node.expr.deep_copy
+ yield
+ end
+
+ def visit_each(node)
+ node.list = node.list.deep_copy
+ yield
+ end
+
+ def visit_extend(node)
+ node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
+ yield
+ end
+
+ def visit_for(node)
+ node.from = node.from.deep_copy
+ node.to = node.to.deep_copy
+ yield
+ end
+
+ def visit_function(node)
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
+ yield
+ end
+
+ def visit_if(node)
+ node.expr = node.expr.deep_copy if node.expr
+ node.else = visit(node.else) if node.else
+ yield
+ end
+
+ def visit_mixindef(node)
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
+ yield
+ end
+
+ def visit_mixin(node)
+ node.args = node.args.map {|a| a.deep_copy}
+ node.keywords = Hash[node.keywords.map {|k, v| [k, v.deep_copy]}]
+ yield
+ end
+
+ def visit_prop(node)
+ node.name = node.name.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
+ node.value = node.value.deep_copy
+ yield
+ end
+
+ def visit_return(node)
+ node.expr = node.expr.deep_copy
+ yield
+ end
+
+ def visit_rule(node)
+ node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
+ yield
+ end
+
+ def visit_variable(node)
+ node.expr = node.expr.deep_copy
+ yield
+ end
+
+ def visit_warn(node)
+ node.expr = node.expr.deep_copy
+ yield
+ end
+
+ def visit_while(node)
+ node.expr = node.expr.deep_copy
+ yield
+ end
+
+ def visit_directive(node)
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
+ yield
+ end
+
+ def visit_media(node)
+ node.query = node.query.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
+ yield
+ end
+
+ def visit_supports(node)
+ node.condition = node.condition.deep_copy
+ yield
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/extend.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/extend.rb
new file mode 100644
index 0000000..4fcad49
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/extend.rb
@@ -0,0 +1,68 @@
+# A visitor for performing selector inheritance on a static CSS tree.
+#
+# Destructively modifies the tree.
+class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
+ # Performs the given extensions on the static CSS tree based in `root`, then
+ # validates that all extends matched some selector.
+ #
+ # @param root [Tree::Node] The root node of the tree to visit.
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
+ # Sass::Tree::Visitors::Cssize::Extend}]
+ # The extensions to perform on this tree.
+ # @return [Object] The return value of \{#visit} for the root node.
+ def self.visit(root, extends)
+ return if extends.empty?
+ new(extends).send(:visit, root)
+ check_extends_fired! extends
+ end
+
+ protected
+
+ def initialize(extends)
+ @parent_directives = []
+ @extends = extends
+ end
+
+ # If an exception is raised, this adds proper metadata to the backtrace.
+ def visit(node)
+ super(node)
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ # Keeps track of the current parent directives.
+ def visit_children(parent)
+ @parent_directives.push parent if parent.is_a?(Sass::Tree::DirectiveNode)
+ super
+ ensure
+ @parent_directives.pop if parent.is_a?(Sass::Tree::DirectiveNode)
+ end
+
+ # Applies the extend to a single rule's selector.
+ def visit_rule(node)
+ node.resolved_rules = node.resolved_rules.do_extend(@extends, @parent_directives)
+ end
+
+ private
+
+ def self.check_extends_fired!(extends)
+ extends.each_value do |ex|
+ next if ex.result == :succeeded || ex.node.optional?
+ message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
+ reason =
+ if ex.result == :not_found
+ "The selector \"#{ex.target.join}\" was not found."
+ else
+ "No selectors matching \"#{ex.target.join}\" could be unified with \"#{ex.extender}\"."
+ end
+
+ # TODO(nweiz): this should use the Sass stack trace of the extend node.
+ raise Sass::SyntaxError.new(<<MESSAGE, :filename => ex.node.filename, :line => ex.node.line)
+#{message}
+#{reason}
+Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
+MESSAGE
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/perform.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/perform.rb
new file mode 100644
index 0000000..280c936
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/perform.rb
@@ -0,0 +1,547 @@
+# A visitor for converting a dynamic Sass tree into a static Sass tree.
+class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
+ class << self
+ # @param root [Tree::Node] The root node of the tree to visit.
+ # @param environment [Sass::Environment] The lexical environment.
+ # @return [Tree::Node] The resulting tree of static nodes.
+ def visit(root, environment = nil)
+ new(environment).send(:visit, root)
+ end
+
+ # @api private
+ # @comment
+ # rubocop:disable MethodLength
+ def perform_arguments(callable, args, splat, environment)
+ desc = "#{callable.type.capitalize} #{callable.name}"
+ downcase_desc = "#{callable.type} #{callable.name}"
+
+ # All keywords are contained in splat.keywords for consistency,
+ # even if there were no splats passed in.
+ old_keywords_accessed = splat.keywords_accessed
+ keywords = splat.keywords
+ splat.keywords_accessed = old_keywords_accessed
+
+ begin
+ unless keywords.empty?
+ unknown_args = Sass::Util.array_minus(keywords.keys,
+ callable.args.map {|var| var.first.underscored_name})
+ if callable.splat && unknown_args.include?(callable.splat.underscored_name)
+ raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} " +
+ "cannot be used as a named argument.")
+ elsif unknown_args.any?
+ description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
+ raise Sass::SyntaxError.new("#{desc} doesn't have #{description} " +
+ "#{unknown_args.map {|name| "$#{name}"}.join ', '}.")
+ end
+ end
+ rescue Sass::SyntaxError => keyword_exception
+ end
+
+ # If there's no splat, raise the keyword exception immediately. The actual
+ # raising happens in the ensure clause at the end of this function.
+ return if keyword_exception && !callable.splat
+
+ splat_sep = :comma
+ if splat
+ args += splat.to_a
+ splat_sep = splat.separator
+ end
+
+ if args.size > callable.args.size && !callable.splat
+ extra_args_because_of_splat = splat && args.size - splat.to_a.size <= callable.args.size
+
+ takes = callable.args.size
+ passed = args.size
+ message = "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed."
+ raise Sass::SyntaxError.new(message) unless extra_args_because_of_splat
+ # TODO: when the deprecation period is over, make this an error.
+ Sass::Util.sass_warn("WARNING: #{message}\n" +
+ environment.stack.to_s.gsub(/^/m, " " * 8) + "\n" +
+ "This will be an error in future versions of Sass.")
+ end
+
+ env = Sass::Environment.new(callable.environment)
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
+ if value && keywords.has_key?(var.name)
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} " +
+ "both by position and by name.")
+ end
+
+ value ||= keywords.delete(var.name)
+ value ||= default && default.perform(env)
+ raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
+ env.set_local_var(var.name, value)
+ end
+
+ if callable.splat
+ rest = args[callable.args.length..-1] || []
+ arg_list = Sass::Script::Value::ArgList.new(rest, keywords, splat_sep)
+ arg_list.options = env.options
+ env.set_local_var(callable.splat.name, arg_list)
+ end
+
+ yield env
+ rescue StandardError => e
+ ensure
+ # If there's a keyword exception, we don't want to throw it immediately,
+ # because the invalid keywords may be part of a glob argument that should be
+ # passed on to another function. So we only raise it if we reach the end of
+ # this function *and* the keywords attached to the argument list glob object
+ # haven't been accessed.
+ #
+ # The keyword exception takes precedence over any Sass errors, but not over
+ # non-Sass exceptions.
+ if keyword_exception &&
+ !(arg_list && arg_list.keywords_accessed) &&
+ (e.nil? || e.is_a?(Sass::SyntaxError))
+ raise keyword_exception
+ elsif e
+ raise e
+ end
+ end
+
+ # @api private
+ # @return [Sass::Script::Value::ArgList]
+ def perform_splat(splat, performed_keywords, kwarg_splat, environment)
+ args, kwargs, separator = [], nil, :comma
+
+ if splat
+ splat = splat.perform(environment)
+ separator = splat.separator || separator
+ if splat.is_a?(Sass::Script::Value::ArgList)
+ args = splat.to_a
+ kwargs = splat.keywords
+ elsif splat.is_a?(Sass::Script::Value::Map)
+ kwargs = arg_hash(splat)
+ else
+ args = splat.to_a
+ end
+ end
+ kwargs ||= Sass::Util::NormalizedMap.new
+ kwargs.update(performed_keywords)
+
+ if kwarg_splat
+ kwarg_splat = kwarg_splat.perform(environment)
+ unless kwarg_splat.is_a?(Sass::Script::Value::Map)
+ raise Sass::SyntaxError.new("Variable keyword arguments must be a map " +
+ "(was #{kwarg_splat.inspect}).")
+ end
+ kwargs.update(arg_hash(kwarg_splat))
+ end
+
+ Sass::Script::Value::ArgList.new(args, kwargs, separator)
+ end
+
+ private
+
+ def arg_hash(map)
+ Sass::Util.map_keys(map.to_h) do |key|
+ next key.value if key.is_a?(Sass::Script::Value::String)
+ raise Sass::SyntaxError.new("Variable keyword argument map must have string keys.\n" +
+ "#{key.inspect} is not a string in #{map.inspect}.")
+ end
+ end
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ protected
+
+ def initialize(env)
+ @environment = env
+ end
+
+ # If an exception is raised, this adds proper metadata to the backtrace.
+ def visit(node)
+ return super(node.dup) unless @environment
+ @environment.stack.with_base(node.filename, node.line) {super(node.dup)}
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ # Keeps track of the current environment.
+ def visit_children(parent)
+ with_environment Sass::Environment.new(@environment, parent.options) do
+ parent.children = super.flatten
+ parent
+ end
+ end
+
+ # Runs a block of code with the current environment replaced with the given one.
+ #
+ # @param env [Sass::Environment] The new environment for the duration of the block.
+ # @yield A block in which the environment is set to `env`.
+ # @return [Object] The return value of the block.
+ def with_environment(env)
+ old_env, @environment = @environment, env
+ yield
+ ensure
+ @environment = old_env
+ end
+
+ # Sets the options on the environment if this is the top-level root.
+ def visit_root(node)
+ yield
+ rescue Sass::SyntaxError => e
+ e.sass_template ||= node.template
+ raise e
+ end
+
+ # Removes this node from the tree if it's a silent comment.
+ def visit_comment(node)
+ return [] if node.invisible?
+ node.resolved_value = run_interp_no_strip(node.value)
+ node.resolved_value.gsub!(/\\([\\#])/, '\1')
+ node
+ end
+
+ # Prints the expression to STDERR.
+ def visit_debug(node)
+ res = node.expr.perform(@environment)
+ if res.is_a?(Sass::Script::Value::String)
+ res = res.value
+ else
+ res = res.to_sass
+ end
+ if node.filename
+ Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}"
+ else
+ Sass::Util.sass_warn "Line #{node.line} DEBUG: #{res}"
+ end
+ []
+ end
+
+ # Throws the expression as an error.
+ def visit_error(node)
+ res = node.expr.perform(@environment)
+ if res.is_a?(Sass::Script::Value::String)
+ res = res.value
+ else
+ res = res.to_sass
+ end
+ raise Sass::SyntaxError.new(res)
+ end
+
+ # Runs the child nodes once for each value in the list.
+ def visit_each(node)
+ list = node.list.perform(@environment)
+
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
+ list.to_a.map do |value|
+ if node.vars.length == 1
+ @environment.set_local_var(node.vars.first, value)
+ else
+ node.vars.zip(value.to_a) do |(var, sub_value)|
+ @environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new)
+ end
+ end
+ node.children.map {|c| visit(c)}
+ end.flatten
+ end
+ end
+
+ # Runs SassScript interpolation in the selector,
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
+ def visit_extend(node)
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.selector),
+ node.filename, node.options[:importer], node.line)
+ node.resolved_selector = parser.parse_selector
+ node
+ end
+
+ # Runs the child nodes once for each time through the loop, varying the variable each time.
+ def visit_for(node)
+ from = node.from.perform(@environment)
+ to = node.to.perform(@environment)
+ from.assert_int!
+ to.assert_int!
+
+ to = to.coerce(from.numerator_units, from.denominator_units)
+ direction = from.to_i > to.to_i ? -1 : 1
+ range = Range.new(direction * from.to_i, direction * to.to_i, node.exclusive)
+
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
+ range.map do |i|
+ @environment.set_local_var(node.var,
+ Sass::Script::Value::Number.new(direction * i,
+ from.numerator_units, from.denominator_units))
+ node.children.map {|c| visit(c)}
+ end.flatten
+ end
+ end
+
+ # Loads the function into the environment.
+ def visit_function(node)
+ env = Sass::Environment.new(@environment, node.options)
+ @environment.set_local_function(node.name,
+ Sass::Callable.new(node.name, node.args, node.splat, env,
+ node.children, !:has_content, "function"))
+ []
+ end
+
+ # Runs the child nodes if the conditional expression is true;
+ # otherwise, tries the else nodes.
+ def visit_if(node)
+ if node.expr.nil? || node.expr.perform(@environment).to_bool
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
+ node.children.map {|c| visit(c)}
+ end.flatten
+ elsif node.else
+ visit(node.else)
+ else
+ []
+ end
+ end
+
+ # Returns a static DirectiveNode if this is importing a CSS file,
+ # or parses and includes the imported Sass file.
+ def visit_import(node)
+ if (path = node.css_import?)
+ resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
+ resolved_node.source_range = node.source_range
+ return resolved_node
+ end
+ file = node.imported_file
+ if @environment.stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
+ handle_import_loop!(node)
+ end
+
+ begin
+ @environment.stack.with_import(node.filename, node.line) do
+ root = file.to_tree
+ Sass::Tree::Visitors::CheckNesting.visit(root)
+ node.children = root.children.map {|c| visit(c)}.flatten
+ node
+ end
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.imported_file.options[:filename])
+ e.add_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+ end
+
+ # Loads a mixin into the environment.
+ def visit_mixindef(node)
+ env = Sass::Environment.new(@environment, node.options)
+ @environment.set_local_mixin(node.name,
+ Sass::Callable.new(node.name, node.args, node.splat, env,
+ node.children, node.has_content, "mixin"))
+ []
+ end
+
+ # Runs a mixin.
+ def visit_mixin(node)
+ @environment.stack.with_mixin(node.filename, node.line, node.name) do
+ mixin = @environment.mixin(node.name)
+ raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
+
+ if node.children.any? && !mixin.has_content
+ raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
+ end
+
+ args = node.args.map {|a| a.perform(@environment)}
+ keywords = Sass::Util.map_vals(node.keywords) {|v| v.perform(@environment)}
+ splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment)
+
+ self.class.perform_arguments(mixin, args, splat, @environment) do |env|
+ env.caller = Sass::Environment.new(@environment)
+ env.content = [node.children, @environment] if node.has_children
+
+ trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
+ with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
+ trace_node
+ end
+ end
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
+ e.add_backtrace(:line => node.line)
+ raise e
+ end
+
+ def visit_content(node)
+ content, content_env = @environment.content
+ return [] unless content
+ @environment.stack.with_mixin(node.filename, node.line, '@content') do
+ trace_node = Sass::Tree::TraceNode.from_node('@content', node)
+ content_env = Sass::Environment.new(content_env)
+ content_env.caller = Sass::Environment.new(@environment)
+ with_environment(content_env) do
+ trace_node.children = content.map {|c| visit(c.dup)}.flatten
+ end
+ trace_node
+ end
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:mixin => '@content', :line => node.line)
+ e.add_backtrace(:line => node.line)
+ raise e
+ end
+
+ # Runs any SassScript that may be embedded in a property.
+ def visit_prop(node)
+ node.resolved_name = run_interp(node.name)
+ val = node.value.perform(@environment)
+ node.resolved_value = val.to_s
+ node.value_source_range = val.source_range if val.source_range
+ yield
+ end
+
+ # Returns the value of the expression.
+ def visit_return(node)
+ throw :_sass_return, node.expr.perform(@environment)
+ end
+
+ # Runs SassScript interpolation in the selector,
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
+ def visit_rule(node)
+ old_at_root_without_rule = @at_root_without_rule
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
+ node.filename, node.options[:importer], node.line)
+ if @in_keyframes
+ keyframe_rule_node = Sass::Tree::KeyframeRuleNode.new(parser.parse_keyframes_selector)
+ keyframe_rule_node.options = node.options
+ keyframe_rule_node.line = node.line
+ keyframe_rule_node.filename = node.filename
+ keyframe_rule_node.source_range = node.source_range
+ keyframe_rule_node.has_children = node.has_children
+ with_environment Sass::Environment.new(@environment, node.options) do
+ keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten
+ end
+ keyframe_rule_node
+ else
+ @at_root_without_rule = false
+ node.parsed_rules ||= parser.parse_selector
+ node.resolved_rules = node.parsed_rules.resolve_parent_refs(
+ @environment.selector, !old_at_root_without_rule)
+ node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
+ with_environment Sass::Environment.new(@environment, node.options) do
+ @environment.selector = node.resolved_rules
+ node.children = node.children.map {|c| visit(c)}.flatten
+ end
+ node
+ end
+ ensure
+ @at_root_without_rule = old_at_root_without_rule
+ end
+
+ # Sets a variable that indicates that the first level of rule nodes
+ # shouldn't include the parent selector by default.
+ def visit_atroot(node)
+ if node.query
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
+ node.filename, node.options[:importer], node.line)
+ node.resolved_type, node.resolved_value = parser.parse_static_at_root_query
+ else
+ node.resolved_type, node.resolved_value = :without, ['rule']
+ end
+
+ old_at_root_without_rule = @at_root_without_rule
+ old_in_keyframes = @in_keyframes
+ @at_root_without_rule = true if node.exclude?('rule')
+ @in_keyframes = false if node.exclude?('keyframes')
+ yield
+ ensure
+ @in_keyframes = old_in_keyframes
+ @at_root_without_rule = old_at_root_without_rule
+ end
+
+ # Loads the new variable value into the environment.
+ def visit_variable(node)
+ env = @environment
+ env = env.global_env if node.global
+ if node.guarded
+ var = env.var(node.name)
+ return [] if var && !var.null?
+ end
+
+ val = node.expr.perform(@environment)
+ if node.expr.source_range
+ val.source_range = node.expr.source_range
+ else
+ val.source_range = node.source_range
+ end
+ env.set_var(node.name, val)
+ []
+ end
+
+ # Prints the expression to STDERR with a stylesheet trace.
+ def visit_warn(node)
+ res = node.expr.perform(@environment)
+ res = res.value if res.is_a?(Sass::Script::Value::String)
+ msg = "WARNING: #{res}\n "
+ msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
+ Sass::Util.sass_warn msg
+ []
+ end
+
+ # Runs the child nodes until the continuation expression becomes false.
+ def visit_while(node)
+ children = []
+ with_environment Sass::SemiGlobalEnvironment.new(@environment) do
+ children += node.children.map {|c| visit(c)} while node.expr.perform(@environment).to_bool
+ end
+ children.flatten
+ end
+
+ def visit_directive(node)
+ node.resolved_value = run_interp(node.value)
+ old_in_keyframes, @in_keyframes = @in_keyframes, node.normalized_name == "@keyframes"
+ with_environment Sass::Environment.new(@environment) do
+ node.children = node.children.map {|c| visit(c)}.flatten
+ node
+ end
+ ensure
+ @in_keyframes = old_in_keyframes
+ end
+
+ def visit_media(node)
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
+ node.filename, node.options[:importer], node.line)
+ node.resolved_query ||= parser.parse_media_query_list
+ yield
+ end
+
+ def visit_supports(node)
+ node.condition = node.condition.deep_copy
+ node.condition.perform(@environment)
+ yield
+ end
+
+ def visit_cssimport(node)
+ node.resolved_uri = run_interp([node.uri])
+ if node.query && !node.query.empty?
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
+ node.filename, node.options[:importer], node.line)
+ node.resolved_query ||= parser.parse_media_query_list
+ end
+ yield
+ end
+
+ private
+
+ def run_interp_no_strip(text)
+ text.map do |r|
+ next r if r.is_a?(String)
+ r.perform(@environment).to_s(:quote => :none)
+ end.join
+ end
+
+ def run_interp(text)
+ run_interp_no_strip(text).strip
+ end
+
+ def handle_import_loop!(node)
+ msg = "An @import loop has been found:"
+ files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
+ if node.filename == node.imported_file.options[:filename]
+ raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
+ end
+
+ files << node.filename << node.imported_file.options[:filename]
+ msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
+ " #{m1} imports #{m2}"
+ end.join("\n")
+ raise Sass::SyntaxError.new(msg)
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/set_options.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/set_options.rb
new file mode 100644
index 0000000..48cd946
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/set_options.rb
@@ -0,0 +1,139 @@
+# A visitor for setting options on the Sass tree
+class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
+ # @param root [Tree::Node] The root node of the tree to visit.
+ # @param options [{Symbol => Object}] The options has to set.
+ def self.visit(root, options); new(options).send(:visit, root); end
+
+ protected
+
+ def initialize(options)
+ @options = options
+ end
+
+ def visit(node)
+ node.instance_variable_set('@options', @options)
+ super
+ end
+
+ def visit_comment(node)
+ node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
+ yield
+ end
+
+ def visit_debug(node)
+ node.expr.options = @options
+ yield
+ end
+
+ def visit_error(node)
+ node.expr.options = @options
+ yield
+ end
+
+ def visit_each(node)
+ node.list.options = @options
+ yield
+ end
+
+ def visit_extend(node)
+ node.selector.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
+ yield
+ end
+
+ def visit_for(node)
+ node.from.options = @options
+ node.to.options = @options
+ yield
+ end
+
+ def visit_function(node)
+ node.args.each do |k, v|
+ k.options = @options
+ v.options = @options if v
+ end
+ node.splat.options = @options if node.splat
+ yield
+ end
+
+ def visit_if(node)
+ node.expr.options = @options if node.expr
+ visit(node.else) if node.else
+ yield
+ end
+
+ def visit_import(node)
+ # We have no good way of propagating the new options through an Engine
+ # instance, so we just null it out. This also lets us avoid caching an
+ # imported Engine along with the importing source tree.
+ node.imported_file = nil
+ yield
+ end
+
+ def visit_mixindef(node)
+ node.args.each do |k, v|
+ k.options = @options
+ v.options = @options if v
+ end
+ node.splat.options = @options if node.splat
+ yield
+ end
+
+ def visit_mixin(node)
+ node.args.each {|a| a.options = @options}
+ node.keywords.each {|k, v| v.options = @options}
+ node.splat.options = @options if node.splat
+ node.kwarg_splat.options = @options if node.kwarg_splat
+ yield
+ end
+
+ def visit_prop(node)
+ node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
+ node.value.options = @options
+ yield
+ end
+
+ def visit_return(node)
+ node.expr.options = @options
+ yield
+ end
+
+ def visit_rule(node)
+ node.rule.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
+ yield
+ end
+
+ def visit_variable(node)
+ node.expr.options = @options
+ yield
+ end
+
+ def visit_warn(node)
+ node.expr.options = @options
+ yield
+ end
+
+ def visit_while(node)
+ node.expr.options = @options
+ yield
+ end
+
+ def visit_directive(node)
+ node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
+ yield
+ end
+
+ def visit_media(node)
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
+ yield
+ end
+
+ def visit_cssimport(node)
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} if node.query
+ yield
+ end
+
+ def visit_supports(node)
+ node.condition.options = @options
+ yield
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/to_css.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/to_css.rb
new file mode 100644
index 0000000..0d5c03a
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/visitors/to_css.rb
@@ -0,0 +1,381 @@
+# A visitor for converting a Sass tree into CSS.
+class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
+ # The source mapping for the generated CSS file. This is only set if
+ # `build_source_mapping` is passed to the constructor and \{Sass::Engine#render} has been
+ # run.
+ attr_reader :source_mapping
+
+ # @param build_source_mapping [Boolean] Whether to build a
+ # \{Sass::Source::Map} while creating the CSS output. The mapping will
+ # be available from \{#source\_mapping} after the visitor has completed.
+ def initialize(build_source_mapping = false)
+ @tabs = 0
+ @line = 1
+ @offset = 1
+ @result = ""
+ @source_mapping = Sass::Source::Map.new if build_source_mapping
+ end
+
+ # Runs the visitor on `node`.
+ #
+ # @param node [Sass::Tree::Node] The root node of the tree to convert to CSS>
+ # @return [String] The CSS output.
+ def visit(node)
+ super
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ protected
+
+ def with_tabs(tabs)
+ old_tabs, @tabs = @tabs, tabs
+ yield
+ ensure
+ @tabs = old_tabs
+ end
+
+ # Associate all output produced in a block with a given node. Used for source
+ # mapping.
+ def for_node(node, attr_prefix = nil)
+ return yield unless @source_mapping
+ start_pos = Sass::Source::Position.new(@line, @offset)
+ yield
+
+ range_attr = attr_prefix ? :"#{attr_prefix}_source_range" : :source_range
+ return if node.invisible? || !node.send(range_attr)
+ source_range = node.send(range_attr)
+ target_end_pos = Sass::Source::Position.new(@line, @offset)
+ target_range = Sass::Source::Range.new(start_pos, target_end_pos, nil)
+ @source_mapping.add(source_range, target_range)
+ end
+
+ # Move the output cursor back `chars` characters.
+ def erase!(chars)
+ return if chars == 0
+ str = @result.slice!(-chars..-1)
+ newlines = str.count("\n")
+ if newlines > 0
+ @line -= newlines
+ @offset = @result[ result rindex("\n") || 0..-1].size
+ else
+ @offset -= chars
+ end
+ end
+
+ # Avoid allocating lots of new strings for `#output`. This is important
+ # because `#output` is called all the time.
+ NEWLINE = "\n"
+
+ # Add `s` to the output string and update the line and offset information
+ # accordingly.
+ def output(s)
+ if @lstrip
+ s = s.gsub(/\A\s+/, "")
+ @lstrip = false
+ end
+
+ newlines = s.count(NEWLINE)
+ if newlines > 0
+ @line += newlines
+ @offset = s[s.rindex(NEWLINE)..-1].size
+ else
+ @offset += s.size
+ end
+
+ @result << s
+ end
+
+ # Strip all trailing whitespace from the output string.
+ def rstrip!
+ erase! @result.length - 1 - (@result.rindex(/[^\s]/) || -1)
+ end
+
+ # lstrip the first output in the given block.
+ def lstrip
+ old_lstrip = @lstrip
+ @lstrip = true
+ yield
+ ensure
+ @lstrip = @lstrip && old_lstrip
+ end
+
+ # Prepend `prefix` to the output string.
+ def prepend!(prefix)
+ @result.insert 0, prefix
+ return unless @source_mapping
+
+ line_delta = prefix.count("\n")
+ offset_delta = prefix.gsub(/.*\n/, '').size
+ @source_mapping.shift_output_offsets(offset_delta)
+ @source_mapping.shift_output_lines(line_delta)
+ end
+
+ def visit_root(node)
+ node.children.each do |child|
+ next if child.invisible?
+ visit(child)
+ unless node.style == :compressed
+ output "\n"
+ if child.is_a?(Sass::Tree::DirectiveNode) && child.has_children && !child.bubbles?
+ output "\n"
+ end
+ end
+ end
+ rstrip!
+ return "" if @result.empty?
+
+ output "\n"
+
+ unless Sass::Util.ruby1_8? || @result.ascii_only?
+ if node.style == :compressed
+ # A byte order mark is sufficient to tell browsers that this
+ # file is UTF-8 encoded, and will override any other detection
+ # methods as per http://encoding.spec.whatwg.org/#decode-and-encode.
+ prepend! "\uFEFF"
+ else
+ prepend! "@charset \"UTF-8\";\n"
+ end
+ end
+
+ @result
+ rescue Sass::SyntaxError => e
+ e.sass_template ||= node.template
+ raise e
+ end
+
+ def visit_charset(node)
+ for_node(node) {output("@charset \"#{node.name}\";")}
+ end
+
+ def visit_comment(node)
+ return if node.invisible?
+ spaces = (' ' * [ tabs - node.resolved_value[/^ */].size, 0].max)
+
+ content = node.resolved_value.gsub(/^/, spaces)
+ if node.type == :silent
+ content.gsub!(%r{^(\s*)//(.*)$}) {|md| "#{$1}/*#{$2} */"}
+ end
+ if (node.style == :compact || node.style == :compressed) && node.type != :loud
+ content.gsub!(/\n +(\* *(?!\/))?/, ' ')
+ end
+ for_node(node) {output(content)}
+ end
+
+ # @comment
+ # rubocop:disable MethodLength
+ def visit_directive(node)
+ was_in_directive = @in_directive
+ tab_str = ' ' * @tabs
+ if !node.has_children || node.children.empty?
+ output(tab_str)
+ for_node(node) {output(node.resolved_value)}
+ output(!node.has_children ? ";" : " {}")
+ return
+ end
+
+ @in_directive = @in_directive || !node.is_a?(Sass::Tree::MediaNode)
+ output(tab_str) if node.style != :compressed
+ for_node(node) {output(node.resolved_value)}
+ output(node.style == :compressed ? "{" : " {")
+ output(node.style == :compact ? ' ' : "\n") if node.style != :compressed
+
+ was_prop = false
+ first = true
+ node.children.each do |child|
+ next if child.invisible?
+ if node.style == :compact
+ if child.is_a?(Sass::Tree::PropNode)
+ with_tabs(first || was_prop ? 0 : @tabs + 1) do
+ visit(child)
+ output(' ')
+ end
+ else
+ if was_prop
+ erase! 1
+ output "\n"
+ end
+
+ if first
+ lstrip {with_tabs(@tabs + 1) {visit(child)}}
+ else
+ with_tabs(@tabs + 1) {visit(child)}
+ end
+
+ rstrip!
+ output "\n"
+ end
+ was_prop = child.is_a?(Sass::Tree::PropNode)
+ first = false
+ elsif node.style == :compressed
+ output(was_prop ? ";" : "")
+ with_tabs(0) {visit(child)}
+ was_prop = child.is_a?(Sass::Tree::PropNode)
+ else
+ with_tabs(@tabs + 1) {visit(child)}
+ output "\n"
+ end
+ end
+ rstrip!
+ if node.style == :expanded
+ output("\n#{tab_str}")
+ elsif node.style != :compressed
+ output(" ")
+ end
+ output("}")
+ ensure
+ @in_directive = was_in_directive
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ def visit_media(node)
+ with_tabs(@tabs + node.tabs) {visit_directive(node)}
+ output("\n") if node.style != :compressed && node.group_end
+ end
+
+ def visit_supports(node)
+ visit_media(node)
+ end
+
+ def visit_cssimport(node)
+ visit_directive(node)
+ end
+
+ def visit_prop(node)
+ return if node.resolved_value.empty?
+ tab_str = ' ' * (@tabs + node.tabs)
+ output(tab_str)
+ for_node(node, :name) {output(node.resolved_name)}
+ if node.style == :compressed
+ output(":")
+ for_node(node, :value) {output(node.resolved_value)}
+ else
+ output(": ")
+ for_node(node, :value) {output(node.resolved_value)}
+ output(";")
+ end
+ end
+
+ # @comment
+ # rubocop:disable MethodLength
+ def visit_rule(node)
+ with_tabs(@tabs + node.tabs) do
+ rule_separator = node.style == :compressed ? ',' : ', '
+ line_separator =
+ case node.style
+ when :nested, :expanded; "\n"
+ when :compressed; ""
+ else; " "
+ end
+ rule_indent = ' ' * @tabs
+ per_rule_indent, total_indent = if [:nested, :expanded].include?(node.style)
+ [rule_indent, '']
+ else
+ ['', rule_indent]
+ end
+
+ joined_rules = node.resolved_rules.members.map do |seq|
+ next if seq.has_placeholder?
+ rule_part = seq.to_s
+ if node.style == :compressed
+ rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
+ rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
+ rule_part.strip!
+ end
+ rule_part
+ end.compact.join(rule_separator)
+
+ joined_rules.lstrip!
+ joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
+
+ old_spaces = ' ' * @tabs
+ if node.style != :compressed
+ if node.options[:debug_info] && ! in_directive
+ visit(debug_info_rule(node.debug_info, node.options))
+ output "\n"
+ elsif node.options[:trace_selectors]
+ output("#{old_spaces}/* ")
+ output(node.stack_trace.gsub("\n", "\n #{old_spaces}"))
+ output(" */\n")
+ elsif node.options[:line_comments]
+ output("#{old_spaces}/* line #{node.line}")
+
+ if node.filename
+ relative_filename =
+ if node.options[:css_filename]
+ begin
+ Sass::Util.relative_path_from(
+ node.filename, File.dirname(node.options[:css_filename])).to_s
+ rescue ArgumentError
+ nil
+ end
+ end
+ relative_filename ||= node.filename
+ output(", #{relative_filename}")
+ end
+
+ output(" */\n")
+ end
+ end
+
+ end_props, trailer, tabs = '', '', 0
+ if node.style == :compact
+ separator, end_props, bracket = ' ', ' ', ' { '
+ trailer = "\n" if node.group_end
+ elsif node.style == :compressed
+ separator, bracket = ';', '{'
+ else
+ tabs = @tabs + 1
+ separator, bracket = "\n", " {\n"
+ trailer = "\n" if node.group_end
+ end_props = (node.style == :expanded ? "\n" + old_spaces : ' ')
+ end
+ output(total_indent + per_rule_indent)
+ for_node(node, :selector) {output(joined_rules)}
+ output(bracket)
+
+ with_tabs(tabs) do
+ node.children.each_with_index do |child, i|
+ output(separator) if i > 0
+ visit(child)
+ end
+ end
+
+ output(end_props)
+ output("}" + trailer)
+ end
+ end
+ # @comment
+ # rubocop:enable MethodLength
+
+ def visit_keyframerule(node)
+ visit_directive(node)
+ end
+
+ private
+
+ def debug_info_rule(debug_info, options)
+ node = Sass::Tree::DirectiveNode.resolved("@media -sass-debug-info")
+ Sass::Util.hash_to_a(debug_info.map {|k, v| [k.to_s, v.to_s]}).each do |k, v|
+ rule = Sass::Tree::RuleNode.new([""])
+ rule.resolved_rules = Sass::Selector::CommaSequence.new(
+ [Sass::Selector::Sequence.new(
+ [Sass::Selector::SimpleSequence.new(
+ [Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)],
+ false)
+ ])
+ ])
+ prop = Sass::Tree::PropNode.new([""], Sass::Script::Value::String.new(''), :new)
+ prop.resolved_name = "font-family"
+ prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
+ rule << prop
+ node << rule
+ end
+ node.options = options.merge(:debug_info => false,
+ :line_comments => false,
+ :style => :compressed)
+ node
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/warn_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/warn_node.rb
new file mode 100644
index 0000000..4af4789
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/warn_node.rb
@@ -0,0 +1,18 @@
+module Sass
+ module Tree
+ # A dynamic node representing a Sass ` warn` statement.
+ #
+ # @see Sass::Tree
+ class WarnNode < Node
+ # The expression to print.
+ # @return [Script::Tree::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Tree::Node] The expression to print
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/tree/while_node.rb
b/backends/css/gems/sass-3.4.9/lib/sass/tree/while_node.rb
new file mode 100644
index 0000000..93529f0
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/tree/while_node.rb
@@ -0,0 +1,18 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A dynamic node representing a Sass ` while` loop.
+ #
+ # @see Sass::Tree
+ class WhileNode < Node
+ # The parse tree for the continuation expression.
+ # @return [Script::Tree::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Tree::Node] See \{#expr}
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/util.rb b/backends/css/gems/sass-3.4.9/lib/sass/util.rb
new file mode 100644
index 0000000..4fcc45f
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/util.rb
@@ -0,0 +1,1376 @@
+# -*- coding: utf-8 -*-
+require 'erb'
+require 'set'
+require 'enumerator'
+require 'stringio'
+require 'rbconfig'
+require 'uri'
+require 'thread'
+require 'pathname'
+
+require 'sass/root'
+require 'sass/util/subset_map'
+
+module Sass
+ # A module containing various useful functions.
+ module Util
+ extend self
+
+ # An array of ints representing the Ruby version number.
+ # @api public
+ RUBY_VERSION_COMPONENTS = RUBY_VERSION.split(".").map {|s| s.to_i}
+
+ # The Ruby engine we're running under. Defaults to `"ruby"`
+ # if the top-level constant is undefined.
+ # @api public
+ RUBY_ENGINE = defined?(::RUBY_ENGINE) ? ::RUBY_ENGINE : "ruby"
+
+ # Returns the path of a file relative to the Sass root directory.
+ #
+ # @param file [String] The filename relative to the Sass root
+ # @return [String] The filename relative to the the working directory
+ def scope(file)
+ File.join(Sass::ROOT_DIR, file)
+ end
+
+ # Converts an array of `[key, value]` pairs to a hash.
+ #
+ # @example
+ # to_hash([[:foo, "bar"], [:baz, "bang"]])
+ # #=> {:foo => "bar", :baz => "bang"}
+ # @param arr [Array<(Object, Object)>] An array of pairs
+ # @return [Hash] A hash
+ def to_hash(arr)
+ ordered_hash(*arr.compact)
+ end
+
+ # Maps the keys in a hash according to a block.
+ #
+ # @example
+ # map_keys({:foo => "bar", :baz => "bang"}) {|k| k.to_s}
+ # #=> {"foo" => "bar", "baz" => "bang"}
+ # @param hash [Hash] The hash to map
+ # @yield [key] A block in which the keys are transformed
+ # @yieldparam key [Object] The key that should be mapped
+ # @yieldreturn [Object] The new value for the key
+ # @return [Hash] The mapped hash
+ # @see #map_vals
+ # @see #map_hash
+ def map_keys(hash)
+ map_hash(hash) {|k, v| [yield(k), v]}
+ end
+
+ # Maps the values in a hash according to a block.
+ #
+ # @example
+ # map_values({:foo => "bar", :baz => "bang"}) {|v| v.to_sym}
+ # #=> {:foo => :bar, :baz => :bang}
+ # @param hash [Hash] The hash to map
+ # @yield [value] A block in which the values are transformed
+ # @yieldparam value [Object] The value that should be mapped
+ # @yieldreturn [Object] The new value for the value
+ # @return [Hash] The mapped hash
+ # @see #map_keys
+ # @see #map_hash
+ def map_vals(hash)
+ # We don't delegate to map_hash for performance here
+ # because map_hash does more than is necessary.
+ rv = hash.class.new
+ hash = hash.as_stored if hash.is_a?(NormalizedMap)
+ hash.each do |k, v|
+ rv[k] = yield(v)
+ end
+ rv
+ end
+
+ # Maps the key-value pairs of a hash according to a block.
+ #
+ # @example
+ # map_hash({:foo => "bar", :baz => "bang"}) {|k, v| [k.to_s, v.to_sym]}
+ # #=> {"foo" => :bar, "baz" => :bang}
+ # @param hash [Hash] The hash to map
+ # @yield [key, value] A block in which the key-value pairs are transformed
+ # @yieldparam [key] The hash key
+ # @yieldparam [value] The hash value
+ # @yieldreturn [(Object, Object)] The new value for the `[key, value]` pair
+ # @return [Hash] The mapped hash
+ # @see #map_keys
+ # @see #map_vals
+ def map_hash(hash)
+ # Copy and modify is more performant than mapping to an array and using
+ # to_hash on the result.
+ rv = hash.class.new
+ hash.each do |k, v|
+ new_key, new_value = yield(k, v)
+ new_key = hash.denormalize(new_key) if hash.is_a?(NormalizedMap) && new_key == k
+ rv[new_key] = new_value
+ end
+ rv
+ end
+
+ # Computes the powerset of the given array.
+ # This is the set of all subsets of the array.
+ #
+ # @example
+ # powerset([1, 2, 3]) #=>
+ # Set[Set[], Set[1], Set[2], Set[3], Set[1, 2], Set[2, 3], Set[1, 3], Set[1, 2, 3]]
+ # @param arr [Enumerable]
+ # @return [Set<Set>] The subsets of `arr`
+ def powerset(arr)
+ arr.inject([Set.new].to_set) do |powerset, el|
+ new_powerset = Set.new
+ powerset.each do |subset|
+ new_powerset << subset
+ new_powerset << subset + [el]
+ end
+ new_powerset
+ end
+ end
+
+ # Restricts a number to falling within a given range.
+ # Returns the number if it falls within the range,
+ # or the closest value in the range if it doesn't.
+ #
+ # @param value [Numeric]
+ # @param range [Range<Numeric>]
+ # @return [Numeric]
+ def restrict(value, range)
+ [[value, range.first].max, range.last].min
+ end
+
+ # Concatenates all strings that are adjacent in an array,
+ # while leaving other elements as they are.
+ #
+ # @example
+ # merge_adjacent_strings([1, "foo", "bar", 2, "baz"])
+ # #=> [1, "foobar", 2, "baz"]
+ # @param arr [Array]
+ # @return [Array] The enumerable with strings merged
+ def merge_adjacent_strings(arr)
+ # Optimize for the common case of one element
+ return arr if arr.size < 2
+ arr.inject([]) do |a, e|
+ if e.is_a?(String)
+ if a.last.is_a?(String)
+ a.last << e
+ else
+ a << e.dup
+ end
+ else
+ a << e
+ end
+ a
+ end
+ end
+
+ # Non-destructively replaces all occurrences of a subsequence in an array
+ # with another subsequence.
+ #
+ # @example
+ # replace_subseq([1, 2, 3, 4, 5], [2, 3], [:a, :b])
+ # #=> [1, :a, :b, 4, 5]
+ #
+ # @param arr [Array] The array whose subsequences will be replaced.
+ # @param subseq [Array] The subsequence to find and replace.
+ # @param replacement [Array] The sequence that `subseq` will be replaced with.
+ # @return [Array] `arr` with `subseq` replaced with `replacement`.
+ def replace_subseq(arr, subseq, replacement)
+ new = []
+ matched = []
+ i = 0
+ arr.each do |elem|
+ if elem != subseq[i]
+ new.push(*matched)
+ matched = []
+ i = 0
+ new << elem
+ next
+ end
+
+ if i == subseq.length - 1
+ matched = []
+ i = 0
+ new.push(*replacement)
+ else
+ matched << elem
+ i += 1
+ end
+ end
+ new.push(*matched)
+ new
+ end
+
+ # Intersperses a value in an enumerable, as would be done with `Array#join`
+ # but without concatenating the array together afterwards.
+ #
+ # @param enum [Enumerable]
+ # @param val
+ # @return [Array]
+ def intersperse(enum, val)
+ enum.inject([]) {|a, e| a << e << val}[0...-1]
+ end
+
+ def slice_by(enum)
+ results = []
+ enum.each do |value|
+ key = yield(value)
+ if !results.empty? && results.last.first == key
+ results.last.last << value
+ else
+ results << [key, [value]]
+ end
+ end
+ results
+ end
+
+ # Substitutes a sub-array of one array with another sub-array.
+ #
+ # @param ary [Array] The array in which to make the substitution
+ # @param from [Array] The sequence of elements to replace with `to`
+ # @param to [Array] The sequence of elements to replace `from` with
+ def substitute(ary, from, to)
+ res = ary.dup
+ i = 0
+ while i < res.size
+ if res[i...i + from.size] == from
+ res[i...i + from.size] = to
+ end
+ i += 1
+ end
+ res
+ end
+
+ # Destructively strips whitespace from the beginning and end
+ # of the first and last elements, respectively,
+ # in the array (if those elements are strings).
+ #
+ # @param arr [Array]
+ # @return [Array] `arr`
+ def strip_string_array(arr)
+ arr.first.lstrip! if arr.first.is_a?(String)
+ arr.last.rstrip! if arr.last.is_a?(String)
+ arr
+ end
+
+ # Return an array of all possible paths through the given arrays.
+ #
+ # @param arrs [Array<Array>]
+ # @return [Array<Arrays>]
+ #
+ # @example
+ # paths([[1, 2], [3, 4], [5]]) #=>
+ # # [[1, 3, 5],
+ # # [2, 3, 5],
+ # # [1, 4, 5],
+ # # [2, 4, 5]]
+ def paths(arrs)
+ arrs.inject([[]]) do |paths, arr|
+ flatten(arr.map {|e| paths.map {|path| path + [e]}}, 1)
+ end
+ end
+
+ # Computes a single longest common subsequence for `x` and `y`.
+ # If there are more than one longest common subsequences,
+ # the one returned is that which starts first in `x`.
+ #
+ # @param x [Array]
+ # @param y [Array]
+ # @yield [a, b] An optional block to use in place of a check for equality
+ # between elements of `x` and `y`.
+ # @yieldreturn [Object, nil] If the two values register as equal,
+ # this will return the value to use in the LCS array.
+ # @return [Array] The LCS
+ def lcs(x, y, &block)
+ x = [nil, *x]
+ y = [nil, *y]
+ block ||= proc {|a, b| a == b && a}
+ lcs_backtrace(lcs_table(x, y, &block), x, y, x.size - 1, y.size - 1, &block)
+ end
+
+ # Converts a Hash to an Array. This is usually identical to `Hash#to_a`,
+ # with the following exceptions:
+ #
+ # * In Ruby 1.8, `Hash#to_a` is not deterministically ordered, but this is.
+ # * In Ruby 1.9 when running tests, this is ordered in the same way it would
+ # be under Ruby 1.8 (sorted key order rather than insertion order).
+ #
+ # @param hash [Hash]
+ # @return [Array]
+ def hash_to_a(hash)
+ return hash.to_a unless ruby1_8? || defined?(Test::Unit)
+ hash.sort_by {|k, v| k}
+ end
+
+ # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed
+ # order. Unlike {Util#hash_to_a}, the resulting order isn't sorted key order;
+ # instead, it's the same order as `#group_by` has under Ruby 1.9 (key
+ # appearance order).
+ #
+ # @param enum [Enumerable]
+ # @return [Array<[Object, Array]>] An array of pairs.
+ def group_by_to_a(enum)
+ return enum.group_by {|e| yield(e)}.to_a unless ruby1_8?
+ order = {}
+ arr = []
+ groups = enum.group_by do |e|
+ res = yield(e)
+ unless order.include?(res)
+ order[res] = order.size
+ end
+ res
+ end
+ groups.each do |key, vals|
+ arr[order[key]] = [key, vals]
+ end
+ arr
+ end
+
+ # Returns a sub-array of `minuend` containing only elements that are also in
+ # `subtrahend`. Ensures that the return value has the same order as
+ # `minuend`, even on Rubinius where that's not guaranteed by `Array#-`.
+ #
+ # @param minuend [Array]
+ # @param subtrahend [Array]
+ # @return [Array]
+ def array_minus(minuend, subtrahend)
+ return minuend - subtrahend unless rbx?
+ set = Set.new(minuend) - subtrahend
+ minuend.select {|e| set.include?(e)}
+ end
+
+ # Returns the maximum of `val1` and `val2`. We use this over \{Array.max} to
+ # avoid unnecessary garbage collection.
+ def max(val1, val2)
+ val1 > val2 ? val1 : val2
+ end
+
+ # Returns the minimum of `val1` and `val2`. We use this over \{Array.min} to
+ # avoid unnecessary garbage collection.
+ def min(val1, val2)
+ val1 <= val2 ? val1 : val2
+ end
+
+ # Returns a string description of the character that caused an
+ # `Encoding::UndefinedConversionError`.
+ #
+ # @param e [Encoding::UndefinedConversionError]
+ # @return [String]
+ def undefined_conversion_error_char(e)
+ # Rubinius (as of 2.0.0.rc1) pre-quotes the error character.
+ return e.error_char if rbx?
+ # JRuby (as of 1.7.2) doesn't have an error_char field on
+ # Encoding::UndefinedConversionError.
+ return e.error_char.dump unless jruby?
+ e.message[/^"[^"]+"/] # "
+ end
+
+ # Asserts that `value` falls within `range` (inclusive), leaving
+ # room for slight floating-point errors.
+ #
+ # @param name [String] The name of the value. Used in the error message.
+ # @param range [Range] The allowed range of values.
+ # @param value [Numeric, Sass::Script::Value::Number] The value to check.
+ # @param unit [String] The unit of the value. Used in error reporting.
+ # @return [Numeric] `value` adjusted to fall within range, if it
+ # was outside by a floating-point margin.
+ def check_range(name, range, value, unit = '')
+ grace = (-0.00001..0.00001)
+ str = value.to_s
+ value = value.value if value.is_a?(Sass::Script::Value::Number)
+ return value if range.include?(value)
+ return range.first if grace.include?(value - range.first)
+ return range.last if grace.include?(value - range.last)
+ raise ArgumentError.new(
+ "#{name} #{str} must be between #{range.first}#{unit} and #{range.last}#{unit}")
+ end
+
+ # Returns whether or not `seq1` is a subsequence of `seq2`. That is, whether
+ # or not `seq2` contains every element in `seq1` in the same order (and
+ # possibly more elements besides).
+ #
+ # @param seq1 [Array]
+ # @param seq2 [Array]
+ # @return [Boolean]
+ def subsequence?(seq1, seq2)
+ i = j = 0
+ loop do
+ return true if i == seq1.size
+ return false if j == seq2.size
+ i += 1 if seq1[i] == seq2[j]
+ j += 1
+ end
+ end
+
+ # Returns information about the caller of the previous method.
+ #
+ # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
+ # @return [[String, Fixnum, (String, nil)]]
+ # An array containing the filename, line, and method name of the caller.
+ # The method name may be nil
+ def caller_info(entry = nil)
+ # JRuby evaluates `caller` incorrectly when it's in an actual default argument.
+ entry ||= caller[1]
+ info = entry.scan(/^((?:[A-Za-z]:)?.*?):(-?.*?)(?::.*`(.+)')?$/).first
+ info[1] = info[1].to_i
+ # This is added by Rubinius to designate a block, but we don't care about it.
+ info[2].sub!(/ \{\}\Z/, '') if info[2]
+ info
+ end
+
+ # Returns whether one version string represents a more recent version than another.
+ #
+ # @param v1 [String] A version string.
+ # @param v2 [String] Another version string.
+ # @return [Boolean]
+ def version_gt(v1, v2)
+ # Construct an array to make sure the shorter version is padded with nil
+ Array.new([v1.length, v2.length].max).zip(v1.split("."), v2.split(".")) do |_, p1, p2|
+ p1 ||= "0"
+ p2 ||= "0"
+ release1 = p1 =~ /^[0-9]+$/
+ release2 = p2 =~ /^[0-9]+$/
+ if release1 && release2
+ # Integer comparison if both are full releases
+ p1, p2 = p1.to_i, p2.to_i
+ next if p1 == p2
+ return p1 > p2
+ elsif !release1 && !release2
+ # String comparison if both are prereleases
+ next if p1 == p2
+ return p1 > p2
+ else
+ # If only one is a release, that one is newer
+ return release1
+ end
+ end
+ end
+
+ # Returns whether one version string represents the same or a more
+ # recent version than another.
+ #
+ # @param v1 [String] A version string.
+ # @param v2 [String] Another version string.
+ # @return [Boolean]
+ def version_geq(v1, v2)
+ version_gt(v1, v2) || !version_gt(v2, v1)
+ end
+
+ # Throws a NotImplementedError for an abstract method.
+ #
+ # @param obj [Object] `self`
+ # @raise [NotImplementedError]
+ def abstract(obj)
+ raise NotImplementedError.new("#{obj.class} must implement ##{caller_info[2]}")
+ end
+
+ # Prints a deprecation warning for the caller method.
+ #
+ # @param obj [Object] `self`
+ # @param message [String] A message describing what to do instead.
+ def deprecated(obj, message = nil)
+ obj_class = obj.is_a?(Class) ? "#{obj}." : "#{obj.class}#"
+ full_message = "DEPRECATION WARNING: #{obj_class}#{caller_info[2]} " +
+ "will be removed in a future version of Sass.#{("\n" + message) if message}"
+ Sass::Util.sass_warn full_message
+ end
+
+ # Silence all output to STDERR within a block.
+ #
+ # @yield A block in which no output will be printed to STDERR
+ def silence_warnings
+ the_real_stderr, $stderr = $stderr, StringIO.new
+ yield
+ ensure
+ $stderr = the_real_stderr
+ end
+
+ # Silences all Sass warnings within a block.
+ #
+ # @yield A block in which no Sass warnings will be printed
+ def silence_sass_warnings
+ old_level, Sass.logger.log_level = Sass.logger.log_level, :error
+ yield
+ ensure
+ Sass.logger.log_level = old_level
+ end
+
+ # The same as `Kernel#warn`, but is silenced by \{#silence\_sass\_warnings}.
+ #
+ # @param msg [String]
+ def sass_warn(msg)
+ msg = msg + "\n" unless ruby1?
+ Sass.logger.warn(msg)
+ end
+
+ ## Cross Rails Version Compatibility
+
+ # Returns the root of the Rails application,
+ # if this is running in a Rails context.
+ # Returns `nil` if no such root is defined.
+ #
+ # @return [String, nil]
+ def rails_root
+ if defined?(::Rails.root)
+ return ::Rails.root.to_s if ::Rails.root
+ raise "ERROR: Rails.root is nil!"
+ end
+ return RAILS_ROOT.to_s if defined?(RAILS_ROOT)
+ nil
+ end
+
+ # Returns the environment of the Rails application,
+ # if this is running in a Rails context.
+ # Returns `nil` if no such environment is defined.
+ #
+ # @return [String, nil]
+ def rails_env
+ return ::Rails.env.to_s if defined?(::Rails.env)
+ return RAILS_ENV.to_s if defined?(RAILS_ENV)
+ nil
+ end
+
+ # Returns whether this environment is using ActionPack
+ # version 3.0.0 or greater.
+ #
+ # @return [Boolean]
+ def ap_geq_3?
+ ap_geq?("3.0.0.beta1")
+ end
+
+ # Returns whether this environment is using ActionPack
+ # of a version greater than or equal to that specified.
+ #
+ # @param version [String] The string version number to check against.
+ # Should be greater than or equal to Rails 3,
+ # because otherwise ActionPack::VERSION isn't autoloaded
+ # @return [Boolean]
+ def ap_geq?(version)
+ # The ActionPack module is always loaded automatically in Rails >= 3
+ return false unless defined?(ActionPack) && defined?(ActionPack::VERSION) &&
+ defined?(ActionPack::VERSION::STRING)
+
+ version_geq(ActionPack::VERSION::STRING, version)
+ end
+
+ # Returns whether this environment is using Listen
+ # version 2.0.0 or greater.
+ #
+ # @return [Boolean]
+ def listen_geq_2?
+ return @listen_geq_2 unless @listen_geq_2.nil?
+ @listen_geq_2 =
+ begin
+ require 'listen/version'
+ version_geq(::Listen::VERSION, '2.0.0')
+ rescue LoadError
+ false
+ end
+ end
+
+ # Returns an ActionView::Template* class.
+ # In pre-3.0 versions of Rails, most of these classes
+ # were of the form `ActionView::TemplateFoo`,
+ # while afterwards they were of the form `ActionView;:Template::Foo`.
+ #
+ # @param name [#to_s] The name of the class to get.
+ # For example, `:Error` will return `ActionView::TemplateError`
+ # or `ActionView::Template::Error`.
+ def av_template_class(name)
+ return ActionView.const_get("Template#{name}") if ActionView.const_defined?("Template#{name}")
+ ActionView::Template.const_get(name.to_s)
+ end
+
+ ## Cross-OS Compatibility
+ #
+ # These methods are cached because some of them are called quite frequently
+ # and even basic checks like String#== are too costly to be called repeatedly.
+
+ # Whether or not this is running on Windows.
+ #
+ # @return [Boolean]
+ def windows?
+ return @windows if defined?(@windows)
+ @windows = (RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i)
+ end
+
+ # Whether or not this is running on IronRuby.
+ #
+ # @return [Boolean]
+ def ironruby?
+ return @ironruby if defined?(@ironruby)
+ @ironruby = RUBY_ENGINE == "ironruby"
+ end
+
+ # Whether or not this is running on Rubinius.
+ #
+ # @return [Boolean]
+ def rbx?
+ return @rbx if defined?(@rbx)
+ @rbx = RUBY_ENGINE == "rbx"
+ end
+
+ # Whether or not this is running on JRuby.
+ #
+ # @return [Boolean]
+ def jruby?
+ return @jruby if defined?(@jruby)
+ @jruby = RUBY_PLATFORM =~ /java/
+ end
+
+ # Returns an array of ints representing the JRuby version number.
+ #
+ # @return [Array<Fixnum>]
+ def jruby_version
+ @jruby_version ||= ::JRUBY_VERSION.split(".").map {|s| s.to_i}
+ end
+
+ # Like `Dir.glob`, but works with backslash-separated paths on Windows.
+ #
+ # @param path [String]
+ def glob(path)
+ path = path.gsub('\\', '/') if windows?
+ if block_given?
+ Dir.glob(path) {|f| yield(f)}
+ else
+ Dir.glob(path)
+ end
+ end
+
+ # Like `Pathname.new`, but normalizes Windows paths to always use backslash
+ # separators.
+ #
+ # `Pathname#relative_path_from` can break if the two pathnames aren't
+ # consistent in their slash style.
+ #
+ # @param path [String]
+ # @return [Pathname]
+ def pathname(path)
+ path = path.tr("/", "\\") if windows?
+ Pathname.new(path)
+ end
+
+ # Like `Pathname#cleanpath`, but normalizes Windows paths to always use
+ # backslash separators. Normally, `Pathname#cleanpath` actually does the
+ # reverse -- it will convert backslashes to forward slashes, which can break
+ # `Pathname#relative_path_from`.
+ #
+ # @param path [String, Pathname]
+ # @return [Pathname]
+ def cleanpath(path)
+ path = Pathname.new(path) unless path.is_a?(Pathname)
+ pathname(path.cleanpath.to_s)
+ end
+
+ # Returns `path` with all symlinks resolved.
+ #
+ # @param path [String, Pathname]
+ # @return [Pathname]
+ def realpath(path)
+ path = Pathname.new(path) unless path.is_a?(Pathname)
+
+ # Explicitly DON'T run #pathname here. We don't want to convert
+ # to Windows directory separators because we're comparing these
+ # against the paths returned by Listen, which use forward
+ # slashes everywhere.
+ begin
+ path.realpath
+ rescue SystemCallError
+ # If [path] doesn't actually exist, don't bail, just
+ # return the original.
+ path
+ end
+ end
+
+ # Returns `path` relative to `from`.
+ #
+ # This is like `Pathname#relative_path_from` except it accepts both strings
+ # and pathnames, it handles Windows path separators correctly, and it throws
+ # an error rather than crashing if the paths use different encodings
+ # (https://github.com/ruby/ruby/pull/713).
+ #
+ # @param path [String, Pathname]
+ # @param from [String, Pathname]
+ # @return [Pathname?]
+ def relative_path_from(path, from)
+ pathname(path.to_s).relative_path_from(pathname(from.to_s))
+ rescue NoMethodError => e
+ raise e unless e.name == :zero?
+
+ # Work around https://github.com/ruby/ruby/pull/713.
+ path = path.to_s
+ from = from.to_s
+ raise ArgumentError("Incompatible path encodings: #{path.inspect} is #{path.encoding}, " +
+ "#{from.inspect} is #{from.encoding}")
+ end
+
+ # Converts `path` to a "file:" URI. This handles Windows paths correctly.
+ #
+ # @param path [String, Pathname]
+ # @return [String]
+ def file_uri_from_path(path)
+ path = path.to_s if path.is_a?(Pathname)
+ path = path.tr('\\', '/') if windows?
+ path = Sass::Util.escape_uri(path)
+ return path.start_with?('/') ? "file://" + path : path unless windows?
+ return "file:///" + path.tr("\\", "/") if path =~ /^[a-zA-Z]:[\/\\]/
+ return "file:" + path.tr("\\", "/") if path =~ /\\\\[^\\]+\\[^\\\/]+/
+ path.tr("\\", "/")
+ end
+
+ # Retries a filesystem operation if it fails on Windows. Windows
+ # has weird and flaky locking rules that can cause operations to fail.
+ #
+ # @yield [] The filesystem operation.
+ def retry_on_windows
+ return yield unless windows?
+
+ begin
+ yield
+ rescue SystemCallError
+ sleep 0.1
+ yield
+ end
+ end
+
+ # Prepare a value for a destructuring assignment (e.g. `a, b =
+ # val`). This works around a performance bug when using
+ # ActiveSupport, and only needs to be called when `val` is likely
+ # to be `nil` reasonably often.
+ #
+ # See [this bug report](http://redmine.ruby-lang.org/issues/4917).
+ #
+ # @param val [Object]
+ # @return [Object]
+ def destructure(val)
+ val || []
+ end
+
+ ## Cross-Ruby-Version Compatibility
+
+ # Whether or not this is running under a Ruby version under 2.0.
+ #
+ # @return [Boolean]
+ def ruby1?
+ return @ruby1 if defined?(@ruby1)
+ @ruby1 = RUBY_VERSION_COMPONENTS[0] <= 1
+ end
+
+ # Whether or not this is running under Ruby 1.8 or lower.
+ #
+ # Note that IronRuby counts as Ruby 1.8,
+ # because it doesn't support the Ruby 1.9 encoding API.
+ #
+ # @return [Boolean]
+ def ruby1_8?
+ # IronRuby says its version is 1.9, but doesn't support any of the encoding APIs.
+ # We have to fall back to 1.8 behavior.
+ return @ruby1_8 if defined?(@ruby1_8)
+ @ruby1_8 = ironruby? ||
+ (RUBY_VERSION_COMPONENTS[0] == 1 && RUBY_VERSION_COMPONENTS[1] < 9)
+ end
+
+ # Whether or not this is running under Ruby 1.8.6 or lower.
+ # Note that lower versions are not officially supported.
+ #
+ # @return [Boolean]
+ def ruby1_8_6?
+ return @ruby1_8_6 if defined?(@ruby1_8_6)
+ @ruby1_8_6 = ruby1_8? && RUBY_VERSION_COMPONENTS[2] < 7
+ end
+
+ # Whether or not this is running under Ruby 1.9.2 exactly.
+ #
+ # @return [Boolean]
+ def ruby1_9_2?
+ return @ruby1_9_2 if defined?(@ruby1_9_2)
+ @ruby1_9_2 = RUBY_VERSION_COMPONENTS == [1, 9, 2]
+ end
+
+ # Wehter or not this is running under JRuby 1.6 or lower.
+ def jruby1_6?
+ return @jruby1_6 if defined?(@jruby1_6)
+ @jruby1_6 = jruby? && jruby_version[0] == 1 && jruby_version[1] < 7
+ end
+
+ # Whether or not this is running under MacRuby.
+ #
+ # @return [Boolean]
+ def macruby?
+ return @macruby if defined?(@macruby)
+ @macruby = RUBY_ENGINE == 'macruby'
+ end
+
+ require 'sass/util/ordered_hash' if ruby1_8?
+
+ # Converts a hash or a list of pairs into an order-preserving hash.
+ #
+ # On Ruby 1.8.7, this uses the orderedhash gem to simulate an
+ # order-preserving hash. On Ruby 1.9 and up, it just uses the native Hash
+ # class, since that preserves the order itself.
+ #
+ # @overload ordered_hash(hash)
+ # @param hash [Hash] a normal hash to convert to an ordered hash
+ # @return [Hash]
+ # @overload ordered_hash(*pairs)
+ # @example
+ # ordered_hash([:foo, "bar"], [:baz, "bang"])
+ # #=> {:foo => "bar", :baz => "bang"}
+ # ordered_hash #=> {}
+ # @param pairs [Array<(Object, Object)>] the list of key/value pairs for
+ # the hash.
+ # @return [Hash]
+ def ordered_hash(*pairs_or_hash)
+ if pairs_or_hash.length == 1 && pairs_or_hash.first.is_a?(Hash)
+ hash = pairs_or_hash.first
+ return hash unless ruby1_8?
+ return OrderedHash.new.merge hash
+ end
+
+ return Hash[pairs_or_hash] unless ruby1_8?
+ (pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*flatten(pairs_or_hash, 1)]
+ end
+
+ unless ruby1_8?
+ CHARSET_REGEXP = /\A charset "([^"]+)"/
+ UTF_8_BOM = "\xEF\xBB\xBF".force_encoding('BINARY')
+ UTF_16BE_BOM = "\xFE\xFF".force_encoding('BINARY')
+ UTF_16LE_BOM = "\xFF\xFE".force_encoding('BINARY')
+ end
+
+ # Like {\#check\_encoding}, but also checks for a ` charset` declaration
+ # at the beginning of the file and uses that encoding if it exists.
+ #
+ # Sass follows CSS's decoding rules.
+ #
+ # @param str [String] The string of which to check the encoding
+ # @return [(String, Encoding)] The original string encoded as UTF-8,
+ # and the source encoding of the string (or `nil` under Ruby 1.8)
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
+ # cannot be converted to UTF-8
+ # @raise [ArgumentError] if the document uses an unknown encoding with ` charset`
+ # @raise [Sass::SyntaxError] If the document declares an encoding that
+ # doesn't match its contents, or it doesn't declare an encoding and its
+ # contents are invalid in the native encoding.
+ def check_sass_encoding(str)
+ # On Ruby 1.8 we can't do anything complicated with encodings.
+ # Instead, we just strip out a UTF-8 BOM if it exists and
+ # sanitize according to Section 3.3 of CSS Syntax Level 3. We
+ # don't sanitize null characters since they might be components
+ # of other characters.
+ if ruby1_8?
+ return str.gsub(/\A\xEF\xBB\xBF/, '').gsub(/\r\n?|\f/, "\n"), nil
+ end
+
+ # Determine the fallback encoding following section 3.2 of CSS Syntax Level 3 and Encodings:
+ # http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#determine-the-fallback-encoding
+ # http://encoding.spec.whatwg.org/#decode
+ binary = str.dup.force_encoding("BINARY")
+ if binary.start_with?(UTF_8_BOM)
+ binary.slice! 0, UTF_8_BOM.length
+ str = binary.force_encoding('UTF-8')
+ elsif binary.start_with?(UTF_16BE_BOM)
+ binary.slice! 0, UTF_16BE_BOM.length
+ str = binary.force_encoding('UTF-16BE')
+ elsif binary.start_with?(UTF_16LE_BOM)
+ binary.slice! 0, UTF_16LE_BOM.length
+ str = binary.force_encoding('UTF-16LE')
+ elsif binary =~ CHARSET_REGEXP
+ charset = $1.force_encoding('US-ASCII')
+ # Ruby 1.9.2 doesn't recognize a UTF-16 encoding without an endian marker.
+ if ruby1_9_2? && charset.downcase == 'utf-16'
+ encoding = Encoding.find('UTF-8')
+ else
+ encoding = Encoding.find(charset)
+ if encoding.name == 'UTF-16' || encoding.name == 'UTF-16BE'
+ encoding = Encoding.find('UTF-8')
+ end
+ end
+ str = binary.force_encoding(encoding)
+ elsif str.encoding.name == "ASCII-8BIT"
+ # Normally we want to fall back on believing the Ruby string
+ # encoding, but if that's just binary we want to make sure
+ # it's valid UTF-8.
+ str = str.force_encoding('utf-8')
+ end
+
+ find_encoding_error(str) unless str.valid_encoding?
+
+ begin
+ # If the string is valid, preprocess it according to section 3.3 of CSS Syntax Level 3.
+ return str.encode("UTF-8").gsub(/\r\n?|\f/, "\n").tr("\u0000", "�"), str.encoding
+ rescue EncodingError
+ find_encoding_error(str)
+ end
+ end
+
+ # Checks to see if a class has a given method.
+ # For example:
+ #
+ # Sass::Util.has?(:public_instance_method, String, :gsub) #=> true
+ #
+ # Method collections like `Class#instance_methods`
+ # return strings in Ruby 1.8 and symbols in Ruby 1.9 and on,
+ # so this handles checking for them in a compatible way.
+ #
+ # @param attr [#to_s] The (singular) name of the method-collection method
+ # (e.g. `:instance_methods`, `:private_methods`)
+ # @param klass [Module] The class to check the methods of which to check
+ # @param method [String, Symbol] The name of the method do check for
+ # @return [Boolean] Whether or not the given collection has the given method
+ def has?(attr, klass, method)
+ klass.send("#{attr}s").include?(ruby1_8? ? method.to_s : method.to_sym)
+ end
+
+ # A version of `Enumerable#enum_with_index` that works in Ruby 1.8 and 1.9.
+ #
+ # @param enum [Enumerable] The enumerable to get the enumerator for
+ # @return [Enumerator] The with-index enumerator
+ def enum_with_index(enum)
+ ruby1_8? ? enum.enum_with_index : enum.each_with_index
+ end
+
+ # A version of `Enumerable#enum_cons` that works in Ruby 1.8 and 1.9.
+ #
+ # @param enum [Enumerable] The enumerable to get the enumerator for
+ # @param n [Fixnum] The size of each cons
+ # @return [Enumerator] The consed enumerator
+ def enum_cons(enum, n)
+ ruby1_8? ? enum.enum_cons(n) : enum.each_cons(n)
+ end
+
+ # A version of `Enumerable#enum_slice` that works in Ruby 1.8 and 1.9.
+ #
+ # @param enum [Enumerable] The enumerable to get the enumerator for
+ # @param n [Fixnum] The size of each slice
+ # @return [Enumerator] The consed enumerator
+ def enum_slice(enum, n)
+ ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
+ end
+
+ # Destructively removes all elements from an array that match a block, and
+ # returns the removed elements.
+ #
+ # @param array [Array] The array from which to remove elements.
+ # @yield [el] Called for each element.
+ # @yieldparam el [*] The element to test.
+ # @yieldreturn [Boolean] Whether or not to extract the element.
+ # @return [Array] The extracted elements.
+ def extract!(array)
+ out = []
+ array.reject! do |e|
+ next false unless yield e
+ out << e
+ true
+ end
+ out
+ end
+
+ # Returns the ASCII code of the given character.
+ #
+ # @param c [String] All characters but the first are ignored.
+ # @return [Fixnum] The ASCII code of `c`.
+ def ord(c)
+ ruby1_8? ? c[0] : c.ord
+ end
+
+ # Flattens the first `n` nested arrays in a cross-version manner.
+ #
+ # @param arr [Array] The array to flatten
+ # @param n [Fixnum] The number of levels to flatten
+ # @return [Array] The flattened array
+ def flatten(arr, n)
+ return arr.flatten(n) unless ruby1_8_6?
+ return arr if n == 0
+ arr.inject([]) {|res, e| e.is_a?(Array) ? res.concat(flatten(e, n - 1)) : res << e}
+ end
+
+ # Flattens the first level of nested arrays in `arrs`. Unlike
+ # `Array#flatten`, this orders the result by taking the first
+ # values from each array in order, then the second, and so on.
+ #
+ # @param arrs [Array] The array to flatten.
+ # @return [Array] The flattened array.
+ def flatten_vertically(arrs)
+ result = []
+ arrs = arrs.map {|sub| sub.is_a?(Array) ? sub.dup : Array(sub)}
+ until arrs.empty?
+ arrs.reject! do |arr|
+ result << arr.shift
+ arr.empty?
+ end
+ end
+ result
+ end
+
+ # Returns the hash code for a set in a cross-version manner.
+ # Aggravatingly, this is order-dependent in Ruby 1.8.6.
+ #
+ # @param set [Set]
+ # @return [Fixnum] The order-independent hashcode of `set`
+ def set_hash(set)
+ return set.hash unless ruby1_8_6?
+ set.map {|e| e.hash}.uniq.sort.hash
+ end
+
+ # Tests the hash-equality of two sets in a cross-version manner.
+ # Aggravatingly, this is order-dependent in Ruby 1.8.6.
+ #
+ # @param set1 [Set]
+ # @param set2 [Set]
+ # @return [Boolean] Whether or not the sets are hashcode equal
+ def set_eql?(set1, set2)
+ return set1.eql?(set2) unless ruby1_8_6?
+ set1.to_a.uniq.sort_by {|e| e.hash}.eql?(set2.to_a.uniq.sort_by {|e| e.hash})
+ end
+
+ # Like `Object#inspect`, but preserves non-ASCII characters rather than
+ # escaping them under Ruby 1.9.2. This is necessary so that the
+ # precompiled Haml template can be `#encode`d into ` options[:encoding]`
+ # before being evaluated.
+ #
+ # @param obj {Object}
+ # @return {String}
+ def inspect_obj(obj)
+ return obj.inspect unless version_geq(RUBY_VERSION, "1.9.2")
+ return ':' + inspect_obj(obj.to_s) if obj.is_a?(Symbol)
+ return obj.inspect unless obj.is_a?(String)
+ '"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"'
+ end
+
+ # Extracts the non-string vlaues from an array containing both strings and non-strings.
+ # These values are replaced with escape sequences.
+ # This can be undone using \{#inject\_values}.
+ #
+ # This is useful e.g. when we want to do string manipulation
+ # on an interpolated string.
+ #
+ # The precise format of the resulting string is not guaranteed.
+ # However, it is guaranteed that newlines and whitespace won't be affected.
+ #
+ # @param arr [Array] The array from which values are extracted.
+ # @return [(String, Array)] The resulting string, and an array of extracted values.
+ def extract_values(arr)
+ values = []
+ mapped = arr.map do |e|
+ next e.gsub('{', '{{') if e.is_a?(String)
+ values << e
+ next "{#{values.count - 1}}"
+ end
+ return mapped.join, values
+ end
+
+ # Undoes \{#extract\_values} by transforming a string with escape sequences
+ # into an array of strings and non-string values.
+ #
+ # @param str [String] The string with escape sequences.
+ # @param values [Array] The array of values to inject.
+ # @return [Array] The array of strings and values.
+ def inject_values(str, values)
+ return [str.gsub('{{', '{')] if values.empty?
+ # Add an extra { so that we process the tail end of the string
+ result = (str + '{{').scan(/(.*?)(?:(\{\{)|\{(\d+)\})/m).map do |(pre, esc, n)|
+ [pre, esc ? '{' : '', n ? values[n.to_i] : '']
+ end.flatten(1)
+ result[-2] = '' # Get rid of the extra {
+ merge_adjacent_strings(result).reject {|s| s == ''}
+ end
+
+ # Allows modifications to be performed on the string form
+ # of an array containing both strings and non-strings.
+ #
+ # @param arr [Array] The array from which values are extracted.
+ # @yield [str] A block in which string manipulation can be done to the array.
+ # @yieldparam str [String] The string form of `arr`.
+ # @yieldreturn [String] The modified string.
+ # @return [Array] The modified, interpolated array.
+ def with_extracted_values(arr)
+ str, vals = extract_values(arr)
+ str = yield str
+ inject_values(str, vals)
+ end
+
+ # Builds a sourcemap file name given the generated CSS file name.
+ #
+ # @param css [String] The generated CSS file name.
+ # @return [String] The source map file name.
+ def sourcemap_name(css)
+ css + ".map"
+ end
+
+ # Escapes certain characters so that the result can be used
+ # as the JSON string value. Returns the original string if
+ # no escaping is necessary.
+ #
+ # @param s [String] The string to be escaped
+ # @return [String] The escaped string
+ def json_escape_string(s)
+ return s if s !~ /["\\\b\f\n\r\t]/
+
+ result = ""
+ s.split("").each do |c|
+ case c
+ when '"', "\\"
+ result << "\\" << c
+ when "\n" then result << "\\n"
+ when "\t" then result << "\\t"
+ when "\r" then result << "\\r"
+ when "\f" then result << "\\f"
+ when "\b" then result << "\\b"
+ else
+ result << c
+ end
+ end
+ result
+ end
+
+ # Converts the argument into a valid JSON value.
+ #
+ # @param v [Fixnum, String, Array, Boolean, nil]
+ # @return [String]
+ def json_value_of(v)
+ case v
+ when Fixnum
+ v.to_s
+ when String
+ "\"" + json_escape_string(v) + "\""
+ when Array
+ "[" + v.map {|x| json_value_of(x)}.join(",") + "]"
+ when NilClass
+ "null"
+ when TrueClass
+ "true"
+ when FalseClass
+ "false"
+ else
+ raise ArgumentError.new("Unknown type: #{v.class.name}")
+ end
+ end
+
+ VLQ_BASE_SHIFT = 5
+ VLQ_BASE = 1 << VLQ_BASE_SHIFT
+ VLQ_BASE_MASK = VLQ_BASE - 1
+ VLQ_CONTINUATION_BIT = VLQ_BASE
+
+ BASE64_DIGITS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+', '/']
+ BASE64_DIGIT_MAP = begin
+ map = {}
+ Sass::Util.enum_with_index(BASE64_DIGITS).map do |digit, i|
+ map[digit] = i
+ end
+ map
+ end
+
+ # Encodes `value` as VLQ (http://en.wikipedia.org/wiki/VLQ).
+ #
+ # @param value [Fixnum]
+ # @return [String] The encoded value
+ def encode_vlq(value)
+ if value < 0
+ value = ((-value) << 1) | 1
+ else
+ value <<= 1
+ end
+
+ result = ''
+ begin
+ digit = value & VLQ_BASE_MASK
+ value >>= VLQ_BASE_SHIFT
+ if value > 0
+ digit |= VLQ_CONTINUATION_BIT
+ end
+ result << BASE64_DIGITS[digit]
+ end while value > 0
+ result
+ end
+
+ # This is a hack around the fact that you can't instantiate a URI parser on
+ # 1.8, so we have to have this hacky stuff to work around it. When 1.8
+ # support is dropped, we can remove this method.
+ #
+ # @private
+ URI_ESCAPE = URI.const_defined?("DEFAULT_PARSER") ? URI::DEFAULT_PARSER : URI
+
+ # URI-escape `string`.
+ #
+ # @param string [String]
+ # @return [String]
+ def escape_uri(string)
+ URI_ESCAPE.escape string
+ end
+
+ # A cross-platform implementation of `File.absolute_path`.
+ #
+ # @param path [String]
+ # @param dir_string [String] The directory to consider [path] relative to.
+ # @return [String] The absolute version of `path`.
+ def absolute_path(path, dir_string = nil)
+ # Ruby 1.8 doesn't support File.absolute_path.
+ return File.absolute_path(path, dir_string) unless ruby1_8?
+
+ # File.expand_path expands "~", which we don't want.
+ return File.expand_path(path, dir_string) unless path[0] == ?~
+ File.expand_path(File.join(".", path), dir_string)
+ end
+
+ ## Static Method Stuff
+
+ # The context in which the ERB for \{#def\_static\_method} will be run.
+ class StaticConditionalContext
+ # @param set [#include?] The set of variables that are defined for this context.
+ def initialize(set)
+ @set = set
+ end
+
+ # Checks whether or not a variable is defined for this context.
+ #
+ # @param name [Symbol] The name of the variable
+ # @return [Boolean]
+ def method_missing(name, *args)
+ super unless args.empty? && !block_given?
+ @set.include?(name)
+ end
+ end
+
+ # @private
+ ATOMIC_WRITE_MUTEX = Mutex.new
+
+ # This creates a temp file and yields it for writing. When the
+ # write is complete, the file is moved into the desired location.
+ # The atomicity of this operation is provided by the filesystem's
+ # rename operation.
+ #
+ # @param filename [String] The file to write to.
+ # @param perms [Integer] The permissions used for creating this file.
+ # Will be masked by the process umask. Defaults to readable/writeable
+ # by all users however the umask usually changes this to only be writable
+ # by the process's user.
+ # @yieldparam tmpfile [Tempfile] The temp file that can be written to.
+ # @return The value returned by the block.
+ def atomic_create_and_write_file(filename, perms = 0666)
+ require 'tempfile'
+ tmpfile = Tempfile.new(File.basename(filename), File.dirname(filename))
+ tmpfile.binmode if tmpfile.respond_to?(:binmode)
+ result = yield tmpfile
+ tmpfile.close
+ ATOMIC_WRITE_MUTEX.synchronize do
+ begin
+ File.chmod(perms & ~File.umask, tmpfile.path)
+ rescue Errno::EPERM
+ # If we don't have permissions to chmod the file, don't let that crash
+ # the compilation. See issue 1215.
+ end
+ File.rename tmpfile.path, filename
+ end
+ result
+ ensure
+ # close and remove the tempfile if it still exists,
+ # presumably due to an error during write
+ tmpfile.close if tmpfile
+ tmpfile.unlink if tmpfile
+ end
+
+ def load_listen!
+ if defined?(gem)
+ begin
+ gem 'listen', '>= 1.1.0', '< 3.0.0'
+ require 'listen'
+ rescue Gem::LoadError
+ dir = scope("vendor/listen/lib")
+ $LOAD_PATH.unshift dir
+ begin
+ require 'listen'
+ rescue LoadError => e
+ if version_geq(RUBY_VERSION, "1.9.3")
+ version_constraint = "~> 2.7"
+ else
+ version_constraint = "~> 1.1"
+ end
+ e.message << "\n" <<
+ "Run \"gem install listen --version '#{version_constraint}'\" to get it."
+ raise e
+ end
+ end
+ else
+ begin
+ require 'listen'
+ rescue LoadError => e
+ dir = scope("vendor/listen/lib")
+ if $LOAD_PATH.include?(dir)
+ raise e unless File.exist?(scope(".git"))
+ e.message << "\n" <<
+ 'Run "git submodule update --init" to get the bundled version.'
+ else
+ $LOAD_PATH.unshift dir
+ retry
+ end
+ end
+ end
+ end
+
+ private
+
+ def find_encoding_error(str)
+ encoding = str.encoding
+ cr = Regexp.quote("\r".encode(encoding).force_encoding('BINARY'))
+ lf = Regexp.quote("\n".encode(encoding).force_encoding('BINARY'))
+ ff = Regexp.quote("\f".encode(encoding).force_encoding('BINARY'))
+ line_break = /#{cr}#{lf}?|#{ff}|#{lf}/
+
+ str.force_encoding("binary").split(line_break).each_with_index do |line, i|
+ begin
+ line.encode(encoding)
+ rescue Encoding::UndefinedConversionError => e
+ raise Sass::SyntaxError.new(
+ "Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}",
+ :line => i + 1)
+ end
+ end
+
+ # We shouldn't get here, but it's possible some weird encoding stuff causes it.
+ return str, str.encoding
+ end
+
+ # rubocop:disable LineLength
+
+ # Calculates the memoization table for the Least Common Subsequence algorithm.
+ # Algorithm from
[Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS)
+ def lcs_table(x, y)
+ # This method does not take a block as an explicit parameter for performance reasons.
+ # rubocop:enable LineLength
+ c = Array.new(x.size) {[]}
+ x.size.times {|i| c[i][0] = 0}
+ y.size.times {|j| c[0][j] = 0}
+ (1...x.size).each do |i|
+ (1...y.size).each do |j|
+ c[i][j] =
+ if yield x[i], y[j]
+ c[i - 1][j - 1] + 1
+ else
+ [c[i][j - 1], c[i - 1][j]].max
+ end
+ end
+ end
+ c
+ end
+ # rubocop:disable ParameterLists, LineLength
+
+ # Computes a single longest common subsequence for arrays x and y.
+ # Algorithm from
[Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS)
+ def lcs_backtrace(c, x, y, i, j, &block)
+ # rubocop:enable ParameterList, LineLengths
+ return [] if i == 0 || j == 0
+ if (v = yield(x[i], y[j]))
+ return lcs_backtrace(c, x, y, i - 1, j - 1, &block) << v
+ end
+
+ return lcs_backtrace(c, x, y, i, j - 1, &block) if c[i][j - 1] > c[i - 1][j]
+ lcs_backtrace(c, x, y, i - 1, j, &block)
+ end
+
+ singleton_methods.each {|method| module_function method}
+ end
+end
+
+require 'sass/util/multibyte_string_scanner'
+require 'sass/util/normalized_map'
+require 'sass/util/cross_platform_random'
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/util/cross_platform_random.rb
b/backends/css/gems/sass-3.4.9/lib/sass/util/cross_platform_random.rb
new file mode 100644
index 0000000..54ff739
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/util/cross_platform_random.rb
@@ -0,0 +1,19 @@
+module Sass
+ module Util
+ # Ruby 1.8 doesn't support an actual Random class with a settable seed.
+ class CrossPlatformRandom
+ def initialize(seed = nil)
+ if Sass::Util.ruby1_8?
+ srand(seed) if seed
+ else
+ @random = seed ? ::Random.new(seed) : ::Random.new
+ end
+ end
+
+ def rand(*args)
+ return @random.rand(*args) if @random
+ Kernel.rand(*args)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/util/multibyte_string_scanner.rb
b/backends/css/gems/sass-3.4.9/lib/sass/util/multibyte_string_scanner.rb
new file mode 100644
index 0000000..6675aa5
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/util/multibyte_string_scanner.rb
@@ -0,0 +1,157 @@
+require 'strscan'
+
+if Sass::Util.ruby1_8?
+ # rubocop:disable ConstantName
+ Sass::Util::MultibyteStringScanner = StringScanner
+ # rubocop:enable ConstantName
+else
+ if Sass::Util.rbx?
+ # Rubinius's StringScanner class implements some of its methods in terms of
+ # others, which causes us to double-count bytes in some cases if we do
+ # straightforward inheritance. To work around this, we use a delegate class.
+ require 'delegate'
+ class Sass::Util::MultibyteStringScanner < DelegateClass(StringScanner)
+ def initialize(str)
+ super(StringScanner.new(str))
+ @mb_pos = 0
+ @mb_matched_size = nil
+ @mb_last_pos = nil
+ end
+
+ def is_a?(klass)
+ __getobj__.is_a?(klass) || super
+ end
+ end
+ else
+ class Sass::Util::MultibyteStringScanner < StringScanner
+ def initialize(str)
+ super
+ @mb_pos = 0
+ @mb_matched_size = nil
+ @mb_last_pos = nil
+ end
+ end
+ end
+
+ # A wrapper of the native StringScanner class that works correctly with
+ # multibyte character encodings. The native class deals only in bytes, not
+ # characters, for methods like [#pos] and [#matched_size]. This class deals
+ # only in characters, instead.
+ class Sass::Util::MultibyteStringScanner
+ def self.new(str)
+ return StringScanner.new(str) if str.ascii_only?
+ super
+ end
+
+ alias_method :byte_pos, :pos
+ alias_method :byte_matched_size, :matched_size
+
+ def check(pattern); _match super; end
+ def check_until(pattern); _matched super; end
+ def getch; _forward _match super; end
+ def match?(pattern); _size check(pattern); end
+ def matched_size; @mb_matched_size; end
+ def peek(len); string[ mb_pos, len]; end
+ alias_method :peep, :peek
+ def pos; @mb_pos; end
+ alias_method :pointer, :pos
+ def rest_size; rest.size; end
+ def scan(pattern); _forward _match super; end
+ def scan_until(pattern); _forward _matched super; end
+ def skip(pattern); _size scan(pattern); end
+ def skip_until(pattern); _matched _size scan_until(pattern); end
+
+ def get_byte
+ raise "MultibyteStringScanner doesn't support #get_byte."
+ end
+
+ def getbyte
+ raise "MultibyteStringScanner doesn't support #getbyte."
+ end
+
+ def pos=(n)
+ @mb_last_pos = nil
+
+ # We set position kind of a lot during parsing, so we want it to be as
+ # efficient as possible. This is complicated by the fact that UTF-8 is a
+ # variable-length encoding, so it's difficult to find the byte length that
+ # corresponds to a given character length.
+ #
+ # Our heuristic here is to try to count the fewest possible characters. So
+ # if the new position is close to the current one, just count the
+ # characters between the two; if the new position is closer to the
+ # beginning of the string, just count the characters from there.
+ if @mb_pos - n < @mb_pos / 2
+ # New position is close to old position
+ byte_delta = @mb_pos > n ? -string[n mb_pos] bytesize : string[ mb_pos n] bytesize
+ super(byte_pos + byte_delta)
+ else
+ # New position is close to BOS
+ super(string[0...n].bytesize)
+ end
+ @mb_pos = n
+ end
+
+ def reset
+ @mb_pos = 0
+ @mb_matched_size = nil
+ @mb_last_pos = nil
+ super
+ end
+
+ def scan_full(pattern, advance_pointer_p, return_string_p)
+ res = _match super(pattern, advance_pointer_p, true)
+ _forward res if advance_pointer_p
+ return res if return_string_p
+ end
+
+ def search_full(pattern, advance_pointer_p, return_string_p)
+ res = super(pattern, advance_pointer_p, true)
+ _forward res if advance_pointer_p
+ _matched((res if return_string_p))
+ end
+
+ def string=(str)
+ @mb_pos = 0
+ @mb_matched_size = nil
+ @mb_last_pos = nil
+ super
+ end
+
+ def terminate
+ @mb_pos = string.size
+ @mb_matched_size = nil
+ @mb_last_pos = nil
+ super
+ end
+ alias_method :clear, :terminate
+
+ def unscan
+ super
+ @mb_pos = @mb_last_pos
+ @mb_last_pos = @mb_matched_size = nil
+ end
+
+ private
+
+ def _size(str)
+ str && str.size
+ end
+
+ def _match(str)
+ @mb_matched_size = str && str.size
+ str
+ end
+
+ def _matched(res)
+ _match matched
+ res
+ end
+
+ def _forward(str)
+ @mb_last_pos = @mb_pos
+ @mb_pos += str.size if str
+ str
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/util/normalized_map.rb
b/backends/css/gems/sass-3.4.9/lib/sass/util/normalized_map.rb
new file mode 100644
index 0000000..b7d7e83
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/util/normalized_map.rb
@@ -0,0 +1,130 @@
+require 'delegate'
+require 'sass/util'
+
+module Sass
+ module Util
+ # A hash that normalizes its string keys while still allowing you to get back
+ # to the original keys that were stored. If several different values normalize
+ # to the same value, whichever is stored last wins.
+ require 'sass/util/ordered_hash' if ruby1_8?
+ class NormalizedMap
+ # Create a normalized map
+ def initialize(map = nil)
+ @key_strings = {}
+ @map = Util.ruby1_8? ? OrderedHash.new : {}
+
+ map.each {|key, value| self[key] = value} if map
+ end
+
+ # Specifies how to transform the key.
+ #
+ # This can be overridden to create other normalization behaviors.
+ def normalize(key)
+ key.tr("-", "_")
+ end
+
+ # Returns the version of `key` as it was stored before
+ # normalization. If `key` isn't in the map, returns it as it was
+ # passed in.
+ #
+ # @return [String]
+ def denormalize(key)
+ @key_strings[normalize(key)] || key
+ end
+
+ # @private
+ def []=(k, v)
+ normalized = normalize(k)
+ @map[normalized] = v
+ @key_strings[normalized] = k
+ v
+ end
+
+ # @private
+ def [](k)
+ @map[normalize(k)]
+ end
+
+ # @private
+ def has_key?(k)
+ @map.has_key?(normalize(k))
+ end
+
+ # @private
+ def delete(k)
+ normalized = normalize(k)
+ @key_strings.delete(normalized)
+ @map.delete(normalized)
+ end
+
+ # @return [Hash] Hash with the keys as they were stored (before normalization).
+ def as_stored
+ Sass::Util.map_keys(@map) {|k| @key_strings[k]}
+ end
+
+ def empty?
+ @map.empty?
+ end
+
+ def values
+ @map.values
+ end
+
+ def keys
+ @map.keys
+ end
+
+ def each
+ @map.each {|k, v| yield(k, v)}
+ end
+
+ def size
+ @map.size
+ end
+
+ def to_hash
+ @map.dup
+ end
+
+ def to_a
+ @map.to_a
+ end
+
+ def map
+ @map.map {|k, v| yield(k, v)}
+ end
+
+ def dup
+ d = super
+ d.send(:instance_variable_set, "@map", @map.dup)
+ d
+ end
+
+ def sort_by
+ @map.sort_by {|k, v| yield k, v}
+ end
+
+ def update(map)
+ map = map.as_stored if map.is_a?(NormalizedMap)
+ map.each {|k, v| self[k] = v}
+ end
+
+ def method_missing(method, *args, &block)
+ if Sass.tests_running
+ raise ArgumentError.new("The method #{method} must be implemented explicitly")
+ end
+ @map.send(method, *args, &block)
+ end
+
+ if Sass::Util.ruby1_8?
+ def respond_to?(method, include_private = false)
+ super || @map.respond_to?(method, include_private)
+ end
+ end
+
+ def respond_to_missing?(method, include_private = false)
+ @map.respond_to?(method, include_private)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/util/ordered_hash.rb
b/backends/css/gems/sass-3.4.9/lib/sass/util/ordered_hash.rb
new file mode 100644
index 0000000..e11a6ec
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/util/ordered_hash.rb
@@ -0,0 +1,192 @@
+# Copyright (c) 2005-2013 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This class was copied from an old version of ActiveSupport.
+class OrderedHash < ::Hash
+ # In MRI the Hash class is core and written in C. In particular, methods are
+ # programmed with explicit C function calls and polymorphism is not honored.
+ #
+ # For example, []= is crucial in this implementation to maintain the @keys
+ # array but hash.c invokes rb_hash_aset() originally. This prevents method
+ # reuse through inheritance and forces us to reimplement stuff.
+ #
+ # For instance, we cannot use the inherited #merge! because albeit the algorithm
+ # itself would work, our []= is not being called at all by the C code.
+
+ def initialize(*args)
+ super
+ @keys = []
+ end
+
+ def self.[](*args)
+ ordered_hash = new
+
+ if args.length == 1 && args.first.is_a?(Array)
+ args.first.each do |key_value_pair|
+ next unless key_value_pair.is_a?(Array)
+ ordered_hash[key_value_pair[0]] = key_value_pair[1]
+ end
+
+ return ordered_hash
+ end
+
+ unless args.size.even?
+ raise ArgumentError.new("odd number of arguments for Hash")
+ end
+
+ args.each_with_index do |val, ind|
+ next if ind.odd?
+ ordered_hash[val] = args[ind + 1]
+ end
+
+ ordered_hash
+ end
+
+ def initialize_copy(other)
+ super
+ # make a deep copy of keys
+ @keys = other.keys
+ end
+
+ def []=(key, value)
+ @keys << key unless has_key?(key)
+ super
+ end
+
+ def delete(key)
+ if has_key? key
+ index = @keys.index(key)
+ @keys.delete_at index
+ end
+ super
+ end
+
+ def delete_if
+ super
+ sync_keys!
+ self
+ end
+
+ def reject!
+ super
+ sync_keys!
+ self
+ end
+
+ def reject
+ dup.reject! {|h, k| yield h, k}
+ end
+
+ def keys
+ @keys.dup
+ end
+
+ def values
+ @keys.map {|key| self[key]}
+ end
+
+ def to_hash
+ self
+ end
+
+ def to_a
+ @keys.map {|key| [key, self[key]]}
+ end
+
+ def each_key
+ return to_enum(:each_key) unless block_given?
+ @keys.each {|key| yield key}
+ self
+ end
+
+ def each_value
+ return to_enum(:each_value) unless block_given?
+ @keys.each {|key| yield self[key]}
+ self
+ end
+
+ def each
+ return to_enum(:each) unless block_given?
+ @keys.each {|key| yield [key, self[key]]}
+ self
+ end
+
+ def each_pair
+ return to_enum(:each_pair) unless block_given?
+ @keys.each {|key| yield key, self[key]}
+ self
+ end
+
+ alias_method :select, :find_all
+
+ def clear
+ super
+ @keys.clear
+ self
+ end
+
+ def shift
+ k = @keys.first
+ v = delete(k)
+ [k, v]
+ end
+
+ def merge!(other_hash)
+ if block_given?
+ other_hash.each {|k, v| self[k] = key?(k) ? yield(k, self[k], v) : v}
+ else
+ other_hash.each {|k, v| self[k] = v}
+ end
+ self
+ end
+
+ alias_method :update, :merge!
+
+ def merge(other_hash)
+ if block_given?
+ dup.merge!(other_hash) {|k, v1, v2| yield k, v1, v2}
+ else
+ dup.merge!(other_hash)
+ end
+ end
+
+ # When replacing with another hash, the initial order of our keys must come from the other hash --
+ # ordered or not.
+ def replace(other)
+ super
+ @keys = other.keys
+ self
+ end
+
+ def invert
+ OrderedHash[to_a.map! {|key_value_pair| key_value_pair.reverse}]
+ end
+
+ def inspect
+ "#<OrderedHash #{super}>"
+ end
+
+ private
+
+ def sync_keys!
+ @keys.delete_if {|k| !has_key?(k)}
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/util/subset_map.rb
b/backends/css/gems/sass-3.4.9/lib/sass/util/subset_map.rb
new file mode 100644
index 0000000..a976acf
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/util/subset_map.rb
@@ -0,0 +1,110 @@
+require 'set'
+
+module Sass
+ module Util
+ # A map from sets to values.
+ # A value is \{#\[]= set} by providing a set (the "set-set") and a value,
+ # which is then recorded as corresponding to that set.
+ # Values are \{#\[] accessed} by providing a set (the "get-set")
+ # and returning all values that correspond to set-sets
+ # that are subsets of the get-set.
+ #
+ # SubsetMap preserves the order of values as they're inserted.
+ #
+ # @example
+ # ssm = SubsetMap.new
+ # ssm[Set[1, 2]] = "Foo"
+ # ssm[Set[2, 3]] = "Bar"
+ # ssm[Set[1, 2, 3]] = "Baz"
+ #
+ # ssm[Set[1, 2, 3]] #=> ["Foo", "Bar", "Baz"]
+ class SubsetMap
+ # Creates a new, empty SubsetMap.
+ def initialize
+ @hash = {}
+ @vals = []
+ end
+
+ # Whether or not this SubsetMap has any key-value pairs.
+ #
+ # @return [Boolean]
+ def empty?
+ @hash.empty?
+ end
+
+ # Associates a value with a set.
+ # When `set` or any of its supersets is accessed,
+ # `value` will be among the values returned.
+ #
+ # Note that if the same `set` is passed to this method multiple times,
+ # all given `value`s will be associated with that `set`.
+ #
+ # This runs in `O(n)` time, where `n` is the size of `set`.
+ #
+ # @param set [#to_set] The set to use as the map key. May not be empty.
+ # @param value [Object] The value to associate with `set`.
+ # @raise [ArgumentError] If `set` is empty.
+ def []=(set, value)
+ raise ArgumentError.new("SubsetMap keys may not be empty.") if set.empty?
+
+ index = @vals.size
+ @vals << value
+ set.each do |k|
+ @hash[k] ||= []
+ @hash[k] << [set, set.to_set, index]
+ end
+ end
+
+ # Returns all values associated with subsets of `set`.
+ #
+ # In the worst case, this runs in `O(m*max(n, log m))` time,
+ # where `n` is the size of `set`
+ # and `m` is the number of associations in the map.
+ # However, unless many keys in the map overlap with `set`,
+ # `m` will typically be much smaller.
+ #
+ # @param set [Set] The set to use as the map key.
+ # @return [Array<(Object, #to_set)>] An array of pairs,
+ # where the first value is the value associated with a subset of `set`,
+ # and the second value is that subset of `set`
+ # (or whatever `#to_set` object was used to set the value)
+ # This array is in insertion order.
+ # @see #[]
+ def get(set)
+ res = set.map do |k|
+ subsets = @hash[k]
+ next unless subsets
+ subsets.map do |subenum, subset, index|
+ next unless subset.subset?(set)
+ [index, subenum]
+ end
+ end
+ res = Sass::Util.flatten(res, 1)
+ res.compact!
+ res.uniq!
+ res.sort!
+ res.map! {|i, s| [ vals[i], s]}
+ res
+ end
+
+ # Same as \{#get}, but doesn't return the subsets of the argument
+ # for which values were found.
+ #
+ # @param set [Set] The set to use as the map key.
+ # @return [Array] The array of all values
+ # associated with subsets of `set`, in insertion order.
+ # @see #get
+ def [](set)
+ get(set).map {|v, _| v}
+ end
+
+ # Iterates over each value in the subset map. Ignores keys completely. If
+ # multiple keys have the same value, this will return them multiple times.
+ #
+ # @yield [Object] Each value in the map.
+ def each_value
+ @vals.each {|v| yield v}
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/util/test.rb
b/backends/css/gems/sass-3.4.9/lib/sass/util/test.rb
new file mode 100644
index 0000000..905e81f
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/util/test.rb
@@ -0,0 +1,9 @@
+module Sass
+ module Util
+ module Test
+ def skip(msg = nil, bt = caller)
+ super if defined?(super)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/lib/sass/version.rb
b/backends/css/gems/sass-3.4.9/lib/sass/version.rb
new file mode 100644
index 0000000..89c8a06
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/lib/sass/version.rb
@@ -0,0 +1,124 @@
+require 'date'
+require 'sass/util'
+
+module Sass
+ # Handles Sass version-reporting.
+ # Sass not only reports the standard three version numbers,
+ # but its Git revision hash as well,
+ # if it was installed from Git.
+ module Version
+ # Returns a hash representing the version of Sass.
+ # The `:major`, `:minor`, and `:teeny` keys have their respective numbers as Fixnums.
+ # The `:name` key has the name of the version.
+ # The `:string` key contains a human-readable string representation of the version.
+ # The `:number` key is the major, minor, and teeny keys separated by periods.
+ # The `:date` key, which is not guaranteed to be defined, is the `DateTime`
+ # at which this release was cut.
+ # If Sass is checked out from Git, the `:rev` key will have the revision hash.
+ # For example:
+ #
+ # {
+ # :string => "2.1.0.9616393",
+ # :rev => "9616393b8924ef36639c7e82aa88a51a24d16949",
+ # :number => "2.1.0",
+ # :date => DateTime.parse("Apr 30 13:52:01 2009 -0700"),
+ # :major => 2, :minor => 1, :teeny => 0
+ # }
+ #
+ # If a prerelease version of Sass is being used,
+ # the `:string` and `:number` fields will reflect the full version
+ # (e.g. `"2.2.beta.1"`), and the `:teeny` field will be `-1`.
+ # A `:prerelease` key will contain the name of the prerelease (e.g. `"beta"`),
+ # and a `:prerelease_number` key will contain the rerelease number.
+ # For example:
+ #
+ # {
+ # :string => "3.0.beta.1",
+ # :number => "3.0.beta.1",
+ # :date => DateTime.parse("Mar 31 00:38:04 2010 -0700"),
+ # :major => 3, :minor => 0, :teeny => -1,
+ # :prerelease => "beta",
+ # :prerelease_number => 1
+ # }
+ #
+ # @return [{Symbol => String/Fixnum}] The version hash
+ # @comment
+ # rubocop:disable ClassVars
+ def version
+ return @@version if defined?(@@version)
+
+ numbers = File.read(Sass::Util.scope('VERSION')).strip.split('.').
+ map {|n| n =~ /^[0-9]+$/ ? n.to_i : n}
+ name = File.read(Sass::Util.scope('VERSION_NAME')).strip
+ @@version = {
+ :major => numbers[0],
+ :minor => numbers[1],
+ :teeny => numbers[2],
+ :name => name
+ }
+
+ if (date = version_date)
+ @@version[:date] = date
+ end
+
+ if numbers[3].is_a?(String)
+ @@version[:teeny] = -1
+ @@version[:prerelease] = numbers[3]
+ @@version[:prerelease_number] = numbers[4]
+ end
+
+ @@version[:number] = numbers.join('.')
+ @@version[:string] = @@version[:number].dup
+
+ if (rev = revision_number)
+ @@version[:rev] = rev
+ unless rev[0] == ?(
+ @@version[:string] << "." << rev[0...7]
+ end
+ end
+
+ @@version[:string] << " (#{name})"
+ @@version
+ end
+ # rubocop:enable ClassVars
+
+ private
+
+ def revision_number
+ if File.exist?(Sass::Util.scope('REVISION'))
+ rev = File.read(Sass::Util.scope('REVISION')).strip
+ return rev unless rev =~ /^([a-f0-9]+|\(.*\))$/ || rev == '(unknown)'
+ end
+
+ return unless File.exist?(Sass::Util.scope('.git/HEAD'))
+ rev = File.read(Sass::Util.scope('.git/HEAD')).strip
+ return rev unless rev =~ /^ref: (.*)$/
+
+ ref_name = $1
+ ref_file = Sass::Util.scope(".git/#{ref_name}")
+ info_file = Sass::Util.scope(".git/info/refs")
+ return File.read(ref_file).strip if File.exist?(ref_file)
+ return unless File.exist?(info_file)
+ File.open(info_file) do |f|
+ f.each do |l|
+ sha, ref = l.strip.split("\t", 2)
+ next unless ref == ref_name
+ return sha
+ end
+ end
+ nil
+ end
+
+ def version_date
+ return unless File.exist?(Sass::Util.scope('VERSION_DATE'))
+ DateTime.parse(File.read(Sass::Util.scope('VERSION_DATE')).strip)
+ end
+ end
+
+ extend Sass::Version
+
+ # A string representing the version of Sass.
+ # A more fine-grained representation is available from Sass.version.
+ # @api public
+ VERSION = version[:string] unless defined?(Sass::VERSION)
+end
diff --git a/backends/css/gems/sass-3.2.12/rails/init.rb b/backends/css/gems/sass-3.4.9/rails/init.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/rails/init.rb
rename to backends/css/gems/sass-3.4.9/rails/init.rb
diff --git a/backends/css/gems/sass-3.4.9/test/sass/cache_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/cache_test.rb
new file mode 100755
index 0000000..b03be89
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/cache_test.rb
@@ -0,0 +1,131 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+require 'sass/engine'
+
+class CacheTest < MiniTest::Test
+ @@cache_dir = "tmp/file_cache"
+
+ def setup
+ FileUtils.mkdir_p @@cache_dir
+ end
+
+ def teardown
+ FileUtils.rm_rf @@cache_dir
+ clean_up_sassc
+ end
+
+ def test_file_cache_writes_a_file
+ file_store = Sass::CacheStores::Filesystem.new(@@cache_dir)
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
+ assert File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ end
+
+ def test_file_cache_reads_a_file
+ file_store = Sass::CacheStores::Filesystem.new(@@cache_dir)
+ assert !File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
+ assert File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ assert_kind_of Sass::Tree::RootNode, file_store.retrieve("asdf/foo.scssc", "fakesha1")
+ end
+
+ def test_file_cache_miss_returns_nil
+ file_store = Sass::CacheStores::Filesystem.new(@@cache_dir)
+ assert !File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ assert_nil file_store.retrieve("asdf/foo.scssc", "fakesha1")
+ end
+
+ def test_sha_change_invalidates_cache_and_cleans_up
+ file_store = Sass::CacheStores::Filesystem.new(@@cache_dir)
+ assert !File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
+ assert File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ assert_nil file_store.retrieve("asdf/foo.scssc", "differentsha1")
+ assert !File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ end
+
+ def test_version_change_invalidates_cache_and_cleans_up
+ file_store = Sass::CacheStores::Filesystem.new(@@cache_dir)
+ assert !File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
+ assert File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ real_version = Sass::VERSION
+ begin
+ Sass::VERSION.replace("a different version")
+ assert_nil file_store.retrieve("asdf/foo.scssc", "fakesha1")
+ assert !File.exist?("#{@@cache_dir}/asdf/foo.scssc")
+ ensure
+ Sass::VERSION.replace(real_version)
+ end
+ end
+
+ def test_arbitrary_objects_can_go_into_cache
+ cache = Sass::CacheStores::Memory.new
+ an_object = {:foo => :bar}
+ cache.store("an_object", "", an_object)
+ assert_equal an_object, cache.retrieve("an_object", "")
+ end
+
+ def test_cache_node_with_unmarshalable_option
+ engine_with_unmarshalable_options("foo {a: b + c}").to_tree
+ end
+
+ # Regression tests
+
+ def test_cache_mixin_def_splat_sass_node_with_unmarshalable_option
+ engine_with_unmarshalable_options(<<SASS, :syntax => :sass).to_tree
+=color($args...)
+ color: red
+SASS
+ end
+
+ def test_cache_mixin_def_splat_scss_node_with_unmarshalable_option
+ engine_with_unmarshalable_options(<<SCSS, :syntax => :scss).to_tree
+ mixin color($args...) {
+ color: red;
+}
+SCSS
+ end
+
+ def test_cache_function_splat_sass_node_with_unmarshalable_option
+ engine_with_unmarshalable_options(<<SASS, :syntax => :sass).to_tree
+ function color($args...)
+ @return red
+SASS
+ end
+
+ def test_cache_function_splat_scss_node_with_unmarshalable_option
+ engine_with_unmarshalable_options(<<SCSS, :syntax => :scss).to_tree
+ function color($args...) {
+ @return red;
+}
+SCSS
+ end
+
+ def test_cache_include_splat_sass_node_with_unmarshalable_option
+ engine_with_unmarshalable_options(<<SASS, :syntax => :sass).to_tree
+ include color($args..., $kwargs...)
+SASS
+ end
+
+ def test_cache_include_splat_scss_node_with_unmarshalable_option
+ engine_with_unmarshalable_options(<<SCSS, :syntax => :scss).to_tree
+ include color($args..., $kwargs...);
+SCSS
+ end
+
+ private
+ def root_node
+ Sass::Engine.new(<<-SCSS, :syntax => :scss).to_tree
+ @mixin color($c) { color: $c}
+ div { @include color(red); }
+ SCSS
+ end
+
+ def engine_with_unmarshalable_options(src, options={})
+ Sass::Engine.new(src, {
+ :syntax => :scss, :object => Class.new.new, :filename => 'file.scss',
+ :importer => Sass::Importers::Filesystem.new(absolutize('templates'))
+ }.merge(options))
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/callbacks_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/callbacks_test.rb
new file mode 100755
index 0000000..0e72112
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/callbacks_test.rb
@@ -0,0 +1,61 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/callbacks'
+
+class CallerBack
+ extend Sass::Callbacks
+ define_callback :foo
+ define_callback :bar
+
+ def do_foo
+ run_foo
+ end
+
+ def do_bar
+ run_bar 12
+ end
+end
+
+module ClassLevelCallerBack
+ extend Sass::Callbacks
+ define_callback :foo
+ extend self
+
+ def do_foo
+ run_foo
+ end
+end
+
+class SassCallbacksTest < MiniTest::Test
+ def test_simple_callback
+ cb = CallerBack.new
+ there = false
+ cb.on_foo {there = true}
+ cb.do_foo
+ assert there, "Expected callback to be called."
+ end
+
+ def test_multiple_callbacks
+ cb = CallerBack.new
+ str = ""
+ cb.on_foo {str += "first"}
+ cb.on_foo {str += " second"}
+ cb.do_foo
+ assert_equal "first second", str
+ end
+
+ def test_callback_with_arg
+ cb = CallerBack.new
+ val = nil
+ cb.on_bar {|a| val = a}
+ cb.do_bar
+ assert_equal 12, val
+ end
+
+ def test_class_level_callback
+ there = false
+ ClassLevelCallerBack.on_foo {there = true}
+ ClassLevelCallerBack.do_foo
+ assert there, "Expected callback to be called."
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/compiler_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/compiler_test.rb
new file mode 100755
index 0000000..d986f26
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/compiler_test.rb
@@ -0,0 +1,236 @@
+#!/usr/bin/env ruby
+require 'minitest/autorun'
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/plugin'
+require 'sass/plugin/compiler'
+
+class CompilerTest < MiniTest::Test
+ class FakeListener
+ attr_accessor :options
+ attr_accessor :directories
+ attr_reader :start_called
+ attr_reader :thread
+
+ def initialize(*args, &on_filesystem_event)
+ self.options = args.last.is_a?(Hash) ? args.pop : {}
+ self.directories = args
+ @on_filesystem_event = on_filesystem_event
+ @start_called = false
+ reset_events!
+ end
+
+ def fire_events!(*args)
+ @on_filesystem_event.call(@modified, @added, @removed)
+ reset_events!
+ end
+
+ def changed(filename)
+ @modified << File.expand_path(filename)
+ end
+
+ def added(filename)
+ @added << File.expand_path(filename)
+ end
+
+ def removed(filename)
+ @removed << File.expand_path(filename)
+ end
+
+ def on_start!(&run_during_start)
+ @run_during_start = run_during_start
+ end
+
+ # used for Listen < 2.0
+ def start!
+ @run_during_start.call(self) if @run_during_start
+ end
+
+ # used for Listen >= 2.0
+ def start
+ parent = Thread.current
+ @thread = Thread.new do
+ @run_during_start.call(self) if @run_during_start
+ parent.raise Interrupt
+ end
+ end
+
+ def stop
+ end
+
+ def reset_events!
+ @modified = []
+ @added = []
+ @removed = []
+ end
+ end
+
+ module MockWatcher
+ attr_accessor :run_during_start
+ attr_accessor :update_stylesheets_times
+ attr_accessor :update_stylesheets_called_with
+ attr_accessor :deleted_css_files
+
+ def fake_listener
+ @fake_listener
+ end
+
+ def update_stylesheets(individual_files)
+ @update_stylesheets_times ||= 0
+ @update_stylesheets_times += 1
+ (@update_stylesheets_called_with ||= []) << individual_files
+ end
+
+ def try_delete_css(css_filename)
+ (@deleted_css_files ||= []) << css_filename
+ end
+
+ private
+ def create_listener(*args, &on_filesystem_event)
+ if Sass::Util.listen_geq_2?
+ options = args.pop if args.last.is_a?(Hash)
+ args.map do |dir|
+ @fake_listener = FakeListener.new(*args, &on_filesystem_event)
+ @fake_listener.on_start!(&run_during_start)
+ @fake_listener
+ end
+ else
+ @fake_listener = FakeListener.new(*args, &on_filesystem_event)
+ @fake_listener.on_start!(&run_during_start)
+ @fake_listener
+ end
+ end
+ end
+
+ def test_initialize
+ watcher
+ end
+
+ def test_watch_starts_the_listener
+ start_called = false
+ c = watcher do |listener|
+ start_called = true
+ end
+ c.watch
+ assert start_called, "start! was not called"
+ end
+
+ def test_sass_callbacks_fire_from_listener_events
+ c = watcher do |listener|
+ listener.changed "changed.scss"
+ listener.added "added.scss"
+ listener.removed "removed.scss"
+ listener.fire_events!
+ end
+
+ modified_fired = false
+ c.on_template_modified do |sass_file|
+ modified_fired = true
+ assert_equal "changed.scss", sass_file
+ end
+
+ added_fired = false
+ c.on_template_created do |sass_file|
+ added_fired = true
+ assert_equal "added.scss", sass_file
+ end
+
+ removed_fired = false
+ c.on_template_deleted do |sass_file|
+ removed_fired = true
+ assert_equal "removed.scss", sass_file
+ end
+
+ c.watch
+
+ assert_equal 2, c.update_stylesheets_times
+ assert modified_fired
+ assert added_fired
+ assert removed_fired
+ end
+
+ def test_removing_a_sass_file_removes_corresponding_css_file
+ c = watcher do |listener|
+ listener.removed "remove_me.scss"
+ listener.fire_events!
+ end
+
+ c.watch
+
+ assert_equal "./remove_me.css", c.deleted_css_files.first
+ end
+
+ def test_an_importer_can_watch_an_image
+ image_importer = Sass::Importers::Filesystem.new(".")
+ class << image_importer
+ def watched_file?(filename)
+ filename =~ /\.png$/
+ end
+ end
+ c = watcher(:load_paths => [image_importer]) do |listener|
+ listener.changed "image.png"
+ listener.fire_events!
+ end
+
+ modified_fired = false
+ c.on_template_modified do |f|
+ modified_fired = true
+ assert_equal "image.png", f
+ end
+
+ c.watch
+
+ assert_equal 2, c.update_stylesheets_times
+ assert modified_fired
+ end
+
+ def test_watching_specific_files_and_one_is_deleted
+ directories = nil
+ c = watcher do |listener|
+ directories = listener.directories
+ listener.removed File.expand_path("./foo.scss")
+ listener.fire_events!
+ end
+ c.watch([[File.expand_path("./foo.scss"), File.expand_path("./foo.css"), nil]])
+ assert directories.include?(File.expand_path(".")), directories.inspect
+ assert_equal File.expand_path("./foo.css"), c.deleted_css_files.first, "the corresponding css file was
not deleted"
+ assert_equal [], c.update_stylesheets_called_with[1], "the sass file should not have been compiled"
+ end
+
+ def test_watched_directories_are_dedupped
+ directories = nil
+ c = watcher(:load_paths => [".", "./foo", "."]) do |listener|
+ directories = listener.directories
+ end
+ c.watch
+ assert_equal [File.expand_path(".")], directories
+ end
+
+ def test_a_changed_css_in_a_watched_directory_does_not_force_a_compile
+ c = watcher do |listener|
+ listener.changed "foo.css"
+ listener.fire_events!
+ end
+
+ c.on_template_modified do |f|
+ assert false, "Should not have been called"
+ end
+
+ c.watch
+
+ assert_equal 1, c.update_stylesheets_times
+ end
+
+ private
+
+ def default_options
+ {:template_location => [[".","."]]}
+ end
+
+ def watcher(options = {}, &run_during_start)
+ options = default_options.merge(options)
+ watcher = Sass::Plugin::Compiler.new(options)
+ watcher.extend(MockWatcher)
+ watcher.run_during_start = run_during_start
+ watcher
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/conversion_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/conversion_test.rb
new file mode 100755
index 0000000..3c7941a
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/conversion_test.rb
@@ -0,0 +1,2069 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+
+class ConversionTest < MiniTest::Test
+ def test_basic
+ assert_renders <<SASS, <<SCSS
+foo bar
+ baz: bang
+ bip: bop
+SASS
+foo bar {
+ baz: bang;
+ bip: bop;
+}
+SCSS
+ assert_renders <<SASS, <<SCSS, :old => true
+foo bar
+ :baz bang
+ :bip bop
+SASS
+foo bar {
+ baz: bang;
+ bip: bop;
+}
+SCSS
+ end
+
+ def test_empty_selector
+ assert_renders "foo bar", "foo bar {}"
+ end
+
+ def test_empty_directive
+ assert_renders "@media screen", "@media screen {}"
+ end
+
+ def test_empty_control_directive
+ assert_renders "@if false", "@if false {}"
+ end
+
+ def test_nesting
+ assert_renders <<SASS, <<SCSS
+foo bar
+ baz bang
+ baz: bang
+ bip: bop
+ blat: boo
+SASS
+foo bar {
+ baz bang {
+ baz: bang;
+ bip: bop;
+ }
+ blat: boo;
+}
+SCSS
+ end
+
+ def test_nesting_with_parent_ref
+ assert_renders <<SASS, <<SCSS
+foo bar
+ &:hover
+ baz: bang
+SASS
+foo bar {
+ &:hover {
+ baz: bang;
+ }
+}
+SCSS
+ end
+
+ def test_selector_interpolation
+ assert_renders <<SASS, <<SCSS
+foo \#{$bar + "baz"}.bip
+ baz: bang
+
+foo /\#{$bar + "baz"}/ .bip
+ baz: bang
+SASS
+foo \#{$bar + "baz"}.bip {
+ baz: bang;
+}
+
+foo /\#{$bar + "baz"}/ .bip {
+ baz: bang;
+}
+SCSS
+ end
+
+ def test_multiline_selector_with_commas
+ assert_renders <<SASS, <<SCSS
+foo bar,
+baz bang
+ baz: bang
+SASS
+foo bar,
+baz bang {
+ baz: bang;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+blat
+ foo bar,
+ baz bang
+ baz: bang
+SASS
+blat {
+ foo bar,
+ baz bang {
+ baz: bang;
+ }
+}
+SCSS
+ end
+
+ def test_multiline_selector_without_commas
+ assert_scss_to_sass <<SASS, <<SCSS
+foo bar baz bang
+ baz: bang
+SASS
+foo bar
+baz bang {
+ baz: bang;
+}
+SCSS
+
+ assert_scss_to_scss <<SCSS
+foo bar
+baz bang {
+ baz: bang;
+}
+SCSS
+ end
+
+ def test_escaped_selector
+ assert_renders <<SASS, <<SCSS
+foo bar
+ \\:hover
+ baz: bang
+SASS
+foo bar {
+ :hover {
+ baz: bang;
+ }
+}
+SCSS
+ end
+
+ def test_property_name_interpolation
+ assert_renders <<SASS, <<SCSS
+foo bar
+ baz\#{$bang}bip\#{$bop}: 12
+SASS
+foo bar {
+ baz\#{$bang}bip\#{$bop}: 12;
+}
+SCSS
+ end
+
+ def test_property_value_interpolation
+ assert_renders <<SASS, <<SCSS
+foo bar
+ baz: 12 \#{$bang} bip \#{"bop"} blat
+SASS
+foo bar {
+ baz: 12 \#{$bang} bip \#{"bop"} blat;
+}
+SCSS
+ end
+
+ def test_dynamic_properties
+ assert_renders <<SASS, <<SCSS
+foo bar
+ baz: 12 $bang "bip"
+SASS
+foo bar {
+ baz: 12 $bang "bip";
+}
+SCSS
+ end
+
+ def test_dynamic_properties_with_old
+ assert_renders <<SASS, <<SCSS, :old => true
+foo bar
+ :baz 12 $bang "bip"
+SASS
+foo bar {
+ baz: 12 $bang "bip";
+}
+SCSS
+ end
+
+ def test_multiline_properties
+ assert_scss_to_sass <<SASS, <<SCSS
+foo bar
+ baz: bip bam boon
+SASS
+foo bar {
+ baz:
+ bip
+ bam
+ boon;
+}
+SCSS
+
+ assert_scss_to_scss <<OUT, <<IN
+foo bar {
+ baz: bip bam boon;
+}
+OUT
+foo bar {
+ baz:
+ bip
+ bam
+ boon;
+}
+IN
+ end
+
+ def test_multiline_dynamic_properties
+ assert_scss_to_sass <<SASS, <<SCSS
+foo bar
+ baz: $bip "bam" 12px
+SASS
+foo bar {
+ baz:
+ $bip
+ "bam"
+ 12px;
+}
+SCSS
+
+ assert_scss_to_scss <<OUT, <<IN
+foo bar {
+ baz: $bip "bam" 12px;
+}
+OUT
+foo bar {
+ baz:
+ $bip
+ "bam"
+ 12px;
+}
+IN
+ end
+
+ def test_silent_comments
+ assert_renders <<SASS, <<SCSS
+// foo
+
+// bar
+
+// baz
+
+foo bar
+ a: b
+SASS
+// foo
+
+// bar
+
+// baz
+
+foo bar {
+ a: b;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+// foo
+// bar
+// baz
+// bang
+
+foo bar
+ a: b
+SASS
+// foo
+// bar
+// baz
+// bang
+
+foo bar {
+ a: b;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+// foo
+// bar
+// baz
+// bang
+
+foo bar
+ a: b
+SASS
+// foo
+// bar
+// baz
+// bang
+
+foo bar {
+ a: b;
+}
+SCSS
+ end
+
+ def test_nested_silent_comments
+ assert_renders <<SASS, <<SCSS
+foo
+ bar: baz
+ // bip bop
+ // beep boop
+ bang: bizz
+ // bubble bubble
+ // toil trouble
+SASS
+foo {
+ bar: baz;
+ // bip bop
+ // beep boop
+ bang: bizz;
+ // bubble bubble
+ // toil trouble
+}
+SCSS
+
+ assert_sass_to_scss <<SCSS, <<SASS
+foo {
+ bar: baz;
+ // bip bop
+ // beep boop
+ // bap blimp
+ bang: bizz;
+ // bubble bubble
+ // toil trouble
+ // gorp
+}
+SCSS
+foo
+ bar: baz
+ // bip bop
+ beep boop
+ bap blimp
+ bang: bizz
+ // bubble bubble
+ toil trouble
+ gorp
+SASS
+ end
+
+ def test_preserves_triple_slash_comments
+ assert_renders <<SASS, <<SCSS
+/// foo
+/// bar
+foo
+ /// bip bop
+ /// beep boop
+SASS
+/// foo
+/// bar
+foo {
+ /// bip bop
+ /// beep boop
+}
+SCSS
+ end
+
+ def test_loud_comments
+ assert_renders <<SASS, <<SCSS
+/* foo
+
+/* bar
+
+/* baz
+
+foo bar
+ a: b
+SASS
+/* foo */
+
+/* bar */
+
+/* baz */
+
+foo bar {
+ a: b;
+}
+SCSS
+
+ assert_scss_to_sass <<SASS, <<SCSS
+/* foo
+ * bar
+ * baz
+ * bang
+
+foo bar
+ a: b
+SASS
+/* foo
+ bar
+ baz
+ bang */
+
+foo bar {
+ a: b;
+}
+SCSS
+
+ assert_scss_to_scss <<SCSS
+/* foo
+ bar
+ baz
+ bang */
+
+foo bar {
+ a: b;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+/* foo
+ * bar
+ * baz
+ * bang
+
+foo bar
+ a: b
+SASS
+/* foo
+ * bar
+ * baz
+ * bang */
+
+foo bar {
+ a: b;
+}
+SCSS
+ end
+
+ def test_nested_loud_comments
+ assert_renders <<SASS, <<SCSS
+foo
+ bar: baz
+ /* bip bop
+ * beep boop
+ bang: bizz
+ /* bubble bubble
+ * toil trouble
+SASS
+foo {
+ bar: baz;
+ /* bip bop
+ * beep boop */
+ bang: bizz;
+ /* bubble bubble
+ * toil trouble */
+}
+SCSS
+
+ assert_sass_to_scss <<SCSS, <<SASS
+foo {
+ bar: baz;
+ /* bip bop
+ * beep boop
+ * bap blimp */
+ bang: bizz;
+ /* bubble bubble
+ * toil trouble
+ * gorp */
+}
+SCSS
+foo
+ bar: baz
+ /* bip bop
+ beep boop
+ bap blimp
+ bang: bizz
+ /* bubble bubble
+ toil trouble
+ gorp
+SASS
+ end
+
+ def test_preserves_double_star_comments
+ assert_renders <<SASS, <<SCSS
+/** foo
+ * bar
+foo
+ /** bip bop
+ * beep boop
+SASS
+/** foo
+ * bar */
+foo {
+ /** bip bop
+ * beep boop */
+}
+SCSS
+ end
+
+ def test_loud_comments_with_weird_indentation
+ assert_scss_to_sass <<SASS, <<SCSS
+foo
+ /* foo
+ * bar
+ * baz
+ a: b
+SASS
+foo {
+ /* foo
+bar
+ baz */
+ a: b;
+}
+SCSS
+
+ assert_sass_to_scss <<SCSS, <<SASS
+foo {
+ /* foo
+ * bar
+ * baz */
+ a: b;
+}
+SCSS
+foo
+ /* foo
+ bar
+ baz
+ a: b
+SASS
+ end
+
+ def test_loud_comment_containing_silent_comment
+ assert_scss_to_sass <<SASS, <<SCSS
+/*
+ *// foo bar
+SASS
+/*
+// foo bar
+*/
+SCSS
+ end
+
+ def test_silent_comment_containing_loud_comment
+ assert_scss_to_sass <<SASS, <<SCSS
+// /*
+// * foo bar
+// */
+SASS
+// /*
+// * foo bar
+// */
+SCSS
+ end
+
+ def test_immediately_preceding_comments
+ assert_renders <<SASS, <<SCSS
+/* Foo
+ * Bar
+ * Baz
+.foo#bar
+ a: b
+SASS
+/* Foo
+ * Bar
+ * Baz */
+.foo#bar {
+ a: b;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+// Foo
+// Bar
+// Baz
+=foo
+ a: b
+SASS
+// Foo
+// Bar
+// Baz
+ mixin foo {
+ a: b;
+}
+SCSS
+ end
+
+ def test_immediately_following_comments
+ assert_sass_to_scss <<SCSS, <<SASS
+.foobar {
+ // trailing comment
+ a: 1px;
+}
+SCSS
+.foobar // trailing comment
+ a: 1px
+SASS
+
+ assert_sass_to_scss <<SCSS, <<SASS
+.foobar {
+ // trailing comment
+ a: 1px;
+}
+SCSS
+.foobar /* trailing comment */
+ a: 1px
+SASS
+ end
+
+ def test_debug
+ assert_renders <<SASS, <<SCSS
+foo
+ @debug 12px
+ bar: baz
+SASS
+foo {
+ @debug 12px;
+ bar: baz;
+}
+SCSS
+ end
+
+ def test_error
+ assert_renders <<SASS, <<SCSS
+foo
+ @error "oh no!"
+ bar: baz
+SASS
+foo {
+ @error "oh no!";
+ bar: baz;
+}
+SCSS
+ end
+
+ def test_directive_without_children
+ assert_renders <<SASS, <<SCSS
+foo
+ @foo #bar "baz"
+ bar: baz
+SASS
+foo {
+ @foo #bar "baz";
+ bar: baz;
+}
+SCSS
+ end
+
+ def test_directive_with_prop_children
+ assert_renders <<SASS, <<SCSS
+foo
+ @foo #bar "baz"
+ a: b
+ c: d
+
+ bar: baz
+SASS
+foo {
+ @foo #bar "baz" {
+ a: b;
+ c: d;
+ }
+
+ bar: baz;
+}
+SCSS
+ end
+
+ def test_directive_with_rule_children
+ assert_renders <<SASS, <<SCSS
+foo
+ @foo #bar "baz"
+ #blat
+ a: b
+ .bang
+ c: d
+ e: f
+
+ bar: baz
+SASS
+foo {
+ @foo #bar "baz" {
+ #blat {
+ a: b;
+ }
+ .bang {
+ c: d;
+ e: f;
+ }
+ }
+
+ bar: baz;
+}
+SCSS
+ end
+
+ def test_directive_with_rule_and_prop_children
+ assert_renders <<SASS, <<SCSS
+foo
+ @foo #bar "baz"
+ g: h
+ #blat
+ a: b
+ .bang
+ c: d
+ e: f
+ i: j
+
+ bar: baz
+SASS
+foo {
+ @foo #bar "baz" {
+ g: h;
+ #blat {
+ a: b;
+ }
+ .bang {
+ c: d;
+ e: f;
+ }
+ i: j;
+ }
+
+ bar: baz;
+}
+SCSS
+ end
+
+ def test_charset
+ assert_renders <<SASS, <<SCSS
+ charset "utf-8"
+SASS
+ charset "utf-8";
+SCSS
+ end
+
+ def test_for
+ assert_renders <<SASS, <<SCSS
+foo
+ @for $a from $b to $c
+ a: b
+ @for $c from 1 to 16
+ d: e
+ f: g
+SASS
+foo {
+ @for $a from $b to $c {
+ a: b;
+ }
+ @for $c from 1 to 16 {
+ d: e;
+ f: g;
+ }
+}
+SCSS
+ end
+
+ def test_while
+ assert_renders <<SASS, <<SCSS
+foo
+ @while flaz($a + $b)
+ a: b
+ @while 1
+ d: e
+ f: g
+SASS
+foo {
+ @while flaz($a + $b) {
+ a: b;
+ }
+ @while 1 {
+ d: e;
+ f: g;
+ }
+}
+SCSS
+ end
+
+ def test_if
+ assert_renders <<SASS, <<SCSS
+foo
+ @if $foo or $bar
+ a: b
+ @if $baz
+ d: e
+ @else if $bang
+ f: g
+ @else
+ h: i
+SASS
+foo {
+ @if $foo or $bar {
+ a: b;
+ }
+ @if $baz {
+ d: e;
+ }
+ @else if $bang {
+ f: g;
+ }
+ @else {
+ h: i;
+ }
+}
+SCSS
+ end
+
+ def test_each
+ assert_renders <<SASS, <<SCSS
+a
+ @each $number in 1px 2px 3px 4px
+ b: $number
+
+c
+ @each $str in foo, bar, baz, bang
+ d: $str
+
+c
+ @each $key, $value in (foo: 1, bar: 2, baz: 3)
+ \#{$key}: $value
+SASS
+a {
+ @each $number in 1px 2px 3px 4px {
+ b: $number;
+ }
+}
+
+c {
+ @each $str in foo, bar, baz, bang {
+ d: $str;
+ }
+}
+
+c {
+ @each $key, $value in (foo: 1, bar: 2, baz: 3) {
+ \#{$key}: $value;
+ }
+}
+SCSS
+ end
+
+ def test_import
+ assert_renders <<SASS, <<SCSS
+ import foo
+
+ import url(bar.css)
+
+foo
+ bar: baz
+SASS
+ import "foo";
+
+ import url(bar.css);
+
+foo {
+ bar: baz;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+ import foo.css
+
+ import url(bar.css)
+
+foo
+ bar: baz
+SASS
+ import "foo.css";
+
+ import url(bar.css);
+
+foo {
+ bar: baz;
+}
+SCSS
+ end
+
+ def test_import_as_directive_in_sass
+ assert_equal "@import foo.css\n", to_sass('@import "foo.css"')
+ end
+
+ def test_import_as_directive_in_scss
+ assert_renders <<SASS, <<SCSS
+ import "foo.css" print
+SASS
+ import "foo.css" print;
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+ import url(foo.css) screen, print
+SASS
+ import url(foo.css) screen, print;
+SCSS
+ end
+
+ def test_adjacent_imports
+ assert_renders <<SASS, <<SCSS
+ import foo.sass
+ import bar.scss
+ import baz
+SASS
+ import "foo.sass";
+ import "bar.scss";
+ import "baz";
+SCSS
+ end
+
+ def test_non_adjacent_imports
+ assert_renders <<SASS, <<SCSS
+ import foo.sass
+
+ import bar.scss
+
+ import baz
+SASS
+ import "foo.sass";
+
+ import "bar.scss";
+
+ import "baz";
+SCSS
+ end
+
+ def test_import_with_interpolation
+ assert_renders <<SASS, <<SCSS
+$family: unquote("Droid+Sans")
+
+ import url("http://fonts.googleapis.com/css?family=\#{$family}")
+SASS
+$family: unquote("Droid+Sans");
+
+ import url("http://fonts.googleapis.com/css?family=\#{$family}");
+SCSS
+ end
+
+ def test_extend
+ assert_renders <<SASS, <<SCSS
+.foo
+ @extend .bar
+ @extend .baz:bang
+SASS
+.foo {
+ @extend .bar;
+ @extend .baz:bang;
+}
+SCSS
+ end
+
+ def test_comma_extendee
+ assert_renders <<SASS, <<SCSS
+.baz
+ @extend .foo, .bar
+SASS
+.baz {
+ @extend .foo, .bar;
+}
+SCSS
+ end
+
+ def test_argless_mixin_definition
+ assert_renders <<SASS, <<SCSS
+=foo-bar
+ baz
+ a: b
+SASS
+ mixin foo-bar {
+ baz {
+ a: b;
+ }
+}
+SCSS
+
+ assert_scss_to_sass <<SASS, <<SCSS
+=foo-bar
+ baz
+ a: b
+SASS
+ mixin foo-bar() {
+ baz {
+ a: b;
+ }
+}
+SCSS
+
+ assert_sass_to_scss <<SCSS, <<SASS
+ mixin foo-bar {
+ baz {
+ a: b;
+ }
+}
+SCSS
+=foo-bar()
+ baz
+ a: b
+SASS
+ end
+
+ def test_mixin_definition_without_defaults
+ assert_renders <<SASS, <<SCSS
+=foo-bar($baz, $bang)
+ baz
+ a: $baz $bang
+SASS
+ mixin foo-bar($baz, $bang) {
+ baz {
+ a: $baz $bang;
+ }
+}
+SCSS
+ end
+
+ def test_mixin_definition_with_defaults
+ assert_renders <<SASS, <<SCSS
+=foo-bar($baz, $bang: 12px)
+ baz
+ a: $baz $bang
+SASS
+ mixin foo-bar($baz, $bang: 12px) {
+ baz {
+ a: $baz $bang;
+ }
+}
+SCSS
+
+ assert_sass_to_scss <<SCSS, <<SASS
+ mixin foo-bar($baz, $bang: foo) {
+ baz {
+ a: $baz $bang;
+ }
+}
+SCSS
+=foo-bar($baz, $bang: foo)
+ baz
+ a: $baz $bang
+SASS
+ end
+
+ def test_argless_mixin_include
+ assert_renders <<SASS, <<SCSS
+foo
+ +foo-bar
+ a: blip
+SASS
+foo {
+ @include foo-bar;
+ a: blip;
+}
+SCSS
+ end
+
+ def test_mixin_include
+ assert_renders <<SASS, <<SCSS
+foo
+ +foo-bar(12px, "blaz")
+ a: blip
+SASS
+foo {
+ @include foo-bar(12px, "blaz");
+ a: blip;
+}
+SCSS
+ end
+
+ def test_mixin_include_with_keyword_args
+ assert_renders <<SASS, <<SCSS
+foo
+ +foo-bar(12px, "blaz", $blip: blap, $bloop: blop)
+ +foo-bar($blip: blap, $bloop: blop)
+ a: blip
+SASS
+foo {
+ @include foo-bar(12px, "blaz", $blip: blap, $bloop: blop);
+ @include foo-bar($blip: blap, $bloop: blop);
+ a: blip;
+}
+SCSS
+ end
+
+ def test_mixin_include_with_hyphen_conversion_keyword_arg
+ assert_renders <<SASS, <<SCSS
+foo
+ +foo-bar($a-b_c: val)
+ a: blip
+SASS
+foo {
+ @include foo-bar($a-b_c: val);
+ a: blip;
+}
+SCSS
+ end
+
+ def test_argless_function_definition
+ assert_renders <<SASS, <<SCSS
+ function foo()
+ $var: 1 + 1
+ @return $var
+SASS
+ function foo() {
+ $var: 1 + 1;
+ @return $var;
+}
+SCSS
+ end
+
+ def test_function_definition_without_defaults
+ assert_renders <<SASS, <<SCSS
+ function foo($var1, $var2)
+ @if $var1
+ @return $var1 + $var2
+SASS
+ function foo($var1, $var2) {
+ @if $var1 {
+ @return $var1 + $var2;
+ }
+}
+SCSS
+ end
+
+ def test_function_definition_with_defaults
+ assert_renders <<SASS, <<SCSS
+ function foo($var1, $var2: foo)
+ @if $var1
+ @return $var1 + $var2
+SASS
+ function foo($var1, $var2: foo) {
+ @if $var1 {
+ @return $var1 + $var2;
+ }
+}
+SCSS
+ end
+
+ def test_variable_definition
+ assert_renders <<SASS, <<SCSS
+$var1: 12px + 15px
+
+foo
+ $var2: flaz(#abcdef)
+ val: $var1 $var2
+SASS
+$var1: 12px + 15px;
+
+foo {
+ $var2: flaz(#abcdef);
+ val: $var1 $var2;
+}
+SCSS
+ end
+
+ def test_guarded_variable_definition
+ assert_renders <<SASS, <<SCSS
+$var1: 12px + 15px !default
+
+foo
+ $var2: flaz(#abcdef) !default
+ val: $var1 $var2
+SASS
+$var1: 12px + 15px !default;
+
+foo {
+ $var2: flaz(#abcdef) !default;
+ val: $var1 $var2;
+}
+SCSS
+ end
+
+ def test_multiple_variable_definitions
+ assert_renders <<SASS, <<SCSS
+$var1: foo
+$var2: bar
+$var3: baz
+
+$var4: bip
+$var5: bap
+SASS
+$var1: foo;
+$var2: bar;
+$var3: baz;
+
+$var4: bip;
+$var5: bap;
+SCSS
+ end
+
+ def test_division_asserted_with_parens
+ assert_renders <<SASS, <<SCSS
+foo
+ a: (1px / 2px)
+SASS
+foo {
+ a: (1px / 2px);
+}
+SCSS
+ end
+
+ def test_division_not_asserted_when_unnecessary
+ assert_renders <<SASS, <<SCSS
+$var: 1px / 2px
+
+foo
+ a: $var
+SASS
+$var: 1px / 2px;
+
+foo {
+ a: $var;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+$var: 1px
+
+foo
+ a: $var / 2px
+SASS
+$var: 1px;
+
+foo {
+ a: $var / 2px;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS
+foo
+ a: 1 + 1px / 2px
+SASS
+foo {
+ a: 1 + 1px / 2px;
+}
+SCSS
+ end
+
+ def test_literal_slash
+ assert_renders <<SASS, <<SCSS
+foo
+ a: 1px / 2px
+SASS
+foo {
+ a: 1px / 2px;
+}
+SCSS
+ end
+
+ def test_directive_with_interpolation
+ assert_renders <<SASS, <<SCSS
+$baz: 12
+
+ foo bar\#{$baz} qux
+ a: b
+SASS
+$baz: 12;
+
+ foo bar\#{$baz} qux {
+ a: b;
+}
+SCSS
+ end
+
+ def test_media_with_interpolation
+ assert_renders <<SASS, <<SCSS
+$baz: 12
+
+ media bar\#{$baz}
+ a: b
+SASS
+$baz: 12;
+
+ media bar\#{$baz} {
+ a: b;
+}
+SCSS
+ end
+
+ def test_media_with_expressions
+ assert_renders <<SASS, <<SCSS
+$media1: screen
+$media2: print
+$var: -webkit-min-device-pixel-ratio
+$val: 20
+
+ media \#{$media1} and ($var + "-foo": $val + 5), only \#{$media2}
+ a: b
+SASS
+$media1: screen;
+$media2: print;
+$var: -webkit-min-device-pixel-ratio;
+$val: 20;
+
+ media \#{$media1} and ($var + "-foo": $val + 5), only \#{$media2} {
+ a: b;
+}
+SCSS
+ end
+
+ def test_media_with_feature
+ assert_renders <<SASS, <<SCSS
+ media screen and (-webkit-transform-3d)
+ a: b
+SASS
+ media screen and (-webkit-transform-3d) {
+ a: b;
+}
+SCSS
+ end
+
+ def test_supports_with_expressions
+ assert_renders <<SASS, <<SCSS
+$query: "(feature1: val)"
+$feature: feature2
+$val: val
+
+ supports \#{$query} and ($feature: $val) or (not ($feature + 3: $val + 4))
+ foo
+ a: b
+SASS
+$query: "(feature1: val)";
+$feature: feature2;
+$val: val;
+
+ supports \#{$query} and ($feature: $val) or (not ($feature + 3: $val + 4)) {
+ foo {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ # Hacks
+
+ def test_declaration_hacks
+ assert_renders <<SASS, <<SCSS
+foo
+ _name: val
+ *name: val
+ #name: val
+ .name: val
+ name/**/: val
+ name/*\\**/: val
+ name: val
+SASS
+foo {
+ _name: val;
+ *name: val;
+ #name: val;
+ .name: val;
+ name/**/: val;
+ name/*\\**/: val;
+ name: val;
+}
+SCSS
+ end
+
+ def test_old_declaration_hacks
+ assert_renders <<SASS, <<SCSS, :old => true
+foo
+ :_name val
+ :*name val
+ :#name val
+ :.name val
+ :name val
+SASS
+foo {
+ _name: val;
+ *name: val;
+ #name: val;
+ .name: val;
+ name: val;
+}
+SCSS
+ end
+
+ def test_selector_hacks
+ assert_selector_renders = lambda do |s|
+ assert_renders <<SASS, <<SCSS
+#{s}
+ a: b
+SASS
+#{s} {
+ a: b;
+}
+SCSS
+ end
+
+ assert_selector_renders['> E']
+ assert_selector_renders['+ E']
+ assert_selector_renders['~ E']
+ assert_selector_renders['> > E']
+
+ assert_selector_renders['E*']
+ assert_selector_renders['E*.foo']
+ assert_selector_renders['E*:hover']
+ end
+
+ def test_disallowed_colon_hack
+ assert_raise_message(Sass::SyntaxError, 'The ":name: val" hack is not allowed in the Sass indented
syntax') do
+ to_sass("foo {:name: val;}", :syntax => :scss)
+ end
+ end
+
+ def test_nested_properties
+ assert_renders <<SASS, <<SCSS
+div
+ before: before
+ background:
+ color: blue
+ repeat: no-repeat
+ after: after
+SASS
+div {
+ before: before;
+ background: {
+ color: blue;
+ repeat: no-repeat;
+ };
+ after: after;
+}
+
+SCSS
+ end
+
+ def test_dasherize
+ assert_sass_to_scss(<<SCSS, <<SASS, :dasherize => true)
+ mixin under-scored-mixin($under-scored-arg: $under-scored-default) {
+ bar: $under-scored-arg;
+}
+
+div {
+ foo: under-scored-fn($under-scored-var + "before\#{$another-under-scored-var}after");
+ @include under-scored-mixin($passed-arg);
+ selector-\#{$under-scored-interp}: bold;
+}
+
+ if $under-scored {
+ @for $for-var from $from-var to $to-var {
+ @while $while-var == true {
+ $while-var: false;
+ }
+ }
+}
+SCSS
+=under_scored_mixin($under_scored_arg: $under_scored_default)
+ bar: $under_scored_arg
+div
+ foo: under_scored_fn($under_scored_var + "before\#{$another_under_scored_var}after")
+ +under_scored_mixin($passed_arg)
+ selector-\#{$under_scored_interp}: bold
+ if $under_scored
+ @for $for_var from $from_var to $to_var
+ @while $while_var == true
+ $while_var : false
+SASS
+ end
+
+ def test_loud_comment_conversion
+ assert_renders(<<SASS, <<SCSS)
+/*! \#{"interpolated"}
+SASS
+/*! \#{"interpolated"} */
+SCSS
+ end
+
+ def test_content_conversion
+ assert_renders(<<SASS, <<SCSS)
+$color: blue
+
+=context($class, $color: red)
+ .\#{$class}
+ background-color: $color
+ @content
+ border-color: $color
+
++context(parent)
+ +context(child, $color: yellow)
+ color: $color
+SASS
+$color: blue;
+
+ mixin context($class, $color: red) {
+ .\#{$class} {
+ background-color: $color;
+ @content;
+ border-color: $color;
+ }
+}
+
+ include context(parent) {
+ @include context(child, $color: yellow) {
+ color: $color;
+ }
+}
+SCSS
+
+ end
+
+ def test_empty_content
+ assert_scss_to_scss(<<SCSS)
+ mixin foo {
+ @content;
+}
+
+ include foo {}
+SCSS
+ end
+
+ def test_placeholder_conversion
+ assert_renders(<<SASS, <<SCSS)
+#content a%foo.bar
+ color: blue
+SASS
+#content a%foo.bar {
+ color: blue;
+}
+SCSS
+ end
+
+ def test_reference_selector
+ assert_renders(<<SASS, <<SCSS)
+foo /bar|baz/ bang
+ a: b
+SASS
+foo /bar|baz/ bang {
+ a: b;
+}
+SCSS
+ end
+
+ def test_subject
+ assert_renders(<<SASS, <<SCSS)
+foo bar! baz
+ a: b
+SASS
+foo bar! baz {
+ a: b;
+}
+SCSS
+ end
+
+ def test_placeholder_interoplation_conversion
+ assert_renders(<<SASS, <<SCSS)
+$foo: foo
+
+%\#{$foo}
+ color: blue
+
+.bar
+ @extend %foo
+SASS
+$foo: foo;
+
+%\#{$foo} {
+ color: blue;
+}
+
+.bar {
+ @extend %foo;
+}
+SCSS
+ end
+
+ def test_indent
+ assert_renders <<SASS, <<SCSS, :indent => " "
+foo bar
+ baz bang
+ baz: bang
+ bip: bop
+ blat: boo
+SASS
+foo bar {
+ baz bang {
+ baz: bang;
+ bip: bop;
+ }
+ blat: boo;
+}
+SCSS
+
+ assert_renders <<SASS, <<SCSS, :indent => "\t"
+foo bar
+ baz bang
+ baz: bang
+ bip: bop
+ blat: boo
+SASS
+foo bar {
+ baz bang {
+ baz: bang;
+ bip: bop;
+ }
+ blat: boo;
+}
+SCSS
+
+ assert_sass_to_scss <<SCSS, <<SASS, :indent => " "
+foo bar {
+ baz bang {
+ baz: bang;
+ bip: bop;
+ }
+ blat: boo;
+}
+SCSS
+foo bar
+ baz bang
+ baz: bang
+ bip: bop
+ blat: boo
+SASS
+
+ assert_sass_to_scss <<SCSS, <<SASS, :indent => "\t"
+foo bar {
+ baz bang {
+ baz: bang;
+ bip: bop;
+ }
+ blat: boo;
+}
+SCSS
+foo bar
+ baz bang
+ baz: bang
+ bip: bop
+ blat: boo
+SASS
+
+ assert_scss_to_sass <<SASS, <<SCSS, :indent => " "
+foo bar
+ baz bang
+ baz: bang
+ bip: bop
+ blat: boo
+SASS
+foo bar {
+ baz bang {
+ baz: bang;
+ bip: bop;
+ }
+ blat: boo;
+}
+SCSS
+
+ assert_scss_to_sass <<SASS, <<SCSS, :indent => "\t"
+foo bar
+ baz bang
+ baz: bang
+ bip: bop
+ blat: boo
+SASS
+foo bar {
+ baz bang {
+ baz: bang;
+ bip: bop;
+ }
+ blat: boo;
+}
+SCSS
+ end
+
+ def test_extend_with_optional
+ assert_renders <<SASS, <<SCSS
+foo
+ @extend .bar !optional
+SASS
+foo {
+ @extend .bar !optional;
+}
+SCSS
+ end
+
+ def test_mixin_var_args
+ assert_renders <<SASS, <<SCSS
+=foo($args...)
+ a: b
+
+=bar($a, $args...)
+ a: b
+
+.foo
+ +foo($list...)
+ +bar(1, $list...)
+SASS
+ mixin foo($args...) {
+ a: b;
+}
+
+ mixin bar($a, $args...) {
+ a: b;
+}
+
+.foo {
+ @include foo($list...);
+ @include bar(1, $list...);
+}
+SCSS
+ end
+
+ def test_mixin_var_kwargs
+ assert_renders <<SASS, <<SCSS
+=foo($a: b, $c: d)
+ a: $a
+ c: $c
+
+.foo
+ +foo($list..., $map...)
+ +foo(pos, $list..., $kwd: val, $map...)
+SASS
+ mixin foo($a: b, $c: d) {
+ a: $a;
+ c: $c;
+}
+
+.foo {
+ @include foo($list..., $map...);
+ @include foo(pos, $list..., $kwd: val, $map...);
+}
+SCSS
+ end
+
+ def test_function_var_args
+ assert_renders <<SASS, <<SCSS
+ function foo($args...)
+ @return foo
+
+ function bar($a, $args...)
+ @return bar
+
+.foo
+ a: foo($list...)
+ b: bar(1, $list...)
+SASS
+ function foo($args...) {
+ @return foo;
+}
+
+ function bar($a, $args...) {
+ @return bar;
+}
+
+.foo {
+ a: foo($list...);
+ b: bar(1, $list...);
+}
+SCSS
+ end
+
+ def test_function_var_kwargs
+ assert_renders <<SASS, <<SCSS
+ function foo($a: b, $c: d)
+ @return foo
+
+.foo
+ a: foo($list..., $map...)
+ b: foo(pos, $list..., $kwd: val, $map...)
+SASS
+ function foo($a: b, $c: d) {
+ @return foo;
+}
+
+.foo {
+ a: foo($list..., $map...);
+ b: foo(pos, $list..., $kwd: val, $map...);
+}
+SCSS
+ end
+
+ def test_at_root
+ assert_renders <<SASS, <<SCSS
+.foo
+ @at-root
+ .bar
+ a: b
+ .baz
+ c: d
+SASS
+.foo {
+ @at-root {
+ .bar {
+ a: b;
+ }
+ .baz {
+ c: d;
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_selector
+ assert_renders <<SASS, <<SCSS
+.foo
+ @at-root .bar
+ a: b
+SASS
+.foo {
+ @at-root .bar {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without
+ assert_renders <<SASS, <<SCSS
+.foo
+ @at-root (without: media rule)
+ a: b
+SASS
+.foo {
+ @at-root (without: media rule) {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with
+ assert_renders <<SASS, <<SCSS
+.foo
+ @at-root (with: media rule)
+ a: b
+SASS
+.foo {
+ @at-root (with: media rule) {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_function_var_kwargs_with_list
+ assert_renders <<SASS, <<SCSS
+ function foo($a: b, $c: d)
+ @return $a, $c
+
+.foo
+ a: foo($list..., $map...)
+SASS
+ function foo($a: b, $c: d) {
+ @return $a, $c;
+}
+
+.foo {
+ a: foo($list..., $map...);
+}
+SCSS
+ end
+
+ def test_keyframes
+ assert_renders(<<SASS, <<SCSS)
+ keyframes identifier
+ 0%
+ top: 0
+ left: 0
+ 30%
+ top: 50px
+ 68%, 72%
+ left: 50px
+ 100%
+ top: 100px
+ left: 100%
+SASS
+ keyframes identifier {
+ 0% {
+ top: 0;
+ left: 0;
+ }
+ 30% {
+ top: 50px;
+ }
+ 68%, 72% {
+ left: 50px;
+ }
+ 100% {
+ top: 100px;
+ left: 100%;
+ }
+}
+SCSS
+ end
+
+ ## Regression Tests
+
+ def test_list_in_args
+ assert_renders(<<SASS, <<SCSS)
++mixin((a, b, c))
+
++mixin($arg: (a, b, c))
+
++mixin(a, b, (c, d, e)...)
+SASS
+ include mixin((a, b, c));
+
+ include mixin($arg: (a, b, c));
+
+ include mixin(a, b, (c, d, e)...);
+SCSS
+ end
+
+ def test_media_query_with_expr
+ assert_renders <<SASS, <<SCSS
+ media foo and (bar: baz)
+ a: b
+SASS
+ media foo and (bar: baz) {
+ a: b;
+}
+SCSS
+ end
+
+ def test_nested_if_statements
+ assert_renders(<<SASS, <<SCSS)
+ if $foo
+ one
+ a: b
+ else
+ @if $bar
+ two
+ a: b
+ @else
+ three
+ a: b
+SASS
+ if $foo {
+ one {
+ a: b;
+ }
+}
+ else {
+ @if $bar {
+ two {
+ a: b;
+ }
+ }
+ @else {
+ three {
+ a: b;
+ }
+ }
+}
+SCSS
+ end
+
+ def test_comment_indentation
+ assert_renders(<<SASS, <<SCSS, :indent => ' ')
+foo
+ // bar
+ /* baz
+ a: b
+SASS
+foo {
+ // bar
+ /* baz */
+ a: b;
+}
+SCSS
+ end
+
+ def test_keyword_arguments
+ assert_renders(<<SASS, <<SCSS, :dasherize => true)
+$foo: foo($dash-ed: 2px)
+SASS
+$foo: foo($dash-ed: 2px);
+SCSS
+ assert_scss_to_sass(<<SASS, <<SCSS, :dasherize => true)
+$foo: foo($dash-ed: 2px)
+SASS
+$foo: foo($dash_ed: 2px);
+SCSS
+ assert_sass_to_scss(<<SCSS, <<SASS, :dasherize => true)
+$foo: foo($dash-ed: 2px);
+SCSS
+$foo: foo($dash_ed: 2px)
+SASS
+ assert_renders(<<SASS, <<SCSS)
+$foo: foo($under_scored: 1px)
+SASS
+$foo: foo($under_scored: 1px);
+SCSS
+ assert_renders(<<SASS, <<SCSS)
+$foo: foo($dash-ed: 2px, $under_scored: 1px)
+SASS
+$foo: foo($dash-ed: 2px, $under_scored: 1px);
+SCSS
+ end
+
+ def test_ambiguous_negation
+ assert_renders(<<SASS, <<SCSS, :indent => ' ')
+foo
+ ok: -$foo
+ comma: 10px, -$foo
+ needs-parens: 10px (-$foo)
+ no-parens: a 50px + 60px b
+SASS
+foo {
+ ok: -$foo;
+ comma: 10px, -$foo;
+ needs-parens: 10px (-$foo);
+ no-parens: a 50px + 60px b;
+}
+SCSS
+ end
+
+ def test_variable_with_global
+ assert_renders(<<SASS, <<SCSS)
+$var: 1
+
+foo
+ $var: 2 !global
+ $var: 3 !global !default
+SASS
+$var: 1;
+
+foo {
+ $var: 2 !global;
+ $var: 3 !global !default;
+}
+SCSS
+ end
+
+ private
+
+ def assert_sass_to_sass(sass, options = {})
+ assert_equal(sass.rstrip, to_sass(sass, options).rstrip,
+ "Expected Sass to transform to itself")
+ end
+
+ def assert_scss_to_sass(sass, scss, options = {})
+ assert_equal(sass.rstrip, to_sass(scss, options.merge(:syntax => :scss)).rstrip,
+ "Expected SCSS to transform to Sass")
+ end
+
+ def assert_scss_to_scss(scss, in_scss = nil, options = nil)
+ if in_scss.is_a?(Hash)
+ options = in_scss
+ in_scss = nil
+ end
+
+ in_scss ||= scss
+ options ||= {}
+
+ assert_equal(scss.rstrip, to_scss(in_scss, options.merge(:syntax => :scss)).rstrip,
+ "Expected SCSS to transform to #{scss == in_scss ? 'itself' : 'SCSS'}")
+ end
+
+ def assert_sass_to_scss(scss, sass, options = {})
+ assert_equal(scss.rstrip, to_scss(sass, options).rstrip,
+ "Expected Sass to transform to SCSS")
+ end
+
+ def assert_renders(sass, scss, options = {})
+ assert_sass_to_sass(sass, options)
+ assert_scss_to_sass(sass, scss, options)
+ assert_scss_to_scss(scss, options)
+ assert_sass_to_scss(scss, sass, options)
+ end
+
+ def to_sass(scss, options = {})
+ Sass::Util.silence_sass_warnings do
+ Sass::Engine.new(scss, options).to_tree.to_sass(options)
+ end
+ end
+
+ def to_scss(sass, options = {})
+ Sass::Util.silence_sass_warnings do
+ Sass::Engine.new(sass, options).to_tree.to_scss(options)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/css2sass_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/css2sass_test.rb
new file mode 100755
index 0000000..b8cd1f6
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/css2sass_test.rb
@@ -0,0 +1,477 @@
+#!/usr/bin/env ruby
+require 'minitest/autorun'
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/css'
+
+class CSS2SassTest < MiniTest::Test
+ def test_basic
+ css = <<CSS
+h1 {
+ color: red;
+}
+CSS
+ assert_equal(<<SASS, css2sass(css))
+h1
+ color: red
+SASS
+ assert_equal(<<SASS, css2sass(css, :old => true))
+h1
+ :color red
+SASS
+ end
+
+ def test_nesting
+ assert_equal(<<SASS, css2sass(<<CSS))
+li
+ display: none
+ a
+ text-decoration: none
+ span
+ color: yellow
+ &:hover
+ text-decoration: underline
+SASS
+li {
+ display: none;
+}
+
+li a {
+ text-decoration: none;
+}
+
+li a span {
+ color: yellow;
+}
+
+li a:hover {
+ text-decoration: underline;
+}
+CSS
+ end
+
+ def test_no_nesting_around_rules
+ assert_equal(<<SASS, css2sass(<<CSS))
+div .warning
+ color: #d21a19
+
+span .debug
+ cursor: crosshair
+
+div .debug
+ cursor: default
+SASS
+div .warning {
+ color: #d21a19; }
+span .debug {
+ cursor: crosshair;}
+div .debug {
+ cursor: default; }
+CSS
+ end
+
+ def test_comments_multiline
+ css = <<CSS
+/* comment */
+elephant.rawr {
+ rampages: excessively;
+}
+
+/* actual multiline
+ comment */
+span.turkey {
+ isdinner: true;
+}
+
+.turducken {
+ /* Sounds funny
+ doesn't it */
+ chimera: not_really;
+}
+
+#overhere {
+ bored: sorta; /* it's for a good
+ cause */
+ better_than: thread_pools;
+}
+
+#one_more {
+ finally: srsly;
+} /* just a line here */
+CSS
+ sass = <<SASS
+/* comment
+
+elephant.rawr
+ rampages: excessively
+
+/* actual multiline
+ *comment
+
+span.turkey
+ isdinner: true
+
+.turducken
+ /* Sounds funny
+ * doesn't it
+ chimera: not_really
+
+#overhere
+ bored: sorta
+ /* it's for a good
+ * cause
+ better_than: thread_pools
+
+#one_more
+ finally: srsly
+
+/* just a line here
+SASS
+ assert_equal(sass, css2sass(css))
+ end
+
+ def test_fold_commas
+ assert_equal(<<SASS, css2sass(<<CSS))
+li
+ .one, .two
+ color: red
+SASS
+li .one {
+ color: red;
+}
+li .two {
+ color: red;
+}
+CSS
+
+ assert_equal(<<SASS, css2sass(<<CSS))
+.one
+ color: green
+
+.two
+ color: green
+ color: red
+
+.three
+ color: red
+SASS
+.one, .two {
+ color: green;
+}
+
+.two, .three {
+ color: red;
+}
+CSS
+ end
+
+ def test_bad_formatting
+ assert_equal(<<SASS, css2sass(<<CSS))
+hello
+ parent: true
+ there
+ parent: false
+ who
+ hoo: false
+ why
+ y: true
+ when
+ wen: nao
+
+down_here
+ yeah: true
+SASS
+hello {
+ parent: true;
+}
+
+hello there {
+ parent: false;
+}
+hello who {
+ hoo: false;
+}
+hello why {
+ y: true;
+}
+hello when {
+ wen: nao;
+}
+
+
+
+down_here { yeah: true; }
+CSS
+ end
+
+ def test_comments_in_selectors
+ assert_equal(<<SASS, css2sass(<<CSS))
+.js
+ #location-navigation-form .form-submit, #business-listing-form .form-submit, #detailTabs ul,
#detailsEnhanced #addTags, #locationSearchList, #moreHoods
+ display: none
+
+#navListLeft
+ display: none
+SASS
+.js #location-navigation-form .form-submit,
+.js #business-listing-form .form-submit,
+.js #detailTabs ul,
+/* .js #addReview, */
+/* .js #addTags, */
+.js #detailsEnhanced #addTags,
+.js #locationSearchList,
+.js #moreHoods,
+#navListLeft
+ { display: none; }
+CSS
+ end
+
+ def test_pseudo_classes_are_escaped
+ assert_equal(<<SASS, css2sass(<<CSS))
+\\:focus
+ a: b
+ \\:foo
+ bar: baz
+SASS
+:focus {a: b;}
+:focus :foo {bar: baz;}
+CSS
+ end
+
+ def test_subject
+ silence_warnings {assert_equal(<<SASS, css2sass(<<CSS))}
+.foo
+ .bar!
+ .baz
+ a: b
+ .bip
+ c: d
+ .bar .bonk
+ e: f
+
+.flip!
+ &.bar
+ a: b
+ &.baz
+ c: d
+SASS
+.foo .bar! .baz {a: b;}
+.foo .bar! .bip {c: d;}
+.foo .bar .bonk {e: f;}
+
+.flip.bar! {a: b;}
+.flip.baz! {c: d;}
+CSS
+ end
+
+ # Regressions
+
+ def test_nesting_with_matching_property
+ assert_equal(<<SASS, css2sass(<<CSS))
+ul
+ width: 10px
+ div
+ width: 20px
+
+article
+ width: 10px
+ p
+ width: 20px
+SASS
+ul {width: 10px}
+ul div {width: 20px}
+article {width: 10px}
+article p {width: 20px}
+CSS
+ end
+
+ def test_empty_rule
+ assert_equal(<<SASS, css2sass(<<CSS))
+a
+SASS
+a {}
+CSS
+ end
+
+ def test_empty_rule_with_selector_combinator
+ assert_equal(<<SASS, css2sass(<<CSS))
+a
+ color: red
+ > b
+SASS
+a {color: red}
+a > b {}
+CSS
+ end
+
+ def test_nesting_within_media
+ assert_equal(<<SASS, css2sass(<<CSS))
+ media all
+ .next .vevent
+ padding-left: 0
+ padding-right: 0
+SASS
+ media all {
+ .next .vevent {
+ padding-left: 0;
+ padding-right: 0; } }
+CSS
+ end
+
+ def test_multiline_selector_within_media_and_with_child_selector
+ assert_equal(<<SASS, css2sass(<<CSS))
+ media all
+ foo bar, baz
+ padding-left: 0
+ padding-right: 0
+SASS
+ media all {
+ foo bar,
+ baz {
+ padding-left: 0;
+ padding-right: 0; } }
+CSS
+ end
+
+ def test_double_comma
+ assert_equal(<<SASS, css2sass(<<CSS))
+foo, bar
+ a: b
+SASS
+foo, , bar { a: b }
+CSS
+ end
+
+ def test_selector_splitting
+ assert_equal(<<SASS, css2sass(<<CSS))
+.foo >
+ .bar
+ a: b
+ .baz
+ c: d
+SASS
+.foo>.bar {a: b}
+.foo>.baz {c: d}
+CSS
+
+ assert_equal(<<SASS, css2sass(<<CSS))
+.foo
+ &::bar
+ a: b
+ &::baz
+ c: d
+SASS
+.foo::bar {a: b}
+.foo::baz {c: d}
+CSS
+ end
+
+ def test_triple_nesting
+ assert_equal(<<SASS, css2sass(<<CSS))
+.foo .bar .baz
+ a: b
+SASS
+.foo .bar .baz {a: b}
+CSS
+
+ assert_equal(<<SASS, css2sass(<<CSS))
+.bar > .baz
+ c: d
+SASS
+.bar > .baz {c: d}
+CSS
+ end
+
+ # Error reporting
+
+ def test_error_reporting
+ css2sass("foo")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(1, err.sass_line)
+ assert_equal('Invalid CSS after "foo": expected "{", was ""', err.message)
+ end
+
+ def test_error_reporting_in_line
+ css2sass("foo\nbar }\nbaz")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(2, err.sass_line)
+ assert_equal('Invalid CSS after "bar ": expected "{", was "}"', err.message)
+ end
+
+ def test_error_truncate_after
+ css2sass("#{"a" * 16}foo")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(1, err.sass_line)
+ assert_equal('Invalid CSS after "...aaaaaaaaaaaafoo": expected "{", was ""', err.message)
+ end
+
+ def test_error_truncate_was
+ css2sass("foo }foo#{"a" * 15}")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(1, err.sass_line)
+ assert_equal('Invalid CSS after "foo ": expected "{", was "}fooaaaaaaaaaaa..."', err.message)
+ end
+
+ def test_error_doesnt_truncate_after_when_elipsis_would_add_length
+ css2sass("#{"a" * 15}foo")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(1, err.sass_line)
+ assert_equal('Invalid CSS after "aaaaaaaaaaaaaaafoo": expected "{", was ""', err.message)
+ end
+
+ def test_error_doesnt_truncate_was_when_elipsis_would_add_length
+ css2sass("foo }foo#{"a" * 14}")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(1, err.sass_line)
+ assert_equal('Invalid CSS after "foo ": expected "{", was "}fooaaaaaaaaaaaaaa"', err.message)
+ end
+
+ def test_error_gets_rid_of_trailing_newline_for_after
+ css2sass("foo \n ")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(2, err.sass_line)
+ assert_equal('Invalid CSS after "foo": expected "{", was ""', err.message)
+ end
+
+ def test_error_gets_rid_of_trailing_newline_for_was
+ css2sass("foo \n }foo")
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => err
+ assert_equal(2, err.sass_line)
+ assert_equal('Invalid CSS after "foo": expected "{", was "}foo"', err.message)
+ end
+
+ # Encodings
+
+ unless Sass::Util.ruby1_8?
+ def test_encoding_error
+ css2sass("foo\nbar\nb\xFEaz".force_encoding("utf-8"))
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal(3, e.sass_line)
+ assert_equal('Invalid UTF-8 character "\xFE"', e.message)
+ end
+
+ def test_ascii_incompatible_encoding_error
+ template = "foo\nbar\nb_z".encode("utf-16le")
+ template[9] = "\xFE".force_encoding("utf-16le")
+ css2sass(template)
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal(3, e.sass_line)
+ assert_equal('Invalid UTF-16LE character "\xFE"', e.message)
+ end
+ end
+
+ private
+
+ def css2sass(string, opts={})
+ Sass::CSS.new(string, opts).render
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/data/hsl-rgb.txt
b/backends/css/gems/sass-3.4.9/test/sass/data/hsl-rgb.txt
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/data/hsl-rgb.txt
rename to backends/css/gems/sass-3.4.9/test/sass/data/hsl-rgb.txt
diff --git a/backends/css/gems/sass-3.4.9/test/sass/encoding_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/encoding_test.rb
new file mode 100755
index 0000000..f2f1b93
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/encoding_test.rb
@@ -0,0 +1,219 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+require 'sass/util/test'
+
+class EncodingTest < MiniTest::Test
+ include Sass::Util::Test
+
+ def test_encoding_error
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ render("foo\nbar\nb\xFEaz".force_encoding("utf-8"))
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal(3, e.sass_line)
+ assert_equal('Invalid UTF-8 character "\xFE"', e.message)
+ end
+
+ def test_ascii_incompatible_encoding_error
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ template = "foo\nbar\nb_z".encode("utf-16le")
+ template[9] = "\xFE".force_encoding("utf-16le")
+ render(template)
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal(3, e.sass_line)
+ assert_equal('Invalid UTF-16LE character "\xFE"', e.message)
+ end
+
+ def test_prefers_charset_to_ruby_encoding
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_renders_encoded(<<CSS, <<SASS.encode("IBM866").force_encoding("UTF-8"))
+ charset "UTF-8";
+fЖЖ {
+ a: b; }
+CSS
+ charset "ibm866"
+fЖЖ
+ a: b
+SASS
+ end
+
+ def test_uses_ruby_encoding_without_charset
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_renders_encoded(<<CSS, <<SASS.encode("IBM866"))
+ charset "UTF-8";
+тАЬ {
+ a: b; }
+CSS
+тАЬ
+ a: b
+SASS
+ end
+
+ def test_multibyte_charset_without_bom_declared_as_binary
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ engine = Sass::Engine.new(<<SASS.encode("UTF-16LE").force_encoding("BINARY"))
+ charset "utf-16le"
+fóó
+ a: b
+SASS
+ # Since multibyte encodings' @charset declarations aren't
+ # ASCII-compatible, we have to interpret the files as UTF-8 which will
+ # inevitably fail.
+ assert_raise_message(Sass::SyntaxError, "Invalid UTF-8 character \"\\xF3\"") {engine.render}
+ end
+
+ def test_multibyte_charset_without_bom_declared_as_utf_8
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ engine = Sass::Engine.new(<<SASS.encode("UTF-16LE").force_encoding("UTF-8"))
+ charset "utf-16le"
+fóó
+ a: b
+SASS
+ # Since multibyte encodings' @charset declarations aren't
+ # ASCII-compatible, we have to interpret the files as UTF-8 which will
+ # inevitably fail.
+ assert_raise_message(Sass::SyntaxError, "Invalid UTF-8 character \"\\xF3\"") {engine.render}
+ end
+
+ def test_utf_16le_with_bom
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_renders_encoded(<<CSS, <<SASS.encode("UTF-16LE").force_encoding("BINARY"))
+ charset "UTF-8";
+fóó {
+ a: b; }
+CSS
+\uFEFFfóó
+ a: b
+SASS
+ end
+
+ def test_utf_16be_with_bom
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_renders_encoded(<<CSS, <<SASS.encode("UTF-16BE").force_encoding("BINARY"))
+ charset "UTF-8";
+fóó {
+ a: b; }
+CSS
+\uFEFFfóó
+ a: b
+SASS
+ end
+
+ def test_utf_8_with_bom
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_renders_encoded(<<CSS, <<SASS.force_encoding("BINARY"))
+ charset "UTF-8";
+fóó {
+ a: b; }
+CSS
+\uFEFFfóó
+ a: b
+SASS
+ end
+
+ def test_charset_with_multibyte_encoding
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ engine = Sass::Engine.new(<<SASS)
+ charset "utf-32be"
+fóó
+ a: b
+SASS
+ # The charset declaration is just false here, so we should get an
+ # encoding error.
+ assert_raise_message(Sass::SyntaxError, "Invalid UTF-32BE character \"\\xC3\"") {engine.render}
+ end
+
+ def test_charset_with_special_case_encoding
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ # For some reason, a file with an ASCII-compatible UTF-16 charset
+ # declaration is specced to be parsed as UTF-8.
+ assert_renders_encoded(<<CSS, <<SASS.force_encoding("BINARY"))
+ charset "UTF-8";
+fóó {
+ a: b; }
+CSS
+ charset "utf-16"
+fóó
+ a: b
+SASS
+ end
+
+ def test_compressed_output_uses_bom
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_equal("\uFEFFfóó{a:b}\n", render(<<SASS, :style => :compressed))
+fóó
+ a: b
+SASS
+ end
+
+ def test_newline_normalization
+ assert_equal("/* foo\nbar\nbaz\nbang\nqux */\n",
+ render("/* foo\nbar\r\nbaz\fbang\rqux */", :syntax => :scss))
+ end
+
+ def test_null_normalization
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_equal(<<CSS, render("/* foo\x00bar\x00baz */", :syntax => :scss))
+#{"@charset \"UTF-8\";\n" unless Sass::Util.ruby1_8?
+}/* foo�bar�baz */
+CSS
+ end
+
+ # Regression
+
+ def test_multibyte_prop_name
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_equal(<<CSS, render(<<SASS))
+ charset "UTF-8";
+#bar {
+ cölor: blue; }
+CSS
+#bar
+ cölor: blue
+SASS
+ end
+
+ def test_multibyte_and_interpolation
+ return skip "Can't be run on Ruby 1.8." if Sass::Util.ruby1_8?
+
+ assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))
+#bar {
+ background: a 0%; }
+CSS
+#bar {
+ //
+ background: \#{a} 0%;
+}
+SCSS
+ end
+
+ private
+
+ def assert_renders_encoded(css, sass)
+ result = render(sass)
+ assert_equal css.encoding, result.encoding
+ assert_equal css, result
+ end
+
+ def render(sass, options = {})
+ munge_filename options
+ Sass::Engine.new(sass, options).render
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/engine_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/engine_test.rb
new file mode 100755
index 0000000..f39ed0a
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/engine_test.rb
@@ -0,0 +1,3301 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+require 'sass/engine'
+require 'stringio'
+require 'mock_importer'
+require 'pathname'
+
+module Sass::Script::Functions::UserFunctions
+ def option(name)
+ Sass::Script::Value::String.new(@options[name.value.to_sym].to_s)
+ end
+
+ def set_a_variable(name, value)
+ environment.set_var(name.value, value)
+ return Sass::Script::Value::Null.new
+ end
+
+ def set_a_global_variable(name, value)
+ environment.set_global_var(name.value, value)
+ return Sass::Script::Value::Null.new
+ end
+
+ def get_a_variable(name)
+ environment.var(name.value) || Sass::Script::Value::String.new("undefined")
+ end
+end
+
+module Sass::Script::Functions
+ include Sass::Script::Functions::UserFunctions
+end
+
+class SassEngineTest < MiniTest::Test
+ FAKE_FILE_NAME = __FILE__.gsub(/rb$/,"sass")
+ # A map of erroneous Sass documents to the error messages they should produce.
+ # The error messages may be arrays;
+ # if so, the second element should be the line number that should be reported for the error.
+ # If this isn't provided, the tests will assume the line number should be the last line of the document.
+ EXCEPTION_MAP = {
+ "$a: 1 + " => 'Invalid CSS after "1 +": expected expression (e.g. 1px, bold), was ""',
+ "$a: 1 + 2 +" => 'Invalid CSS after "1 + 2 +": expected expression (e.g. 1px, bold), was ""',
+ "$a: 1 + 2 + %" => 'Invalid CSS after "1 + 2 + ": expected expression (e.g. 1px, bold), was "%"',
+ "$a: foo(\"bar\"" => 'Invalid CSS after "foo("bar"": expected ")", was ""',
+ "$a: 1 }" => 'Invalid CSS after "1 ": expected expression (e.g. 1px, bold), was "}"',
+ "$a: 1 }foo\"" => 'Invalid CSS after "1 ": expected expression (e.g. 1px, bold), was "}foo""',
+ ":" => 'Invalid property: ":".',
+ ": a" => 'Invalid property: ": a".',
+ "a\n :b" => <<MSG,
+Invalid property: ":b" (no value).
+If ":b" should be a selector, use "\\:b" instead.
+MSG
+ "a\n b:" => 'Invalid property: "b:" (no value).',
+ "a\n :b: c" => 'Invalid property: ":b: c".',
+ "a\n :b:c d" => 'Invalid property: ":b:c d".',
+ "a\n :b c;" => 'Invalid CSS after "c": expected expression (e.g. 1px, bold), was ";"',
+ "a\n b: c;" => 'Invalid CSS after "c": expected expression (e.g. 1px, bold), was ";"',
+ ".foo ^bar\n a: b" => ['Invalid CSS after ".foo ": expected selector, was "^bar"', 1],
+ "a\n @extend .foo ^bar" => 'Invalid CSS after ".foo ": expected selector, was "^bar"',
+ "a\n @extend .foo .bar" => "Can't extend .foo .bar: can't extend nested selectors",
+ "a\n @extend >" => "Can't extend >: invalid selector",
+ "a\n @extend &.foo" => "Can't extend &.foo: can't extend parent selectors",
+ "a: b" => 'Properties are only allowed within rules, directives, mixin includes, or other properties.',
+ ":a b" => 'Properties are only allowed within rules, directives, mixin includes, or other properties.',
+ "$" => 'Invalid variable: "$".',
+ "$a" => 'Invalid variable: "$a".',
+ "$ a" => 'Invalid variable: "$ a".',
+ "$a b" => 'Invalid variable: "$a b".',
+ "$a: 1b + 2c" => "Incompatible units: 'c' and 'b'.",
+ "$a: 1b < 2c" => "Incompatible units: 'c' and 'b'.",
+ "$a: 1b > 2c" => "Incompatible units: 'c' and 'b'.",
+ "$a: 1b <= 2c" => "Incompatible units: 'c' and 'b'.",
+ "$a: 1b >= 2c" => "Incompatible units: 'c' and 'b'.",
+ "a\n b: 1b * 2c" => "2b*c isn't a valid CSS value.",
+ "a\n b: 1b % 2c" => "Incompatible units: 'c' and 'b'.",
+ "$a: 2px + #ccc" => "Cannot add a number with units (2px) to a color (#ccc).",
+ "$a: #ccc + 2px" => "Cannot add a number with units (2px) to a color (#ccc).",
+ "& a\n :b c" => ["Base-level rules cannot contain the parent-selector-referencing character '&'.", 1],
+ "a\n :b\n c" => "Illegal nesting: Only properties may be nested beneath properties.",
+ "$a: b\n :c d\n" => "Illegal nesting: Nothing may be nested beneath variable declarations.",
+ "$a: b\n :c d\n" => "Illegal nesting: Nothing may be nested beneath variable declarations.",
+ "@import templates/basic\n foo" => "Illegal nesting: Nothing may be nested beneath import directives.",
+ "foo\n @import foo.css" => "CSS import directives may only be used at the root of a document.",
+ "@if true\n @import foo" => "Import directives may not be used within control directives or mixins.",
+ "@if true\n .foo\n @import foo" => "Import directives may not be used within control directives or
mixins.",
+ "@mixin foo\n @import foo" => "Import directives may not be used within control directives or mixins.",
+ "@mixin foo\n .foo\n @import foo" => "Import directives may not be used within control directives or
mixins.",
+ "@import foo;" => "Invalid @import: expected end of line, was \";\".",
+ '$foo: "bar" "baz" !' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was
"!"},
+ '$foo: "bar" "baz" $' => %Q{Invalid CSS after ""bar" "baz" ": expected expression (e.g. 1px, bold), was
"$"}, #'
+ "=foo\n :color red\n.bar\n +bang" => "Undefined mixin 'bang'.",
+ "=foo\n :color red\n.bar\n +bang_bop" => "Undefined mixin 'bang_bop'.",
+ "=foo\n :color red\n.bar\n +bang-bop" => "Undefined mixin 'bang-bop'.",
+ ".foo\n =foo\n :color red\n.bar\n +foo" => "Undefined mixin 'foo'.",
+ " a\n b: c" => ["Indenting at the beginning of the document is illegal.", 1],
+ " \n \n\t\n a\n b: c" => ["Indenting at the beginning of the document is illegal.", 4],
+ "a\n b: c\n b: c" => ["Inconsistent indentation: 1 space was used for indentation, but the rest of the
document was indented using 2 spaces.", 3],
+ "a\n b: c\na\n b: c" => ["Inconsistent indentation: 1 space was used for indentation, but the rest of
the document was indented using 2 spaces.", 4],
+ "a\n\t\tb: c\n\tb: c" => ["Inconsistent indentation: 1 tab was used for indentation, but the rest of the
document was indented using 2 tabs.", 3],
+ "a\n b: c\n b: c" => ["Inconsistent indentation: 3 spaces were used for indentation, but the rest of
the document was indented using 2 spaces.", 3],
+ "a\n b: c\n a\n d: e" => ["Inconsistent indentation: 3 spaces were used for indentation, but the
rest of the document was indented using 2 spaces.", 4],
+ "a\n b: c\na\n d: e" => ["The line was indented 2 levels deeper than the previous line.", 4],
+ "a\n b: c\n a\n d: e" => ["The line was indented 3 levels deeper than the previous line.", 4],
+ "a\n \tb: c" => ["Indentation can't use both tabs and spaces.", 2],
+ "=a(" => 'Invalid CSS after "(": expected variable (e.g. $foo), was ""',
+ "=a(b)" => 'Invalid CSS after "(": expected variable (e.g. $foo), was "b)"',
+ "=a(,)" => 'Invalid CSS after "(": expected variable (e.g. $foo), was ",)"',
+ "=a($)" => 'Invalid CSS after "(": expected variable (e.g. $foo), was "$)"',
+ "=a($foo bar)" => 'Invalid CSS after "($foo ": expected ")", was "bar)"',
+ "=foo\n bar: baz\n+foo" => ["Properties are only allowed within rules, directives, mixin includes, or
other properties.", 2],
+ "a-\#{$b\n c: d" => ['Invalid CSS after "a-#{$b": expected "}", was ""', 1],
+ "=a($b: 1, $c)" => "Required argument $c must come before any optional arguments.",
+ "=a($b: 1)\n a: $b\ndiv\n +a(1,2)" => "Mixin a takes 1 argument but 2 were passed.",
+ "=a($b: 1)\n a: $b\ndiv\n +a(1,$c: 3)" => "Mixin a doesn't have an argument named $c.",
+ "=a($b)\n a: $b\ndiv\n +a" => "Mixin a is missing argument $b.",
+ "@function foo()\n 1 + 2" => "Functions can only contain variable declarations and control directives.",
+ "@function foo()\n foo: bar" => "Functions can only contain variable declarations and control
directives.",
+ "@function foo()\n foo: bar\n @return 3" => ["Functions can only contain variable declarations and
control directives.", 2],
+ "@function foo\n @return 1" => ['Invalid CSS after "": expected "(", was ""', 1],
+ "@function foo(\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was ""', 1],
+ "@function foo(b)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "b)"', 1],
+ "@function foo(,)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was ",)"', 1],
+ "@function foo($)\n @return 1" => ['Invalid CSS after "(": expected variable (e.g. $foo), was "$)"', 1],
+ "@function foo()\n @return" => 'Invalid @return: expected expression.',
+ "@function foo()\n @return 1\n $var: val" => 'Illegal nesting: Nothing may be nested beneath return
directives.',
+ "@function foo($a)\n @return 1\na\n b: foo()" => 'Function foo is missing argument $a.',
+ "@function foo()\n @return 1\na\n b: foo(2)" => 'Function foo takes 0 arguments but 1 was passed.',
+ "@function foo()\n @return 1\na\n b: foo($a: 1)" => "Function foo doesn't have an argument named $a.",
+ "@function foo()\n @return 1\na\n b: foo($a: 1, $b: 2)" => "Function foo doesn't have the following
arguments: $a, $b.",
+ "@return 1" => '@return may only be used within a function.',
+ "@if true\n @return 1" => '@return may only be used within a function.',
+ "@mixin foo\n @return 1\n include foo" => ['@return may only be used within a function.', 2],
+ "@else\n a\n b: c" => ["@else must come after @if.", 1],
+ "@if false\n else foo" => "Invalid else directive '@else foo': expected 'if <expr>'.",
+ "@if false\n else if " => "Invalid else directive '@else if': expected 'if <expr>'.",
+ "a\n $b: 12\nc\n d: $b" => 'Undefined variable: "$b".',
+ "=foo\n $b: 12\nc\n +foo\n d: $b" => 'Undefined variable: "$b".',
+ "c\n d: $b-foo" => 'Undefined variable: "$b-foo".',
+ "c\n d: $b_foo" => 'Undefined variable: "$b_foo".',
+ '@for $a from "foo" to 1' => '"foo" is not an integer.',
+ '@for $a from 1 to "2"' => '"2" is not an integer.',
+ '@for $a from 1 to "foo"' => '"foo" is not an integer.',
+ '@for $a from 1 to 1.232323' => '1.23232 is not an integer.',
+ '@for $a from 1px to 3em' => "Incompatible units: 'em' and 'px'.",
+ '@if' => "Invalid if directive '@if': expected expression.",
+ '@while' => "Invalid while directive '@while': expected expression.",
+ '@debug' => "Invalid debug directive '@debug': expected expression.",
+ %Q{ debug "a message"\n "nested message"} => "Illegal nesting: Nothing may be nested beneath debug
directives.",
+ '@error' => "Invalid error directive '@error': expected expression.",
+ %Q{ error "a message"\n "nested message"} => "Illegal nesting: Nothing may be nested beneath error
directives.",
+ '@warn' => "Invalid warn directive '@warn': expected expression.",
+ %Q{ warn "a message"\n "nested message"} => "Illegal nesting: Nothing may be nested beneath warn
directives.",
+ "/* foo\n bar\n baz" => "Inconsistent indentation: previous line was indented by 4 spaces, but this
line was indented by 2 spaces.",
+ '+foo(1 + 1: 2)' => 'Invalid CSS after "(1 + 1": expected comma, was ": 2)"',
+ '+foo($var: )' => 'Invalid CSS after "($var: ": expected mixin argument, was ")"',
+ '+foo($var: a, $var: b)' => 'Keyword argument "$var" passed more than once',
+ '+foo($var-var: a, $var_var: b)' => 'Keyword argument "$var_var" passed more than once',
+ '+foo($var_var: a, $var-var: b)' => 'Keyword argument "$var-var" passed more than once',
+ "a\n b: foo(1 + 1: 2)" => 'Invalid CSS after "foo(1 + 1": expected comma, was ": 2)"',
+ "a\n b: foo($var: )" => 'Invalid CSS after "foo($var: ": expected function argument, was ")"',
+ "a\n b: foo($var: a, $var: b)" => 'Keyword argument "$var" passed more than once',
+ "a\n b: foo($var-var: a, $var_var: b)" => 'Keyword argument "$var_var" passed more than once',
+ "a\n b: foo($var_var: a, $var-var: b)" => 'Keyword argument "$var-var" passed more than once',
+ "@if foo\n @extend .bar" => ["Extend directives may only be used within rules.", 2],
+ "$var: true\n while $var\n @extend .bar\n $var: false" => ["Extend directives may only be used within
rules.", 3],
+ "@for $i from 0 to 1\n @extend .bar" => ["Extend directives may only be used within rules.", 2],
+ "@mixin foo\n @extend bar\n include foo" => ["Extend directives may only be used within rules.", 2],
+ "foo %\n a: b" => ['Invalid CSS after "foo %": expected placeholder name, was ""', 1],
+ "=foo\n @content error" => "Invalid content directive. Trailing characters found: \"error\".",
+ "=foo\n @content\n b: c" => "Illegal nesting: Nothing may be nested beneath @content directives.",
+ "@content" => '@content may only be used within a mixin.',
+ "=simple\n .simple\n color: red\n+simple\n color: blue" => ['Mixin "simple" does not accept a
content block.', 4],
+ "@import \"foo\" // bar" => "Invalid CSS after \"\"foo\" \": expected media query list, was \"// bar\"",
+ "@at-root\n a: b" => "Properties are only allowed within rules, directives, mixin includes, or other
properties.",
+
+ # Regression tests
+ "a\n b:\n c\n d" => ["Illegal nesting: Only properties may be nested beneath properties.", 3],
+ "& foo\n bar: baz\n blat: bang" => ["Base-level rules cannot contain the parent-selector-referencing
character '&'.", 1],
+ "a\n b: c\n& foo\n bar: baz\n blat: bang" => ["Base-level rules cannot contain the
parent-selector-referencing character '&'.", 3],
+ "@" => "Invalid directive: '@'.",
+ }
+
+ def teardown
+ clean_up_sassc
+ end
+
+ def test_basic_render
+ renders_correctly "basic", { :style => :compact }
+ end
+
+ def test_empty_render
+ assert_equal "", render("")
+ end
+
+ def test_multiple_calls_to_render
+ sass = Sass::Engine.new("a\n b: c")
+ assert_equal sass.render, sass.render
+ end
+
+ def test_alternate_styles
+ renders_correctly "expanded", { :style => :expanded }
+ renders_correctly "compact", { :style => :compact }
+ renders_correctly "nested", { :style => :nested }
+ renders_correctly "compressed", { :style => :compressed }
+ end
+
+ def test_compile
+ assert_equal "div { hello: world; }\n", Sass.compile("$who: world\ndiv\n hello: $who", :syntax =>
:sass, :style => :compact)
+ assert_equal "div { hello: world; }\n", Sass.compile("$who: world; div { hello: $who }", :style =>
:compact)
+ end
+
+ def test_compile_file
+ FileUtils.mkdir_p(absolutize("tmp"))
+ open(absolutize("tmp/test_compile_file.sass"), "w") {|f| f.write("$who: world\ndiv\n hello: $who")}
+ open(absolutize("tmp/test_compile_file.scss"), "w") {|f| f.write("$who: world; div { hello: $who }")}
+ assert_equal "div { hello: world; }\n", Sass.compile_file(absolutize("tmp/test_compile_file.sass"),
:style => :compact)
+ assert_equal "div { hello: world; }\n", Sass.compile_file(absolutize("tmp/test_compile_file.scss"),
:style => :compact)
+ ensure
+ FileUtils.rm_rf(absolutize("tmp"))
+ end
+
+ def test_compile_file_to_css_file
+ FileUtils.mkdir_p(absolutize("tmp"))
+ open(absolutize("tmp/test_compile_file.sass"), "w") {|f| f.write("$who: world\ndiv\n hello: $who")}
+ open(absolutize("tmp/test_compile_file.scss"), "w") {|f| f.write("$who: world; div { hello: $who }")}
+ Sass.compile_file(absolutize("tmp/test_compile_file.sass"),
absolutize("tmp/test_compile_file_sass.css"), :style => :compact)
+ Sass.compile_file(absolutize("tmp/test_compile_file.scss"),
absolutize("tmp/test_compile_file_scss.css"), :style => :compact)
+ assert_equal "div { hello: world; }\n", File.read(absolutize("tmp/test_compile_file_sass.css"))
+ assert_equal "div { hello: world; }\n", File.read(absolutize("tmp/test_compile_file_scss.css"))
+ ensure
+ FileUtils.rm_rf(absolutize("tmp"))
+ end
+
+ def test_flexible_tabulation
+ assert_equal("p {\n a: b; }\n p q {\n c: d; }\n",
+ render("p\n a: b\n q\n c: d\n"))
+ assert_equal("p {\n a: b; }\n p q {\n c: d; }\n",
+ render("p\n\ta: b\n\tq\n\t\tc: d\n"))
+ end
+
+ def test_import_same_name_different_ext
+ assert_raise_message Sass::SyntaxError, <<ERROR do
+It's not clear which file to import for '@import "same_name_different_ext"'.
+Candidates:
+ same_name_different_ext.sass
+ same_name_different_ext.scss
+Please delete or rename all but one of these files.
+ERROR
+ options = {:load_paths => [File.dirname(__FILE__) + '/templates/']}
+ munge_filename options
+ Sass::Engine.new("@import 'same_name_different_ext'", options).render
+ end
+ end
+
+ def test_import_same_name_different_partiality
+ assert_raise_message Sass::SyntaxError, <<ERROR do
+It's not clear which file to import for '@import "same_name_different_partiality"'.
+Candidates:
+ _same_name_different_partiality.scss
+ same_name_different_partiality.scss
+Please delete or rename all but one of these files.
+ERROR
+ options = {:load_paths => [File.dirname(__FILE__) + '/templates/']}
+ munge_filename options
+ Sass::Engine.new("@import 'same_name_different_partiality'", options).render
+ end
+ end
+
+ EXCEPTION_MAP.each do |key, value|
+ define_method("test_exception (#{key.inspect})") do
+ line = 10
+ begin
+ silence_warnings {Sass::Engine.new(key, :filename => FAKE_FILE_NAME, :line => line).render}
+ rescue Sass::SyntaxError => err
+ value = [value] unless value.is_a?(Array)
+
+ assert_equal(value.first.rstrip, err.message, "Line: #{key}")
+ assert_equal(FAKE_FILE_NAME, err.sass_filename)
+ assert_equal((value[1] || key.split("\n").length) + line - 1, err.sass_line, "Line: #{key}")
+ assert_match(/#{Regexp.escape(FAKE_FILE_NAME)}:[0-9]+/, err.backtrace[0], "Line: #{key}")
+ else
+ assert(false, "Exception not raised for\n#{key}")
+ end
+ end
+ end
+
+ def test_exception_line
+ to_render = <<SASS
+rule
+ :prop val
+ // comment!
+
+ :broken
+SASS
+ begin
+ Sass::Engine.new(to_render).render
+ rescue Sass::SyntaxError => err
+ assert_equal(5, err.sass_line)
+ else
+ assert(false, "Exception not raised for '#{to_render}'!")
+ end
+ end
+
+ def test_exception_location
+ to_render = <<SASS
+rule
+ :prop val
+ // comment!
+
+ :broken
+SASS
+ begin
+ Sass::Engine.new(to_render, :filename => FAKE_FILE_NAME, :line => (__LINE__-7)).render
+ rescue Sass::SyntaxError => err
+ assert_equal(FAKE_FILE_NAME, err.sass_filename)
+ assert_equal((__LINE__-6), err.sass_line)
+ else
+ assert(false, "Exception not raised for '#{to_render}'!")
+ end
+ end
+
+ def test_imported_exception
+ [1, 2, 3, 4].each do |i|
+ begin
+ Sass::Engine.new("@import bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
+ rescue Sass::SyntaxError => err
+ assert_equal(2, err.sass_line)
+ assert_match(/(\/|^)bork#{i}\.sass$/, err.sass_filename)
+
+ assert_hash_has(err.sass_backtrace.first,
+ :filename => err.sass_filename, :line => err.sass_line)
+
+ assert_nil(err.sass_backtrace[1][:filename])
+ assert_equal(1, err.sass_backtrace[1][:line])
+
+ assert_match(/(\/|^)bork#{i}\.sass:2$/, err.backtrace.first)
+ assert_equal("(sass):1", err.backtrace[1])
+ else
+ assert(false, "Exception not raised for imported template: bork#{i}")
+ end
+ end
+ end
+
+ def test_double_imported_exception
+ [1, 2, 3, 4].each do |i|
+ begin
+ Sass::Engine.new("@import nested_bork#{i}", :load_paths => [File.dirname(__FILE__) +
'/templates/']).render
+ rescue Sass::SyntaxError => err
+ assert_equal(2, err.sass_line)
+ assert_match(/(\/|^)bork#{i}\.sass$/, err.sass_filename)
+
+ assert_hash_has(err.sass_backtrace.first,
+ :filename => err.sass_filename, :line => err.sass_line)
+
+ assert_match(/(\/|^)nested_bork#{i}\.sass$/, err.sass_backtrace[1][:filename])
+ assert_equal(2, err.sass_backtrace[1][:line])
+
+ assert_nil(err.sass_backtrace[2][:filename])
+ assert_equal(1, err.sass_backtrace[2][:line])
+
+ assert_match(/(\/|^)bork#{i}\.sass:2$/, err.backtrace.first)
+ assert_match(/(\/|^)nested_bork#{i}\.sass:2$/, err.backtrace[1])
+ assert_equal("(sass):1", err.backtrace[2])
+ else
+ assert(false, "Exception not raised for imported template: bork#{i}")
+ end
+ end
+ end
+
+ def test_selector_tracing
+ actual_css = render(<<-SCSS, :syntax => :scss, :trace_selectors => true)
+ @mixin mixed {
+ .mixed { color: red; }
+ }
+ .context {
+ @include mixed;
+ }
+ SCSS
+ assert_equal(<<CSS,actual_css)
+/* on line 2 of test_selector_tracing_inline.scss, in `mixed'
+ from line 5 of test_selector_tracing_inline.scss */
+.context .mixed {
+ color: red; }
+CSS
+ end
+
+ def test_mixin_exception
+ render(<<SASS)
+=error-mixin($a)
+ color: $a * 1em * 1px
+
+=outer-mixin($a)
+ +error-mixin($a)
+
+.error
+ +outer-mixin(12)
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_equal(2, err.sass_line)
+ assert_equal(filename_for_test, err.sass_filename)
+ assert_equal("error-mixin", err.sass_mixin)
+
+ assert_hash_has(err.sass_backtrace.first, :line => err.sass_line,
+ :filename => err.sass_filename, :mixin => err.sass_mixin)
+ assert_hash_has(err.sass_backtrace[1], :line => 5,
+ :filename => filename_for_test, :mixin => "outer-mixin")
+ assert_hash_has(err.sass_backtrace[2], :line => 8,
+ :filename => filename_for_test, :mixin => nil)
+
+ assert_equal("#{filename_for_test}:2:in `error-mixin'", err.backtrace.first)
+ assert_equal("#{filename_for_test}:5:in `outer-mixin'", err.backtrace[1])
+ assert_equal("#{filename_for_test}:8", err.backtrace[2])
+ end
+
+ def test_mixin_callsite_exception
+ render(<<SASS)
+=one-arg-mixin($a)
+ color: $a
+
+=outer-mixin($a)
+ +one-arg-mixin($a, 12)
+
+.error
+ +outer-mixin(12)
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_hash_has(err.sass_backtrace.first, :line => 5,
+ :filename => filename_for_test, :mixin => "one-arg-mixin")
+ assert_hash_has(err.sass_backtrace[1], :line => 5,
+ :filename => filename_for_test, :mixin => "outer-mixin")
+ assert_hash_has(err.sass_backtrace[2], :line => 8,
+ :filename => filename_for_test, :mixin => nil)
+ end
+
+ def test_mixin_exception_cssize
+ render(<<SASS)
+=parent-ref-mixin
+ & foo
+ a: b
+
+=outer-mixin
+ +parent-ref-mixin
+
++outer-mixin
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_hash_has(err.sass_backtrace.first, :line => 2,
+ :filename => filename_for_test, :mixin => "parent-ref-mixin")
+ assert_hash_has(err.sass_backtrace[1], :line => 6,
+ :filename => filename_for_test, :mixin => "outer-mixin")
+ assert_hash_has(err.sass_backtrace[2], :line => 8,
+ :filename => filename_for_test, :mixin => nil)
+ end
+
+ def test_mixin_and_import_exception
+ Sass::Engine.new("@import nested_mixin_bork", :load_paths => [File.dirname(__FILE__) +
'/templates/']).render
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_match(/(\/|^)nested_mixin_bork\.sass$/, err.sass_backtrace.first[:filename])
+ assert_hash_has(err.sass_backtrace.first, :mixin => "error-mixin", :line => 4)
+
+ assert_match(/(\/|^)mixin_bork\.sass$/, err.sass_backtrace[1][:filename])
+ assert_hash_has(err.sass_backtrace[1], :mixin => "outer-mixin", :line => 2)
+
+ assert_match(/(\/|^)mixin_bork\.sass$/, err.sass_backtrace[2][:filename])
+ assert_hash_has(err.sass_backtrace[2], :mixin => nil, :line => 5)
+
+ assert_match(/(\/|^)nested_mixin_bork\.sass$/, err.sass_backtrace[3][:filename])
+ assert_hash_has(err.sass_backtrace[3], :mixin => nil, :line => 6)
+
+ assert_hash_has(err.sass_backtrace[4], :filename => nil, :mixin => nil, :line => 1)
+ end
+
+ def test_recursive_mixin
+ assert_equal <<CSS, render(<<SASS)
+.foo .bar .baz {
+ color: blue; }
+.foo .bar .qux {
+ color: red; }
+.foo .zap {
+ color: green; }
+CSS
+ mixin map-to-rule($map-or-color)
+ @if type-of($map-or-color) == map
+ @each $key, $value in $map-or-color
+ .\#{$key}
+ @include map-to-rule($value)
+ @else
+ color: $map-or-color
+
+ include map-to-rule((foo: (bar: (baz: blue, qux: red), zap: green)))
+SASS
+ end
+
+ def test_double_import_loop_exception
+ importer = MockImporter.new
+ importer.add_import("foo", "@import 'bar'")
+ importer.add_import("bar", "@import 'foo'")
+
+ engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
+ :load_paths => [importer], :importer => importer)
+
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
+An @import loop has been found:
+ #{filename_for_test} imports foo
+ foo imports bar
+ bar imports foo
+ERR
+ end
+
+ def test_deep_import_loop_exception
+ importer = MockImporter.new
+ importer.add_import("foo", "@import 'bar'")
+ importer.add_import("bar", "@import 'baz'")
+ importer.add_import("baz", "@import 'foo'")
+
+ engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
+ :load_paths => [importer], :importer => importer)
+
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
+An @import loop has been found:
+ #{filename_for_test} imports foo
+ foo imports bar
+ bar imports baz
+ baz imports foo
+ERR
+ end
+
+ def test_exception_css_with_offset
+ opts = {:full_exception => true, :line => 362}
+ render(("a\n b: c\n" * 10) + "d\n e:\n" + ("f\n g: h\n" * 10), opts)
+ rescue Sass::SyntaxError => e
+ assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e, opts[:line]).split("\n")[0..15].join("\n"))
+/*
+Error: Invalid property: "e:" (no value).
+ on line 383 of test_exception_css_with_offset_inline.sass
+
+378: a
+379: b: c
+380: a
+381: b: c
+382: d
+383: e:
+384: f
+385: g: h
+386: f
+387: g: h
+388: f
+CSS
+ else
+ assert(false, "Exception not raised for test_exception_css_with_offset")
+ end
+
+ def test_exception_css_with_mixins
+ render(<<SASS, :full_exception => true)
+=error-mixin($a)
+ color: $a * 1em * 1px
+
+=outer-mixin($a)
+ +error-mixin($a)
+
+.error
+ +outer-mixin(12)
+SASS
+ rescue Sass::SyntaxError => e
+ assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e).split("\n")[0..13].join("\n"))
+/*
+Error: 12em*px isn't a valid CSS value.
+ on line 2 of test_exception_css_with_mixins_inline.sass, in `error-mixin'
+ from line 5 of test_exception_css_with_mixins_inline.sass, in `outer-mixin'
+ from line 8 of test_exception_css_with_mixins_inline.sass
+
+1: =error-mixin($a)
+2: color: $a * 1em * 1px
+3:
+4: =outer-mixin($a)
+5: +error-mixin($a)
+6:
+7: .error
+CSS
+ else
+ assert(false, "Exception not raised")
+ end
+
+ def test_cssize_exception_css
+ render(<<SASS, :full_exception => true)
+.filler
+ stuff: "stuff!"
+
+a: b
+
+.more.filler
+ a: b
+SASS
+ rescue Sass::SyntaxError => e
+ assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e).split("\n")[0..11].join("\n"))
+/*
+Error: Properties are only allowed within rules, directives, mixin includes, or other properties.
+ on line 4 of test_cssize_exception_css_inline.sass
+
+1: .filler
+2: stuff: "stuff!"
+3:
+4: a: b
+5:
+6: .more.filler
+7: a: b
+CSS
+ else
+ assert(false, "Exception not raised")
+ end
+
+ def test_css_import
+ assert_equal("@import url(./fonts.css);\n", render("@import \"./fonts.css\""))
+ end
+
+ def test_http_import
+ assert_equal("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\";\n",
+ render("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\""))
+ end
+
+ def test_protocol_relative_import
+ assert_equal("@import \"//fonts.googleapis.com/css?family=Droid+Sans\";\n",
+ render("@import \"//fonts.googleapis.com/css?family=Droid+Sans\""))
+ end
+
+ def test_import_with_interpolation
+ assert_equal(<<CSS, render(<<SASS))
+ import url("http://fonts.googleapis.com/css?family=Droid+Sans");
+CSS
+$family: unquote("Droid+Sans")
+ import url("http://fonts.googleapis.com/css?family=\#{$family}")
+SASS
+ end
+
+ def test_import_with_dynamic_media_query
+ assert_equal(<<CSS, render(<<SASS))
+ import "foo" print and (-webkit-min-device-pixel-ratio-foo: 25);
+CSS
+$media: print
+$key: -webkit-min-device-pixel-ratio
+$value: 20
+ import "foo" \#{$media} and ($key + "-foo": $value + 5)
+SASS
+ end
+
+ def test_url_import
+ assert_equal("@import url(fonts.sass);\n", render("@import url(fonts.sass)"))
+ end
+
+ def test_sass_import
+ sassc_file = sassc_path("importee")
+ assert !File.exist?(sassc_file)
+ renders_correctly "import", { :style => :compact, :load_paths => [File.dirname(__FILE__) + "/templates"]
}
+ assert File.exist?(sassc_file)
+ end
+
+ def test_sass_pathname_import
+ sassc_file = sassc_path("importee")
+ assert !File.exist?(sassc_file)
+ renders_correctly("import",
+ :style => :compact,
+ :load_paths => [Pathname.new(File.dirname(__FILE__) + "/templates")])
+ assert File.exist?(sassc_file)
+ end
+
+ def test_import_from_global_load_paths
+ importer = MockImporter.new
+ importer.add_import("imported", "div{color:red}")
+ Sass.load_paths << importer
+
+ assert_equal "div {\n color: red; }\n", Sass::Engine.new('@import "imported"', :importer =>
importer).render
+ ensure
+ Sass.load_paths.clear
+ end
+
+ def test_nonexistent_import
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) do
+File to import not found or unreadable: nonexistent.sass.
+Load path: #{Dir.pwd}
+ERR
+ render("@import nonexistent.sass")
+ end
+ end
+
+ def test_nonexistent_extensionless_import
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) do
+File to import not found or unreadable: nonexistent.
+Load path: #{Dir.pwd}
+ERR
+ render("@import nonexistent")
+ end
+ end
+
+ def test_no_cache
+ assert !File.exist?(sassc_path("importee"))
+ renders_correctly("import", {
+ :style => :compact, :cache => false,
+ :load_paths => [File.dirname(__FILE__) + "/templates"],
+ })
+ assert !File.exist?(sassc_path("importee"))
+ end
+
+ def test_import_in_rule
+ assert_equal(<<CSS, render(<<SASS, :load_paths => [File.dirname(__FILE__) + '/templates/']))
+.foo #foo {
+ background-color: #baf; }
+
+.bar {
+ a: b; }
+ .bar #foo {
+ background-color: #baf; }
+CSS
+.foo
+ @import partial
+
+.bar
+ a: b
+ @import partial
+SASS
+ end
+
+ def test_units
+ renders_correctly "units"
+ end
+
+ def test_default_function
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ bar: url("foo.png"); }
+CSS
+foo
+ bar: url("foo.png")
+SASS
+ assert_equal("foo {\n bar: url(); }\n", render("foo\n bar: url()\n"));
+ end
+
+ def test_string_minus
+ assert_equal("foo {\n bar: baz-boom-bat; }\n", render(%Q{foo\n bar: baz-boom-bat}))
+ assert_equal("foo {\n bar: -baz-boom; }\n", render(%Q{foo\n bar: -baz-boom}))
+ end
+
+ def test_string_div
+ assert_equal("foo {\n bar: baz/boom/bat; }\n", render(%Q{foo\n bar: baz/boom/bat}))
+ assert_equal("foo {\n bar: /baz/boom; }\n", render(%Q{foo\n bar: /baz/boom}))
+ end
+
+ def test_basic_multiline_selector
+ assert_equal("#foo #bar,\n#baz #boom {\n foo: bar; }\n",
+ render("#foo #bar,\n#baz #boom\n :foo bar"))
+ assert_equal("#foo #bar,\n#foo #baz {\n foo: bar; }\n",
+ render("#foo\n #bar,\n #baz\n :foo bar"))
+ assert_equal("#foo,\n#bar {\n foo: bar; }\n #foo #baz,\n #bar #baz {\n foo: bar; }\n",
+ render("#foo,\n#bar\n :foo bar\n #baz\n :foo bar"))
+ assert_equal("#foo #bar, #baz #boom { foo: bar; }\n",
+ render("#foo #bar,\n#baz #boom\n :foo bar", :style => :compact))
+
+ assert_equal("#foo #bar,#baz #boom{foo:bar}\n",
+ render("#foo #bar,\n#baz #boom\n :foo bar", :style => :compressed))
+
+ assert_equal("#foo #bar,\n#baz #boom {\n foo: bar; }\n",
+ render("#foo #bar,,\n,#baz #boom,\n :foo bar"))
+
+ assert_equal("#bip #bop {\n foo: bar; }\n",
+ render("#bip #bop,, ,\n :foo bar"))
+ end
+
+ def test_complex_multiline_selector
+ renders_correctly "multiline"
+ end
+
+ def test_colon_only
+ begin
+ render("a\n b: c", :property_syntax => :old)
+ rescue Sass::SyntaxError => e
+ assert_equal("Illegal property syntax: can't use new syntax when :property_syntax => :old is set.",
+ e.message)
+ assert_equal(2, e.sass_line)
+ else
+ assert(false, "SyntaxError not raised for :property_syntax => :old")
+ end
+
+ begin
+ render("a\n :b c", :property_syntax => :new)
+ assert_equal(2, e.sass_line)
+ rescue Sass::SyntaxError => e
+ assert_equal("Illegal property syntax: can't use old syntax when :property_syntax => :new is set.",
+ e.message)
+ else
+ assert(false, "SyntaxError not raised for :property_syntax => :new")
+ end
+ end
+
+ def test_pseudo_elements
+ assert_equal(<<CSS, render(<<SASS))
+::first-line {
+ size: 10em; }
+CSS
+::first-line
+ size: 10em
+SASS
+ end
+
+ def test_directive
+ assert_equal("@a b;\n", render("@a b"))
+
+ assert_equal("@a {\n b: c; }\n", render("@a\n :b c"))
+ assert_equal("@a { b: c; }\n", render("@a\n :b c", :style => :compact))
+ assert_equal("@a {\n b: c;\n}\n", render("@a\n :b c", :style => :expanded))
+ assert_equal("@a{b:c}\n", render("@a\n :b c", :style => :compressed))
+
+ assert_equal("@a {\n b: c;\n d: e; }\n",
+ render("@a\n :b c\n :d e"))
+ assert_equal("@a { b: c; d: e; }\n",
+ render("@a\n :b c\n :d e", :style => :compact))
+ assert_equal("@a {\n b: c;\n d: e;\n}\n",
+ render("@a\n :b c\n :d e", :style => :expanded))
+ assert_equal("@a{b:c;d:e}\n",
+ render("@a\n :b c\n :d e", :style => :compressed))
+
+ assert_equal("@a {\n #b {\n c: d; } }\n",
+ render("@a\n #b\n :c d"))
+ assert_equal("@a { #b { c: d; } }\n",
+ render("@a\n #b\n :c d", :style => :compact))
+ assert_equal("@a {\n #b {\n c: d;\n }\n}\n",
+ render("@a\n #b\n :c d", :style => :expanded))
+ assert_equal("@a{#b{c:d}}\n",
+ render("@a\n #b\n :c d", :style => :compressed))
+
+ assert_equal("@a {\n #b {\n a: b; }\n #b #c {\n d: e; } }\n",
+ render("@a\n #b\n :a b\n #c\n :d e"))
+ assert_equal("@a { #b { a: b; }\n #b #c { d: e; } }\n",
+ render("@a\n #b\n :a b\n #c\n :d e", :style => :compact))
+ assert_equal("@a {\n #b {\n a: b;\n }\n #b #c {\n d: e;\n }\n}\n",
+ render("@a\n #b\n :a b\n #c\n :d e", :style => :expanded))
+ assert_equal("@a{#b{a:b}#b #c{d:e}}\n",
+ render("@a\n #b\n :a b\n #c\n :d e", :style => :compressed))
+
+ assert_equal("@a {\n #foo,\n #bar {\n b: c; } }\n",
+ render("@a\n #foo, \n #bar\n :b c"))
+ assert_equal("@a { #foo, #bar { b: c; } }\n",
+ render("@a\n #foo, \n #bar\n :b c", :style => :compact))
+ assert_equal("@a {\n #foo,\n #bar {\n b: c;\n }\n}\n",
+ render("@a\n #foo, \n #bar\n :b c", :style => :expanded))
+ assert_equal("@a{#foo,#bar{b:c}}\n",
+ render("@a\n #foo, \n #bar\n :b c", :style => :compressed))
+
+ to_render = <<END
+ a
+ :b c
+ #d
+ :e f
+ :g h
+END
+ rendered = <<END
+ a { b: c;
+ #d { e: f; }
+ g: h; }
+END
+ assert_equal(rendered, render(to_render, :style => :compact))
+
+ assert_equal("@a{b:c;#d{e:f}g:h}\n", render(to_render, :style => :compressed))
+ end
+
+ def test_property_hacks
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ _name: val;
+ *name: val;
+ #name: val;
+ .name: val;
+ name/**/: val;
+ name/*\\**/: val;
+ name: val; }
+CSS
+foo
+ _name: val
+ *name: val
+ #name: val
+ .name: val
+ name/**/: val
+ name/*\\**/: val
+ name: val
+SASS
+ end
+
+ def test_properties_with_space_after_colon
+ assert_equal <<CSS, render(<<SASS)
+foo {
+ bar: baz;
+ bizz: bap; }
+CSS
+foo
+ bar : baz
+ bizz : bap
+SASS
+ end
+
+ def test_line_annotations
+ assert_equal(<<CSS, render(<<SASS, :line_comments => true, :style => :compact))
+/* line 2, test_line_annotations_inline.sass */
+foo bar { foo: bar; }
+/* line 5, test_line_annotations_inline.sass */
+foo baz { blip: blop; }
+
+/* line 9, test_line_annotations_inline.sass */
+floodle { flop: blop; }
+
+/* line 18, test_line_annotations_inline.sass */
+bup { mix: on; }
+/* line 15, test_line_annotations_inline.sass */
+bup mixin { moop: mup; }
+
+/* line 22, test_line_annotations_inline.sass */
+bip hop, skip hop { a: b; }
+CSS
+foo
+ bar
+ foo: bar
+
+ baz
+ blip: blop
+
+
+floodle
+
+ flop: blop
+
+=mxn
+ mix: on
+ mixin
+ moop: mup
+
+bup
+ +mxn
+
+bip, skip
+ hop
+ a: b
+SASS
+ end
+
+ def test_line_annotations_with_filename
+ renders_correctly "line_numbers", :line_comments => true, :load_paths => [File.dirname(__FILE__) +
"/templates"]
+ end
+
+ def test_debug_info
+ esc_file_name = Sass::SCSS::RX.escape_ident(Sass::Util.scope("test_debug_info_inline.sass"))
+
+ assert_equal(<<CSS, render(<<SASS, :debug_info => true, :style => :compact))
+ media -sass-debug-info{filename{font-family:file\\:\\/\\/#{esc_file_name}}line{font-family:\\000032}}
+foo bar { foo: bar; }
+ media -sass-debug-info{filename{font-family:file\\:\\/\\/#{esc_file_name}}line{font-family:\\000035}}
+foo baz { blip: blop; }
+
+ media -sass-debug-info{filename{font-family:file\\:\\/\\/#{esc_file_name}}line{font-family:\\000039}}
+floodle { flop: blop; }
+
+ media -sass-debug-info{filename{font-family:file\\:\\/\\/#{esc_file_name}}line{font-family:\\0000318}}
+bup { mix: on; }
+ media -sass-debug-info{filename{font-family:file\\:\\/\\/#{esc_file_name}}line{font-family:\\0000315}}
+bup mixin { moop: mup; }
+
+ media -sass-debug-info{filename{font-family:file\\:\\/\\/#{esc_file_name}}line{font-family:\\0000322}}
+bip hop, skip hop { a: b; }
+CSS
+foo
+ bar
+ foo: bar
+
+ baz
+ blip: blop
+
+
+floodle
+
+ flop: blop
+
+=mxn
+ mix: on
+ mixin
+ moop: mup
+
+bup
+ +mxn
+
+bip, skip
+ hop
+ a: b
+SASS
+ end
+
+ def test_debug_info_without_filename
+ assert_equal(<<CSS, Sass::Engine.new(<<SASS, :debug_info => true).render)
+ media -sass-debug-info{filename{}line{font-family:\\000031}}
+foo {
+ a: b; }
+CSS
+foo
+ a: b
+SASS
+ end
+
+ def test_debug_info_with_compressed
+ assert_equal(<<CSS, render(<<SASS, :debug_info => true, :style => :compressed))
+foo{a:b}
+CSS
+foo
+ a: b
+SASS
+ end
+
+ def test_debug_info_with_line_annotations
+ esc_file_name =
Sass::SCSS::RX.escape_ident(Sass::Util.scope("test_debug_info_with_line_annotations_inline.sass"))
+
+ assert_equal(<<CSS, render(<<SASS, :debug_info => true, :line_comments => true))
+ media -sass-debug-info{filename{font-family:file\\:\\/\\/#{esc_file_name}}line{font-family:\\000031}}
+foo {
+ a: b; }
+CSS
+foo
+ a: b
+SASS
+ end
+
+ def test_debug_info_in_keyframes
+ assert_equal(<<CSS, render(<<SASS, :debug_info => true))
+ -webkit-keyframes warm {
+ from {
+ color: black; }
+ to {
+ color: red; } }
+CSS
+ -webkit-keyframes warm
+ from
+ color: black
+ to
+ color: red
+SASS
+ end
+
+ def test_empty_first_line
+ assert_equal("#a {\n b: c; }\n", render("#a\n\n b: c"))
+ end
+
+ def test_escaped_rule
+ assert_equal(":focus {\n a: b; }\n", render("\\:focus\n a: b"))
+ assert_equal("a {\n b: c; }\n a :focus {\n d: e; }\n", render("\\a\n b: c\n \\:focus\n d: e"))
+ end
+
+ def test_cr_newline
+ assert_equal("foo {\n a: b;\n c: d;\n e: f; }\n", render("foo\r a: b\r\n c: d\n\r e: f"))
+ end
+
+ def test_property_with_content_and_nested_props
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ a: b;
+ a-c: d;
+ a-c-e: f; }
+CSS
+foo
+ a: b
+ c: d
+ e: f
+SASS
+
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ a: b;
+ a-c-e: f; }
+CSS
+foo
+ a: b
+ c:
+ e: f
+SASS
+ end
+
+ def test_guarded_assign
+ assert_equal("foo {\n a: b; }\n", render(%Q{$foo: b\n$foo: c !default\nfoo\n a: $foo}))
+ assert_equal("foo {\n a: b; }\n", render(%Q{$foo: b !default\nfoo\n a: $foo}))
+ assert_equal("foo {\n a: b; }\n", render(%Q{$foo: null\n$foo: b !default\nfoo\n a: $foo}))
+ end
+
+ def test_mixins
+ renders_correctly "mixins", { :style => :expanded }
+ end
+
+ def test_directive_style_mixins
+ assert_equal <<CSS, render(<<SASS)
+bar {
+ prop: baz; }
+CSS
+ mixin foo($arg)
+ prop: $arg
+
+bar
+ @include foo(baz)
+SASS
+ end
+
+ def test_mixins_dont_interfere_with_sibling_combinator
+ assert_equal("foo + bar {\n a: b; }\nfoo + baz {\n c: d; }\n",
+ render("foo\n +\n bar\n a: b\n baz\n c: d"))
+ end
+
+ def test_mixin_args
+ assert_equal("blat {\n baz: hi; }\n", render(<<SASS))
+=foo($bar)
+ baz: $bar
+blat
+ +foo(hi)
+SASS
+ assert_equal("blat {\n baz: 3; }\n", render(<<SASS))
+=foo($a, $b)
+ baz: $a + $b
+blat
+ +foo(1, 2)
+SASS
+ assert_equal("blat {\n baz: 4;\n baz: 3;\n baz: 5;\n bang: 3; }\n", render(<<SASS))
+=foo($c: (6 + 4) / 2)
+ baz: $c
+$c: 3
+blat
+ +foo($c + 1)
+ +foo(($c + 3)/2)
+ +foo
+ bang: $c
+SASS
+ end
+
+ def test_default_values_for_mixin_arguments
+ assert_equal(<<CSS, render(<<SASS))
+white {
+ color: #FFF; }
+
+black {
+ color: #000; }
+CSS
+=foo($a: #FFF)
+ :color $a
+white
+ +foo
+black
+ +foo(#000)
+SASS
+ assert_equal(<<CSS, render(<<SASS))
+one {
+ color: #fff;
+ padding: 1px;
+ margin: 4px; }
+
+two {
+ color: #fff;
+ padding: 2px;
+ margin: 5px; }
+
+three {
+ color: #fff;
+ padding: 2px;
+ margin: 3px; }
+CSS
+$a: 5px
+=foo($a, $b: 1px, $c: 3px + $b)
+ :color $a
+ :padding $b
+ :margin $c
+one
+ +foo(#fff)
+two
+ +foo(#fff, 2px)
+three
+ +foo(#fff, 2px, 3px)
+SASS
+ assert_equal(<<CSS, render(<<SASS))
+one {
+ color: #fff;
+ padding: 1px;
+ margin: 4px; }
+
+two {
+ color: #fff;
+ padding: 2px;
+ margin: 5px; }
+
+three {
+ color: #fff;
+ padding: 2px;
+ margin: 3px; }
+CSS
+$a: 5px
+=foo($a, $b: 1px, $c: null)
+ $c: 3px + $b !default
+ color: $a
+ padding: $b
+ margin: $c
+one
+ +foo(#fff)
+two
+ +foo(#fff, 2px)
+three
+ +foo(#fff, 2px, 3px)
+SASS
+ end
+
+ def test_hyphen_underscore_insensitive_mixins
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 12;
+ c: foo; }
+CSS
+=mixin-hyphen
+ b: 12
+
+=mixin_under
+ c: foo
+
+a
+ +mixin_hyphen
+ +mixin-under
+SASS
+ end
+
+ def test_css_identifier_mixin
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ foo: 12; }
+CSS
+=\\{foo\\(12\\)($a)
+ foo: $a
+
+a
+ +\\{foo\\(12\\)(12)
+SASS
+ end
+
+ def test_basic_function
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 3; }
+CSS
+ function foo()
+ @return 1 + 2
+
+bar
+ a: foo()
+SASS
+ end
+
+ def test_function_args
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 3; }
+CSS
+ function plus($var1, $var2)
+ @return $var1 + $var2
+
+bar
+ a: plus(1, 2)
+SASS
+ end
+
+ def test_function_arg_default
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 3; }
+CSS
+ function plus($var1, $var2: 2)
+ @return $var1 + $var2
+
+bar
+ a: plus(1)
+SASS
+ end
+
+ def test_function_arg_keyword
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 1bar; }
+CSS
+ function plus($var1: 1, $var2: 2)
+ @return $var1 + $var2
+
+bar
+ a: plus($var2: bar)
+SASS
+ end
+
+ def test_function_with_missing_argument
+ render(<<SASS)
+ function plus($var1, $var2)
+ @return $var1 + $var2
+
+bar
+ a: plus($var2: bar)
+SASS
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function plus is missing argument $var1.", e.message)
+ end
+
+ def test_function_with_extra_argument
+ render(<<SASS)
+ function plus($var1, $var2)
+ @return $var1 + $var2
+
+bar
+ a: plus($var1: foo, $var2: bar, $var3: baz)
+SASS
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function plus doesn't have an argument named $var3.", e.message)
+ end
+
+ def test_function_with_positional_and_keyword_argument
+ render(<<SASS)
+ function plus($var1, $var2)
+ @return $var1 + $var2
+
+bar
+ a: plus(foo, bar, $var2: baz)
+SASS
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function plus was passed argument $var2 both by position and by name.", e.message)
+ end
+
+ def test_function_with_keyword_before_positional_argument
+ render(<<SASS)
+ function plus($var1, $var2)
+ @return $var1 + $var2
+
+bar
+ a: plus($var2: foo, bar)
+SASS
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Positional arguments must come before keyword arguments.", e.message)
+ end
+
+ def test_function_with_if
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: foo;
+ b: bar; }
+CSS
+ function my-if($cond, $val1, $val2)
+ @if $cond
+ @return $val1
+ @else
+ @return $val2
+
+bar
+ a: my-if(true, foo, bar)
+ b: my-if(false, foo, bar)
+SASS
+ end
+
+ def test_function_with_var
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 1; }
+CSS
+ function foo($val1, $val2)
+ $intermediate: $val1 + $val2
+ @return $intermediate/3
+
+bar
+ a: foo(1, 2)
+SASS
+ end
+
+ def test_user_defined_function_variable_scope
+ render(<<SASS)
+bar
+ -no-op: set-a-variable(variable, 5)
+ a: $variable
+SASS
+ flunk("Exception not raised for test_user_defined_function_variable_scope")
+ rescue Sass::SyntaxError => e
+ assert_equal('Undefined variable: "$variable".', e.message)
+ end
+
+ def test_user_defined_function_can_change_global_variable
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 5; }
+CSS
+$variable: 0
+bar
+ $local: 10
+ -no-op: set-a-global-variable(variable, 5)
+ a: $variable
+SASS
+ end
+
+ def test_user_defined_function_cannot_read_local_variable
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ global: 0;
+ local: undefined; }
+CSS
+$global: 0
+bar
+ $local: 10
+ global: get-a-variable(global)
+ local: get-a-variable(local)
+SASS
+ end
+
+ def test_control_directive_in_nested_property
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ a-b: c; }
+CSS
+foo
+ a:
+ @if true
+ b: c
+SASS
+ end
+
+ def test_interpolation
+ assert_equal("a-1 {\n b-2-3: c-3; }\n", render(<<SASS))
+$a: 1
+$b: 2
+$c: 3
+a-\#{$a}
+ b-\#{$b}-\#{$c}: c-\#{$a + $b}
+SASS
+ end
+
+ def test_complex_property_interpolation
+ assert_equal(<<CSS, render(<<SASS))
+a-1 {
+ b-2 3-fizzap18: c-3; }
+CSS
+$a: 1
+$b: 2
+$c: 3
+a-\#{$a}
+ b-\#{$b $c}-\#{fizzap + ($c + 15)}: c-\#{$a + $b}
+SASS
+ end
+
+ def test_if_directive
+ assert_equal("a {\n b: 1; }\n", render(<<SASS))
+$var: true
+a
+ @if $var
+ b: 1
+ @if not $var
+ b: 2
+SASS
+
+ assert_equal("a {\n b: 2; }\n", render(<<SASS))
+$var: null
+a
+ @if $var
+ b: 1
+ @if not $var
+ b: 2
+SASS
+ end
+
+ def test_for
+ assert_equal(<<CSS, render(<<SASS))
+a-0 {
+ two-i: 0; }
+
+a-1 {
+ two-i: 2; }
+
+a-2 {
+ two-i: 4; }
+
+a-3 {
+ two-i: 6; }
+
+b-1 {
+ j-1: 0; }
+
+b-2 {
+ j-1: 1; }
+
+b-3 {
+ j-1: 2; }
+
+b-4 {
+ j-1: 3; }
+CSS
+$a: 3
+ for $i from 0 to $a + 1
+ a-\#{$i}
+ two-i: 2 * $i
+
+ for $j from 1 through 4
+ b-\#{$j}
+ j-1: $j - 1
+SASS
+ end
+
+ def test_while
+ assert_equal(<<CSS, render(<<SASS))
+a-5 {
+ blooble: gloop; }
+
+a-4 {
+ blooble: gloop; }
+
+a-3 {
+ blooble: gloop; }
+
+a-2 {
+ blooble: gloop; }
+
+a-1 {
+ blooble: gloop; }
+CSS
+$a: 5
+ while $a != 0
+ a-\#{$a}
+ blooble: gloop
+ $a: $a - 1 !global
+SASS
+ end
+
+ def test_else
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ t1: t;
+ t2: t;
+ t3: t;
+ t4: t; }
+CSS
+a
+ @if true
+ t1: t
+ @else
+ f1: f
+
+ @if false
+ f2: f
+ @else
+ t2: t
+
+ @if false
+ f3: f1
+ @else if 1 + 1 == 3
+ f3: f2
+ @else
+ t3: t
+
+ @if false
+ f4: f1
+ @else if 1 + 1 == 2
+ t4: t
+ @else
+ f4: f2
+
+ @if false
+ f5: f1
+ @else if false
+ f5: f2
+SASS
+ end
+
+ def test_each
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 1px;
+ b: 2px;
+ b: 3px;
+ b: 4px;
+ c: foo;
+ c: bar;
+ c: baz;
+ c: bang;
+ d: blue; }
+CSS
+a
+ @each $number in 1px 2px 3px 4px
+ b: $number
+ @each $str in foo, bar, baz, bang
+ c: $str
+ @each $single in blue
+ d: $single
+SASS
+ end
+
+ def test_destructuring_each
+ assert_equal <<CSS, render(<<SCSS)
+a {
+ foo: 1px;
+ bar: 2px;
+ baz: 3px; }
+
+c {
+ foo: "Value is bar";
+ bar: "Value is baz";
+ bang: "Value is "; }
+CSS
+a
+ @each $name, $number in (foo: 1px, bar: 2px, baz: 3px)
+ \#{$name}: $number
+c
+ @each $key, $value in (foo bar) (bar, baz) bang
+ \#{$key}: "Value is \#{$value}"
+SCSS
+ end
+
+ def test_variable_reassignment
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 1;
+ c: 2; }
+CSS
+a
+ $a: 1
+ b: $a
+ $a: 2
+ c: $a
+SASS
+ end
+
+ def test_hyphen_underscore_insensitive_variables
+ assert_equal(<<CSS, render(<<SASS))
+d {
+ e: 13;
+ f: foobar; }
+CSS
+$var-hyphen: 12
+$var_under: foo
+
+$var_hyphen: 1 + $var_hyphen
+$var-under: $var-under + bar
+
+d
+ e: $var-hyphen
+ f: $var_under
+SASS
+ end
+
+ def test_css_identifier_variable
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 12; }
+CSS
+$\\{foo\\(12\\): 12
+
+a
+ b: $\\{foo\\(12\\)
+SASS
+ end
+
+ def test_important
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 12px !important; }
+CSS
+$foo: 12px
+a
+ b: $foo !important
+SASS
+ end
+
+ def test_argument_error
+ assert_raises(Sass::SyntaxError) { render("a\n b: hsl(1)") }
+ end
+
+ def test_comments_at_the_top_of_a_document
+ render(<<SASS)
+//
+ This is a comment that
+ continues to the second line.
+foo
+ bar: baz
+SASS
+ end
+
+ def test_loud_comments_containing_a_comment_close
+ actual_css = render(<<SASS)
+/*
+ This is a comment that
+ continues to the second line. */
+foo
+ bar: baz
+SASS
+assert_equal(<<CSS, actual_css)
+/* This is a comment that
+ * continues to the second line. */
+foo {
+ bar: baz; }
+CSS
+ end
+
+ def test_loud_comments_with_starred_lines
+ assert_equal(<<CSS, render(<<SASS))
+/* This is a comment that
+ * continues to the second line.
+ * And even to the third! */
+CSS
+/* This is a comment that
+ * continues to the second line.
+ * And even to the third!
+SASS
+ end
+
+ def test_loud_comments_with_no_space_after_starred_lines
+ assert_equal(<<CSS, render(<<SASS))
+/*bip bop
+ *beep boop
+ *bap blimp */
+CSS
+/*bip bop
+ *beep boop
+ *bap blimp
+SASS
+ end
+
+ def test_comment_indentation_at_beginning_of_doc
+ assert_equal <<CSS, render(<<SASS)
+/* foo
+ * bar
+ * baz */
+foo {
+ a: b; }
+CSS
+/* foo
+ bar
+ baz
+foo
+ a: b
+SASS
+ end
+
+ def test_unusual_comment_indentation
+ assert_equal <<CSS, render(<<SASS)
+foo {
+ /* foo
+ * bar
+ * baz */ }
+CSS
+foo
+ /* foo
+ bar
+ baz
+SASS
+ end
+
+ def test_loud_comment_with_close
+ assert_equal <<CSS, render(<<SASS)
+foo {
+ /* foo
+ * bar */ }
+CSS
+foo
+ /* foo
+ bar */
+SASS
+ end
+
+ def test_loud_comment_with_separate_line_close
+ assert_equal <<CSS, render(<<SASS)
+foo {
+ /* foo
+ * bar
+ */ }
+CSS
+foo
+ /* foo
+ * bar
+ */
+SASS
+ end
+
+ def test_loud_comment_in_compressed_mode
+ assert_equal <<CSS, render(<<SASS, :style => :compressed)
+foo{color:blue;/*! foo
+ * bar
+ */}
+CSS
+foo
+ color: blue
+ /*! foo
+ * bar
+ */
+SASS
+ end
+
+ def test_loud_comment_is_evaluated
+ assert_equal <<CSS, render(<<SASS)
+/*! Hue: 327.21649deg */
+CSS
+/*! Hue: \#{hue(#f836a0)}
+SASS
+ end
+
+ def test_attribute_selector_with_spaces
+ assert_equal(<<CSS, render(<<SASS))
+a b[foo=bar] {
+ c: d; }
+CSS
+a
+ b[foo = bar]
+ c: d
+SASS
+ end
+
+ def test_quoted_colon
+ assert_equal(<<CSS, render(<<SASS))
+a b[foo="bar: baz"] {
+ c: d; }
+CSS
+a
+ b[foo="bar: baz"]
+ c: d
+SASS
+ end
+
+ def test_quoted_comma
+ assert_equal(<<CSS, render(<<SASS))
+a b[foo="bar, baz"] {
+ c: d; }
+CSS
+a
+ b[foo="bar, baz"]
+ c: d
+SASS
+ end
+
+ def test_quoted_ampersand
+ assert_equal(<<CSS, render(<<SASS))
+a b[foo="bar & baz"] {
+ c: d; }
+CSS
+a
+ b[foo="bar & baz"]
+ c: d
+SASS
+ end
+
+ def test_empty_selector_warning
+ assert_warning(<<END) {render("foo bar")}
+WARNING on line 1 of test_empty_selector_warning_inline.sass:
+This selector doesn't have any properties and will not be rendered.
+END
+ end
+
+ def test_nonprinting_empty_property
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ c: "";
+ e: f; }
+CSS
+$null-value: null
+$empty-string: ''
+$empty-list: (null)
+a
+ b: $null-value
+ c: $empty-string
+ d: $empty-list
+ e: f
+
+g
+ h: null
+SASS
+ end
+
+ def test_root_level_pseudo_class_with_new_properties
+ assert_equal(<<CSS, render(<<SASS, :property_syntax => :new))
+:focus {
+ outline: 0; }
+CSS
+:focus
+ outline: 0
+SASS
+ end
+
+ def test_pseudo_class_with_new_properties
+ assert_equal(<<CSS, render(<<SASS, :property_syntax => :new))
+p :focus {
+ outline: 0; }
+CSS
+p
+ :focus
+ outline: 0
+SASS
+ end
+
+ def test_nil_option
+ assert_equal(<<CSS, render(<<SASS, :format => nil))
+foo {
+ a: b; }
+CSS
+foo
+ a: b
+SASS
+ end
+
+ def test_interpolation_in_raw_functions
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ filter: progid:Microsoft.foo.bar.Baz(flip=foobar, bang=#00ff00cc); }
+CSS
+foo
+ filter: progid:Microsoft.foo.bar.Baz(flip=\#{foo + bar}, bang=#00ff00cc)
+SASS
+ end
+
+ # SassScript string behavior
+
+ def test_plus_preserves_quotedness
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ a: "foo1";
+ b: "1foo";
+ c: foo1;
+ d: 1foo;
+ e: "foobar";
+ f: foobar; }
+CSS
+foo
+ a: "foo" + 1
+ b: 1 + "foo"
+ c: foo + 1
+ d: 1 + foo
+ e: "foo" + bar
+ f: foo + "bar"
+SASS
+ end
+
+ def test_colon_properties_preserve_quotedness
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ a: "foo";
+ b: bar;
+ c: "foo" bar;
+ d: foo, "bar"; }
+CSS
+foo
+ a: "foo"
+ b: bar
+ c: "foo" bar
+ d: foo, "bar"
+SASS
+ end
+
+ def test_colon_variables_preserve_quotedness
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ a: "foo";
+ b: bar; }
+CSS
+$a: "foo"
+$b: bar
+
+foo
+ a: $a
+ b: $b
+SASS
+ end
+
+ def test_colon_args_preserve_quotedness
+ assert_equal(<<CSS, render(<<SASS))
+foo {
+ a: "foo";
+ b: bar;
+ c: "foo" bar;
+ d: foo, "bar"; }
+CSS
+=foo($a: "foo", $b: bar, $c: "foo" bar, $d: (foo, "bar"))
+ foo
+ a: $a
+ b: $b
+ c: $c
+ d: $d
+
++foo
+SASS
+ end
+
+ def test_interpolation_unquotes_strings
+ assert_equal(<<CSS, render(<<SASS))
+.foo-bar {
+ a: b; }
+CSS
+.foo-\#{"bar"}
+ a: b
+SASS
+
+ assert_equal(<<CSS, render(<<SASS))
+.foo {
+ a: b c; }
+CSS
+.foo
+ a: b \#{"c"}
+SASS
+ end
+
+ def test_interpolation_unquotes_strings_in_vars
+ assert_equal(<<CSS, render(<<SASS))
+.foo-bar {
+ a: b; }
+CSS
+$var: "bar"
+
+.foo-\#{$var}
+ a: b
+SASS
+ end
+
+ def test_interpolation_deep_unquotes_strings
+ assert_equal(<<CSS, render(<<SASS))
+.foo {
+ a: bar baz; }
+CSS
+.foo
+ a: \#{"bar" "baz"}
+SASS
+ end
+
+ def test_warn_directive
+ expected_warning = <<EXPECTATION
+WARNING: this is a warning
+ on line 4 of test_warn_directive_inline.sass
+
+WARNING: this is a mixin warning
+ on line 2 of test_warn_directive_inline.sass, in `foo'
+ from line 7 of test_warn_directive_inline.sass
+EXPECTATION
+ assert_warning expected_warning do
+ assert_equal <<CSS, render(<<SASS)
+bar {
+ c: d; }
+CSS
+=foo
+ @warn "this is a mixin warning"
+
+ warn "this is a warning"
+bar
+ c: d
+ +foo
+SASS
+ end
+ end
+
+ def test_warn_directive_when_quiet
+ assert_warning "" do
+ assert_equal <<CSS, render(<<SASS, :quiet => true)
+CSS
+ warn "this is a warning"
+SASS
+ end
+ end
+
+ def test_warn_with_imports
+ expected_warning = <<WARN
+WARNING: In the main file
+ on line 1 of #{File.dirname(__FILE__)}/templates/warn.sass
+
+WARNING: Imported
+ on line 1 of #{File.dirname(__FILE__)}/templates/warn_imported.sass
+ from line 2 of #{File.dirname(__FILE__)}/templates/warn.sass
+
+WARNING: In an imported mixin
+ on line 4 of #{File.dirname(__FILE__)}/templates/warn_imported.sass, in `emits-a-warning'
+ from line 3 of #{File.dirname(__FILE__)}/templates/warn.sass
+WARN
+ assert_warning expected_warning do
+ renders_correctly "warn", :style => :compact, :load_paths => [File.dirname(__FILE__) + "/templates"]
+ end
+ end
+
+ def test_media_bubbling
+ assert_equal <<CSS, render(<<SASS)
+.foo {
+ a: b; }
+ @media bar {
+ .foo {
+ c: d; } }
+ .foo .baz {
+ e: f; }
+ @media bip {
+ .foo .baz {
+ g: h; } }
+
+.other {
+ i: j; }
+CSS
+.foo
+ a: b
+ @media bar
+ c: d
+ .baz
+ e: f
+ @media bip
+ g: h
+
+.other
+ i: j
+SASS
+
+ assert_equal <<CSS, render(<<SASS, :style => :compact)
+.foo { a: b; }
+ media bar { .foo { c: d; } }
+.foo .baz { e: f; }
+ media bip { .foo .baz { g: h; } }
+
+.other { i: j; }
+CSS
+.foo
+ a: b
+ @media bar
+ c: d
+ .baz
+ e: f
+ @media bip
+ g: h
+
+.other
+ i: j
+SASS
+
+ assert_equal <<CSS, render(<<SASS, :style => :expanded)
+.foo {
+ a: b;
+}
+ media bar {
+ .foo {
+ c: d;
+ }
+}
+.foo .baz {
+ e: f;
+}
+ media bip {
+ .foo .baz {
+ g: h;
+ }
+}
+
+.other {
+ i: j;
+}
+CSS
+.foo
+ a: b
+ @media bar
+ c: d
+ .baz
+ e: f
+ @media bip
+ g: h
+
+.other
+ i: j
+SASS
+ end
+
+ def test_double_media_bubbling
+ assert_equal <<CSS, render(<<SASS)
+ media bar and (a: b) {
+ .foo {
+ c: d; } }
+CSS
+ media bar
+ @media (a: b)
+ .foo
+ c: d
+SASS
+
+ assert_equal <<CSS, render(<<SASS)
+ media bar {
+ .foo {
+ a: b; } }
+ @media bar and (a: b) {
+ .foo {
+ c: d; } }
+CSS
+.foo
+ @media bar
+ a: b
+ @media (a: b)
+ c: d
+SASS
+ end
+
+ def test_double_media_bubbling_with_commas
+ assert_equal <<CSS, render(<<SASS)
+ media (a: b) and (e: f), (c: d) and (e: f), (a: b) and (g: h), (c: d) and (g: h) {
+ .foo {
+ c: d; } }
+CSS
+ media (a: b), (c: d)
+ @media (e: f), (g: h)
+ .foo
+ c: d
+SASS
+ end
+
+ def test_double_media_bubbling_with_surrounding_rules
+ assert_equal <<CSS, render(<<SASS)
+ media (min-width: 0) {
+ a {
+ a: a; }
+
+ b {
+ before: b;
+ after: b; } }
+ @media (min-width: 0) and (max-width: 5000px) {
+ b {
+ x: x; } }
+
+ media (min-width: 0) {
+ c {
+ c: c; } }
+CSS
+ media (min-width: 0)
+ a
+ a: a
+ b
+ before: b
+ @media (max-width: 5000px)
+ x: x
+ after: b
+ c
+ c: c
+SASS
+ end
+
+ def test_rule_media_rule_bubbling
+ assert_equal <<CSS, render(<<SASS)
+ media bar {
+ .foo {
+ a: b;
+ e: f; }
+ .foo .baz {
+ c: d; } }
+CSS
+.foo
+ @media bar
+ a: b
+ .baz
+ c: d
+ e: f
+SASS
+ end
+
+ def test_nested_media_around_properties
+ assert_equal <<CSS, render(<<SASS)
+.outside {
+ color: red;
+ background: blue; }
+ @media print {
+ .outside {
+ color: black; } }
+ @media print and (a: b) {
+ .outside .inside {
+ border: 1px solid black; } }
+
+ .outside .middle {
+ display: block; }
+CSS
+.outside
+ color: red
+ @media print
+ color: black
+ .inside
+ @media (a: b)
+ border: 1px solid black
+ background: blue
+ .middle
+ display: block
+SASS
+ end
+
+ def test_media_with_parent_references
+ sass_str = <<SASS
+.outside
+ @media print
+ &.inside
+ border: 1px solid black
+SASS
+ css_str = <<CSS
+ media print {
+ .outside.inside {
+ border: 1px solid black; } }
+CSS
+ assert_equal css_str, render(sass_str)
+ end
+
+ def test_eliminated_media_bubbling
+ assert_equal <<CSS, render(<<SASS)
+ media screen {
+ a: b; }
+CSS
+ media screen
+ a: b
+ @media print
+ c: d
+SASS
+
+ assert_equal <<CSS, render(<<SASS)
+ media not print {
+ a: b; }
+CSS
+ media not print
+ a: b
+ @media print
+ c: d
+SASS
+
+ assert_equal <<CSS, render(<<SASS)
+ media not print {
+ a: b; }
+CSS
+ media not print
+ a: b
+ @media not screen
+ c: d
+SASS
+ end
+
+ def test_non_eliminated_media_bubbling
+ assert_equal <<CSS, render(<<SASS)
+ media screen {
+ a: b; }
+ media screen and (a: b) {
+ c: d; }
+CSS
+ media screen
+ a: b
+ @media screen and (a: b)
+ c: d
+SASS
+
+ assert_equal <<CSS, render(<<SASS)
+ media not print {
+ a: b; }
+ media screen {
+ c: d; }
+CSS
+ media not print
+ a: b
+ @media screen
+ c: d
+SASS
+
+ assert_equal <<CSS, render(<<SASS)
+ media only screen {
+ a: b; }
+ media only screen and (a: b) {
+ c: d; }
+CSS
+ media only screen
+ a: b
+ @media screen and (a: b)
+ c: d
+SASS
+ end
+
+ def test_directive_interpolation
+ assert_equal <<CSS, render(<<SASS)
+ foo bar12 qux {
+ a: b; }
+CSS
+$baz: 12
+ foo bar\#{$baz} qux
+ a: b
+SASS
+ end
+
+ def test_media_interpolation
+ assert_equal <<CSS, render(<<SASS)
+ media bar12 {
+ a: b; }
+CSS
+$baz: 12
+ media bar\#{$baz}
+ a: b
+SASS
+ end
+
+ def test_variables_in_media
+ assert_equal <<CSS, render(<<SASS)
+ media screen and (-webkit-min-device-pixel-ratio-foo: 25), only print {
+ a: b; }
+CSS
+$media1: screen
+$media2: print
+$var: -webkit-min-device-pixel-ratio
+$val: 20
+ media \#{$media1} and ($var + "-foo": $val + 5), only \#{$media2}
+ a: b
+SASS
+ end
+
+ def test_at_root
+ assert_equal <<CSS, render(<<SASS)
+.bar {
+ a: b; }
+CSS
+.foo
+ @at-root
+ .bar
+ a: b
+SASS
+ end
+
+ def test_at_root_with_selector
+ assert_equal <<CSS, render(<<SASS)
+.bar {
+ a: b; }
+CSS
+.foo
+ @at-root .bar
+ a: b
+SASS
+ end
+
+ def test_at_root_with_query
+ assert_equal <<CSS, render(<<SASS)
+.foo .bar {
+ a: b; }
+CSS
+.foo
+ @media screen
+ @at-root (without: media)
+ .bar
+ a: b
+SASS
+ end
+
+ def test_variable_assignment_with_global
+ assert_no_warning {assert_equal(<<CSS, render(<<SASS))}
+.foo {
+ a: x; }
+
+.bar {
+ b: x; }
+CSS
+$var: 1
+
+.foo
+ $var: x !global
+ a: $var
+
+.bar
+ b: $var
+SASS
+ end
+
+ # Regression tests
+
+ def test_list_separator_with_arg_list
+ assert_equal(<<CSS, render(<<SASS))
+.test {
+ separator: comma; }
+CSS
+ mixin arglist-test($args...)
+ separator: list-separator($args)
+
+.test
+ @include arglist-test(this, is, comma, separated)
+SASS
+ end
+
+ def test_parent_mixin_in_content_nested
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: c; }
+CSS
+=foo
+ @content
+
+=bar
+ +foo
+ +foo
+ a
+ b: c
+
++bar
+SASS
+ end
+
+ def test_supports_bubbles
+ assert_equal <<CSS, render(<<SASS)
+parent {
+ background: orange; }
+ @supports (perspective: 10px) or (-moz-perspective: 10px) {
+ parent child {
+ background: blue; } }
+CSS
+parent
+ background: orange
+ @supports (perspective: 10px) or (-moz-perspective: 10px)
+ child
+ background: blue
+SASS
+ end
+
+ def test_line_numbers_with_dos_line_endings
+ assert_equal <<CSS, render(<<SASS, :line_comments => true)
+/* line 5, test_line_numbers_with_dos_line_endings_inline.sass */
+.foo {
+ a: b; }
+CSS
+\r
+\r
+\r
+\r
+.foo
+ a: b
+SASS
+ end
+
+ def test_variable_in_media_in_mixin
+ assert_equal <<CSS, render(<<SASS)
+ media screen and (min-width: 10px) {
+ body {
+ background: red; } }
+ media screen and (min-width: 20px) {
+ body {
+ background: blue; } }
+CSS
+ mixin respond-to($width)
+ @media screen and (min-width: $width)
+ @content
+
+body
+ @include respond-to(10px)
+ background: red
+ @include respond-to(20px)
+ background: blue
+SASS
+ end
+
+ def test_interpolated_comment_in_mixin
+ assert_equal <<CSS, render(<<SASS)
+/*! color: red */
+.foo {
+ color: red; }
+
+/*! color: blue */
+.foo {
+ color: blue; }
+
+/*! color: green */
+.foo {
+ color: green; }
+CSS
+=foo($var)
+ /*! color: \#{$var}
+ .foo
+ color: $var
+
++foo(red)
++foo(blue)
++foo(green)
+SASS
+ end
+
+ def test_parens_in_mixins
+ assert_equal(<<CSS, render(<<SASS))
+.foo {
+ color: #01ff7f;
+ background-color: #000102; }
+CSS
+=foo($c1, $c2: rgb(0, 1, 2))
+ color: $c1
+ background-color: $c2
+
+.foo
+ +foo(rgb(1,255,127))
+SASS
+ end
+
+ def test_comment_beneath_prop
+ assert_equal(<<RESULT, render(<<SOURCE))
+.box {
+ border-style: solid; }
+RESULT
+.box
+ :border
+ //:color black
+ :style solid
+SOURCE
+
+ assert_equal(<<RESULT, render(<<SOURCE))
+.box {
+ /* :color black */
+ border-style: solid; }
+RESULT
+.box
+ :border
+ /* :color black
+ :style solid
+SOURCE
+
+ assert_equal(<<RESULT, render(<<SOURCE, :style => :compressed))
+.box{border-style:solid}
+RESULT
+.box
+ :border
+ /*:color black
+ :style solid
+SOURCE
+ end
+
+ def test_compressed_comment_beneath_directive
+ assert_equal(<<RESULT, render(<<SOURCE, :style => :compressed))
+ foo{a:b}
+RESULT
+ foo
+ a: b
+ /*b: c
+SOURCE
+ end
+
+ def test_comment_with_crazy_indentation
+ assert_equal(<<CSS, render(<<SASS))
+/* This is a loud comment:
+ * Where the indentation is wonky. */
+.comment {
+ width: 1px; }
+CSS
+/*
+ This is a loud comment:
+ Where the indentation is wonky.
+//
+ This is a silent comment:
+ Where the indentation is wonky.
+.comment
+ width: 1px
+SASS
+ end
+
+ def test_plus_with_space
+ assert_equal(<<CSS, render(<<SASS))
+a + b {
+ color: green; }
+CSS
+a
+ + b
+ color: green
+SASS
+ end
+
+ def test_empty_line_comment
+ assert_equal(<<CSS, render(<<SASS))
+/* Foo
+ *
+ * Bar */
+CSS
+/*
+ Foo
+
+ Bar
+SASS
+ end
+
+ def test_empty_comment
+ assert_equal(<<CSS, render(<<SASS))
+/* */
+a {
+ /* */
+ b: c; }
+CSS
+/*
+a
+ /*
+ b: c
+SASS
+ end
+
+ def test_options_available_in_environment
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: nested; }
+CSS
+a
+ b: option("style")
+SASS
+ end
+
+ def test_mixin_no_arg_error
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "($bar,": expected variable (e.g. $foo), was
")"') do
+ render(<<SASS)
+=foo($bar,)
+ bip: bap
+SASS
+ end
+ end
+
+ def test_import_with_commas_in_url
+ assert_equal <<CSS, render(<<SASS)
+ import url(foo.css?bar,baz);
+CSS
+ import url(foo.css?bar,baz)
+SASS
+ end
+
+ def test_silent_comment_in_prop_val_after_important
+ assert_equal(<<CSS, render(<<SASS))
+.advanced {
+ display: none !important; }
+CSS
+.advanced
+ display: none !important // yeah, yeah. it's not really a style anyway.
+SASS
+ end
+
+ def test_mixin_with_keyword_args
+ assert_equal <<CSS, render(<<SASS)
+.mixed {
+ required: foo;
+ arg1: default-val1;
+ arg2: non-default-val2; }
+CSS
+=a-mixin($required, $arg1: default-val1, $arg2: default-val2)
+ required: $required
+ arg1: $arg1
+ arg2: $arg2
+.mixed
+ +a-mixin(foo, $arg2: non-default-val2)
+SASS
+ end
+
+ def test_mixin_with_keyword_arg_variable_value
+ assert_equal <<CSS, render(<<SASS)
+.mixed {
+ required: foo;
+ arg1: default-val1;
+ arg2: a-value; }
+CSS
+=a-mixin($required, $arg1: default-val1, $arg2: default-val2)
+ required: $required
+ arg1: $arg1
+ arg2: $arg2
+.mixed
+ $a-value: a-value
+ +a-mixin(foo, $arg2: $a-value)
+SASS
+ end
+
+ def test_mixin_keyword_args_handle_variable_underscore_dash_equivalence
+ assert_equal <<CSS, render(<<SASS)
+.mixed {
+ required: foo;
+ arg1: non-default-val1;
+ arg2: non-default-val2; }
+CSS
+=a-mixin($required, $arg-1: default-val1, $arg_2: default-val2)
+ required: $required
+ arg1: $arg_1
+ arg2: $arg-2
+.mixed
+ +a-mixin(foo, $arg-2: non-default-val2, $arg_1: non-default-val1)
+SASS
+ end
+
+ def test_passing_required_args_as_a_keyword_arg
+ assert_equal <<CSS, render(<<SASS)
+.mixed {
+ required: foo;
+ arg1: default-val1;
+ arg2: default-val2; }
+CSS
+=a-mixin($required, $arg1: default-val1, $arg2: default-val2)
+ required: $required
+ arg1: $arg1
+ arg2: $arg2
+.mixed
+ +a-mixin($required: foo)
+SASS
+ end
+
+ def test_passing_all_as_keyword_args_in_opposite_order
+ assert_equal <<CSS, render(<<SASS)
+.mixed {
+ required: foo;
+ arg1: non-default-val1;
+ arg2: non-default-val2; }
+CSS
+=a-mixin($required, $arg1: default-val1, $arg2: default-val2)
+ required: $required
+ arg1: $arg1
+ arg2: $arg2
+.mixed
+ +a-mixin($arg2: non-default-val2, $arg1: non-default-val1, $required: foo)
+SASS
+ end
+
+ def test_function_output_with_comma
+ assert_equal <<CSS, render(<<SASS)
+foo {
+ a: b(c), d(e); }
+CSS
+foo
+ a: b(c), d(e)
+SASS
+ end
+
+ def test_interpolation_with_comma
+ assert_equal <<CSS, render(<<SASS)
+foo {
+ a: foo, bar; }
+CSS
+$foo: foo
+foo
+ a: \#{$foo}, bar
+SASS
+ end
+
+ def test_string_interpolation_with_comma
+ assert_equal <<CSS, render(<<SASS)
+foo {
+ a: "bip foo bap", bar; }
+CSS
+$foo: foo
+foo
+ a: "bip \#{$foo} bap", bar
+SASS
+ end
+
+ def test_unknown_directive
+ assert_equal <<CSS, render(<<SASS)
+ baz {
+ c: d; }
+CSS
+ baz
+ c: d
+SASS
+ end
+
+ def test_loud_comment_interpolations_can_be_escaped
+ assert_equal <<CSS, render(<<SASS)
+/* \#{foo} */
+CSS
+/* \\\#{foo}
+SASS
+ assert_equal <<CSS, render(<<SASS)
+/*! \#{foo} */
+CSS
+/*! \\\#{foo}
+SASS
+ end
+
+ def test_selector_compression
+ assert_equal <<CSS, render(<<SASS, :style => :compressed)
+a>b,c+d,:-moz-any(e,f,g){h:i}
+CSS
+a > b, c + d, :-moz-any(e, f, g)
+ h: i
+SASS
+ end
+
+ def test_comment_like_selector
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "/": expected identifier, was " foo"')
{render(<<SASS)}
+/ foo
+ a: b
+SASS
+ end
+
+ def test_nested_empty_directive
+ assert_equal <<CSS, render(<<SASS)
+ media screen {
+ .foo {
+ a: b; }
+
+ @unknown-directive; }
+CSS
+ media screen
+ .foo
+ a: b
+
+ @unknown-directive
+SASS
+ end
+
+ def test_original_filename_set
+ importer = MockImporter.new
+ importer.add_import("imported", "div{color:red}")
+
+ original_filename = filename_for_test
+ engine = Sass::Engine.new('@import "imported"; div{color:blue}',
+ :filename => original_filename, :load_paths => [importer], :syntax => :scss, :importer => importer)
+ engine.render
+
+ assert_equal original_filename, engine.options[:original_filename]
+ assert_equal original_filename, importer.engine("imported").options[:original_filename]
+ end
+
+ def test_changing_precision
+ old_precision = Sass::Script::Value::Number.precision
+ begin
+ Sass::Script::Value::Number.precision = 8
+ assert_equal <<CSS, render(<<SASS)
+div {
+ maximum: 1.00000001;
+ too-much: 1.0; }
+CSS
+div
+ maximum : 1.00000001
+ too-much: 1.000000001
+SASS
+ ensure
+ Sass::Script::Value::Number.precision = old_precision
+ end
+ end
+
+ def test_content
+ assert_equal <<CSS, render(<<SASS)
+.children {
+ background-color: red;
+ color: blue;
+ border-color: red; }
+CSS
+$color: blue
+=context($class, $color: red)
+ .\#{$class}
+ background-color: $color
+ @content
+ border-color: $color
++context(children)
+ color: $color
+SASS
+ end
+
+ def test_selector_in_content
+ assert_equal <<CSS, render(<<SASS)
+.parent {
+ background-color: red;
+ border-color: red; }
+ .parent .children {
+ color: blue; }
+CSS
+$color: blue
+=context($class, $color: red)
+ .\#{$class}
+ background-color: $color
+ @content
+ border-color: $color
++context(parent)
+ .children
+ color: $color
+SASS
+ end
+
+ def test_using_parent_mixin_in_content
+ assert_equal <<CSS, render(<<SASS)
+.parent {
+ before-color: red;
+ after-color: red; }
+ .parent .sibling {
+ before-color: yellow;
+ after-color: yellow; }
+ .parent .sibling .child {
+ before-color: green;
+ color: blue;
+ after-color: green; }
+CSS
+$color: blue
+=context($class, $color: red)
+ .\#{$class}
+ before-color: $color
+ @content
+ after-color: $color
++context(parent)
+ +context(sibling, $color: yellow)
+ +context(child, $color: green)
+ color: $color
+SASS
+ end
+
+ def test_content_more_than_once
+ assert_equal <<CSS, render(<<SASS)
+.once {
+ color: blue; }
+
+.twice {
+ color: blue; }
+CSS
+$color: blue
+=context($class, $color: red)
+ .once
+ @content
+ .twice
+ @content
++context(parent)
+ color: $color
+SASS
+ end
+
+ def test_content_with_variable
+ assert_equal <<CSS, render(<<SASS)
+.foo {
+ a: 1px; }
+CSS
+=foo
+ .foo
+ @content
++foo
+ $a: 1px
+ a: $a
+SASS
+ end
+
+ def test_nested_content_blocks
+ assert_equal <<CSS, render(<<SASS)
+.foo {
+ a: foo; }
+ .foo .bar {
+ a: bar; }
+ .foo .bar .baz {
+ a: baz; }
+ .foo .bar .baz .outside {
+ a: outside;
+ color: red; }
+CSS
+$a: outside
+=baz($a: baz)
+ .baz
+ a: $a
+ @content
+=bar($a: bar)
+ .bar
+ a: $a
+ +baz
+ @content
+=foo($a: foo)
+ .foo
+ a: $a
+ +bar
+ @content
++foo
+ .outside
+ a: $a
+ color: red
+SASS
+ end
+
+ def test_content_not_seen_through_mixin
+ assert_equal <<CSS, render(<<SASS)
+a foo {
+ mixin: foo;
+ a: b; }
+ a foo bar {
+ mixin: bar; }
+CSS
+=foo
+ foo
+ mixin: foo
+ @content
+ +bar
+=bar
+ bar
+ mixin: bar
+ @content
+a
+ +foo
+ a: b
+SASS
+ end
+
+ def test_content_backtrace_for_perform
+ render(<<SASS)
+=foo
+ @content
+
+a
+ +foo
+ b: 1em + 2px
+SASS
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal([
+ {:mixin => '@content', :line => 6, :filename => 'test_content_backtrace_for_perform_inline.sass'},
+ {:mixin => 'foo', :line => 2, :filename => 'test_content_backtrace_for_perform_inline.sass'},
+ {:line => 5, :filename => 'test_content_backtrace_for_perform_inline.sass'},
+ ], e.sass_backtrace)
+ end
+
+ def test_content_backtrace_for_cssize
+ render(<<SASS)
+=foo
+ @content
+
+a
+ +foo
+ @extend foo bar baz
+SASS
+ assert(false, "Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal([
+ {:mixin => '@content', :line => 6, :filename => 'test_content_backtrace_for_cssize_inline.sass'},
+ {:mixin => 'foo', :line => 2, :filename => 'test_content_backtrace_for_cssize_inline.sass'},
+ {:line => 5, :filename => 'test_content_backtrace_for_cssize_inline.sass'},
+ ], e.sass_backtrace)
+ end
+
+ def test_mixin_with_args_and_varargs_passed_no_var_args
+ assert_equal <<CSS, render(<<SASS, :syntax => :scss)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3; }
+CSS
+ mixin three-or-more-args($a, $b, $c, $rest...) {
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+.foo {
+ @include three-or-more-args($a: 1, $b: 2, $c: 3);
+}
+SASS
+
+ end
+
+ def test_debug_inspects_sass_objects
+ assert_warning(<<END) {render("@debug (a: 1, b: 2)")}
+test_debug_inspects_sass_objects_inline.sass:1 DEBUG: (a: 1, b: 2)
+END
+ assert_warning(<<END) {render("$map: (a: 1, b: 2); @debug $map", :syntax => :scss)}
+test_debug_inspects_sass_objects_inline.scss:1 DEBUG: (a: 1, b: 2)
+END
+ end
+
+ def test_error_throws_sass_objects
+ assert_raise_message(Sass::SyntaxError, "(a: 1, b: 2)") {render("@error (a: 1, b: 2)")}
+ assert_raise_message(Sass::SyntaxError, "(a: 1, b: 2)") do
+ render("$map: (a: 1, b: 2); @error $map", :syntax => :scss)
+ end
+ end
+
+ def test_default_arg_before_splat
+ assert_equal <<CSS, render(<<SASS, :syntax => :scss)
+.foo-positional {
+ a: 1;
+ b: 2;
+ positional-arguments: 3, 4;
+ keyword-arguments: (); }
+
+.foo-keywords {
+ a: true;
+ positional-arguments: ();
+ keyword-arguments: (c: c, d: d); }
+CSS
+ mixin foo($a: true, $b: null, $arguments...) {
+ a: $a;
+ b: $b;
+ positional-arguments: inspect($arguments);
+ keyword-arguments: inspect(keywords($arguments));
+}
+.foo-positional {
+ @include foo(1, 2, 3, 4);
+}
+.foo-keywords {
+ @include foo($c: c, $d: d);
+}
+SASS
+ end
+
+ def test_keyframes
+ assert_equal <<CSS, render(<<SASS)
+ keyframes identifier {
+ 0% {
+ top: 0;
+ left: 0; }
+ 30% {
+ top: 50px; }
+ 68%, 72% {
+ left: 50px; }
+ 100% {
+ top: 100px;
+ left: 100%; } }
+CSS
+ keyframes identifier
+ 0%
+ top: 0
+ left: 0
+ \#{"30%"}
+ top: 50px
+ 68%, 72%
+ left: 50px
+ 100%
+ top: 100px
+ left: 100%
+SASS
+ end
+
+ def test_prefixed_keyframes
+ assert_equal <<CSS, render(<<SASS)
+ -moz-keyframes identifier {
+ 0% {
+ top: 0;
+ left: 0; }
+ 30% {
+ top: 50px; }
+ 68%, 72% {
+ left: 50px; }
+ 100% {
+ top: 100px;
+ left: 100%; } }
+CSS
+ -moz-keyframes identifier
+ 0%
+ top: 0
+ left: 0
+ \#{"30%"}
+ top: 50px
+ 68%, 72%
+ left: 50px
+ 100%
+ top: 100px
+ left: 100%
+SASS
+ end
+
+ def test_uppercase_keyframes
+ assert_equal <<CSS, render(<<SASS)
+ KEYFRAMES identifier {
+ 0% {
+ top: 0;
+ left: 0; }
+ 30% {
+ top: 50px; }
+ 68%, 72% {
+ left: 50px; }
+ 100% {
+ top: 100px;
+ left: 100%; } }
+CSS
+ KEYFRAMES identifier
+ 0%
+ top: 0
+ left: 0
+ \#{"30%"}
+ top: 50px
+ 68%, 72%
+ left: 50px
+ 100%
+ top: 100px
+ left: 100%
+SASS
+ end
+
+ private
+
+ def assert_hash_has(hash, expected)
+ expected.each {|k, v| assert_equal(v, hash[k])}
+ end
+
+ def assert_renders_encoded(css, sass)
+ result = render(sass)
+ assert_equal css.encoding, result.encoding
+ assert_equal css, result
+ end
+
+ def render(sass, options = {})
+ munge_filename options
+ options[:importer] ||= MockImporter.new
+ Sass::Engine.new(sass, options).render
+ end
+
+ def renders_correctly(name, options={})
+ sass_file = load_file(name, "sass")
+ css_file = load_file(name, "css")
+ options[:filename] ||= filename(name, "sass")
+ options[:syntax] ||= :sass
+ options[:css_filename] ||= filename(name, "css")
+ css_result = Sass::Engine.new(sass_file, options).render
+ assert_equal css_file, css_result
+ end
+
+ def load_file(name, type = "sass")
+ @result = ''
+ File.new(filename(name, type)).each_line { |l| @result += l }
+ @result
+ end
+
+ def filename(name, type)
+ File.dirname(__FILE__) + "/#{type == 'sass' ? 'templates' : 'results'}/#{name}.#{type}"
+ end
+
+ def sassc_path(template)
+ sassc_path = File.join(File.dirname(__FILE__) + "/templates/#{template}.sass")
+ engine = Sass::Engine.new("", :filename => sassc_path,
+ :importer => Sass::Importers::Filesystem.new("."))
+ key = engine.send(:sassc_key)
+ File.join(engine.options[:cache_location], key)
+ end
+end
+
diff --git a/backends/css/gems/sass-3.4.9/test/sass/exec_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/exec_test.rb
new file mode 100755
index 0000000..c606b6a
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/exec_test.rb
@@ -0,0 +1,86 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/util/test'
+require 'tmpdir'
+
+class ExecTest < MiniTest::Test
+ include Sass::Util::Test
+
+ def setup
+ @dir = Dir.mktmpdir
+ end
+
+ def teardown
+ FileUtils.rm_rf(@dir)
+ clean_up_sassc
+ end
+
+ def test_scss_t_expanded
+ src = get_path("src.scss")
+ dest = get_path("dest.css")
+ write(src, ".ruleset { margin: 0 }")
+ assert(exec(*%w[scss --sourcemap=none -t expanded --unix-newlines].push(src, dest)))
+ assert_equal(".ruleset {\n margin: 0;\n}\n", read(dest))
+ end
+
+ def test_sass_convert_T_sass
+ src = get_path("src.scss")
+ dest = get_path("dest.css")
+ write(src, ".ruleset { margin: 0 }")
+ assert(exec(*%w[sass-convert -T sass --unix-newlines].push(src, dest)))
+ assert_equal(".ruleset\n margin: 0\n", read(dest))
+ end
+
+ def test_sass_convert_T_sass_in_place
+ src = get_path("src.scss")
+ write(src, ".ruleset { margin: 0 }")
+ assert(exec(*%w[sass-convert -T sass --in-place --unix-newlines].push(src)))
+ assert_equal(".ruleset\n margin: 0\n", read(src))
+ end
+
+ def test_scss_t_expanded_no_unix_newlines
+ return skip "Can be run on Windows only" unless Sass::Util.windows?
+ src = get_path("src.scss")
+ dest = get_path("dest.css")
+ write(src, ".ruleset { margin: 0 }")
+ assert(exec(*%w[scss -t expanded].push(src, dest)))
+ assert_equal(".ruleset {\r\n margin: 0;\r\n}\r\n", read(dest))
+ end
+
+ def test_sass_convert_T_sass_no_unix_newlines
+ return skip "Can be run on Windows only" unless Sass::Util.windows?
+ src = get_path("src.scss")
+ dest = get_path("dest.sass")
+ write(src, ".ruleset { margin: 0 }")
+ assert(exec(*%w[sass-convert -T sass].push(src, dest)))
+ assert_equal(".ruleset\r\n margin: 0\r\n", read(dest))
+ end
+
+ def test_sass_convert_T_sass_in_place_no_unix_newlines
+ return skip "Can be run on Windows only" unless Sass::Util.windows?
+ src = get_path("src.scss")
+ write(src, ".ruleset { margin: 0 }")
+ assert(exec(*%w[sass-convert -T sass --in-place].push(src)))
+ assert_equal(".ruleset\r\n margin: 0\r\n", read(src))
+ end
+
+ private
+
+ def get_path(name)
+ File.join(@dir, name)
+ end
+
+ def read(file)
+ open(file, 'rb') {|f| f.read}
+ end
+
+ def write(file, content)
+ open(file, 'wb') {|f| f.write(content)}
+ end
+
+ def exec(script, *args)
+ script = File.dirname(__FILE__) + '/../../bin/' + script
+ ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'] +
RbConfig::CONFIG['EXEEXT'])
+ system(ruby, script, *args)
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/extend_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/extend_test.rb
new file mode 100755
index 0000000..48a73d2
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/extend_test.rb
@@ -0,0 +1,1687 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+
+class ExtendTest < MiniTest::Test
+ def test_basic
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .bar {
+ a: b; }
+CSS
+.foo {a: b}
+.bar { extend .foo}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .bar {
+ a: b; }
+CSS
+.bar { extend .foo}
+.foo {a: b}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .bar {
+ a: b; }
+
+.bar {
+ c: d; }
+CSS
+.foo {a: b}
+.bar {c: d; @extend .foo}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .bar {
+ a: b; }
+
+.bar {
+ c: d; }
+CSS
+.foo {a: b}
+.bar { extend .foo; c: d}
+SCSS
+ end
+
+ def test_indented_syntax
+ assert_equal <<CSS, render(<<SASS, :syntax => :sass)
+.foo, .bar {
+ a: b; }
+CSS
+.foo
+ a: b
+.bar
+ @extend .foo
+SASS
+
+ assert_equal <<CSS, render(<<SASS, :syntax => :sass)
+.foo, .bar {
+ a: b; }
+CSS
+.foo
+ a: b
+.bar
+ @extend \#{".foo"}
+SASS
+ end
+
+ def test_multiple_targets
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .bar {
+ a: b; }
+
+.blip .foo, .blip .bar {
+ c: d; }
+CSS
+.foo {a: b}
+.bar { extend .foo}
+.blip .foo {c: d}
+SCSS
+ end
+
+ def test_multiple_extendees
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .baz {
+ a: b; }
+
+.bar, .baz {
+ c: d; }
+CSS
+.foo {a: b}
+.bar {c: d}
+.baz { extend .foo; @extend .bar}
+SCSS
+ end
+
+ def test_multiple_extends_with_single_extender_and_single_target
+ assert_extends('.foo .bar', '.baz { extend .foo; @extend .bar}',
+ '.foo .bar, .baz .bar, .foo .baz, .baz .baz')
+ assert_extends '.foo.bar', '.baz { extend .foo; @extend .bar}', '.foo.bar, .baz'
+ end
+
+ def test_multiple_extends_with_multiple_extenders_and_single_target
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar, .baz .bar, .foo .bang, .baz .bang {
+ a: b; }
+CSS
+.foo .bar {a: b}
+.baz { extend .foo}
+.bang { extend .bar}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo.bar, .bar.baz, .baz.bang, .foo.bang {
+ a: b; }
+CSS
+.foo.bar {a: b}
+.baz { extend .foo}
+.bang { extend .bar}
+SCSS
+ end
+
+ def test_chained_extends
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .bar, .baz, .bip {
+ a: b; }
+CSS
+.foo {a: b}
+.bar { extend .foo}
+.baz { extend .bar}
+.bip { extend .bar}
+SCSS
+ end
+
+ def test_dynamic_extendee
+ assert_extends '.foo', '.bar { extend #{".foo"}}', '.foo, .bar'
+ assert_extends('[baz^="blip12px"]', '.bar { extend [baz^="blip#{12px}"]}',
+ '[baz^="blip12px"], .bar')
+ end
+
+ def test_nested_target
+ assert_extends '.foo .bar', '.baz { extend .bar}', '.foo .bar, .foo .baz'
+ end
+
+ def test_target_with_child
+ assert_extends '.foo .bar', '.baz { extend .foo}', '.foo .bar, .baz .bar'
+ end
+
+ def test_class_unification
+ assert_unification '.foo.bar', '.baz { extend .foo}', '.foo.bar, .bar.baz'
+ assert_unification '.foo.baz', '.baz { extend .foo}', '.baz'
+ end
+
+ def test_id_unification
+ assert_unification '.foo.bar', '#baz { extend .foo}', '.foo.bar, .bar#baz'
+ assert_unification '.foo#baz', '#baz { extend .foo}', '#baz'
+
+ assert_extend_doesnt_match('#bar', '.foo', :failed_to_unify, 2) do
+ render_unification '.foo#baz', '#bar { extend .foo}'
+ end
+ end
+
+ def test_universal_unification_with_simple_target
+ assert_unification '.foo', '* { extend .foo}', '.foo, *'
+ assert_unification '.foo', '*|* { extend .foo}', '.foo, *|*'
+ assert_unification '.foo.bar', '* { extend .foo}', '.bar'
+ assert_unification '.foo.bar', '*|* { extend .foo}', '.bar'
+ assert_unification '.foo.bar', 'ns|* { extend .foo}', '.foo.bar, ns|*.bar'
+ end
+
+ def test_universal_unification_with_namespaceless_universal_target
+ assert_unification '*.foo', '* { extend .foo}', '*'
+ assert_unification '*.foo', '*|* { extend .foo}', '*'
+ assert_unification '*|*.foo', '* { extend .foo}', '*|*.foo, *'
+ assert_unification '*|*.foo', '*|* { extend .foo}', '*|*'
+ assert_unification '*.foo', 'ns|* { extend .foo}', '*.foo, ns|*'
+ assert_unification '*|*.foo', 'ns|* { extend .foo}', '*|*.foo, ns|*'
+ end
+
+ def test_universal_unification_with_namespaced_universal_target
+ assert_unification 'ns|*.foo', '* { extend .foo}', 'ns|*'
+ assert_unification 'ns|*.foo', '*|* { extend .foo}', 'ns|*'
+
+ assert_extend_doesnt_match('ns2|*', '.foo', :failed_to_unify, 2) do
+ render_unification 'ns1|*.foo', 'ns2|* { extend .foo}'
+ end
+
+ assert_unification 'ns|*.foo', 'ns|* { extend .foo}', 'ns|*'
+ end
+
+ def test_universal_unification_with_namespaceless_element_target
+ assert_unification 'a.foo', '* { extend .foo}', 'a'
+ assert_unification 'a.foo', '*|* { extend .foo}', 'a'
+ assert_unification '*|a.foo', '* { extend .foo}', '*|a.foo, a'
+ assert_unification '*|a.foo', '*|* { extend .foo}', '*|a'
+ assert_unification 'a.foo', 'ns|* { extend .foo}', 'a.foo, ns|a'
+ assert_unification '*|a.foo', 'ns|* { extend .foo}', '*|a.foo, ns|a'
+ end
+
+ def test_universal_unification_with_namespaced_element_target
+ assert_unification 'ns|a.foo', '* { extend .foo}', 'ns|a'
+ assert_unification 'ns|a.foo', '*|* { extend .foo}', 'ns|a'
+
+ assert_extend_doesnt_match('ns2|*', '.foo', :failed_to_unify, 2) do
+ render_unification 'ns1|a.foo', 'ns2|* { extend .foo}'
+ end
+
+ assert_unification 'ns|a.foo', 'ns|* { extend .foo}', 'ns|a'
+ end
+
+ def test_element_unification_with_simple_target
+ assert_unification '.foo', 'a { extend .foo}', '.foo, a'
+ assert_unification '.foo.bar', 'a { extend .foo}', '.foo.bar, a.bar'
+ assert_unification '.foo.bar', '*|a { extend .foo}', '.foo.bar, *|a.bar'
+ assert_unification '.foo.bar', 'ns|a { extend .foo}', '.foo.bar, ns|a.bar'
+ end
+
+ def test_element_unification_with_namespaceless_universal_target
+ assert_unification '*.foo', 'a { extend .foo}', '*.foo, a'
+ assert_unification '*.foo', '*|a { extend .foo}', '*.foo, a'
+ assert_unification '*|*.foo', 'a { extend .foo}', '*|*.foo, a'
+ assert_unification '*|*.foo', '*|a { extend .foo}', '*|*.foo, *|a'
+ assert_unification '*.foo', 'ns|a { extend .foo}', '*.foo, ns|a'
+ assert_unification '*|*.foo', 'ns|a { extend .foo}', '*|*.foo, ns|a'
+ end
+
+ def test_element_unification_with_namespaced_universal_target
+ assert_unification 'ns|*.foo', 'a { extend .foo}', 'ns|*.foo, ns|a'
+ assert_unification 'ns|*.foo', '*|a { extend .foo}', 'ns|*.foo, ns|a'
+
+ assert_extend_doesnt_match('ns2|a', '.foo', :failed_to_unify, 2) do
+ render_unification 'ns1|*.foo', 'ns2|a { extend .foo}'
+ end
+
+ assert_unification 'ns|*.foo', 'ns|a { extend .foo}', 'ns|*.foo, ns|a'
+ end
+
+ def test_element_unification_with_namespaceless_element_target
+ assert_unification 'a.foo', 'a { extend .foo}', 'a'
+ assert_unification 'a.foo', '*|a { extend .foo}', 'a'
+ assert_unification '*|a.foo', 'a { extend .foo}', '*|a.foo, a'
+ assert_unification '*|a.foo', '*|a { extend .foo}', '*|a'
+ assert_unification 'a.foo', 'ns|a { extend .foo}', 'a.foo, ns|a'
+ assert_unification '*|a.foo', 'ns|a { extend .foo}', '*|a.foo, ns|a'
+
+ assert_extend_doesnt_match('h1', '.foo', :failed_to_unify, 2) do
+ render_unification 'a.foo', 'h1 { extend .foo}'
+ end
+ end
+
+ def test_element_unification_with_namespaced_element_target
+ assert_unification 'ns|a.foo', 'a { extend .foo}', 'ns|a'
+ assert_unification 'ns|a.foo', '*|a { extend .foo}', 'ns|a'
+
+ assert_extend_doesnt_match('ns2|a', '.foo', :failed_to_unify, 2) do
+ render_unification 'ns1|a.foo', 'ns2|a { extend .foo}'
+ end
+
+ assert_unification 'ns|a.foo', 'ns|a { extend .foo}', 'ns|a'
+ end
+
+ def test_attribute_unification
+ assert_unification '[foo=bar].baz', '[foo=baz] { extend .baz}', '[foo=bar].baz, [foo=bar][foo=baz]'
+ assert_unification '[foo=bar].baz', '[foo^=bar] { extend .baz}', '[foo=bar].baz, [foo=bar][foo^=bar]'
+ assert_unification '[foo=bar].baz', '[foot=bar] { extend .baz}', '[foo=bar].baz, [foo=bar][foot=bar]'
+ assert_unification '[foo=bar].baz', '[ns|foo=bar] { extend .baz}', '[foo=bar].baz, [foo=bar][ns|foo=bar]'
+ assert_unification '%-a [foo=bar].bar', '[foo=bar] { extend .bar}', '-a [foo=bar]'
+ end
+
+ def test_pseudo_unification
+ assert_unification ':foo.baz', ':foo(2n+1) { extend .baz}', ':foo.baz, :foo:foo(2n+1)'
+ assert_unification ':foo.baz', '::foo { extend .baz}', ':foo.baz, :foo::foo'
+
+ assert_extend_doesnt_match('::bar', '.baz', :failed_to_unify, 2) do
+ render_unification '::foo.baz', '::bar { extend .baz}'
+ end
+
+ assert_extend_doesnt_match('::foo(2n+1)', '.baz', :failed_to_unify, 2) do
+ render_unification '::foo.baz', '::foo(2n+1) { extend .baz}'
+ end
+
+ assert_unification '::foo.baz', '::foo { extend .baz}', '::foo'
+ assert_unification '::foo(2n+1).baz', '::foo(2n+1) { extend .baz}', '::foo(2n+1)'
+ assert_unification ':foo.baz', ':bar { extend .baz}', ':foo.baz, :foo:bar'
+ assert_unification '.baz:foo', ':after { extend .baz}', '.baz:foo, :foo:after'
+ assert_unification '.baz:after', ':foo { extend .baz}', '.baz:after, :foo:after'
+ assert_unification ':foo.baz', ':foo { extend .baz}', ':foo'
+ end
+
+ def test_pseudoelement_remains_at_end_of_selector
+ assert_extends '.foo::bar', '.baz { extend .foo}', '.foo::bar, .baz::bar'
+ assert_extends 'a.foo::bar', '.baz { extend .foo}', 'a.foo::bar, a.baz::bar'
+ end
+
+ def test_pseudoclass_remains_at_end_of_selector
+ assert_extends '.foo:bar', '.baz { extend .foo}', '.foo:bar, .baz:bar'
+ assert_extends 'a.foo:bar', '.baz { extend .foo}', 'a.foo:bar, a.baz:bar'
+ end
+
+ def test_not_remains_at_end_of_selector
+ assert_extends '.foo:not(.bar)', '.baz { extend .foo}', '.foo:not(.bar), .baz:not(.bar)'
+ end
+
+ def test_pseudoelement_goes_lefter_than_pseudoclass
+ assert_extends '.foo::bar', '.baz:bang { extend .foo}', '.foo::bar, .baz:bang::bar'
+ assert_extends '.foo:bar', '.baz::bang { extend .foo}', '.foo:bar, .baz:bar::bang'
+ end
+
+ def test_pseudoelement_goes_lefter_than_not
+ assert_extends '.foo::bar', '.baz:not(.bang) { extend .foo}', '.foo::bar, .baz:not(.bang)::bar'
+ assert_extends '.foo:not(.bang)', '.baz::bar { extend .foo}', '.foo:not(.bang), .baz:not(.bang)::bar'
+ end
+
+ def test_negation_unification
+ assert_extends ':not(.foo).baz', ':not(.bar) { extend .baz}', ':not(.foo).baz, :not(.foo):not(.bar)'
+ # Unifying to :not(.foo) here would reduce the specificity of the original selector.
+ assert_extends ':not(.foo).baz', ':not(.foo) { extend .baz}', ':not(.foo).baz, :not(.foo)'
+ end
+
+ def test_prefixed_pseudoclass_unification
+ assert_unification(
+ ':nth-child(2n+1 of .foo).baz',
+ ':nth-child(2n of .foo) { extend .baz}',
+ ':nth-child(2n+1 of .foo).baz, :nth-child(2n+1 of .foo):nth-child(2n of .foo)')
+
+ assert_unification(
+ ':nth-child(2n+1 of .foo).baz',
+ ':nth-child(2n+1 of .bar) { extend .baz}',
+ ':nth-child(2n+1 of .foo).baz, :nth-child(2n+1 of .foo):nth-child(2n+1 of .bar)')
+
+ assert_unification(
+ ':nth-child(2n+1 of .foo).baz',
+ ':nth-child(2n+1 of .foo) { extend .baz}',
+ ':nth-child(2n+1 of .foo)')
+ end
+
+ def test_extend_into_not
+ assert_extends(':not(.foo)', '.x { extend .foo}', ':not(.foo):not(.x)')
+ assert_extends(':not(.foo.bar)', '.x { extend .bar}', ':not(.foo.bar):not(.foo.x)')
+ assert_extends(
+ ':not(.foo.bar, .baz.bar)',
+ '.x { extend .bar}',
+ ':not(.foo.bar, .foo.x, .baz.bar, .baz.x)')
+ end
+
+ def test_extend_into_mergeable_pseudoclasses
+ assert_extends(':matches(.foo)', '.x { extend .foo}', ':matches(.foo, .x)')
+ assert_extends(':matches(.foo.bar)', '.x { extend .bar}', ':matches(.foo.bar, .foo.x)')
+ assert_extends(
+ ':matches(.foo.bar, .baz.bar)',
+ '.x { extend .bar}',
+ ':matches(.foo.bar, .foo.x, .baz.bar, .baz.x)')
+
+ assert_extends(':-moz-any(.foo)', '.x { extend .foo}', ':-moz-any(.foo, .x)')
+ assert_extends(':current(.foo)', '.x { extend .foo}', ':current(.foo, .x)')
+ assert_extends(':has(.foo)', '.x { extend .foo}', ':has(.foo, .x)')
+ assert_extends(':host(.foo)', '.x { extend .foo}', ':host(.foo, .x)')
+ assert_extends(':host-context(.foo)', '.x { extend .foo}', ':host-context(.foo, .x)')
+ assert_extends(':nth-child(n of .foo)', '.x { extend .foo}', ':nth-child(n of .foo, .x)')
+ assert_extends(
+ ':nth-last-child(n of .foo)',
+ '.x { extend .foo}',
+ ':nth-last-child(n of .foo, .x)')
+ end
+
+ def test_complex_extend_into_pseudoclass
+ # Unlike other selectors, we don't allow complex selectors to be
+ # added to `:not` if they weren't there before. At time of
+ # writing, most browsers don't support that and will throw away
+ # the entire selector if it exists.
+ #assert_extends(':not(.bar)', '.x .y { extend .bar}', ':not(.bar)')
+
+ # If the `:not()` already has a complex selector, we won't break
+ # anything by adding a new one.
+ assert_extends(':not(.baz .bar)', '.x .y { extend .bar}',
+ ':not(.baz .bar):not(.baz .x .y):not(.x .baz .y)')
+
+ # If the `:not()` would only contain complex selectors, there's no
+ # harm in letting it continue to exist.
+ assert_extends(':not(%bar)', '.x .y { extend %bar}', ':not(.x .y)')
+
+ assert_extends(':matches(.bar)', '.x .y { extend .bar}', ':matches(.bar, .x .y)')
+ assert_extends(':current(.bar)', '.x .y { extend .bar}', ':current(.bar, .x .y)')
+ assert_extends(':has(.bar)', '.x .y { extend .bar}', ':has(.bar, .x .y)')
+ assert_extends(':host(.bar)', '.x .y { extend .bar}', ':host(.bar, .x .y)')
+ assert_extends(':host-context(.bar)', '.x .y { extend .bar}', ':host-context(.bar, .x .y)')
+ assert_extends(
+ ':-moz-any(.bar)',
+ '.x .y { extend .bar}',
+ ':-moz-any(.bar, .x .y)')
+ assert_extends(
+ ':nth-child(n of .bar)',
+ '.x .y { extend .bar}',
+ ':nth-child(n of .bar, .x .y)')
+ assert_extends(
+ ':nth-last-child(n of .bar)',
+ '.x .y { extend .bar}',
+ ':nth-last-child(n of .bar, .x .y)')
+ end
+
+ def test_extend_over_selector_pseudoclass
+ assert_extends(':not(.foo)', '.x { extend :not(.foo)}', ':not(.foo), .x')
+ assert_extends(
+ ':matches(.foo, .bar)',
+ '.x { extend :matches(.foo, .bar)}',
+ ':matches(.foo, .bar), .x')
+ end
+
+ def test_matches_within_not
+ assert_extends(
+ ':not(.foo, .bar)',
+ ':matches(.x, .y) { extend .foo}',
+ ':not(.foo, .x, .y, .bar)')
+ end
+
+ def test_pseudoclasses_merge
+ assert_extends(':matches(.foo)', ':matches(.bar) { extend .foo}', ':matches(.foo, .bar)')
+ assert_extends(':-moz-any(.foo)', ':-moz-any(.bar) { extend .foo}', ':-moz-any(.foo, .bar)')
+ assert_extends(':current(.foo)', ':current(.bar) { extend .foo}', ':current(.foo, .bar)')
+ assert_extends(
+ ':nth-child(n of .foo)',
+ ':nth-child(n of .bar) { extend .foo}',
+ ':nth-child(n of .foo, .bar)')
+ assert_extends(
+ ':nth-last-child(n of .foo)',
+ ':nth-last-child(n of .bar) { extend .foo}',
+ ':nth-last-child(n of .foo, .bar)')
+ end
+
+ def test_nesting_pseudoclasses_merge
+ assert_extends(':has(.foo)', ':has(.bar) { extend .foo}', ':has(.foo, :has(.bar))')
+ assert_extends(':host(.foo)', ':host(.bar) { extend .foo}', ':host(.foo, :host(.bar))')
+ assert_extends(
+ ':host-context(.foo)',
+ ':host-context(.bar) { extend .foo}',
+ ':host-context(.foo, :host-context(.bar))')
+ end
+
+ def test_not_unifies_with_unique_values
+ assert_unification('foo', ':not(bar) { extend foo}', ':not(bar)')
+ assert_unification('#foo', ':not(#bar) { extend #foo}', ':not(#bar)')
+ end
+
+ def test_not_adds_no_specificity
+ assert_specificity_equals(':not(.foo)', '.foo')
+ end
+
+ def test_matches_has_a_specificity_range
+ # `:matches(.foo, #bar)` has minimum specificity equal to that of `.foo`,
+ # which means `:matches(.foo, #bar) .a` can have less specificity than
+ # `#b.a`. Thus the selector generated by `#b.a` should be preserved.
+ assert_equal <<CSS, render(<<SCSS)
+:matches(.foo, #bar) .a, :matches(.foo, #bar) #b.a {
+ a: b; }
+CSS
+:matches(.foo, #bar) %x {a: b}
+.a { extend %x}
+#b.a { extend %x}
+SCSS
+
+ # `:matches(.foo, #bar)` has maximum specificity equal to that of `#bar`,
+ # which means `:matches(.foo, #bar).b` can have greater specificity than `.a
+ # .b`. Thus the selector generated by `:matches(.foo, #bar).b` should be
+ # preserved.
+ assert_equal <<CSS, render(<<SCSS)
+.a .b, .a .b:matches(.foo, #bar) {
+ a: b; }
+CSS
+.a %x {a: b}
+.b { extend %x}
+.b:matches(.foo, #bar) { extend %x}
+SCSS
+ end
+
+ def test_extend_into_not_and_normal_extend
+ assert_equal <<CSS, render(<<SCSS)
+.x:not(.y):not(.bar), .foo:not(.y):not(.bar) {
+ a: b; }
+CSS
+.x:not(.y) {a: b}
+.foo { extend .x}
+.bar { extend .y}
+SCSS
+ end
+
+ def test_extend_into_matches_and_normal_extend
+ assert_equal <<CSS, render(<<SCSS)
+.x:matches(.y, .bar), .foo:matches(.y, .bar) {
+ a: b; }
+CSS
+.x:matches(.y) {a: b}
+.foo { extend .x}
+.bar { extend .y}
+SCSS
+ end
+
+ def test_multilayer_pseudoclass_extend
+ assert_equal <<CSS, render(<<SCSS)
+:matches(.x, .foo, .bar) {
+ a: b; }
+CSS
+:matches(.x) {a: b}
+.foo { extend .x}
+.bar { extend .foo}
+SCSS
+ end
+
+ def test_comma_extendee
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .baz {
+ a: b; }
+
+.bar, .baz {
+ c: d; }
+CSS
+.foo {a: b}
+.bar {c: d}
+.baz { extend .foo, .bar}
+SCSS
+ end
+
+ def test_redundant_selector_elimination
+ assert_equal <<CSS, render(<<SCSS)
+.foo.bar, .x, .y {
+ a: b; }
+CSS
+.foo.bar {a: b}
+.x { extend .foo, .bar}
+.y { extend .foo, .bar}
+SCSS
+ end
+
+ def test_nested_pseudo_selectors
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar:not(.baz), .bang .bar:not(.baz) {
+ a: b; }
+CSS
+.foo {
+ .bar:not(.baz) {a: b}
+}
+.bang { extend .foo}
+SCSS
+ end
+
+ ## Long Extendees
+
+ def test_long_extendee
+ assert_extends '.foo.bar', '.baz { extend .foo.bar}', '.foo.bar, .baz'
+ end
+
+ def test_long_extendee_requires_all_selectors
+ assert_extend_doesnt_match('.baz', '.foo.bar', :not_found, 2) do
+ render_extends '.foo', '.baz { extend .foo.bar}'
+ end
+ end
+
+ def test_long_extendee_matches_supersets
+ assert_extends '.foo.bar.bap', '.baz { extend .foo.bar}', '.foo.bar.bap, .bap.baz'
+ end
+
+ def test_long_extendee_runs_unification
+ assert_extends 'ns|*.foo.bar', 'a.baz { extend .foo.bar}', 'ns|*.foo.bar, ns|a.baz'
+ end
+
+ ## Long Extenders
+
+ def test_long_extender
+ assert_extends '.foo.bar', '.baz.bang { extend .foo}', '.foo.bar, .bar.baz.bang'
+ end
+
+ def test_long_extender_runs_unification
+ assert_extends 'ns|*.foo.bar', 'a.baz { extend .foo}', 'ns|*.foo.bar, ns|a.bar.baz'
+ end
+
+ def test_long_extender_aborts_unification
+ assert_extend_doesnt_match('h1.baz', '.foo', :failed_to_unify, 2) do
+ render_extends 'a.foo#bar', 'h1.baz { extend .foo}'
+ end
+
+ assert_extend_doesnt_match('.bang#baz', '.foo', :failed_to_unify, 2) do
+ render_extends 'a.foo#bar', '.bang#baz { extend .foo}'
+ end
+ end
+
+ ## Nested Extenders
+
+ def test_nested_extender
+ assert_extends '.foo', 'foo bar { extend .foo}', '.foo, foo bar'
+ end
+
+ def test_nested_extender_runs_unification
+ assert_extends '.foo.bar', 'foo bar { extend .foo}', '.foo.bar, foo bar.bar'
+ end
+
+ def test_nested_extender_aborts_unification
+ assert_extend_doesnt_match('foo bar', '.foo', :failed_to_unify, 2) do
+ render_extends 'baz.foo', 'foo bar { extend .foo}'
+ end
+ end
+
+ def test_nested_extender_alternates_parents
+ assert_extends('.baz .bip .foo', 'foo .grank bar { extend .foo}',
+ '.baz .bip .foo, .baz .bip foo .grank bar, foo .grank .baz .bip bar')
+ end
+
+ def test_nested_extender_unifies_identical_parents
+ assert_extends('.baz .bip .foo', '.baz .bip bar { extend .foo}',
+ '.baz .bip .foo, .baz .bip bar')
+ end
+
+ def test_nested_extender_unifies_common_substring
+ assert_extends('.baz .bip .bap .bink .foo', '.brat .bip .bap bar { extend .foo}',
+ '.baz .bip .bap .bink .foo, .baz .brat .bip .bap .bink bar, .brat .baz .bip .bap .bink bar')
+ end
+
+ def test_nested_extender_unifies_common_subseq
+ assert_extends('.a .x .b .y .foo', '.a .n .b .m bar { extend .foo}',
+ '.a .x .b .y .foo, .a .x .n .b .y .m bar, .a .n .x .b .y .m bar, .a .x .n .b .m .y bar, .a .n .x .b .m
.y bar')
+ end
+
+ def test_nested_extender_chooses_first_subseq
+ assert_extends('.a .b .c .d .foo', '.c .d .a .b .bar { extend .foo}',
+ '.a .b .c .d .foo, .a .b .c .d .a .b .bar')
+ end
+
+ def test_nested_extender_counts_extended_subselectors
+ assert_extends('.a .bip.bop .foo', '.b .bip .bar { extend .foo}',
+ '.a .bip.bop .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar')
+ end
+
+ def test_nested_extender_counts_extended_superselectors
+ assert_extends('.a .bip .foo', '.b .bip.bop .bar { extend .foo}',
+ '.a .bip .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar')
+ end
+
+ def test_nested_extender_with_child_selector
+ assert_extends '.baz .foo', 'foo > bar { extend .foo}', '.baz .foo, .baz foo > bar'
+ end
+
+ def test_nested_extender_finds_common_selectors_around_child_selector
+ assert_extends 'a > b c .c1', 'a c .c2 { extend .c1}', 'a > b c .c1, a > b c .c2'
+ assert_extends 'a > b c .c1', 'b c .c2 { extend .c1}', 'a > b c .c1, a > b c .c2'
+ end
+
+ def test_nested_extender_doesnt_find_common_selectors_around_adjacent_sibling_selector
+ assert_extends 'a + b c .c1', 'a c .c2 { extend .c1}', 'a + b c .c1, a + b a c .c2, a a + b c .c2'
+ assert_extends 'a + b c .c1', 'a b .c2 { extend .c1}', 'a + b c .c1, a a + b c .c2'
+ assert_extends 'a + b c .c1', 'b c .c2 { extend .c1}', 'a + b c .c1, a + b c .c2'
+ end
+
+ def test_nested_extender_doesnt_find_common_selectors_around_sibling_selector
+ assert_extends 'a ~ b c .c1', 'a c .c2 { extend .c1}', 'a ~ b c .c1, a ~ b a c .c2, a a ~ b c .c2'
+ assert_extends 'a ~ b c .c1', 'a b .c2 { extend .c1}', 'a ~ b c .c1, a a ~ b c .c2'
+ assert_extends 'a ~ b c .c1', 'b c .c2 { extend .c1}', 'a ~ b c .c1, a ~ b c .c2'
+ end
+
+ def test_nested_extender_doesnt_find_common_selectors_around_reference_selector
+ assert_extends 'a /for/ b c .c1', 'a c .c2 { extend .c1}', 'a /for/ b c .c1, a /for/ b a c .c2, a a
/for/ b c .c2'
+ assert_extends 'a /for/ b c .c1', 'a b .c2 { extend .c1}', 'a /for/ b c .c1, a a /for/ b c .c2'
+ assert_extends 'a /for/ b c .c1', 'b c .c2 { extend .c1}', 'a /for/ b c .c1, a /for/ b c .c2'
+ end
+
+ def test_nested_extender_with_early_child_selectors_doesnt_subseq_them
+ assert_extends('.bip > .bap .foo', '.grip > .bap .bar { extend .foo}',
+ '.bip > .bap .foo, .bip > .bap .grip > .bap .bar, .grip > .bap .bip > .bap .bar')
+ assert_extends('.bap > .bip .foo', '.bap > .grip .bar { extend .foo}',
+ '.bap > .bip .foo, .bap > .bip .bap > .grip .bar, .bap > .grip .bap > .bip .bar')
+ end
+
+ def test_nested_extender_with_child_selector_unifies
+ assert_extends '.baz.foo', 'foo > bar { extend .foo}', '.baz.foo, foo > bar.baz'
+
+ assert_equal <<CSS, render(<<SCSS)
+.baz > .foo, .baz > .bar {
+ a: b; }
+CSS
+.baz > {
+ .foo {a: b}
+ .bar { extend .foo}
+}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar, .foo > .baz {
+ a: b; }
+CSS
+.foo {
+ .bar {a: b}
+ > .baz { extend .bar}
+}
+SCSS
+ end
+
+ def test_nested_extender_with_early_child_selector
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar, .foo .bip > .baz {
+ a: b; }
+CSS
+.foo {
+ .bar {a: b}
+ .bip > .baz { extend .bar}
+}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bip .bar, .foo .bip .foo > .baz {
+ a: b; }
+CSS
+.foo {
+ .bip .bar {a: b}
+ > .baz { extend .bar}
+}
+SCSS
+
+ assert_extends '.foo > .bar', '.bip + .baz { extend .bar}', '.foo > .bar, .foo > .bip + .baz'
+ assert_extends '.foo + .bar', '.bip > .baz { extend .bar}', '.foo + .bar, .bip > .foo + .baz'
+ assert_extends '.foo > .bar', '.bip > .baz { extend .bar}', '.foo > .bar, .bip.foo > .baz'
+ end
+
+ def test_nested_extender_with_trailing_child_selector
+ assert_raises(Sass::SyntaxError, "bar > can't extend: invalid selector") do
+ render("bar > { extend .baz}")
+ end
+ end
+
+ def test_nested_extender_with_sibling_selector
+ assert_extends '.baz .foo', 'foo + bar { extend .foo}', '.baz .foo, .baz foo + bar'
+ end
+
+ def test_nested_extender_with_hacky_selector
+ assert_extends('.baz .foo', 'foo + > > + bar { extend .foo}',
+ '.baz .foo, .baz foo + > > + bar, foo .baz + > > + bar')
+ assert_extends '.baz .foo', '> > bar { extend .foo}', '.baz .foo, > > .baz bar'
+ end
+
+ def test_nested_extender_merges_with_same_selector
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar, .foo .baz {
+ a: b; }
+CSS
+.foo {
+ .bar {a: b}
+ .baz { extend .bar} }
+SCSS
+ end
+
+ def test_nested_extender_with_child_selector_merges_with_same_selector
+ assert_extends('.foo > .bar .baz', '.foo > .bar .bang { extend .baz}',
+ '.foo > .bar .baz, .foo > .bar .bang')
+ end
+
+ # Combinator Unification
+
+ def test_combinator_unification_for_hacky_combinators
+ assert_extends '.a > + x', '.b y { extend x}', '.a > + x, .a .b > + y, .b .a > + y'
+ assert_extends '.a x', '.b > + y { extend x}', '.a x, .a .b > + y, .b .a > + y'
+ assert_extends '.a > + x', '.b > + y { extend x}', '.a > + x, .a .b > + y, .b .a > + y'
+ assert_extends '.a ~ > + x', '.b > + y { extend x}', '.a ~ > + x, .a .b ~ > + y, .b .a ~ > + y'
+ assert_extends '.a + > x', '.b > + y { extend x}', '.a + > x'
+ assert_extends '.a + > x', '.b > + y { extend x}', '.a + > x'
+ assert_extends '.a ~ > + .b > x', '.c > + .d > y { extend x}', '.a ~ > + .b > x, .a .c ~ > + .d.b > y,
.c .a ~ > + .d.b > y'
+ end
+
+ def test_combinator_unification_double_tilde
+ assert_extends '.a.b ~ x', '.a ~ y { extend x}', '.a.b ~ x, .a.b ~ y'
+ assert_extends '.a ~ x', '.a.b ~ y { extend x}', '.a ~ x, .a.b ~ y'
+ assert_extends '.a ~ x', '.b ~ y { extend x}', '.a ~ x, .a ~ .b ~ y, .b ~ .a ~ y, .b.a ~ y'
+ assert_extends 'a.a ~ x', 'b.b ~ y { extend x}', 'a.a ~ x, a.a ~ b.b ~ y, b.b ~ a.a ~ y'
+ end
+
+ def test_combinator_unification_tilde_plus
+ assert_extends '.a.b + x', '.a ~ y { extend x}', '.a.b + x, .a.b + y'
+ assert_extends '.a + x', '.a.b ~ y { extend x}', '.a + x, .a.b ~ .a + y, .a.b + y'
+ assert_extends '.a + x', '.b ~ y { extend x}', '.a + x, .b ~ .a + y, .b.a + y'
+ assert_extends 'a.a + x', 'b.b ~ y { extend x}', 'a.a + x, b.b ~ a.a + y'
+ assert_extends '.a.b ~ x', '.a + y { extend x}', '.a.b ~ x, .a.b ~ .a + y, .a.b + y'
+ assert_extends '.a ~ x', '.a.b + y { extend x}', '.a ~ x, .a.b + y'
+ assert_extends '.a ~ x', '.b + y { extend x}', '.a ~ x, .a ~ .b + y, .a.b + y'
+ assert_extends 'a.a ~ x', 'b.b + y { extend x}', 'a.a ~ x, a.a ~ b.b + y'
+ end
+
+ def test_combinator_unification_angle_sibling
+ assert_extends '.a > x', '.b ~ y { extend x}', '.a > x, .a > .b ~ y'
+ assert_extends '.a > x', '.b + y { extend x}', '.a > x, .a > .b + y'
+ assert_extends '.a ~ x', '.b > y { extend x}', '.a ~ x, .b > .a ~ y'
+ assert_extends '.a + x', '.b > y { extend x}', '.a + x, .b > .a + y'
+ end
+
+ def test_combinator_unification_double_angle
+ assert_extends '.a.b > x', '.b > y { extend x}', '.a.b > x, .b.a > y'
+ assert_extends '.a > x', '.a.b > y { extend x}', '.a > x, .a.b > y'
+ assert_extends '.a > x', '.b > y { extend x}', '.a > x, .b.a > y'
+ assert_extends 'a.a > x', 'b.b > y { extend x}', 'a.a > x'
+ end
+
+ def test_combinator_unification_double_plus
+ assert_extends '.a.b + x', '.b + y { extend x}', '.a.b + x, .b.a + y'
+ assert_extends '.a + x', '.a.b + y { extend x}', '.a + x, .a.b + y'
+ assert_extends '.a + x', '.b + y { extend x}', '.a + x, .b.a + y'
+ assert_extends 'a.a + x', 'b.b + y { extend x}', 'a.a + x'
+ end
+
+ def test_combinator_unification_angle_space
+ assert_extends '.a.b > x', '.a y { extend x}', '.a.b > x, .a.b > y'
+ assert_extends '.a > x', '.a.b y { extend x}', '.a > x, .a.b .a > y'
+ assert_extends '.a > x', '.b y { extend x}', '.a > x, .b .a > y'
+ assert_extends '.a.b x', '.a > y { extend x}', '.a.b x, .a.b .a > y'
+ assert_extends '.a x', '.a.b > y { extend x}', '.a x, .a.b > y'
+ assert_extends '.a x', '.b > y { extend x}', '.a x, .a .b > y'
+ end
+
+ def test_combinator_unification_plus_space
+ assert_extends '.a.b + x', '.a y { extend x}', '.a.b + x, .a .a.b + y'
+ assert_extends '.a + x', '.a.b y { extend x}', '.a + x, .a.b .a + y'
+ assert_extends '.a + x', '.b y { extend x}', '.a + x, .b .a + y'
+ assert_extends '.a.b x', '.a + y { extend x}', '.a.b x, .a.b .a + y'
+ assert_extends '.a x', '.a.b + y { extend x}', '.a x, .a .a.b + y'
+ assert_extends '.a x', '.b + y { extend x}', '.a x, .a .b + y'
+ end
+
+ def test_combinator_unification_nested
+ assert_extends '.a > .b + x', '.c > .d + y { extend x}', '.a > .b + x, .c.a > .d.b + y'
+ assert_extends '.a > .b + x', '.c > y { extend x}', '.a > .b + x, .c.a > .b + y'
+ end
+
+ def test_combinator_unification_with_newlines
+ assert_equal <<CSS, render(<<SCSS)
+.a >
+.b
++ x, .c.a > .d.b + y {
+ a: b; }
+CSS
+.a >
+.b
++ x {a: b}
+.c
+> .d +
+y { extend x}
+SCSS
+ end
+
+ # Loops
+
+ def test_extend_self_loop
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+CSS
+.foo {a: b; @extend .foo}
+SCSS
+ end
+
+ def test_basic_extend_loop
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .bar {
+ a: b; }
+
+.bar, .foo {
+ c: d; }
+CSS
+.foo {a: b; @extend .bar}
+.bar {c: d; @extend .foo}
+SCSS
+ end
+
+ def test_three_level_extend_loop
+ assert_equal <<CSS, render(<<SCSS)
+.foo, .baz, .bar {
+ a: b; }
+
+.bar, .foo, .baz {
+ c: d; }
+
+.baz, .bar, .foo {
+ e: f; }
+CSS
+.foo {a: b; @extend .bar}
+.bar {c: d; @extend .baz}
+.baz {e: f; @extend .foo}
+SCSS
+ end
+
+ def test_nested_extend_loop
+ assert_equal <<CSS, render(<<SCSS)
+.bar, .bar .foo {
+ a: b; }
+ .bar .foo {
+ c: d; }
+CSS
+.bar {
+ a: b;
+ .foo {c: d; @extend .bar}
+}
+SCSS
+ end
+
+ def test_cross_loop
+ # The first law of extend means the selector should stick around.
+ assert_equal <<CSS, render(<<SCSS)
+.foo.bar, .foo, .bar {
+ a: b; }
+CSS
+.foo.bar {a: b}
+.foo { extend .bar}
+.bar { extend .foo}
+SCSS
+ end
+
+ def test_multiple_extender_merges_with_superset_selector
+ assert_equal <<CSS, render(<<SCSS)
+a.bar.baz, a.foo {
+ a: b; }
+CSS
+.foo { extend .bar; @extend .baz}
+a.bar.baz {a: b}
+SCSS
+ end
+
+ def test_control_flow_if
+ assert_equal <<CSS, render(<<SCSS)
+.true, .also-true {
+ color: green; }
+
+.false, .also-false {
+ color: red; }
+CSS
+.true { color: green; }
+.false { color: red; }
+.also-true {
+ @if true { @extend .true; }
+ @else { @extend .false; }
+}
+.also-false {
+ @if false { @extend .true; }
+ @else { @extend .false; }
+}
+SCSS
+ end
+
+ def test_control_flow_for
+ assert_equal <<CSS, render(<<SCSS)
+.base-0, .added {
+ color: green; }
+
+.base-1, .added {
+ display: block; }
+
+.base-2, .added {
+ border: 1px solid blue; }
+CSS
+.base-0 { color: green; }
+.base-1 { display: block; }
+.base-2 { border: 1px solid blue; }
+.added {
+ @for $i from 0 to 3 {
+ @extend .base-\#{$i};
+ }
+}
+SCSS
+ end
+
+ def test_control_flow_while
+ assert_equal <<CSS, render(<<SCSS)
+.base-0, .added {
+ color: green; }
+
+.base-1, .added {
+ display: block; }
+
+.base-2, .added {
+ border: 1px solid blue; }
+CSS
+.base-0 { color: green; }
+.base-1 { display: block; }
+.base-2 { border: 1px solid blue; }
+.added {
+ $i : 0;
+ @while $i < 3 {
+ @extend .base-\#{$i};
+ $i : $i + 1;
+ }
+}
+SCSS
+ end
+
+ def test_basic_placeholder_selector
+ assert_extends '%foo', '.bar { extend %foo}', '.bar'
+ end
+
+ def test_unused_placeholder_selector
+ assert_equal <<CSS, render(<<SCSS)
+.baz {
+ color: blue; }
+CSS
+%foo {color: blue}
+%bar {color: red}
+.baz { extend %foo}
+SCSS
+ end
+
+ def test_placeholder_descendant_selector
+ assert_extends '#context %foo a', '.bar { extend %foo}', '#context .bar a'
+ end
+
+ def test_semi_placeholder_selector
+ assert_equal <<CSS, render(<<SCSS)
+.bar .baz {
+ color: blue; }
+CSS
+#context %foo, .bar .baz {color: blue}
+SCSS
+ end
+
+ def test_placeholder_selector_with_multiple_extenders
+ assert_equal <<CSS, render(<<SCSS)
+.bar, .baz {
+ color: blue; }
+CSS
+%foo {color: blue}
+.bar { extend %foo}
+.baz { extend %foo}
+SCSS
+ end
+
+ def test_placeholder_selector_as_modifier
+ assert_extend_doesnt_match('div', '%foo', :failed_to_unify, 3) do
+ render(<<SCSS)
+a%foo.baz {color: blue}
+.bar { extend %foo}
+div { extend %foo}
+SCSS
+ end
+ end
+
+ def test_placeholder_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+.bar {
+ color: blue; }
+CSS
+$foo: foo;
+
+%\#{$foo} {color: blue}
+.bar { extend %foo}
+SCSS
+ end
+
+ def test_placeholder_in_selector_pseudoclass
+ assert_equal <<CSS, render(<<SCSS)
+:matches(.bar, .baz) {
+ color: blue; }
+CSS
+:matches(%foo) {color: blue}
+.bar { extend %foo}
+.baz { extend %foo}
+SCSS
+ end
+
+ def test_media_in_placeholder_selector
+ assert_equal <<CSS, render(<<SCSS)
+.baz {
+ c: d; }
+CSS
+%foo {bar { media screen {a: b}}}
+.baz {c: d}
+SCSS
+ end
+
+ def test_extend_out_of_media
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+You may not @extend an outer selector from within @media.
+You may only @extend selectors within the same directive.
+From "@extend .foo" on line 3 of test_extend_out_of_media_inline.scss.
+ERR
+.foo {a: b}
+ media screen {
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_out_of_unknown_directive
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+You may not @extend an outer selector from within @flooblehoof.
+You may only @extend selectors within the same directive.
+From "@extend .foo" on line 3 of test_extend_out_of_unknown_directive_inline.scss.
+ERR
+.foo {a: b}
+ flooblehoof {
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_out_of_nested_directives
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+You may not @extend an outer selector from within @flooblehoof.
+You may only @extend selectors within the same directive.
+From "@extend .foo" on line 4 of test_extend_out_of_nested_directives_inline.scss.
+ERR
+ media screen {
+ .foo {a: b}
+ @flooblehoof {
+ .bar { extend .foo}
+ }
+}
+SCSS
+ end
+
+ def test_extend_within_media
+ assert_equal(<<CSS, render(<<SCSS))
+ media screen {
+ .foo, .bar {
+ a: b; } }
+CSS
+ media screen {
+ .foo {a: b}
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_within_unknown_directive
+ assert_equal(<<CSS, render(<<SCSS))
+ flooblehoof {
+ .foo, .bar {
+ a: b; } }
+CSS
+ flooblehoof {
+ .foo {a: b}
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_within_nested_directives
+ assert_equal(<<CSS, render(<<SCSS))
+ media screen {
+ @flooblehoof {
+ .foo, .bar {
+ a: b; } } }
+CSS
+ media screen {
+ @flooblehoof {
+ .foo {a: b}
+ .bar { extend .foo}
+ }
+}
+SCSS
+ end
+
+ def test_extend_within_disparate_media
+ assert_equal(<<CSS, render(<<SCSS))
+ media screen {
+ .foo, .bar {
+ a: b; } }
+CSS
+ media screen {.foo {a: b}}
+ media screen {.bar { extend .foo}}
+SCSS
+ end
+
+ def test_extend_within_disparate_unknown_directive
+ assert_equal(<<CSS, render(<<SCSS))
+ flooblehoof {
+ .foo, .bar {
+ a: b; } }
+ flooblehoof {}
+CSS
+ flooblehoof {.foo {a: b}}
+ flooblehoof {.bar { extend .foo}}
+SCSS
+ end
+
+ def test_extend_within_disparate_nested_directives
+ assert_equal(<<CSS, render(<<SCSS))
+ media screen {
+ @flooblehoof {
+ .foo, .bar {
+ a: b; } } }
+ media screen {
+ @flooblehoof {} }
+CSS
+ media screen { flooblehoof {.foo {a: b}}}
+ media screen { flooblehoof {.bar { extend .foo}}}
+SCSS
+ end
+
+ def test_extend_within_and_without_media
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+You may not @extend an outer selector from within @media.
+You may only @extend selectors within the same directive.
+From "@extend .foo" on line 4 of test_extend_within_and_without_media_inline.scss.
+ERR
+.foo {a: b}
+ media screen {
+ .foo {c: d}
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_within_and_without_unknown_directive
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+You may not @extend an outer selector from within @flooblehoof.
+You may only @extend selectors within the same directive.
+From "@extend .foo" on line 4 of test_extend_within_and_without_unknown_directive_inline.scss.
+ERR
+.foo {a: b}
+ flooblehoof {
+ .foo {c: d}
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_within_and_without_nested_directives
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+You may not @extend an outer selector from within @flooblehoof.
+You may only @extend selectors within the same directive.
+From "@extend .foo" on line 5 of test_extend_within_and_without_nested_directives_inline.scss.
+ERR
+ media screen {
+ .foo {a: b}
+ @flooblehoof {
+ .foo {c: d}
+ .bar { extend .foo}
+ }
+}
+SCSS
+ end
+
+ def test_extend_with_subject_transfers_subject_to_extender
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
+foo bar! baz, foo .bip .bap! baz, .bip foo .bap! baz {
+ a: b; }
+CSS
+foo bar! baz {a: b}
+.bip .bap { extend bar}
+SCSS
+
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
+foo.x bar.y! baz.z, foo.x .bip bar.bap! baz.z, .bip foo.x bar.bap! baz.z {
+ a: b; }
+CSS
+foo.x bar.y! baz.z {a: b}
+.bip .bap { extend .y}
+SCSS
+ end
+
+ def test_extend_with_subject_retains_subject_on_target
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
+.foo! .bar, .foo! .bip .bap, .bip .foo! .bap {
+ a: b; }
+CSS
+.foo! .bar {a: b}
+.bip .bap { extend .bar}
+SCSS
+ end
+
+ def test_extend_with_subject_transfers_subject_to_target
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
+a.foo .bar, .bip a.bap! .bar {
+ a: b; }
+CSS
+a.foo .bar {a: b}
+.bip .bap! { extend .foo}
+SCSS
+ end
+
+ def test_extend_with_subject_retains_subject_on_extender
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
+.foo .bar, .foo .bip! .bap, .bip! .foo .bap {
+ a: b; }
+CSS
+.foo .bar {a: b}
+.bip! .bap { extend .bar}
+SCSS
+ end
+
+ def test_extend_with_subject_fails_with_conflicting_subject
+ silence_warnings {assert_equal(<<CSS, render(<<SCSS))}
+x! .bar {
+ a: b; }
+CSS
+x! .bar {a: b}
+y! .bap { extend .bar}
+SCSS
+ end
+
+ def test_extend_warns_when_extendee_doesnt_exist
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+".foo" failed to @extend ".bar".
+The selector ".bar" was not found.
+Use "@extend .bar !optional" if the extend should be able to fail.
+ERR
+.foo { extend .bar}
+SCSS
+ end
+
+ def test_extend_warns_when_extension_fails
+ assert_raise_message(Sass::SyntaxError, <<ERR) {render(<<SCSS)}
+"b.foo" failed to @extend ".bar".
+No selectors matching ".bar" could be unified with "b.foo".
+Use "@extend .bar !optional" if the extend should be able to fail.
+ERR
+a.bar {a: b}
+b.foo { extend .bar}
+SCSS
+ end
+
+ def test_extend_succeeds_when_one_extension_fails_but_others_dont
+ assert_equal(<<CSS, render(<<SCSS))
+a.bar {
+ a: b; }
+
+.bar, b.foo {
+ c: d; }
+CSS
+a.bar {a: b}
+.bar {c: d}
+b.foo { extend .bar}
+SCSS
+ end
+
+ def test_optional_extend_succeeds_when_extendee_doesnt_exist
+ assert_equal("", render(<<SCSS))
+.foo { extend .bar !optional}
+SCSS
+ end
+
+ def test_optional_extend_succeeds_when_extension_fails
+ assert_equal(<<CSS, render(<<SCSS))
+a.bar {
+ a: b; }
+CSS
+a.bar {a: b}
+b.foo { extend .bar !optional}
+SCSS
+ end
+
+ # Regression Tests
+
+ def test_extend_parent_selector_suffix
+ assert_equal <<CSS, render(<<SCSS)
+.a-b, .c {
+ x: y; }
+CSS
+.a {&-b {x: y}}
+.c { extend .a-b}
+SCSS
+ end
+
+ def test_pseudo_element_superselector
+ # Pseudo-elements shouldn't be removed in superselector calculations.
+ assert_equal <<CSS, render(<<SCSS)
+a#bar, a#bar::fblthp {
+ a: b; }
+CSS
+%x#bar {a: b} // Add an id to make the results have high specificity
+%y, %y::fblthp { extend %x}
+a { extend %y}
+SCSS
+
+ # Pseudo-classes can be removed when the second law allows.
+ assert_equal <<CSS, render(<<SCSS)
+a#bar {
+ a: b; }
+CSS
+%x#bar {a: b}
+%y, %y:fblthp { extend %x}
+a { extend %y}
+SCSS
+
+ # A few pseudo-elements can be written as pseudo-elements for historical
+ # reasons. See http://www.w3.org/TR/selectors4/#pseudo-elements.
+ %w[first-line first-letter before after].each do |pseudo|
+ assert_equal <<CSS, render(<<SCSS)
+a#bar, a#bar:#{pseudo} {
+ a: b; }
+CSS
+%x#bar {a: b}
+%y, %y:#{pseudo} { extend %x}
+a { extend %y}
+SCSS
+ end
+ end
+
+ def test_multiple_source_redundancy_elimination
+ assert_equal <<CSS, render(<<SCSS)
+.test-case, .test-case:active {
+ color: red; }
+
+.test-case:hover {
+ color: green; }
+CSS
+%default-color {color: red}
+%alt-color {color: green}
+
+%default-style {
+ @extend %default-color;
+ &:hover { extend %alt-color}
+ &:active { extend %default-color}
+}
+
+.test-case { extend %default-style}
+SCSS
+ end
+
+ def test_nested_sibling_extend
+ assert_equal <<CSS, render(<<SCSS)
+.parent .bar, .parent .foo {
+ width: 2000px; }
+CSS
+.foo { extend .bar}
+
+.parent {
+ .bar {
+ width: 2000px;
+ }
+ .foo {
+ @extend .bar
+ }
+}
+SCSS
+ end
+
+ def test_parent_and_sibling_extend
+ assert_equal <<CSS, render(<<SCSS)
+.parent1 .parent2 .child1.child2, .parent2 .parent1 .child1.child2 {
+ c: d; }
+CSS
+%foo %bar%baz {c: d}
+
+.parent1 {
+ @extend %foo;
+ .child1 { extend %bar}
+}
+
+.parent2 {
+ @extend %foo;
+ .child2 { extend %baz}
+}
+SCSS
+ end
+
+ def test_nested_extend_specificity
+ assert_equal <<CSS, render(<<SCSS)
+a :b, a :b:c {
+ a: b; }
+CSS
+%foo {a: b}
+
+a {
+ :b { extend %foo}
+ :b:c { extend %foo}
+}
+SCSS
+ end
+
+ def test_nested_double_extend_optimization
+ assert_equal <<CSS, render(<<SCSS)
+.parent1 .child {
+ a: b; }
+CSS
+%foo %bar {
+ a: b;
+}
+
+.parent1 {
+ @extend %foo;
+
+ .child {
+ @extend %bar;
+ }
+}
+
+.parent2 {
+ @extend %foo;
+}
+SCSS
+ end
+
+ def test_extend_in_double_nested_media_query
+ assert_equal <<CSS, render(<<SCSS)
+ media all and (orientation: landscape) {
+ .bar {
+ color: blue; } }
+CSS
+ media all {
+ @media (orientation: landscape) {
+ %foo {color: blue}
+ .bar { extend %foo}
+ }
+}
+SCSS
+ end
+
+ def test_partially_failed_extend
+ assert_no_warning {assert_equal(<<CSS, render(<<SCSS))}
+.rc, test {
+ color: white; }
+
+.prices span.pill span.rc {
+ color: red; }
+CSS
+test { @extend .rc; }
+.rc {color: white;}
+.prices span.pill span.rc {color: red;}
+SCSS
+ end
+
+ def test_newline_near_combinator
+ assert_equal <<CSS, render(<<SCSS)
+.a +
+.b x, .a +
+.b .c y, .c .a +
+.b y {
+ a: b; }
+CSS
+.a +
+.b x {a: b}
+.c y { extend x}
+SCSS
+ end
+
+ def test_duplicated_selector_with_newlines
+ assert_equal(<<CSS, render(<<SCSS))
+.example-1-1,
+.example-1-2,
+.my-page-1 .my-module-1-1,
+.example-1-3 {
+ a: b; }
+CSS
+.example-1-1,
+.example-1-2,
+.example-1-3 {
+ a: b;
+}
+
+.my-page-1 .my-module-1-1 { extend .example-1-2}
+SCSS
+ end
+
+ def test_nested_selector_with_child_selector_hack_extendee
+ assert_extends '> .foo', 'foo bar { extend .foo}', '> .foo, > foo bar'
+ end
+
+ def test_nested_selector_with_child_selector_hack_extender
+ assert_extends '.foo .bar', '> foo bar { extend .bar}', '.foo .bar, > .foo foo bar, > foo .foo bar'
+ end
+
+ def test_nested_selector_with_child_selector_hack_extender_and_extendee
+ assert_extends '> .foo', '> foo bar { extend .foo}', '> .foo, > foo bar'
+ end
+
+ def test_nested_selector_with_child_selector_hack_extender_and_sibling_selector_extendee
+ assert_extends '~ .foo', '> foo bar { extend .foo}', '~ .foo'
+ end
+
+ def test_nested_selector_with_child_selector_hack_extender_and_extendee_and_newline
+ assert_equal <<CSS, render(<<SCSS)
+> .foo, > flip,
+> foo bar {
+ a: b; }
+CSS
+> .foo {a: b}
+flip,
+> foo bar { extend .foo}
+SCSS
+ end
+
+ def test_extended_parent_and_child_redundancy_elimination
+ assert_equal <<CSS, render(<<SCSS)
+a b, d b, a c, d c {
+ a: b; }
+CSS
+a {
+ b {a: b}
+ c { extend b}
+}
+d { extend a}
+SCSS
+ end
+
+ def test_extend_redundancy_elimination_when_it_would_reduce_specificity
+ assert_extends 'a', 'a.foo { extend a}', 'a, a.foo'
+ end
+
+ def test_extend_redundancy_elimination_when_it_would_preserve_specificity
+ assert_extends '.bar a', 'a.foo { extend a}', '.bar a'
+ end
+
+ def test_extend_redundancy_elimination_never_eliminates_base_selector
+ assert_extends 'a.foo', '.foo { extend a}', 'a.foo, .foo'
+ end
+
+ def test_extend_cross_branch_redundancy_elimination
+ assert_equal <<CSS, render(<<SCSS)
+.a .c .d, .b .c .a .d {
+ a: b; }
+CSS
+%x .c %y {a: b}
+.a, .b { extend %x}
+.a .d { extend %y}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.e .a .c .d, .a .c .e .d, .e .b .c .a .d, .b .c .a .e .d {
+ a: b; }
+CSS
+.e %z {a: b}
+%x .c %y { extend %z}
+.a, .b { extend %x}
+.a .d { extend %y}
+SCSS
+ end
+
+ private
+
+ def assert_extend_doesnt_match(extender, target, reason, line, syntax = :scss)
+ message = "\"#{extender}\" failed to @extend \"#{target}\"."
+ reason =
+ if reason == :not_found
+ "The selector \"#{target}\" was not found."
+ else
+ "No selectors matching \"#{target}\" could be unified with \"#{extender}\"."
+ end
+
+ assert_raise_message(Sass::SyntaxError, <<ERR) {yield}
+#{message}
+#{reason}
+Use "@extend #{target} !optional" if the extend should be able to fail.
+ERR
+ end
+
+ def assert_unification(selector, extension, unified, nested = true)
+ # Do some trickery so the first law of extend doesn't get in our way.
+ assert_extends(
+ "%-a #{selector}",
+ extension + " -a { extend %-a}",
+ unified.split(', ').map {|s| "-a #{s}"}.join(', '))
+ end
+
+ def assert_specificity_equals(sel1, sel2)
+ assert_specificity_gte(sel1, sel2)
+ assert_specificity_gte(sel2, sel1)
+ end
+
+ def assert_specificity_gte(sel1, sel2)
+ assert_equal <<CSS, render(<<SCSS)
+#{sel1} .a {
+ a: b; }
+CSS
+#{sel1} %-a {a: b}
+.a { extend %-a}
+#{sel2}.a { extend %-a}
+SCSS
+ end
+
+ def render_unification(selector, extension)
+ render_extends(
+ "%-a #{selector}",
+ extension + " -a { extend %-a}")
+ end
+
+ def assert_extends(selector, extension, result)
+ assert_equal <<CSS, render_extends(selector, extension)
+#{result} {
+ a: b; }
+CSS
+ end
+
+ def assert_extends_to_nothing(selector, extension)
+ assert_equal '', render_extends(selector, extension)
+ end
+
+ def render_extends(selector, extension)
+ render(<<SCSS)
+#{selector} {a: b}
+#{extension}
+SCSS
+ end
+
+ def render(sass, options = {})
+ options = {:syntax => :scss}.merge(options)
+ munge_filename options
+ Sass::Engine.new(sass, options).render
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.css
b/backends/css/gems/sass-3.4.9/test/sass/fixtures/test_staleness_check_across_importers.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.css
rename to backends/css/gems/sass-3.4.9/test/sass/fixtures/test_staleness_check_across_importers.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.scss
b/backends/css/gems/sass-3.4.9/test/sass/fixtures/test_staleness_check_across_importers.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.scss
rename to backends/css/gems/sass-3.4.9/test/sass/fixtures/test_staleness_check_across_importers.scss
diff --git a/backends/css/gems/sass-3.4.9/test/sass/functions_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/functions_test.rb
new file mode 100755
index 0000000..b13addb
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/functions_test.rb
@@ -0,0 +1,1954 @@
+#!/usr/bin/env ruby
+require 'minitest/autorun'
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+require 'sass/script'
+require 'mock_importer'
+
+module Sass::Script::Functions
+ def no_kw_args
+ Sass::Script::Value::String.new("no-kw-args")
+ end
+
+ def only_var_args(*args)
+ Sass::Script::Value::String.new("only-var-args("+args.map{|a|
a.plus(Sass::Script::Value::Number.new(1)).to_s }.join(", ")+")")
+ end
+ declare :only_var_args, [], :var_args => true
+
+ def only_kw_args(kwargs)
+ Sass::Script::Value::String.new("only-kw-args(" + kwargs.keys.map {|a| a.to_s}.sort.join(", ") + ")")
+ end
+ declare :only_kw_args, [], :var_kwargs => true
+
+ def deprecated_arg_fn(arg1, arg2, arg3 = nil)
+ Sass::Script::Value::List.new([arg1, arg2, arg3 || Sass::Script::Value::Null.new], :space)
+ end
+ declare :deprecated_arg_fn, [:arg1, :arg2, :arg3], :deprecated => [:arg_1, :arg_2, :arg3]
+ declare :deprecated_arg_fn, [:arg1, :arg2], :deprecated => [:arg_1, :arg_2]
+end
+
+module Sass::Script::Functions::UserFunctions
+ def call_options_on_new_value
+ str = Sass::Script::Value::String.new("foo")
+ str.options[:foo]
+ str
+ end
+
+ def user_defined
+ Sass::Script::Value::String.new("I'm a user-defined string!")
+ end
+
+ def _preceding_underscore
+ Sass::Script::Value::String.new("I'm another user-defined string!")
+ end
+
+ def fetch_the_variable
+ environment.var('variable')
+ end
+end
+
+module Sass::Script::Functions
+ include Sass::Script::Functions::UserFunctions
+end
+
+class SassFunctionTest < MiniTest::Test
+ # Tests taken from:
+ # http://www.w3.org/Style/CSS/Test/CSS3/Color/20070927/html4/t040204-hsl-h-rotating-b.htm
+ # http://www.w3.org/Style/CSS/Test/CSS3/Color/20070927/html4/t040204-hsl-values-b.htm
+ File.read(File.dirname(__FILE__) + "/data/hsl-rgb.txt").split("\n\n").each do |chunk|
+ hsls, rgbs = chunk.strip.split("====")
+ hsls.strip.split("\n").zip(rgbs.strip.split("\n")) do |hsl, rgb|
+ hsl_method = "test_hsl: #{hsl} = #{rgb}"
+ unless method_defined?(hsl_method)
+ define_method(hsl_method) do
+ assert_equal(evaluate(rgb), evaluate(hsl))
+ end
+ end
+
+ rgb_to_hsl_method = "test_rgb_to_hsl: #{rgb} = #{hsl}"
+ unless method_defined?(rgb_to_hsl_method)
+ define_method(rgb_to_hsl_method) do
+ rgb_color = perform(rgb)
+ hsl_color = perform(hsl)
+
+ white = hsl_color.lightness == 100
+ black = hsl_color.lightness == 0
+ grayscale = white || black || hsl_color.saturation == 0
+
+ assert_in_delta(hsl_color.hue, rgb_color.hue, 0.0001,
+ "Hues should be equal") unless grayscale
+ assert_in_delta(hsl_color.saturation, rgb_color.saturation, 0.0001,
+ "Saturations should be equal") unless white || black
+ assert_in_delta(hsl_color.lightness, rgb_color.lightness, 0.0001,
+ "Lightnesses should be equal")
+ end
+ end
+ end
+ end
+
+ def test_hsl_kwargs
+ assert_equal "#33cccc", evaluate("hsl($hue: 180, $saturation: 60%, $lightness: 50%)")
+ end
+
+ def test_hsl_clamps_bounds
+ assert_equal("#1f1f1f", evaluate("hsl(10, -114, 12)"))
+ assert_equal("white", evaluate("hsl(10, 10, 256%)"))
+ end
+
+ def test_hsl_checks_types
+ assert_error_message("$hue: \"foo\" is not a number for `hsl'", "hsl(\"foo\", 10, 12)");
+ assert_error_message("$saturation: \"foo\" is not a number for `hsl'", "hsl(10, \"foo\", 12)");
+ assert_error_message("$lightness: \"foo\" is not a number for `hsl'", "hsl(10, 10, \"foo\")");
+ end
+
+ def test_hsla
+ assert_equal "rgba(51, 204, 204, 0.4)", evaluate("hsla(180, 60%, 50%, 0.4)")
+ assert_equal "#33cccc", evaluate("hsla(180, 60%, 50%, 1)")
+ assert_equal "rgba(51, 204, 204, 0)", evaluate("hsla(180, 60%, 50%, 0)")
+ assert_equal "rgba(51, 204, 204, 0.4)", evaluate("hsla($hue: 180, $saturation: 60%, $lightness: 50%,
$alpha: 0.4)")
+ end
+
+ def test_hsla_clamps_bounds
+ assert_equal("#1f1f1f", evaluate("hsla(10, -114, 12, 1)"))
+ assert_equal("rgba(255, 255, 255, 0)", evaluate("hsla(10, 10, 256%, 0)"))
+ assert_equal("rgba(28, 24, 23, 0)", evaluate("hsla(10, 10, 10, -0.1)"))
+ assert_equal("#1c1817", evaluate("hsla(10, 10, 10, 1.1)"))
+ end
+
+ def test_hsla_checks_types
+ assert_error_message("$hue: \"foo\" is not a number for `hsla'", "hsla(\"foo\", 10, 12, 0.3)");
+ assert_error_message("$saturation: \"foo\" is not a number for `hsla'", "hsla(10, \"foo\", 12, 0)");
+ assert_error_message("$lightness: \"foo\" is not a number for `hsla'", "hsla(10, 10, \"foo\", 1)");
+ assert_error_message("$alpha: \"foo\" is not a number for `hsla'", "hsla(10, 10, 10, \"foo\")");
+ end
+
+ def test_hsla_percent_warning
+ assert_warning(<<WARNING) {evaluate("hsla(180, 60%, 50%, 40%)")}
+DEPRECATION WARNING: Passing a percentage as the alpha value to hsla() will be
+interpreted differently in future versions of Sass. For now, use 40 instead.
+WARNING
+ end
+
+ def test_hsla_unit_warning
+ assert_warning(<<WARNING) {evaluate("hsla(180, 60%, 50%, 40em)")}
+DEPRECATION WARNING: Passing a number with units as the alpha value to hsla() is
+deprecated and will be an error in future versions of Sass. Use 40 instead.
+WARNING
+ end
+
+ def test_percentage
+ assert_equal("50%", evaluate("percentage(.5)"))
+ assert_equal("100%", evaluate("percentage(1)"))
+ assert_equal("25%", evaluate("percentage(25px / 100px)"))
+ assert_equal("50%", evaluate("percentage($number: 0.5)"))
+ end
+
+ def test_percentage_checks_types
+ assert_error_message("$number: 25px is not a unitless number for `percentage'", "percentage(25px)")
+ assert_error_message("$number: #cccccc is not a unitless number for `percentage'", "percentage(#ccc)")
+ assert_error_message("$number: \"string\" is not a unitless number for `percentage'",
%Q{percentage("string")})
+ end
+
+ def test_round
+ assert_equal("5", evaluate("round(4.8)"))
+ assert_equal("5px", evaluate("round(4.8px)"))
+ assert_equal("5px", evaluate("round(5.49px)"))
+ assert_equal("5px", evaluate("round($number: 5.49px)"))
+ end
+
+ def test_round_checks_types
+ assert_error_message("$value: #cccccc is not a number for `round'", "round(#ccc)")
+ end
+
+ def test_floor
+ assert_equal("4", evaluate("floor(4.8)"))
+ assert_equal("4px", evaluate("floor(4.8px)"))
+ assert_equal("4px", evaluate("floor($number: 4.8px)"))
+ end
+
+ def test_floor_checks_types
+ assert_error_message("$value: \"foo\" is not a number for `floor'", "floor(\"foo\")")
+ end
+
+ def test_ceil
+ assert_equal("5", evaluate("ceil(4.1)"))
+ assert_equal("5px", evaluate("ceil(4.8px)"))
+ assert_equal("5px", evaluate("ceil($number: 4.8px)"))
+ end
+
+ def test_ceil_checks_types
+ assert_error_message("$value: \"a\" is not a number for `ceil'", "ceil(\"a\")")
+ end
+
+ def test_abs
+ assert_equal("5", evaluate("abs(-5)"))
+ assert_equal("5px", evaluate("abs(-5px)"))
+ assert_equal("5", evaluate("abs(5)"))
+ assert_equal("5px", evaluate("abs(5px)"))
+ assert_equal("5px", evaluate("abs($number: 5px)"))
+ end
+
+ def test_abs_checks_types
+ assert_error_message("$value: #aaaaaa is not a number for `abs'", "abs(#aaa)")
+ end
+
+ def test_min
+ assert_equal("1", evaluate("min(1, 2, 3)"))
+ assert_equal("1", evaluate("min(3px, 2px, 1)"))
+ assert_equal("4em", evaluate("min(4em)"))
+ assert_equal("10cm", evaluate("min(10cm, 6in)"))
+
+ assert_error_message("#aaaaaa is not a number for `min'", "min(#aaa)")
+ assert_error_message("Incompatible units: 'px' and 'em'.", "min(3em, 4em, 1px)")
+ end
+
+ def test_max
+ assert_equal("3", evaluate("max(1, 2, 3)"))
+ assert_equal("3", evaluate("max(3, 2px, 1px)"))
+ assert_equal("4em", evaluate("max(4em)"))
+ assert_equal("6in", evaluate("max(10cm, 6in)"))
+
+ assert_error_message("#aaaaaa is not a number for `max'", "max(#aaa)")
+ assert_error_message("Incompatible units: 'px' and 'em'.", "max(3em, 4em, 1px)")
+ end
+
+ def test_rgb
+ assert_equal("#123456", evaluate("rgb(18, 52, 86)"))
+ assert_equal("#beaded", evaluate("rgb(190, 173, 237)"))
+ assert_equal("springgreen", evaluate("rgb(0, 255, 127)"))
+ assert_equal("springgreen", evaluate("rgb($red: 0, $green: 255, $blue: 127)"))
+ end
+
+ def test_rgb_percent
+ assert_equal("#123456", evaluate("rgb(7.1%, 20.4%, 34%)"))
+ assert_equal("#beaded", evaluate("rgb(74.7%, 173, 93%)"))
+ assert_equal("#beaded", evaluate("rgb(190, 68%, 237)"))
+ assert_equal("springgreen", evaluate("rgb(0%, 100%, 50%)"))
+ end
+
+ def test_rgb_clamps_bounds
+ assert_equal("#ff0101", evaluate("rgb(256, 1, 1)"))
+ assert_equal("#01ff01", evaluate("rgb(1, 256, 1)"))
+ assert_equal("#0101ff", evaluate("rgb(1, 1, 256)"))
+ assert_equal("#01ffff", evaluate("rgb(1, 256, 257)"))
+ assert_equal("#000101", evaluate("rgb(-1, 1, 1)"))
+ end
+
+ def test_rgb_clamps_percent_bounds
+ assert_equal("red", evaluate("rgb(100.1%, 0, 0)"))
+ assert_equal("black", evaluate("rgb(0, -0.1%, 0)"))
+ assert_equal("blue", evaluate("rgb(0, 0, 101%)"))
+ end
+
+ def test_rgb_tests_types
+ assert_error_message("$red: \"foo\" is not a number for `rgb'", "rgb(\"foo\", 10, 12)");
+ assert_error_message("$green: \"foo\" is not a number for `rgb'", "rgb(10, \"foo\", 12)");
+ assert_error_message("$blue: \"foo\" is not a number for `rgb'", "rgb(10, 10, \"foo\")");
+ end
+
+ def test_rgba
+ assert_equal("rgba(18, 52, 86, 0.5)", evaluate("rgba(18, 52, 86, 0.5)"))
+ assert_equal("#beaded", evaluate("rgba(190, 173, 237, 1)"))
+ assert_equal("rgba(0, 255, 127, 0)", evaluate("rgba(0, 255, 127, 0)"))
+ assert_equal("rgba(0, 255, 127, 0)", evaluate("rgba($red: 0, $green: 255, $blue: 127, $alpha: 0)"))
+ end
+
+ def test_rgba_clamps_bounds
+ assert_equal("rgba(255, 1, 1, 0.3)", evaluate("rgba(256, 1, 1, 0.3)"))
+ assert_equal("rgba(1, 255, 1, 0.3)", evaluate("rgba(1, 256, 1, 0.3)"))
+ assert_equal("rgba(1, 1, 255, 0.3)", evaluate("rgba(1, 1, 256, 0.3)"))
+ assert_equal("rgba(1, 255, 255, 0.3)", evaluate("rgba(1, 256, 257, 0.3)"))
+ assert_equal("rgba(0, 1, 1, 0.3)", evaluate("rgba(-1, 1, 1, 0.3)"))
+ assert_equal("rgba(1, 1, 1, 0)", evaluate("rgba(1, 1, 1, -0.2)"))
+ assert_equal("#010101", evaluate("rgba(1, 1, 1, 1.2)"))
+ end
+
+ def test_rgba_tests_types
+ assert_error_message("$red: \"foo\" is not a number for `rgba'", "rgba(\"foo\", 10, 12, 0.2)");
+ assert_error_message("$green: \"foo\" is not a number for `rgba'", "rgba(10, \"foo\", 12, 0.1)");
+ assert_error_message("$blue: \"foo\" is not a number for `rgba'", "rgba(10, 10, \"foo\", 0)");
+ assert_error_message("$alpha: \"foo\" is not a number for `rgba'", "rgba(10, 10, 10, \"foo\")");
+ end
+
+ def test_rgba_with_color
+ assert_equal "rgba(16, 32, 48, 0.5)", evaluate("rgba(#102030, 0.5)")
+ assert_equal "rgba(0, 0, 255, 0.5)", evaluate("rgba(blue, 0.5)")
+ assert_equal "rgba(0, 0, 255, 0.5)", evaluate("rgba($color: blue, $alpha: 0.5)")
+ end
+
+ def test_rgba_with_color_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `rgba'", "rgba(\"foo\", 0.2)");
+ assert_error_message("$alpha: \"foo\" is not a number for `rgba'", "rgba(blue, \"foo\")");
+ end
+
+ def test_rgba_tests_num_args
+ assert_error_message("wrong number of arguments (0 for 4) for `rgba'", "rgba()");
+ assert_error_message("wrong number of arguments (1 for 4) for `rgba'", "rgba(blue)");
+ assert_error_message("wrong number of arguments (3 for 4) for `rgba'", "rgba(1, 2, 3)");
+ assert_error_message("wrong number of arguments (5 for 4) for `rgba'", "rgba(1, 2, 3, 0.4, 5)");
+ end
+
+ def test_rgba_percent_warning
+ assert_warning(<<WARNING) {evaluate("rgba(1, 2, 3, 40%)")}
+DEPRECATION WARNING: Passing a percentage as the alpha value to rgba() will be
+interpreted differently in future versions of Sass. For now, use 40 instead.
+WARNING
+ end
+
+ def test_rgba_unit_warning
+ assert_warning(<<WARNING) {evaluate("rgba(1, 2, 3, 40em)")}
+DEPRECATION WARNING: Passing a number with units as the alpha value to rgba() is
+deprecated and will be an error in future versions of Sass. Use 40 instead.
+WARNING
+ end
+
+ def test_red
+ assert_equal("18", evaluate("red(#123456)"))
+ assert_equal("18", evaluate("red($color: #123456)"))
+ end
+
+ def test_red_exception
+ assert_error_message("$color: 12 is not a color for `red'", "red(12)")
+ end
+
+ def test_green
+ assert_equal("52", evaluate("green(#123456)"))
+ assert_equal("52", evaluate("green($color: #123456)"))
+ end
+
+ def test_green_exception
+ assert_error_message("$color: 12 is not a color for `green'", "green(12)")
+ end
+
+ def test_blue
+ assert_equal("86", evaluate("blue(#123456)"))
+ assert_equal("86", evaluate("blue($color: #123456)"))
+ end
+
+ def test_blue_exception
+ assert_error_message("$color: 12 is not a color for `blue'", "blue(12)")
+ end
+
+ def test_hue
+ assert_equal("18deg", evaluate("hue(hsl(18, 50%, 20%))"))
+ assert_equal("18deg", evaluate("hue($color: hsl(18, 50%, 20%))"))
+ end
+
+ def test_hue_exception
+ assert_error_message("$color: 12 is not a color for `hue'", "hue(12)")
+ end
+
+ def test_saturation
+ assert_equal("52%", evaluate("saturation(hsl(20, 52%, 20%))"))
+ assert_equal("52%", evaluate("saturation(hsl(20, 52, 20%))"))
+ assert_equal("52%", evaluate("saturation($color: hsl(20, 52, 20%))"))
+ end
+
+ def test_saturation_exception
+ assert_error_message("$color: 12 is not a color for `saturation'", "saturation(12)")
+ end
+
+ def test_lightness
+ assert_equal("86%", evaluate("lightness(hsl(120, 50%, 86%))"))
+ assert_equal("86%", evaluate("lightness(hsl(120, 50%, 86))"))
+ assert_equal("86%", evaluate("lightness($color: hsl(120, 50%, 86))"))
+ end
+
+ def test_lightness_exception
+ assert_error_message("$color: 12 is not a color for `lightness'", "lightness(12)")
+ end
+
+ def test_alpha
+ assert_equal("1", evaluate("alpha(#123456)"))
+ assert_equal("0.34", evaluate("alpha(rgba(0, 1, 2, 0.34))"))
+ assert_equal("0", evaluate("alpha(hsla(0, 1, 2, 0))"))
+ assert_equal("0", evaluate("alpha($color: hsla(0, 1, 2, 0))"))
+ end
+
+ def test_alpha_exception
+ assert_error_message("$color: 12 is not a color for `alpha'", "alpha(12)")
+ end
+
+ def test_opacity
+ assert_equal("1", evaluate("opacity(#123456)"))
+ assert_equal("0.34", evaluate("opacity(rgba(0, 1, 2, 0.34))"))
+ assert_equal("0", evaluate("opacity(hsla(0, 1, 2, 0))"))
+ assert_equal("0", evaluate("opacity($color: hsla(0, 1, 2, 0))"))
+ assert_equal("opacity(20%)", evaluate("opacity(20%)"))
+ end
+
+ def test_opacity_exception
+ assert_error_message("$color: \"foo\" is not a color for `opacity'", "opacity(foo)")
+ end
+
+ def test_opacify
+ assert_equal("rgba(0, 0, 0, 0.75)", evaluate("opacify(rgba(0, 0, 0, 0.5), 0.25)"))
+ assert_equal("rgba(0, 0, 0, 0.3)", evaluate("opacify(rgba(0, 0, 0, 0.2), 0.1)"))
+ assert_equal("rgba(0, 0, 0, 0.7)", evaluate("fade-in(rgba(0, 0, 0, 0.2), 0.5px)"))
+ assert_equal("black", evaluate("fade_in(rgba(0, 0, 0, 0.2), 0.8)"))
+ assert_equal("black", evaluate("opacify(rgba(0, 0, 0, 0.2), 1)"))
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("opacify(rgba(0, 0, 0, 0.2), 0%)"))
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("opacify($color: rgba(0, 0, 0, 0.2), $amount: 0%)"))
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("fade-in($color: rgba(0, 0, 0, 0.2), $amount: 0%)"))
+ end
+
+ def test_opacify_tests_bounds
+ assert_error_message("Amount -0.001 must be between 0 and 1 for `opacify'",
+ "opacify(rgba(0, 0, 0, 0.2), -0.001)")
+ assert_error_message("Amount 1.001 must be between 0 and 1 for `opacify'",
+ "opacify(rgba(0, 0, 0, 0.2), 1.001)")
+ end
+
+ def test_opacify_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `opacify'", "opacify(\"foo\", 10%)")
+ assert_error_message("$amount: \"foo\" is not a number for `opacify'", "opacify(#fff, \"foo\")")
+ end
+
+ def test_transparentize
+ assert_equal("rgba(0, 0, 0, 0.3)", evaluate("transparentize(rgba(0, 0, 0, 0.5), 0.2)"))
+ assert_equal("rgba(0, 0, 0, 0.1)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 0.1)"))
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("fade-out(rgba(0, 0, 0, 0.5), 0.3px)"))
+ assert_equal("transparent", evaluate("fade_out(rgba(0, 0, 0, 0.2), 0.2)"))
+ assert_equal("transparent", evaluate("transparentize(rgba(0, 0, 0, 0.2), 1)"))
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 0)"))
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("transparentize($color: rgba(0, 0, 0, 0.2), $amount: 0)"))
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("fade-out($color: rgba(0, 0, 0, 0.2), $amount: 0)"))
+ end
+
+ def test_transparentize_tests_bounds
+ assert_error_message("Amount -0.001 must be between 0 and 1 for `transparentize'",
+ "transparentize(rgba(0, 0, 0, 0.2), -0.001)")
+ assert_error_message("Amount 1.001 must be between 0 and 1 for `transparentize'",
+ "transparentize(rgba(0, 0, 0, 0.2), 1.001)")
+ end
+
+ def test_transparentize_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `transparentize'", "transparentize(\"foo\",
10%)")
+ assert_error_message("$amount: \"foo\" is not a number for `transparentize'", "transparentize(#fff,
\"foo\")")
+ end
+
+ def test_lighten
+ assert_equal("#4d4d4d", evaluate("lighten(hsl(0, 0, 0), 30%)"))
+ assert_equal("#ee0000", evaluate("lighten(#800, 20%)"))
+ assert_equal("white", evaluate("lighten(#fff, 20%)"))
+ assert_equal("white", evaluate("lighten(#800, 100%)"))
+ assert_equal("#880000", evaluate("lighten(#800, 0%)"))
+ assert_equal("rgba(238, 0, 0, 0.5)", evaluate("lighten(rgba(136, 0, 0, 0.5), 20%)"))
+ assert_equal("rgba(238, 0, 0, 0.5)", evaluate("lighten($color: rgba(136, 0, 0, 0.5), $amount: 20%)"))
+ end
+
+ def test_lighten_tests_bounds
+ assert_error_message("Amount -0.001 must be between 0% and 100% for `lighten'",
+ "lighten(#123, -0.001)")
+ assert_error_message("Amount 100.001 must be between 0% and 100% for `lighten'",
+ "lighten(#123, 100.001)")
+ end
+
+ def test_lighten_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `lighten'", "lighten(\"foo\", 10%)")
+ assert_error_message("$amount: \"foo\" is not a number for `lighten'", "lighten(#fff, \"foo\")")
+ end
+
+ def test_darken
+ assert_equal("#ff6a00", evaluate("darken(hsl(25, 100, 80), 30%)"))
+ assert_equal("#220000", evaluate("darken(#800, 20%)"))
+ assert_equal("black", evaluate("darken(#000, 20%)"))
+ assert_equal("black", evaluate("darken(#800, 100%)"))
+ assert_equal("#880000", evaluate("darken(#800, 0%)"))
+ assert_equal("rgba(34, 0, 0, 0.5)", evaluate("darken(rgba(136, 0, 0, 0.5), 20%)"))
+ assert_equal("rgba(34, 0, 0, 0.5)", evaluate("darken($color: rgba(136, 0, 0, 0.5), $amount: 20%)"))
+ end
+
+ def test_darken_tests_bounds
+ assert_error_message("Amount -0.001 must be between 0% and 100% for `darken'",
+ "darken(#123, -0.001)")
+ assert_error_message("Amount 100.001 must be between 0% and 100% for `darken'",
+ "darken(#123, 100.001)")
+ end
+
+ def test_darken_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `darken'", "darken(\"foo\", 10%)")
+ assert_error_message("$amount: \"foo\" is not a number for `darken'", "darken(#fff, \"foo\")")
+ end
+
+ def test_saturate
+ assert_equal("#d9f2d9", evaluate("saturate(hsl(120, 30, 90), 20%)"))
+ assert_equal("#9e3f3f", evaluate("saturate(#855, 20%)"))
+ assert_equal("black", evaluate("saturate(#000, 20%)"))
+ assert_equal("white", evaluate("saturate(#fff, 20%)"))
+ assert_equal("#33ff33", evaluate("saturate(#8a8, 100%)"))
+ assert_equal("#88aa88", evaluate("saturate(#8a8, 0%)"))
+ assert_equal("rgba(158, 63, 63, 0.5)", evaluate("saturate(rgba(136, 85, 85, 0.5), 20%)"))
+ assert_equal("rgba(158, 63, 63, 0.5)", evaluate("saturate($color: rgba(136, 85, 85, 0.5), $amount:
20%)"))
+ assert_equal("saturate(50%)", evaluate("saturate(50%)"))
+ end
+
+ def test_saturate_tests_bounds
+ assert_error_message("Amount -0.001 must be between 0% and 100% for `saturate'",
+ "saturate(#123, -0.001)")
+ assert_error_message("Amount 100.001 must be between 0% and 100% for `saturate'",
+ "saturate(#123, 100.001)")
+ end
+
+ def test_saturate_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `saturate'", "saturate(\"foo\", 10%)")
+ assert_error_message("$amount: \"foo\" is not a number for `saturate'", "saturate(#fff, \"foo\")")
+ end
+
+ def test_desaturate
+ assert_equal("#e3e8e3", evaluate("desaturate(hsl(120, 30, 90), 20%)"))
+ assert_equal("#726b6b", evaluate("desaturate(#855, 20%)"))
+ assert_equal("black", evaluate("desaturate(#000, 20%)"))
+ assert_equal("white", evaluate("desaturate(#fff, 20%)"))
+ assert_equal("#999999", evaluate("desaturate(#8a8, 100%)"))
+ assert_equal("#88aa88", evaluate("desaturate(#8a8, 0%)"))
+ assert_equal("rgba(114, 107, 107, 0.5)", evaluate("desaturate(rgba(136, 85, 85, 0.5), 20%)"))
+ assert_equal("rgba(114, 107, 107, 0.5)", evaluate("desaturate($color: rgba(136, 85, 85, 0.5), $amount:
20%)"))
+ end
+
+ def test_desaturate_tests_bounds
+ assert_error_message("Amount -0.001 must be between 0% and 100% for `desaturate'",
+ "desaturate(#123, -0.001)")
+ assert_error_message("Amount 100.001 must be between 0% and 100% for `desaturate'",
+ "desaturate(#123, 100.001)")
+ end
+
+ def test_desaturate_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `desaturate'", "desaturate(\"foo\", 10%)")
+ assert_error_message("$amount: \"foo\" is not a number for `desaturate'", "desaturate(#fff, \"foo\")")
+ end
+
+ def test_adjust_hue
+ assert_equal("#deeded", evaluate("adjust-hue(hsl(120, 30, 90), 60deg)"))
+ assert_equal("#ededde", evaluate("adjust-hue(hsl(120, 30, 90), -60deg)"))
+ assert_equal("#886a11", evaluate("adjust-hue(#811, 45deg)"))
+ assert_equal("black", evaluate("adjust-hue(#000, 45deg)"))
+ assert_equal("white", evaluate("adjust-hue(#fff, 45deg)"))
+ assert_equal("#88aa88", evaluate("adjust-hue(#8a8, 360deg)"))
+ assert_equal("#88aa88", evaluate("adjust-hue(#8a8, 0deg)"))
+ assert_equal("rgba(136, 106, 17, 0.5)", evaluate("adjust-hue(rgba(136, 17, 17, 0.5), 45deg)"))
+ assert_equal("rgba(136, 106, 17, 0.5)", evaluate("adjust-hue($color: rgba(136, 17, 17, 0.5), $degrees:
45deg)"))
+ end
+
+ def test_adjust_hue_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `adjust-hue'", "adjust-hue(\"foo\", 10%)")
+ assert_error_message("$degrees: \"foo\" is not a number for `adjust-hue'", "adjust-hue(#fff, \"foo\")")
+ end
+
+ def test_adjust_color
+ # HSL
+ assert_equal(evaluate("hsl(180, 30, 90)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $hue: 60deg)"))
+ assert_equal(evaluate("hsl(120, 50, 90)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $saturation: 20%)"))
+ assert_equal(evaluate("hsl(120, 30, 60)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $lightness: -30%)"))
+ # RGB
+ assert_equal(evaluate("rgb(15, 20, 30)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $red: 5)"))
+ assert_equal(evaluate("rgb(10, 15, 30)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $green: -5)"))
+ assert_equal(evaluate("rgb(10, 20, 40)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $blue: 10)"))
+ # Alpha
+ assert_equal(evaluate("hsla(120, 30, 90, 0.65)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $alpha: -0.35)"))
+ assert_equal(evaluate("rgba(10, 20, 30, 0.9)"),
+ evaluate("adjust-color(rgba(10, 20, 30, 0.4), $alpha: 0.5)"))
+
+ # HSL composability
+ assert_equal(evaluate("hsl(180, 20, 90)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $hue: 60deg, $saturation: -10%)"))
+ assert_equal(evaluate("hsl(180, 20, 95)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $hue: 60deg, $saturation: -10%, $lightness: 5%)"))
+ assert_equal(evaluate("hsla(120, 20, 95, 0.3)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $saturation: -10%, $lightness: 5%, $alpha: -0.7)"))
+
+ # RGB composability
+ assert_equal(evaluate("rgb(15, 20, 29)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $red: 5, $blue: -1)"))
+ assert_equal(evaluate("rgb(15, 45, 29)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $red: 5, $green: 25, $blue: -1)"))
+ assert_equal(evaluate("rgba(10, 25, 29, 0.7)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $green: 5, $blue: -1, $alpha: -0.3)"))
+
+ # HSL range restriction
+ assert_equal(evaluate("hsl(120, 30, 90)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $hue: 720deg)"))
+ assert_equal(evaluate("hsl(120, 0, 90)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $saturation: -90%)"))
+ assert_equal(evaluate("hsl(120, 30, 100)"),
+ evaluate("adjust-color(hsl(120, 30, 90), $lightness: 30%)"))
+
+ # RGB range restriction
+ assert_equal(evaluate("rgb(255, 20, 30)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $red: 250)"))
+ assert_equal(evaluate("rgb(10, 0, 30)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $green: -30)"))
+ assert_equal(evaluate("rgb(10, 20, 0)"),
+ evaluate("adjust-color(rgb(10, 20, 30), $blue: -40)"))
+ end
+
+ def test_adjust_color_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `adjust-color'", "adjust-color(foo, $hue: 10)")
+ # HSL
+ assert_error_message("$hue: \"foo\" is not a number for `adjust-color'",
+ "adjust-color(blue, $hue: foo)")
+ assert_error_message("$saturation: \"foo\" is not a number for `adjust-color'",
+ "adjust-color(blue, $saturation: foo)")
+ assert_error_message("$lightness: \"foo\" is not a number for `adjust-color'",
+ "adjust-color(blue, $lightness: foo)")
+ # RGB
+ assert_error_message("$red: \"foo\" is not a number for `adjust-color'",
+ "adjust-color(blue, $red: foo)")
+ assert_error_message("$green: \"foo\" is not a number for `adjust-color'",
+ "adjust-color(blue, $green: foo)")
+ assert_error_message("$blue: \"foo\" is not a number for `adjust-color'",
+ "adjust-color(blue, $blue: foo)")
+ # Alpha
+ assert_error_message("$alpha: \"foo\" is not a number for `adjust-color'",
+ "adjust-color(blue, $alpha: foo)")
+ end
+
+ def test_adjust_color_tests_arg_range
+ # HSL
+ assert_error_message("$saturation: Amount 101% must be between -100% and 100% for `adjust-color'",
+ "adjust-color(blue, $saturation: 101%)")
+ assert_error_message("$saturation: Amount -101% must be between -100% and 100% for `adjust-color'",
+ "adjust-color(blue, $saturation: -101%)")
+ assert_error_message("$lightness: Amount 101% must be between -100% and 100% for `adjust-color'",
+ "adjust-color(blue, $lightness: 101%)")
+ assert_error_message("$lightness: Amount -101% must be between -100% and 100% for `adjust-color'",
+ "adjust-color(blue, $lightness: -101%)")
+ # RGB
+ assert_error_message("$red: Amount 256 must be between -255 and 255 for `adjust-color'",
+ "adjust-color(blue, $red: 256)")
+ assert_error_message("$red: Amount -256 must be between -255 and 255 for `adjust-color'",
+ "adjust-color(blue, $red: -256)")
+ assert_error_message("$green: Amount 256 must be between -255 and 255 for `adjust-color'",
+ "adjust-color(blue, $green: 256)")
+ assert_error_message("$green: Amount -256 must be between -255 and 255 for `adjust-color'",
+ "adjust-color(blue, $green: -256)")
+ assert_error_message("$blue: Amount 256 must be between -255 and 255 for `adjust-color'",
+ "adjust-color(blue, $blue: 256)")
+ assert_error_message("$blue: Amount -256 must be between -255 and 255 for `adjust-color'",
+ "adjust-color(blue, $blue: -256)")
+ # Alpha
+ assert_error_message("$alpha: Amount 1.1 must be between -1 and 1 for `adjust-color'",
+ "adjust-color(blue, $alpha: 1.1)")
+ assert_error_message("$alpha: Amount -1.1 must be between -1 and 1 for `adjust-color'",
+ "adjust-color(blue, $alpha: -1.1)")
+ end
+
+ def test_adjust_color_argument_errors
+ assert_error_message("Unknown argument $hoo (260deg) for `adjust-color'",
+ "adjust-color(blue, $hoo: 260deg)")
+ assert_error_message("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'",
+ "adjust-color(blue, $hue: 120deg, $red: 10)");
+ assert_error_message("10px is not a keyword argument for `adjust_color'",
+ "adjust-color(blue, 10px)")
+ assert_error_message("10px is not a keyword argument for `adjust_color'",
+ "adjust-color(blue, 10px, 20px)")
+ assert_error_message("10px is not a keyword argument for `adjust_color'",
+ "adjust-color(blue, 10px, $hue: 180deg)")
+ end
+
+ def test_scale_color
+ # HSL
+ assert_equal(evaluate("hsl(120, 51, 90)"),
+ evaluate("scale-color(hsl(120, 30, 90), $saturation: 30%)"))
+ assert_equal(evaluate("hsl(120, 30, 76.5)"),
+ evaluate("scale-color(hsl(120, 30, 90), $lightness: -15%)"))
+ # RGB
+ assert_equal(evaluate("rgb(157, 20, 30)"),
+ evaluate("scale-color(rgb(10, 20, 30), $red: 60%)"))
+ assert_equal(evaluate("rgb(10, 38.8, 30)"),
+ evaluate("scale-color(rgb(10, 20, 30), $green: 8%)"))
+ assert_equal(evaluate("rgb(10, 20, 20)"),
+ evaluate("scale-color(rgb(10, 20, 30), $blue: -(1/3)*100%)"))
+ # Alpha
+ assert_equal(evaluate("hsla(120, 30, 90, 0.86)"),
+ evaluate("scale-color(hsl(120, 30, 90), $alpha: -14%)"))
+ assert_equal(evaluate("rgba(10, 20, 30, 0.82)"),
+ evaluate("scale-color(rgba(10, 20, 30, 0.8), $alpha: 10%)"))
+
+ # HSL composability
+ assert_equal(evaluate("hsl(120, 51, 76.5)"),
+ evaluate("scale-color(hsl(120, 30, 90), $saturation: 30%, $lightness: -15%)"))
+ assert_equal(evaluate("hsla(120, 51, 90, 0.2)"),
+ evaluate("scale-color(hsl(120, 30, 90), $saturation: 30%, $alpha: -80%)"))
+
+ # RGB composability
+ assert_equal(evaluate("rgb(157, 38.8, 30)"),
+ evaluate("scale-color(rgb(10, 20, 30), $red: 60%, $green: 8%)"))
+ assert_equal(evaluate("rgb(157, 38.8, 20)"),
+ evaluate("scale-color(rgb(10, 20, 30), $red: 60%, $green: 8%, $blue: -(1/3)*100%)"))
+ assert_equal(evaluate("rgba(10, 38.8, 20, 0.55)"),
+ evaluate("scale-color(rgba(10, 20, 30, 0.5), $green: 8%, $blue: -(1/3)*100%, $alpha: 10%)"))
+
+ # Extremes
+ assert_equal(evaluate("hsl(120, 100, 90)"),
+ evaluate("scale-color(hsl(120, 30, 90), $saturation: 100%)"))
+ assert_equal(evaluate("hsl(120, 30, 90)"),
+ evaluate("scale-color(hsl(120, 30, 90), $saturation: 0%)"))
+ assert_equal(evaluate("hsl(120, 0, 90)"),
+ evaluate("scale-color(hsl(120, 30, 90), $saturation: -100%)"))
+ end
+
+ def test_scale_color_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `scale-color'", "scale-color(foo, $red: 10%)")
+ # HSL
+ assert_error_message("$saturation: \"foo\" is not a number for `scale-color'",
+ "scale-color(blue, $saturation: foo)")
+ assert_error_message("$lightness: \"foo\" is not a number for `scale-color'",
+ "scale-color(blue, $lightness: foo)")
+ # RGB
+ assert_error_message("$red: \"foo\" is not a number for `scale-color'",
+ "scale-color(blue, $red: foo)")
+ assert_error_message("$green: \"foo\" is not a number for `scale-color'",
+ "scale-color(blue, $green: foo)")
+ assert_error_message("$blue: \"foo\" is not a number for `scale-color'",
+ "scale-color(blue, $blue: foo)")
+ # Alpha
+ assert_error_message("$alpha: \"foo\" is not a number for `scale-color'",
+ "scale-color(blue, $alpha: foo)")
+ end
+
+ def test_scale_color_argument_errors
+ # Range
+ assert_error_message("$saturation: Amount 101% must be between -100% and 100% for `scale-color'",
+ "scale-color(blue, $saturation: 101%)")
+ assert_error_message("$red: Amount -101% must be between -100% and 100% for `scale-color'",
+ "scale-color(blue, $red: -101%)")
+ assert_error_message("$alpha: Amount -101% must be between -100% and 100% for `scale-color'",
+ "scale-color(blue, $alpha: -101%)")
+
+ # Unit
+ assert_error_message("Expected $saturation to have a unit of % but got 80 for `scale-color'",
+ "scale-color(blue, $saturation: 80)")
+ assert_error_message("Expected $alpha to have a unit of % but got 0.5 for `scale-color'",
+ "scale-color(blue, $alpha: 0.5)")
+
+ # Unknown argument
+ assert_error_message("Unknown argument $hue (80%) for `scale-color'", "scale-color(blue, $hue: 80%)")
+
+ # Non-keyword arg
+ assert_error_message("10px is not a keyword argument for `scale_color'", "scale-color(blue, 10px)")
+
+ # HSL/RGB
+ assert_error_message("Cannot specify HSL and RGB values for a color at the same time for `scale-color'",
+ "scale-color(blue, $lightness: 10%, $red: 20%)");
+ end
+
+ def test_change_color
+ # HSL
+ assert_equal(evaluate("hsl(195, 30, 90)"),
+ evaluate("change-color(hsl(120, 30, 90), $hue: 195deg)"))
+ assert_equal(evaluate("hsl(120, 50, 90)"),
+ evaluate("change-color(hsl(120, 30, 90), $saturation: 50%)"))
+ assert_equal(evaluate("hsl(120, 30, 40)"),
+ evaluate("change-color(hsl(120, 30, 90), $lightness: 40%)"))
+ # RGB
+ assert_equal(evaluate("rgb(123, 20, 30)"),
+ evaluate("change-color(rgb(10, 20, 30), $red: 123)"))
+ assert_equal(evaluate("rgb(10, 234, 30)"),
+ evaluate("change-color(rgb(10, 20, 30), $green: 234)"))
+ assert_equal(evaluate("rgb(10, 20, 198)"),
+ evaluate("change-color(rgb(10, 20, 30), $blue: 198)"))
+ # Alpha
+ assert_equal(evaluate("rgba(10, 20, 30, 0.76)"),
+ evaluate("change-color(rgb(10, 20, 30), $alpha: 0.76)"))
+
+ # HSL composability
+ assert_equal(evaluate("hsl(56, 30, 47)"),
+ evaluate("change-color(hsl(120, 30, 90), $hue: 56deg, $lightness: 47%)"))
+ assert_equal(evaluate("hsla(56, 30, 47, 0.9)"),
+ evaluate("change-color(hsl(120, 30, 90), $hue: 56deg, $lightness: 47%, $alpha: 0.9)"))
+ end
+
+ def test_change_color_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `change-color'", "change-color(foo, $red: 10%)")
+ # HSL
+ assert_error_message("$saturation: \"foo\" is not a number for `change-color'",
+ "change-color(blue, $saturation: foo)")
+ assert_error_message("$lightness: \"foo\" is not a number for `change-color'",
+ "change-color(blue, $lightness: foo)")
+ # RGB
+ assert_error_message("$red: \"foo\" is not a number for `change-color'", "change-color(blue, $red: foo)")
+ assert_error_message("$green: \"foo\" is not a number for `change-color'", "change-color(blue, $green:
foo)")
+ assert_error_message("$blue: \"foo\" is not a number for `change-color'", "change-color(blue, $blue:
foo)")
+ # Alpha
+ assert_error_message("$alpha: \"foo\" is not a number for `change-color'", "change-color(blue, $alpha:
foo)")
+ end
+
+ def test_change_color_argument_errors
+ # Range
+ assert_error_message("Saturation 101% must be between 0% and 100% for `change-color'",
+ "change-color(blue, $saturation: 101%)")
+ assert_error_message("Lightness 101% must be between 0% and 100% for `change-color'",
+ "change-color(blue, $lightness: 101%)")
+ assert_error_message("Red value -1 must be between 0 and 255 for `change-color'",
+ "change-color(blue, $red: -1)")
+ assert_error_message("Green value 256 must be between 0 and 255 for `change-color'",
+ "change-color(blue, $green: 256)")
+ assert_error_message("Blue value 500 must be between 0 and 255 for `change-color'",
+ "change-color(blue, $blue: 500)")
+
+ # Unknown argument
+ assert_error_message("Unknown argument $hoo (80%) for `change-color'", "change-color(blue, $hoo: 80%)")
+
+ # Non-keyword arg
+ assert_error_message("10px is not a keyword argument for `change_color'", "change-color(blue, 10px)")
+
+ # HSL/RGB
+ assert_error_message("Cannot specify HSL and RGB values for a color at the same time for `change-color'",
+ "change-color(blue, $lightness: 10%, $red: 120)");
+ end
+
+ def test_ie_hex_str
+ assert_equal("#FFAA11CC", evaluate('ie-hex-str(#aa11cc)'))
+ assert_equal("#FFAA11CC", evaluate('ie-hex-str(#a1c)'))
+ assert_equal("#FFAA11CC", evaluate('ie-hex-str(#A1c)'))
+ assert_equal("#80FF0000", evaluate('ie-hex-str(rgba(255, 0, 0, 0.5))'))
+ end
+
+ def test_mix
+ assert_equal("#7f007f", evaluate("mix(#f00, #00f)"))
+ assert_equal("#7f7f7f", evaluate("mix(#f00, #0ff)"))
+ assert_equal("#7f9055", evaluate("mix(#f70, #0aa)"))
+ assert_equal("#3f00bf", evaluate("mix(#f00, #00f, 25%)"))
+ assert_equal("rgba(63, 0, 191, 0.75)", evaluate("mix(rgba(255, 0, 0, 0.5), #00f)"))
+ assert_equal("red", evaluate("mix(#f00, #00f, 100%)"))
+ assert_equal("blue", evaluate("mix(#f00, #00f, 0%)"))
+ assert_equal("rgba(255, 0, 0, 0.5)", evaluate("mix(#f00, transparentize(#00f, 1))"))
+ assert_equal("rgba(0, 0, 255, 0.5)", evaluate("mix(transparentize(#f00, 1), #00f)"))
+ assert_equal("red", evaluate("mix(#f00, transparentize(#00f, 1), 100%)"))
+ assert_equal("blue", evaluate("mix(transparentize(#f00, 1), #00f, 0%)"))
+ assert_equal("rgba(0, 0, 255, 0)", evaluate("mix(#f00, transparentize(#00f, 1), 0%)"))
+ assert_equal("rgba(255, 0, 0, 0)", evaluate("mix(transparentize(#f00, 1), #00f, 100%)"))
+ assert_equal("rgba(255, 0, 0, 0)", evaluate("mix($color1: transparentize(#f00, 1), $color2: #00f,
$weight: 100%)"))
+ end
+
+ def test_mix_tests_types
+ assert_error_message("$color1: \"foo\" is not a color for `mix'", "mix(\"foo\", #f00, 10%)")
+ assert_error_message("$color2: \"foo\" is not a color for `mix'", "mix(#f00, \"foo\", 10%)")
+ assert_error_message("$weight: \"foo\" is not a number for `mix'", "mix(#f00, #baf, \"foo\")")
+ end
+
+ def test_mix_tests_bounds
+ assert_error_message("Weight -0.001 must be between 0% and 100% for `mix'",
+ "mix(#123, #456, -0.001)")
+ assert_error_message("Weight 100.001 must be between 0% and 100% for `mix'",
+ "mix(#123, #456, 100.001)")
+ end
+
+ def test_grayscale
+ assert_equal("#bbbbbb", evaluate("grayscale(#abc)"))
+ assert_equal("gray", evaluate("grayscale(#f00)"))
+ assert_equal("gray", evaluate("grayscale(#00f)"))
+ assert_equal("white", evaluate("grayscale(white)"))
+ assert_equal("black", evaluate("grayscale(black)"))
+ assert_equal("black", evaluate("grayscale($color: black)"))
+
+ assert_equal("grayscale(2)", evaluate("grayscale(2)"))
+ assert_equal("grayscale(-5px)", evaluate("grayscale(-5px)"))
+ end
+
+ def tets_grayscale_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `grayscale'", "grayscale(\"foo\")")
+ end
+
+ def test_complement
+ assert_equal("#ccbbaa", evaluate("complement(#abc)"))
+ assert_equal("cyan", evaluate("complement(red)"))
+ assert_equal("red", evaluate("complement(cyan)"))
+ assert_equal("white", evaluate("complement(white)"))
+ assert_equal("black", evaluate("complement(black)"))
+ assert_equal("black", evaluate("complement($color: black)"))
+ end
+
+ def tets_complement_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `complement'", "complement(\"foo\")")
+ end
+
+ def test_invert
+ assert_equal("#112233", evaluate("invert(#edc)"))
+ assert_equal("rgba(245, 235, 225, 0.5)", evaluate("invert(rgba(10, 20, 30, 0.5))"))
+ assert_equal("invert(20%)", evaluate("invert(20%)"))
+ end
+
+ def test_invert_tests_types
+ assert_error_message("$color: \"foo\" is not a color for `invert'", "invert(\"foo\")")
+ end
+
+ def test_unquote
+ assert_equal('foo', evaluate('unquote("foo")'))
+ assert_equal('foo', evaluate('unquote(foo)'))
+ assert_equal('foo', evaluate('unquote($string: foo)'))
+ end
+
+ def test_quote
+ assert_equal('"foo"', evaluate('quote(foo)'))
+ assert_equal('"foo"', evaluate('quote("foo")'))
+ assert_equal('"foo"', evaluate('quote($string: "foo")'))
+ end
+
+ def test_quote_tests_type
+ assert_error_message("$string: #ff0000 is not a string for `quote'", "quote(#f00)")
+ end
+
+ def test_str_length
+ assert_equal('3', evaluate('str-length(foo)'))
+ end
+
+ def test_str_length_requires_a_string
+ assert_error_message("$string: #ff0000 is not a string for `str-length'", "str-length(#f00)")
+ end
+
+ def test_str_insert
+ assert_equal('Xabcd', evaluate('str-insert(abcd, X, 0)'))
+ assert_equal('Xabcd', evaluate('str-insert(abcd, X, 1)'))
+ assert_equal('abcXd', evaluate('str-insert(abcd, X, 4)'))
+ assert_equal('abcdX', evaluate('str-insert(abcd, X, 100)'))
+ assert_equal('Xabcd', evaluate('str-insert(abcd, X, -100)'))
+ assert_equal('aXbcd', evaluate('str-insert(abcd, X, -4)'))
+ assert_equal('abcdX', evaluate('str-insert(abcd, X, -1)'))
+ end
+
+ def test_str_insert_maintains_quote_of_primary_string
+ assert_equal('"Xfoo"', evaluate('str-insert("foo", X, 1)'))
+ assert_equal('"Xfoo"', evaluate('str-insert("foo", "X", 1)'))
+ assert_equal('Xfoo', evaluate('str-insert(foo, "X", 1)'))
+ end
+
+ def test_str_insert_asserts_types
+ assert_error_message("$string: #ff0000 is not a string for `str-insert'", "str-insert(#f00, X, 1)")
+ assert_error_message("$insert: #ff0000 is not a string for `str-insert'", "str-insert(foo, #f00, 1)")
+ assert_error_message("$index: #ff0000 is not a number for `str-insert'", "str-insert(foo, X, #f00)")
+ assert_error_message("Expected $index to be unitless but got 10px for `str-insert'", "str-insert(foo, X,
10px)")
+ end
+
+ def test_str_index
+ assert_equal('1', evaluate('str-index(abcd, a)'))
+ assert_equal('1', evaluate('str-index(abcd, ab)'))
+ assert_equal(Sass::Script::Value::Null.new, perform('str-index(abcd, X)'))
+ assert_equal('3', evaluate('str-index(abcd, c)'))
+ end
+
+ def test_str_index_asserts_types
+ assert_error_message("$string: #ff0000 is not a string for `str-index'", "str-index(#f00, X)")
+ assert_error_message("$substring: #ff0000 is not a string for `str-index'", "str-index(asdf, #f00)")
+ end
+
+ def test_to_lower_case
+ assert_equal('abcd', evaluate('to-lower-case(ABCD)'))
+ assert_equal('"abcd"', evaluate('to-lower-case("ABCD")'))
+ assert_error_message("$string: #ff0000 is not a string for `to-lower-case'", "to-lower-case(#f00)")
+ end
+
+ def test_to_upper_case
+ assert_equal('ABCD', evaluate('to-upper-case(abcd)'))
+ assert_equal('"ABCD"', evaluate('to-upper-case("abcd")'))
+ assert_error_message("$string: #ff0000 is not a string for `to-upper-case'", "to-upper-case(#f00)")
+ end
+
+ def test_str_slice
+ assert_equal('bc', evaluate('str-slice(abcd,2,3)')) # in the middle of the string
+ assert_equal('a', evaluate('str-slice(abcd,1,1)')) # when start = end
+ assert_equal('ab', evaluate('str-slice(abcd,1,2)')) # for completeness
+ assert_equal('abcd', evaluate('str-slice(abcd,1,4)')) # at the end points
+ assert_equal('abcd', evaluate('str-slice(abcd,0,4)')) # when start is before the start of the string
+ assert_equal('', evaluate('str-slice(abcd,1,0)')) # when end is before the start of the string
+ assert_equal('abcd', evaluate('str-slice(abcd,1,100)')) # when end is past the end of the string
+ assert_equal('', evaluate('str-slice(abcd,2,1)')) # when end is before start
+ assert_equal('"bc"', evaluate('str-slice("abcd",2,3)')) # when used with a quoted string
+ assert_equal('bcd', evaluate('str-slice(abcd,2)')) # when end is omitted, you get the remainder of
the string
+ assert_equal('cd', evaluate('str-slice(abcd,-2)')) # when start is negative, it counts from the
beginning
+ assert_equal('bc', evaluate('str-slice(abcd,2,-2)')) # when end is negative it counts in from the end
+ assert_equal('', evaluate('str-slice(abcd,3,-3)')) # when end is negative and comes before the
start
+ assert_equal('bc', evaluate('str-slice(abcd,-3,-2)')) # when both are negative
+ assert_error_message("$string: #ff0000 is not a string for `str-slice'", "str-slice(#f00,2,3)")
+ assert_error_message("$start-at: #ff0000 is not a number for `str-slice'", "str-slice(abcd,#f00,3)")
+ assert_error_message("$end-at: #ff0000 is not a number for `str-slice'", "str-slice(abcd,2,#f00)")
+ assert_error_message("Expected $end-at to be unitless but got 3px for `str-slice'",
"str-slice(abcd,2,3px)")
+ assert_error_message("Expected $start-at to be unitless but got 2px for `str-slice'",
"str-slice(abcd,2px,3)")
+ end
+
+ def test_user_defined_function
+ assert_equal("I'm a user-defined string!", evaluate("user_defined()"))
+ end
+
+ def test_user_defined_function_with_preceding_underscore
+ assert_equal("I'm another user-defined string!", evaluate("_preceding_underscore()"))
+ assert_equal("I'm another user-defined string!", evaluate("-preceding-underscore()"))
+ end
+
+ def test_user_defined_function_using_environment
+ environment = env('variable' => Sass::Script::Value::String.new('The variable'))
+ assert_equal("The variable", evaluate("fetch_the_variable()", environment))
+ end
+
+ def test_options_on_new_values_fails
+ assert_error_message(<<MSG, "call-options-on-new-value()")
+The #options attribute is not set on this Sass::Script::Value::String.
+ This error is probably occurring because #to_s was called
+ on this value within a custom Sass function without first
+ setting the #options attribute.
+MSG
+ end
+
+ def test_type_of
+ assert_equal("string", evaluate("type-of(\"asdf\")"))
+ assert_equal("string", evaluate("type-of(asdf)"))
+ assert_equal("number", evaluate("type-of(1px)"))
+ assert_equal("bool", evaluate("type-of(true)"))
+ assert_equal("color", evaluate("type-of(#fff)"))
+ assert_equal("color", evaluate("type-of($value: #fff)"))
+ assert_equal("null", evaluate("type-of(null)"))
+ assert_equal("list", evaluate("type-of(1 2 3)"))
+ assert_equal("list", evaluate("type-of((1, 2, 3))"))
+ assert_equal("list", evaluate("type-of(())"))
+ assert_equal("map", evaluate("type-of((foo: bar))"))
+ end
+
+ def test_feature_exists
+ assert_raises ArgumentError do
+ Sass.add_feature("my-test-feature")
+ end
+ Sass.add_feature("-my-test-feature")
+ assert_equal("true", evaluate("feature-exists(-my-test-feature)"))
+ assert_equal("false", evaluate("feature-exists(whatisthisidontevenknow)"))
+ assert_equal("true", evaluate("feature-exists($feature: -my-test-feature)"))
+ ensure
+ Sass::Features::KNOWN_FEATURES.delete("-my-test-feature")
+ end
+
+ def test_unit
+ assert_equal(%Q{""}, evaluate("unit(100)"))
+ assert_equal(%Q{"px"}, evaluate("unit(100px)"))
+ assert_equal(%Q{"em*px"}, evaluate("unit(10px * 5em)"))
+ assert_equal(%Q{"em*px"}, evaluate("unit(5em * 10px)"))
+ assert_equal(%Q{"em/rem"}, evaluate("unit(10px * 5em / 30cm / 1rem)"))
+ assert_equal(%Q{"em*vh/cm*rem"}, evaluate("unit(10vh * 5em / 30cm / 1rem)"))
+ assert_equal(%Q{"px"}, evaluate("unit($number: 100px)"))
+ assert_error_message("$number: #ff0000 is not a number for `unit'", "unit(#f00)")
+ end
+
+ def test_unitless
+ assert_equal(%Q{true}, evaluate("unitless(100)"))
+ assert_equal(%Q{false}, evaluate("unitless(100px)"))
+ assert_equal(%Q{false}, evaluate("unitless($number: 100px)"))
+ assert_error_message("$number: #ff0000 is not a number for `unitless'", "unitless(#f00)")
+ end
+
+ def test_comparable
+ assert_equal(%Q{true}, evaluate("comparable(2px, 1px)"))
+ assert_equal(%Q{true}, evaluate("comparable(10cm, 3mm)"))
+ assert_equal(%Q{false}, evaluate("comparable(100px, 3em)"))
+ assert_equal(%Q{false}, evaluate("comparable($number1: 100px, $number2: 3em)"))
+ end
+
+ def test_comparable_checks_types
+ assert_error_message("$number1: #ff0000 is not a number for `comparable'", "comparable(#f00, 1px)")
+ assert_error_message("$number2: #ff0000 is not a number for `comparable'", "comparable(1px, #f00)")
+ end
+
+ def test_length
+ assert_equal("5", evaluate("length(1 2 3 4 5)"))
+ assert_equal("4", evaluate("length((foo, bar, baz, bip))"))
+ assert_equal("3", evaluate("length((foo, bar, baz bip))"))
+ assert_equal("3", evaluate("length((foo, bar, (baz, bip)))"))
+ assert_equal("1", evaluate("length(#f00)"))
+ assert_equal("0", evaluate("length(())"))
+ assert_equal("4", evaluate("length(1 2 () 3)"))
+
+ assert_equal("2", evaluate("length((foo: bar, bar: baz))"))
+ end
+
+ def test_nth
+ assert_equal("1", evaluate("nth(1 2 3, 1)"))
+ assert_equal("2", evaluate("nth(1 2 3, 2)"))
+ assert_equal("3", evaluate("nth(1 2 3, -1)"))
+ assert_equal("1", evaluate("nth(1 2 3, -3)"))
+ assert_equal("3", evaluate("nth((1, 2, 3), 3)"))
+ assert_equal("3", evaluate("nth($list: (1, 2, 3), $n: 3)"))
+ assert_equal("foo", evaluate("nth(foo, 1)"))
+ assert_equal("bar baz", evaluate("nth(foo (bar baz) bang, 2)"))
+ assert_error_message("List index 0 must be a non-zero integer for `nth'", "nth(foo, 0)")
+ assert_error_message("List index is -10 but list is only 1 item long for `nth'", "nth(foo, -10)")
+ assert_error_message("List index 1.5 must be a non-zero integer for `nth'", "nth(foo, 1.5)")
+ assert_error_message("List index is 5 but list is only 4 items long for `nth'", "nth(1 2 3 4, 5)")
+ assert_error_message("List index is 2 but list is only 1 item long for `nth'", "nth(foo, 2)")
+ assert_error_message("List index is 1 but list has no items for `nth'", "nth((), 1)")
+ assert_error_message("$n: \"foo\" is not a number for `nth'", "nth(1 2 3, foo)")
+
+ assert_equal("foo bar", evaluate("nth((foo: bar, bar: baz), 1)"))
+ assert_equal("bar baz", evaluate("nth((foo: bar, bar: baz), 2)"))
+ end
+
+ def test_set_nth
+ assert_equal("a 2 3", evaluate("set-nth(1 2 3, 1, a)"))
+ assert_equal("1 a 3", evaluate("set-nth(1 2 3, 2, a)"))
+ assert_equal("1 2 a", evaluate("set-nth(1 2 3, -1, a)"))
+ assert_equal("a 2 3", evaluate("set-nth(1 2 3, -3, a)"))
+ assert_equal("a 2 3", evaluate("set-nth($list: 1 2 3, $n: -3, $value: a)"))
+ assert_equal("1, 2, a", evaluate("set-nth((1, 2, 3), 3, a)"))
+ assert_equal("a", evaluate("set-nth(foo, 1, a)"))
+ assert_equal("foo, a b, baz", evaluate("set-nth((foo, bar, baz), 2, (a b))"))
+ assert_error_message("List index 0 must be a non-zero integer for `set-nth'", "set-nth(foo, 0, a)")
+ assert_error_message("List index is -10 but list is only 1 item long for `set-nth'", "set-nth(foo, -10,
a)")
+ assert_error_message("List index 1.5 must be a non-zero integer for `set-nth'", "set-nth(foo, 1.5, a)")
+ assert_error_message("List index is 5 but list is only 4 items long for `set-nth'", "set-nth(1 2 3 4, 5,
a)")
+ assert_error_message("List index is 2 but list is only 1 item long for `set-nth'", "set-nth(foo, 2, a)")
+ assert_error_message("List index is 1 but list has no items for `set-nth'", "set-nth((), 1, a)")
+ assert_error_message("$n: \"foo\" is not a number for `set-nth'", "set-nth(1 2 3, foo, a)")
+ end
+
+ def test_join
+ assert_equal("1 2 3", evaluate("join(1 2, 3)"))
+ assert_equal("1 2 3", evaluate("join(1, 2 3)"))
+ assert_equal("1 2 3 4", evaluate("join(1 2, 3 4)"))
+ assert_equal("true", evaluate("(1 2 3 4) == join(1 2, 3 4)"))
+ assert_equal("false", evaluate("(1 2 (3 4)) == join(1 2, 3 4)"))
+ assert_equal("1, 2, 3", evaluate("join((1, 2), 3)"))
+ assert_equal("1, 2, 3", evaluate("join(1, (2, 3))"))
+ assert_equal("1, 2, 3, 4", evaluate("join((1, 2), (3, 4))"))
+ assert_equal("true", evaluate("(1, 2, 3, 4) == join((1, 2), (3, 4))"))
+ assert_equal("false", evaluate("(1, 2, (3, 4)) == join((1, 2), (3, 4))"))
+
+ assert_equal("1 2", evaluate("join(1, 2)"))
+ assert_equal("1 2 3 4", evaluate("join(1 2, (3, 4))"))
+ assert_equal("1, 2, 3, 4", evaluate("join((1, 2), 3 4)"))
+
+ assert_equal("1 2", evaluate("join(1, 2, auto)"))
+ assert_equal("1, 2, 3, 4", evaluate("join(1 2, 3 4, comma)"))
+ assert_equal("1 2 3 4", evaluate("join((1, 2), (3, 4), space)"))
+ assert_equal("1, 2", evaluate("join(1, 2, comma)"))
+
+ assert_equal("1 2", evaluate("join(1 2, ())"))
+ assert_equal("1, 2", evaluate("join((1, 2), ())"))
+ assert_equal("true", evaluate("(1 2) == join(1 2, ())"))
+ assert_equal("true", evaluate("(1, 2) == join((1, 2), ())"))
+ assert_equal("false", evaluate("(1 2 ()) == join(1 2, ())"))
+ assert_equal("false", evaluate("(1, 2, ()) == join((1, 2), ())"))
+
+ assert_equal("1 2", evaluate("join((), 1 2)"))
+ assert_equal("1, 2", evaluate("join((), (1, 2))"))
+ assert_equal("true", evaluate("(1 2) == join((), 1 2)"))
+ assert_equal("true", evaluate("(1, 2) == join((), (1, 2))"))
+ assert_equal("false", evaluate("(1 2 ()) == join((), 1 2)"))
+ assert_equal("false", evaluate("(1, 2, ()) == join((), (1, 2))"))
+
+ assert_error_message("Separator name must be space, comma, or auto for `join'", "join(1, 2, baboon)")
+ assert_error_message("$separator: 12 is not a string for `join'", "join(1, 2, 12)")
+
+ assert_equal("foo bar, bar baz, baz bip, bip bop",
+ perform("join((foo: bar, bar: baz), (baz: bip, bip: bop))").to_sass)
+ assert_equal("(foo bar) (bar baz) (baz bip) (bip bop)",
+ perform("join((foo: bar, bar: baz), (baz: bip, bip: bop), space)").to_sass)
+ assert_equal("foo bar (baz bip) (bip bop)",
+ perform("join(foo bar, (baz: bip, bip: bop))").to_sass)
+ assert_equal("foo bar, bar baz, bip, bop",
+ perform("join((foo: bar, bar: baz), bip bop)").to_sass)
+ assert_equal("baz bip, bip bop",
+ perform("join((), (baz: bip, bip: bop))").to_sass)
+ assert_equal("foo bar, bar baz",
+ perform("join((foo: bar, bar: baz), ())").to_sass)
+ end
+
+ def test_append
+ assert_equal("1 2 3", evaluate("append(1 2, 3)"))
+ assert_equal("1 2 3 4", evaluate("append(1 2, 3 4)"))
+ assert_equal("false", evaluate("(1 2 3 4) == append(1 2, 3 4)"))
+ assert_equal("true", evaluate("(1 2 (3 4)) == append(1 2, 3 4)"))
+ assert_equal("1, 2, 3", evaluate("append((1, 2), 3)"))
+ assert_equal("1, 2, 3, 4", evaluate("append((1, 2), (3, 4))"))
+ assert_equal("false", evaluate("(1, 2, 3, 4) == append((1, 2), (3, 4))"))
+ assert_equal("true", evaluate("(1, 2, (3, 4)) == append((1, 2), (3, 4))"))
+
+ assert_equal("1 2", evaluate("append(1, 2)"))
+ assert_equal("1 2 3, 4", evaluate("append(1 2, (3, 4))"))
+ assert_equal("true", evaluate("(1 2 (3, 4)) == append(1 2, (3, 4))"))
+ assert_equal("1, 2, 3 4", evaluate("append((1, 2), 3 4)"))
+ assert_equal("true", evaluate("(1, 2, 3 4) == append((1, 2), 3 4)"))
+
+ assert_equal("1 2", evaluate("append(1, 2, auto)"))
+ assert_equal("1, 2, 3 4", evaluate("append(1 2, 3 4, comma)"))
+ assert_equal("1 2 3, 4", evaluate("append((1, 2), (3, 4), space)"))
+ assert_equal("1, 2", evaluate("append(1, 2, comma)"))
+
+ assert_equal("1 2", evaluate("append(1 2, ())"))
+ assert_equal("1, 2", evaluate("append((1, 2), ())"))
+ assert_equal("true", evaluate("(1 2 ()) == append(1 2, ())"))
+ assert_equal("true", evaluate("(1, 2, ()) == append((1, 2), ())"))
+
+ assert_equal("1 2", evaluate("append((), 1 2)"))
+ assert_equal("1, 2", evaluate("append((), (1, 2))"))
+ assert_equal("false", evaluate("(1 2) == append((), 1 2)"))
+ assert_equal("true", evaluate("(1 2) == nth(append((), 1 2), 1)"))
+
+ assert_error_message("Separator name must be space, comma, or auto for `append'", "append(1, 2, baboon)")
+ assert_error_message("$separator: 12 is not a string for `append'", "append(1, 2, 12)")
+
+ assert_equal("1 2 (foo: bar)", perform("append(1 2, (foo: bar))").to_sass)
+ assert_equal("foo bar, bar baz, 1", perform("append((foo: bar, bar: baz), 1)").to_sass)
+ assert_equal("foo bar, bar baz, (baz: bip)",
+ perform("append((foo: bar, bar: baz), (baz: bip))").to_sass)
+ end
+
+ def test_zip
+ assert_equal("1 3 5, 2 4 6", evaluate("zip(1 2, 3 4, 5 6)"))
+ assert_equal("1 4 7, 2 5 8", evaluate("zip(1 2 3, 4 5 6, 7 8)"))
+ assert_equal("1 2 3", evaluate("zip(1, 2, 3)"))
+ assert_equal("(foo bar) 1 3, (bar baz) 2 4",
+ perform("zip((foo: bar, bar: baz), 1 2, 3 4)").to_sass)
+ end
+
+ def test_index
+ null = Sass::Script::Value::Null.new
+ assert_equal("1", evaluate("index(1px solid blue, 1px)"))
+ assert_equal("2", evaluate("index(1px solid blue, solid)"))
+ assert_equal("3", evaluate("index(1px solid blue, #00f)"))
+ assert_equal("1", evaluate("index(1px, 1px)"))
+ assert_equal(null, perform("index(1px solid blue, 1em)"))
+ assert_equal(null, perform("index(1px solid blue, notfound)"))
+ assert_equal(null, perform("index(1px, #00f)"))
+
+ assert_equal("1", evaluate("index((foo: bar, bar: baz), (foo bar))"))
+ assert_equal(null, perform("index((foo: bar, bar: baz), (foo: bar))"))
+ end
+
+ def test_list_separator
+ assert_equal("space", evaluate("list-separator(1 2 3 4 5)"))
+ assert_equal("comma", evaluate("list-separator((foo, bar, baz, bip))"))
+ assert_equal("comma", evaluate("list-separator((foo, bar, baz bip))"))
+ assert_equal("comma", evaluate("list-separator((foo, bar, (baz, bip)))"))
+ assert_equal("space", evaluate("list-separator(#f00)"))
+ assert_equal("space", evaluate("list-separator(())"))
+ assert_equal("space", evaluate("list-separator(1 2 () 3)"))
+
+ assert_equal("comma", evaluate("list-separator((foo: bar, bar: baz))"))
+ end
+
+ def test_if
+ assert_equal("1px", evaluate("if(true, 1px, 2px)"))
+ assert_equal("2px", evaluate("if(false, 1px, 2px)"))
+ assert_equal("2px", evaluate("if(null, 1px, 2px)"))
+ assert_equal("1px", evaluate("if(true, 1px, $broken)"))
+ assert_equal("1px", evaluate("if(false, $broken, 1px)"))
+ assert_equal("1px", evaluate("if(false, $if-true: $broken, $if-false: 1px)"))
+ assert_equal("1px", evaluate("if(true, $if-true: 1px, $if-false: $broken)"))
+ assert_equal(<<CSS, render(<<SCSS))
+.if {
+ result: yay; }
+CSS
+.if {
+ $something: yay;
+ result: if(true, $if-true: $something, $if-false: $broken);
+}
+SCSS
+ assert_equal(<<CSS, render(<<SCSS))
+.if {
+ result: 1px; }
+CSS
+.if {
+ $splat: 1px, 2px;
+ result: if(true, $splat...);
+}
+SCSS
+ end
+
+ def test_counter
+ assert_equal("counter(foo)", evaluate("counter(foo)"))
+ assert_equal('counter(item,".")', evaluate('counter(item, ".")'))
+ assert_equal('counter(item,".")', evaluate('counter(item,".")'))
+ end
+
+ def test_counters
+ assert_equal("counters(foo)", evaluate("counters(foo)"))
+ assert_equal('counters(item,".")', evaluate('counters(item, ".")'))
+ assert_equal('counters(item,".")', evaluate('counters(item,".")'))
+ end
+
+ def test_keyword_args_rgb
+ assert_equal(%Q{white}, evaluate("rgb($red: 255, $green: 255, $blue: 255)"))
+ end
+
+ def test_keyword_args_rgba
+ assert_equal(%Q{rgba(255, 255, 255, 0.5)}, evaluate("rgba($red: 255, $green: 255, $blue: 255, $alpha:
0.5)"))
+ assert_equal(%Q{rgba(255, 255, 255, 0.5)}, evaluate("rgba($color: #fff, $alpha: 0.5)"))
+ end
+
+ def test_keyword_args_rgba_with_extra_args
+ evaluate("rgba($red: 255, $green: 255, $blue: 255, $alpha: 0.5, $extra: error)")
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function rgba doesn't have an argument named $extra", e.message)
+ end
+
+ def test_keyword_args_must_have_signature
+ evaluate("no-kw-args($fake: value)")
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function no_kw_args doesn't support keyword arguments", e.message)
+ end
+
+ def test_keyword_args_with_missing_argument
+ evaluate("rgb($red: 255, $green: 255)")
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function rgb requires an argument named $blue", e.message)
+ end
+
+ def test_keyword_args_with_extra_argument
+ evaluate("rgb($red: 255, $green: 255, $blue: 255, $purple: 255)")
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function rgb doesn't have an argument named $purple", e.message)
+ end
+
+ def test_keyword_args_with_positional_and_keyword_argument
+ evaluate("rgb(255, 255, 255, $red: 255)")
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Function rgb was passed argument $red both by position and by name", e.message)
+ end
+
+ def test_keyword_args_with_keyword_before_positional_argument
+ evaluate("rgb($red: 255, 255, 255)")
+ flunk("Expected exception")
+ rescue Sass::SyntaxError => e
+ assert_equal("Positional arguments must come before keyword arguments.", e.message)
+ end
+
+ def test_only_var_args
+ assert_equal "only-var-args(2px, 3px, 4px)", evaluate("only-var-args(1px, 2px, 3px)")
+ end
+
+ def test_only_kw_args
+ assert_equal "only-kw-args(a, b, c)", evaluate("only-kw-args($a: 1, $b: 2, $c: 3)")
+ end
+
+ def test_unique_id
+ last_id, current_id = nil, evaluate("unique-id()")
+
+ 50.times do
+ last_id, current_id = current_id, evaluate("unique-id()")
+ assert_match(/u[a-z0-9]{8}/, current_id)
+ refute_equal last_id, current_id
+ end
+ end
+
+ def test_map_get
+ assert_equal "1", evaluate("map-get((foo: 1, bar: 2), foo)")
+ assert_equal "2", evaluate("map-get((foo: 1, bar: 2), bar)")
+ assert_equal "null", perform("map-get((foo: 1, bar: 2), baz)").to_sass
+ assert_equal "null", perform("map-get((), foo)").to_sass
+ end
+
+ def test_map_get_checks_type
+ assert_error_message("$map: 12 is not a map for `map-get'", "map-get(12, bar)")
+ end
+
+ def test_map_merge
+ assert_equal("(foo: 1, bar: 2, baz: 3)",
+ perform("map-merge((foo: 1, bar: 2), (baz: 3))").to_sass)
+ assert_equal("(foo: 1, bar: 2)",
+ perform("map-merge((), (foo: 1, bar: 2))").to_sass)
+ assert_equal("(foo: 1, bar: 2)",
+ perform("map-merge((foo: 1, bar: 2), ())").to_sass)
+ end
+
+ def test_map_merge_checks_type
+ assert_error_message("$map1: 12 is not a map for `map-merge'", "map-merge(12, (foo: 1))")
+ assert_error_message("$map2: 12 is not a map for `map-merge'", "map-merge((foo: 1), 12)")
+ end
+
+ def test_map_remove
+ assert_equal("(foo: 1, baz: 3)",
+ perform("map-remove((foo: 1, bar: 2, baz: 3), bar)").to_sass)
+ assert_equal("(foo: 1, baz: 3)",
+ perform("map-remove($map: (foo: 1, bar: 2, baz: 3), $key: bar)").to_sass)
+ assert_equal("()",
+ perform("map-remove((foo: 1, bar: 2, baz: 3), foo, bar, baz)").to_sass)
+ assert_equal("()", perform("map-remove((), foo)").to_sass)
+ assert_equal("()", perform("map-remove((), foo, bar)").to_sass)
+ end
+
+ def test_map_remove_checks_type
+ assert_error_message("$map: 12 is not a map for `map-remove'", "map-remove(12, foo)")
+ end
+
+ def test_map_keys
+ assert_equal("foo, bar",
+ perform("map-keys((foo: 1, bar: 2))").to_sass)
+ assert_equal("()", perform("map-keys(())").to_sass)
+ end
+
+ def test_map_keys_checks_type
+ assert_error_message("$map: 12 is not a map for `map-keys'", "map-keys(12)")
+ end
+
+ def test_map_values
+ assert_equal("1, 2", perform("map-values((foo: 1, bar: 2))").to_sass)
+ assert_equal("1, 2, 2",
+ perform("map-values((foo: 1, bar: 2, baz: 2))").to_sass)
+ assert_equal("()", perform("map-values(())").to_sass)
+ end
+
+ def test_map_values_checks_type
+ assert_error_message("$map: 12 is not a map for `map-values'", "map-values(12)")
+ end
+
+ def test_map_has_key
+ assert_equal "true", evaluate("map-has-key((foo: 1, bar: 1), foo)")
+ assert_equal "false", evaluate("map-has-key((foo: 1, bar: 1), baz)")
+ assert_equal "false", evaluate("map-has-key((), foo)")
+ end
+
+ def test_map_has_key_checks_type
+ assert_error_message("$map: 12 is not a map for `map-has-key'", "map-has-key(12, foo)")
+ end
+
+ def test_keywords
+ # The actual functionality is tested in tests where real arglists are passed.
+ assert_error_message("$args: 12 is not a variable argument list for `keywords'", "keywords(12)")
+ assert_error_message(
+ "$args: (1 2 3) is not a variable argument list for `keywords'", "keywords(1 2 3)")
+ end
+
+ def test_partial_list_of_pairs_doesnt_work_as_a_map
+ assert_raises(Sass::SyntaxError) {evaluate("map-get((foo bar, baz bang, bip), 1)")}
+ assert_raises(Sass::SyntaxError) {evaluate("map-get((foo bar, baz bang, bip bap bop), 1)")}
+ assert_raises(Sass::SyntaxError) {evaluate("map-get((foo bar), 1)")}
+ end
+
+ def test_assert_unit
+ ctx = Sass::Script::Functions::EvaluationContext.new(Sass::Environment.new(nil, {}))
+ ctx.assert_unit Sass::Script::Value::Number.new(10, ["px"], []), "px"
+ ctx.assert_unit Sass::Script::Value::Number.new(10, [], []), nil
+
+ begin
+ ctx.assert_unit Sass::Script::Value::Number.new(10, [], []), "px"
+ fail
+ rescue ArgumentError => e
+ assert_equal "Expected 10 to have a unit of px", e.message
+ end
+
+ begin
+ ctx.assert_unit Sass::Script::Value::Number.new(10, ["px"], []), nil
+ fail
+ rescue ArgumentError => e
+ assert_equal "Expected 10px to be unitless", e.message
+ end
+
+ begin
+ ctx.assert_unit Sass::Script::Value::Number.new(10, [], []), "px", "arg"
+ fail
+ rescue ArgumentError => e
+ assert_equal "Expected $arg to have a unit of px but got 10", e.message
+ end
+
+ begin
+ ctx.assert_unit Sass::Script::Value::Number.new(10, ["px"], []), nil, "arg"
+ fail
+ rescue ArgumentError => e
+ assert_equal "Expected $arg to be unitless but got 10px", e.message
+ end
+ end
+
+ def test_call_with_positional_arguments
+ assert_equal evaluate("lighten(blue, 5%)"), evaluate("call(lighten, blue, 5%)")
+ end
+
+ def test_call_with_keyword_arguments
+ assert_equal(
+ evaluate("lighten($color: blue, $amount: 5%)"),
+ evaluate("call(lighten, $color: blue, $amount: 5%)"))
+ end
+
+ def test_call_with_keyword_and_positional_arguments
+ assert_equal(
+ evaluate("lighten(blue, $amount: 5%)"),
+ evaluate("call(lighten, blue, $amount: 5%)"))
+ end
+
+ def test_call_with_dynamic_name
+ assert_equal(
+ evaluate("lighten($color: blue, $amount: 5%)"),
+ evaluate("call($fn, $color: blue, $amount: 5%)",
+ env("fn" => Sass::Script::String.new("lighten"))))
+ end
+
+ def test_call_uses_local_scope
+ assert_equal <<CSS, render(<<SCSS)
+.first-scope {
+ a: local; }
+
+.second-scope {
+ a: global; }
+CSS
+ function foo() { return global}
+
+.first-scope {
+ @function foo() { return local}
+ a: call(foo);
+}
+
+.second-scope {
+ a: call(foo);
+}
+SCSS
+ end
+
+ def test_call_unknown_function
+ assert_equal evaluate("unknown(red, blue)"), evaluate("call(unknown, red, blue)")
+ end
+
+ def test_call_with_non_string_argument
+ assert_error_message "$name: 3px is not a string for `call'", "call(3px)"
+ end
+
+ def test_errors_in_called_function
+ assert_error_message "$color: 3px is not a color for `lighten'", "call(lighten, 3px, 5%)"
+ end
+
+ def test_variable_exists
+ assert_equal <<CSS, render(<<SCSS)
+.test {
+ false: false;
+ true: true;
+ true: true;
+ true: true;
+ true: true; }
+CSS
+$global-var: has-value;
+.test {
+ false: variable-exists(foo);
+ $foo: has-value;
+ true: variable-exists(foo);
+ true: variable-exists($name: foo);
+ true: variable-exists(global-var);
+ true: variable-exists($name: global-var);
+}
+SCSS
+ end
+
+ def test_variable_exists_checks_type
+ assert_error_message("$name: 1 is not a string for `variable-exists'", "variable-exists(1)")
+ end
+
+ def test_global_variable_exists
+ assert_equal <<CSS, render(<<SCSS)
+.test {
+ false: false;
+ false: false;
+ true: true;
+ true: true;
+ false: false;
+ true: true;
+ true: true; }
+CSS
+$g: something;
+$h: null;
+$false: global-variable-exists(foo);
+$true: global-variable-exists(g);
+$named: global-variable-exists($name: g);
+.test {
+ $foo: locally-defined;
+ false: global-variable-exists(foo);
+ false: global-variable-exists(foo2);
+ true: global-variable-exists(g);
+ true: global-variable-exists(h);
+ false: $false;
+ true: $true;
+ true: $named;
+}
+SCSS
+ end
+
+ def test_global_variable_exists_checks_type
+ assert_error_message("$name: 1 is not a string for `global-variable-exists'",
+ "global-variable-exists(1)")
+ end
+
+ def test_function_exists
+ # built-ins
+ assert_equal "true", evaluate("function-exists(lighten)")
+ # with named argument
+ assert_equal "true", evaluate("function-exists($name: lighten)")
+ # user-defined
+ assert_equal <<CSS, render(<<SCSS)
+.test {
+ foo-exists: true;
+ bar-exists: false; }
+CSS
+ function foo() { @return "foo" }
+.test {
+ foo-exists: function-exists(foo);
+ bar-exists: function-exists(bar);
+}
+SCSS
+ end
+
+ def test_function_exists_checks_type
+ assert_error_message("$name: 1 is not a string for `function-exists'", "function-exists(1)")
+ end
+
+ def test_mixin_exists
+ assert_equal "false", evaluate("mixin-exists(foo)")
+ # with named argument
+ assert_equal "false", evaluate("mixin-exists($name: foo)")
+ assert_equal <<CSS, render(<<SCSS)
+.test {
+ foo-exists: true;
+ bar-exists: false; }
+CSS
+ mixin foo() { foo: exists }
+.test {
+ foo-exists: mixin-exists(foo);
+ bar-exists: mixin-exists(bar);
+}
+SCSS
+ end
+
+ def test_mixin_exists_checks_type
+ assert_error_message("$name: 1 is not a string for `mixin-exists'", "mixin-exists(1)")
+ end
+
+ def test_inspect
+ assert_equal "()", evaluate("inspect(())")
+ assert_equal "null", evaluate("inspect(null)")
+ assert_equal "1px null 3px", evaluate("inspect(1px null 3px)")
+ assert_equal "(a: 1, b: 2)", evaluate("inspect((a: 1, b: 2))")
+ end
+
+ def test_random
+ Sass::Script::Functions.random_seed = 1
+ assert_equal "0.41702", evaluate("random()")
+ assert_equal "13", evaluate("random(100)")
+ end
+
+ def test_random_works_without_a_seed
+ if Sass::Script::Functions.instance_variable_defined?("@random_number_generator")
+ Sass::Script::Functions.send(:remove_instance_variable, "@random_number_generator")
+ end
+
+ result = perform("random()")
+ assert_kind_of Sass::Script::Number, result
+ assert result.value >= 0, "Random number was below 0"
+ assert result.value <= 1, "Random number was above 1"
+ end
+
+ def test_random_with_limit_one
+ # Passing 1 as the limit should always return 1, since limit calls return
+ # integers from 1 to the argument, so when the argument is 1, its a predicatble
+ # outcome
+ assert "1", evaluate("random(1)")
+ end
+
+ def test_random_with_limit_too_low
+ assert_error_message("$limit 0 must be greater than or equal to 1 for `random'", "random(0)")
+ end
+
+ def test_random_with_non_integer_limit
+ assert_error_message("Expected $limit to be an integer but got 1.5 for `random'", "random(1.5)")
+ end
+
+ # This could *possibly* fail, but exceedingly unlikely
+ def test_random_is_semi_unique
+ if Sass::Script::Functions.instance_variable_defined?("@random_number_generator")
+ Sass::Script::Functions.send(:remove_instance_variable, "@random_number_generator")
+ end
+ refute_equal evaluate("random()"), evaluate("random()")
+ end
+
+ def test_deprecated_arg_names
+ assert_warning <<WARNING do
+DEPRECATION WARNING: The `$arg-1' argument for `deprecated-arg-fn()' has been renamed to `$arg1'.
+DEPRECATION WARNING: The `$arg-2' argument for `deprecated-arg-fn()' has been renamed to `$arg2'.
+WARNING
+ assert_equal("1 2 3",
+ evaluate("deprecated-arg-fn($arg-1: 1, $arg-2: 2, $arg3: 3)"))
+ end
+
+ assert_warning <<WARNING do
+DEPRECATION WARNING: The `$arg-1' argument for `deprecated-arg-fn()' has been renamed to `$arg1'.
+DEPRECATION WARNING: The `$arg-2' argument for `deprecated-arg-fn()' has been renamed to `$arg2'.
+WARNING
+ assert_equal("1 2",
+ evaluate("deprecated-arg-fn($arg-1: 1, $arg-2: 2)"))
+ end
+
+ assert_warning <<WARNING do
+DEPRECATION WARNING: The `$arg_1' argument for `deprecated-arg-fn()' has been renamed to `$arg1'.
+DEPRECATION WARNING: The `$arg_2' argument for `deprecated-arg-fn()' has been renamed to `$arg2'.
+WARNING
+ assert_equal("1 2",
+ evaluate("deprecated-arg-fn($arg_1: 1, $arg_2: 2)"))
+ end
+ end
+
+ def test_non_deprecated_arg_names
+ assert_equal("1 2 3", evaluate("deprecated-arg-fn($arg1: 1, $arg2: 2, $arg3: 3)"))
+ assert_equal("1 2", evaluate("deprecated-arg-fn($arg1: 1, $arg2: 2)"))
+ end
+
+ ## Selector Functions
+
+ def test_selector_argument_parsing
+ assert_equal("true", evaluate("selector-parse('.foo') == (join(('.foo',), (), space),)"))
+ assert_equal("true", evaluate("selector-parse('.foo .bar') == ('.foo' '.bar',)"))
+ assert_equal("true",
+ evaluate("selector-parse('.foo .bar, .baz .bang') == ('.foo' '.bar', '.baz' '.bang')"))
+
+ assert_equal(".foo %bar", evaluate("selector-parse('.foo %bar')"))
+
+ assert_equal("true",
+ evaluate("selector-parse(('.foo', '.bar')) == selector-parse('.foo, .bar')"))
+ assert_equal("true",
+ evaluate("selector-parse('.foo' '.bar') == selector-parse('.foo .bar')"))
+
+ assert_equal("true", evaluate("selector-parse(('.foo' '.bar', '.baz' '.bang')) == " +
+ "selector-parse('.foo .bar, .baz .bang')"))
+ assert_equal("true", evaluate("selector-parse(('.foo .bar', '.baz .bang')) == " +
+ "selector-parse('.foo .bar, .baz .bang')"))
+
+ # This may throw an error in the future.
+ assert_equal("true", evaluate("selector-parse(('.foo, .bar' '.baz, .bang')) == " +
+ "selector-parse('.foo, .bar .baz, .bang')"))
+ end
+
+ def test_selector_argument_validation
+ assert_error_message("$selector: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-parse'", "selector-parse(12)")
+ assert_error_message("$selector: (((\".foo\" \".bar\"), \".baz\") (\".bang\", \".qux\")) is not a valid
selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-parse'",
+ "selector-parse(('.foo' '.bar', '.baz') ('.bang', '.qux'))")
+ assert_error_message("$selector: \".#\" is not a valid selector: Invalid CSS after \".\": " +
+ "expected class name, was \"#\" for `selector-parse'", "selector-parse('.#')")
+ assert_error_message("$selector: \"&.foo\" is not a valid selector: Invalid CSS after \"\": " +
+ "expected selector, was \"&.foo\" for `selector-parse'", "selector-parse('&.foo')")
+ end
+
+ def test_selector_nest
+ assert_equal(".foo", evaluate("selector-nest('.foo')"))
+ assert_equal(".foo .bar", evaluate("selector-nest('.foo', '.bar')"))
+ assert_equal(".foo .bar .baz", evaluate("selector-nest('.foo', '.bar', '.baz')"))
+ assert_equal(".a .foo .b .bar", evaluate("selector-nest('.a .foo', '.b .bar')"))
+ assert_equal(".foo.bar", evaluate("selector-nest('.foo', '&.bar')"))
+ assert_equal(".baz .foo.bar", evaluate("selector-nest('.foo', '&.bar', '.baz &')"))
+ end
+
+ def test_selector_nest_checks_types
+ assert_error_message("$selectors: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-nest'",
+ "selector-nest(12)")
+ assert_error_message("$selectors: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-nest'",
+ "selector-nest('.foo', 12)")
+ end
+
+ def test_selector_nest_argument_validation
+ assert_error_message("$selectors: At least one selector must be passed for `selector-nest'",
+ "selector-nest()")
+ end
+
+ def test_selector_append
+ assert_equal(".foo.bar", evaluate("selector-append('.foo', '.bar')"))
+ assert_equal(".a .foo.b .bar", evaluate("selector-append('.a .foo', '.b .bar')"))
+ assert_equal(".foo-suffix", evaluate("selector-append('.foo', '-suffix')"))
+ assert_equal(".foo.bar, .foo-suffix", evaluate("selector-append('.foo', '.bar, -suffix')"))
+ assert_equal(".foo--suffix", evaluate("selector-append('.foo', '--suffix')"))
+ assert_equal(".foo.bar, .foo--suffix", evaluate("selector-append('.foo', '.bar, --suffix')"))
+ end
+
+ def test_selector_append_checks_types
+ assert_error_message("$selectors: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-append'",
+ "selector-append(12)")
+ assert_error_message("$selectors: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-append'",
+ "selector-append('.foo', 12)")
+ end
+
+ def test_selector_append_errors
+ assert_error_message("$selectors: At least one selector must be passed for `selector-append'",
+ "selector-append()")
+ assert_error_message("Can't append \"> .bar\" to \".foo\" for `selector-append'",
+ "selector-append('.foo', '> .bar')")
+ assert_error_message("Can't append \"*.bar\" to \".foo\" for `selector-append'",
+ "selector-append('.foo', '*.bar')")
+ assert_error_message("Can't append \"ns|suffix\" to \".foo\" for `selector-append'",
+ "selector-append('.foo', 'ns|suffix')")
+ end
+
+ def test_selector_extend
+ assert_equal(".foo .x, .foo .a .bar, .a .foo .bar",
+ evaluate("selector-extend('.foo .x', '.x', '.a .bar')"))
+ assert_equal(".foo .x, .foo .bang, .x.bar, .bar.bang",
+ evaluate("selector-extend('.foo .x, .x.bar', '.x', '.bang')"))
+ assert_equal(".y .x, .foo .x, .y .foo, .foo .foo",
+ evaluate("selector-extend('.y .x', '.x, .y', '.foo')"))
+ assert_equal(".foo .x, .foo .bar, .foo .bang",
+ evaluate("selector-extend('.foo .x', '.x', '.bar, .bang')"))
+ assert_equal(".foo.x, .foo",
+ evaluate("selector-extend('.foo.x', '.x', '.foo')"))
+ end
+
+ def test_selector_extend_checks_types
+ assert_error_message("$selector: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-extend'",
+ "selector-extend(12, '.foo', '.bar')")
+ assert_error_message("$extendee: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-extend'",
+ "selector-extend('.foo', 12, '.bar')")
+ assert_error_message("$extender: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-extend'",
+ "selector-extend('.foo', '.bar', 12)")
+ end
+
+ def test_selector_extend_errors
+ assert_error_message("Can't extend .bar .baz: can't extend nested selectors for " +
+ "`selector-extend'", "selector-extend('.foo', '.bar .baz', '.bang')")
+ assert_error_message("Can't extend >: invalid selector for `selector-extend'",
+ "selector-extend('.foo', '>', '.bang')")
+ assert_error_message(".bang > can't extend: invalid selector for `selector-extend'",
+ "selector-extend('.foo', '.bar', '.bang >')")
+ end
+
+ def test_selector_replace
+ assert_equal(".bar", evaluate("selector-replace('.foo', '.foo', '.bar')"))
+ assert_equal(".foo.baz", evaluate("selector-replace('.foo.bar', '.bar', '.baz')"))
+ assert_equal(".a .foo.baz", evaluate("selector-replace('.foo.bar', '.bar', '.a .baz')"))
+ assert_equal(".foo.bar", evaluate("selector-replace('.foo.bar', '.baz.bar', '.qux')"))
+ assert_equal(".bar.qux", evaluate("selector-replace('.foo.bar.baz', '.foo.baz', '.qux')"))
+
+ assert_equal(":not(.bar)", evaluate("selector-replace(':not(.foo)', '.foo', '.bar')"))
+ assert_equal(".bar", evaluate("selector-replace(':not(.foo)', ':not(.foo)', '.bar')"))
+ end
+
+ def test_selector_replace_checks_types
+ assert_error_message("$selector: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-replace'",
+ "selector-replace(12, '.foo', '.bar')")
+ assert_error_message("$original: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-replace'",
+ "selector-replace('.foo', 12, '.bar')")
+ assert_error_message("$replacement: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-replace'",
+ "selector-replace('.foo', '.bar', 12)")
+ end
+
+ def test_selector_replace_errors
+ assert_error_message("Can't extend .bar .baz: can't extend nested selectors for " +
+ "`selector-replace'", "selector-replace('.foo', '.bar .baz', '.bang')")
+ assert_error_message("Can't extend >: invalid selector for `selector-replace'",
+ "selector-replace('.foo', '>', '.bang')")
+ assert_error_message(".bang > can't extend: invalid selector for `selector-replace'",
+ "selector-replace('.foo', '.bar', '.bang >')")
+ end
+
+ def test_selector_unify
+ assert_equal(".foo", evaluate("selector-unify('.foo', '.foo')"))
+ assert_equal(".foo.bar", evaluate("selector-unify('.foo', '.bar')"))
+ assert_equal(".foo.bar.baz", evaluate("selector-unify('.foo.bar', '.bar.baz')"))
+ assert_equal(".a .b .foo.bar, .b .a .foo.bar", evaluate("selector-unify('.a .foo', '.b .bar')"))
+ assert_equal(".a .foo.bar", evaluate("selector-unify('.a .foo', '.a .bar')"))
+ assert_equal("", evaluate("selector-unify('p', 'a')"))
+ assert_equal("", evaluate("selector-unify('.foo >', '.bar')"))
+ assert_equal("", evaluate("selector-unify('.foo', '.bar >')"))
+ assert_equal(".foo.baz, .foo.bang, .bar.baz, .bar.bang",
+ evaluate("selector-unify('.foo, .bar', '.baz, .bang')"))
+ end
+
+ def test_selector_unify_checks_types
+ assert_error_message("$selector1: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-unify'",
+ "selector-unify(12, '.foo')")
+ assert_error_message("$selector2: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `selector-unify'",
+ "selector-unify('.foo', 12)")
+ end
+
+ def test_simple_selectors
+ assert_equal('(.foo,)', evaluate("inspect(simple-selectors('.foo'))"))
+ assert_equal('.foo, .bar', evaluate("inspect(simple-selectors('.foo.bar'))"))
+ assert_equal('.foo, .bar, :pseudo("flip, flap")',
+ evaluate("inspect(simple-selectors('.foo.bar:pseudo(\"flip, flap\")'))"))
+ end
+
+ def test_simple_selectors_checks_types
+ assert_error_message("$selector: 12 is not a string for `simple-selectors'",
+ "simple-selectors(12)")
+ end
+
+ def test_simple_selectors_errors
+ assert_error_message("$selector: \".foo .bar\" is not a compound selector for `simple-selectors'",
+ "simple-selectors('.foo .bar')")
+ assert_error_message("$selector: \".foo,.bar\" is not a compound selector for `simple-selectors'",
+ "simple-selectors('.foo,.bar')")
+ assert_error_message("$selector: \".#\" is not a valid selector: Invalid CSS after \".\": " +
+ "expected class name, was \"#\" for `simple-selectors'", "simple-selectors('.#')")
+ end
+
+ def test_is_superselector
+ assert_equal("true", evaluate("is-superselector('.foo', '.foo.bar')"))
+ assert_equal("false", evaluate("is-superselector('.foo.bar', '.foo')"))
+ assert_equal("true", evaluate("is-superselector('.foo', '.foo')"))
+ assert_equal("true", evaluate("is-superselector('.bar', '.foo .bar')"))
+ assert_equal("false", evaluate("is-superselector('.foo .bar', '.bar')"))
+ assert_equal("true", evaluate("is-superselector('.foo .bar', '.foo > .bar')"))
+ assert_equal("false", evaluate("is-superselector('.foo > .bar', '.foo .bar')"))
+ end
+
+ def test_is_superselector_checks_types
+ assert_error_message("$super: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `is-superselector'",
+ "is-superselector(12, '.foo')")
+ assert_error_message("$sub: 12 is not a valid selector: it must be a string,\n" +
+ "a list of strings, or a list of lists of strings for `is-superselector'",
+ "is-superselector('.foo', 12)")
+ end
+
+ ## Regression Tests
+
+ def test_inspect_nested_empty_lists
+ assert_equal "() ()", evaluate("inspect(() ())")
+ end
+
+ def test_saturation_bounds
+ assert_equal "#fbfdff", evaluate("hsl(hue(#fbfdff), saturation(#fbfdff), lightness(#fbfdff))")
+ end
+
+ private
+ def env(hash = {}, parent = nil)
+ env = Sass::Environment.new(parent)
+ hash.each {|k, v| env.set_var(k, v)}
+ env
+ end
+
+ def evaluate(value, environment = env)
+ result = perform(value, environment)
+ assert_kind_of Sass::Script::Value::Base, result
+ return result.to_s
+ end
+
+ def perform(value, environment = env)
+ Sass::Script::Parser.parse(value, 0, 0).perform(environment)
+ end
+
+ def render(sass, options = {})
+ options[:syntax] ||= :scss
+ munge_filename options
+ options[:importer] ||= MockImporter.new
+ Sass::Engine.new(sass, options).render
+ end
+
+ def assert_error_message(message, value)
+ evaluate(value)
+ flunk("Error message expected but not raised: #{message}")
+ rescue Sass::SyntaxError => e
+ assert_equal(message, e.message)
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/importer_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/importer_test.rb
new file mode 100755
index 0000000..9d5890c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/importer_test.rb
@@ -0,0 +1,421 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+require 'mock_importer'
+require 'sass/plugin'
+
+class ImporterTest < MiniTest::Test
+
+ class FruitImporter < Sass::Importers::Base
+ def find(name, context = nil)
+ fruit = parse(name)
+ return unless fruit
+ color = case fruit
+ when "apple"
+ "red"
+ when "orange"
+ "orange"
+ else
+ "blue"
+ end
+ contents = %Q{
+ $#{fruit}-color: #{color} !default;
+ @mixin #{fruit} {
+ color: $#{fruit}-color;
+ }
+ }
+ Sass::Engine.new(contents, :filename => name, :syntax => :scss, :importer => self)
+ end
+
+ def key(name, context)
+ [self.class.name, name]
+ end
+
+ def public_url(name, sourcemap_directory = nil)
+ "http://#{parse(name)}.example.com/style.scss"
+ end
+
+ private
+
+ def parse(name)
+ name[%r{fruits/(\w+)(\.s[ac]ss)?}, 1]
+ end
+ end
+
+ class NoPublicUrlImporter < FruitImporter
+ def public_url(name, sourcemap_directory = nil)
+ nil
+ end
+
+ private
+
+ def parse(name)
+ name[%r{ephemeral/(\w+)(\.s[ac]ss)?}, 1]
+ end
+ end
+
+ # This class proves that you can override the extension scheme for importers
+ class ReversedExtImporter < Sass::Importers::Filesystem
+ def extensions
+ {"sscs" => :scss, "ssas" => :sass}
+ end
+ end
+
+ # This importer maps one import to another import
+ # based on the mappings passed to importer's constructor.
+ class IndirectImporter < Sass::Importers::Base
+ def initialize(mappings, mtimes)
+ @mappings = mappings
+ @mtimes = mtimes
+ end
+ def find_relative(uri, base, options)
+ nil
+ end
+ def find(name, options)
+ if @mappings.has_key?(name)
+ Sass::Engine.new(
+ %Q[ import "#{ mappings[name]}";],
+ options.merge(
+ :filename => name,
+ :syntax => :scss,
+ :importer => self
+ )
+ )
+ end
+ end
+ def mtime(uri, options)
+ @mtimes.fetch(uri, @mtimes.has_key?(uri) ? Time.now : nil)
+ end
+ def key(uri, options)
+ [self.class.name, uri]
+ end
+ def to_s
+ "IndirectImporter(#{ mappings keys join(", ")})"
+ end
+ end
+
+ # This importer maps the import to single class
+ # based on the mappings passed to importer's constructor.
+ class ClassImporter < Sass::Importers::Base
+ def initialize(mappings, mtimes)
+ @mappings = mappings
+ @mtimes = mtimes
+ end
+ def find_relative(uri, base, options)
+ nil
+ end
+ def find(name, options)
+ if @mappings.has_key?(name)
+ Sass::Engine.new(
+ %Q[ #{name}{#{ mappings[name]}}],
+ options.merge(
+ :filename => name,
+ :syntax => :scss,
+ :importer => self
+ )
+ )
+ end
+ end
+ def mtime(uri, options)
+ @mtimes.fetch(uri, @mtimes.has_key?(uri) ? Time.now : nil)
+ end
+ def key(uri, options)
+ [self.class.name, uri]
+ end
+ def to_s
+ "ClassImporter(#{ mappings keys join(", ")})"
+ end
+ end
+
+ def test_can_resolve_generated_imports
+ scss_file = %Q{
+ $pear-color: green;
+ @import "fruits/apple"; @import "fruits/orange"; @import "fruits/pear";
+ .apple { @include apple; }
+ .orange { @include orange; }
+ .pear { @include pear; }
+ }
+ css_file = <<CSS
+.apple { color: red; }
+
+.orange { color: orange; }
+
+.pear { color: green; }
+CSS
+ options = {:style => :compact, :load_paths => [FruitImporter.new], :syntax => :scss}
+ assert_equal css_file, Sass::Engine.new(scss_file, options).render
+ end
+
+ def test_extension_overrides
+ FileUtils.mkdir_p(absolutize("tmp"))
+ open(absolutize("tmp/foo.ssas"), "w") {|f| f.write(".foo\n reversed: true\n")}
+ open(absolutize("tmp/bar.sscs"), "w") {|f| f.write(".bar {reversed: true}\n")}
+ scss_file = %Q{
+ @import "foo", "bar";
+ @import "foo.ssas", "bar.sscs";
+ }
+ css_file = <<CSS
+.foo { reversed: true; }
+
+.bar { reversed: true; }
+
+.foo { reversed: true; }
+
+.bar { reversed: true; }
+CSS
+ options = {:style => :compact, :load_paths => [ReversedExtImporter.new(absolutize("tmp"))], :syntax =>
:scss}
+ assert_equal css_file, Sass::Engine.new(scss_file, options).render
+ ensure
+ FileUtils.rm_rf(absolutize("tmp"))
+ end
+
+ def test_staleness_check_across_importers
+ file_system_importer = Sass::Importers::Filesystem.new(fixture_dir)
+ # Make sure the first import is older
+ indirect_importer = IndirectImporter.new({"apple" => "pear"}, {"apple" => Time.now - 1})
+ # Make css file is newer so the dependencies are the only way for the css file to be out of date.
+ FileUtils.touch(fixture_file("test_staleness_check_across_importers.css"))
+ # Make sure the first import is older
+ class_importer = ClassImporter.new({"pear" => %Q{color: green;}}, {"pear" => Time.now + 1})
+
+ options = {
+ :style => :compact,
+ :filename => fixture_file("test_staleness_check_across_importers.scss"),
+ :importer => file_system_importer,
+ :load_paths => [file_system_importer, indirect_importer, class_importer],
+ :syntax => :scss
+ }
+
+ assert_equal File.read(fixture_file("test_staleness_check_across_importers.css")),
+ Sass::Engine.new(File.read(fixture_file("test_staleness_check_across_importers.scss")),
options).render
+
+ checker = Sass::Plugin::StalenessChecker.new(options)
+
+ assert checker.stylesheet_needs_update?(
+ fixture_file("test_staleness_check_across_importers.css"),
+ fixture_file("test_staleness_check_across_importers.scss"),
+ file_system_importer
+ )
+ end
+
+ def test_source_map_with_only_css_uri_supports_public_url_imports
+ fruit_importer = FruitImporter.new
+
+ options = {
+ :filename => 'fruits/orange',
+ :importer => fruit_importer,
+ :syntax => :scss
+ }
+
+ engine = Sass::Engine.new(<<SCSS, options)
+.orchard {
+ color: blue;
+}
+SCSS
+
+ _, sourcemap = engine.render_with_sourcemap('sourcemap_uri')
+ assert_equal <<JSON.strip, sourcemap.to_json(:css_uri => 'css_uri')
+{
+"version": 3,
+"mappings": "AAAA,QAAS;EACP,KAAK,EAAE,IAAI",
+"sources": ["http://orange.example.com/style.scss"],
+"names": [],
+"file": "css_uri"
+}
+JSON
+ end
+
+ def test_source_map_with_only_css_uri_can_have_no_public_url
+ ephemeral_importer = NoPublicUrlImporter.new
+ mock_importer = MockImporter.new
+ def mock_importer.public_url(name, sourcemap_directory = nil)
+ "source_uri"
+ end
+
+ options = {
+ :filename => filename_for_test,
+ :sourcemap_filename => sourcemap_filename_for_test,
+ :importer => mock_importer,
+ :syntax => :scss,
+ :load_paths => [ephemeral_importer],
+ :cache => false
+ }
+
+ engine = Sass::Engine.new(<<SCSS, options)
+ import "ephemeral/orange";
+.orange {
+ @include orange;
+}
+SCSS
+
+ css_output, sourcemap = engine.render_with_sourcemap('sourcemap_uri')
+ assert_equal <<CSS.strip, css_output.strip
+.orange {
+ color: orange; }
+
+/*# sourceMappingURL=sourcemap_uri */
+CSS
+ map = sourcemap.to_json(:css_uri => 'css_uri')
+ assert_equal <<JSON.strip, map
+{
+"version": 3,
+"mappings": "AACA,OAAQ",
+"sources": ["source_uri"],
+"names": [],
+"file": "css_uri"
+}
+JSON
+ end
+
+ def test_source_map_with_only_css_uri_falls_back_to_file_uris
+ file_system_importer = Sass::Importers::Filesystem.new('.')
+ options = {
+ :filename => filename_for_test(:scss),
+ :sourcemap_filename => sourcemap_filename_for_test,
+ :importer => file_system_importer,
+ :syntax => :scss
+ }
+
+ engine = Sass::Engine.new(<<SCSS, options)
+.foo {a: b}
+SCSS
+
+ _, sourcemap = engine.render_with_sourcemap('http://1.example.com/style.map')
+
+ uri = Sass::Util.file_uri_from_path(Sass::Util.absolute_path(filename_for_test(:scss)))
+ assert_equal <<JSON.strip, sourcemap.to_json(:css_uri => 'css_uri')
+{
+"version": 3,
+"mappings": "AAAA,IAAK;EAAC,CAAC,EAAE,CAAC",
+"sources": ["#{uri}"],
+"names": [],
+"file": "css_uri"
+}
+JSON
+ end
+
+ def test_source_map_with_css_uri_and_css_path_falls_back_to_file_uris
+ file_system_importer = Sass::Importers::Filesystem.new('.')
+ options = {
+ :filename => filename_for_test(:scss),
+ :sourcemap_filename => sourcemap_filename_for_test,
+ :importer => file_system_importer,
+ :syntax => :scss
+ }
+
+ engine = Sass::Engine.new(<<SCSS, options)
+.foo {a: b}
+SCSS
+
+ _, sourcemap = engine.render_with_sourcemap('http://1.example.com/style.map')
+
+ uri = Sass::Util.file_uri_from_path(Sass::Util.absolute_path(filename_for_test(:scss)))
+ assert_equal <<JSON.strip, sourcemap.to_json(:css_uri => 'css_uri', :css_path => 'css_path')
+{
+"version": 3,
+"mappings": "AAAA,IAAK;EAAC,CAAC,EAAE,CAAC",
+"sources": ["#{uri}"],
+"names": [],
+"file": "css_uri"
+}
+JSON
+ end
+
+ def test_source_map_with_css_uri_and_sourcemap_path_supports_filesystem_importer
+ file_system_importer = Sass::Importers::Filesystem.new('.')
+ css_uri = 'css_uri'
+ sourcemap_path = 'map/style.map'
+ options = {
+ :filename => 'sass/style.scss',
+ :sourcemap_filename => sourcemap_path,
+ :importer => file_system_importer,
+ :syntax => :scss
+ }
+
+ engine = Sass::Engine.new(<<SCSS, options)
+.foo {a: b}
+SCSS
+
+ rendered, sourcemap = engine.render_with_sourcemap('http://1.example.com/style.map')
+
+
+ rendered, sourcemap = engine.render_with_sourcemap('http://map.example.com/map/style.map')
+ assert_equal <<JSON.strip, sourcemap.to_json(:css_uri => css_uri, :sourcemap_path => sourcemap_path)
+{
+"version": 3,
+"mappings": "AAAA,IAAK;EAAC,CAAC,EAAE,CAAC",
+"sources": ["../sass/style.scss"],
+"names": [],
+"file": "css_uri"
+}
+JSON
+ end
+
+ def test_source_map_with_css_path_and_sourcemap_path_supports_file_system_importer
+ file_system_importer = Sass::Importers::Filesystem.new('.')
+ sass_path = 'sass/style.scss'
+ css_path = 'static/style.css'
+ sourcemap_path = 'map/style.map'
+ options = {
+ :filename => sass_path,
+ :sourcemap_filename => sourcemap_path,
+ :importer => file_system_importer,
+ :syntax => :scss
+ }
+
+ engine = Sass::Engine.new(<<SCSS, options)
+.foo {a: b}
+SCSS
+
+ _, sourcemap = engine.render_with_sourcemap('http://map.example.com/map/style.map')
+ assert_equal <<JSON.strip, sourcemap.to_json(:css_path => css_path, :sourcemap_path => sourcemap_path)
+{
+"version": 3,
+"mappings": "AAAA,IAAK;EAAC,CAAC,EAAE,CAAC",
+"sources": ["../sass/style.scss"],
+"names": [],
+"file": "../static/style.css"
+}
+JSON
+ end
+
+ def test_render_with_sourcemap_requires_filename
+ file_system_importer = Sass::Importers::Filesystem.new('.')
+ engine = Sass::Engine.new(".foo {a: b}", :syntax => :scss, :importer => file_system_importer)
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE) {engine.render_with_sourcemap('sourcemap_url')}
+Error generating source map: couldn't determine public URL for the source stylesheet.
+ No filename is available so there's nothing for the source map to link to.
+MESSAGE
+ end
+
+ def test_render_with_sourcemap_requires_importer_with_public_url
+ class_importer = ClassImporter.new({"pear" => "color: green;"}, {"pear" => Time.now})
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE) {class_importer.find("pear",
{}).render_with_sourcemap('sourcemap_url')}
+Error generating source map: couldn't determine public URL for "pear".
+ Without a public URL, there's nothing for the source map to link to.
+ Custom importers should define the #public_url method.
+MESSAGE
+ end
+
+ def fixture_dir
+ File.join(File.dirname(__FILE__), "fixtures")
+ end
+
+ def fixture_file(path)
+ File.join(fixture_dir, path)
+ end
+
+ def test_filesystem_importer_eql
+ importer = Sass::Importers::Filesystem.new('.')
+ assert importer.eql?(Sass::Importers::Filesystem.new('.'))
+ assert importer.eql?(ReversedExtImporter.new('.'))
+ assert !importer.eql?(Sass::Importers::Filesystem.new('foo'))
+ assert !importer.eql?(nil)
+ assert !importer.eql?('foo')
+ end
+
+ def test_absolute_files_across_template_locations
+ importer = Sass::Importers::Filesystem.new(absolutize 'templates')
+ refute_nil importer.mtime(absolutize('more_templates/more1.sass'), {})
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/logger_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/logger_test.rb
new file mode 100755
index 0000000..cc6c9aa
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/logger_test.rb
@@ -0,0 +1,58 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require 'pathname'
+
+class LoggerTest < MiniTest::Test
+
+ class InterceptedLogger < Sass::Logger::Base
+
+ attr_accessor :messages
+
+ def initialize(*args)
+ super
+ self.messages = []
+ end
+
+ def reset!
+ self.messages = []
+ end
+
+ def _log(*args)
+ messages << [args]
+ end
+
+ end
+
+ def test_global_sass_logger_instance_exists
+ assert Sass.logger.respond_to?(:warn)
+ end
+
+ def test_log_level_orders
+ logged_levels = {
+ :trace => [ [], [:trace, :debug, :info, :warn, :error]],
+ :debug => [ [:trace], [:debug, :info, :warn, :error]],
+ :info => [ [:trace, :debug], [:info, :warn, :error]],
+ :warn => [ [:trace, :debug, :info], [:warn, :error]],
+ :error => [ [:trace, :debug, :info, :warn], [:error]]
+ }
+ logged_levels.each do |level, (should_not_be_logged, should_be_logged)|
+ logger = Sass::Logger::Base.new(level)
+ should_not_be_logged.each do |should_level|
+ assert !logger.logging_level?(should_level)
+ end
+ should_be_logged.each do |should_level|
+ assert logger.logging_level?(should_level)
+ end
+ end
+ end
+
+ def test_logging_can_be_disabled
+ logger = InterceptedLogger.new
+ logger.error("message #1")
+ assert_equal 1, logger.messages.size
+ logger.reset!
+ logger.disabled = true
+ logger.error("message #2")
+ assert_equal 0, logger.messages.size
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/mock_importer.rb
b/backends/css/gems/sass-3.4.9/test/sass/mock_importer.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/mock_importer.rb
rename to backends/css/gems/sass-3.4.9/test/sass/mock_importer.rb
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_results/more1.css
b/backends/css/gems/sass-3.4.9/test/sass/more_results/more1.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/more_results/more1.css
rename to backends/css/gems/sass-3.4.9/test/sass/more_results/more1.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_results/more1_with_line_comments.css
b/backends/css/gems/sass-3.4.9/test/sass/more_results/more1_with_line_comments.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/more_results/more1_with_line_comments.css
rename to backends/css/gems/sass-3.4.9/test/sass/more_results/more1_with_line_comments.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/more_results/more_import.css
b/backends/css/gems/sass-3.4.9/test/sass/more_results/more_import.css
new file mode 100644
index 0000000..b5dae09
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/more_results/more_import.css
@@ -0,0 +1,29 @@
+ import url(basic.css);
+ import url(../results/complex.css);
+imported { otherconst: hello; myconst: goodbye; pre-mixin: here; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+midrule { inthe: middle; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+#foo { background-color: #baf; }
+
+nonimported { myconst: hello; otherconst: goodbye; post-mixin: here; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_templates/_more_partial.sass
b/backends/css/gems/sass-3.4.9/test/sass/more_templates/_more_partial.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/more_templates/_more_partial.sass
rename to backends/css/gems/sass-3.4.9/test/sass/more_templates/_more_partial.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_templates/more1.sass
b/backends/css/gems/sass-3.4.9/test/sass/more_templates/more1.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/more_templates/more1.sass
rename to backends/css/gems/sass-3.4.9/test/sass/more_templates/more1.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_templates/more_import.sass
b/backends/css/gems/sass-3.4.9/test/sass/more_templates/more_import.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/more_templates/more_import.sass
rename to backends/css/gems/sass-3.4.9/test/sass/more_templates/more_import.sass
diff --git a/backends/css/gems/sass-3.4.9/test/sass/plugin_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/plugin_test.rb
new file mode 100755
index 0000000..187a2cf
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/plugin_test.rb
@@ -0,0 +1,556 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+require 'sass/plugin'
+require 'fileutils'
+
+module Sass::Script::Functions
+ def filename
+ filename = options[:filename].gsub(%r{.*((/[^/]+){4})}, '\1')
+ Sass::Script::Value::String.new(filename)
+ end
+
+ def whatever
+ custom = options[:custom]
+ whatever = custom && custom[:whatever]
+ Sass::Script::Value::String.new(whatever || "incorrect")
+ end
+end
+
+class SassPluginTest < MiniTest::Test
+ @@templates = %w{
+ complex script parent_ref import scss_import alt
+ subdir/subdir subdir/nested_subdir/nested_subdir
+ options import_content filename_fn
+ }
+ @@templates += %w[import_charset import_charset_ibm866] unless Sass::Util.ruby1_8?
+ @@templates << 'import_charset_1_8' if Sass::Util.ruby1_8?
+
+ @@cache_store = Sass::CacheStores::Memory.new
+
+ def setup
+ Sass::Util.retry_on_windows {FileUtils.mkdir_p tempfile_loc}
+ Sass::Util.retry_on_windows {FileUtils.mkdir_p tempfile_loc(nil,"more_")}
+ set_plugin_opts
+ check_for_updates!
+ reset_mtimes
+ end
+
+ def teardown
+ clean_up_sassc
+ Sass::Plugin.reset!
+ Sass::Util.retry_on_windows {FileUtils.rm_r tempfile_loc}
+ Sass::Util.retry_on_windows {FileUtils.rm_r tempfile_loc(nil,"more_")}
+ end
+
+ @@templates.each do |name|
+ define_method("test_template_renders_correctly (#{name})") do
+ assert_renders_correctly(name)
+ end
+ end
+
+ def test_no_update
+ File.delete(tempfile_loc('basic'))
+ assert_needs_update 'basic'
+ check_for_updates!
+ assert_stylesheet_updated 'basic'
+ end
+
+ def test_update_needed_when_modified
+ touch 'basic'
+ assert_needs_update 'basic'
+ check_for_updates!
+ assert_stylesheet_updated 'basic'
+ end
+
+ def test_update_needed_when_dependency_modified
+ touch 'basic'
+ assert_needs_update 'import'
+ check_for_updates!
+ assert_stylesheet_updated 'basic'
+ assert_stylesheet_updated 'import'
+ end
+
+ def test_update_needed_when_scss_dependency_modified
+ touch 'scss_importee'
+ assert_needs_update 'import'
+ check_for_updates!
+ assert_stylesheet_updated 'scss_importee'
+ assert_stylesheet_updated 'import'
+ end
+
+ def test_scss_update_needed_when_dependency_modified
+ touch 'basic'
+ assert_needs_update 'scss_import'
+ check_for_updates!
+ assert_stylesheet_updated 'basic'
+ assert_stylesheet_updated 'scss_import'
+ end
+
+ def test_update_needed_when_nested_import_dependency_modified
+ touch 'basic'
+ assert_needs_update 'nested_import'
+ check_for_updates!
+ assert_stylesheet_updated 'basic'
+ assert_stylesheet_updated 'scss_import'
+ end
+
+ def test_no_updates_when_always_check_and_always_update_both_false
+ Sass::Plugin.options[:always_update] = false
+ Sass::Plugin.options[:always_check] = false
+
+ touch 'basic'
+ assert_needs_update 'basic'
+ check_for_updates!
+
+ # Check it's still stale
+ assert_needs_update 'basic'
+ end
+
+ def test_full_exception_handling
+ File.delete(tempfile_loc('bork1'))
+ check_for_updates!
+ File.open(tempfile_loc('bork1')) do |file|
+ assert_equal(<<CSS.strip, file.read.split("\n")[0...6].join("\n"))
+/*
+Error: Undefined variable: "$bork".
+ on line 2 of #{template_loc('bork1')}
+
+1: bork
+2: :bork $bork
+CSS
+ end
+ File.delete(tempfile_loc('bork1'))
+ end
+
+ def test_full_exception_with_block_comment
+ File.delete(tempfile_loc('bork5'))
+ check_for_updates!
+ File.open(tempfile_loc('bork5')) do |file|
+ assert_equal(<<CSS.strip, file.read.split("\n")[0...7].join("\n"))
+/*
+Error: Undefined variable: "$bork".
+ on line 3 of #{template_loc('bork5')}
+
+1: bork
+2: /* foo *\\/
+3: :bork $bork
+CSS
+ end
+ File.delete(tempfile_loc('bork1'))
+ end
+
+ def test_single_level_import_loop
+ File.delete(tempfile_loc('single_import_loop'))
+ check_for_updates!
+ File.open(tempfile_loc('single_import_loop')) do |file|
+ assert_equal(<<CSS.strip, file.read.split("\n")[0...2].join("\n"))
+/*
+Error: An @import loop has been found: #{template_loc('single_import_loop')} imports itself
+CSS
+ end
+ end
+
+ def test_double_level_import_loop
+ File.delete(tempfile_loc('double_import_loop1'))
+ check_for_updates!
+ File.open(tempfile_loc('double_import_loop1')) do |file|
+ assert_equal(<<CSS.strip, file.read.split("\n")[0...4].join("\n"))
+/*
+Error: An @import loop has been found:
+ #{template_loc('double_import_loop1')} imports #{template_loc('_double_import_loop2')}
+ #{template_loc('_double_import_loop2')} imports #{template_loc('double_import_loop1')}
+CSS
+ end
+ end
+
+ def test_import_name_cleanup
+ File.delete(tempfile_loc('subdir/import_up1'))
+ check_for_updates!
+ File.open(tempfile_loc('subdir/import_up1')) do |file|
+ assert_equal(<<CSS.strip, file.read.split("\n")[0...5].join("\n"))
+/*
+Error: File to import not found or unreadable: ../subdir/import_up3.scss.
+ Load path: #{template_loc}
+ on line 1 of #{template_loc 'subdir/import_up2'}
+ from line 1 of #{template_loc 'subdir/import_up1'}
+CSS
+ end
+ end
+
+ def test_nonfull_exception_handling
+ old_full_exception = Sass::Plugin.options[:full_exception]
+ Sass::Plugin.options[:full_exception] = false
+
+ File.delete(tempfile_loc('bork1'))
+ assert_raises(Sass::SyntaxError) {check_for_updates!}
+ ensure
+ Sass::Plugin.options[:full_exception] = old_full_exception
+ end
+
+ def test_two_template_directories
+ set_plugin_opts :template_location => {
+ template_loc => tempfile_loc,
+ template_loc(nil,'more_') => tempfile_loc(nil,'more_')
+ }
+ check_for_updates!
+ ['more1', 'more_import'].each { |name| assert_renders_correctly(name, :prefix => 'more_') }
+ end
+
+ def test_two_template_directories_with_line_annotations
+ set_plugin_opts :line_comments => true,
+ :style => :nested,
+ :template_location => {
+ template_loc => tempfile_loc,
+ template_loc(nil,'more_') => tempfile_loc(nil,'more_')
+ }
+ check_for_updates!
+ assert_renders_correctly('more1_with_line_comments', 'more1', :prefix => 'more_')
+ end
+
+ def test_doesnt_render_partials
+ assert !File.exist?(tempfile_loc('_partial'))
+ end
+
+ def test_template_location_array
+ assert_equal [[template_loc, tempfile_loc]], Sass::Plugin.template_location_array
+ end
+
+ def test_add_template_location
+ Sass::Plugin.add_template_location(template_loc(nil, "more_"), tempfile_loc(nil, "more_"))
+ assert_equal(
+ [[template_loc, tempfile_loc], [template_loc(nil, "more_"), tempfile_loc(nil, "more_")]],
+ Sass::Plugin.template_location_array)
+
+ touch 'more1', 'more_'
+ touch 'basic'
+ assert_needs_update "more1", "more_"
+ assert_needs_update "basic"
+ check_for_updates!
+ assert_doesnt_need_update "more1", "more_"
+ assert_doesnt_need_update "basic"
+ end
+
+ def test_remove_template_location
+ Sass::Plugin.add_template_location(template_loc(nil, "more_"), tempfile_loc(nil, "more_"))
+ Sass::Plugin.remove_template_location(template_loc, tempfile_loc)
+ assert_equal(
+ [[template_loc(nil, "more_"), tempfile_loc(nil, "more_")]],
+ Sass::Plugin.template_location_array)
+
+ touch 'more1', 'more_'
+ touch 'basic'
+ assert_needs_update "more1", "more_"
+ assert_needs_update "basic"
+ check_for_updates!
+ assert_doesnt_need_update "more1", "more_"
+ assert_needs_update "basic"
+ end
+
+ def test_import_same_name
+ assert_warning <<WARNING do
+WARNING: In #{template_loc}:
+ There are multiple files that match the name "same_name_different_partiality.scss":
+ _same_name_different_partiality.scss
+ same_name_different_partiality.scss
+WARNING
+ touch "_same_name_different_partiality"
+ assert_needs_update "same_name_different_partiality"
+ end
+ end
+
+ # Callbacks
+
+ def test_updated_stylesheet_callback_for_updated_template
+ Sass::Plugin.options[:always_update] = false
+ touch 'basic'
+ assert_no_callback :updated_stylesheet, template_loc("complex"), tempfile_loc("complex") do
+ assert_callbacks(
+ [:updated_stylesheet, template_loc("basic"), tempfile_loc("basic")],
+ [:updated_stylesheet, template_loc("import"), tempfile_loc("import")])
+ end
+ end
+
+ def test_updated_stylesheet_callback_for_fresh_template
+ Sass::Plugin.options[:always_update] = false
+ assert_no_callback :updated_stylesheet
+ end
+
+ def test_updated_stylesheet_callback_for_error_template
+ Sass::Plugin.options[:always_update] = false
+ touch 'bork1'
+ assert_no_callback :updated_stylesheet
+ end
+
+ def test_not_updating_stylesheet_callback_for_fresh_template
+ Sass::Plugin.options[:always_update] = false
+ assert_callback :not_updating_stylesheet, template_loc("basic"), tempfile_loc("basic")
+ end
+
+ def test_not_updating_stylesheet_callback_for_updated_template
+ Sass::Plugin.options[:always_update] = false
+ assert_callback :not_updating_stylesheet, template_loc("complex"), tempfile_loc("complex") do
+ assert_no_callbacks(
+ [:updated_stylesheet, template_loc("basic"), tempfile_loc("basic")],
+ [:updated_stylesheet, template_loc("import"), tempfile_loc("import")])
+ end
+ end
+
+ def test_not_updating_stylesheet_callback_with_never_update
+ Sass::Plugin.options[:never_update] = true
+ assert_no_callback :not_updating_stylesheet
+ end
+
+ def test_not_updating_stylesheet_callback_for_partial
+ Sass::Plugin.options[:always_update] = false
+ assert_no_callback :not_updating_stylesheet, template_loc("_partial"), tempfile_loc("_partial")
+ end
+
+ def test_not_updating_stylesheet_callback_for_error
+ Sass::Plugin.options[:always_update] = false
+ touch 'bork1'
+ assert_no_callback :not_updating_stylesheet, template_loc("bork1"), tempfile_loc("bork1")
+ end
+
+ def test_compilation_error_callback
+ Sass::Plugin.options[:always_update] = false
+ touch 'bork1'
+ assert_callback(:compilation_error,
+ lambda {|e| e.message == 'Undefined variable: "$bork".'},
+ template_loc("bork1"), tempfile_loc("bork1"))
+ end
+
+ def test_compilation_error_callback_for_file_access
+ Sass::Plugin.options[:always_update] = false
+ assert_callback(:compilation_error,
+ lambda {|e| e.is_a?(Errno::ENOENT)},
+ template_loc("nonexistent"), tempfile_loc("nonexistent")) do
+ Sass::Plugin.update_stylesheets([[template_loc("nonexistent"), tempfile_loc("nonexistent")]])
+ end
+ end
+
+ def test_creating_directory_callback
+ Sass::Plugin.options[:always_update] = false
+ dir = File.join(tempfile_loc, "subdir", "nested_subdir")
+ FileUtils.rm_r dir
+ assert_callback :creating_directory, dir
+ end
+
+ ## Regression
+
+ def test_cached_dependencies_update
+ FileUtils.mv(template_loc("basic"), template_loc("basic", "more_"))
+ set_plugin_opts :load_paths => [template_loc(nil, "more_")]
+
+ touch 'basic', 'more_'
+ assert_needs_update "import"
+ check_for_updates!
+ assert_renders_correctly("import")
+ ensure
+ FileUtils.mv(template_loc("basic", "more_"), template_loc("basic"))
+ end
+
+ def test_cached_relative_import
+ old_always_update = Sass::Plugin.options[:always_update]
+ Sass::Plugin.options[:always_update] = true
+ check_for_updates!
+ assert_renders_correctly('subdir/subdir')
+ ensure
+ Sass::Plugin.options[:always_update] = old_always_update
+ end
+
+ def test_cached_if
+ set_plugin_opts :cache_store => Sass::CacheStores::Filesystem.new(tempfile_loc + '/cache')
+ check_for_updates!
+ assert_renders_correctly 'if'
+ check_for_updates!
+ assert_renders_correctly 'if'
+ ensure
+ set_plugin_opts
+ end
+
+ def test_cached_import_option
+ set_plugin_opts :custom => {:whatever => "correct"}
+ check_for_updates!
+ assert_renders_correctly "cached_import_option"
+
+ @@cache_store.reset!
+ set_plugin_opts :custom => nil, :always_update => false
+ check_for_updates!
+ assert_renders_correctly "cached_import_option"
+
+ set_plugin_opts :custom => {:whatever => "correct"}, :always_update => true
+ check_for_updates!
+ assert_renders_correctly "cached_import_option"
+ ensure
+ set_plugin_opts :custom => nil
+ end
+
+ private
+
+ def assert_renders_correctly(*arguments)
+ options = arguments.last.is_a?(Hash) ? arguments.pop : {}
+ prefix = options[:prefix]
+ result_name = arguments.shift
+ tempfile_name = arguments.shift || result_name
+
+ expected_str = File.read(result_loc(result_name, prefix))
+ actual_str = File.read(tempfile_loc(tempfile_name, prefix))
+ unless Sass::Util.ruby1_8?
+ expected_str = expected_str.force_encoding('IBM866') if result_name == 'import_charset_ibm866'
+ actual_str = actual_str.force_encoding('IBM866') if tempfile_name == 'import_charset_ibm866'
+ end
+ expected_lines = expected_str.split("\n")
+ actual_lines = actual_str.split("\n")
+
+ if actual_lines.first == "/*" && expected_lines.first != "/*"
+ assert(false, actual_lines[0..Sass::Util.enum_with_index(actual_lines).find {|l, i| l ==
"*/"}.last].join("\n"))
+ end
+
+ expected_lines.zip(actual_lines).each_with_index do |pair, line|
+ message = "template: #{result_name}\nline: #{line + 1}"
+ assert_equal(pair.first, pair.last, message)
+ end
+ if expected_lines.size < actual_lines.size
+ assert(false, "#{actual_lines.size - expected_lines.size} Trailing lines found in
#{tempfile_name}.css: #{actual_lines[expected_lines.size..-1].join('\n')}")
+ end
+ end
+
+ def assert_stylesheet_updated(name)
+ assert_doesnt_need_update name
+
+ # Make sure it isn't an exception
+ expected_lines = File.read(result_loc(name)).split("\n")
+ actual_lines = File.read(tempfile_loc(name)).split("\n")
+ if actual_lines.first == "/*" && expected_lines.first != "/*"
+ assert(false, actual_lines[0..actual_lines.enum_with_index.find {|l, i| l == "*/"}.last].join("\n"))
+ end
+ end
+
+ def assert_callback(name, *expected_args)
+ run = false
+ received_args = nil
+ Sass::Plugin.send("on_#{name}") do |*args|
+ received_args = args
+ run ||= expected_args.zip(received_args).all? do |ea, ra|
+ ea.respond_to?(:call) ? ea.call(ra) : ea == ra
+ end
+ end
+
+ if block_given?
+ Sass::Util.silence_sass_warnings {yield}
+ else
+ check_for_updates!
+ end
+
+ assert run, "Expected #{name} callback to be run with arguments:\n #{expected_args.inspect}\nHowever,
it got:\n #{received_args.inspect}"
+ end
+
+ def assert_no_callback(name, *unexpected_args)
+ Sass::Plugin.send("on_#{name}") do |*a|
+ next unless unexpected_args.empty? || a == unexpected_args
+
+ msg = "Expected #{name} callback not to be run"
+ if !unexpected_args.empty?
+ msg << " with arguments #{unexpected_args.inspect}"
+ elsif !a.empty?
+ msg << ",\n was run with arguments #{a.inspect}"
+ end
+
+ flunk msg
+ end
+
+ if block_given?
+ yield
+ else
+ check_for_updates!
+ end
+ end
+
+ def assert_callbacks(*args)
+ return check_for_updates! if args.empty?
+ assert_callback(*args.pop) {assert_callbacks(*args)}
+ end
+
+ def assert_no_callbacks(*args)
+ return check_for_updates! if args.empty?
+ assert_no_callback(*args.pop) {assert_no_callbacks(*args)}
+ end
+
+ def check_for_updates!
+ Sass::Util.silence_sass_warnings do
+ Sass::Plugin.check_for_updates
+ end
+ end
+
+ def assert_needs_update(*args)
+ assert(Sass::Plugin::StalenessChecker.stylesheet_needs_update?(tempfile_loc(*args), template_loc(*args)),
+ "Expected #{template_loc(*args)} to need an update.")
+ end
+
+ def assert_doesnt_need_update(*args)
+ assert(!Sass::Plugin::StalenessChecker.stylesheet_needs_update?(tempfile_loc(*args),
template_loc(*args)),
+ "Expected #{template_loc(*args)} not to need an update.")
+ end
+
+ def touch(*args)
+ FileUtils.touch(template_loc(*args))
+ end
+
+ def reset_mtimes
+ Sass::Plugin::StalenessChecker.dependencies_cache = {}
+ atime = Time.now
+ mtime = Time.now - 5
+ Dir["{#{template_loc},#{tempfile_loc}}/**/*.{css,sass,scss}"].each do |f|
+ Sass::Util.retry_on_windows {File.utime(atime, mtime, f)}
+ end
+ end
+
+ def template_loc(name = nil, prefix = nil)
+ if name
+ scss = absolutize "#{prefix}templates/#{name}.scss"
+ File.exist?(scss) ? scss : absolutize("#{prefix}templates/#{name}.sass")
+ else
+ absolutize "#{prefix}templates"
+ end
+ end
+
+ def tempfile_loc(name = nil, prefix = nil)
+ if name
+ absolutize "#{prefix}tmp/#{name}.css"
+ else
+ absolutize "#{prefix}tmp"
+ end
+ end
+
+ def result_loc(name = nil, prefix = nil)
+ if name
+ absolutize "#{prefix}results/#{name}.css"
+ else
+ absolutize "#{prefix}results"
+ end
+ end
+
+ def set_plugin_opts(overrides = {})
+ Sass::Plugin.options.merge!(
+ :template_location => template_loc,
+ :css_location => tempfile_loc,
+ :style => :compact,
+ :always_update => true,
+ :never_update => false,
+ :full_exception => true,
+ :cache_store => @@cache_store,
+ :sourcemap => :none
+ )
+ Sass::Plugin.options.merge!(overrides)
+ end
+end
+
+class Sass::Engine
+ alias_method :old_render, :render
+
+ def render
+ raise "bork bork bork!" if @template[0] == "{bork now!}"
+ old_render
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/alt.css
b/backends/css/gems/sass-3.4.9/test/sass/results/alt.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/alt.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/alt.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/basic.css
b/backends/css/gems/sass-3.4.9/test/sass/results/basic.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/basic.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/basic.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/cached_import_option.css
b/backends/css/gems/sass-3.4.9/test/sass/results/cached_import_option.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/cached_import_option.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/cached_import_option.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/compact.css
b/backends/css/gems/sass-3.4.9/test/sass/results/compact.css
new file mode 100644
index 0000000..6a4dcb4
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/compact.css
@@ -0,0 +1,5 @@
+#main { width: 15em; color: #0000ff; }
+#main p { border-style: dotted; /* Nested comment More nested stuff */ border-width: 2px; }
+#main .cool { width: 100px; }
+
+#left { font-size: 2em; font-weight: bold; float: left; }
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/complex.css
b/backends/css/gems/sass-3.4.9/test/sass/results/complex.css
new file mode 100644
index 0000000..d632f1c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/complex.css
@@ -0,0 +1,86 @@
+body { margin: 0; font: 0.85em "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; color: #fff;
background: url(/images/global_bg.gif); }
+
+#page { width: 900px; margin: 0 auto; background: #440008; border-top-width: 5px; border-top-style: solid;
border-top-color: #ff8500; }
+
+#header { height: 75px; padding: 0; }
+#header h1 { float: left; width: 274px; height: 75px; margin: 0; background-image:
url(/images/global_logo.gif); /* Crazy nested comment */ background-repeat: no-repeat; text-indent: -9999px; }
+#header .status { float: right; padding-top: 0.5em; padding-left: 0.5em; padding-right: 0.5em;
padding-bottom: 0; }
+#header .status p { float: left; margin-top: 0; margin-right: 0.5em; margin-bottom: 0; margin-left: 0; }
+#header .status ul { float: left; margin: 0; padding: 0; }
+#header .status li { list-style-type: none; display: inline; margin: 0 5px; }
+#header .status a:link, #header .status a:visited { color: #ff8500; text-decoration: none; }
+#header .status a:hover { text-decoration: underline; }
+#header .search { float: right; clear: right; margin: 12px 0 0 0; }
+#header .search form { margin: 0; }
+#header .search input { margin: 0 3px 0 0; padding: 2px; border: none; }
+
+#menu { clear: both; text-align: right; height: 20px; border-bottom: 5px solid #006b95; background: #00a4e4;
}
+#menu .contests ul { margin: 0 5px 0 0; padding: 0; }
+#menu .contests ul li { list-style-type: none; margin: 0 5px; padding: 5px 5px 0 5px; display: inline;
font-size: 1.1em; color: #fff; background: #00a4e4; }
+#menu .contests a:link, #menu .contests a:visited { color: #fff; text-decoration: none; font-weight: bold; }
+#menu .contests a:hover { text-decoration: underline; }
+
+#content { clear: both; }
+#content .container { clear: both; }
+#content .container .column { float: left; }
+#content .container .column .right { float: right; }
+#content a:link, #content a:visited { color: #93d700; text-decoration: none; }
+#content a:hover { text-decoration: underline; }
+
+#content p, #content div { width: 40em; }
+#content p li, #content p dt, #content p dd, #content div li, #content div dt, #content div dd { color:
#ddffdd; background-color: #4792bb; }
+#content .container.video .column.left { width: 200px; }
+#content .container.video .column.left .box { margin-top: 10px; }
+#content .container.video .column.left .box p { margin: 0 1em auto 1em; }
+#content .container.video .column.left .box.participants img { float: left; margin: 0 1em auto 1em; border:
1px solid #6e000d; border-style: solid; }
+#content .container.video .column.left .box.participants h2 { margin: 0 0 10px 0; padding: 0.5em; /* The
background image is a gif! */ background: #6e000d url(/images/hdr_participant.gif) 2px 2px no-repeat; /* Okay
check this out Multiline comments Wow dude I mean seriously, WOW */ text-indent: -9999px; border-top-width:
5px; border-top-style: solid; border-top-color: #a20013; border-right-width: 1px; border-right-style: dotted;
}
+#content .container.video .column.middle { width: 500px; }
+#content .container.video .column.right { width: 200px; }
+#content .container.video .column.right .box { margin-top: 0; }
+#content .container.video .column.right .box p { margin: 0 1em auto 1em; }
+#content .container.video .column p { margin-top: 0; }
+
+#content.contests .container.information .column.right .box { margin: 1em 0; }
+#content.contests .container.information .column.right .box.videos .thumbnail img { width: 200px; height:
150px; margin-bottom: 5px; }
+#content.contests .container.information .column.right .box.videos a:link, #content.contests
.container.information .column.right .box.videos a:visited { color: #93d700; text-decoration: none; }
+#content.contests .container.information .column.right .box.videos a:hover { text-decoration: underline; }
+#content.contests .container.information .column.right .box.votes a { display: block; width: 200px; height:
60px; margin: 15px 0; background: url(/images/btn_votenow.gif) no-repeat; text-indent: -9999px; outline:
none; border: none; }
+#content.contests .container.information .column.right .box.votes h2 { margin: 52px 0 10px 0; padding:
0.5em; background: #6e000d url(/images/hdr_videostats.gif) 2px 2px no-repeat; text-indent: -9999px;
border-top: 5px solid #a20013; }
+
+#content.contests .container.video .box.videos h2 { margin: 0; padding: 0.5em; background: #6e000d
url(/images/hdr_newestclips.gif) 2px 2px no-repeat; text-indent: -9999px; border-top: 5px solid #a20013; }
+#content.contests .container.video .box.videos table { width: 100; }
+#content.contests .container.video .box.videos table td { padding: 1em; width: 25; vertical-align: top; }
+#content.contests .container.video .box.videos table td p { margin: 0 0 5px 0; }
+#content.contests .container.video .box.videos table td a:link, #content.contests .container.video
.box.videos table td a:visited { color: #93d700; text-decoration: none; }
+#content.contests .container.video .box.videos table td a:hover { text-decoration: underline; }
+#content.contests .container.video .box.videos .thumbnail { float: left; }
+#content.contests .container.video .box.videos .thumbnail img { width: 80px; height: 60px; margin: 0 10px 0
0; border: 1px solid #6e000d; }
+
+#content .container.comments .column { margin-top: 15px; }
+#content .container.comments .column.left { width: 600px; }
+#content .container.comments .column.left .box ol { margin: 0; padding: 0; }
+#content .container.comments .column.left .box li { list-style-type: none; padding: 10px; margin: 0 0 1em 0;
background: #6e000d; border-top: 5px solid #a20013; }
+#content .container.comments .column.left .box li div { margin-bottom: 1em; }
+#content .container.comments .column.left .box li ul { text-align: right; }
+#content .container.comments .column.left .box li ul li { display: inline; border: none; padding: 0; }
+#content .container.comments .column.right { width: 290px; padding-left: 10px; }
+#content .container.comments .column.right h2 { margin: 0; padding: 0.5em; background: #6e000d
url(/images/hdr_addcomment.gif) 2px 2px no-repeat; text-indent: -9999px; border-top: 5px solid #a20013; }
+#content .container.comments .column.right .box textarea { width: 290px; height: 100px; border: none; }
+
+#footer { margin-top: 10px; padding: 1.2em 1.5em; background: #ff8500; }
+#footer ul { margin: 0; padding: 0; list-style-type: none; }
+#footer ul li { display: inline; margin: 0 0.5em; color: #440008; }
+#footer ul.links { float: left; }
+#footer ul.links a:link, #footer ul.links a:visited { color: #440008; text-decoration: none; }
+#footer ul.links a:hover { text-decoration: underline; }
+#footer ul.copyright { float: right; }
+
+.clear { clear: both; }
+
+.centered { text-align: center; }
+
+img { border: none; }
+
+button.short { width: 60px; height: 22px; padding: 0 0 2px 0; color: #fff; border: none; background:
url(/images/btn_short.gif) no-repeat; }
+
+table { border-collapse: collapse; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/compressed.css
b/backends/css/gems/sass-3.4.9/test/sass/results/compressed.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/compressed.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/compressed.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/expanded.css
b/backends/css/gems/sass-3.4.9/test/sass/results/expanded.css
new file mode 100644
index 0000000..05f91fa
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/expanded.css
@@ -0,0 +1,19 @@
+#main {
+ width: 15em;
+ color: #0000ff;
+}
+#main p {
+ border-style: dotted;
+ /* Nested comment
+ * More nested stuff */
+ border-width: 2px;
+}
+#main .cool {
+ width: 100px;
+}
+
+#left {
+ font-size: 2em;
+ font-weight: bold;
+ float: left;
+}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/filename_fn.css
b/backends/css/gems/sass-3.4.9/test/sass/results/filename_fn.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/filename_fn.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/filename_fn.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/if.css
b/backends/css/gems/sass-3.4.9/test/sass/results/if.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/if.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/if.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/import.css
b/backends/css/gems/sass-3.4.9/test/sass/results/import.css
new file mode 100644
index 0000000..e728382
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/import.css
@@ -0,0 +1,31 @@
+ import url(basic.css);
+ import url(../results/complex.css);
+imported { otherconst: hello; myconst: goodbye; pre-mixin: here; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+midrule { inthe: middle; }
+
+scss { imported: yes; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+#foo { background-color: #baf; }
+
+nonimported { myconst: hello; otherconst: goodbye; post-mixin: here; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import_charset.css
b/backends/css/gems/sass-3.4.9/test/sass/results/import_charset.css
similarity index 100%
copy from backends/css/gems/sass-3.2.12/test/sass/results/import_charset.css
copy to backends/css/gems/sass-3.4.9/test/sass/results/import_charset.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import_charset_1_8.css
b/backends/css/gems/sass-3.4.9/test/sass/results/import_charset_1_8.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/import_charset_1_8.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/import_charset_1_8.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import_charset.css
b/backends/css/gems/sass-3.4.9/test/sass/results/import_charset_ibm866.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/import_charset.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/import_charset_ibm866.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import_content.css
b/backends/css/gems/sass-3.4.9/test/sass/results/import_content.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/import_content.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/import_content.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/line_numbers.css
b/backends/css/gems/sass-3.4.9/test/sass/results/line_numbers.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/line_numbers.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/line_numbers.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/mixins.css
b/backends/css/gems/sass-3.4.9/test/sass/results/mixins.css
new file mode 100644
index 0000000..5d87f98
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/mixins.css
@@ -0,0 +1,95 @@
+#main {
+ width: 15em;
+ color: #0000ff;
+}
+#main p {
+ border-top-width: 2px;
+ border-top-color: #fc0;
+ border-left-width: 1px;
+ border-left-color: #000;
+ -moz-border-radius: 10px;
+ border-style: dotted;
+ border-width: 2px;
+}
+#main .cool {
+ width: 100px;
+}
+
+#left {
+ border-top-width: 2px;
+ border-top-color: #fc0;
+ border-left-width: 1px;
+ border-left-color: #000;
+ -moz-border-radius: 10px;
+ font-size: 2em;
+ font-weight: bold;
+ float: left;
+}
+
+#right {
+ border-top-width: 2px;
+ border-top-color: #fc0;
+ border-left-width: 1px;
+ border-left-color: #000;
+ -moz-border-radius: 10px;
+ color: #f00;
+ font-size: 20px;
+ float: right;
+}
+
+.bordered {
+ border-top-width: 2px;
+ border-top-color: #fc0;
+ border-left-width: 1px;
+ border-left-color: #000;
+ -moz-border-radius: 10px;
+}
+
+.complex {
+ color: #f00;
+ font-size: 20px;
+ text-decoration: none;
+}
+.complex:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+* html .complex {
+ height: 1px;
+ color: #f00;
+ font-size: 20px;
+}
+
+.more-complex {
+ color: #f00;
+ font-size: 20px;
+ text-decoration: none;
+ display: inline;
+ -webkit-nonsense-top-right: 1px;
+ -webkit-nonsense-bottom-left: 1px;
+}
+.more-complex:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+* html .more-complex {
+ height: 1px;
+ color: #f00;
+ font-size: 20px;
+}
+.more-complex a:hover {
+ text-decoration: underline;
+ color: #f00;
+ font-size: 20px;
+ border-top-width: 2px;
+ border-top-color: #fc0;
+ border-left-width: 1px;
+ border-left-color: #000;
+ -moz-border-radius: 10px;
+}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/multiline.css
b/backends/css/gems/sass-3.4.9/test/sass/results/multiline.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/multiline.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/multiline.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/nested.css
b/backends/css/gems/sass-3.4.9/test/sass/results/nested.css
new file mode 100644
index 0000000..061e6c1
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/nested.css
@@ -0,0 +1,22 @@
+#main {
+ width: 15em;
+ color: #0000ff; }
+ #main p {
+ border-style: dotted;
+ /* Nested comment
+ * More nested stuff */
+ border-width: 2px; }
+ #main .cool {
+ width: 100px; }
+
+#left {
+ font-size: 2em;
+ font-weight: bold;
+ float: left; }
+
+#right .header {
+ border-style: solid; }
+#right .body {
+ border-style: dotted; }
+#right .footer {
+ border-style: dashed; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/options.css
b/backends/css/gems/sass-3.4.9/test/sass/results/options.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/options.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/options.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/parent_ref.css
b/backends/css/gems/sass-3.4.9/test/sass/results/parent_ref.css
new file mode 100644
index 0000000..f384cfe
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/parent_ref.css
@@ -0,0 +1,13 @@
+a { color: #000; }
+a:hover { color: #f00; }
+
+p, div { width: 100em; }
+p foo, div foo { width: 10em; }
+p:hover, p bar, div:hover, div bar { height: 20em; }
+
+#cool { border-style: solid; border-width: 2em; }
+.ie7 #cool, .ie6 #cool { content: string("Totally not cool."); }
+.firefox #cool { content: string("Quite cool."); }
+
+.wow, .snazzy { font-family: fantasy; }
+.wow:hover, .wow:visited, .snazzy:hover, .snazzy:visited { font-weight: bold; }
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/script.css
b/backends/css/gems/sass-3.4.9/test/sass/results/script.css
new file mode 100644
index 0000000..8f39d29
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/script.css
@@ -0,0 +1,16 @@
+#main { content: Hello\!; qstr: 'Quo"ted"!'; hstr: Hyph-en\!; width: 30em; background-color: #000; color:
#ffa; short-color: #123; named-color: olive; con: "foo" bar 9 hi there "boom"; con2: "noquo" quo; }
+#main #sidebar { background-color: #00ff98; num-normal: 10; num-dec: 10.2; num-dec0: 99; num-neg: -10; esc:
10\+12; many: 6; order: 7; complex: #4c9db1hi16; }
+
+#plus { num-num: 7; num-num-un: 25em; num-num-un2: 23em; num-num-neg: 9.87; num-str: 100px; num-col:
#b7b7b7; num-perc: 31%; str-str: "hi there"; str-str2: "hi there"; str-col: "14em solid #123"; str-num:
"times: 13"; col-num: #ff7b9d; col-col: #5173ff; }
+
+#minus { num-num: 900; col-num: #f9f9f4; col-col: #000035; unary-num: -1; unary-const: 10; unary-paren: -11;
unary-two: 12; unary-many: 12; unary-crazy: -15; }
+
+#times { num-num: 7; num-col: #7496b8; col-num: #092345; col-col: #243648; }
+
+#div { num-num: 3.33333; num-num2: 3.33333; col-num: #092345; col-col: #0b0d0f; comp: 1px; }
+
+#mod { num-num: 2; col-col: #0f0e05; col-num: #020001; }
+
+#const { escaped-quote: \$foo \!bar; default: Hello\! !important; }
+
+#regression { a: 4; }
diff --git a/backends/css/gems/sass-3.4.9/test/sass/results/scss_import.css
b/backends/css/gems/sass-3.4.9/test/sass/results/scss_import.css
new file mode 100644
index 0000000..e728382
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/results/scss_import.css
@@ -0,0 +1,31 @@
+ import url(basic.css);
+ import url(../results/complex.css);
+imported { otherconst: hello; myconst: goodbye; pre-mixin: here; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+midrule { inthe: middle; }
+
+scss { imported: yes; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+#foo { background-color: #baf; }
+
+nonimported { myconst: hello; otherconst: goodbye; post-mixin: here; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/scss_importee.css
b/backends/css/gems/sass-3.4.9/test/sass/results/scss_importee.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/scss_importee.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/scss_importee.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/subdir/nested_subdir/nested_subdir.css
b/backends/css/gems/sass-3.4.9/test/sass/results/subdir/nested_subdir/nested_subdir.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/subdir/nested_subdir/nested_subdir.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/subdir/nested_subdir/nested_subdir.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/subdir/subdir.css
b/backends/css/gems/sass-3.4.9/test/sass/results/subdir/subdir.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/subdir/subdir.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/subdir/subdir.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/units.css
b/backends/css/gems/sass-3.4.9/test/sass/results/units.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/units.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/units.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/warn.css
b/backends/css/gems/sass-3.4.9/test/sass/results/warn.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/warn.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/warn.css
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/warn_imported.css
b/backends/css/gems/sass-3.4.9/test/sass/results/warn_imported.css
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/results/warn_imported.css
rename to backends/css/gems/sass-3.4.9/test/sass/results/warn_imported.css
diff --git a/backends/css/gems/sass-3.4.9/test/sass/script_conversion_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/script_conversion_test.rb
new file mode 100755
index 0000000..cfe13e0
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/script_conversion_test.rb
@@ -0,0 +1,333 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/engine'
+
+class SassScriptConversionTest < MiniTest::Test
+ def test_bool
+ assert_renders "true"
+ assert_renders "false"
+ end
+
+ def test_color
+ assert_renders "#abcdef"
+ assert_renders "blue"
+ assert_renders "rgba(0, 1, 2, 0.2)"
+ assert_renders "#abc"
+ assert_renders "#0000ff"
+ end
+
+ def test_number
+ assert_renders "10"
+ assert_renders "10.35"
+ assert_renders "12px"
+ assert_renders "12.45px"
+
+ assert_equal "12.34568", render("12.345678901")
+ end
+
+ def test_string
+ assert_renders '"foo"'
+ assert_renders '"bar baz"'
+ assert_equal '"baz bang"', render("'baz bang'")
+ end
+
+ def test_string_quotes
+ assert_equal "'quote\"quote'", render('"quote\\"quote"')
+ assert_equal '"quote\'quote"', render("'quote\\'quote'")
+ assert_renders '"quote\'quote\\"quote"'
+ assert_equal '"quote\'quote\\"quote"', render("'quote\\'quote\"quote'")
+ end
+
+ def test_string_escapes
+ assert_renders '"foo\\\\bar"'
+ end
+
+ def test_funcall
+ assert_renders "foo(true, blue)"
+ assert_renders "hsla(20deg, 30%, 50%, 0.3)"
+ assert_renders "blam()"
+
+ assert_renders "-\xC3\xBFoo(12px)"
+ assert_renders "-foo(12px)"
+ end
+
+ def test_funcall_with_keyword_args
+ assert_renders "foo(arg1, arg2, $karg1: val, $karg2: val2)"
+ assert_renders "foo($karg1: val, $karg2: val2)"
+ end
+
+ def test_funcall_with_hyphen_conversion_keyword_arg
+ assert_renders "foo($a-b_c: val)"
+ end
+
+ def test_url
+ assert_renders "url(foo.gif)"
+ assert_renders "url($var)"
+ assert_renders "url(\#{$var}/flip.gif)"
+ end
+
+ def test_variable
+ assert_renders "$foo-bar"
+ assert_renders "$flaznicate"
+ end
+
+ def test_null
+ assert_renders "null"
+ end
+
+ def test_empty_list
+ assert_renders "()"
+ end
+
+ def test_list_in_args
+ assert_renders "foo((a, b, c))"
+ assert_renders "foo($arg: (a, b, c))"
+ assert_renders "foo(a, b, (a, b, c)...)"
+ end
+
+ def test_singleton_list
+ assert_renders "(1,)"
+ assert_renders "(1 2 3,)"
+ assert_renders "((1, 2, 3),)"
+ end
+
+ def test_map
+ assert_renders "(foo: bar)"
+ assert_renders "(foo: bar, baz: bip)"
+ assert_renders "(foo: bar, baz: (bip: bap))"
+ end
+
+ def test_map_in_list
+ assert_renders "(foo: bar) baz"
+ assert_renders "(foo: bar), (baz: bip)"
+ end
+
+ def test_list_in_map
+ assert_renders "(foo: bar baz)"
+ assert_renders "(foo: (bar, baz), bip: bop)"
+ end
+
+ def test_selector
+ assert_renders "&"
+ end
+
+ def self.test_precedence(outer, inner)
+ op_outer = Sass::Script::Lexer::OPERATORS_REVERSE[outer]
+ op_inner = Sass::Script::Lexer::OPERATORS_REVERSE[inner]
+ class_eval <<RUBY
+ def test_precedence_#{outer}_#{inner}
+ assert_renders "$foo #{op_outer} $bar #{op_inner} $baz"
+ assert_renders "$foo #{op_inner} $bar #{op_outer} $baz"
+
+ assert_renders "($foo #{op_outer} $bar) #{op_inner} $baz"
+ assert_renders "$foo #{op_inner} ($bar #{op_outer} $baz)"
+
+ assert_equal "$foo #{op_outer} $bar #{op_inner} $baz",
+ render("$foo #{op_outer} ($bar #{op_inner} $baz)")
+ assert_equal "$foo #{op_inner} $bar #{op_outer} $baz",
+ render("($foo #{op_inner} $bar) #{op_outer} $baz")
+ end
+RUBY
+ end
+
+ def self.assert_associative(op_name, sibling_name)
+ op = separator_for(op_name)
+ sibling = separator_for(sibling_name)
+ class_eval <<RUBY
+ def test_associative_#{op_name}_#{sibling_name}
+ assert_renders "$foo#{op}$bar#{op}$baz"
+
+ assert_equal "$foo#{op}$bar#{op}$baz",
+ render("$foo#{op}($bar#{op}$baz)")
+ assert_equal "$foo#{op}$bar#{op}$baz",
+ render("($foo#{op}$bar)#{op}$baz")
+
+ assert_equal "$foo#{op}$bar#{sibling}$baz",
+ render("$foo#{op}($bar#{sibling}$baz)")
+ assert_equal "$foo#{sibling}$bar#{op}$baz",
+ render("($foo#{sibling}$bar)#{op}$baz")
+ end
+RUBY
+ end
+
+ def self.separator_for(op_name)
+ case op_name
+ when :comma; ", "
+ when :space; " "
+ else; " #{Sass::Script::Lexer::OPERATORS_REVERSE[op_name]} "
+ end
+ end
+
+ def self.assert_non_associative(op_name, sibling_name)
+ op = Sass::Script::Lexer::OPERATORS_REVERSE[op_name]
+ sibling = Sass::Script::Lexer::OPERATORS_REVERSE[sibling_name]
+ class_eval <<RUBY
+ def test_non_associative_#{op_name}_#{sibling_name}
+ assert_renders "$foo #{op} $bar #{op} $baz"
+
+ assert_renders "$foo #{op} ($bar #{op} $baz)"
+ assert_equal "$foo #{op} $bar #{op} $baz",
+ render("($foo #{op} $bar) #{op} $baz")
+
+ assert_renders "$foo #{op} ($bar #{sibling} $baz)"
+ assert_equal "$foo #{sibling} $bar #{op} $baz",
+ render("($foo #{sibling} $bar) #{op} $baz")
+ end
+RUBY
+ end
+
+ test_precedence :or, :and
+ test_precedence :and, :eq
+ test_precedence :and, :neq
+ test_precedence :eq, :gt
+ test_precedence :eq, :gte
+ test_precedence :eq, :lt
+ test_precedence :eq, :lte
+ test_precedence :gt, :plus
+ test_precedence :gt, :minus
+ test_precedence :plus, :times
+ test_precedence :plus, :div
+ test_precedence :plus, :mod
+
+ assert_associative :plus, :minus
+ assert_associative :times, :div
+ assert_associative :times, :mod
+
+ assert_non_associative :minus, :plus
+ assert_non_associative :div, :times
+ assert_non_associative :mod, :times
+ assert_non_associative :gt, :gte
+ assert_non_associative :gte, :lt
+ assert_non_associative :lt, :lte
+ assert_non_associative :lte, :gt
+
+ def test_comma_precedence
+ assert_renders "$foo, $bar, $baz"
+
+ assert_renders "$foo ($bar, $baz)"
+ assert_renders "($foo, $bar) $baz"
+
+ assert_equal "$foo, $bar $baz", render("$foo, ($bar $baz)")
+ assert_equal "$foo $bar, $baz", render("($foo $bar), $baz")
+
+ assert_equal "$foo, ($bar, $baz)", render("$foo, ($bar, $baz)")
+ assert_equal "($foo, $bar), $baz", render("($foo, $bar), $baz")
+ end
+
+ def test_space_precedence
+ assert_renders "$foo $bar $baz"
+
+ assert_renders "$foo or ($bar $baz)"
+ assert_renders "($foo $bar) or $baz"
+
+ assert_equal "$foo $bar or $baz", render("$foo ($bar or $baz)")
+ assert_equal "$foo or $bar $baz", render("($foo or $bar) $baz")
+
+ assert_equal "$foo ($bar $baz)", render("$foo ($bar $baz)")
+ assert_equal "($foo $bar) $baz", render("($foo $bar) $baz")
+ end
+
+ def test_unary_op
+ assert_renders "-12px"
+ assert_renders '/"foo"'
+ assert_renders 'not true'
+
+ assert_renders "-(foo(12px))"
+ assert_renders "-(-foo(12px))"
+ assert_renders "-(_foo(12px))"
+ assert_renders "-(\xC3\xBFoo(12px))"
+ assert_renders "-(blue)"
+
+ assert_equal 'not true or false', render('(not true) or false')
+ assert_equal 'not (true or false)', render('not (true or false)')
+ end
+
+ def test_interpolation
+ assert_renders "$foo\#{$bar}$baz"
+ assert_renders "$foo\#{$bar} $baz"
+ assert_renders "$foo \#{$bar}$baz"
+ assert_renders "$foo \#{$bar} $baz"
+ assert_renders "$foo \#{$bar}\#{$bang} $baz"
+ assert_renders "$foo \#{$bar} \#{$bang} $baz"
+ assert_renders "\#{$bar}$baz"
+ assert_renders "$foo\#{$bar}"
+ assert_renders "\#{$bar}"
+ end
+
+ def test_interpolation_in_function
+ assert_renders 'flabnabbit(#{1 + "foo"})'
+ assert_renders 'flabnabbit($foo #{1 + "foo"}$baz)'
+ assert_renders 'flabnabbit($foo #{1 + "foo"}#{2 + "bar"} $baz)'
+ end
+
+ def test_interpolation_in_string_function
+ assert_renders 'calc(#{1 + "foo"})'
+ assert_renders 'calc(foo#{1 + "foo"}baz)'
+ end
+
+ def test_interpolation_near_operators
+ assert_renders '#{1 + 2} , #{3 + 4}'
+ assert_renders '#{1 + 2}, #{3 + 4}'
+ assert_renders '#{1 + 2} ,#{3 + 4}'
+ assert_renders '#{1 + 2},#{3 + 4}'
+ assert_renders '#{1 + 2}, #{3 + 4}, #{5 + 6}'
+ assert_renders '3, #{3 + 4}, 11'
+
+ assert_renders '3 / #{3 + 4}'
+ assert_renders '3 /#{3 + 4}'
+ assert_renders '3/ #{3 + 4}'
+ assert_renders '3/#{3 + 4}'
+
+ assert_renders '#{1 + 2} * 7'
+ assert_renders '#{1 + 2}* 7'
+ assert_renders '#{1 + 2} *7'
+ assert_renders '#{1 + 2}*7'
+
+ assert_renders '-#{1 + 2}'
+ assert_renders '- #{1 + 2}'
+
+ assert_renders '5 + #{1 + 2} * #{3 + 4}'
+ assert_renders '5 +#{1 + 2} * #{3 + 4}'
+ assert_renders '5+#{1 + 2} * #{3 + 4}'
+ assert_renders '#{1 + 2} * #{3 + 4} + 5'
+ assert_renders '#{1 + 2} * #{3 + 4}+ 5'
+ assert_renders '#{1 + 2} * #{3 + 4}+5'
+
+ assert_equal '5 / #{1 + 2} + #{3 + 4}', render('5 / (#{1 + 2} + #{3 + 4})')
+ assert_equal '5 / #{1 + 2} + #{3 + 4}', render('5 /(#{1 + 2} + #{3 + 4})')
+ assert_equal '5 / #{1 + 2} + #{3 + 4}', render('5 /( #{1 + 2} + #{3 + 4} )')
+ assert_equal '#{1 + 2} + #{3 + 4} / 5', render('(#{1 + 2} + #{3 + 4}) / 5')
+ assert_equal '#{1 + 2} + #{3 + 4} / 5', render('(#{1 + 2} + #{3 + 4})/ 5')
+ assert_equal '#{1 + 2} + #{3 + 4} / 5', render('( #{1 + 2} + #{3 + 4} )/ 5')
+
+ assert_renders '#{1 + 2} + 2 + 3'
+ assert_renders '#{1 + 2} +2 + 3'
+ end
+
+ def test_string_interpolation
+ assert_renders '"foo#{$bar}baz"'
+ assert_renders '"foo #{$bar}baz"'
+ assert_renders '"foo#{$bar} baz"'
+ assert_renders '"foo #{$bar} baz"'
+ assert_renders '"foo #{$bar}#{$bang} baz"'
+ assert_renders '"foo #{$bar} #{$bang} baz"'
+ assert_renders '"#{$bar}baz"'
+ assert_renders '"foo#{$bar}"'
+ assert_equal '#{$bar}', render('"#{$bar}"')
+
+ assert_equal '"foo#{$bar}baz"', render("'foo\#{$bar}baz'")
+ end
+
+ private
+
+ def assert_renders(script, options = {})
+ assert_equal(script, render(script, options))
+ end
+
+ def render(script, options = {})
+ munge_filename(options)
+ node = Sass::Script.parse(script, 1, 0, options)
+ node.to_sass
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/script_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/script_test.rb
new file mode 100755
index 0000000..f679abb
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/script_test.rb
@@ -0,0 +1,1170 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/engine'
+
+module Sass::Script::Functions::UserFunctions
+ def assert_options(val)
+ val.options[:foo]
+ Sass::Script::Value::String.new("Options defined!")
+ end
+
+ def arg_error
+ assert_options
+ end
+end
+
+module Sass::Script::Functions
+ include Sass::Script::Functions::UserFunctions
+end
+
+class SassScriptTest < MiniTest::Test
+ include Sass::Script
+
+ def test_color_clamps_input
+ assert_equal 0, Sass::Script::Value::Color.new([1, 2, -1]).blue
+ assert_equal 255, Sass::Script::Value::Color.new([256, 2, 3]).red
+ end
+
+ def test_color_clamps_rgba_input
+ assert_equal 1, Sass::Script::Value::Color.new([1, 2, 3, 1.1]).alpha
+ assert_equal 0, Sass::Script::Value::Color.new([1, 2, 3, -0.1]).alpha
+ end
+
+ def test_string_escapes
+ assert_equal "'", resolve("\"'\"")
+ assert_equal '"', resolve("\"\\\"\"")
+ assert_equal "\\", resolve("\"\\\\\"")
+ assert_equal "☃", resolve("\"\\2603\"")
+ assert_equal "☃f", resolve("\"\\2603 f\"")
+ assert_equal "☃x", resolve("\"\\2603x\"")
+ assert_equal "\\2603", resolve("\"\\\\2603\"")
+
+ # U+FFFD is the replacement character, "�".
+ assert_equal [0xFFFD].pack("U"), resolve("\"\\0\"")
+ assert_equal [0xFFFD].pack("U"), resolve("\"\\FFFFFF\"")
+ assert_equal [0xFFFD].pack("U"), resolve("\"\\D800\"")
+ assert_equal [0xD7FF].pack("U"), resolve("\"\\D7FF\"")
+ assert_equal [0xFFFD].pack("U"), resolve("\"\\DFFF\"")
+ assert_equal [0xE000].pack("U"), resolve("\"\\E000\"")
+ end
+
+ def test_string_escapes_are_resolved_before_operators
+ assert_equal "true", resolve('"abc" == "\61\62\63"')
+ end
+
+ def test_string_quote
+ assert_equal '"foo"', resolve_quoted('"foo"')
+ assert_equal "'f\"oo'", resolve_quoted('"f\"oo"')
+ assert_equal "\"f'oo\"", resolve_quoted("'f\\'oo'")
+ assert_equal "\"f'o\\\"o\"", resolve_quoted("'f\\'o\"o'")
+ assert_equal '"foo bar"', resolve_quoted('"foo\20 bar"')
+ assert_equal '"foo\a bar"', resolve_quoted('"foo\a bar"')
+ assert_equal '"x\ay"', resolve_quoted('"x\a y"')
+ assert_equal '"\a "', resolve_quoted('"\a\20"')
+ assert_equal '"\a abcdef"', resolve_quoted('"\a abcdef"')
+ assert_equal '"☃abcdef"', resolve_quoted('"\2603 abcdef"')
+ assert_equal '"\\\\"', resolve_quoted('"\\\\"')
+ assert_equal '"foobar"', resolve_quoted("\"foo\\\nbar\"")
+ end
+
+ def test_color_names
+ assert_equal "white", resolve("white")
+ assert_equal "#ffffff", resolve("#ffffff")
+ assert_equal "#fffffe", resolve("white - #000001")
+ assert_equal "transparent", resolve("transparent")
+ assert_equal "transparent", resolve("rgba(0, 0, 0, 0)")
+ end
+
+ def test_rgba_color_literals
+ assert_equal Sass::Script::Value::Color.new([1, 2, 3, 0.75]), eval("rgba(1, 2, 3, 0.75)")
+ assert_equal "rgba(1, 2, 3, 0.75)", resolve("rgba(1, 2, 3, 0.75)")
+
+ assert_equal Sass::Script::Value::Color.new([1, 2, 3, 0]), eval("rgba(1, 2, 3, 0)")
+ assert_equal "rgba(1, 2, 3, 0)", resolve("rgba(1, 2, 3, 0)")
+
+ assert_equal Sass::Script::Value::Color.new([1, 2, 3]), eval("rgba(1, 2, 3, 1)")
+ assert_equal Sass::Script::Value::Color.new([1, 2, 3, 1]), eval("rgba(1, 2, 3, 1)")
+ assert_equal "#010203", resolve("rgba(1, 2, 3, 1)")
+ assert_equal "white", resolve("rgba(255, 255, 255, 1)")
+ end
+
+ def test_rgba_color_math
+ assert_equal "rgba(50, 50, 100, 0.35)", resolve("rgba(1, 1, 2, 0.35) * rgba(50, 50, 50, 0.35)")
+ assert_equal "rgba(52, 52, 52, 0.25)", resolve("rgba(2, 2, 2, 0.25) + rgba(50, 50, 50, 0.25)")
+
+ assert_raise_message(Sass::SyntaxError, "Alpha channels must be equal: rgba(1, 2, 3, 0.15) + rgba(50,
50, 50, 0.75)") do
+ resolve("rgba(1, 2, 3, 0.15) + rgba(50, 50, 50, 0.75)")
+ end
+ assert_raise_message(Sass::SyntaxError, "Alpha channels must be equal: #123456 * rgba(50, 50, 50,
0.75)") do
+ resolve("#123456 * rgba(50, 50, 50, 0.75)")
+ end
+ assert_raise_message(Sass::SyntaxError, "Alpha channels must be equal: rgba(50, 50, 50, 0.75) /
#123456") do
+ resolve("rgba(50, 50, 50, 0.75) / #123456")
+ end
+ end
+
+ def test_rgba_number_math
+ assert_equal "rgba(49, 49, 49, 0.75)", resolve("rgba(50, 50, 50, 0.75) - 1")
+ assert_equal "rgba(100, 100, 100, 0.75)", resolve("rgba(50, 50, 50, 0.75) * 2")
+ end
+
+ def test_rgba_rounding
+ assert_equal "rgba(10, 1, 0, 0.12346)", resolve("rgba(10.0, 1.23456789, 0.0, 0.1234567)")
+ end
+
+ def test_compressed_colors
+ assert_equal "#123456", resolve("#123456", :style => :compressed)
+ assert_equal "rgba(1,2,3,0.5)", resolve("rgba(1, 2, 3, 0.5)", :style => :compressed)
+ assert_equal "#123", resolve("#112233", :style => :compressed)
+ assert_equal "#000", resolve("black", :style => :compressed)
+ assert_equal "red", resolve("#f00", :style => :compressed)
+ assert_equal "blue", resolve("blue", :style => :compressed)
+ assert_equal "navy", resolve("#000080", :style => :compressed)
+ assert_equal "navy #fff", resolve("#000080 white", :style => :compressed)
+ assert_equal "This color is #fff", resolve('"This color is #{ white }"', :style => :compressed)
+ assert_equal "transparent", resolve("rgba(0, 0, 0, 0)", :style => :compressed)
+ end
+
+ def test_compressed_comma
+ # assert_equal "foo,bar,baz", resolve("foo, bar, baz", :style => :compressed)
+ # assert_equal "foo,#baf,baz", resolve("foo, #baf, baz", :style => :compressed)
+ assert_equal "foo,#baf,red", resolve("foo, #baf, #f00", :style => :compressed)
+ end
+
+ def test_implicit_strings
+ assert_equal Sass::Script::Value::String.new("foo"), eval("foo")
+ assert_equal Sass::Script::Value::String.new("foo/bar"), eval("foo/bar")
+ end
+
+ def test_basic_interpolation
+ assert_equal "foo3bar", resolve("foo\#{1 + 2}bar")
+ assert_equal "foo3 bar", resolve("foo\#{1 + 2} bar")
+ assert_equal "foo 3bar", resolve("foo \#{1 + 2}bar")
+ assert_equal "foo 3 bar", resolve("foo \#{1 + 2} bar")
+ assert_equal "foo 35 bar", resolve("foo \#{1 + 2}\#{2 + 3} bar")
+ assert_equal "foo 3 5 bar", resolve("foo \#{1 + 2} \#{2 + 3} bar")
+ assert_equal "3bar", resolve("\#{1 + 2}bar")
+ assert_equal "foo3", resolve("foo\#{1 + 2}")
+ assert_equal "3", resolve("\#{1 + 2}")
+ end
+
+ def test_interpolation_in_function
+ assert_equal 'flabnabbit(1foo)', resolve('flabnabbit(#{1 + "foo"})')
+ assert_equal 'flabnabbit(foo 1foobaz)', resolve('flabnabbit(foo #{1 + "foo"}baz)')
+ assert_equal('flabnabbit(foo 1foo2bar baz)',
+ resolve('flabnabbit(foo #{1 + "foo"}#{2 + "bar"} baz)'))
+ end
+
+ def test_interpolation_near_operators
+ assert_equal '3 , 7', resolve('#{1 + 2} , #{3 + 4}')
+ assert_equal '3, 7', resolve('#{1 + 2}, #{3 + 4}')
+ assert_equal '3 ,7', resolve('#{1 + 2} ,#{3 + 4}')
+ assert_equal '3,7', resolve('#{1 + 2},#{3 + 4}')
+ assert_equal '3, 7, 11', resolve('#{1 + 2}, #{3 + 4}, #{5 + 6}')
+ assert_equal '3, 7, 11', resolve('3, #{3 + 4}, 11')
+ assert_equal '3, 7, 11', resolve('3, 7, #{5 + 6}')
+
+ assert_equal '3 / 7', resolve('3 / #{3 + 4}')
+ assert_equal '3 /7', resolve('3 /#{3 + 4}')
+ assert_equal '3/ 7', resolve('3/ #{3 + 4}')
+ assert_equal '3/7', resolve('3/#{3 + 4}')
+
+ assert_equal '3 * 7', resolve('#{1 + 2} * 7')
+ assert_equal '3* 7', resolve('#{1 + 2}* 7')
+ assert_equal '3 *7', resolve('#{1 + 2} *7')
+ assert_equal '3*7', resolve('#{1 + 2}*7')
+
+ assert_equal '-3', resolve('-#{1 + 2}')
+ assert_equal '- 3', resolve('- #{1 + 2}')
+
+ assert_equal '5 + 3 * 7', resolve('5 + #{1 + 2} * #{3 + 4}')
+ assert_equal '5 +3 * 7', resolve('5 +#{1 + 2} * #{3 + 4}')
+ assert_equal '5+3 * 7', resolve('5+#{1 + 2} * #{3 + 4}')
+ assert_equal '3 * 7 + 5', resolve('#{1 + 2} * #{3 + 4} + 5')
+ assert_equal '3 * 7+ 5', resolve('#{1 + 2} * #{3 + 4}+ 5')
+ assert_equal '3 * 7+5', resolve('#{1 + 2} * #{3 + 4}+5')
+
+ assert_equal '5/3 + 7', resolve('5 / (#{1 + 2} + #{3 + 4})')
+ assert_equal '5/3 + 7', resolve('5 /(#{1 + 2} + #{3 + 4})')
+ assert_equal '5/3 + 7', resolve('5 /( #{1 + 2} + #{3 + 4} )')
+ assert_equal '3 + 7/5', resolve('(#{1 + 2} + #{3 + 4}) / 5')
+ assert_equal '3 + 7/5', resolve('(#{1 + 2} + #{3 + 4})/ 5')
+ assert_equal '3 + 7/5', resolve('( #{1 + 2} + #{3 + 4} )/ 5')
+
+ assert_equal '3 + 5', resolve('#{1 + 2} + 2 + 3')
+ assert_equal '3 +5', resolve('#{1 + 2} +2 + 3')
+ end
+
+ def test_string_interpolation
+ assert_equal "foo bar, baz bang", resolve('"foo #{"bar"}, #{"baz"} bang"')
+ assert_equal "foo bar baz bang", resolve('"foo #{"#{"ba" + "r"} baz"} bang"')
+ assert_equal 'foo #{bar baz} bang', resolve('"foo \#{#{"ba" + "r"} baz} bang"')
+ assert_equal 'foo #{baz bang', resolve('"foo #{"\#{" + "baz"} bang"')
+ assert_equal "foo2bar", resolve('\'foo#{1 + 1}bar\'')
+ assert_equal "foo2bar", resolve('"foo#{1 + 1}bar"')
+ assert_equal "foo1bar5baz4bang", resolve('\'foo#{1 + "bar#{2 + 3}baz" + 4}bang\'')
+ end
+
+ def test_interpolation_with_newline
+ assert_equal "\nbang", resolve('"#{"\a "}bang"')
+ assert_equal "\n\nbang", resolve('"#{"\a "}\a bang"')
+ end
+
+ def test_rule_interpolation
+ assert_equal(<<CSS, render(<<SASS))
+foo bar baz bang {
+ a: b; }
+CSS
+foo \#{"\#{"ba" + "r"} baz"} bang
+ a: b
+SASS
+ assert_equal(<<CSS, render(<<SASS))
+foo [bar="\#{bar baz}"] bang {
+ a: b; }
+CSS
+foo [bar="\\\#{\#{"ba" + "r"} baz}"] bang
+ a: b
+SASS
+ assert_equal(<<CSS, render(<<SASS))
+foo [bar="\#{baz"] bang {
+ a: b; }
+CSS
+foo [bar="\#{"\\\#{" + "baz"}"] bang
+ a: b
+SASS
+ end
+
+ def test_inaccessible_functions
+ assert_equal "send(to_s)", resolve("send(to_s)", :line => 2)
+ assert_equal "public_instance_methods()", resolve("public_instance_methods()")
+ end
+
+ def test_adding_functions_directly_to_functions_module
+ assert !Functions.callable?('nonexistant')
+ Functions.class_eval { def nonexistant; end }
+ assert Functions.callable?('nonexistant')
+ Functions.send :remove_method, :nonexistant
+ end
+
+ def test_default_functions
+ assert_equal "url(12)", resolve("url(12)")
+ assert_equal 'blam("foo")', resolve('blam("foo")')
+ end
+
+ def test_function_results_have_options
+ assert_equal "Options defined!", resolve("assert_options(abs(1))")
+ assert_equal "Options defined!", resolve("assert_options(round(1.2))")
+ end
+
+ def test_funcall_requires_no_whitespace_before_lparen
+ assert_equal "no-repeat 15px", resolve("no-repeat (7px + 8px)")
+ assert_equal "no-repeat(15px)", resolve("no-repeat(7px + 8px)")
+ end
+
+ def test_dynamic_url
+ assert_equal "url(foo-bar)", resolve("url($foo)", {}, env('foo' =>
Sass::Script::Value::String.new("foo-bar")))
+ assert_equal "url(foo-bar baz)", resolve("url($foo $bar)", {}, env('foo' =>
Sass::Script::Value::String.new("foo-bar"), 'bar' => Sass::Script::Value::String.new("baz")))
+ assert_equal "url(foo baz)", resolve("url(foo $bar)", {}, env('bar' =>
Sass::Script::Value::String.new("baz")))
+ assert_equal "url(foo bar)", resolve("url(foo bar)")
+ end
+
+ def test_url_with_interpolation
+ assert_equal "url(http://sass-lang.com/images/foo-bar)",
resolve("url(http://sass-lang.com/images/\#{foo-bar})")
+ assert_equal 'url("http://sass-lang.com/images/foo-bar")',
resolve("url('http://sass-lang.com/images/\#{foo-bar}')")
+ assert_equal 'url("http://sass-lang.com/images/foo-bar")',
resolve('url("http://sass-lang.com/images/#{foo-bar}")')
+ assert_unquoted "url(http://sass-lang.com/images/\#{foo-bar})"
+ end
+
+ def test_hyphenated_variables
+ assert_equal("a-b", resolve("$a-b", {}, env("a-b" => Sass::Script::Value::String.new("a-b"))))
+ end
+
+ def test_ruby_equality
+ assert_equal eval('"foo"'), eval('"foo"')
+ assert_equal eval('1'), eval('1.0')
+ assert_equal eval('1 2 3.0'), eval('1 2 3')
+ assert_equal eval('1, 2, 3.0'), eval('1, 2, 3')
+ assert_equal eval('(1 2), (3, 4), (5 6)'), eval('(1 2), (3, 4), (5 6)')
+ refute_equal eval('1, 2, 3'), eval('1 2 3')
+ refute_equal eval('1'), eval('"1"')
+ end
+
+ def test_booleans
+ assert_equal "true", resolve("true")
+ assert_equal "false", resolve("false")
+ end
+
+ def test_null
+ assert_equal "", resolve("null")
+ end
+
+ def test_boolean_ops
+ assert_equal "true", resolve("true and true")
+ assert_equal "true", resolve("false or true")
+ assert_equal "true", resolve("true or false")
+ assert_equal "true", resolve("true or true")
+ assert_equal "false", resolve("false or false")
+ assert_equal "false", resolve("false and true")
+ assert_equal "false", resolve("true and false")
+ assert_equal "false", resolve("false and false")
+
+ assert_equal "true", resolve("not false")
+ assert_equal "false", resolve("not true")
+ assert_equal "true", resolve("not not true")
+
+ assert_equal "1", resolve("false or 1")
+ assert_equal "false", resolve("false and 1")
+ assert_equal "2", resolve("2 or 3")
+ assert_equal "3", resolve("2 and 3")
+
+ assert_equal "true", resolve("null or true")
+ assert_equal "true", resolve("true or null")
+ assert_equal "", resolve("null or null")
+ assert_equal "", resolve("null and true")
+ assert_equal "", resolve("true and null")
+ assert_equal "", resolve("null and null")
+
+ assert_equal "true", resolve("not null")
+
+ assert_equal "1", resolve("null or 1")
+ assert_equal "", resolve("null and 1")
+ end
+
+ def test_arithmetic_ops
+ assert_equal "2", resolve("1 + 1")
+ assert_equal "0", resolve("1 - 1")
+ assert_equal "8", resolve("2 * 4")
+ assert_equal "0.5", resolve("(2 / 4)")
+ assert_equal "2", resolve("(4 / 2)")
+
+ assert_equal "-1", resolve("-1")
+ end
+
+ def test_string_ops
+ assert_equal '"foo" "bar"', resolve('"foo" "bar"')
+ assert_equal "true 1", resolve('true 1')
+ assert_equal '"foo", "bar"', resolve("'foo' , 'bar'")
+ assert_equal "true, 1", resolve('true , 1')
+ assert_equal "foobar", resolve('"foo" + "bar"')
+ assert_equal "\nfoo\nxyz", resolve('"\a foo" + "\axyz"')
+ assert_equal "true1", resolve('true + 1')
+ assert_equal '"foo"-"bar"', resolve("'foo' - 'bar'")
+ assert_equal "true-1", resolve('true - 1')
+ assert_equal '"foo"/"bar"', resolve('"foo" / "bar"')
+ assert_equal "true/1", resolve('true / 1')
+
+ assert_equal '-"bar"', resolve("- 'bar'")
+ assert_equal "-true", resolve('- true')
+ assert_equal '/"bar"', resolve('/ "bar"')
+ assert_equal "/true", resolve('/ true')
+ end
+
+ def test_relational_ops
+ assert_equal "false", resolve("1 > 2")
+ assert_equal "false", resolve("2 > 2")
+ assert_equal "true", resolve("3 > 2")
+ assert_equal "false", resolve("1 >= 2")
+ assert_equal "true", resolve("2 >= 2")
+ assert_equal "true", resolve("3 >= 2")
+ assert_equal "true", resolve("1 < 2")
+ assert_equal "false", resolve("2 < 2")
+ assert_equal "false", resolve("3 < 2")
+ assert_equal "true", resolve("1 <= 2")
+ assert_equal "true", resolve("2 <= 2")
+ assert_equal "false", resolve("3 <= 2")
+ end
+
+ def test_null_ops
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "null plus 1".') {eval("null + 1")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "null minus 1".') {eval("null - 1")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "null times 1".') {eval("null * 1")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "null div 1".') {eval("null / 1")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "null mod 1".') {eval("null % 1")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "1 plus null".') {eval("1 + null")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "1 minus null".') {eval("1 - null")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "1 times null".') {eval("1 * null")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "1 div null".') {eval("1 / null")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "1 mod null".') {eval("1 % null")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "1 gt null".') {eval("1 > null")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "null lt 1".') {eval("null < 1")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: "null plus null".') {eval("null + null")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid null operation: ""foo" plus null".') {eval("foo + null")}
+ end
+
+ def test_equals
+ assert_equal("true", resolve('"foo" == $foo', {},
+ env("foo" => Sass::Script::Value::String.new("foo"))))
+ assert_equal "true", resolve("1 == 1.0")
+ assert_equal "true", resolve("false != true")
+ assert_equal "false", resolve("1em == 1px")
+ assert_equal "false", resolve("12 != 12")
+ assert_equal "true", resolve("(foo bar baz) == (foo bar baz)")
+ assert_equal "true", resolve("(foo, bar, baz) == (foo, bar, baz)")
+ assert_equal "true", resolve('((1 2), (3, 4), (5 6)) == ((1 2), (3, 4), (5 6))')
+ assert_equal "true", resolve('((1 2), (3 4)) == (1 2, 3 4)')
+ assert_equal "false", resolve('((1 2) 3) == (1 2 3)')
+ assert_equal "false", resolve('(1 (2 3)) == (1 2 3)')
+ assert_equal "false", resolve('((1, 2) (3, 4)) == (1, 2 3, 4)')
+ assert_equal "false", resolve('(1 2 3) == (1, 2, 3)')
+
+ assert_equal "true", resolve('null == null')
+ assert_equal "false", resolve('"null" == null')
+ assert_equal "false", resolve('0 == null')
+ assert_equal "false", resolve('() == null')
+
+ assert_equal "false", resolve('null != null')
+ assert_equal "true", resolve('"null" != null')
+ assert_equal "true", resolve('0 != null')
+ assert_equal "true", resolve('() != null')
+ end
+
+ def test_mod
+ assert_equal "5", resolve("29 % 12")
+ assert_equal "5px", resolve("29px % 12")
+ assert_equal "5px", resolve("29px % 12px")
+ end
+
+ def test_operation_precedence
+ assert_equal "false true", resolve("true and false false or true")
+ assert_equal "true", resolve("false and true or true and true")
+ assert_equal "true", resolve("1 == 2 or 3 == 3")
+ assert_equal "true", resolve("1 < 2 == 3 >= 3")
+ assert_equal "true", resolve("1 + 3 > 4 - 2")
+ assert_equal "11", resolve("1 + 2 * 3 + 4")
+ end
+
+ def test_functions
+ assert_equal "#80ff80", resolve("hsl(120, 100%, 75%)")
+ assert_equal "#81ff81", resolve("hsl(120, 100%, 75%) + #010001")
+ end
+
+ def test_operator_unit_conversion
+ assert_equal "1.1cm", resolve("1cm + 1mm")
+ assert_equal "2in", resolve("1in + 96px")
+ assert_equal "true", resolve("2mm < 1cm")
+ assert_equal "true", resolve("10mm == 1cm")
+ assert_equal "true", resolve("1.1cm == 11mm")
+
+ assert_warning(<<WARNING) {assert_equal "true", resolve("1 == 1cm")}
+DEPRECATION WARNING on line 1 of test_operator_unit_conversion_inline.sass:
+The result of `1 == 1cm` will be `false` in future releases of Sass.
+Unitless numbers will no longer be equal to the same numbers with units.
+WARNING
+ end
+
+ def test_length_units
+ assert_equal "2.54", resolve("(1in/1cm)")
+ assert_equal "2.3622", resolve("(1cm/1pc)")
+ assert_equal "4.23333", resolve("(1pc/1mm)")
+ assert_equal "2.83465", resolve("(1mm/1pt)")
+ assert_equal "1.33333", resolve("(1pt/1px)")
+ assert_equal "0.01042", resolve("(1px/1in)")
+ end
+
+ def test_angle_units
+ assert_equal "1.11111", resolve("(1deg/1grad)")
+ assert_equal "0.01571", resolve("(1grad/1rad)")
+ assert_equal "0.15915", resolve("(1rad/1turn)")
+ assert_equal "360", resolve("(1turn/1deg)")
+ end
+
+ def test_time_units
+ assert_equal "1000", resolve("(1s/1ms)")
+ end
+
+ def test_frequency_units
+ assert_equal "0.001", resolve("(1Hz/1kHz)")
+ end
+
+ def test_resolution_units
+ assert_equal "2.54", resolve("(1dpi/1dpcm)")
+ assert_equal "37.79528", resolve("(1dpcm/1dppx)")
+ assert_equal "0.01042", resolve("(1dppx/1dpi)")
+ end
+
+ def test_operations_have_options
+ assert_equal "Options defined!", resolve("assert_options(1 + 1)")
+ assert_equal "Options defined!", resolve("assert_options('bar' + 'baz')")
+ end
+
+ def test_slash_compiles_literally_when_left_alone
+ assert_equal "1px/2px", resolve("1px/2px")
+ assert_equal "1px/2px/3px/4px", resolve("1px/2px/3px/4px")
+
+ assert_equal "1px/2px redpx bluepx", resolve("1px/2px redpx bluepx")
+ assert_equal "foo 1px/2px/3px bar", resolve("foo 1px/2px/3px bar")
+ end
+
+ def test_slash_divides_with_parens
+ assert_equal "0.5", resolve("(1px/2px)")
+ assert_equal "0.5", resolve("(1px)/2px")
+ assert_equal "0.5", resolve("1px/(2px)")
+ end
+
+ def test_slash_divides_with_other_arithmetic
+ assert_equal "0.5px", resolve("1px*1px/2px")
+ assert_equal "0.5px", resolve("1px/2px*1px")
+ assert_equal "0.5", resolve("0+1px/2px")
+ assert_equal "0.5", resolve("1px/2px+0")
+ end
+
+ def test_slash_divides_with_variable
+ assert_equal "0.5", resolve("$var/2px", {}, env("var" => eval("1px")))
+ assert_equal "0.5", resolve("1px/$var", {}, env("var" => eval("2px")))
+ assert_equal "0.5", resolve("$var", {}, env("var" => eval("1px/2px")))
+ end
+
+ def test_non_ident_colors_with_wrong_number_of_digits
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid CSS after "": expected expression (e.g. 1px, bold), was "#1"') {eval("#1")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid CSS after "": expected expression (e.g. 1px, bold), was "#12"') {eval("#12")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid CSS after "": expected expression (e.g. 1px, bold), was "#1234"') {eval("#1234")}
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid CSS after "": expected expression (e.g. 1px, bold), was "#12345"') {eval("#12345")}
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "": expected expression (e.g. ' \
+ '1px, bold), was "#1234567"') {eval("#1234567")}
+ end
+
+ def test_case_insensitive_color_names
+ assert_equal "BLUE", resolve("BLUE")
+ assert_equal "rEd", resolve("rEd")
+ assert_equal "#7f4000", resolve("mix(GrEeN, ReD)")
+ end
+
+ def test_empty_list
+ assert_equal "1 2 3", resolve("1 2 () 3")
+ assert_equal "1 2 3", resolve("1 2 3 ()")
+ assert_equal "1 2 3", resolve("() 1 2 3")
+ assert_raise_message(Sass::SyntaxError, "() isn't a valid CSS value.") {resolve("()")}
+ assert_raise_message(Sass::SyntaxError, "() isn't a valid CSS value.") {resolve("nth(append((), ()),
1)")}
+ end
+
+ def test_list_with_nulls
+ assert_equal "1, 2, 3", resolve("1, 2, null, 3")
+ assert_equal "1 2 3", resolve("1 2 null 3")
+ assert_equal "1, 2, 3", resolve("1, 2, 3, null")
+ assert_equal "1 2 3", resolve("1 2 3 null")
+ assert_equal "1, 2, 3", resolve("null, 1, 2, 3")
+ assert_equal "1 2 3", resolve("null 1 2 3")
+ end
+
+ def test_map_can_have_trailing_comma
+ assert_equal("(foo: 1, bar: 2)", eval("(foo: 1, bar: 2,)").to_sass)
+ end
+
+ def test_list_can_have_trailing_comma
+ assert_equal("1, 2, 3", resolve("1, 2, 3,"))
+ end
+
+ def test_trailing_comma_defines_singleton_list
+ assert_equal("1 2 3", resolve("nth((1 2 3,), 1)"))
+ end
+
+ def test_map_cannot_have_duplicate_keys
+ assert_raise_message(Sass::SyntaxError, 'Duplicate key "foo" in map (foo: bar, foo: baz).') do
+ eval("(foo: bar, foo: baz)")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Duplicate key "foo" in map (foo: bar, fo + o: baz).') do
+ eval("(foo: bar, fo + o: baz)")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Duplicate key "foo" in map (foo: bar, "foo": baz).') do
+ eval("(foo: bar, 'foo': baz)")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Duplicate key 2px in map (2px: bar, 1px + 1px: baz).') do
+ eval("(2px: bar, 1px + 1px: baz)")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Duplicate key #0000ff in map (blue: bar, #00f: baz).') do
+ eval("(blue: bar, #00f: baz)")
+ end
+ end
+
+ def test_non_duplicate_map_keys
+ # These shouldn't throw errors
+ eval("(foo: foo, bar: bar)")
+ eval("(2px: foo, 2: bar)")
+ eval("(2px: foo, 2em: bar)")
+ eval("('2px': foo, 2px: bar)")
+ end
+
+ def test_map_syntax_errors
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "(foo:": expected expression (e.g. 1px,
bold), was ")"') do
+ eval("(foo:)")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "(": expected ")", was ":bar)"') do
+ eval("(:bar)")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "(foo, bar": expected ")", was ": baz)"') do
+ eval("(foo, bar: baz)")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "(foo: bar, baz": expected ":", was ")"') do
+ eval("(foo: bar, baz)")
+ end
+ end
+
+ def test_deep_argument_error_not_unwrapped
+ # JRuby (as of 1.6.7.2) offers no way of distinguishing between
+ # argument errors caused by programming errors in a function and
+ # argument errors explicitly thrown within that function.
+ return if RUBY_PLATFORM =~ /java/
+
+ # Don't validate the message; it's different on Rubinius.
+ assert_raises(ArgumentError) {resolve("arg-error()")}
+ end
+
+ def test_shallow_argument_error_unwrapped
+ assert_raise_message(Sass::SyntaxError, "wrong number of arguments (1 for 0) for `arg-error'")
{resolve("arg-error(1)")}
+ end
+
+ def test_boolean_ops_short_circuit
+ assert_equal "false", resolve("$ie and $ie <= 7", {}, env('ie' => Sass::Script::Value::Bool.new(false)))
+ assert_equal "true", resolve("$ie or $undef", {}, env('ie' => Sass::Script::Value::Bool.new(true)))
+ end
+
+ def test_selector
+ env = Sass::Environment.new
+ assert_equal "true", resolve("& == null", {}, env)
+
+ env.selector = selector('.foo.bar .baz.bang, .bip.bop')
+ assert_equal ".foo.bar .baz.bang, .bip.bop", resolve("&", {}, env)
+ assert_equal ".foo.bar .baz.bang", resolve("nth(&, 1)", {}, env)
+ assert_equal ".bip.bop", resolve("nth(&, 2)", {}, env)
+ assert_equal ".foo.bar", resolve("nth(nth(&, 1), 1)", {}, env)
+ assert_equal ".baz.bang", resolve("nth(nth(&, 1), 2)", {}, env)
+ assert_equal ".bip.bop", resolve("nth(nth(&, 2), 1)", {}, env)
+ assert_equal "string", resolve("type-of(nth(nth(&, 1), 1))", {}, env)
+
+ env.selector = selector('.foo > .bar')
+ assert_equal ".foo > .bar", resolve("&", {}, env)
+ assert_equal ".foo > .bar", resolve("nth(&, 1)", {}, env)
+ assert_equal ".foo", resolve("nth(nth(&, 1), 1)", {}, env)
+ assert_equal ">", resolve("nth(nth(&, 1), 2)", {}, env)
+ assert_equal ".bar", resolve("nth(nth(&, 1), 3)", {}, env)
+ end
+
+ def test_selector_with_newlines
+ env = Sass::Environment.new
+ env.selector = selector(".foo.bar\n.baz.bang,\n\n.bip.bop")
+ assert_equal ".foo.bar .baz.bang, .bip.bop", resolve("&", {}, env)
+ assert_equal ".foo.bar .baz.bang", resolve("nth(&, 1)", {}, env)
+ assert_equal ".bip.bop", resolve("nth(&, 2)", {}, env)
+ assert_equal ".foo.bar", resolve("nth(nth(&, 1), 1)", {}, env)
+ assert_equal ".baz.bang", resolve("nth(nth(&, 1), 2)", {}, env)
+ assert_equal ".bip.bop", resolve("nth(nth(&, 2), 1)", {}, env)
+ assert_equal "string", resolve("type-of(nth(nth(&, 1), 1))", {}, env)
+ end
+
+ def test_setting_global_variable_globally
+ assert_no_warning {assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))}
+.foo {
+ a: 1; }
+
+.bar {
+ b: 2; }
+CSS
+$var: 1;
+
+.foo {
+ a: $var;
+}
+
+$var: 2;
+
+.bar {
+ b: $var;
+}
+SCSS
+ end
+
+ def test_setting_global_variable_locally
+ assert_no_warning {assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))}
+.bar {
+ a: x;
+ b: y;
+ c: z; }
+CSS
+$var1: 1;
+$var3: 3;
+
+.foo {
+ $var1: x !global;
+ $var2: y !global;
+ @each $var3 in _ {
+ $var3: z !global;
+ }
+}
+
+.bar {
+ a: $var1;
+ b: $var2;
+ c: $var3;
+}
+SCSS
+ end
+
+ def test_setting_global_variable_locally_with_default
+ assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))
+.bar {
+ a: 1;
+ b: y;
+ c: z; }
+CSS
+$var1: 1;
+
+.foo {
+ $var1: x !global !default;
+ $var2: y !global !default;
+ @each $var3 in _ {
+ $var3: z !global !default;
+ }
+}
+
+.bar {
+ a: $var1;
+ b: $var2;
+ c: $var3;
+}
+SCSS
+ end
+
+ def test_setting_local_variable
+ assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))
+.a {
+ value: inside; }
+
+.b {
+ value: outside; }
+CSS
+$var: outside;
+
+.a {
+ $var: inside;
+ value: $var;
+}
+
+.b {
+ value: $var;
+}
+SCSS
+ end
+
+ def test_setting_local_variable_from_inner_scope
+ assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))
+.a .b {
+ value: inside; }
+.a .c {
+ value: inside; }
+CSS
+.a {
+ $var: outside;
+
+ .b {
+ $var: inside;
+ value: $var;
+ }
+
+ .c {
+ value: $var;
+ }
+}
+SCSS
+ end
+
+ def test_if_can_assign_to_global_variables
+ assert_equal <<CSS, render(<<SCSS, :syntax => :scss)
+.a {
+ b: 2; }
+CSS
+$var: 1;
+ if true {$var: 2}
+.a {b: $var}
+SCSS
+ end
+
+ def test_else_can_assign_to_global_variables
+ assert_equal <<CSS, render(<<SCSS, :syntax => :scss)
+.a {
+ b: 2; }
+CSS
+$var: 1;
+ if false {}
+ else {$var: 2}
+.a {b: $var}
+SCSS
+ end
+
+ def test_for_can_assign_to_global_variables
+ assert_equal <<CSS, render(<<SCSS, :syntax => :scss)
+.a {
+ b: 2; }
+CSS
+$var: 1;
+ for $i from 1 to 2 {$var: 2}
+.a {b: $var}
+SCSS
+ end
+
+ def test_each_can_assign_to_global_variables
+ assert_equal <<CSS, render(<<SCSS, :syntax => :scss)
+.a {
+ b: 2; }
+CSS
+$var: 1;
+ each $a in 1 {$var: 2}
+.a {b: $var}
+SCSS
+ end
+
+ def test_while_can_assign_to_global_variables
+ assert_equal <<CSS, render(<<SCSS, :syntax => :scss)
+.a {
+ b: 2; }
+CSS
+$var: 1;
+ while $var != 2 {$var: 2}
+.a {b: $var}
+SCSS
+ end
+
+ def test_if_doesnt_leak_local_variables
+ assert_raise_message(Sass::SyntaxError, 'Undefined variable: "$var".') do
+ render(<<SCSS, :syntax => :scss)
+ if true {$var: 1}
+.a {b: $var}
+SCSS
+ end
+ end
+
+ def test_else_doesnt_leak_local_variables
+ assert_raise_message(Sass::SyntaxError, 'Undefined variable: "$var".') do
+ render(<<SCSS, :syntax => :scss)
+ if false {}
+ else {$var: 1}
+.a {b: $var}
+SCSS
+ end
+ end
+
+ def test_for_doesnt_leak_local_variables
+ assert_raise_message(Sass::SyntaxError, 'Undefined variable: "$var".') do
+ render(<<SCSS, :syntax => :scss)
+ for $i from 1 to 2 {$var: 1}
+.a {b: $var}
+SCSS
+ end
+ end
+
+ def test_each_doesnt_leak_local_variables
+ assert_raise_message(Sass::SyntaxError, 'Undefined variable: "$var".') do
+ render(<<SCSS, :syntax => :scss)
+ each $a in 1 {$var: 1}
+.a {b: $var}
+SCSS
+ end
+ end
+
+ def test_while_doesnt_leak_local_variables
+ assert_raise_message(Sass::SyntaxError, 'Undefined variable: "$var".') do
+ render(<<SCSS, :syntax => :scss)
+$iter: true;
+ while $iter {
+ $var: 1;
+ $iter: false;
+}
+.a {b: $var}
+SCSS
+ end
+ end
+
+ def test_color_format_is_preserved_by_default
+ assert_equal "blue", resolve("blue")
+ assert_equal "bLuE", resolve("bLuE")
+ assert_equal "#00f", resolve("#00f")
+ assert_equal "blue #00F", resolve("blue #00F")
+ assert_equal "blue", resolve("nth(blue #00F, 1)")
+ assert_equal "#00F", resolve("nth(blue #00F, 2)")
+ end
+
+ def test_color_format_isnt_always_preserved_in_compressed_style
+ assert_equal "red", resolve("red", :style => :compressed)
+ assert_equal "red", resolve("#f00", :style => :compressed)
+ assert_equal "red red", resolve("red #f00", :style => :compressed)
+ assert_equal "red", resolve("nth(red #f00, 2)", :style => :compressed)
+ end
+
+ def test_color_format_is_sometimes_preserved_in_compressed_style
+ assert_equal "ReD", resolve("ReD", :style => :compressed)
+ assert_equal "blue", resolve("blue", :style => :compressed)
+ assert_equal "#00f", resolve("#00f", :style => :compressed)
+ end
+
+ def test_color_format_isnt_preserved_when_modified
+ assert_equal "magenta", resolve("#f00 + #00f")
+ end
+
+ def test_ids
+ assert_equal "#foo", resolve("#foo")
+ assert_equal "#abcd", resolve("#abcd")
+ assert_equal "#abc-def", resolve("#abc-def")
+ assert_equal "#abc_def", resolve("#abc_def")
+ assert_equal "#uvw-xyz", resolve("#uvw-xyz")
+ assert_equal "#uvw_xyz", resolve("#uvw_xyz")
+ assert_equal "#uvwxyz", resolve("#uvw + xyz")
+ end
+
+ def test_scientific_notation
+ assert_equal "2000", resolve("2e3")
+ assert_equal "2000", resolve("2E3")
+ assert_equal "2000", resolve("2e+3")
+ assert_equal "2000em", resolve("2e3em")
+ assert_equal "25000000000", resolve("2.5e10")
+ assert_equal "0.1234", resolve("1234e-4")
+ assert_equal "12.34", resolve("1.234e1")
+ end
+
+ def test_identifier_units
+ assert_equal "5-foo", resolve("2-foo + 3-foo")
+ assert_equal "5-foo-", resolve("2-foo- + 3-foo-")
+ assert_equal "5-\\u2603", resolve("2-\\u2603 + 3-\\u2603")
+ end
+
+ def test_backslash_newline_in_string
+ assert_equal 'foobar', resolve("\"foo\\\nbar\"")
+ assert_equal 'foobar', resolve("'foo\\\nbar'")
+ end
+
+ def test_unclosed_special_fun
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(foo()": expected ")", was ""') do
+ resolve("calc(foo()")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(#{\')\'}": expected ")", was ""') do
+ resolve("calc(\#{')'}")
+ end
+ assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(#{foo": expected "}", was ""') do
+ resolve("calc(\#{foo")
+ end
+ end
+
+ def test_special_fun_with_interpolation
+ assert_equal "calc())", resolve("calc(\#{')'})")
+ assert_equal "calc(# {foo})", resolve("calc(# {foo})")
+ end
+
+ # Regression Tests
+
+ def test_interpolation_after_string
+ assert_equal '"foobar" 2', resolve('"foobar" #{2}')
+ assert_equal "calc(1 + 2) 3", resolve('calc(1 + 2) #{3}')
+ end
+
+ def test_repeatedly_modified_color
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ link-color: #161C14;
+ link-color-hover: black;
+ link-color-tap: rgba(22, 28, 20, 0.3); }
+CSS
+$green: #161C14
+$link-color: $green
+$link-color-hover: darken($link-color, 10%)
+$link-color-tap: rgba($green, 0.3)
+
+a
+ link-color: $link-color
+ link-color-hover: $link-color-hover
+ link-color-tap: $link-color-tap
+SASS
+ end
+
+ def test_inspect_divided_numbers
+ assert_equal "1px/2px", resolve("inspect(1px/2px)")
+ assert_equal "0.5", resolve("inspect((1px/2px))")
+ end
+
+ def test_minus_without_whitespace
+ assert_equal "5px", resolve("15px-10px")
+ assert_equal "5px-", resolve("15px--10px-")
+ end
+
+ def test_minus_preceded_by_comment
+ assert_equal "15px -10px", resolve("15px/**/-10px")
+ end
+
+ def test_user_defined_function_forces_division
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 10px; }
+CSS
+ function foo()
+ @return 20px
+
+a
+ b: (foo() / 2)
+SASS
+
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 10px; }
+CSS
+ function foo()
+ @return 20px
+
+a
+ b: foo() / 2
+SASS
+end
+
+ def test_funcall_has_higher_precedence_than_color_name
+ assert_equal "teal(12)", resolve("teal(12)")
+ assert_equal "tealbang(12)", resolve("tealbang(12)")
+ assert_equal "teal-bang(12)", resolve("teal-bang(12)")
+ assert_equal "teal\\+bang(12)", resolve("teal\\+bang(12)")
+ end
+
+ def test_funcall_has_higher_precedence_than_true_false_null
+ assert_equal "teal(12)", resolve("teal(12)")
+ assert_equal "tealbang(12)", resolve("tealbang(12)")
+ assert_equal "teal-bang(12)", resolve("teal-bang(12)")
+ assert_equal "teal\\+bang(12)", resolve("teal\\+bang(12)")
+ end
+
+ def test_and_or_not_disallowed_as_function_names
+ %w[and or not].each do |name|
+ assert_raise_message(Sass::SyntaxError, "Invalid function name \"#{name}\".") do
+ render(<<SASS)
+ function #{name}()
+ @return null
+SASS
+ end
+ end
+ end
+
+ def test_interpolation_after_hash
+ assert_equal "#2", resolve('"##{1 + 1}"')
+ end
+
+ def test_misplaced_comma_in_funcall
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid CSS after "foo(bar, ": expected function argument, was ")"') {eval('foo(bar, )')}
+ end
+
+ def test_color_prefixed_identifier
+ assert_equal "tealbang", resolve("tealbang")
+ assert_equal "teal-bang", resolve("teal-bang")
+ end
+
+ def test_op_prefixed_identifier
+ assert_equal "notbang", resolve("notbang")
+ assert_equal "not-bang", resolve("not-bang")
+ assert_equal "or-bang", resolve("or-bang")
+ assert_equal "and-bang", resolve("and-bang")
+ end
+
+ def test_number_initialization
+ assert_equal Sass::Script::Value::Number.new(10, ["px"]), Sass::Script::Value::Number.new(10, "px")
+ assert_equal Sass::Script::Value::Number.new(10, ["px"], ["em"]), Sass::Script::Value::Number.new(10,
"px", "em")
+ end
+
+ def test_is_unit
+ assert Sass::Script::Value::Number.new(10, "px").is_unit?("px")
+ assert Sass::Script::Value::Number.new(10).is_unit?(nil)
+ assert !Sass::Script::Value::Number.new(10, "px", "em").is_unit?("px")
+ assert !Sass::Script::Value::Number.new(10, [], "em").is_unit?("em")
+ assert !Sass::Script::Value::Number.new(10, ["px", "em"]).is_unit?("px")
+ end
+
+ def test_rename_redirect
+ assert_no_warning do
+ assert_equal Sass::Script::Value::Base, Sass::Script::Literal
+ assert_equal Sass::Script::Tree::Node, Sass::Script::Node
+ assert_equal Sass::Script::Tree::Operation, Sass::Script::Operation
+ assert_equal Sass::Script::Value::String, Sass::Script::String
+ end
+ end
+
+ def test_number_printing
+ assert_equal "1", resolve("1")
+ assert_equal "1", resolve("1.0")
+ assert_equal "1000000000", resolve("1000000000")
+ assert_equal "0.00001", resolve("0.00001")
+ assert_equal "1.12121", resolve("1.121214")
+ assert_equal "1.12122", resolve("1.121215")
+ assert_equal "Infinity", resolve("(1.0/0.0)")
+ assert_equal "-Infinity", resolve("(-1.0/0.0)")
+ assert_equal "NaN", resolve("(0.0/0.0)")
+ end
+
+ private
+
+ def resolve(str, opts = {}, environment = env)
+ munge_filename opts
+ val = eval(str, opts, environment)
+ assert_kind_of Sass::Script::Value::Base, val
+ val.is_a?(Sass::Script::Value::String) ? val.value : val.to_s
+ end
+
+ def resolve_quoted(str, opts = {}, environment = env)
+ munge_filename opts
+ val = eval(str, opts, environment)
+ assert_kind_of Sass::Script::Value::Base, val
+ val.to_s
+ end
+
+ def assert_unquoted(str, opts = {}, environment = env)
+ munge_filename opts
+ val = eval(str, opts, environment)
+ assert_kind_of Sass::Script::Value::String, val
+ assert_equal :identifier, val.type
+ end
+
+ def assert_quoted(str, opts = {}, environment = env)
+ munge_filename opts
+ val = eval(str, opts, environment)
+ assert_kind_of Sass::Script::Value::String, val
+ assert_equal :string, val.type
+ end
+
+ def eval(str, opts = {}, environment = env)
+ munge_filename opts
+ Sass::Script.parse(str, opts.delete(:line) || 1,
+ opts.delete(:offset) || 0, opts).
+ perform(Sass::Environment.new(environment, opts))
+ end
+
+ def render(sass, options = {})
+ munge_filename options
+ Sass::Engine.new(sass, options).render
+ end
+
+ def env(hash = {})
+ env = Sass::Environment.new
+ hash.each {|k, v| env.set_var(k, v)}
+ env
+ end
+
+ def selector(str)
+ parser = Sass::SCSS::StaticParser.new(
+ str, filename_for_test, Sass::Importers::Filesystem.new('.'))
+ parser.parse_selector
+ end
+
+ def test_null_is_a_singleton
+ assert_same Sass::Script::Value::Null.new, Sass::Script::Value::Null.new
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/scss/css_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/scss/css_test.rb
new file mode 100755
index 0000000..dd47988
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/scss/css_test.rb
@@ -0,0 +1,1256 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/test_helper'
+require 'sass/scss/css_parser'
+
+# These tests just test the parsing of CSS
+# (both standard and any hacks we intend to support).
+# Tests of SCSS-specific behavior go in scss_test.rb.
+class ScssCssTest < MiniTest::Test
+ include ScssTestHelper
+
+ def test_basic_scss
+ assert_parses <<SCSS
+selector {
+ property: value;
+ property2: value; }
+SCSS
+
+ assert_equal <<CSS, render('sel{p:v}')
+sel {
+ p: v; }
+CSS
+ end
+
+ def test_empty_rule
+ assert_equal "", render("#foo .bar {}")
+ assert_equal "", render(<<SCSS)
+#foo .bar {
+}
+SCSS
+ end
+
+ def test_cdo_and_cdc_ignored_at_toplevel
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: baz; }
+
+bar {
+ bar: baz; }
+
+baz {
+ bar: baz; }
+CSS
+foo {bar: baz}
+<!--
+bar {bar: baz}
+-->
+baz {bar: baz}
+SCSS
+ end
+
+ if Sass::Util.ruby1_8?
+ def test_unicode
+ assert_parses <<SCSS
+ charset "UTF-8";
+foo {
+ bar: föö bâr; }
+SCSS
+ assert_parses <<SCSS
+foo {
+ bar: föö bâr; }
+SCSS
+ end
+ else
+ def test_unicode
+ assert_parses <<SCSS
+ charset "UTF-8";
+foo {
+ bar: föö bâr; }
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+ charset "UTF-8";
+foo {
+ bar: föö bâr; }
+CSS
+foo {
+ bar: föö bâr; }
+SCSS
+ end
+ end
+
+ def test_invisible_comments
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: d; }
+CSS
+foo {a: /* b; c: */ d}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: d; }
+CSS
+foo {a /*: b; c */: d}
+SCSS
+ end
+
+ def test_crazy_comments
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/t040109-c17-comments-00-b.xht
+ assert_equal <<CSS, render(<<SCSS)
+/* This is a CSS comment. */
+.one {
+ color: green; }
+
+/* Another comment */
+/* The following should not be used:
+.two {color: red;} */
+.three {
+ color: green;
+ /* color: red; */ }
+
+/**
+.four {color: red;} */
+.five {
+ color: green; }
+
+/**/
+.six {
+ color: green; }
+
+/*********/
+.seven {
+ color: green; }
+
+/* a comment **/
+.eight {
+ color: green; }
+CSS
+/* This is a CSS comment. */
+.one {color: green;} /* Another comment */
+/* The following should not be used:
+.two {color: red;} */
+.three {color: green; /* color: red; */}
+/**
+.four {color: red;} */
+.five {color: green;}
+/**/
+.six {color: green;}
+/*********/
+.seven {color: green;}
+/* a comment **/
+.eight {color: green;}
+SCSS
+ end
+
+ def test_rule_comments
+ assert_parses <<SCSS
+/* Foo */
+.foo {
+ a: b; }
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+/* Foo
+ * Bar */
+.foo {
+ a: b; }
+CSS
+/* Foo
+ * Bar */.foo {
+ a: b; }
+SCSS
+ end
+
+ def test_property_comments
+ assert_parses <<SCSS
+.foo {
+ /* Foo */
+ a: b; }
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ /* Foo
+ * Bar */
+ a: b; }
+CSS
+.foo {
+ /* Foo
+ * Bar */a: b; }
+SCSS
+ end
+
+ def test_selector_comments
+ assert_equal <<CSS, render(<<SCSS)
+.foo #bar:baz(bip) {
+ a: b; }
+CSS
+.foo /* .a #foo */ #bar:baz(/* bang )*/ bip) {
+ a: b; }
+SCSS
+ end
+
+ def test_lonely_comments
+ assert_parses <<SCSS
+/* Foo
+ * Bar */
+SCSS
+ assert_parses <<SCSS
+.foo {
+ /* Foo
+ * Bar */ }
+SCSS
+ end
+
+ def test_multiple_comments
+ assert_parses <<SCSS
+/* Foo
+ * Bar */
+/* Baz
+ * Bang */
+SCSS
+ assert_parses <<SCSS
+.foo {
+ /* Foo
+ * Bar */
+ /* Baz
+ * Bang */ }
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ /* Foo Bar */
+ /* Baz Bang */ }
+CSS
+.foo {
+ /* Foo Bar *//* Baz Bang */ }
+SCSS
+ end
+
+ def test_bizarrely_formatted_comments
+ assert_parses <<SCSS
+.foo {
+ /* Foo
+Bar
+ Baz */
+ a: b; }
+SCSS
+ assert_parses <<SCSS
+.foo {
+ /* Foo
+Bar
+ Baz */
+ a: b; }
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ /* Foo
+Bar */
+ a: b; }
+CSS
+.foo {/* Foo
+ Bar */
+ a: b; }
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ /* Foo
+ Bar
+Baz */
+ a: b; }
+CSS
+.foo {/* Foo
+ Bar
+ Baz */
+ a: b; }
+SCSS
+ end
+
+ ## Declarations
+
+ def test_vendor_properties
+ assert_parses <<SCSS
+foo {
+ -moz-foo-bar: blat;
+ -o-flat-blang: wibble; }
+SCSS
+ end
+
+ def test_empty_declarations
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: baz; }
+CSS
+foo {;;;;
+ bar: baz;;;;
+ ;;}
+SCSS
+ end
+
+ def test_basic_property_types
+ assert_parses <<SCSS
+foo {
+ a: 2;
+ b: 2.3em;
+ c: 50%;
+ d: "fraz bran";
+ e: flanny-blanny-blan;
+ f: url(http://sass-lang.com);
+ g: U+ffa?;
+ h: #aabbcc; }
+SCSS
+ end
+
+ def test_functions
+ assert_parses <<SCSS
+foo {
+ a: foo-bar(12);
+ b: -foo-bar-baz(13, 14 15); }
+SCSS
+ end
+
+ def test_unary_minus
+ assert_parses <<SCSS
+foo {
+ a: -2;
+ b: -2.3em;
+ c: -50%;
+ d: -foo(bar baz); }
+SCSS
+ end
+
+ def test_operators
+ assert_parses <<SCSS
+foo {
+ a: foo bar baz;
+ b: foo, #aabbcc, -12;
+ c: 1px/2px/-3px;
+ d: foo bar, baz/bang; }
+SCSS
+ end
+
+ def test_important
+ assert_parses <<SCSS
+foo {
+ a: foo !important;
+ b: foo bar !important;
+ b: foo, bar !important; }
+SCSS
+ end
+
+ def test_initial_hyphen
+ assert_parses <<SCSS
+foo {
+ a: -moz-bar-baz;
+ b: foo -o-bar-baz; }
+SCSS
+ end
+
+ def test_ms_long_filter_syntax
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300,
endColorstr=#ff000000);
+ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300,
endColorstr=#ff000000); }
+CSS
+foo {
+ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300,
endColorstr=#ff000000);
+ filter:progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300,
endColorstr=#ff000000); }
+SCSS
+ end
+
+ def test_ms_short_filter_syntax
+ assert_parses <<SCSS
+foo {
+ filter: alpha(opacity=20);
+ filter: alpha(opacity=20, enabled=true);
+ filter: blaznicate(foo=bar, baz=bang bip, bart=#fa4600); }
+SCSS
+ end
+
+ def test_declaration_hacks
+ assert_parses <<SCSS
+foo {
+ _name: val;
+ *name: val;
+ :name: val;
+ .name: val;
+ #name: val;
+ name/**/: val;
+ name/*\\**/: val;
+ name: val; }
+SCSS
+ end
+
+ def test_trailing_hash_hack
+ assert_parses <<SCSS
+foo {
+ foo: bar;
+ #baz: bang;
+ #bip: bop; }
+SCSS
+ end
+
+ def test_zero_arg_functions
+ assert_parses <<SCSS
+foo {
+ a: foo();
+ b: bar baz-bang() bip; }
+SCSS
+ end
+
+ def test_expression_function
+ assert_parses <<SCSS
+foo {
+ a: 12px expression(1 + (3 / Foo.bar("baz" + "bang") + function() {return 12;}) % 12); }
+SCSS
+ end
+
+ def test_calc_function
+ assert_parses <<SCSS
+foo {
+ a: 12px calc(100%/3 - 2*1em - 2*1px);
+ b: 12px -moz-calc(100%/3 - 2*1em - 2*1px);
+ b: 12px -webkit-calc(100%/3 - 2*1em - 2*1px);
+ b: 12px -foobar-calc(100%/3 - 2*1em - 2*1px); }
+SCSS
+ end
+
+ def test_element_function
+ assert_parses <<SCSS
+foo {
+ a: -moz-element(#foo);
+ b: -webkit-element(#foo);
+ b: -foobar-element(#foo); }
+SCSS
+ end
+
+ def test_unary_ops
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: -0.5em;
+ b: +0.5em;
+ c: -foo(12px);
+ d: +foo(12px); }
+CSS
+foo {
+ a: -0.5em;
+ b: +0.5em;
+ c: -foo(12px);
+ d: +foo(12px); }
+SCSS
+ end
+
+ def test_css_string_escapes
+ assert_parses <<SCSS
+foo {
+ a: "\\foo bar";
+ b: "foo\\ bar";
+ c: "\\2022 \\0020";
+ d: "foo\\\\bar";
+ e: "foo\\"'bar"; }
+SCSS
+ end
+
+ def test_css_ident_escapes
+ assert_parses <<SCSS
+foo {
+ a: \\foo bar;
+ b: foo\\ bar;
+ c: \\2022 \\0020;
+ d: foo\\\\bar;
+ e: foo\\"\\'bar; }
+SCSS
+ end
+
+ ## Directives
+
+ def test_namespace_directive
+ assert_parses '@namespace "http://www.w3.org/Profiles/xhtml1-strict";'
+ assert_parses '@namespace url(http://www.w3.org/Profiles/xhtml1-strict);'
+ assert_parses '@namespace html url("http://www.w3.org/Profiles/xhtml1-strict");'
+ end
+
+ def test_media_directive
+ assert_parses <<SCSS
+ media all {
+ rule1 {
+ prop: val; }
+
+ rule2 {
+ prop: val; } }
+SCSS
+ assert_parses <<SCSS
+ media screen, print {
+ rule1 {
+ prop: val; }
+
+ rule2 {
+ prop: val; } }
+SCSS
+ end
+
+ def test_media_directive_with_keywords
+ assert_parses <<SCSS
+ media screen and (-webkit-min-device-pixel-ratio: 0) {
+ a: b; }
+SCSS
+ assert_parses <<SCSS
+ media only screen, print and (foo: 0px) and (bar: flam(12px solid)) {
+ a: b; }
+SCSS
+ end
+
+ def test_import_directive
+ assert_parses '@import "foo.css";'
+ assert_parses '@import url("foo.css");'
+ assert_parses "@import url('foo.css');"
+ assert_parses '@import url(foo.css);'
+
+ assert_equal <<CSS, render(<<SCSS)
+ import "foo.css";
+CSS
+ import 'foo.css';
+SCSS
+ end
+
+ def test_import_directive_with_backslash_newline
+ assert_equal <<CSS, render(<<SCSS)
+ import "foobar.css";
+CSS
+ import "foo\\
+bar.css";
+SCSS
+ end
+
+ def test_string_import_directive_with_media
+ assert_parses '@import "foo.css" screen;'
+ assert_parses '@import "foo.css" screen, print;'
+ assert_parses '@import "foo.css" screen, print and (foo: 0);'
+ assert_parses '@import "foo.css" screen, only print, screen and (foo: 0);'
+ end
+
+ def test_url_import_directive_with_media
+ assert_parses '@import url("foo.css") screen;'
+ assert_parses '@import url("foo.css") screen, print;'
+ assert_parses '@import url("foo.css") screen, print and (foo: 0);'
+ assert_parses '@import url("foo.css") screen, only print, screen and (foo: 0);'
+ end
+
+ def test_page_directive
+ assert_parses <<SCSS
+ page {
+ prop1: val;
+ prop2: val; }
+SCSS
+ assert_parses <<SCSS
+ page flap {
+ prop1: val;
+ prop2: val; }
+SCSS
+ assert_parses <<SCSS
+ page :first {
+ prop1: val;
+ prop2: val; }
+SCSS
+ assert_parses <<SCSS
+ page flap:first {
+ prop1: val;
+ prop2: val; }
+SCSS
+ end
+
+ def test_blockless_directive_without_semicolon
+ assert_equal "@foo \"bar\";\n", render('@foo "bar"')
+ end
+
+ def test_directive_with_lots_of_whitespace
+ assert_equal "@foo \"bar\";\n", render('@foo "bar" ;')
+ end
+
+ def test_empty_blockless_directive
+ assert_parses "@foo;"
+ end
+
+ def test_multiple_blockless_directives
+ assert_parses <<SCSS
+ foo bar;
+ bar baz;
+SCSS
+ end
+
+ def test_empty_block_directive
+ assert_parses "@foo {}"
+ assert_equal "@foo {}\n", render(<<SCSS)
+ foo {
+}
+SCSS
+ end
+
+ def test_multiple_block_directives
+ assert_parses <<SCSS
+ foo bar {
+ a: b; }
+ bar baz {
+ c: d; }
+SCSS
+ end
+
+ def test_block_directive_with_rule_and_property
+ assert_parses <<SCSS
+ foo {
+ rule {
+ a: b; }
+
+ a: b; }
+SCSS
+ end
+
+ def test_block_directive_with_semicolon
+ assert_equal <<CSS, render(<<SCSS)
+ foo {
+ a: b; }
+ bar {
+ a: b; }
+CSS
+ foo {a:b};
+ bar {a:b};
+SCSS
+ end
+
+ def test_moz_document_directive
+ assert_equal <<CSS, render(<<SCSS)
+ -moz-document url(http://www.w3.org/),
+ url-prefix(http://www.w3.org/Style/),
+ domain(mozilla.org),
+ regexp("^https:.*") {
+ .foo {
+ a: b; } }
+CSS
+ -moz-document url(http://www.w3.org/),
+ url-prefix(http://www.w3.org/Style/),
+ domain(mozilla.org),
+ regexp("^https:.*") {
+ .foo {a: b}
+}
+SCSS
+ end
+
+ def test_supports
+ assert_equal <<CSS, render(<<SCSS)
+ supports (a: b) and (c: d) or (not (d: e)) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
+ .foo {
+ a: b; } }
+ supports (a: b) {
+ .foo {
+ a: b; } }
+CSS
+ supports (a: b) and (c: d) or (not (d: e)) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
+ .foo {
+ a: b;
+ }
+}
+
+ supports (a: b) {
+ .foo {
+ a: b;
+ }
+}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+ -prefix-supports (a: b) and (c: d) or (not (d: e)) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
+ .foo {
+ a: b; } }
+CSS
+ -prefix-supports (a: b) and (c: d) or (not (d: e)) and ((not (f: g)) or (not ((h: i) and (j: k)))) {
+ .foo {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_keyframes
+ assert_equal <<CSS, render(<<SCSS)
+ keyframes identifier {
+ 0% {
+ top: 0;
+ left: 0; }
+ 30% {
+ top: 50px; }
+ 68%, 72% {
+ left: 50px; }
+ 100% {
+ top: 100px;
+ left: 100%; } }
+CSS
+ keyframes identifier {
+ 0% {top: 0; left: 0}
+ 30% {top: 50px}
+ 68%, 72% {left: 50px}
+ 100% {top: 100px; left: 100%}
+}
+SCSS
+ end
+
+ def test_keyframes_with_empty_rules
+ assert_equal <<CSS, render(<<SCSS)
+ keyframes identifier {
+ 50% {
+ background-color: black; } }
+CSS
+ keyframes identifier {
+ 0% {}
+ 50% {background-color: black}
+ 100% {}
+}
+SCSS
+ end
+
+ def test_keyframes_with_custom_identifiers
+ assert_equal <<CSS, render(<<SCSS)
+ -skrollr-keyframes identifier {
+ center-top {
+ left: 100%; }
+ top-bottom {
+ left: 0%; } }
+CSS
+ -skrollr-keyframes identifier {
+ center-top {
+ left: 100%;
+ }
+
+ top-bottom {
+ left: 0%;
+ }
+}
+
+SCSS
+ end
+
+ ## Selectors
+
+ # Taken from http://dev.w3.org/csswg/selectors4/#overview
+ def test_summarized_selectors_with_element
+ assert_selector_parses('*')
+ assert_selector_parses('E')
+ assert_selector_parses('E:not(s)')
+ assert_selector_parses('E:not(s1, s2)')
+ assert_selector_parses('E:matches(s1, s2)')
+ assert_selector_parses('E:has(s1, s2)')
+ assert_selector_parses('E:has(> s1, > s2)')
+ assert_selector_parses('E.warning')
+ assert_selector_parses('E#myid')
+ assert_selector_parses('E[foo]')
+ assert_selector_parses('E[foo="bar"]')
+ assert_selector_parses('E[foo="bar" i]')
+ assert_selector_parses('E[foo~="bar"]')
+ assert_selector_parses('E[foo^="bar"]')
+ assert_selector_parses('E[foo$="bar"]')
+ assert_selector_parses('E[foo*="bar"]')
+ assert_selector_parses('E[foo|="en"]')
+ assert_selector_parses('E:dir(ltr)')
+ assert_selector_parses('E:lang(fr)')
+ assert_selector_parses('E:lang(zh, *-hant)')
+ assert_selector_parses('E:any-link')
+ assert_selector_parses('E:link')
+ assert_selector_parses('E:visited')
+ assert_selector_parses('E:local-link')
+ assert_selector_parses('E:local-link(0)')
+ assert_selector_parses('E:target')
+ assert_selector_parses('E:scope')
+ assert_selector_parses('E:current')
+ assert_selector_parses('E:current(s)')
+ assert_selector_parses('E:past')
+ assert_selector_parses('E:future')
+ assert_selector_parses('E:active')
+ assert_selector_parses('E:hover')
+ assert_selector_parses('E:focus')
+ assert_selector_parses('E:enabled')
+ assert_selector_parses('E:disabled')
+ assert_selector_parses('E:checked')
+ assert_selector_parses('E:indeterminate')
+ assert_selector_parses('E:default')
+ assert_selector_parses('E:in-range')
+ assert_selector_parses('E:out-of-range')
+ assert_selector_parses('E:required')
+ assert_selector_parses('E:optional')
+ assert_selector_parses('E:read-only')
+ assert_selector_parses('E:read-write')
+ assert_selector_parses('E:root')
+ assert_selector_parses('E:empty')
+ assert_selector_parses('E:first-child')
+ assert_selector_parses('E:nth-child(n)')
+ assert_selector_parses('E:last-child')
+ assert_selector_parses('E:nth-last-child(n)')
+ assert_selector_parses('E:only-child')
+ assert_selector_parses('E:first-of-type')
+ assert_selector_parses('E:nth-of-type(n)')
+ assert_selector_parses('E:last-of-type')
+ assert_selector_parses('E:nth-last-of-type(n)')
+ assert_selector_parses('E:only-of-type')
+ assert_selector_parses('E:nth-child(n of selector)')
+ assert_selector_parses('E:nth-last-child(n of selector)')
+ assert_selector_parses('E:nth-child(n)')
+ assert_selector_parses('E:nth-last-child(n)')
+ assert_selector_parses('E F')
+ assert_selector_parses('E > F')
+ assert_selector_parses('E + F')
+ assert_selector_parses('E ~ F')
+ assert_selector_parses('E /foo/ F')
+ silence_warnings {assert_selector_parses('E! > F')}
+
+ assert_selector_parses('E /ns|foo/ F')
+
+ # From http://dev.w3.org/csswg/css-scoping-1/
+ assert_selector_parses('E:host(s)')
+ assert_selector_parses('E:host-context(s)')
+ end
+
+ # Taken from http://dev.w3.org/csswg/selectors4/#overview, but without element
+ # names.
+ def test_more_summarized_selectors
+ assert_selector_parses(':not(s)')
+ assert_selector_parses(':not(s1, s2)')
+ assert_selector_parses(':matches(s1, s2)')
+ assert_selector_parses(':has(s1, s2)')
+ assert_selector_parses(':has(> s1, > s2)')
+ assert_selector_parses('.warning')
+ assert_selector_parses('#myid')
+ assert_selector_parses('[foo]')
+ assert_selector_parses('[foo="bar"]')
+ assert_selector_parses('[foo="bar" i]')
+ assert_selector_parses('[foo~="bar"]')
+ assert_selector_parses('[foo^="bar"]')
+ assert_selector_parses('[foo$="bar"]')
+ assert_selector_parses('[foo*="bar"]')
+ assert_selector_parses('[foo|="en"]')
+ assert_selector_parses(':dir(ltr)')
+ assert_selector_parses(':lang(fr)')
+ assert_selector_parses(':lang(zh, *-hant)')
+ assert_selector_parses(':any-link')
+ assert_selector_parses(':link')
+ assert_selector_parses(':visited')
+ assert_selector_parses(':local-link')
+ assert_selector_parses(':local-link(0)')
+ assert_selector_parses(':target')
+ assert_selector_parses(':scope')
+ assert_selector_parses(':current')
+ assert_selector_parses(':current(s)')
+ assert_selector_parses(':past')
+ assert_selector_parses(':future')
+ assert_selector_parses(':active')
+ assert_selector_parses(':hover')
+ assert_selector_parses(':focus')
+ assert_selector_parses(':enabled')
+ assert_selector_parses(':disabled')
+ assert_selector_parses(':checked')
+ assert_selector_parses(':indeterminate')
+ assert_selector_parses(':default')
+ assert_selector_parses(':in-range')
+ assert_selector_parses(':out-of-range')
+ assert_selector_parses(':required')
+ assert_selector_parses(':optional')
+ assert_selector_parses(':read-only')
+ assert_selector_parses(':read-write')
+ assert_selector_parses(':root')
+ assert_selector_parses(':empty')
+ assert_selector_parses(':first-child')
+ assert_selector_parses(':nth-child(n)')
+ assert_selector_parses(':last-child')
+ assert_selector_parses(':nth-last-child(n)')
+ assert_selector_parses(':only-child')
+ assert_selector_parses(':first-of-type')
+ assert_selector_parses(':nth-of-type(n)')
+ assert_selector_parses(':last-of-type')
+ assert_selector_parses(':nth-last-of-type(n)')
+ assert_selector_parses(':only-of-type')
+ assert_selector_parses(':nth-child(n of selector)')
+ assert_selector_parses(':nth-last-child(n of selector)')
+ assert_selector_parses(':nth-child(n)')
+ assert_selector_parses(':nth-last-child(n)')
+
+ # From http://dev.w3.org/csswg/css-scoping-1/
+ assert_selector_parses(':host(s)')
+ assert_selector_parses(':host-context(s)')
+ end
+
+ def test_attribute_selectors_with_identifiers
+ assert_selector_parses('[foo~=bar]')
+ assert_selector_parses('[foo^=bar]')
+ assert_selector_parses('[foo$=bar]')
+ assert_selector_parses('[foo*=bar]')
+ assert_selector_parses('[foo|=en]')
+ end
+
+ def test_nth_selectors
+ assert_selector_parses(':nth-child(-n)')
+ assert_selector_parses(':nth-child(+n)')
+
+ assert_selector_parses(':nth-child(even)')
+ assert_selector_parses(':nth-child(odd)')
+
+ assert_selector_parses(':nth-child(50)')
+ assert_selector_parses(':nth-child(-50)')
+ assert_selector_parses(':nth-child(+50)')
+
+ assert_selector_parses(':nth-child(2n+3)')
+ assert_selector_parses(':nth-child(2n-3)')
+ assert_selector_parses(':nth-child(+2n-3)')
+ assert_selector_parses(':nth-child(-2n+3)')
+ assert_selector_parses(':nth-child(-2n+ 3)')
+
+ assert_equal(<<CSS, render(<<SCSS))
+:nth-child(2n + 3) {
+ a: b; }
+CSS
+:nth-child( 2n + 3 ) {
+ a: b; }
+SCSS
+ end
+
+ def test_selectors_containing_selectors
+ assert_selector_can_contain_selectors(':not(<sel>)')
+ assert_selector_can_contain_selectors(':current(<sel>)')
+ assert_selector_can_contain_selectors(':nth-child(n of <sel>)')
+ assert_selector_can_contain_selectors(':nth-last-child(n of <sel>)')
+ assert_selector_can_contain_selectors(':-moz-any(<sel>)')
+ assert_selector_can_contain_selectors(':has(<sel>)')
+ assert_selector_can_contain_selectors(':has(+ <sel>)')
+ assert_selector_can_contain_selectors(':host(<sel>)')
+ assert_selector_can_contain_selectors(':host-context(<sel>)')
+ end
+
+ def assert_selector_can_contain_selectors(sel)
+ try = lambda {|subsel| assert_selector_parses(sel.gsub('<sel>', subsel))}
+
+ try['foo|bar']
+ try['*|bar']
+
+ try['foo|*']
+ try['*|*']
+
+ try['#blah']
+ try['.blah']
+
+ try['[foo]']
+ try['[foo^="bar"]']
+ try['[baz|foo~="bar"]']
+
+ try[':hover']
+ try[':nth-child(2n + 3)']
+
+ try['h1, h2, h3']
+ try['#foo, bar, [baz]']
+
+ # Not technically allowed for most selectors, but what the heck
+ try[':not(#foo)']
+ try['a#foo.bar']
+ try['#foo .bar > baz']
+ end
+
+ def test_namespaced_selectors
+ assert_selector_parses('foo|E')
+ assert_selector_parses('*|E')
+ assert_selector_parses('foo|*')
+ assert_selector_parses('*|*')
+ end
+
+ def test_namespaced_attribute_selectors
+ assert_selector_parses('[foo|bar=baz]')
+ assert_selector_parses('[*|bar=baz]')
+ assert_selector_parses('[foo|bar|=baz]')
+ end
+
+ def test_comma_selectors
+ assert_selector_parses('E, F')
+ assert_selector_parses('E F, G H')
+ assert_selector_parses('E > F, G > H')
+ end
+
+ def test_selectors_with_newlines
+ assert_selector_parses("E,\nF")
+ assert_selector_parses("E\nF")
+ assert_selector_parses("E, F\nG, H")
+ end
+
+ def test_expression_fallback_selectors
+ assert_directive_parses('0%')
+ assert_directive_parses('60%')
+ assert_directive_parses('100%')
+ assert_directive_parses('12px')
+ assert_directive_parses('"foo"')
+ end
+
+ def test_functional_pseudo_selectors
+ assert_selector_parses(':foo("bar")')
+ assert_selector_parses(':foo(bar)')
+ assert_selector_parses(':foo(12px)')
+ assert_selector_parses(':foo(+)')
+ assert_selector_parses(':foo(-)')
+ assert_selector_parses(':foo(+"bar")')
+ assert_selector_parses(':foo(-++--baz-"bar"12px)')
+ end
+
+ def test_selector_hacks
+ assert_selector_parses('> E')
+ assert_selector_parses('+ E')
+ assert_selector_parses('~ E')
+ assert_selector_parses('> > E')
+ assert_equal <<CSS, render(<<SCSS)
+> > E {
+ a: b; }
+CSS
+>> E {
+ a: b; }
+SCSS
+
+ assert_selector_parses('E*')
+ assert_selector_parses('E*.foo')
+ assert_selector_parses('E*:hover')
+ end
+
+ def test_spaceless_combo_selectors
+ assert_equal "E > F {\n a: b; }\n", render("E>F { a: b;} ")
+ assert_equal "E ~ F {\n a: b; }\n", render("E~F { a: b;} ")
+ assert_equal "E + F {\n a: b; }\n", render("E+F { a: b;} ")
+ end
+
+ def test_escapes_in_selectors
+ assert_selector_parses('.\!foo')
+ assert_selector_parses('.\66 foo')
+ assert_selector_parses('.\21 foo')
+ end
+
+ def test_subject_selector_deprecation
+ assert_warning(<<WARNING) {render(".foo .bar! .baz {a: b}")}
+DEPRECATION WARNING on line 1, column 1:
+The subject selector operator "!" is deprecated and will be removed in a future release.
+This operator has been replaced by ":has()" in the CSS spec.
+For example: .foo .bar:has(.baz)
+WARNING
+
+ assert_warning(<<WARNING) {render(".foo .bar! > .baz {a: b}")}
+DEPRECATION WARNING on line 1, column 1:
+The subject selector operator "!" is deprecated and will be removed in a future release.
+This operator has been replaced by ":has()" in the CSS spec.
+For example: .foo .bar:has(> .baz)
+WARNING
+
+ assert_warning(<<WARNING) {render(".foo .bar! {a: b}")}
+DEPRECATION WARNING on line 1, column 1:
+The subject selector operator "!" is deprecated and will be removed in a future release.
+This operator has been replaced by ":has()" in the CSS spec.
+For example: .foo .bar
+WARNING
+ end
+
+ ## Errors
+
+ def test_invalid_directives
+ assert_not_parses("identifier", '@<err> import "foo";')
+ assert_not_parses("identifier", '@<err>12 "foo";')
+ end
+
+ def test_invalid_classes
+ assert_not_parses("class name", 'p.<err> foo {a: b}')
+ assert_not_parses("class name", 'p.<err>1foo {a: b}')
+ end
+
+ def test_invalid_ids
+ assert_not_parses("id name", 'p#<err> foo {a: b}')
+ end
+
+ def test_no_properties_at_toplevel
+ assert_not_parses('pseudoclass or pseudoelement', 'a:<err> b;')
+ end
+
+ def test_no_scss_directives
+ assert_parses('@import "foo.sass";')
+ assert_parses <<SCSS
+ mixin foo {
+ a: b; }
+SCSS
+ end
+
+ def test_no_variables
+ assert_not_parses("selector or at-rule", "<err>$var = 12;")
+ assert_not_parses('"}"', "foo { <err>!var = 12; }")
+ end
+
+ def test_no_parent_selectors
+ assert_not_parses('"{"', "foo <err>&.bar {a: b}")
+ end
+
+ def test_no_selector_interpolation
+ assert_not_parses('"{"', 'foo <err>#{"bar"}.baz {a: b}')
+ end
+
+ def test_no_prop_name_interpolation
+ assert_not_parses('":"', 'foo {a<err>#{"bar"}baz: b}')
+ end
+
+ def test_no_prop_val_interpolation
+ assert_not_parses('"}"', 'foo {a: b <err>#{"bar"} c}')
+ end
+
+ def test_no_string_interpolation
+ assert_parses <<SCSS
+foo {
+ a: "bang \#{1 + " bar "} bip"; }
+SCSS
+ end
+
+ def test_no_sass_script_values
+ assert_not_parses('"}"', 'foo {a: b <err>* c}')
+ end
+
+ def test_no_nested_rules
+ assert_not_parses('":"', 'foo {bar <err>{a: b}}')
+ assert_not_parses('"}"', 'foo {<err>[bar=baz] {a: b}}')
+ end
+
+ def test_no_nested_properties
+ assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: <err>{a: b}}')
+ assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: bang <err>{a: b}}')
+ end
+
+ def test_no_nested_directives
+ assert_not_parses('"}"', 'foo {<err>@bar {a: b}}')
+ end
+
+ def test_error_with_windows_newlines
+ render <<SCSS
+foo {bar}\r
+baz {a: b}
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after "foo {bar": expected ":", was "}"', e.message
+ assert_equal 1, e.sass_line
+ end
+
+ def test_newline_in_property_value
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ bar: "bazbang"; }
+CSS
+.foo {
+ bar: "baz\\
+bang";
+}
+SCSS
+ end
+
+ ## Regressions
+
+ def test_very_long_comment_doesnt_take_forever
+ string = 'asdf' * (100000)
+ assert_equal(<<CSS, render(<<SCSS))
+/*
+ #{string}
+*/
+CSS
+/*
+ #{string}
+*/
+SCSS
+ end
+
+ def test_long_unclosed_comment_doesnt_take_forever
+ assert_raise_message(Sass::SyntaxError,
+ 'Invalid CSS after "/*": expected "/", was "//*************..."') {render(<<SCSS)}
+/*
+//**************************************************************************
+SCSS
+ end
+
+ def test_double_space_string
+ assert_equal(<<CSS, render(<<SCSS))
+.a {
+ content: " a"; }
+CSS
+.a {
+ content: " a";
+}
+SCSS
+ end
+
+ def test_very_long_number_with_important_doesnt_take_forever
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ width: 97.916666666666666666666666666667% !important; }
+CSS
+.foo {
+ width: 97.916666666666666666666666666667% !important;
+}
+SCSS
+ end
+
+ def test_selector_without_closing_bracket
+ assert_not_parses('"]"', "foo[bar <err>{a: b}")
+ end
+
+ def test_closing_line_comment_end_with_compact_output
+ assert_equal(<<CSS, render(<<SCSS, :style => :compact))
+/* foo */
+bar { baz: bang; }
+CSS
+/*
+ * foo
+ */
+bar {baz: bang}
+SCSS
+ end
+
+ def test_single_line_comment_within_multiline_comment
+ assert_equal(<<CSS, render(<<SCSS))
+body {
+ /*
+ //comment here
+ */ }
+CSS
+body {
+ /*
+ //comment here
+ */
+}
+SCSS
+ end
+
+ def test_malformed_media
+ render <<SCSS
+ media {
+ margin: 0;
+}
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after "@media ": expected media query (e.g. print, screen, print and screen),
was "{"', e.message
+ assert_equal 1, e.sass_line
+ end
+
+ private
+
+ def assert_selector_parses(selector)
+ assert_parses <<SCSS
+#{selector} {
+ a: b; }
+SCSS
+
+ assert_parses <<SCSS
+:not(#{selector}) {
+ a: b; }
+SCSS
+ end
+
+ def assert_directive_parses(param)
+ assert_parses <<SCSS
+ keyframes #{param} {
+ a: b; }
+SCSS
+ end
+
+ def render(scss, options = {})
+ tree = Sass::SCSS::CssParser.new(scss, options[:filename], nil).parse
+ tree.options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
+ tree.render
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/scss/rx_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/scss/rx_test.rb
new file mode 100755
index 0000000..f45e61b
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/scss/rx_test.rb
@@ -0,0 +1,156 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../../test_helper'
+require 'sass/engine'
+
+class ScssRxTest < MiniTest::Test
+ include Sass::SCSS::RX
+
+ def test_identifiers
+ assert_match IDENT, "foo"
+ assert_match IDENT, "\xC3\xBFoo" # Initial char can be nonascii
+ assert_match IDENT, "\\123abcoo" # Initial char can be unicode escape
+ assert_match IDENT, "\\f oo" # Unicode escapes can be followed by whitespace
+ assert_match IDENT, "\\fa\too"
+ assert_match IDENT, "\\ff2\roo"
+ assert_match IDENT, "\\f13a\foo"
+ assert_match IDENT, "\\f13abcoo"
+ assert_match IDENT, "\\ oo" # Initial char can be a plain escape as well
+ assert_match IDENT, "\\~oo"
+ assert_match IDENT, "\\\\oo"
+ assert_match IDENT, "\\{oo"
+ assert_match IDENT, "\\\xC3\xBFoo"
+ assert_match IDENT, "-foo" # Can put a - before anything
+ assert_match IDENT, "-\xC3\xBFoo"
+ assert_match IDENT, "-\\f oo"
+ assert_match IDENT, "_foo" # Can put a _ before anything
+ assert_match IDENT, "_\xC3\xBFoo"
+ assert_match IDENT, "_\\f oo"
+ assert_match IDENT, "--foo" # "Custom" identifier
+
+ assert_match IDENT, "foo-bar"
+ assert_match IDENT, "f012-23"
+ assert_match IDENT, "foo_-_bar"
+ assert_match IDENT, "f012_23"
+
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/escapes-003.xht
+ assert_match IDENT, "c\\lass"
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/escapes-004.xht
+ assert_match IDENT, "c\\00006Cas\\000073"
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/ident-001.xht
+ assert_match IDENT, "IdE6n-3t0_6"
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/ident-006.xht
+ assert_match IDENT, "\\6000ident"
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/ident-007.xht
+ assert_match IDENT, "iden\\6000t\\6000"
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/ident-013.xht
+ assert_match IDENT, "\\-ident"
+ end
+
+ def test_underscores_in_identifiers
+ assert_match IDENT, "foo_bar"
+ assert_match IDENT, "_\xC3\xBFfoo"
+ assert_match IDENT, "__foo"
+ assert_match IDENT, "_1foo"
+ assert_match IDENT, "-_foo"
+ assert_match IDENT, "_-foo"
+ end
+
+ def test_invalid_identifiers
+ assert_no_match IDENT, ""
+ assert_no_match IDENT, "1foo"
+ assert_no_match IDENT, "-1foo"
+ assert_no_match IDENT, "foo bar"
+ assert_no_match IDENT, "foo~bar"
+
+ # http://www.w3.org/Style/CSS/Test/CSS2.1/current/xhtml1/escapes-008.xht
+ assert_no_match IDENT, "c\\06C ass"
+ assert_no_match IDENT, "back\\67\n round"
+ end
+
+ def test_double_quote_strings
+ assert_match STRING, '"foo bar"'
+ assert_match STRING, '"foo\\\nbar"'
+ assert_match STRING, "\"\\\"\""
+ assert_match STRING, '"\t !#$%&(-~()*+,-./0123456789~"'
+ end
+
+ def test_single_quote_strings
+ assert_match STRING, "'foo bar'"
+ assert_match STRING, "'foo\\\nbar'"
+ assert_match STRING, "'\\''"
+ assert_match STRING, "'\t !#\$%&(-~()*+,-./0123456789~'"
+ end
+
+ def test_invalid_strings
+ assert_no_match STRING, "\"foo\nbar\""
+ assert_no_match STRING, "\"foo\"bar\""
+ assert_no_match STRING, "'foo\nbar'"
+ assert_no_match STRING, "'foo'bar'"
+ end
+
+ def test_uri
+ assert_match URI, 'url("foo bar)")'
+ assert_match URI, "url('foo bar)')"
+ assert_match URI, 'url( "foo bar)" )'
+ assert_match URI, "url(#\\%&**+,-./0123456789~)"
+ end
+
+ def test_invalid_uri
+ assert_no_match URI, 'url(foo)bar)'
+ end
+
+ def test_unicode_range
+ assert_match UNICODERANGE, 'U+00-Ff'
+ assert_match UNICODERANGE, 'u+980-9FF'
+ assert_match UNICODERANGE, 'U+9aF??'
+ assert_match UNICODERANGE, 'U+??'
+ end
+
+ def test_escape_empty_ident
+ assert_equal "", Sass::SCSS::RX.escape_ident("")
+ end
+
+ def test_escape_just_prefix_ident
+ assert_equal "\\-", Sass::SCSS::RX.escape_ident("-")
+ assert_equal "\\_", Sass::SCSS::RX.escape_ident("_")
+ end
+
+ def test_escape_plain_ident
+ assert_equal "foo", Sass::SCSS::RX.escape_ident("foo")
+ assert_equal "foo-1bar", Sass::SCSS::RX.escape_ident("foo-1bar")
+ assert_equal "-foo-bar", Sass::SCSS::RX.escape_ident("-foo-bar")
+ assert_equal "f2oo_bar", Sass::SCSS::RX.escape_ident("f2oo_bar")
+ assert_equal "_foo_bar", Sass::SCSS::RX.escape_ident("_foo_bar")
+ end
+
+ def test_escape_initial_funky_ident
+ assert_equal "\\000035foo", Sass::SCSS::RX.escape_ident("5foo")
+ assert_equal "-\\000035foo", Sass::SCSS::RX.escape_ident("-5foo")
+ assert_equal "_\\000035foo", Sass::SCSS::RX.escape_ident("_5foo")
+
+ assert_equal "\\&foo", Sass::SCSS::RX.escape_ident("&foo")
+ assert_equal "-\\&foo", Sass::SCSS::RX.escape_ident("-&foo")
+
+ assert_equal "-\\ foo", Sass::SCSS::RX.escape_ident("- foo")
+ end
+
+ def test_escape_mid_funky_ident
+ assert_equal "foo\\&bar", Sass::SCSS::RX.escape_ident("foo&bar")
+ assert_equal "foo\\ \\ bar", Sass::SCSS::RX.escape_ident("foo bar")
+ assert_equal "foo\\00007fbar", Sass::SCSS::RX.escape_ident("foo\177bar")
+ end
+
+ private
+
+ def assert_match(rx, str)
+ refute_nil(match = rx.match(str))
+ assert_equal str.size, match[0].size
+ end
+
+ def assert_no_match(rx, str)
+ match = rx.match(str)
+ refute_equal str.size, match && match[0].size
+ end
+
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/scss/scss_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/scss/scss_test.rb
new file mode 100755
index 0000000..9d5a02d
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/scss/scss_test.rb
@@ -0,0 +1,4008 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/test_helper'
+
+class ScssTest < MiniTest::Test
+ include ScssTestHelper
+
+ ## One-Line Comments
+
+ def test_one_line_comments
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ baz: bang; }
+CSS
+.foo {// bar: baz;}
+ baz: bang; //}
+}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+.foo bar[val="//"] {
+ baz: bang; }
+CSS
+.foo bar[val="//"] {
+ baz: bang; //}
+}
+SCSS
+ end
+
+ ## Script
+
+ def test_variables
+ assert_equal <<CSS, render(<<SCSS)
+blat {
+ a: foo; }
+CSS
+$var: foo;
+
+blat {a: $var}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 2;
+ b: 6; }
+CSS
+foo {
+ $var: 2;
+ $another-var: 4;
+ a: $var;
+ b: $var + $another-var;}
+SCSS
+ end
+
+ def test_unicode_variables
+ assert_equal <<CSS, render(<<SCSS)
+blat {
+ a: foo; }
+CSS
+$vär: foo;
+
+blat {a: $vär}
+SCSS
+ end
+
+ def test_guard_assign
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 1; }
+CSS
+$var: 1;
+$var: 2 !default;
+
+foo {a: $var}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 2; }
+CSS
+$var: 2 !default;
+
+foo {a: $var}
+SCSS
+ end
+
+ def test_sass_script
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 3;
+ b: -1;
+ c: foobar;
+ d: 12px; }
+CSS
+foo {
+ a: 1 + 2;
+ b: 1 - 2;
+ c: foo + bar;
+ d: floor(12.3px); }
+SCSS
+ end
+
+ def test_debug_directive
+ assert_warning "test_debug_directive_inline.scss:2 DEBUG: hello world!" do
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: b; }
+
+bar {
+ c: d; }
+CSS
+foo {a: b}
+ debug "hello world!";
+bar {c: d}
+SCSS
+ end
+ end
+
+ def test_error_directive
+ assert_raise_message(Sass::SyntaxError, "hello world!") {render(<<SCSS)}
+foo {a: b}
+ error "hello world!";
+bar {c: d}
+SCSS
+ end
+
+ def test_warn_directive
+ expected_warning = <<EXPECTATION
+WARNING: this is a warning
+ on line 2 of test_warn_directive_inline.scss
+
+WARNING: this is a mixin
+ on line 1 of test_warn_directive_inline.scss, in `foo'
+ from line 3 of test_warn_directive_inline.scss
+EXPECTATION
+ assert_warning expected_warning do
+ assert_equal <<CSS, render(<<SCSS)
+bar {
+ c: d; }
+CSS
+ mixin foo { @warn "this is a mixin";}
+ warn "this is a warning";
+bar {c: d; @include foo;}
+SCSS
+ end
+ end
+
+ def test_for_directive
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ a: 2;
+ a: 3;
+ a: 4; }
+CSS
+.foo {
+ @for $var from 1 to 5 {a: $var;}
+}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ a: 2;
+ a: 3;
+ a: 4;
+ a: 5; }
+CSS
+.foo {
+ @for $var from 1 through 5 {a: $var;}
+}
+SCSS
+ end
+
+ def test_for_directive_with_same_start_and_end
+ assert_equal <<CSS, render(<<SCSS)
+CSS
+.foo {
+ @for $var from 1 to 1 {a: $var;}
+}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1; }
+CSS
+.foo {
+ @for $var from 1 through 1 {a: $var;}
+}
+SCSS
+ end
+
+ def test_decrementing_estfor_directive
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 5;
+ a: 4;
+ a: 3;
+ a: 2;
+ a: 1; }
+CSS
+.foo {
+ @for $var from 5 through 1 {a: $var;}
+}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 5;
+ a: 4;
+ a: 3;
+ a: 2; }
+CSS
+.foo {
+ @for $var from 5 to 1 {a: $var;}
+}
+SCSS
+ end
+
+ def test_if_directive
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: b; }
+CSS
+ if "foo" == "foo" {foo {a: b}}
+ if "foo" != "foo" {bar {a: b}}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+bar {
+ a: b; }
+CSS
+ if "foo" != "foo" {foo {a: b}}
+ else if "foo" == "foo" {bar {a: b}}
+ else if true {baz {a: b}}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+bar {
+ a: b; }
+CSS
+ if "foo" != "foo" {foo {a: b}}
+ else {bar {a: b}}
+SCSS
+ end
+
+ def test_comment_after_if_directive
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: b;
+ /* This is a comment */
+ c: d; }
+CSS
+foo {
+ @if true {a: b}
+ /* This is a comment */
+ c: d }
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: b;
+ /* This is a comment */
+ c: d; }
+CSS
+foo {
+ @if true {a: b}
+ @else {x: y}
+ /* This is a comment */
+ c: d }
+SCSS
+ end
+
+ def test_while_directive
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ a: 2;
+ a: 3;
+ a: 4; }
+CSS
+$i: 1;
+
+.foo {
+ @while $i != 5 {
+ a: $i;
+ $i: $i + 1 !global;
+ }
+}
+SCSS
+ end
+
+ def test_each_directive
+ assert_equal <<CSS, render(<<SCSS)
+a {
+ b: 1px;
+ b: 2px;
+ b: 3px;
+ b: 4px; }
+
+c {
+ d: foo;
+ d: bar;
+ d: baz;
+ d: bang; }
+CSS
+a {
+ @each $number in 1px 2px 3px 4px {
+ b: $number;
+ }
+}
+c {
+ @each $str in foo, bar, baz, bang {
+ d: $str;
+ }
+}
+SCSS
+ end
+
+ def test_destructuring_each_directive
+ assert_equal <<CSS, render(<<SCSS)
+a {
+ foo: 1px;
+ bar: 2px;
+ baz: 3px; }
+
+c {
+ foo: "Value is bar";
+ bar: "Value is baz";
+ bang: "Value is "; }
+CSS
+a {
+ @each $name, $number in (foo: 1px, bar: 2px, baz: 3px) {
+ \#{$name}: $number;
+ }
+}
+c {
+ @each $key, $value in (foo bar) (bar, baz) bang {
+ \#{$key}: "Value is \#{$value}";
+ }
+}
+SCSS
+ end
+
+ def test_css_import_directive
+ assert_equal "@import url(foo.css);\n", render('@import "foo.css";')
+ assert_equal "@import url(foo.css);\n", render("@import 'foo.css';")
+ assert_equal "@import url(\"foo.css\");\n", render('@import url("foo.css");')
+ assert_equal "@import url(\"foo.css\");\n", render('@import url("foo.css");')
+ assert_equal "@import url(foo.css);\n", render('@import url(foo.css);')
+ end
+
+ def test_css_string_import_directive_with_media
+ assert_parses '@import "foo.css" screen;'
+ assert_parses '@import "foo.css" screen, print;'
+ assert_parses '@import "foo.css" screen, print and (foo: 0);'
+ assert_parses '@import "foo.css" screen, only print, screen and (foo: 0);'
+ end
+
+ def test_css_url_import_directive_with_media
+ assert_parses '@import url("foo.css") screen;'
+ assert_parses '@import url("foo.css") screen, print;'
+ assert_parses '@import url("foo.css") screen, print and (foo: 0);'
+ assert_parses '@import url("foo.css") screen, only print, screen and (foo: 0);'
+ end
+
+ def test_media_import
+ assert_equal("@import \"./fonts.sass\" all;\n", render("@import \"./fonts.sass\" all;"))
+ end
+
+ def test_dynamic_media_import
+ assert_equal(<<CSS, render(<<SCSS))
+ import "foo" print and (-webkit-min-device-pixel-ratio-foo: 25);
+CSS
+$media: print;
+$key: -webkit-min-device-pixel-ratio;
+$value: 20;
+ import "foo" \#{$media} and ($key + "-foo": $value + 5);
+SCSS
+ end
+
+ def test_http_import
+ assert_equal("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\";\n",
+ render("@import \"http://fonts.googleapis.com/css?family=Droid+Sans\";"))
+ end
+
+ def test_protocol_relative_import
+ assert_equal("@import \"//fonts.googleapis.com/css?family=Droid+Sans\";\n",
+ render("@import \"//fonts.googleapis.com/css?family=Droid+Sans\";"))
+ end
+
+ def test_import_with_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+ import url("http://fonts.googleapis.com/css?family=Droid+Sans");
+CSS
+$family: unquote("Droid+Sans");
+ import url("http://fonts.googleapis.com/css?family=\#{$family}");
+SCSS
+ end
+
+ def test_url_import
+ assert_equal("@import url(fonts.sass);\n", render("@import url(fonts.sass);"))
+ end
+
+ def test_css_import_doesnt_move_through_comments
+ assert_equal <<CSS, render(<<SCSS)
+/* Comment 1 */
+ import url("foo.css");
+/* Comment 2 */
+ import url("bar.css");
+CSS
+/* Comment 1 */
+ import url("foo.css");
+
+/* Comment 2 */
+ import url("bar.css");
+SCSS
+ end
+
+ def test_css_import_movement_stops_at_comments
+ assert_equal <<CSS, render(<<SCSS)
+/* Comment 1 */
+ import url("foo.css");
+/* Comment 2 */
+ import url("bar.css");
+.foo {
+ a: b; }
+
+/* Comment 3 */
+CSS
+/* Comment 1 */
+ import url("foo.css");
+
+/* Comment 2 */
+
+.foo {a: b}
+
+/* Comment 3 */
+ import url("bar.css");
+SCSS
+ end
+
+ def test_block_comment_in_script
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 1bar; }
+CSS
+foo {a: 1 + /* flang */ bar}
+SCSS
+ end
+
+ def test_line_comment_in_script
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 1blang; }
+CSS
+foo {a: 1 + // flang }
+ blang }
+SCSS
+ end
+
+ ## Nested Rules
+
+ def test_nested_rules
+ assert_equal <<CSS, render(<<SCSS)
+foo bar {
+ a: b; }
+CSS
+foo {bar {a: b}}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo bar {
+ a: b; }
+foo baz {
+ b: c; }
+CSS
+foo {
+ bar {a: b}
+ baz {b: c}}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo bar baz {
+ a: b; }
+foo bang bip {
+ a: b; }
+CSS
+foo {
+ bar {baz {a: b}}
+ bang {bip {a: b}}}
+SCSS
+ end
+
+ def test_nested_rules_with_declarations
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: b; }
+ foo bar {
+ c: d; }
+CSS
+foo {
+ a: b;
+ bar {c: d}}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: b; }
+ foo bar {
+ c: d; }
+CSS
+foo {
+ bar {c: d}
+ a: b}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ ump: nump;
+ grump: clump; }
+ foo bar {
+ blat: bang;
+ habit: rabbit; }
+ foo bar baz {
+ a: b; }
+ foo bar bip {
+ c: d; }
+ foo bibble bap {
+ e: f; }
+CSS
+foo {
+ ump: nump;
+ grump: clump;
+ bar {
+ blat: bang;
+ habit: rabbit;
+ baz {a: b}
+ bip {c: d}}
+ bibble {
+ bap {e: f}}}
+SCSS
+ end
+
+ def test_nested_rules_with_fancy_selectors
+ assert_equal <<CSS, render(<<SCSS)
+foo .bar {
+ a: b; }
+foo :baz {
+ c: d; }
+foo bang:bop {
+ e: f; }
+foo ::qux {
+ g: h; }
+foo zap::fblthp {
+ i: j; }
+CSS
+foo {
+ .bar {a: b}
+ :baz {c: d}
+ bang:bop {e: f}
+ ::qux {g: h}
+ zap::fblthp {i: j}}
+SCSS
+ end
+
+ def test_almost_ambiguous_nested_rules_and_declarations
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: baz bang bop biddle woo look at all these elems; }
+ foo bar:baz:bang:bop:biddle:woo:look:at:all:these:pseudoclasses {
+ a: b; }
+ foo bar:baz bang bop biddle woo look at all these elems {
+ a: b; }
+CSS
+foo {
+ bar:baz:bang:bop:biddle:woo:look:at:all:these:pseudoclasses {a: b};
+ bar:baz bang bop biddle woo look at all these elems {a: b};
+ bar:baz bang bop biddle woo look at all these elems; }
+SCSS
+ end
+
+ def test_newlines_in_selectors
+ assert_equal <<CSS, render(<<SCSS)
+foo
+bar {
+ a: b; }
+CSS
+foo
+bar {a: b}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+foo baz,
+foo bang,
+bar baz,
+bar bang {
+ a: b; }
+CSS
+foo,
+bar {
+ baz,
+ bang {a: b}}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+foo
+bar baz
+bang {
+ a: b; }
+foo
+bar bip bop {
+ c: d; }
+CSS
+foo
+bar {
+ baz
+ bang {a: b}
+
+ bip bop {c: d}}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+foo bang, foo bip
+bop, bar
+baz bang, bar
+baz bip
+bop {
+ a: b; }
+CSS
+foo, bar
+baz {
+ bang, bip
+ bop {a: b}}
+SCSS
+ end
+
+ def test_trailing_comma_in_selector
+ assert_equal <<CSS, render(<<SCSS)
+#foo #bar,
+#baz #boom {
+ a: b; }
+
+#bip #bop {
+ c: d; }
+CSS
+#foo #bar,,
+,#baz #boom, {a: b}
+
+#bip #bop, ,, {c: d}
+SCSS
+ end
+
+ def test_parent_selectors
+ assert_equal <<CSS, render(<<SCSS)
+foo:hover {
+ a: b; }
+bar foo.baz {
+ c: d; }
+CSS
+foo {
+ &:hover {a: b}
+ bar &.baz {c: d}}
+SCSS
+ end
+
+ def test_parent_selector_with_subject
+ silence_warnings {assert_equal <<CSS, render(<<SCSS)}
+bar foo.baz! .bip {
+ a: b; }
+
+bar foo bar.baz! .bip {
+ c: d; }
+CSS
+foo {
+ bar &.baz! .bip {a: b}}
+
+foo bar {
+ bar &.baz! .bip {c: d}}
+SCSS
+ end
+
+ def test_parent_selector_with_suffix
+ assert_equal <<CSS, render(<<SCSS)
+.foo-bar {
+ a: b; }
+.foo_bar {
+ c: d; }
+.foobar {
+ e: f; }
+.foo123 {
+ e: f; }
+
+:hover-suffix {
+ g: h; }
+CSS
+.foo {
+ &-bar {a: b}
+ &_bar {c: d}
+ &bar {e: f}
+ &123 {e: f}
+}
+
+:hover {
+ &-suffix {g: h}
+}
+SCSS
+ end
+
+ def test_unknown_directive_bubbling
+ assert_equal(<<CSS, render(<<SCSS, :style => :nested))
+ fblthp {
+ .foo .bar {
+ a: b; } }
+CSS
+.foo {
+ @fblthp {
+ .bar {a: b}
+ }
+}
+SCSS
+ end
+
+ def test_keyframe_bubbling
+ assert_equal(<<CSS, render(<<SCSS, :style => :nested))
+ keyframes spin {
+ 0% {
+ transform: rotate(0deg); } }
+ -webkit-keyframes spin {
+ 0% {
+ transform: rotate(0deg); } }
+CSS
+.foo {
+ @keyframes spin {
+ 0% {transform: rotate(0deg)}
+ }
+ @-webkit-keyframes spin {
+ 0% {transform: rotate(0deg)}
+ }
+}
+SCSS
+ end
+
+ ## Namespace Properties
+
+ def test_namespace_properties
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: baz;
+ bang-bip: 1px;
+ bang-bop: bar; }
+CSS
+foo {
+ bar: baz;
+ bang: {
+ bip: 1px;
+ bop: bar;}}
+SCSS
+ end
+
+ def test_several_namespace_properties
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: baz;
+ bang-bip: 1px;
+ bang-bop: bar;
+ buzz-fram: "foo";
+ buzz-frum: moo; }
+CSS
+foo {
+ bar: baz;
+ bang: {
+ bip: 1px;
+ bop: bar;}
+ buzz: {
+ fram: "foo";
+ frum: moo;
+ }
+}
+SCSS
+ end
+
+ def test_nested_namespace_properties
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: baz;
+ bang-bip: 1px;
+ bang-bop: bar;
+ bang-blat-baf: bort; }
+CSS
+foo {
+ bar: baz;
+ bang: {
+ bip: 1px;
+ bop: bar;
+ blat:{baf:bort}}}
+SCSS
+ end
+
+ def test_namespace_properties_with_value
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: baz;
+ bar-bip: bop;
+ bar-bing: bop; }
+CSS
+foo {
+ bar: baz {
+ bip: bop;
+ bing: bop; }}
+SCSS
+ end
+
+ def test_namespace_properties_with_script_value
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: bazbang;
+ bar-bip: bop;
+ bar-bing: bop; }
+CSS
+foo {
+ bar: baz + bang {
+ bip: bop;
+ bing: bop; }}
+SCSS
+ end
+
+ def test_no_namespace_properties_without_space
+ assert_equal <<CSS, render(<<SCSS)
+foo bar:baz {
+ bip: bop; }
+CSS
+foo {
+ bar:baz {
+ bip: bop }}
+SCSS
+ end
+
+ def test_no_namespace_properties_without_space_even_when_its_unambiguous
+ render(<<SCSS)
+foo {
+ bar:baz calc(1 + 2) {
+ bip: bop }}
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after "bar:baz calc": expected selector, was "(1 + 2)"', e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_namespace_properties_without_space_allowed_for_non_identifier
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: 1px;
+ bar-bip: bop; }
+CSS
+foo {
+ bar:1px {
+ bip: bop }}
+SCSS
+ end
+
+ ## Mixins
+
+ def test_basic_mixins
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+CSS
+ mixin foo {
+ .foo {a: b}}
+
+ include foo;
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+bar {
+ c: d; }
+ bar .foo {
+ a: b; }
+CSS
+ mixin foo {
+ .foo {a: b}}
+
+bar {
+ @include foo;
+ c: d; }
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+bar {
+ a: b;
+ c: d; }
+CSS
+ mixin foo {a: b}
+
+bar {
+ @include foo;
+ c: d; }
+SCSS
+ end
+
+ def test_mixins_with_empty_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+CSS
+ mixin foo() {a: b}
+
+.foo { include foo();}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+CSS
+ mixin foo() {a: b}
+
+.foo { include foo;}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+CSS
+ mixin foo {a: b}
+
+.foo { include foo();}
+SCSS
+ end
+
+ def test_mixins_with_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: bar; }
+CSS
+ mixin foo($a) {a: $a}
+
+.foo { include foo(bar)}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: bar;
+ b: 12px; }
+CSS
+ mixin foo($a, $b) {
+ a: $a;
+ b: $b; }
+
+.foo { include foo(bar, 12px)}
+SCSS
+ end
+
+ def test_keyframes_rules_in_content
+ assert_equal <<CSS, render(<<SCSS)
+ keyframes identifier {
+ 0% {
+ top: 0;
+ left: 0; }
+ 30% {
+ top: 50px; }
+ 68%, 72% {
+ left: 50px; }
+ 100% {
+ top: 100px;
+ left: 100%; } }
+CSS
+ mixin keyframes {
+ @keyframes identifier { @content }
+}
+
+ include keyframes {
+ 0% {top: 0; left: 0}
+ \#{"30%"} {top: 50px}
+ 68%, 72% {left: 50px}
+ 100% {top: 100px; left: 100%}
+}
+SCSS
+ end
+
+ ## Functions
+
+ def test_basic_function
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 3; }
+CSS
+ function foo() {
+ @return 1 + 2;
+}
+
+bar {
+ a: foo();
+}
+SASS
+ end
+
+ def test_function_args
+ assert_equal(<<CSS, render(<<SASS))
+bar {
+ a: 3; }
+CSS
+ function plus($var1, $var2) {
+ @return $var1 + $var2;
+}
+
+bar {
+ a: plus(1, 2);
+}
+SASS
+ end
+
+ ## Var Args
+
+ def test_mixin_var_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2, 3, 4; }
+CSS
+ mixin foo($a, $b...) {
+ a: $a;
+ b: $b;
+}
+
+.foo { include foo(1, 2, 3, 4)}
+SCSS
+ end
+
+ def test_mixin_empty_var_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 0; }
+CSS
+ mixin foo($a, $b...) {
+ a: $a;
+ b: length($b);
+}
+
+.foo { include foo(1)}
+SCSS
+ end
+
+ def test_mixin_var_args_act_like_list
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 3;
+ b: 3; }
+CSS
+ mixin foo($a, $b...) {
+ a: length($b);
+ b: nth($b, 2);
+}
+
+.foo { include foo(1, 2, 3, 4)}
+SCSS
+ end
+
+ def test_mixin_splat_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3;
+ d: 4; }
+CSS
+ mixin foo($a, $b, $c, $d) {
+ a: $a;
+ b: $b;
+ c: $c;
+ d: $d;
+}
+
+$list: 2, 3, 4;
+.foo { include foo(1, $list...)}
+SCSS
+ end
+
+ def test_mixin_splat_expression
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3;
+ d: 4; }
+CSS
+ mixin foo($a, $b, $c, $d) {
+ a: $a;
+ b: $b;
+ c: $c;
+ d: $d;
+}
+
+.foo { include foo(1, (2, 3, 4)...)}
+SCSS
+ end
+
+ def test_mixin_splat_args_with_var_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2, 3, 4; }
+CSS
+ mixin foo($a, $b...) {
+ a: $a;
+ b: $b;
+}
+
+$list: 2, 3, 4;
+.foo { include foo(1, $list...)}
+SCSS
+ end
+
+ def test_mixin_splat_args_with_var_args_and_normal_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3, 4; }
+CSS
+ mixin foo($a, $b, $c...) {
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+$list: 2, 3, 4;
+.foo { include foo(1, $list...)}
+SCSS
+ end
+
+ def test_mixin_splat_args_with_var_args_preserves_separator
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2 3 4 5; }
+CSS
+ mixin foo($a, $b...) {
+ a: $a;
+ b: $b;
+}
+
+$list: 3 4 5;
+.foo { include foo(1, 2, $list...)}
+SCSS
+ end
+
+ def test_mixin_var_and_splat_args_pass_through_keywords
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 3;
+ b: 1;
+ c: 2; }
+CSS
+ mixin foo($a...) {
+ @include bar($a...);
+}
+
+ mixin bar($b, $c, $a) {
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+.foo { include foo(1, $c: 2, $a: 3)}
+SCSS
+ end
+
+ def test_mixin_var_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3; }
+CSS
+ mixin foo($args...) {
+ a: map-get(keywords($args), a);
+ b: map-get(keywords($args), b);
+ c: map-get(keywords($args), c);
+}
+
+.foo { include foo($a: 1, $b: 2, $c: 3)}
+SCSS
+ end
+
+ def test_mixin_empty_var_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ length: 0; }
+CSS
+ mixin foo($args...) {
+ length: length(keywords($args));
+}
+
+.foo { include foo}
+SCSS
+ end
+
+ def test_mixin_map_splat
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3; }
+CSS
+ mixin foo($a, $b, $c) {
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+.foo {
+ $map: (a: 1, b: 2, c: 3);
+ @include foo($map...);
+}
+SCSS
+ end
+
+ def test_mixin_map_and_list_splat
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: x;
+ b: y;
+ c: z;
+ d: 1;
+ e: 2;
+ f: 3; }
+CSS
+ mixin foo($a, $b, $c, $d, $e, $f) {
+ a: $a;
+ b: $b;
+ c: $c;
+ d: $d;
+ e: $e;
+ f: $f;
+}
+
+.foo {
+ $list: x y z;
+ $map: (d: 1, e: 2, f: 3);
+ @include foo($list..., $map...);
+}
+SCSS
+ end
+
+ def test_mixin_map_splat_takes_precedence_over_pass_through
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: z; }
+CSS
+ mixin foo($args...) {
+ $map: (c: z);
+ @include bar($args..., $map...);
+}
+
+ mixin bar($a, $b, $c) {
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+.foo {
+ @include foo(1, $b: 2, $c: 3);
+}
+SCSS
+ end
+
+ def test_mixin_list_of_pairs_splat_treated_as_list
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: a 1;
+ b: b 2;
+ c: c 3; }
+CSS
+ mixin foo($a, $b, $c) {
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+.foo {
+ @include foo((a 1, b 2, c 3)...);
+}
+SCSS
+ end
+
+ def test_mixin_splat_after_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3; }
+CSS
+ mixin foo($a, $b, $c) {
+ a: 1;
+ b: 2;
+ c: 3;
+}
+
+.foo {
+ @include foo(1, $c: 3, 2...);
+}
+SCSS
+ end
+
+ def test_mixin_keyword_args_after_splat
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3; }
+CSS
+ mixin foo($a, $b, $c) {
+ a: 1;
+ b: 2;
+ c: 3;
+}
+
+.foo {
+ @include foo(1, 2..., $c: 3);
+}
+SCSS
+ end
+
+ def test_mixin_keyword_splat_after_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3; }
+CSS
+ mixin foo($a, $b, $c) {
+ a: 1;
+ b: 2;
+ c: 3;
+}
+
+.foo {
+ @include foo(1, $b: 2, (c: 3)...);
+}
+SCSS
+ end
+
+ def test_mixin_triple_keyword_splat_merge
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ foo: 1;
+ bar: 2;
+ kwarg: 3;
+ a: 3;
+ b: 2;
+ c: 3; }
+CSS
+ mixin foo($foo, $bar, $kwarg, $a, $b, $c) {
+ foo: $foo;
+ bar: $bar;
+ kwarg: $kwarg;
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+ mixin bar($args...) {
+ @include foo($args..., $bar: 2, $a: 2, $b: 2, (kwarg: 3, a: 3, c: 3)...);
+}
+
+.foo {
+ @include bar($foo: 1, $a: 1, $b: 1, $c: 1);
+}
+SCSS
+ end
+
+ def test_mixin_map_splat_converts_hyphens_and_underscores_for_real_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: 1;
+ b: 2;
+ c: 3;
+ d: 4; }
+CSS
+ mixin foo($a-1, $b-2, $c_3, $d_4) {
+ a: $a-1;
+ b: $b-2;
+ c: $c_3;
+ d: $d_4;
+}
+
+.foo {
+ $map: (a-1: 1, b_2: 2, c-3: 3, d_4: 4);
+ @include foo($map...);
+}
+SCSS
+ end
+
+ def test_mixin_map_splat_doesnt_convert_hyphens_and_underscores_for_var_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a-1: 1;
+ b_2: 2;
+ c-3: 3;
+ d_4: 4; }
+CSS
+ mixin foo($args...) {
+ @each $key, $value in keywords($args) {
+ \#{$key}: $value;
+ }
+}
+
+.foo {
+ $map: (a-1: 1, b_2: 2, c-3: 3, d_4: 4);
+ @include foo($map...);
+}
+SCSS
+ end
+
+ def test_mixin_conflicting_splat_after_keyword_args
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Mixin foo was passed argument $b both by position and by name.
+MESSAGE
+ mixin foo($a, $b, $c) {
+ a: 1;
+ b: 2;
+ c: 3;
+}
+
+.foo {
+ @include foo(1, $b: 2, 3...);
+}
+SCSS
+ end
+
+ def test_mixin_keyword_splat_must_have_string_keys
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
+Variable keyword argument map must have string keys.
+12 is not a string in (12: 1).
+MESSAGE
+ mixin foo($a) {
+ a: $a;
+}
+
+.foo { include foo((12: 1)...)}
+SCSS
+ end
+
+ def test_mixin_positional_arg_after_splat
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Only keyword arguments may follow variable arguments (...).
+MESSAGE
+ mixin foo($a, $b, $c) {
+ a: 1;
+ b: 2;
+ c: 3;
+}
+
+.foo {
+ @include foo(1, 2..., 3);
+}
+SCSS
+ end
+
+ def test_mixin_var_args_with_keyword
+ assert_raise_message(Sass::SyntaxError, "Positional arguments must come before keyword arguments.")
{render <<SCSS}
+ mixin foo($a, $b...) {
+ a: $a;
+ b: $b;
+}
+
+.foo { include foo($a: 1, 2, 3, 4)}
+SCSS
+ end
+
+ def test_mixin_keyword_for_var_arg
+ assert_raise_message(Sass::SyntaxError, "Argument $b of mixin foo cannot be used as a named argument.")
{render <<SCSS}
+ mixin foo($a, $b...) {
+ a: $a;
+ b: $b;
+}
+
+.foo { include foo(1, $b: 2 3 4)}
+SCSS
+ end
+
+ def test_mixin_keyword_for_unknown_arg_with_var_args
+ assert_raise_message(Sass::SyntaxError, "Mixin foo doesn't have an argument named $c.") {render <<SCSS}
+ mixin foo($a, $b...) {
+ a: $a;
+ b: $b;
+}
+
+.foo { include foo(1, $c: 2 3 4)}
+SCSS
+ end
+
+ def test_mixin_map_splat_before_list_splat
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was (2 3)).") {render
<<SCSS}
+ mixin foo($a, $b, $c) {
+ a: $a;
+ b: $b;
+ c: $c;
+}
+
+.foo {
+ @include foo((a: 1)..., (2 3)...);
+}
+SCSS
+ end
+
+ def test_mixin_map_splat_with_unknown_keyword
+ assert_raise_message(Sass::SyntaxError, "Mixin foo doesn't have an argument named $c.") {render <<SCSS}
+ mixin foo($a, $b) {
+ a: $a;
+ b: $b;
+}
+
+.foo {
+ @include foo(1, 2, (c: 1)...);
+}
+SCSS
+ end
+
+ def test_mixin_map_splat_with_wrong_type
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was 12).") {render
<<SCSS}
+ mixin foo($a, $b) {
+ a: $a;
+ b: $b;
+}
+
+.foo {
+ @include foo((1, 2)..., 12...);
+}
+SCSS
+ end
+
+ def test_mixin_splat_too_many_args
+ assert_warning(<<WARNING) {render <<SCSS}
+WARNING: Mixin foo takes 2 arguments but 4 were passed.
+ on line 2 of #{filename_for_test(:scss)}
+This will be an error in future versions of Sass.
+WARNING
+ mixin foo($a, $b) {}
+ include foo((1, 2, 3, 4)...);
+SCSS
+ end
+
+ def test_function_var_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, 3, 4"; }
+CSS
+ function foo($a, $b...) {
+ @return "a: \#{$a}, b: \#{$b}";
+}
+
+.foo {val: foo(1, 2, 3, 4)}
+SCSS
+ end
+
+ def test_function_empty_var_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 0"; }
+CSS
+ function foo($a, $b...) {
+ @return "a: \#{$a}, b: \#{length($b)}";
+}
+
+.foo {val: foo(1)}
+SCSS
+ end
+
+ def test_function_var_args_act_like_list
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 3, b: 3"; }
+CSS
+ function foo($a, $b...) {
+ @return "a: \#{length($b)}, b: \#{nth($b, 2)}";
+}
+
+.foo {val: foo(1, 2, 3, 4)}
+SCSS
+ end
+
+ def test_function_splat_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3, d: 4"; }
+CSS
+ function foo($a, $b, $c, $d) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}, d: \#{$d}";
+}
+
+$list: 2, 3, 4;
+.foo {val: foo(1, $list...)}
+SCSS
+ end
+
+ def test_function_splat_expression
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3, d: 4"; }
+CSS
+ function foo($a, $b, $c, $d) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}, d: \#{$d}";
+}
+
+.foo {val: foo(1, (2, 3, 4)...)}
+SCSS
+ end
+
+ def test_function_splat_args_with_var_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, 3, 4"; }
+CSS
+ function foo($a, $b...) {
+ @return "a: \#{$a}, b: \#{$b}";
+}
+
+$list: 2, 3, 4;
+.foo {val: foo(1, $list...)}
+SCSS
+ end
+
+ def test_function_splat_args_with_var_args_and_normal_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3, 4"; }
+CSS
+ function foo($a, $b, $c...) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+$list: 2, 3, 4;
+.foo {val: foo(1, $list...)}
+SCSS
+ end
+
+ def test_function_splat_args_with_var_args_preserves_separator
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2 3 4 5"; }
+CSS
+ function foo($a, $b...) {
+ @return "a: \#{$a}, b: \#{$b}";
+}
+
+$list: 3 4 5;
+.foo {val: foo(1, 2, $list...)}
+SCSS
+ end
+
+ def test_function_var_and_splat_args_pass_through_keywords
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 3, b: 1, c: 2"; }
+CSS
+ function foo($a...) {
+ @return bar($a...);
+}
+
+ function bar($b, $c, $a) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {val: foo(1, $c: 2, $a: 3)}
+SCSS
+ end
+
+ def test_function_var_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3"; }
+CSS
+ function foo($args...) {
+ @return "a: \#{map-get(keywords($args), a)}, " +
+ "b: \#{map-get(keywords($args), b)}, " +
+ "c: \#{map-get(keywords($args), c)}";
+}
+
+.foo {val: foo($a: 1, $b: 2, $c: 3)}
+SCSS
+ end
+
+ def test_function_empty_var_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ length: 0; }
+CSS
+ function foo($args...) {
+ @return length(keywords($args));
+}
+
+.foo {length: foo()}
+SCSS
+ end
+
+ def test_function_map_splat
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3"; }
+CSS
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ $map: (a: 1, b: 2, c: 3);
+ val: foo($map...);
+}
+SCSS
+ end
+
+ def test_function_map_and_list_splat
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: x, b: y, c: z, d: 1, e: 2, f: 3"; }
+CSS
+ function foo($a, $b, $c, $d, $e, $f) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}, d: \#{$d}, e: \#{$e}, f: \#{$f}";
+}
+
+.foo {
+ $list: x y z;
+ $map: (d: 1, e: 2, f: 3);
+ val: foo($list..., $map...);
+}
+SCSS
+ end
+
+ def test_function_map_splat_takes_precedence_over_pass_through
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: z"; }
+CSS
+ function foo($args...) {
+ $map: (c: z);
+ @return bar($args..., $map...);
+}
+
+ function bar($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo(1, $b: 2, $c: 3);
+}
+SCSS
+ end
+
+ def test_ruby_function_map_splat_takes_precedence_over_pass_through
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: 1 2 3 z; }
+CSS
+ function foo($args...) {
+ $map: (val: z);
+ @return append($args..., $map...);
+}
+
+.foo {
+ val: foo(1 2 3, $val: 4)
+}
+SCSS
+ end
+
+ def test_function_list_of_pairs_splat_treated_as_list
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: a 1, b: b 2, c: c 3"; }
+CSS
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo((a 1, b 2, c 3)...);
+}
+SCSS
+ end
+
+ def test_function_splat_after_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3"; }
+CSS
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo(1, $c: 3, 2...);
+}
+SCSS
+ end
+
+ def test_function_keyword_args_after_splat
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3"; }
+CSS
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo(1, 2..., $c: 3);
+}
+SCSS
+ end
+
+ def test_function_keyword_splat_after_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "a: 1, b: 2, c: 3"; }
+CSS
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo(1, $b: 2, (c: 3)...);
+}
+SCSS
+ end
+
+ def test_function_triple_keyword_splat_merge
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: "foo: 1, bar: 2, kwarg: 3, a: 3, b: 2, c: 3"; }
+CSS
+ function foo($foo, $bar, $kwarg, $a, $b, $c) {
+ @return "foo: \#{$foo}, bar: \#{$bar}, kwarg: \#{$kwarg}, a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+ function bar($args...) {
+ @return foo($args..., $bar: 2, $a: 2, $b: 2, (kwarg: 3, a: 3, c: 3)...);
+}
+
+.foo {
+ val: bar($foo: 1, $a: 1, $b: 1, $c: 1);
+}
+SCSS
+ end
+
+ def test_function_conflicting_splat_after_keyword_args
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Function foo was passed argument $b both by position and by name.
+MESSAGE
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo(1, $b: 2, 3...);
+}
+SCSS
+ end
+
+ def test_function_positional_arg_after_splat
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Only keyword arguments may follow variable arguments (...).
+MESSAGE
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo(1, 2..., 3);
+}
+SCSS
+ end
+
+ def test_function_var_args_with_keyword
+ assert_raise_message(Sass::SyntaxError, "Positional arguments must come before keyword arguments.")
{render <<SCSS}
+ function foo($a, $b...) {
+ @return "a: \#{$a}, b: \#{$b}";
+}
+
+.foo {val: foo($a: 1, 2, 3, 4)}
+SCSS
+ end
+
+ def test_function_keyword_for_var_arg
+ assert_raise_message(Sass::SyntaxError, "Argument $b of function foo cannot be used as a named
argument.") {render <<SCSS}
+ function foo($a, $b...) {
+ @return "a: \#{$a}, b: \#{$b}";
+}
+
+.foo {val: foo(1, $b: 2 3 4)}
+SCSS
+ end
+
+ def test_function_keyword_for_unknown_arg_with_var_args
+ assert_raise_message(Sass::SyntaxError, "Function foo doesn't have an argument named $c.") {render
<<SCSS}
+ function foo($a, $b...) {
+ @return "a: \#{$a}, b: \#{length($b)}";
+}
+
+.foo {val: foo(1, $c: 2 3 4)}
+SCSS
+ end
+
+ def test_function_var_args_passed_to_native
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ val: #102035; }
+CSS
+ function foo($args...) {
+ @return adjust-color($args...);
+}
+
+.foo {val: foo(#102030, $blue: 5)}
+SCSS
+ end
+
+ def test_function_map_splat_before_list_splat
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was (2 3)).") {render
<<SCSS}
+ function foo($a, $b, $c) {
+ @return "a: \#{$a}, b: \#{$b}, c: \#{$c}";
+}
+
+.foo {
+ val: foo((a: 1)..., (2 3)...);
+}
+SCSS
+ end
+
+ def test_function_map_splat_with_unknown_keyword
+ assert_raise_message(Sass::SyntaxError, "Function foo doesn't have an argument named $c.") {render
<<SCSS}
+ function foo($a, $b) {
+ @return "a: \#{$a}, b: \#{$b}";
+}
+
+.foo {
+ val: foo(1, 2, (c: 1)...);
+}
+SCSS
+ end
+
+ def test_function_map_splat_with_wrong_type
+ assert_raise_message(Sass::SyntaxError, "Variable keyword arguments must be a map (was 12).") {render
<<SCSS}
+ function foo($a, $b) {
+ @return "a: \#{$a}, b: \#{$b}";
+}
+
+.foo {
+ val: foo((1, 2)..., 12...);
+}
+SCSS
+ end
+
+ def test_function_keyword_splat_must_have_string_keys
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
+Variable keyword argument map must have string keys.
+12 is not a string in (12: 1).
+MESSAGE
+ function foo($a) {
+ @return $a;
+}
+
+.foo {val: foo((12: 1)...)}
+SCSS
+ end
+
+ def test_function_splat_too_many_args
+ assert_warning(<<WARNING) {render <<SCSS}
+WARNING: Function foo takes 2 arguments but 4 were passed.
+ on line 2 of #{filename_for_test(:scss)}
+This will be an error in future versions of Sass.
+WARNING
+ function foo($a, $b) { return null}
+$var: foo((1, 2, 3, 4)...);
+SCSS
+ end
+
+ ## Interpolation
+
+ def test_basic_selector_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+foo ab baz {
+ a: b; }
+CSS
+foo \#{'a' + 'b'} baz {a: b}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo.bar baz {
+ a: b; }
+CSS
+foo\#{".bar"} baz {a: b}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo.bar baz {
+ a: b; }
+CSS
+\#{"foo"}.bar baz {a: b}
+SCSS
+ end
+
+ def test_selector_only_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+foo bar {
+ a: b; }
+CSS
+\#{"foo" + " bar"} {a: b}
+SCSS
+ end
+
+ def test_selector_interpolation_before_element_name
+ assert_equal <<CSS, render(<<SCSS)
+foo barbaz {
+ a: b; }
+CSS
+\#{"foo" + " bar"}baz {a: b}
+SCSS
+ end
+
+ def test_selector_interpolation_in_string
+ assert_equal <<CSS, render(<<SCSS)
+foo[val="bar foo bar baz"] {
+ a: b; }
+CSS
+foo[val="bar \#{"foo" + " bar"} baz"] {a: b}
+SCSS
+ end
+
+ def test_selector_interpolation_in_pseudoclass
+ assert_equal <<CSS, render(<<SCSS)
+foo:nth-child(5n) {
+ a: b; }
+CSS
+foo:nth-child(\#{5 + "n"}) {a: b}
+SCSS
+ end
+
+ def test_selector_interpolation_at_class_begininng
+ assert_equal <<CSS, render(<<SCSS)
+.zzz {
+ a: b; }
+CSS
+$zzz: zzz;
+.\#{$zzz} { a: b; }
+SCSS
+ end
+
+ def test_selector_interpolation_at_id_begininng
+ assert_equal <<CSS, render(<<SCSS)
+#zzz {
+ a: b; }
+CSS
+$zzz: zzz;
+#\#{$zzz} { a: b; }
+SCSS
+ end
+
+ def test_selector_interpolation_at_pseudo_begininng
+ assert_equal <<CSS, render(<<SCSS)
+:zzz::zzz {
+ a: b; }
+CSS
+$zzz: zzz;
+:\#{$zzz}::\#{$zzz} { a: b; }
+SCSS
+ end
+
+ def test_selector_interpolation_at_attr_beginning
+ assert_equal <<CSS, render(<<SCSS)
+[zzz=foo] {
+ a: b; }
+CSS
+$zzz: zzz;
+[\#{$zzz}=foo] { a: b; }
+SCSS
+ end
+
+ def test_selector_interpolation_at_attr_end
+ assert_equal <<CSS, render(<<SCSS)
+[foo=zzz] {
+ a: b; }
+CSS
+$zzz: zzz;
+[foo=\#{$zzz}] { a: b; }
+SCSS
+ end
+
+ def test_selector_interpolation_at_dashes
+ assert_equal <<CSS, render(<<SCSS)
+div {
+ -foo-a-b-foo: foo; }
+CSS
+$a : a;
+$b : b;
+div { -foo-\#{$a}-\#{$b}-foo: foo }
+SCSS
+ end
+
+ def test_selector_interpolation_in_reference_combinator
+ assert_equal <<CSS, render(<<SCSS)
+.foo /a/ .bar /b|c/ .baz {
+ a: b; }
+CSS
+$a: a;
+$b: b;
+$c: c;
+.foo /\#{$a}/ .bar /\#{$b}|\#{$c}/ .baz {a: b}
+SCSS
+ end
+
+ def test_parent_selector_with_parent_and_subject
+ silence_warnings {assert_equal <<CSS, render(<<SCSS)}
+bar foo.baz! .bip {
+ c: d; }
+CSS
+$subject: "!";
+foo {
+ bar &.baz\#{$subject} .bip {c: d}}
+SCSS
+ end
+
+ def test_basic_prop_name_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ barbazbang: blip; }
+CSS
+foo {bar\#{"baz" + "bang"}: blip}
+SCSS
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar3: blip; }
+CSS
+foo {bar\#{1 + 2}: blip}
+SCSS
+ end
+
+ def test_prop_name_only_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bazbang: blip; }
+CSS
+foo {\#{"baz" + "bang"}: blip}
+SCSS
+ end
+
+ def test_directive_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+ foo bar12 qux {
+ a: b; }
+CSS
+$baz: 12;
+ foo bar\#{$baz} qux {a: b}
+SCSS
+ end
+
+ def test_media_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+ media bar12 {
+ a: b; }
+CSS
+$baz: 12;
+ media bar\#{$baz} {a: b}
+SCSS
+ end
+
+ def test_script_in_media
+ assert_equal <<CSS, render(<<SCSS)
+ media screen and (-webkit-min-device-pixel-ratio: 20), only print {
+ a: b; }
+CSS
+$media1: screen;
+$media2: print;
+$var: -webkit-min-device-pixel-ratio;
+$val: 20;
+ media \#{$media1} and ($var: $val), only \#{$media2} {a: b}
+SCSS
+
+ assert_equal <<CSS, render(<<SCSS)
+ media screen and (-webkit-min-device-pixel-ratio: 13) {
+ a: b; }
+CSS
+$vals: 1 2 3;
+ media screen and (-webkit-min-device-pixel-ratio: 5 + 6 + nth($vals, 2)) {a: b}
+SCSS
+ end
+
+ def test_media_interpolation_with_reparse
+ assert_equal <<CSS, render(<<SCSS)
+ media screen and (max-width: 300px) {
+ a: b; }
+ media screen and (max-width: 300px) {
+ a: b; }
+ media screen and (max-width: 300px) {
+ a: b; }
+ media screen and (max-width: 300px), print and (max-width: 300px) {
+ a: b; }
+CSS
+$constraint: "(max-width: 300px)";
+$fragment: "nd \#{$constraint}";
+$comma: "een, pri";
+ media screen and \#{$constraint} {a: b}
+ media screen {
+ @media \#{$constraint} {a: b}
+}
+ media screen a\#{$fragment} {a: b}
+ media scr\#{$comma}nt {
+ @media \#{$constraint} {a: b}
+}
+SCSS
+ end
+
+ def test_moz_document_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+ -moz-document url(http://sass-lang.com/),
+ url-prefix(http://sass-lang.com/docs),
+ domain(sass-lang.com),
+ domain("sass-lang.com") {
+ .foo {
+ a: b; } }
+CSS
+$domain: "sass-lang.com";
+ -moz-document url(http://\#{$domain}/),
+ url-prefix(http://\#{$domain}/docs),
+ domain(\#{$domain}),
+ \#{domain($domain)} {
+ .foo {a: b}
+}
+SCSS
+ end
+
+ def test_supports_with_expressions
+ assert_equal <<CSS, render(<<SCSS)
+ supports (feature1: val) and (feature2: val) or (not (feature23: val4)) {
+ foo {
+ a: b; } }
+CSS
+$query: "(feature1: val)";
+$feature: feature2;
+$val: val;
+ supports \#{$query} and ($feature: $val) or (not ($feature + 3: $val + 4)) {
+ foo {a: b}
+}
+SCSS
+ end
+
+ def test_supports_bubbling
+ assert_equal <<CSS, render(<<SCSS)
+ supports (foo: bar) {
+ a {
+ b: c; }
+ @supports (baz: bang) {
+ a {
+ d: e; } } }
+CSS
+a {
+ @supports (foo: bar) {
+ b: c;
+ @supports (baz: bang) {
+ d: e;
+ }
+ }
+}
+SCSS
+ end
+
+ def test_random_directive_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+ foo url(http://sass-lang.com/),
+ domain("sass-lang.com"),
+ "foobarbaz",
+ foobarbaz {
+ .foo {
+ a: b; } }
+CSS
+$domain: "sass-lang.com";
+ foo url(http://\#{$domain}/),
+ \#{domain($domain)},
+ "foo\#{'ba' + 'r'}baz",
+ foo\#{'ba' + 'r'}baz {
+ .foo {a: b}
+}
+SCSS
+ end
+
+ def test_color_interpolation_warning_in_selector
+ assert_warning(<<WARNING) {assert_equal <<CSS, render(<<SCSS)}
+WARNING on line 1, column 4 of #{filename_for_test(:scss)}:
+You probably don't mean to use the color value `blue' in interpolation here.
+It may end up represented as #0000ff, which will likely produce invalid CSS.
+Always quote color names when using them as strings (for example, "blue").
+If you really want to use the color value here, use `"" + blue'.
+WARNING
+fooblue {
+ a: b; }
+CSS
+foo\#{blue} {a: b}
+SCSS
+ end
+
+ def test_color_interpolation_warning_in_directive
+ assert_warning(<<WARNING) {assert_equal <<CSS, render(<<SCSS)}
+WARNING on line 1, column 12 of #{filename_for_test(:scss)}:
+You probably don't mean to use the color value `blue' in interpolation here.
+It may end up represented as #0000ff, which will likely produce invalid CSS.
+Always quote color names when using them as strings (for example, "blue").
+If you really want to use the color value here, use `"" + blue'.
+WARNING
+ fblthp fooblue {
+ a: b; }
+CSS
+ fblthp foo\#{blue} {a: b}
+SCSS
+ end
+
+ def test_color_interpolation_warning_in_property_name
+ assert_warning(<<WARNING) {assert_equal <<CSS, render(<<SCSS)}
+WARNING on line 1, column 8 of #{filename_for_test(:scss)}:
+You probably don't mean to use the color value `blue' in interpolation here.
+It may end up represented as #0000ff, which will likely produce invalid CSS.
+Always quote color names when using them as strings (for example, "blue").
+If you really want to use the color value here, use `"" + blue'.
+WARNING
+foo {
+ a-blue: b; }
+CSS
+foo {a-\#{blue}: b}
+SCSS
+ end
+
+ def test_no_color_interpolation_warning_in_property_value
+ assert_no_warning {assert_equal <<CSS, render(<<SCSS)}
+foo {
+ a: b-blue; }
+CSS
+foo {a: b-\#{blue}}
+SCSS
+ end
+
+ def test_no_color_interpolation_warning_for_nameless_color
+ assert_no_warning {assert_equal <<CSS, render(<<SCSS)}
+foo-#abcdef {
+ a: b; }
+CSS
+foo-\#{#abcdef} {a: b}
+SCSS
+ end
+
+ def test_nested_mixin_def
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: b; }
+CSS
+foo {
+ @mixin bar {a: b}
+ @include bar; }
+SCSS
+ end
+
+ def test_nested_mixin_shadow
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ c: d; }
+
+baz {
+ a: b; }
+CSS
+ mixin bar {a: b}
+
+foo {
+ @mixin bar {c: d}
+ @include bar;
+}
+
+baz { include bar}
+SCSS
+ end
+
+ def test_nested_function_def
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 1; }
+
+bar {
+ b: foo(); }
+CSS
+foo {
+ @function foo() { return 1}
+ a: foo(); }
+
+bar {b: foo()}
+SCSS
+ end
+
+ def test_nested_function_shadow
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 2; }
+
+baz {
+ b: 1; }
+CSS
+ function foo() { return 1}
+
+foo {
+ @function foo() { return 2}
+ a: foo();
+}
+
+baz {b: foo()}
+SCSS
+ end
+
+ ## @at-root
+
+ def test_simple_at_root
+ assert_equal <<CSS, render(<<SCSS)
+.bar {
+ a: b; }
+CSS
+.foo {
+ @at-root {
+ .bar {a: b}
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_selector
+ assert_equal <<CSS, render(<<SCSS)
+.bar {
+ a: b; }
+CSS
+.foo {
+ @at-root .bar {a: b}
+}
+SCSS
+ end
+
+ def test_at_root_in_mixin
+ assert_equal <<CSS, render(<<SCSS)
+.bar {
+ a: b; }
+CSS
+ mixin bar {
+ @at-root .bar {a: b}
+}
+
+.foo {
+ @include bar;
+}
+SCSS
+ end
+
+ def test_at_root_in_media
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ .bar {
+ a: b; } }
+CSS
+ media screen {
+ .foo {
+ @at-root .bar {a: b}
+ }
+}
+SCSS
+ end
+
+ def test_at_root_in_bubbled_media
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @at-root .bar {a: b}
+ }
+}
+SCSS
+ end
+
+ def test_at_root_in_unknown_directive
+ assert_equal <<CSS, render(<<SCSS)
+ fblthp {
+ .bar {
+ a: b; } }
+CSS
+ fblthp {
+ .foo {
+ @at-root .bar {a: b}
+ }
+}
+SCSS
+ end
+
+ def test_comments_in_at_root
+ assert_equal <<CSS, render(<<SCSS)
+/* foo */
+.bar {
+ a: b; }
+
+/* baz */
+CSS
+.foo {
+ @at-root {
+ /* foo */
+ .bar {a: b}
+ /* baz */
+ }
+}
+SCSS
+ end
+
+ def test_comments_in_at_root_in_media
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ /* foo */
+ .bar {
+ a: b; }
+
+ /* baz */ }
+CSS
+ media screen {
+ .foo {
+ @at-root {
+ /* foo */
+ .bar {a: b}
+ /* baz */
+ }
+ }
+}
+SCSS
+ end
+
+ def test_comments_in_at_root_in_unknown_directive
+ assert_equal <<CSS, render(<<SCSS)
+ fblthp {
+ /* foo */
+ .bar {
+ a: b; }
+
+ /* baz */ }
+CSS
+ fblthp {
+ .foo {
+ @at-root {
+ /* foo */
+ .bar {a: b}
+ /* baz */
+ }
+ }
+}
+SCSS
+ end
+
+ def test_media_directive_in_at_root
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @at-root {
+ @media screen {.bar {a: b}}
+ }
+}
+SCSS
+ end
+
+ def test_bubbled_media_directive_in_at_root
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ .bar .baz {
+ a: b; } }
+CSS
+.foo {
+ @at-root {
+ .bar {
+ @media screen {.baz {a: b}}
+ }
+ }
+}
+SCSS
+ end
+
+ def test_unknown_directive_in_at_root
+ assert_equal <<CSS, render(<<SCSS)
+ fblthp {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @at-root {
+ @fblthp {.bar {a: b}}
+ }
+}
+SCSS
+ end
+
+ def test_at_root_in_at_root
+ assert_equal <<CSS, render(<<SCSS)
+.bar {
+ a: b; }
+CSS
+.foo {
+ @at-root {
+ @at-root .bar {a: b}
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_parent_ref
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+CSS
+.foo {
+ @at-root & {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_multi_level_at_root_with_parent_ref
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar {
+ a: b; }
+CSS
+.foo {
+ @at-root & {
+ .bar {
+ @at-root & {
+ a: b;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_multi_level_at_root_with_inner_parent_ref
+ assert_equal <<CSS, render(<<SCSS)
+.bar {
+ a: b; }
+CSS
+.foo {
+ @at-root .bar {
+ @at-root & {
+ a: b;
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_beneath_comma_selector
+ assert_equal(<<CSS, render(<<SCSS))
+.baz {
+ a: b; }
+CSS
+.foo, .bar {
+ @at-root .baz {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_parent_ref_and_class
+ assert_equal(<<CSS, render(<<SCSS))
+.foo.bar {
+ a: b; }
+CSS
+.foo {
+ @at-root &.bar {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_at_root_beneath_comma_selector_with_parent_ref
+ assert_equal(<<CSS, render(<<SCSS))
+.foo.baz, .bar.baz {
+ a: b; }
+CSS
+.foo, .bar {
+ @at-root &.baz {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ ## @at-root (...)
+
+ def test_at_root_without_media
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar {
+ a: b; }
+CSS
+.foo {
+ @media screen {
+ @at-root (without: media) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without_supports
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar {
+ a: b; }
+CSS
+.foo {
+ @supports (foo: bar) {
+ @at-root (without: supports) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without_rule
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @at-root (without: rule) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without_unknown_directive
+ assert_equal <<CSS, render(<<SCSS)
+ fblthp {}
+.foo .bar {
+ a: b; }
+CSS
+.foo {
+ @fblthp {
+ @at-root (without: fblthp) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without_multiple
+ assert_equal <<CSS, render(<<SCSS)
+ supports (foo: bar) {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @supports (foo: bar) {
+ @at-root (without: media rule) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without_all
+ assert_equal <<CSS, render(<<SCSS)
+ supports (foo: bar) {
+ @fblthp {} }
+.bar {
+ a: b; }
+CSS
+.foo {
+ @supports (foo: bar) {
+ @fblthp {
+ @at-root (without: all) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_media
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ @fblthp {}
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @fblthp {
+ @supports (foo: bar) {
+ @at-root (with: media) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_rule
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ @fblthp {} }
+.foo .bar {
+ a: b; }
+CSS
+.foo {
+ @media screen {
+ @fblthp {
+ @supports (foo: bar) {
+ @at-root (with: rule) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_supports
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ @fblthp {} }
+ supports (foo: bar) {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @fblthp {
+ @supports (foo: bar) {
+ @at-root (with: supports) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_unknown_directive
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ @fblthp {} }
+ fblthp {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @fblthp {
+ @supports (foo: bar) {
+ @at-root (with: fblthp) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_multiple
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ @fblthp {}
+ .foo .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @fblthp {
+ @supports (foo: bar) {
+ @at-root (with: media rule) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_all
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ @fblthp {
+ @supports (foo: bar) {
+ .foo .bar {
+ a: b; } } } }
+CSS
+.foo {
+ @media screen {
+ @fblthp {
+ @supports (foo: bar) {
+ @at-root (with: all) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_dynamic_values
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ .bar {
+ a: b; } }
+CSS
+$key: with;
+$value: media;
+.foo {
+ @media screen {
+ @at-root ($key: $value) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_interpolated_query
+ assert_equal <<CSS, render(<<SCSS)
+ media screen {
+ .bar {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @at-root (\#{"with: media"}) {
+ .bar {
+ a: b;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_plus_extend
+ assert_equal <<CSS, render(<<SCSS)
+.foo .bar {
+ a: b; }
+CSS
+%base {
+ a: b;
+}
+
+.foo {
+ @media screen {
+ @at-root (without: media) {
+ .bar {
+ @extend %base;
+ }
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without_keyframes_in_keyframe_rule
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+CSS
+ keyframes identifier {
+ 0% {
+ @at-root (without: keyframes) {
+ .foo {a: b}
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_without_rule_in_keyframe_rule
+ assert_equal <<CSS, render(<<SCSS)
+ keyframes identifier {
+ 0% {
+ a: b; } }
+CSS
+ keyframes identifier {
+ 0% {
+ @at-root (without: rule) {a: b}
+ }
+}
+SCSS
+ end
+
+ ## Selector Script
+
+ def test_selector_script
+ assert_equal(<<CSS, render(<<SCSS))
+.foo .bar {
+ content: ".foo .bar"; }
+CSS
+.foo .bar {
+ content: "\#{&}";
+}
+SCSS
+ end
+
+ def test_nested_selector_script
+ assert_equal(<<CSS, render(<<SCSS))
+.foo .bar {
+ content: ".foo .bar"; }
+CSS
+.foo {
+ .bar {
+ content: "\#{&}";
+ }
+}
+SCSS
+ end
+
+ def test_nested_selector_script_with_outer_comma_selector
+ assert_equal(<<CSS, render(<<SCSS))
+.foo .baz, .bar .baz {
+ content: ".foo .baz, .bar .baz"; }
+CSS
+.foo, .bar {
+ .baz {
+ content: "\#{&}";
+ }
+}
+SCSS
+ end
+
+ def test_nested_selector_script_with_inner_comma_selector
+ assert_equal(<<CSS, render(<<SCSS))
+.foo .bar, .foo .baz {
+ content: ".foo .bar, .foo .baz"; }
+CSS
+.foo {
+ .bar, .baz {
+ content: "\#{&}";
+ }
+}
+SCSS
+ end
+
+ def test_selector_script_through_mixin
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ content: ".foo"; }
+CSS
+ mixin mixin {
+ content: "\#{&}";
+}
+
+.foo {
+ @include mixin;
+}
+SCSS
+ end
+
+ def test_selector_script_through_content
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ content: ".foo"; }
+CSS
+ mixin mixin {
+ @content;
+}
+
+.foo {
+ @include mixin {
+ content: "\#{&}";
+ }
+}
+SCSS
+ end
+
+ def test_selector_script_through_function
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ content: ".foo"; }
+CSS
+ function fn() {
+ @return "\#{&}";
+}
+
+.foo {
+ content: fn();
+}
+SCSS
+ end
+
+ def test_selector_script_through_media
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ content: "outer"; }
+ @media screen {
+ .foo .bar {
+ content: ".foo .bar"; } }
+CSS
+.foo {
+ content: "outer";
+ @media screen {
+ .bar {
+ content: "\#{&}";
+ }
+ }
+}
+SCSS
+ end
+
+ def test_selector_script_save_and_reuse
+ assert_equal(<<CSS, render(<<SCSS))
+.bar {
+ content: ".foo"; }
+CSS
+$var: null;
+.foo {
+ $var: & !global;
+}
+
+.bar {
+ content: "\#{$var}";
+}
+SCSS
+ end
+
+ def test_selector_script_with_at_root
+ assert_equal(<<CSS, render(<<SCSS))
+.foo-bar {
+ a: b; }
+CSS
+.foo {
+ @at-root \#{&}-bar {
+ a: b;
+ }
+}
+SCSS
+ end
+
+ def test_multi_level_at_root_with_inner_selector_script
+ assert_equal <<CSS, render(<<SCSS)
+.bar {
+ a: b; }
+CSS
+.foo {
+ @at-root .bar {
+ @at-root \#{&} {
+ a: b;
+ }
+ }
+}
+SCSS
+ end
+
+ def test_at_root_with_at_root_through_mixin
+ assert_equal(<<CSS, render(<<SCSS))
+.bar-baz {
+ a: b; }
+CSS
+ mixin foo {
+ .bar {
+ @at-root \#{&}-baz {
+ a: b;
+ }
+ }
+}
+
+ include foo;
+SCSS
+ end
+
+ # See https://github.com/sass/sass/issues/1294
+ def test_extend_top_leveled_by_at_root
+ render(<<SCSS)
+.span-10 {
+ @at-root (without: all) {
+ @extend %column;
+ }
+}
+SCSS
+
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal "Extend directives may only be used within rules.", e.message
+ assert_equal 3, e.sass_line
+ end
+
+ def test_at_root_doesnt_always_break_blocks
+ assert_equal <<CSS, render(<<SCSS)
+.foo {
+ a: b; }
+
+ media screen {
+ .foo {
+ c: d; }
+ .bar {
+ e: f; } }
+CSS
+%base {
+ a: b;
+}
+
+ media screen {
+ .foo {
+ c: d;
+ @at-root (without: media) {
+ @extend %base;
+ }
+ }
+
+ .bar {e: f}
+}
+SCSS
+ end
+
+ ## Errors
+
+ def test_keyframes_rule_outside_of_keyframes
+ render <<SCSS
+0% {
+ top: 0; }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after "": expected selector, was "0%"', e.message
+ assert_equal 1, e.sass_line
+ end
+
+ def test_selector_rule_in_keyframes
+ render <<SCSS
+ keyframes identifier {
+ .foo {
+ top: 0; } }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after "": expected keyframes selector (e.g. 10%), was ".foo"', e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_nested_mixin_def_is_scoped
+ render <<SCSS
+foo {
+ @mixin bar {a: b}}
+bar { include bar}
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal "Undefined mixin 'bar'.", e.message
+ assert_equal 3, e.sass_line
+ end
+
+ def test_rules_beneath_properties
+ render <<SCSS
+foo {
+ bar: {
+ baz {
+ bang: bop }}}
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Illegal nesting: Only properties may be nested beneath properties.', e.message
+ assert_equal 3, e.sass_line
+ end
+
+ def test_uses_property_exception_with_star_hack
+ render <<SCSS
+foo {
+ *bar:baz [fail]; }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " *bar:baz ": expected ";", was "[fail]; }"', e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_uses_property_exception_with_colon_hack
+ render <<SCSS
+foo {
+ :bar:baz [fail]; }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " :bar:baz ": expected ";", was "[fail]; }"', e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_uses_rule_exception_with_dot_hack
+ render <<SCSS
+foo {
+ .bar:baz <fail>; }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " .bar:baz <fail>": expected expression (e.g. 1px, bold), was "; }"',
e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_uses_property_exception_with_space_after_name
+ render <<SCSS
+foo {
+ bar: baz [fail]; }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " bar: baz ": expected ";", was "[fail]; }"', e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_uses_property_exception_with_non_identifier_after_name
+ render <<SCSS
+foo {
+ bar:1px [fail]; }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " bar:1px ": expected ";", was "[fail]; }"', e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_uses_property_exception_when_followed_by_open_bracket
+ render <<SCSS
+foo {
+ bar:{baz: .fail} }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " bar:{baz: ": expected expression (e.g. 1px, bold), was ".fail} }"',
e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_script_error
+ render <<SCSS
+foo {
+ bar: "baz" * * }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " bar: "baz" * ": expected expression (e.g. 1px, bold), was "* }"',
e.message
+ assert_equal 2, e.sass_line
+ end
+
+ def test_multiline_script_syntax_error
+ render <<SCSS
+foo {
+ bar:
+ "baz" * * }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after " "baz" * ": expected expression (e.g. 1px, bold), was "* }"',
e.message
+ assert_equal 3, e.sass_line
+ end
+
+ def test_multiline_script_runtime_error
+ render <<SCSS
+foo {
+ bar: "baz" +
+ "bar" +
+ $bang }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal "Undefined variable: \"$bang\".", e.message
+ assert_equal 4, e.sass_line
+ end
+
+ def test_post_multiline_script_runtime_error
+ render <<SCSS
+foo {
+ bar: "baz" +
+ "bar" +
+ "baz";
+ bip: $bop; }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal "Undefined variable: \"$bop\".", e.message
+ assert_equal 5, e.sass_line
+ end
+
+ def test_multiline_property_runtime_error
+ render <<SCSS
+foo {
+ bar: baz
+ bar
+ \#{$bang} }
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal "Undefined variable: \"$bang\".", e.message
+ assert_equal 4, e.sass_line
+ end
+
+ def test_post_resolution_selector_error
+ render "\n\nfoo \#{\") bar\"} {a: b}"
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal 'Invalid CSS after "foo ": expected selector, was ") bar"', e.message
+ assert_equal 3, e.sass_line
+ end
+
+ def test_parent_in_mid_selector_error
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
+Invalid CSS after ".foo": expected "{", was "&.bar"
+
+"&.bar" may only be used at the beginning of a compound selector.
+MESSAGE
+flim {
+ .foo&.bar {a: b}
+}
+SCSS
+ end
+
+ def test_parent_after_selector_error
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
+Invalid CSS after ".foo.bar": expected "{", was "&"
+
+"&" may only be used at the beginning of a compound selector.
+MESSAGE
+flim {
+ .foo.bar& {a: b}
+}
+SCSS
+ end
+
+ def test_double_parent_selector_error
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
+Invalid CSS after "&": expected "{", was "&"
+
+"&" may only be used at the beginning of a compound selector.
+MESSAGE
+flim {
+ && {a: b}
+}
+SCSS
+ end
+
+ def test_no_lonely_else
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
+Invalid CSS: @else must come after @if
+MESSAGE
+ else {foo: bar}
+SCSS
+ end
+
+ def test_failed_parent_selector_with_suffix
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": "*"
+MESSAGE
+* {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": "[foo=bar]"
+MESSAGE
+[foo=bar] {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": "::nth-child(2n+1)"
+MESSAGE
+::nth-child(2n+1) {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": ":not(.foo)"
+MESSAGE
+:not(.foo) {
+ &-bar {a: b}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid parent selector for "&-bar": ".foo +"
+MESSAGE
+.foo + {
+ &-bar {a: b}
+}
+SCSS
+ end
+
+ def test_empty_media_query_error
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render(<<SCSS)}
+Invalid CSS after "": expected media query list, was ""
+MESSAGE
+ media \#{""} {
+ foo {a: b}
+}
+SCSS
+ end
+
+ def test_newline_in_property_value
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ bar: "bazbang"; }
+CSS
+.foo {
+ $var: "baz\\
+bang";
+ bar: $var;
+}
+SCSS
+ end
+
+ def test_raw_newline_warning
+ assert_warning(<<MESSAGE.rstrip) {assert_equal(<<CSS, render(<<SCSS))}
+DEPRECATION WARNING on line 2, column 9 of #{filename_for_test :scss}:
+Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
+To include a newline in a string, use "\\a" or "\\a " as in CSS.
+MESSAGE
+.foo {
+ bar: "baz\\a bang"; }
+CSS
+.foo {
+ $var: "baz
+bang";
+ bar: $var;
+}
+SCSS
+ end
+
+ # Regression
+
+ def test_escape_in_selector
+ assert_equal(<<CSS, render(".\\!foo {a: b}"))
+.\\!foo {
+ a: b; }
+CSS
+ end
+
+ def test_for_directive_with_float_bounds
+ assert_equal(<<CSS, render(<<SCSS))
+.a {
+ b: 0;
+ b: 1;
+ b: 2;
+ b: 3;
+ b: 4;
+ b: 5; }
+CSS
+.a {
+ @for $i from 0.0 through 5.0 {b: $i}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, "0.5 is not an integer.") {render(<<SCSS)}
+.a {
+ @for $i from 0.5 through 5.0 {b: $i}
+}
+SCSS
+
+ assert_raise_message(Sass::SyntaxError, "5.5 is not an integer.") {render(<<SCSS)}
+.a {
+ @for $i from 0.0 through 5.5 {b: $i}
+}
+SCSS
+ end
+
+ def test_parent_selector_in_function_pseudo_selector
+ assert_equal <<CSS, render(<<SCSS)
+.bar:not(.foo) {
+ a: b; }
+
+.qux:nth-child(2n of .baz .bang) {
+ c: d; }
+CSS
+.foo {
+ .bar:not(&) {a: b}
+}
+
+.baz .bang {
+ .qux:nth-child(2n of &) {c: d}
+}
+SCSS
+ end
+
+ def test_attribute_selector_in_selector_pseudoclass
+ # Even though this is plain CSS, it only failed when given to the SCSS
+ # parser.
+ assert_equal(<<CSS, render(<<SCSS))
+[href^='http://'] {
+ color: red; }
+CSS
+[href^='http://'] {
+ color: red;
+}
+SCSS
+ end
+
+ def test_top_level_unknown_directive_in_at_root
+ assert_equal(<<CSS, render(<<SCSS))
+ fblthp {
+ a: b; }
+CSS
+ at-root {
+ @fblthp {a: b}
+}
+SCSS
+ end
+
+ def test_parent_ref_with_newline
+ assert_equal(<<CSS, render(<<SCSS))
+a.c
+, b.c {
+ x: y; }
+CSS
+a
+, b {&.c {x: y}}
+SCSS
+ end
+
+ def test_parent_ref_in_nested_at_root
+ assert_equal(<<CSS, render(<<SCSS))
+#test {
+ border: 0; }
+ #test:hover {
+ display: none; }
+CSS
+a {
+ @at-root #test {
+ border: 0;
+ &:hover{
+ display: none;
+ }
+ }
+}
+SCSS
+ end
+
+ def test_loud_comment_in_compressed_mode
+ assert_equal(<<CSS, render(<<SCSS))
+/*! foo */
+CSS
+/*! foo */
+SCSS
+ end
+
+ def test_parsing_decimals_followed_by_comments_doesnt_take_forever
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ padding: 4.21053% 4.21053% 5.63158%; }
+CSS
+.foo {
+ padding: 4.21052631578947% 4.21052631578947% 5.631578947368421% /**/
+}
+SCSS
+ end
+
+ def test_parsing_many_numbers_doesnt_take_forever
+ values = ["80% 90%"] * 1000
+ assert_equal(<<CSS, render(<<SCSS))
+.foo {
+ padding: #{values.join(', ')}; }
+CSS
+.foo {
+ padding: #{values.join(', ')};
+}
+SCSS
+ end
+
+ def test_import_comments_in_imports
+ assert_equal(<<CSS, render(<<SCSS))
+ import url(foo.css);
+ import url(bar.css);
+ import url(baz.css);
+CSS
+ import "foo.css", // this is a comment
+ "bar.css", /* this is another comment */
+ "baz.css"; // this is a third comment
+SCSS
+ end
+
+ def test_reference_combinator_with_parent_ref
+ assert_equal <<CSS, render(<<SCSS)
+a /foo/ b {
+ c: d; }
+CSS
+a {& /foo/ b {c: d}}
+SCSS
+ end
+
+ def test_newline_selector_rendered_multiple_times
+ assert_equal <<CSS, render(<<SCSS)
+form input,
+form select {
+ color: white; }
+
+form input,
+form select {
+ color: white; }
+CSS
+ for $i from 1 through 2 {
+ form {
+ input,
+ select {
+ color: white;
+ }
+ }
+}
+SCSS
+ end
+
+ def test_prop_name_interpolation_after_hyphen
+ assert_equal <<CSS, render(<<SCSS)
+a {
+ -foo-bar: b; }
+CSS
+a { -\#{"foo"}-bar: b; }
+SCSS
+ end
+
+ def test_star_plus_and_parent
+ assert_equal <<CSS, render(<<SCSS)
+* + html foo {
+ a: b; }
+CSS
+foo {*+html & {a: b}}
+SCSS
+ end
+
+ def test_weird_added_space
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ bar: -moz-bip; }
+CSS
+$value : bip;
+
+foo {
+ bar: -moz-\#{$value};
+}
+SCSS
+ end
+
+ def test_interpolation_with_bracket_on_next_line
+ assert_equal <<CSS, render(<<SCSS)
+a.foo b {
+ color: red; }
+CSS
+a.\#{"foo"} b
+{color: red}
+SCSS
+ end
+
+ def test_extra_comma_in_mixin_arglist_error
+ assert_raise_message(Sass::SyntaxError, <<MESSAGE.rstrip) {render <<SCSS}
+Invalid CSS after "...clude foo(bar, ": expected mixin argument, was ");"
+MESSAGE
+ mixin foo($a1, $a2) {
+ baz: $a1 $a2;
+}
+
+.bar {
+ @include foo(bar, );
+}
+SCSS
+ end
+
+ def test_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+ul li#foo a span.label {
+ foo: bar; }
+CSS
+$bar : "#foo";
+ul li\#{$bar} a span.label { foo: bar; }
+SCSS
+ end
+
+ def test_mixin_with_keyword_args
+ assert_equal <<CSS, render(<<SCSS)
+.mixed {
+ required: foo;
+ arg1: default-val1;
+ arg2: non-default-val2; }
+CSS
+ mixin a-mixin($required, $arg1: default-val1, $arg2: default-val2) {
+ required: $required;
+ arg1: $arg1;
+ arg2: $arg2;
+}
+.mixed { @include a-mixin(foo, $arg2: non-default-val2); }
+SCSS
+ end
+
+ def test_passing_required_args_as_a_keyword_arg
+ assert_equal <<CSS, render(<<SCSS)
+.mixed {
+ required: foo;
+ arg1: default-val1;
+ arg2: default-val2; }
+CSS
+ mixin a-mixin($required, $arg1: default-val1, $arg2: default-val2) {
+ required: $required;
+ arg1: $arg1;
+ arg2: $arg2; }
+.mixed { @include a-mixin($required: foo); }
+SCSS
+ end
+
+ def test_passing_all_as_keyword_args_in_opposite_order
+ assert_equal <<CSS, render(<<SCSS)
+.mixed {
+ required: foo;
+ arg1: non-default-val1;
+ arg2: non-default-val2; }
+CSS
+ mixin a-mixin($required, $arg1: default-val1, $arg2: default-val2) {
+ required: $required;
+ arg1: $arg1;
+ arg2: $arg2; }
+.mixed { @include a-mixin($arg2: non-default-val2, $arg1: non-default-val1, $required: foo); }
+SCSS
+ end
+
+ def test_keyword_args_in_functions
+ assert_equal <<CSS, render(<<SCSS)
+.keyed {
+ color: rgba(170, 119, 204, 0.4); }
+CSS
+.keyed { color: rgba($color: #a7c, $alpha: 0.4) }
+SCSS
+ end
+
+ def test_unknown_keyword_arg_raises_error
+ assert_raise_message(Sass::SyntaxError, "Mixin a doesn't have an argument named $c.") {render <<SCSS}
+ mixin a($b: 1) { a: $b; }
+div { @include a(1, $c: 3); }
+SCSS
+ end
+
+
+ def test_newlines_removed_from_selectors_when_compressed
+ assert_equal <<CSS, render(<<SCSS, :style => :compressed)
+z a,z b{display:block}
+CSS
+a
+, b {
+ z & {
+ display: block;
+ }
+}
+SCSS
+ end
+
+ def test_if_error_line
+ assert_raise_line(2) {render(<<SCSS)}
+ if true {foo: bar}
+}
+SCSS
+ end
+
+ def test_multiline_var
+ assert_equal <<CSS, render(<<SCSS)
+foo {
+ a: 3;
+ b: false;
+ c: a b c; }
+CSS
+foo {
+ $var1: 1 +
+ 2;
+ $var2: true and
+ false;
+ $var3: a b
+ c;
+ a: $var1;
+ b: $var2;
+ c: $var3; }
+SCSS
+ end
+
+ def test_mixin_content
+ assert_equal <<CSS, render(<<SASS)
+.parent {
+ background-color: red;
+ border-color: red; }
+ .parent .child {
+ background-color: yellow;
+ color: blue;
+ border-color: yellow; }
+CSS
+$color: blue;
+ mixin context($class, $color: red) {
+ .\#{$class} {
+ background-color: $color;
+ @content;
+ border-color: $color;
+ }
+}
+ include context(parent) {
+ @include context(child, $color: yellow) {
+ color: $color;
+ }
+}
+SASS
+ end
+
+ def test_empty_content
+ assert_equal <<CSS, render(<<SCSS)
+a {
+ b: c; }
+CSS
+ mixin foo { @content }
+a { b: c; @include foo {} }
+SCSS
+ end
+
+ def test_options_passed_to_script
+ assert_equal <<CSS, render(<<SCSS, :style => :compressed)
+foo{color:#000}
+CSS
+foo {color: darken(black, 10%)}
+SCSS
+ end
+
+ # ref: https://github.com/nex3/sass/issues/104
+ def test_no_buffer_overflow
+ template = render <<SCSS
+.aaa {
+ background-color: white;
+}
+.aaa .aaa .aaa {
+ background-color: black;
+}
+.bbb {
+ @extend .aaa;
+}
+.xxx {
+ @extend .bbb;
+}
+.yyy {
+ @extend .bbb;
+}
+.zzz {
+ @extend .bbb;
+}
+SCSS
+ Sass::SCSS::Parser.new(template, "test.scss", nil).parse
+ end
+
+ def test_extend_in_media_in_rule
+ assert_equal(<<CSS, render(<<SCSS))
+ media screen {
+ .foo {
+ a: b; } }
+CSS
+.foo {
+ @media screen {
+ @extend %bar;
+ }
+}
+
+ media screen {
+ %bar {
+ a: b;
+ }
+}
+SCSS
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/scss/test_helper.rb
b/backends/css/gems/sass-3.4.9/test/sass/scss/test_helper.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/scss/test_helper.rb
rename to backends/css/gems/sass-3.4.9/test/sass/scss/test_helper.rb
diff --git a/backends/css/gems/sass-3.4.9/test/sass/source_map_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/source_map_test.rb
new file mode 100755
index 0000000..00424bf
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/source_map_test.rb
@@ -0,0 +1,977 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+
+class SourcemapTest < MiniTest::Test
+ def test_to_json_requires_args
+ _, sourcemap = render_with_sourcemap('')
+ assert_raises(ArgumentError) {sourcemap.to_json({})}
+ assert_raises(ArgumentError) {sourcemap.to_json({:css_path => 'foo'})}
+ assert_raises(ArgumentError) {sourcemap.to_json({:sourcemap_path => 'foo'})}
+ end
+
+ def test_simple_mapping_scss
+ assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON
+a {
+ foo: bar;
+/* SOME COMMENT */
+ font-size: 12px;
+}
+SCSS
+a {
+ foo: bar;
+ /* SOME COMMENT */
+ font-size: 12px; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+{
+"version": 3,
+"mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;;EAER,SAAS,EAAE,IAAI",
+"sources": ["test_simple_mapping_scss_inline.scss"],
+"names": [],
+"file": "test.css"
+}
+JSON
+ end
+
+ def test_simple_mapping_sass
+ assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, :syntax => :sass
+a
+ foo: bar
+ /* SOME COMMENT */
+ :font-size 12px
+SASS
+a {
+ foo: bar;
+ /* SOME COMMENT */
+ font-size: 12px; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+{
+"version": 3,
+"mappings": "AAAA,CAAC;EACC,GAAG,EAAE,GAAG;;EAEP,SAAS,EAAC,IAAI",
+"sources": ["test_simple_mapping_sass_inline.sass"],
+"names": [],
+"file": "test.css"
+}
+JSON
+ end
+
+ def test_simple_mapping_with_file_uris
+ uri = Sass::Util.file_uri_from_path(Sass::Util.absolute_path(filename_for_test(:scss)))
+ assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON, :sourcemap => :file
+a {
+ foo: bar;
+/* SOME COMMENT */
+ font-size: 12px;
+}
+SCSS
+a {
+ foo: bar;
+ /* SOME COMMENT */
+ font-size: 12px; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+{
+"version": 3,
+"mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;;EAER,SAAS,EAAE,IAAI",
+"sources": ["#{uri}"],
+"names": [],
+"file": "test.css"
+}
+JSON
+ end
+
+ def test_mapping_with_directory_scss
+ options = {:filename => "scss/style.scss", :output => "css/style.css"}
+ assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON, options
+a {
+ foo: bar;
+/* SOME COMMENT */
+ font-size: 12px;
+}
+SCSS
+a {
+ foo: bar;
+ /* SOME COMMENT */
+ font-size: 12px; }
+
+/*# sourceMappingURL=style.css.map */
+CSS
+{
+"version": 3,
+"mappings": "AAAA,CAAE;EACA,GAAG,EAAE,GAAG;;EAER,SAAS,EAAE,IAAI",
+"sources": ["../scss/style.scss"],
+"names": [],
+"file": "style.css"
+}
+JSON
+ end
+
+ def test_mapping_with_directory_sass
+ options = {:filename => "sass/style.sass", :output => "css/style.css", :syntax => :sass}
+ assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, options
+a
+ foo: bar
+ /* SOME COMMENT */
+ :font-size 12px
+SASS
+a {
+ foo: bar;
+ /* SOME COMMENT */
+ font-size: 12px; }
+
+/*# sourceMappingURL=style.css.map */
+CSS
+{
+"version": 3,
+"mappings": "AAAA,CAAC;EACC,GAAG,EAAE,GAAG;;EAEP,SAAS,EAAC,IAAI",
+"sources": ["../sass/style.sass"],
+"names": [],
+"file": "style.css"
+}
+JSON
+ end
+
+ unless Sass::Util.ruby1_8?
+ def test_simple_charset_mapping_scss
+ assert_parses_with_sourcemap <<SCSS, <<CSS, <<JSON
+a {
+ fóó: bár;
+}
+SCSS
+ charset "UTF-8";
+a {
+ fóó: bár; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+{
+"version": 3,
+"mappings": ";AAAA,CAAE;EACA,GAAG,EAAE,GAAG",
+"sources": ["test_simple_charset_mapping_scss_inline.scss"],
+"names": [],
+"file": "test.css"
+}
+JSON
+ end
+
+ def test_simple_charset_mapping_sass
+ assert_parses_with_sourcemap <<SASS, <<CSS, <<JSON, :syntax => :sass
+a
+ fóó: bár
+SASS
+ charset "UTF-8";
+a {
+ fóó: bár; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+{
+"version": 3,
+"mappings": ";AAAA,CAAC;EACC,GAAG,EAAE,GAAG",
+"sources": ["test_simple_charset_mapping_sass_inline.sass"],
+"names": [],
+"file": "test.css"
+}
+JSON
+ end
+
+ def test_different_charset_than_encoding_scss
+ assert_parses_with_sourcemap(<<SCSS.force_encoding("IBM866"), <<CSS, <<JSON)
+ charset "IBM866";
+f\x86\x86 {
+ \x86: b;
+}
+SCSS
+ charset "UTF-8";
+fЖЖ {
+ Ж: b; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+{
+"version": 3,
+"mappings": ";AACA,GAAI;EACF,CAAC,EAAE,CAAC",
+"sources": ["test_different_charset_than_encoding_scss_inline.scss"],
+"names": [],
+"file": "test.css"
+}
+JSON
+ end
+
+ def test_different_charset_than_encoding_sass
+ assert_parses_with_sourcemap(<<SASS.force_encoding("IBM866"), <<CSS, <<JSON, :syntax => :sass)
+ charset "IBM866"
+f\x86\x86
+ \x86: b
+SASS
+ charset "UTF-8";
+fЖЖ {
+ Ж: b; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+{
+"version": 3,
+"mappings": ";AACA,GAAG;EACD,CAAC,EAAE,CAAC",
+"sources": ["test_different_charset_than_encoding_sass_inline.sass"],
+"names": [],
+"file": "test.css"
+}
+JSON
+ end
+ end
+
+ def test_import_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+ import {{1}}url(foo){{/1}},{{2}}url(moo) {{/2}}, {{3}}url(bar) {{/3}};
+ import {{4}}url(baz) screen print{{/4}};
+SCSS
+{{1}} import url(foo){{/1}};
+{{2}} import url(moo){{/2}};
+{{3}} import url(bar){{/3}};
+{{4}} import url(baz) screen print{{/4}};
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_import_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+ import {{1}}foo.css{{/1}},{{2}}moo.css{{/2}}, {{3}}bar.css{{/3}}
+ import {{4}}url(baz.css){{/4}}
+ import {{5}}url(qux.css) screen print{{/5}}
+SASS
+{{1}} import url(foo.css){{/1}};
+{{2}} import url(moo.css){{/2}};
+{{3}} import url(bar.css){{/3}};
+{{4}} import url(baz.css){{/4}};
+{{5}} import url(qux.css) screen print{{/5}};
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_media_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+{{1}} media screen, tv {{/1}}{
+ {{2}}body {{/2}}{
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}};
+ }
+}
+SCSS
+{{1}} media screen, tv{{/1}} {
+ {{2}}body{{/2}} {
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}}; } }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_media_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+{{1}} media screen, tv{{/1}}
+ {{2}}body{{/2}}
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}}
+SASS
+{{1}} media screen, tv{{/1}} {
+ {{2}}body{{/2}} {
+ {{3}}max-width{{/3}}: {{4}}1070px{{/4}}; } }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_interpolation_and_vars_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+$te: "te";
+$teal: {{5}}teal{{/5}};
+{{1}}p {{/1}}{
+ {{2}}con#{$te}nt{{/2}}: {{3}}"I a#{$te} #{5 + 10} pies!"{{/3}};
+ {{4}}color{{/4}}: $teal;
+}
+
+$name: foo;
+$attr: border;
+{{6}}p.#{$name} {{/6}}{
+ {{7}}#{$attr}-color{{/7}}: {{8}}blue{{/8}};
+ $font-size: 12px;
+ $line-height: 30px;
+ {{9}}font{{/9}}: {{10}}#{$font-size}/#{$line-height}{{/10}};
+}
+SCSS
+{{1}}p{{/1}} {
+ {{2}}content{{/2}}: {{3}}"I ate 15 pies!"{{/3}};
+ {{4}}color{{/4}}: {{5}}teal{{/5}}; }
+
+{{6}}p.foo{{/6}} {
+ {{7}}border-color{{/7}}: {{8}}blue{{/8}};
+ {{9}}font{{/9}}: {{10}}12px/30px{{/10}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_interpolation_and_vars_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+$te: "te"
+$teal: {{5}}teal{{/5}}
+{{1}}p{{/1}}
+ {{2}}con#{$te}nt{{/2}}: {{3}}"I a#{$te} #{5 + 10} pies!"{{/3}}
+ {{4}}color{{/4}}: $teal
+
+$name: foo
+$attr: border
+{{6}}p.#{$name}{{/6}}
+ {{7}}#{$attr}-color{{/7}}: {{8}}blue{{/8}}
+ $font-size: 12px
+ $line-height: 30px
+ {{9}}font{{/9}}: {{10}}#{$font-size}/#{$line-height}{{/10}}
+SASS
+{{1}}p{{/1}} {
+ {{2}}content{{/2}}: {{3}}"I ate 15 pies!"{{/3}};
+ {{4}}color{{/4}}: {{5}}teal{{/5}}; }
+
+{{6}}p.foo{{/6}} {
+ {{7}}border-color{{/7}}: {{8}}blue{{/8}};
+ {{9}}font{{/9}}: {{10}}12px/30px{{/10}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_selectors_properties_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+$width: 2px;
+$translucent-red: rgba(255, 0, 0, 0.5);
+{{1}}a {{/1}}{
+ {{8}}.special {{/8}}{
+ {{9}}color{{/9}}: {{10}}red{{/10}};
+ {{11}}&:hover {{/11}}{
+ {{12}}foo{{/12}}: {{13}}bar{{/13}};
+ {{14}}cursor{{/14}}: {{15}}e + -resize{{/15}};
+ {{16}}color{{/16}}: {{17}}opacify($translucent-red, 0.3){{/17}};
+ }
+ {{18}}&:after {{/18}}{
+ {{19}}content{{/19}}: {{20}}"I ate #{5 + 10} pies #{$width} thick!"{{/20}};
+ }
+ }
+ {{21}}&:active {{/21}}{
+ {{22}}color{{/22}}: {{23}}#010203 + #040506{{/23}};
+ {{24}}border{{/24}}: {{25}}$width solid black{{/25}};
+ }
+/* SOME COMMENT */
+ {{2}}font{{/2}}: {{3}}2px/3px {{/3}}{
+ {{4}}family{{/4}}: {{5}}fantasy{{/5}};
+ {{6}}size{{/6}}: {{7}}1em + (2em * 3){{/7}};
+ }
+}
+SCSS
+{{1}}a{{/1}} {
+ /* SOME COMMENT */
+ {{2}}font{{/2}}: {{3}}2px/3px{{/3}};
+ {{4}}font-family{{/4}}: {{5}}fantasy{{/5}};
+ {{6}}font-size{{/6}}: {{7}}7em{{/7}}; }
+ {{8}}a .special{{/8}} {
+ {{9}}color{{/9}}: {{10}}red{{/10}}; }
+ {{11}}a .special:hover{{/11}} {
+ {{12}}foo{{/12}}: {{13}}bar{{/13}};
+ {{14}}cursor{{/14}}: {{15}}e-resize{{/15}};
+ {{16}}color{{/16}}: {{17}}rgba(255, 0, 0, 0.8){{/17}}; }
+ {{18}}a .special:after{{/18}} {
+ {{19}}content{{/19}}: {{20}}"I ate 15 pies 2px thick!"{{/20}}; }
+ {{21}}a:active{{/21}} {
+ {{22}}color{{/22}}: {{23}}#050709{{/23}};
+ {{24}}border{{/24}}: {{25}}2px solid black{{/25}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_selectors_properties_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+$width: 2px
+$translucent-red: rgba(255, 0, 0, 0.5)
+{{1}}a{{/1}}
+ {{8}}.special{{/8}}
+ {{9}}color{{/9}}: {{10}}red{{/10}}
+ {{11}}&:hover{{/11}}
+ {{12}}foo{{/12}}: {{13}}bar{{/13}}
+ {{14}}cursor{{/14}}: {{15}}e + -resize{{/15}}
+ {{16}}color{{/16}}: {{17}}opacify($translucent-red, 0.3){{/17}}
+ {{18}}&:after{{/18}}
+ {{19}}content{{/19}}: {{20}}"I ate #{5 + 10} pies #{$width} thick!"{{/20}}
+ {{21}}&:active{{/21}}
+ {{22}}color{{/22}}: {{23}}#010203 + #040506{{/23}}
+ {{24}}border{{/24}}: {{25}}$width solid black{{/25}}
+
+ /* SOME COMMENT */
+ {{2}}font{{/2}}: {{3}}2px/3px{{/3}}
+ {{4}}family{{/4}}: {{5}}fantasy{{/5}}
+ {{6}}size{{/6}}: {{7}}1em + (2em * 3){{/7}}
+SASS
+{{1}}a{{/1}} {
+ /* SOME COMMENT */
+ {{2}}font{{/2}}: {{3}}2px/3px{{/3}};
+ {{4}}font-family{{/4}}: {{5}}fantasy{{/5}};
+ {{6}}font-size{{/6}}: {{7}}7em{{/7}}; }
+ {{8}}a .special{{/8}} {
+ {{9}}color{{/9}}: {{10}}red{{/10}}; }
+ {{11}}a .special:hover{{/11}} {
+ {{12}}foo{{/12}}: {{13}}bar{{/13}};
+ {{14}}cursor{{/14}}: {{15}}e-resize{{/15}};
+ {{16}}color{{/16}}: {{17}}rgba(255, 0, 0, 0.8){{/17}}; }
+ {{18}}a .special:after{{/18}} {
+ {{19}}content{{/19}}: {{20}}"I ate 15 pies 2px thick!"{{/20}}; }
+ {{21}}a:active{{/21}} {
+ {{22}}color{{/22}}: {{23}}#050709{{/23}};
+ {{24}}border{{/24}}: {{25}}2px solid black{{/25}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_extend_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+{{1}}.error {{/1}}{
+ {{2}}border{{/2}}: {{3}}1px #ff00aa{{/3}};
+ {{4}}background-color{{/4}}: {{5}}#fdd{{/5}};
+}
+{{6}}.seriousError {{/6}}{
+ @extend .error;
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}};
+}
+SCSS
+{{1}}.error, .seriousError{{/1}} {
+ {{2}}border{{/2}}: {{3}}1px #ff00aa{{/3}};
+ {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}; }
+
+{{6}}.seriousError{{/6}} {
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_extend_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+{{1}}.error{{/1}}
+ {{2}}border{{/2}}: {{3}}1px #f00{{/3}}
+ {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}
+
+{{6}}.seriousError{{/6}}
+ @extend .error
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}}
+SASS
+{{1}}.error, .seriousError{{/1}} {
+ {{2}}border{{/2}}: {{3}}1px #f00{{/3}};
+ {{4}}background-color{{/4}}: {{5}}#fdd{{/5}}; }
+
+{{6}}.seriousError{{/6}} {
+ {{7}}border-width{{/7}}: {{8}}3px{{/8}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_for_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+ for $i from 1 through 3 {
+ {{1}}{{4}}{{7}}.item-#{$i} {{/1}}{{/4}}{{/7}}{ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em
* $i{{/3}}{{/6}}{{/9}}; }
+}
+SCSS
+{{1}}.item-1{{/1}} {
+ {{2}}width{{/2}}: {{3}}2em{{/3}}; }
+
+{{4}}.item-2{{/4}} {
+ {{5}}width{{/5}}: {{6}}4em{{/6}}; }
+
+{{7}}.item-3{{/7}} {
+ {{8}}width{{/8}}: {{9}}6em{{/9}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_for_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+ for $i from 1 through 3
+ {{1}}{{4}}{{7}}.item-#{$i}{{/1}}{{/4}}{{/7}}
+ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}
+SASS
+{{1}}.item-1{{/1}} {
+ {{2}}width{{/2}}: {{3}}2em{{/3}}; }
+
+{{4}}.item-2{{/4}} {
+ {{5}}width{{/5}}: {{6}}4em{{/6}}; }
+
+{{7}}.item-3{{/7}} {
+ {{8}}width{{/8}}: {{9}}6em{{/9}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_while_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+$i: 6;
+ while $i > 0 {
+ {{1}}{{4}}{{7}}.item-#{$i} {{/1}}{{/4}}{{/7}}{ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em
* $i{{/3}}{{/6}}{{/9}}; }
+ $i: $i - 2 !global;
+}
+SCSS
+{{1}}.item-6{{/1}} {
+ {{2}}width{{/2}}: {{3}}12em{{/3}}; }
+
+{{4}}.item-4{{/4}} {
+ {{5}}width{{/5}}: {{6}}8em{{/6}}; }
+
+{{7}}.item-2{{/7}} {
+ {{8}}width{{/8}}: {{9}}4em{{/9}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_while_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+$i: 6
+ while $i > 0
+ {{1}}{{4}}{{7}}.item-#{$i}{{/1}}{{/4}}{{/7}}
+ {{2}}{{5}}{{8}}width{{/2}}{{/5}}{{/8}}: {{3}}{{6}}{{9}}2em * $i{{/3}}{{/6}}{{/9}}
+ $i: $i - 2 !global
+SASS
+{{1}}.item-6{{/1}} {
+ {{2}}width{{/2}}: {{3}}12em{{/3}}; }
+
+{{4}}.item-4{{/4}} {
+ {{5}}width{{/5}}: {{6}}8em{{/6}}; }
+
+{{7}}.item-2{{/7}} {
+ {{8}}width{{/8}}: {{9}}4em{{/9}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_each_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+ each $animal in puma, sea-slug, egret, salamander {
+ {{1}}{{4}}{{7}}{{10}}.#{$animal}-icon {{/1}}{{/4}}{{/7}}{{/10}}{
+ {{2}}{{5}}{{8}}{{11}}background-image{{/2}}{{/5}}{{/8}}{{/11}}:
{{3}}{{6}}{{9}}{{12}}url('/images/#{$animal}.png'){{/3}}{{/6}}{{/9}}{{/12}};
+ }
+}
+SCSS
+{{1}}.puma-icon{{/1}} {
+ {{2}}background-image{{/2}}: {{3}}url("/images/puma.png"){{/3}}; }
+
+{{4}}.sea-slug-icon{{/4}} {
+ {{5}}background-image{{/5}}: {{6}}url("/images/sea-slug.png"){{/6}}; }
+
+{{7}}.egret-icon{{/7}} {
+ {{8}}background-image{{/8}}: {{9}}url("/images/egret.png"){{/9}}; }
+
+{{10}}.salamander-icon{{/10}} {
+ {{11}}background-image{{/11}}: {{12}}url("/images/salamander.png"){{/12}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_each_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+ each $animal in puma, sea-slug, egret, salamander
+ {{1}}{{4}}{{7}}{{10}}.#{$animal}-icon{{/1}}{{/4}}{{/7}}{{/10}}
+ {{2}}{{5}}{{8}}{{11}}background-image{{/2}}{{/5}}{{/8}}{{/11}}:
{{3}}{{6}}{{9}}{{12}}url('/images/#{$animal}.png'){{/3}}{{/6}}{{/9}}{{/12}}
+SASS
+{{1}}.puma-icon{{/1}} {
+ {{2}}background-image{{/2}}: {{3}}url("/images/puma.png"){{/3}}; }
+
+{{4}}.sea-slug-icon{{/4}} {
+ {{5}}background-image{{/5}}: {{6}}url("/images/sea-slug.png"){{/6}}; }
+
+{{7}}.egret-icon{{/7}} {
+ {{8}}background-image{{/8}}: {{9}}url("/images/egret.png"){{/9}}; }
+
+{{10}}.salamander-icon{{/10}} {
+ {{11}}background-image{{/11}}: {{12}}url("/images/salamander.png"){{/12}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_mixin_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+ mixin large-text {
+ font: {
+ {{2}}size{{/2}}: {{3}}20px{{/3}};
+ {{4}}weight{{/4}}: {{5}}bold{{/5}};
+ }
+ {{6}}color{{/6}}: {{7}}#ff0000{{/7}};
+}
+
+{{1}}.page-title {{/1}}{
+ @include large-text;
+ {{8}}padding{{/8}}: {{9}}4px{{/9}};
+}
+
+ mixin dashed-border($color, $width: {{14}}1in{{/14}}) {
+ border: {
+ {{11}}{{18}}color{{/11}}{{/18}}: $color;
+ {{13}}{{20}}width{{/13}}{{/20}}: $width;
+ {{15}}{{22}}style{{/15}}{{/22}}: {{16}}{{23}}dashed{{/16}}{{/23}};
+ }
+}
+
+{{10}}p {{/10}}{ @include dashed-border({{12}}blue{{/12}}); }
+{{17}}h1 {{/17}}{ @include dashed-border({{19}}blue{{/19}}, {{21}}2in{{/21}}); }
+
+ mixin box-shadow($shadows...) {
+ {{25}}-moz-box-shadow{{/25}}: {{26}}$shadows{{/26}};
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}$shadows{{/28}};
+ {{29}}box-shadow{{/29}}: {{30}}$shadows{{/30}};
+}
+
+{{24}}.shadows {{/24}}{
+ @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
+}
+SCSS
+{{1}}.page-title{{/1}} {
+ {{2}}font-size{{/2}}: {{3}}20px{{/3}};
+ {{4}}font-weight{{/4}}: {{5}}bold{{/5}};
+ {{6}}color{{/6}}: {{7}}#ff0000{{/7}};
+ {{8}}padding{{/8}}: {{9}}4px{{/9}}; }
+
+{{10}}p{{/10}} {
+ {{11}}border-color{{/11}}: {{12}}blue{{/12}};
+ {{13}}border-width{{/13}}: {{14}}1in{{/14}};
+ {{15}}border-style{{/15}}: {{16}}dashed{{/16}}; }
+
+{{17}}h1{{/17}} {
+ {{18}}border-color{{/18}}: {{19}}blue{{/19}};
+ {{20}}border-width{{/20}}: {{21}}2in{{/21}};
+ {{22}}border-style{{/22}}: {{23}}dashed{{/23}}; }
+
+{{24}}.shadows{{/24}} {
+ {{25}}-moz-box-shadow{{/25}}: {{26}}0px 4px 5px #666, 2px 6px 10px #999{{/26}};
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}0px 4px 5px #666, 2px 6px 10px #999{{/28}};
+ {{29}}box-shadow{{/29}}: {{30}}0px 4px 5px #666, 2px 6px 10px #999{{/30}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+def test_mixin_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+=large-text
+ :font
+ {{2}}size{{/2}}: {{3}}20px{{/3}}
+ {{4}}weight{{/4}}: {{5}}bold{{/5}}
+ {{6}}color{{/6}}: {{7}}#ff0000{{/7}}
+
+{{1}}.page-title{{/1}}
+ +large-text
+ {{8}}padding{{/8}}: {{9}}4px{{/9}}
+
+=dashed-border($color, $width: {{14}}1in{{/14}})
+ border:
+ {{11}}{{18}}color{{/11}}{{/18}}: $color
+ {{13}}{{20}}width{{/13}}{{/20}}: $width
+ {{15}}{{22}}style{{/15}}{{/22}}: {{16}}{{23}}dashed{{/16}}{{/23}}
+
+{{10}}p{{/10}}
+ +dashed-border({{12}}blue{{/12}})
+
+{{17}}h1{{/17}}
+ +dashed-border({{19}}blue{{/19}}, {{21}}2in{{/21}})
+
+=box-shadow($shadows...)
+ {{25}}-moz-box-shadow{{/25}}: {{26}}$shadows{{/26}}
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}$shadows{{/28}}
+ {{29}}box-shadow{{/29}}: {{30}}$shadows{{/30}}
+
+{{24}}.shadows{{/24}}
+ +box-shadow(0px 4px 5px #666, 2px 6px 10px #999)
+SASS
+{{1}}.page-title{{/1}} {
+ {{2}}font-size{{/2}}: {{3}}20px{{/3}};
+ {{4}}font-weight{{/4}}: {{5}}bold{{/5}};
+ {{6}}color{{/6}}: {{7}}#ff0000{{/7}};
+ {{8}}padding{{/8}}: {{9}}4px{{/9}}; }
+
+{{10}}p{{/10}} {
+ {{11}}border-color{{/11}}: {{12}}blue{{/12}};
+ {{13}}border-width{{/13}}: {{14}}1in{{/14}};
+ {{15}}border-style{{/15}}: {{16}}dashed{{/16}}; }
+
+{{17}}h1{{/17}} {
+ {{18}}border-color{{/18}}: {{19}}blue{{/19}};
+ {{20}}border-width{{/20}}: {{21}}2in{{/21}};
+ {{22}}border-style{{/22}}: {{23}}dashed{{/23}}; }
+
+{{24}}.shadows{{/24}} {
+ {{25}}-moz-box-shadow{{/25}}: {{26}}0px 4px 5px #666, 2px 6px 10px #999{{/26}};
+ {{27}}-webkit-box-shadow{{/27}}: {{28}}0px 4px 5px #666, 2px 6px 10px #999{{/28}};
+ {{29}}box-shadow{{/29}}: {{30}}0px 4px 5px #666, 2px 6px 10px #999{{/30}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+end
+
+ def test_function_sourcemap_scss
+ assert_parses_with_mapping <<'SCSS', <<'CSS'
+$grid-width: 20px;
+$gutter-width: 5px;
+
+ function grid-width($n) {
+ @return $n * $grid-width + ($n - 1) * $gutter-width;
+}
+{{1}}sidebar {{/1}}{ {{2}}width{{/2}}: {{3}}grid-width(5){{/3}}; }
+SCSS
+{{1}}sidebar{{/1}} {
+ {{2}}width{{/2}}: {{3}}120px{{/3}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_function_sourcemap_sass
+ assert_parses_with_mapping <<'SASS', <<'CSS', :syntax => :sass
+$grid-width: 20px
+$gutter-width: 5px
+
+ function grid-width($n)
+ @return $n * $grid-width + ($n - 1) * $gutter-width
+
+{{1}}sidebar{{/1}}
+ {{2}}width{{/2}}: {{3}}grid-width(5){{/3}}
+SASS
+{{1}}sidebar{{/1}} {
+ {{2}}width{{/2}}: {{3}}120px{{/3}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ # Regression tests
+
+ def test_properties_sass
+ assert_parses_with_mapping <<SASS, <<CSS, :syntax => :sass
+{{1}}.foo{{/1}}
+ :{{2}}name{{/2}} {{3}}value{{/3}}
+ {{4}}name{{/4}}: {{5}}value{{/5}}
+ :{{6}}name{{/6}} {{7}}value{{/7}}
+ {{8}}name{{/8}}: {{9}}value{{/9}}
+SASS
+{{1}}.foo{{/1}} {
+ {{2}}name{{/2}}: {{3}}value{{/3}};
+ {{4}}name{{/4}}: {{5}}value{{/5}};
+ {{6}}name{{/6}}: {{7}}value{{/7}};
+ {{8}}name{{/8}}: {{9}}value{{/9}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_multiline_script_scss
+ assert_parses_with_mapping <<SCSS, <<CSS, :syntax => :scss
+$var: {{3}}foo +
+ bar{{/3}}; {{1}}x {{/1}}{ {{2}}y{{/2}}: $var }
+SCSS
+{{1}}x{{/1}} {
+ {{2}}y{{/2}}: {{3}}foobar{{/3}}; }
+
+/*# sourceMappingURL=test.css.map */
+CSS
+ end
+
+ def test_multiline_interpolation_source_range
+ engine = Sass::Engine.new(<<-SCSS, :cache => false, :syntax => :scss)
+p {
+ filter: progid:DXImageTransform(
+ '\#{123}');
+}
+SCSS
+
+ interpolated = engine.to_tree.children.
+ first.children.
+ first.value.children[1]
+ assert_equal "123", interpolated.to_sass
+ range = interpolated.source_range
+ assert_equal 3, range.start_pos.line
+ assert_equal 14, range.start_pos.offset
+ assert_equal 3, range.end_pos.line
+ assert_equal 17, range.end_pos.offset
+ end
+
+ def test_sources_array_is_uri_escaped
+ map = Sass::Source::Map.new
+ importer = Sass::Importers::Filesystem.new('.')
+ map.add(
+ Sass::Source::Range.new(
+ Sass::Source::Position.new(0, 0),
+ Sass::Source::Position.new(0, 10),
+ 'source file.scss',
+ importer),
+ Sass::Source::Range.new(
+ Sass::Source::Position.new(0, 0),
+ Sass::Source::Position.new(0, 10),
+ nil, nil))
+
+ json = map.to_json(:css_path => 'output file.css', :sourcemap_path => 'output file.css.map')
+ assert_equal json, <<JSON.rstrip
+{
+"version": 3,
+"mappings": "DADD,UAAU",
+"sources": ["source%20file.scss"],
+"names": [],
+"file": "output%20file.css"
+}
+JSON
+ end
+
+ private
+
+ ANNOTATION_REGEX = /\{\{(\/?)([^}]+)\}\}/
+
+ def build_ranges(text, file_name = nil)
+ ranges = Hash.new {|h, k| h[k] = []}
+ start_positions = {}
+ text.split("\n").each_with_index do |line_text, line|
+ line += 1 # lines shoud be 1-based
+ while (match = line_text.match(ANNOTATION_REGEX))
+ closing = !match[1].empty?
+ name = match[2]
+ match_offsets = match.offset(0)
+ offset = match_offsets[0] + 1 # Offsets are 1-based in source maps.
+ assert(!closing || start_positions[name], "Closing annotation #{name} found before opening one.")
+ position = Sass::Source::Position.new(line, offset)
+ if closing
+ ranges[name] << Sass::Source::Range.new(
+ start_positions[name], position, file_name,
+ Sass::Importers::Filesystem.new('.'))
+ start_positions.delete name
+ else
+ assert(!start_positions[name], "Overlapping range annotation #{name} encountered on line #{line}")
+ start_positions[name] = position
+ end
+ line_text.slice!(match_offsets[0], match_offsets[1] - match_offsets[0])
+ end
+ end
+ ranges
+ end
+
+ def build_mapping_from_annotations(source, css, source_file_name)
+ source_ranges = build_ranges(source, source_file_name)
+ target_ranges = build_ranges(css)
+ map = Sass::Source::Map.new
+ Sass::Util.flatten(source_ranges.map do |(name, sources)|
+ assert(sources.length == 1, "#{sources.length} source ranges encountered for annotation #{name}")
+ assert(target_ranges[name], "No target ranges for annotation #{name}")
+ target_ranges[name].map {|target_range| [sources.first, target_range]}
+ end, 1).
+ sort_by {|(_, target)| [target.start_pos.line, target.start_pos.offset]}.
+ each {|(s2, target)| map.add(s2, target)}
+ map
+ end
+
+ def assert_parses_with_mapping(source, css, options={})
+ options[:syntax] ||= :scss
+ input_filename = filename_for_test(options[:syntax])
+ mapping = build_mapping_from_annotations(source, css, input_filename)
+ source.gsub!(ANNOTATION_REGEX, "")
+ css.gsub!(ANNOTATION_REGEX, "")
+ rendered, sourcemap = render_with_sourcemap(source, options)
+ assert_equal css.rstrip, rendered.rstrip
+ assert_sourcemaps_equal source, css, mapping, sourcemap
+ end
+
+ def assert_positions_equal(expected, actual, lines, message = nil)
+ prefix = message ? message + ": " : ""
+ expected_location = lines[expected.line - 1] + "\n" + ("-" * (expected.offset - 1)) + "^"
+ actual_location = lines[actual.line - 1] + "\n" + ("-" * (actual.offset - 1)) + "^"
+ assert_equal(expected.line, actual.line, prefix +
+ "Expected #{expected.inspect}\n" +
+ expected_location + "\n\n" +
+ "But was #{actual.inspect}\n" +
+ actual_location)
+ assert_equal(expected.offset, actual.offset, prefix +
+ "Expected #{expected.inspect}\n" +
+ expected_location + "\n\n" +
+ "But was #{actual.inspect}\n" +
+ actual_location)
+ end
+
+ def assert_ranges_equal(expected, actual, lines, prefix)
+ assert_positions_equal(expected.start_pos, actual.start_pos, lines, prefix + " start position")
+ assert_positions_equal(expected.end_pos, actual.end_pos, lines, prefix + " end position")
+ assert_equal(expected.file, actual.file)
+ end
+
+ def assert_sourcemaps_equal(source, css, expected, actual)
+ assert_equal(expected.data.length, actual.data.length, <<MESSAGE)
+Wrong number of mappings. Expected:
+#{dump_sourcemap_as_expectation(source, css, expected).gsub(/^/, '| ')}
+
+Actual:
+#{dump_sourcemap_as_expectation(source, css, actual).gsub(/^/, '| ')}
+MESSAGE
+ source_lines = source.split("\n")
+ css_lines = css.split("\n")
+ expected.data.zip(actual.data) do |expected_mapping, actual_mapping|
+ assert_ranges_equal(expected_mapping.input, actual_mapping.input, source_lines, "Input")
+ assert_ranges_equal(expected_mapping.output, actual_mapping.output, css_lines, "Output")
+ end
+ end
+
+ def assert_parses_with_sourcemap(source, css, sourcemap_json, options={})
+ rendered, sourcemap = render_with_sourcemap(source, options)
+ css_path = options[:output] || "test.css"
+ sourcemap_path = Sass::Util.sourcemap_name(css_path)
+ rendered_json = sourcemap.to_json(:css_path => css_path, :sourcemap_path => sourcemap_path, :type =>
options[:sourcemap])
+
+ assert_equal css.rstrip, rendered.rstrip
+ assert_equal sourcemap_json.rstrip, rendered_json
+ end
+
+ def render_with_sourcemap(source, options={})
+ options[:syntax] ||= :scss
+ munge_filename options
+ engine = Sass::Engine.new(source, options)
+ engine.options[:cache] = false
+ sourcemap_path = Sass::Util.sourcemap_name(options[:output] || "test.css")
+ engine.render_with_sourcemap File.basename(sourcemap_path)
+ end
+
+ def dump_sourcemap_as_expectation(source, css, sourcemap)
+ mappings_to_annotations(source, sourcemap.data.map {|d| d.input}) + "\n\n" +
+ "=" * 20 + " maps to:\n\n" +
+ mappings_to_annotations(css, sourcemap.data.map {|d| d.output})
+ end
+
+ def mappings_to_annotations(source, ranges)
+ additional_offsets = Hash.new(0)
+ lines = source.split("\n")
+
+ add_annotation = lambda do |pos, str|
+ line_num = pos.line - 1
+ line = lines[line_num]
+ offset = pos.offset + additional_offsets[line_num] - 1
+ line << " " * (offset - line.length) if offset > line.length
+ line.insert(offset, str)
+ additional_offsets[line_num] += str.length
+ end
+
+ ranges.each_with_index do |range, i|
+ add_annotation[range.start_pos, "{{#{i + 1}}}"]
+ add_annotation[range.end_pos, "{{/#{i + 1}}}"]
+ end
+
+ return lines.join("\n")
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/superselector_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/superselector_test.rb
new file mode 100755
index 0000000..3ac22de
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/superselector_test.rb
@@ -0,0 +1,210 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+
+class SuperselectorTest < MiniTest::Test
+ def test_superselector_reflexivity
+ assert_superselector 'h1', 'h1'
+ assert_superselector '.foo', '.foo'
+ assert_superselector '#foo > .bar, baz', '#foo > .bar, baz'
+ end
+
+ def test_smaller_compound_superselector
+ assert_strict_superselector '.foo', '.foo.bar'
+ assert_strict_superselector '.bar', '.foo.bar'
+ assert_strict_superselector 'a', 'a#b'
+ assert_strict_superselector '#b', 'a#b'
+ end
+
+ def test_smaller_complex_superselector
+ assert_strict_superselector '.bar', '.foo .bar'
+ assert_strict_superselector '.bar', '.foo > .bar'
+ assert_strict_superselector '.bar', '.foo + .bar'
+ assert_strict_superselector '.bar', '.foo ~ .bar'
+ end
+
+ def test_selector_list_subset_superselector
+ assert_strict_superselector '.foo, .bar', '.foo'
+ assert_strict_superselector '.foo, .bar, .baz', '.foo, .baz'
+ assert_strict_superselector '.foo, .baz, .qux', '.foo.bar, .baz.bang'
+ end
+
+ def test_leading_combinator_superselector
+ refute_superselector '+ .foo', '.foo'
+ refute_superselector '+ .foo', '.bar + .foo'
+ end
+
+ def test_trailing_combinator_superselector
+ refute_superselector '.foo +', '.foo'
+ refute_superselector '.foo +', '.foo + .bar'
+ end
+
+ def test_matching_combinator_superselector
+ assert_strict_superselector '.foo + .bar', '.foo + .bar.baz'
+ assert_strict_superselector '.foo + .bar', '.foo.baz + .bar'
+ assert_strict_superselector '.foo > .bar', '.foo > .bar.baz'
+ assert_strict_superselector '.foo > .bar', '.foo.baz > .bar'
+ assert_strict_superselector '.foo ~ .bar', '.foo ~ .bar.baz'
+ assert_strict_superselector '.foo ~ .bar', '.foo.baz ~ .bar'
+ end
+
+ def test_following_sibling_is_superselector_of_next_sibling
+ assert_strict_superselector '.foo ~ .bar', '.foo + .bar.baz'
+ assert_strict_superselector '.foo ~ .bar', '.foo.baz + .bar'
+ end
+
+ def test_descendant_is_superselector_of_child
+ assert_strict_superselector '.foo .bar', '.foo > .bar.baz'
+ assert_strict_superselector '.foo .bar', '.foo.baz > .bar'
+ assert_strict_superselector '.foo .baz', '.foo > .bar > .baz'
+ end
+
+ def test_child_isnt_superselector_of_longer_child
+ refute_superselector '.foo > .baz', '.foo > .bar > .baz'
+ refute_superselector '.foo > .baz', '.foo > .bar .baz'
+ end
+
+ def test_following_sibling_isnt_superselector_of_longer_following_sibling
+ refute_superselector '.foo + .baz', '.foo + .bar + .baz'
+ refute_superselector '.foo + .baz', '.foo + .bar .baz'
+ end
+
+ def test_sibling_isnt_superselector_of_longer_sibling
+ # This actually is a superselector, but it's a very narrow edge case and
+ # detecting it is very difficult and may be exponential in the worst case.
+ refute_superselector '.foo ~ .baz', '.foo ~ .bar ~ .baz'
+
+ refute_superselector '.foo ~ .baz', '.foo ~ .bar .baz'
+ end
+
+ def test_matches_is_superselector_of_constituent_selectors
+ %w[matches -moz-any].each do |name|
+ assert_strict_superselector ":#{name}(.foo, .bar)", '.foo.baz'
+ assert_strict_superselector ":#{name}(.foo, .bar)", '.bar.baz'
+ assert_strict_superselector ":#{name}(.foo .bar, .baz)", '.x .foo .bar'
+ end
+ end
+
+ def test_matches_is_superselector_of_subset_matches
+ assert_strict_superselector ':matches(.foo, .bar, .baz)', '#x:matches(.foo.bip, .baz.bang)'
+ assert_strict_superselector ':-moz-any(.foo, .bar, .baz)', '#x:-moz-any(.foo.bip, .baz.bang)'
+ end
+
+ def test_matches_is_not_superselector_of_any
+ refute_superselector ':matches(.foo, .bar)', ':-moz-any(.foo, .bar)'
+ refute_superselector ':-moz-any(.foo, .bar)', ':matches(.foo, .bar)'
+ end
+
+ def test_matches_can_be_subselector
+ %w[matches -moz-any].each do |name|
+ assert_superselector '.foo', ":#{name}(.foo.bar)"
+ assert_superselector '.foo.bar', ":#{name}(.foo.bar.baz)"
+ assert_superselector '.foo', ":#{name}(.foo.bar, .foo.baz)"
+ end
+ end
+
+ def test_any_is_not_superselector_of_different_prefix
+ refute_superselector ':-moz-any(.foo, .bar)', ':-s-any(.foo, .bar)'
+ end
+
+ def test_not_is_superselector_of_less_complex_not
+ assert_strict_superselector ':not(.foo.bar)', ':not(.foo)'
+ assert_strict_superselector ':not(.foo .bar)', ':not(.bar)'
+ end
+
+ def test_not_is_superselector_of_superset
+ assert_strict_superselector ':not(.foo.bip, .baz.bang)', ':not(.foo, .bar, .baz)'
+ assert_strict_superselector ':not(.foo.bip, .baz.bang)', ':not(.foo):not(.bar):not(.baz)'
+ end
+
+ def test_not_is_superselector_of_unique_selectors
+ assert_strict_superselector ':not(h1.foo)', 'a'
+ assert_strict_superselector ':not(.baz #foo)', '#bar'
+ end
+
+ def test_not_is_not_superselector_of_non_unique_selectors
+ refute_superselector ':not(.foo)', '.bar'
+ refute_superselector ':not(:hover)', ':visited'
+ end
+
+ def test_current_is_superselector_with_identical_innards
+ assert_superselector ':current(.foo)', ':current(.foo)'
+ end
+
+ def test_current_is_superselector_with_subselector_innards
+ refute_superselector ':current(.foo)', ':current(.foo.bar)'
+ refute_superselector ':current(.foo.bar)', ':current(.foo)'
+ end
+
+ def test_nth_match_is_superselector_of_subset_nth_match
+ assert_strict_superselector(
+ ':nth-child(2n of .foo, .bar, .baz)', '#x:nth-child(2n of .foo.bip, .baz.bang)')
+ assert_strict_superselector(
+ ':nth-last-child(2n of .foo, .bar, .baz)', '#x:nth-last-child(2n of .foo.bip, .baz.bang)')
+ end
+
+ def test_nth_match_is_not_superselector_of_nth_match_with_different_arg
+ refute_superselector(
+ ':nth-child(2n of .foo, .bar, .baz)', '#x:nth-child(2n + 1 of .foo.bip, .baz.bang)')
+ refute_superselector(
+ ':nth-last-child(2n of .foo, .bar, .baz)', '#x:nth-last-child(2n + 1 of .foo.bip, .baz.bang)')
+ end
+
+ def test_nth_match_is_not_superselector_of_nth_last_match
+ refute_superselector ':nth-child(2n of .foo, .bar)', ':nth-last-child(2n of .foo, .bar)'
+ refute_superselector ':nth-last-child(2n of .foo, .bar)', ':nth-child(2n of .foo, .bar)'
+ end
+
+ def test_nth_match_can_be_subselector
+ %w[nth-child nth-last-child].each do |name|
+ assert_superselector '.foo', ":#{name}(2n of .foo.bar)"
+ assert_superselector '.foo.bar', ":#{name}(2n of .foo.bar.baz)"
+ assert_superselector '.foo', ":#{name}(2n of .foo.bar, .foo.baz)"
+ end
+ end
+
+ def has_is_superselector_of_subset_host
+ assert_strict_superselector ':has(.foo, .bar, .baz)', ':has(.foo.bip, .baz.bang)'
+ end
+
+ def has_isnt_superselector_of_contained_selector
+ assert_strict_superselector ':has(.foo, .bar, .baz)', '.foo'
+ end
+
+ def host_is_superselector_of_subset_host
+ assert_strict_superselector ':host(.foo, .bar, .baz)', ':host(.foo.bip, .baz.bang)'
+ end
+
+ def host_isnt_superselector_of_contained_selector
+ assert_strict_superselector ':host(.foo, .bar, .baz)', '.foo'
+ end
+
+ def host_context_is_superselector_of_subset_host
+ assert_strict_superselector(
+ ':host-context(.foo, .bar, .baz)', ':host-context(.foo.bip, .baz.bang)')
+ end
+
+ def host_context_isnt_superselector_of_contained_selector
+ assert_strict_superselector ':host-context(.foo, .bar, .baz)', '.foo'
+ end
+
+ private
+
+ def assert_superselector(superselector, subselector)
+ assert(parse_selector(superselector).superselector?(parse_selector(subselector)),
+ "Expected #{superselector} to be a superselector of #{subselector}.")
+ end
+
+ def refute_superselector(superselector, subselector)
+ assert(!parse_selector(superselector).superselector?(parse_selector(subselector)),
+ "Expected #{superselector} not to be a superselector of #{subselector}.")
+ end
+
+ def assert_strict_superselector(superselector, subselector)
+ assert_superselector(superselector, subselector)
+ refute_superselector(subselector, superselector)
+ end
+
+ def parse_selector(selector)
+ Sass::SCSS::CssParser.new(selector, filename_for_test, nil).parse_selector
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_cached_import_option_partial.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/_cached_import_option_partial.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_cached_import_option_partial.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_cached_import_option_partial.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_double_import_loop2.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/_double_import_loop2.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_double_import_loop2.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_double_import_loop2.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_filename_fn_import.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/_filename_fn_import.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_filename_fn_import.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_filename_fn_import.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_ibm866.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/_imported_charset_ibm866.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_ibm866.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_imported_charset_ibm866.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_utf8.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/_imported_charset_utf8.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_utf8.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_imported_charset_utf8.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_content.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/_imported_content.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_imported_content.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_imported_content.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_partial.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/_partial.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_partial.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_partial.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_same_name_different_partiality.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/_same_name_different_partiality.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/_same_name_different_partiality.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/_same_name_different_partiality.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/alt.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/alt.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/alt.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/alt.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/basic.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/basic.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/basic.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/basic.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork1.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/bork1.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/bork1.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/bork1.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork2.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/bork2.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/bork2.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/bork2.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork3.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/bork3.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/bork3.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/bork3.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork4.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/bork4.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/bork4.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/bork4.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork5.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/bork5.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/bork5.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/bork5.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/cached_import_option.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/cached_import_option.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/cached_import_option.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/cached_import_option.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/compact.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/compact.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/compact.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/compact.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/complex.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/complex.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/complex.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/complex.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/compressed.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/compressed.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/compressed.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/compressed.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/double_import_loop1.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/double_import_loop1.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/double_import_loop1.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/double_import_loop1.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/expanded.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/expanded.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/expanded.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/expanded.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/filename_fn.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/filename_fn.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/filename_fn.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/filename_fn.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/if.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/if.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/if.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/if.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/import.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/import.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/import.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/import_charset.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/import_charset.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/import_charset.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_1_8.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/import_charset_1_8.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_1_8.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/import_charset_1_8.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_ibm866.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/import_charset_ibm866.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_ibm866.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/import_charset_ibm866.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_content.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/import_content.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/import_content.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/import_content.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/importee.less
b/backends/css/gems/sass-3.4.9/test/sass/templates/importee.less
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/importee.less
rename to backends/css/gems/sass-3.4.9/test/sass/templates/importee.less
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/importee.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/importee.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/importee.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/importee.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/line_numbers.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/line_numbers.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/line_numbers.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/line_numbers.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/mixin_bork.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/mixin_bork.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/mixin_bork.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/mixin_bork.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/mixins.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/mixins.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/mixins.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/mixins.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/multiline.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/multiline.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/multiline.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/multiline.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/nested.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/nested.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/nested.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork1.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork1.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork1.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork1.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork2.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork2.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork2.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork2.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork3.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork3.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork3.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork3.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork4.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork4.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork4.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/nested_bork4.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_import.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/nested_import.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/nested_import.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/nested_import.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_mixin_bork.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/nested_mixin_bork.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/nested_mixin_bork.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/nested_mixin_bork.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/options.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/options.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/options.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/options.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/parent_ref.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/parent_ref.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/parent_ref.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/parent_ref.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_ext.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_ext.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_ext.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_ext.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_partiality.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_partiality.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_partiality.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/same_name_different_partiality.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/script.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/script.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/script.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/script.sass
diff --git a/backends/css/gems/sass-3.4.9/test/sass/templates/scss_import.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/scss_import.scss
new file mode 100644
index 0000000..b9c5c79
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/templates/scss_import.scss
@@ -0,0 +1,12 @@
+$preconst: hello;
+
+ mixin premixin {pre-mixin: here}
+
+ import "importee.sass", "scss_importee", "basic.sass", "basic.css", "../results/complex.css";
+ import "part\
+ial.sass";
+
+nonimported {
+ myconst: $preconst;
+ otherconst: $postconst;
+ @include postmixin; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/scss_importee.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/scss_importee.scss
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/scss_importee.scss
rename to backends/css/gems/sass-3.4.9/test/sass/templates/scss_importee.scss
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/single_import_loop.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/single_import_loop.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/single_import_loop.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/single_import_loop.sass
diff --git a/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up1.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up1.scss
new file mode 100644
index 0000000..90559f5
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up1.scss
@@ -0,0 +1 @@
+ import "../subdir/import_up2.scss";
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up2.scss
b/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up2.scss
new file mode 100644
index 0000000..22f5a59
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/import_up2.scss
@@ -0,0 +1 @@
+ import "../subdir/import_up3.scss";
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/_nested_partial.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/nested_subdir/_nested_partial.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/_nested_partial.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/subdir/nested_subdir/_nested_partial.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/subdir.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/subdir/subdir.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/subdir/subdir.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/subdir/subdir.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/units.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/units.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/units.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/units.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/warn.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/warn.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/warn.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/warn.sass
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/warn_imported.sass
b/backends/css/gems/sass-3.4.9/test/sass/templates/warn_imported.sass
similarity index 100%
rename from backends/css/gems/sass-3.2.12/test/sass/templates/warn_imported.sass
rename to backends/css/gems/sass-3.4.9/test/sass/templates/warn_imported.sass
diff --git a/backends/css/gems/sass-3.4.9/test/sass/test_helper.rb
b/backends/css/gems/sass-3.4.9/test/sass/test_helper.rb
new file mode 100644
index 0000000..eaa433f
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/test_helper.rb
@@ -0,0 +1,8 @@
+test_dir = File.dirname(__FILE__)
+$:.unshift test_dir unless $:.include?(test_dir)
+
+class MiniTest::Test
+ def absolutize(file)
+ File.expand_path("#{File.dirname(__FILE__)}/#{file}")
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/util/multibyte_string_scanner_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/util/multibyte_string_scanner_test.rb
new file mode 100755
index 0000000..f8dbab7
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/util/multibyte_string_scanner_test.rb
@@ -0,0 +1,147 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../../test_helper'
+
+unless Sass::Util.ruby1_8?
+ class MultibyteStringScannerTest < MiniTest::Test
+ def setup
+ @scanner = Sass::Util::MultibyteStringScanner.new("cölorfül")
+ end
+
+ def test_initial
+ assert_scanner_state 0, 0, nil, nil
+ end
+
+ def test_check
+ assert_equal 'cö', @scanner.check(/../)
+ assert_scanner_state 0, 0, 2, 3
+ assert_equal 0, @scanner.pos
+ assert_equal 0, @scanner.pos
+ assert_equal 2, @scanner.matched_size
+ assert_equal 3, @scanner.byte_matched_size
+ end
+
+ def test_check_until
+ assert_equal 'cölorfü', @scanner.check_until(/f./)
+ assert_scanner_state 0, 0, 2, 3
+ end
+
+ def test_getch
+ assert_equal 'c', @scanner.getch
+ assert_equal 'ö', @scanner.getch
+ assert_scanner_state 2, 3, 1, 2
+ end
+
+ def test_match?
+ assert_equal 2, @scanner.match?(/../)
+ assert_scanner_state 0, 0, 2, 3
+ end
+
+ def test_peek
+ assert_equal 'cö', @scanner.peek(2)
+ assert_scanner_state 0, 0, nil, nil
+ end
+
+ def test_rest_size
+ assert_equal 'cö', @scanner.scan(/../)
+ assert_equal 6, @scanner.rest_size
+ end
+
+ def test_scan
+ assert_equal 'cö', @scanner.scan(/../)
+ assert_scanner_state 2, 3, 2, 3
+ end
+
+ def test_scan_until
+ assert_equal 'cölorfü', @scanner.scan_until(/f./)
+ assert_scanner_state 7, 9, 2, 3
+ end
+
+ def test_skip
+ assert_equal 2, @scanner.skip(/../)
+ assert_scanner_state 2, 3, 2, 3
+ end
+
+ def test_skip_until
+ assert_equal 7, @scanner.skip_until(/f./)
+ assert_scanner_state 7, 9, 2, 3
+ end
+
+ def test_set_pos
+ @scanner.pos = 7
+ assert_scanner_state 7, 9, nil, nil
+ @scanner.pos = 6
+ assert_scanner_state 6, 7, nil, nil
+ @scanner.pos = 1
+ assert_scanner_state 1, 1, nil, nil
+ end
+
+ def test_reset
+ @scanner.scan(/../)
+ @scanner.reset
+ assert_scanner_state 0, 0, nil, nil
+ end
+
+ def test_scan_full
+ assert_equal 'cö', @scanner.scan_full(/../, true, true)
+ assert_scanner_state 2, 3, 2, 3
+
+ @scanner.reset
+ assert_equal 'cö', @scanner.scan_full(/../, false, true)
+ assert_scanner_state 0, 0, 2, 3
+
+ @scanner.reset
+ assert_nil @scanner.scan_full(/../, true, false)
+ assert_scanner_state 2, 3, 2, 3
+
+ @scanner.reset
+ assert_nil @scanner.scan_full(/../, false, false)
+ assert_scanner_state 0, 0, 2, 3
+ end
+
+ def test_search_full
+ assert_equal 'cölorfü', @scanner.search_full(/f./, true, true)
+ assert_scanner_state 7, 9, 2, 3
+
+ @scanner.reset
+ assert_equal 'cölorfü', @scanner.search_full(/f./, false, true)
+ assert_scanner_state 0, 0, 2, 3
+
+ @scanner.reset
+ assert_nil @scanner.search_full(/f./, true, false)
+ assert_scanner_state 7, 9, 2, 3
+
+ @scanner.reset
+ assert_nil @scanner.search_full(/f./, false, false)
+ assert_scanner_state 0, 0, 2, 3
+ end
+
+ def test_set_string
+ @scanner.scan(/../)
+ @scanner.string = 'föóbâr'
+ assert_scanner_state 0, 0, nil, nil
+ end
+
+ def test_terminate
+ @scanner.scan(/../)
+ @scanner.terminate
+ assert_scanner_state 8, 10, nil, nil
+ end
+
+ def test_unscan
+ @scanner.scan(/../)
+ @scanner.scan_until(/f./)
+ @scanner.unscan
+ assert_scanner_state 2, 3, nil, nil
+ end
+
+ private
+
+ def assert_scanner_state(pos, byte_pos, matched_size, byte_matched_size)
+ assert_equal pos, @scanner.pos, 'pos'
+ assert_equal byte_pos, @scanner.byte_pos, 'byte_pos'
+ assert_equal matched_size, @scanner.matched_size, 'matched_size'
+ assert_equal byte_matched_size, @scanner.byte_matched_size, 'byte_matched_size'
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/util/normalized_map_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/util/normalized_map_test.rb
new file mode 100755
index 0000000..99a1719
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/util/normalized_map_test.rb
@@ -0,0 +1,51 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../test_helper'
+require 'sass/util/normalized_map'
+
+class NormalizedMapTest < MiniTest::Test
+ extend PublicApiLinter
+
+ lint_api Hash, Sass::Util::NormalizedMap
+
+ def lint_instance
+ Sass::Util::NormalizedMap.new
+ end
+
+ def test_normalized_map_errors_unless_explicitly_implemented
+ assert Sass.tests_running
+ assert_raise_message(ArgumentError, "The method invert must be implemented explicitly") do
+ Sass::Util::NormalizedMap.new.invert
+ end
+ end
+
+ def test_normalized_map_does_not_error_when_released
+ Sass.tests_running = false
+ assert_equal({}, Sass::Util::NormalizedMap.new.invert)
+ ensure
+ Sass.tests_running = true
+ end
+
+ def test_basic_lifecycle
+ m = Sass::Util::NormalizedMap.new
+ m["a-b"] = 1
+ assert_equal ["a_b"], m.keys
+ assert_equal 1, m["a_b"]
+ assert_equal 1, m["a-b"]
+ assert m.has_key?("a_b")
+ assert m.has_key?("a-b")
+ assert_equal({"a-b" => 1}, m.as_stored)
+ assert_equal 1, m.delete("a-b")
+ assert !m.has_key?("a-b")
+ m["a_b"] = 2
+ assert_equal({"a_b" => 2}, m.as_stored)
+ end
+
+ def test_dup
+ m = Sass::Util::NormalizedMap.new
+ m["a-b"] = 1
+ m2 = m.dup
+ m.delete("a-b")
+ assert !m.has_key?("a-b")
+ assert m2.has_key?("a-b")
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/util/subset_map_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/util/subset_map_test.rb
new file mode 100755
index 0000000..9d42543
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/util/subset_map_test.rb
@@ -0,0 +1,91 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../test_helper'
+
+class SubsetMapTest < MiniTest::Test
+ def setup
+ @ssm = Sass::Util::SubsetMap.new
+ @ssm[Set[1, 2]] = "Foo"
+ @ssm[Set["fizz", "fazz"]] = "Bar"
+
+ @ssm[Set[:foo, :bar]] = "Baz"
+ @ssm[Set[:foo, :bar, :baz]] = "Bang"
+
+ @ssm[Set[:bip, :bop, :blip]] = "Qux"
+ @ssm[Set[:bip, :bop]] = "Thram"
+ end
+
+ def test_equal_keys
+ assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2])
+ assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz"])
+ end
+
+ def test_subset_keys
+ assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2, "fuzz"])
+ assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz", 3])
+ end
+
+ def test_superset_keys
+ assert_equal [], @ssm.get(Set[1])
+ assert_equal [], @ssm.get(Set[2])
+ assert_equal [], @ssm.get(Set["fizz"])
+ assert_equal [], @ssm.get(Set["fazz"])
+ end
+
+ def test_disjoint_keys
+ assert_equal [], @ssm.get(Set[3, 4])
+ assert_equal [], @ssm.get(Set["fuzz", "frizz"])
+ assert_equal [], @ssm.get(Set["gran", 15])
+ end
+
+ def test_semi_disjoint_keys
+ assert_equal [], @ssm.get(Set[2, 3])
+ assert_equal [], @ssm.get(Set["fizz", "fuzz"])
+ assert_equal [], @ssm.get(Set[1, "fazz"])
+ end
+
+ def test_empty_key_set
+ assert_raises(ArgumentError) { ssm[Set[]] = "Fail"}
+ end
+
+ def test_empty_key_get
+ assert_equal [], @ssm.get(Set[])
+ end
+
+ def test_multiple_subsets
+ assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, "fizz", "fazz"])
+ assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, 3, "fizz", "fazz",
"fuzz"])
+
+ assert_equal [["Baz", Set[:foo, :bar]]], @ssm.get(Set[:foo, :bar])
+ assert_equal [["Baz", Set[:foo, :bar]], ["Bang", Set[:foo, :bar, :baz]]], @ssm.get(Set[:foo, :bar, :baz])
+ end
+
+ def test_bracket_bracket
+ assert_equal ["Foo"], @ssm[Set[1, 2, "fuzz"]]
+ assert_equal ["Baz", "Bang"], @ssm[Set[:foo, :bar, :baz]]
+ end
+
+ def test_order_preserved
+ @ssm[Set[10, 11, 12]] = 1
+ @ssm[Set[10, 11]] = 2
+ @ssm[Set[11]] = 3
+ @ssm[Set[11, 12]] = 4
+ @ssm[Set[9, 10, 11, 12, 13]] = 5
+ @ssm[Set[10, 13]] = 6
+
+ assert_equal(
+ [[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]],
+ [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]],
+ @ssm.get(Set[9, 10, 11, 12, 13]))
+ end
+
+ def test_multiple_equal_values
+ @ssm[Set[11, 12]] = 1
+ @ssm[Set[12, 13]] = 2
+ @ssm[Set[13, 14]] = 1
+ @ssm[Set[14, 15]] = 1
+
+ assert_equal(
+ [[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]],
+ @ssm.get(Set[11, 12, 13, 14, 15]))
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/util_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/util_test.rb
new file mode 100755
index 0000000..bcb7fda
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/util_test.rb
@@ -0,0 +1,471 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require 'pathname'
+require 'tmpdir'
+
+class UtilTest < MiniTest::Test
+ include Sass::Util
+
+ def test_scope
+ assert(File.exist?(scope("Rakefile")))
+ end
+
+ def test_to_hash
+ assert_equal({
+ :foo => 1,
+ :bar => 2,
+ :baz => 3
+ }, to_hash([[:foo, 1], [:bar, 2], [:baz, 3]]))
+ end
+
+ def test_map_keys
+ assert_equal({
+ "foo" => 1,
+ "bar" => 2,
+ "baz" => 3
+ }, map_keys({:foo => 1, :bar => 2, :baz => 3}) {|k| k.to_s})
+ end
+
+ def test_map_vals
+ assert_equal({
+ :foo => "1",
+ :bar => "2",
+ :baz => "3"
+ }, map_vals({:foo => 1, :bar => 2, :baz => 3}) {|k| k.to_s})
+ end
+
+ def test_map_hash
+ assert_equal({
+ "foo" => "1",
+ "bar" => "2",
+ "baz" => "3"
+ }, map_hash({:foo => 1, :bar => 2, :baz => 3}) {|k, v| [k.to_s, v.to_s]})
+ end
+
+ def test_map_hash_with_normalized_map
+ map = NormalizedMap.new("foo-bar" => 1, "baz_bang" => 2)
+ result = map_hash(map) {|k, v| [k, v.to_s]}
+ assert_equal("1", result["foo-bar"])
+ assert_equal("1", result["foo_bar"])
+ assert_equal("2", result["baz-bang"])
+ assert_equal("2", result["baz_bang"])
+ end
+
+ def test_powerset
+ return unless Set[Set[]] == Set[Set[]] # There's a bug in Ruby 1.8.6 that breaks nested set equality
+ assert_equal([[].to_set].to_set,
+ powerset([]))
+ assert_equal([[].to_set, [1].to_set].to_set,
+ powerset([1]))
+ assert_equal([[].to_set, [1].to_set, [2].to_set, [1, 2].to_set].to_set,
+ powerset([1, 2]))
+ assert_equal([[].to_set, [1].to_set, [2].to_set, [3].to_set,
+ [1, 2].to_set, [2, 3].to_set, [1, 3].to_set, [1, 2, 3].to_set].to_set,
+ powerset([1, 2, 3]))
+ end
+
+ def test_restrict
+ assert_equal(0.5, restrict(0.5, 0..1))
+ assert_equal(1, restrict(2, 0..1))
+ assert_equal(1.3, restrict(2, 0..1.3))
+ assert_equal(0, restrict(-1, 0..1))
+ end
+
+ def test_merge_adjacent_strings
+ assert_equal(["foo bar baz", :bang, "biz bop", 12],
+ merge_adjacent_strings(["foo ", "bar ", "baz", :bang, "biz", " bop", 12]))
+ str = "foo"
+ assert_equal(["foo foo foo", :bang, "foo foo", 12],
+ merge_adjacent_strings([str, " ", str, " ", str, :bang, str, " ", str, 12]))
+ end
+
+ def test_replace_subseq
+ assert_equal([1, 2, :a, :b, 5],
+ replace_subseq([1, 2, 3, 4, 5], [3, 4], [:a, :b]))
+ assert_equal([1, 2, 3, 4, 5],
+ replace_subseq([1, 2, 3, 4, 5], [3, 4, 6], [:a, :b]))
+ assert_equal([1, 2, 3, 4, 5],
+ replace_subseq([1, 2, 3, 4, 5], [4, 5, 6], [:a, :b]))
+ end
+
+ def test_intersperse
+ assert_equal(["foo", " ", "bar", " ", "baz"],
+ intersperse(%w[foo bar baz], " "))
+ assert_equal([], intersperse([], " "))
+ end
+
+ def test_substitute
+ assert_equal(["foo", "bar", "baz", 3, 4],
+ substitute([1, 2, 3, 4], [1, 2], ["foo", "bar", "baz"]))
+ assert_equal([1, "foo", "bar", "baz", 4],
+ substitute([1, 2, 3, 4], [2, 3], ["foo", "bar", "baz"]))
+ assert_equal([1, 2, "foo", "bar", "baz"],
+ substitute([1, 2, 3, 4], [3, 4], ["foo", "bar", "baz"]))
+
+ assert_equal([1, "foo", "bar", "baz", 2, 3, 4],
+ substitute([1, 2, 2, 2, 3, 4], [2, 2], ["foo", "bar", "baz"]))
+ end
+
+ def test_strip_string_array
+ assert_equal(["foo ", " bar ", " baz"],
+ strip_string_array([" foo ", " bar ", " baz "]))
+ assert_equal([:foo, " bar ", " baz"],
+ strip_string_array([:foo, " bar ", " baz "]))
+ assert_equal(["foo ", " bar ", :baz],
+ strip_string_array([" foo ", " bar ", :baz]))
+ end
+
+ def test_paths
+ assert_equal([[1, 3, 5], [2, 3, 5], [1, 4, 5], [2, 4, 5]],
+ paths([[1, 2], [3, 4], [5]]))
+ assert_equal([[]], paths([]))
+ assert_equal([[1, 2, 3]], paths([[1], [2], [3]]))
+ end
+
+ def test_lcs
+ assert_equal([1, 2, 3], lcs([1, 2, 3], [1, 2, 3]))
+ assert_equal([], lcs([], [1, 2, 3]))
+ assert_equal([], lcs([1, 2, 3], []))
+ assert_equal([1, 2, 3], lcs([5, 1, 4, 2, 3, 17], [0, 0, 1, 2, 6, 3]))
+
+ assert_equal([1], lcs([1, 2, 3, 4], [4, 3, 2, 1]))
+ assert_equal([1, 2], lcs([1, 2, 3, 4], [3, 4, 1, 2]))
+ end
+
+ def test_lcs_with_block
+ assert_equal(["1", "2", "3"],
+ lcs([1, 4, 2, 5, 3], [1, 2, 3]) {|a, b| a == b && a.to_s})
+ assert_equal([-4, 2, 8],
+ lcs([-5, 3, 2, 8], [-4, 1, 8]) {|a, b| (a - b).abs <= 1 && [a, b].max})
+ end
+
+ def test_group_by_to_a
+ assert_equal([[1, [1, 3, 5, 7]], [0, [2, 4, 6, 8]]],
+ group_by_to_a(1..8) {|i| i % 2})
+ assert_equal([[1, [1, 4, 7, 10]], [2, [2, 5, 8, 11]], [0, [3, 6, 9, 12]]],
+ group_by_to_a(1..12) {|i| i % 3})
+ end
+
+ def test_subsequence
+ assert(subsequence?([1, 2, 3], [1, 2, 3]))
+ assert(subsequence?([1, 2, 3], [1, :a, 2, :b, 3]))
+ assert(subsequence?([1, 2, 3], [:a, 1, :b, :c, 2, :d, 3, :e, :f]))
+
+ assert(!subsequence?([1, 2, 3], [1, 2]))
+ assert(!subsequence?([1, 2, 3], [1, 3, 2]))
+ assert(!subsequence?([1, 2, 3], [3, 2, 1]))
+ end
+
+ def test_silence_warnings
+ old_stderr, $stderr = $stderr, StringIO.new
+ warn "Out"
+ assert_equal("Out\n", $stderr.string)
+ silence_warnings {warn "In"}
+ assert_equal("Out\n", $stderr.string)
+ ensure
+ $stderr = old_stderr
+ end
+
+ def test_sass_warn
+ assert_warning("Foo!") {sass_warn "Foo!"}
+ end
+
+ def test_silence_sass_warnings
+ old_stderr, $stderr = $stderr, StringIO.new
+ silence_sass_warnings {warn "Out"}
+ assert_equal("Out\n", $stderr.string)
+ silence_sass_warnings {sass_warn "In"}
+ assert_equal("Out\n", $stderr.string)
+ ensure
+ $stderr = old_stderr
+ end
+
+ def test_has
+ assert(has?(:instance_method, String, :chomp!))
+ assert(has?(:private_instance_method, Sass::Engine, :parse_interp))
+ end
+
+ def test_enum_with_index
+ assert_equal(%w[foo0 bar1 baz2],
+ enum_with_index(%w[foo bar baz]).map {|s, i| "#{s}#{i}"})
+ end
+
+ def test_enum_cons
+ assert_equal(%w[foobar barbaz],
+ enum_cons(%w[foo bar baz], 2).map {|s1, s2| "#{s1}#{s2}"})
+ end
+
+ def test_extract
+ arr = [1, 2, 3, 4, 5]
+ assert_equal([1, 3, 5], extract!(arr) {|e| e % 2 == 1})
+ assert_equal([2, 4], arr)
+ end
+
+ def test_ord
+ assert_equal(102, ord("f"))
+ assert_equal(98, ord("bar"))
+ end
+
+ def test_flatten
+ assert_equal([1, 2, 3], flatten([1, 2, 3], 0))
+ assert_equal([1, 2, 3], flatten([1, 2, 3], 1))
+ assert_equal([1, 2, 3], flatten([1, 2, 3], 2))
+
+ assert_equal([[1, 2], 3], flatten([[1, 2], 3], 0))
+ assert_equal([1, 2, 3], flatten([[1, 2], 3], 1))
+ assert_equal([1, 2, 3], flatten([[1, 2], 3], 2))
+
+ assert_equal([[[1], 2], [3], 4], flatten([[[1], 2], [3], 4], 0))
+ assert_equal([[1], 2, 3, 4], flatten([[[1], 2], [3], 4], 1))
+ assert_equal([1, 2, 3, 4], flatten([[[1], 2], [3], 4], 2))
+ end
+
+ def test_flatten_vertically
+ assert_equal([1, 2, 3], flatten_vertically([1, 2, 3]))
+ assert_equal([1, 3, 5, 2, 4, 6], flatten_vertically([[1, 2], [3, 4], [5, 6]]))
+ assert_equal([1, 2, 4, 3, 5, 6], flatten_vertically([1, [2, 3], [4, 5, 6]]))
+ assert_equal([1, 4, 6, 2, 5, 3], flatten_vertically([[1, 2, 3], [4, 5], 6]))
+ end
+
+ def test_set_hash
+ assert(set_hash(Set[1, 2, 3]) == set_hash(Set[3, 2, 1]))
+ assert(set_hash(Set[1, 2, 3]) == set_hash(Set[1, 2, 3]))
+
+ s1 = Set[]
+ s1 << 1
+ s1 << 2
+ s1 << 3
+ s2 = Set[]
+ s2 << 3
+ s2 << 2
+ s2 << 1
+ assert(set_hash(s1) == set_hash(s2))
+ end
+
+ def test_set_eql
+ assert(set_eql?(Set[1, 2, 3], Set[3, 2, 1]))
+ assert(set_eql?(Set[1, 2, 3], Set[1, 2, 3]))
+
+ s1 = Set[]
+ s1 << 1
+ s1 << 2
+ s1 << 3
+ s2 = Set[]
+ s2 << 3
+ s2 << 2
+ s2 << 1
+ assert(set_eql?(s1, s2))
+ end
+
+ def test_extract_and_inject_values
+ test = lambda {|arr| assert_equal(arr, with_extracted_values(arr) {|str| str})}
+
+ test[['foo bar']]
+ test[['foo {12} bar']]
+ test[['foo {{12} bar']]
+ test[['foo {{1', 12, '2} bar']]
+ test[['foo 1', 2, '{3', 4, 5, 6, '{7}', 8]]
+ test[['foo 1', [2, 3, 4], ' bar']]
+ test[['foo ', 1, "\n bar\n", [2, 3, 4], "\n baz"]]
+ end
+
+ def nested_caller_info_fn
+ caller_info
+ end
+
+ def double_nested_caller_info_fn
+ nested_caller_info_fn
+ end
+
+ def test_caller_info
+ assert_equal(["/tmp/foo.rb", 12, "fizzle"], caller_info("/tmp/foo.rb:12: in `fizzle'"))
+ assert_equal(["/tmp/foo.rb", 12, nil], caller_info("/tmp/foo.rb:12"))
+ assert_equal(["C:/tmp/foo.rb", 12, nil], caller_info("C:/tmp/foo.rb:12"))
+ assert_equal(["(sass)", 12, "blah"], caller_info("(sass):12: in `blah'"))
+ assert_equal(["", 12, "boop"], caller_info(":12: in `boop'"))
+ assert_equal(["/tmp/foo.rb", -12, "fizzle"], caller_info("/tmp/foo.rb:-12: in `fizzle'"))
+ assert_equal(["/tmp/foo.rb", 12, "fizzle"], caller_info("/tmp/foo.rb:12: in `fizzle {}'"))
+ assert_equal(["C:/tmp/foo.rb", 12, "fizzle"], caller_info("C:/tmp/foo.rb:12: in `fizzle {}'"))
+
+ info = nested_caller_info_fn
+ assert_equal(__FILE__, info[0])
+ assert_equal("test_caller_info", info[2])
+
+ info = proc {nested_caller_info_fn}.call
+ assert_equal(__FILE__, info[0])
+ assert_match(/^(block in )?test_caller_info$/, info[2])
+
+ info = double_nested_caller_info_fn
+ assert_equal(__FILE__, info[0])
+ assert_equal("double_nested_caller_info_fn", info[2])
+
+ info = proc {double_nested_caller_info_fn}.call
+ assert_equal(__FILE__, info[0])
+ assert_equal("double_nested_caller_info_fn", info[2])
+ end
+
+ def test_version_gt
+ assert_version_gt("2.0.0", "1.0.0")
+ assert_version_gt("1.1.0", "1.0.0")
+ assert_version_gt("1.0.1", "1.0.0")
+ assert_version_gt("1.0.0", "1.0.0.rc")
+ assert_version_gt("1.0.0.1", "1.0.0.rc")
+ assert_version_gt("1.0.0.rc", "0.9.9")
+ assert_version_gt("1.0.0.beta", "1.0.0.alpha")
+
+ assert_version_eq("1.0.0", "1.0.0")
+ assert_version_eq("1.0.0", "1.0.0.0")
+ end
+
+ def assert_version_gt(v1, v2)
+ #assert(version_gt(v1, v2), "Expected #{v1} > #{v2}")
+ assert(!version_gt(v2, v1), "Expected #{v2} < #{v1}")
+ end
+
+ def assert_version_eq(v1, v2)
+ assert(!version_gt(v1, v2), "Expected #{v1} = #{v2}")
+ assert(!version_gt(v2, v1), "Expected #{v2} = #{v1}")
+ end
+
+ class FooBar
+ def foo
+ Sass::Util.abstract(self)
+ end
+ def old_method
+ Sass::Util.deprecated(self)
+ end
+ def old_method_with_custom_message
+ Sass::Util.deprecated(self, "Call FooBar#new_method instead.")
+ end
+ def self.another_old_method
+ Sass::Util.deprecated(self)
+ end
+ end
+
+ def test_abstract
+ assert_raise_message(NotImplementedError,
+ "UtilTest::FooBar must implement #foo") {FooBar.new.foo}
+ end
+
+ def test_deprecated
+ assert_warning("DEPRECATION WARNING: UtilTest::FooBar#old_method will be removed in a future version of
Sass.") { FooBar.new.old_method }
+ assert_warning(<<WARNING) { FooBar.new.old_method_with_custom_message }
+DEPRECATION WARNING: UtilTest::FooBar#old_method_with_custom_message will be removed in a future version of
Sass.
+Call FooBar#new_method instead.
+WARNING
+ assert_warning("DEPRECATION WARNING: UtilTest::FooBar.another_old_method will be removed in a future
version of Sass.") { FooBar.another_old_method }
+ end
+
+ def test_json_escape_string
+ assert_json_string "", ""
+ alphanum = (("0".."9").to_a).concat(("a".."z").to_a).concat(("A".."Z").to_a).join
+ assert_json_string alphanum, alphanum
+ assert_json_string "'\"\\'", "'\\\"\\\\'"
+ assert_json_string "\b\f\n\r\t", "\\b\\f\\n\\r\\t"
+ end
+
+ def assert_json_string(source, target)
+ assert_equal target, json_escape_string(source)
+ end
+
+ def test_json_value_of
+ assert_json_value 0, "0"
+ assert_json_value(-42, "-42")
+ assert_json_value 42, "42"
+ assert_json_value true, "true"
+ assert_json_value false, "false"
+ assert_json_value "", "\"\""
+ assert_json_value "\"\"", "\"\\\"\\\"\""
+ assert_json_value "Multi\nLine\rString", "\"Multi\\nLine\\rString\""
+ assert_json_value [1, "some\nstr,ing", false, nil], "[1,\"some\\nstr,ing\",false,null]"
+ end
+
+ def assert_json_value(source, target)
+ assert_equal target, json_value_of(source)
+ end
+
+ def test_vlq
+ assert_equal "A", encode_vlq(0)
+ assert_equal "e", encode_vlq(15)
+ assert_equal "gB", encode_vlq(16)
+ assert_equal "wH", encode_vlq(120)
+ end
+
+ def assert_vlq_encodes(int, vlq)
+ vlq_from_decimal_array = decimal_array.map {|d| encode_vlq(d)}.join
+ decimal_array_from_vlq = decode_vlq(vlq)
+ assert_equal vlq, vlq_from_decimal_array
+ assert_equal decimal_array, decimal_array_from_vlq
+ end
+
+ def test_atomic_writes
+ # when using normal writes, this test fails about 90% of the time.
+ filename = File.join(Dir.tmpdir, "test_atomic")
+ 5.times do
+ writes_to_perform = %w(1 2 3 4 5 6 7 8 9).map {|i| "#{i}\n" * 100_000}
+ threads = writes_to_perform.map do |to_write|
+ Thread.new do
+ # To see this test fail with a normal write,
+ # change to the standard file open mechanism:
+ # open(filename, "w") do |f|
+ atomic_create_and_write_file(filename) do |f|
+ f.write(to_write)
+ end
+ end
+ end
+ loop do
+ contents = File.exist?(filename) ? File.read(filename) : nil
+ next if contents.nil? || contents.size == 0
+ unless writes_to_perform.include?(contents)
+ if contents.size != writes_to_perform.first.size
+ fail "Incomplete write detected: was #{contents.size} characters, " +
+ "should have been #{writes_to_perform.first.size}"
+ else
+ fail "Corrupted read/write detected"
+ end
+ end
+ break if threads.all? {|t| !t.alive?}
+ end
+ threads.each {|t| t.join}
+ end
+ ensure
+ Sass::Util.retry_on_windows {File.delete filename if File.exist?(filename)}
+ end
+
+ def test_atomic_write_permissions
+ atomic_filename = File.join(Dir.tmpdir, "test_atomic_perms.atomic")
+ normal_filename = File.join(Dir.tmpdir, "test_atomic_perms.normal")
+ atomic_create_and_write_file(atomic_filename) {|f| f.write("whatever\n") }
+ open(normal_filename, "wb") {|f| f.write("whatever\n") }
+ assert_equal File.stat(normal_filename).mode.to_s(8), File.stat(atomic_filename).mode.to_s(8)
+ ensure
+ File.unlink(atomic_filename) rescue nil
+ File.unlink(normal_filename) rescue nil
+ end
+
+ def test_atomic_writes_respect_umask
+ atomic_filename = File.join(Dir.tmpdir, "test_atomic_perms.atomic")
+ atomic_create_and_write_file(atomic_filename) do |f|
+ f.write("whatever\n")
+ end
+ assert_equal 0, File.stat(atomic_filename).mode & File.umask
+ ensure
+ File.unlink(atomic_filename)
+ end
+
+ class FakeError < RuntimeError; end
+
+ def test_atomic_writes_handles_exceptions
+ filename = File.join(Dir.tmpdir, "test_atomic_exception")
+ FileUtils.rm_f(filename)
+ tmp_filename = nil
+ assert_raises FakeError do
+ atomic_create_and_write_file(filename) do |f|
+ tmp_filename = f.path
+ raise FakeError.new "Borken"
+ end
+ end
+ assert !File.exist?(filename)
+ assert !File.exist?(tmp_filename)
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/sass/value_helpers_test.rb
b/backends/css/gems/sass-3.4.9/test/sass/value_helpers_test.rb
new file mode 100755
index 0000000..53b2290
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/sass/value_helpers_test.rb
@@ -0,0 +1,179 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+
+class ValueHelpersTest < MiniTest::Test
+ include Sass::Script
+ include Sass::Script::Value::Helpers
+
+ def test_bool
+ assert_same Value::Bool::TRUE, bool(true)
+ assert_same Value::Bool::FALSE, bool(false)
+ assert_same Value::Bool::FALSE, bool(nil)
+ assert_same Value::Bool::TRUE, bool(Object.new)
+ end
+
+ def test_hex_color_with_three_digits
+ color = hex_color("F07")
+ assert_equal 255, color.red
+ assert_equal 0, color.green
+ assert_equal 119, color.blue
+ assert_equal 1, color.alpha
+ end
+
+ def test_hex_color_without_hash
+ color_without_hash = hex_color("FF007F")
+ assert_equal 255, color_without_hash.red
+ assert_equal 0, color_without_hash.green
+ assert_equal 127, color_without_hash.blue
+ assert_equal 1, color_without_hash.alpha
+ end
+
+ def test_hex_color_with_hash
+ color_with_hash = hex_color("#FF007F")
+ assert_equal 255, color_with_hash.red
+ assert_equal 0, color_with_hash.green
+ assert_equal 127, color_with_hash.blue
+ assert_equal 1, color_with_hash.alpha
+ end
+
+ def test_malformed_hex_color
+ assert_raises ArgumentError do
+ hex_color("green")
+ end
+ assert_raises ArgumentError do
+ hex_color("#abcd")
+ end
+ end
+
+
+ def test_hex_color_with_alpha
+ color_with_alpha = hex_color("FF007F", 0.5)
+ assert_equal 0.5, color_with_alpha.alpha
+ end
+
+ def test_hex_color_alpha_clamps_0_to_1
+ assert_equal 1, hex_color("FF007F", 50).alpha
+ end
+
+ def test_hsl_color_without_alpha
+ no_alpha = hsl_color(1, 0.5, 1)
+ assert_equal 1, no_alpha.hue
+ assert_equal 0.5, no_alpha.saturation
+ assert_equal 1, no_alpha.lightness
+ assert_equal 1, no_alpha.alpha
+ end
+
+ def test_hsl_color_with_alpha
+ has_alpha = hsl_color(1, 0.5, 1, 0.5)
+ assert_equal 1, has_alpha.hue
+ assert_equal 0.5, has_alpha.saturation
+ assert_equal 1, has_alpha.lightness
+ assert_equal 0.5, has_alpha.alpha
+ end
+
+ def test_rgb_color_without_alpha
+ no_alpha = rgb_color(255, 0, 0)
+ assert_equal 255, no_alpha.red
+ assert_equal 0, no_alpha.green
+ assert_equal 0, no_alpha.blue
+ assert_equal 1, no_alpha.alpha
+ end
+
+ def test_rgb_color_with_alpha
+ has_alpha = rgb_color(255, 255, 255, 0.5)
+ assert_equal 255, has_alpha.red
+ assert_equal 255, has_alpha.green
+ assert_equal 255, has_alpha.blue
+ assert_equal 0.5, has_alpha.alpha
+ end
+
+ def test_number
+ n = number(1)
+ assert_equal 1, n.value
+ assert_equal "1", n.to_sass
+ end
+
+ def test_number_with_single_unit
+ n = number(1, "px")
+ assert_equal 1, n.value
+ assert_equal "1px", n.to_sass
+ end
+
+ def test_number_with_singal_numerator_and_denominator
+ ratio = number(1, "px/em")
+ assert_equal "1px/em", ratio.to_sass
+ end
+
+ def test_number_with_many_numerator_and_denominator_units
+ complex = number(1, "px*in/em*%")
+ assert_equal "1in*px/%*em", complex.to_sass
+ end
+
+ def test_number_with_many_numerator_and_denominator_units_with_spaces
+ complex = number(1, "px * in / em * %")
+ assert_equal "1in*px/%*em", complex.to_sass
+ end
+
+ def test_number_with_malformed_units
+ assert_raises ArgumentError do
+ number(1, "px/em/%")
+ end
+ assert_raises ArgumentError do
+ number(1, "/")
+ end
+ assert_raises ArgumentError do
+ number(1, "px/")
+ end
+ end
+
+ def test_space_list
+ l = list(number(1, "px"), hex_color("#f71"), :space)
+ l.options = {}
+ assert_kind_of Sass::Script::Value::List, l
+ assert_equal "1px #f71", l.to_sass
+ end
+
+ def test_comma_list
+ l = list(number(1, "px"), hex_color("#f71"), :comma)
+ l.options = {}
+ assert_kind_of Sass::Script::Value::List, l
+ assert_equal "1px, #f71", l.to_sass
+ end
+
+ def test_missing_list_type
+ assert_raises ArgumentError do
+ list(number(1, "px"), hex_color("#f71"))
+ end
+ end
+
+ def test_null
+ assert_kind_of Sass::Script::Value::Null, null
+ end
+
+ def test_quoted_string
+ s = quoted_string("sassy string")
+ s.options = {}
+ assert_kind_of Sass::Script::Value::String, s
+ assert_equal "sassy string", s.value
+ assert_equal :string, s.type
+ assert_equal '"sassy string"', s.to_sass
+ end
+
+ def test_identifier
+ s = identifier("a-sass-ident")
+ s.options = {}
+ assert_kind_of Sass::Script::Value::String, s
+ assert_equal "a-sass-ident", s.value
+ assert_equal :identifier, s.type
+ assert_equal "a-sass-ident", s.to_sass
+ end
+
+ def test_unquoted_string
+ s = unquoted_string("a-sass-ident")
+ s.options = {}
+ assert_kind_of Sass::Script::Value::String, s
+ assert_equal "a-sass-ident", s.value
+ assert_equal :identifier, s.type
+ assert_equal "a-sass-ident", s.to_sass
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/test/test_helper.rb
b/backends/css/gems/sass-3.4.9/test/test_helper.rb
new file mode 100644
index 0000000..128d476
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/test/test_helper.rb
@@ -0,0 +1,109 @@
+lib_dir = File.dirname(__FILE__) + '/../lib'
+
+require 'minitest/autorun'
+require 'fileutils'
+$:.unshift lib_dir unless $:.include?(lib_dir)
+require 'sass'
+require 'mathn' if ENV['MATHN'] == 'true'
+
+Sass::RAILS_LOADED = true unless defined?(Sass::RAILS_LOADED)
+
+Sass.tests_running = true
+
+if defined?(Encoding)
+ $-w, w = false, $-w
+ Encoding.default_external = 'UTF-8'
+ $-w = w
+end
+
+module Sass::Script::Functions
+ def option(name)
+ Sass::Script::Value::String.new(@options[name.value.to_sym].to_s)
+ end
+end
+
+class MiniTest::Test
+ def munge_filename(opts = {})
+ opts[:filename] ||= filename_for_test(opts[:syntax] || :sass)
+ opts[:sourcemap_filename] ||= sourcemap_filename_for_test
+ opts
+ end
+
+ def test_name
+ caller.
+ map {|c| Sass::Util.caller_info(c)[2]}.
+ compact.
+ map {|c| c.sub(/^(block|rescue) in /, '')}.
+ find {|c| c =~ /^test_/}
+ end
+
+ def filename_for_test(syntax = :sass)
+ "#{test_name}_inline.#{syntax}"
+ end
+
+ def sourcemap_filename_for_test(syntax = :sass)
+ "#{test_name}_inline.css.map"
+ end
+
+ def clean_up_sassc
+ path = File.dirname(__FILE__) + "/../.sass-cache"
+ Sass::Util.retry_on_windows {FileUtils.rm_r(path) if File.exist?(path)}
+ end
+
+ def assert_warning(message)
+ the_real_stderr, $stderr = $stderr, StringIO.new
+ yield
+
+ if message.is_a?(Regexp)
+ assert_match message, $stderr.string.strip
+ else
+ assert_equal message.strip, $stderr.string.strip
+ end
+ ensure
+ $stderr = the_real_stderr
+ end
+
+ def assert_no_warning
+ the_real_stderr, $stderr = $stderr, StringIO.new
+ yield
+
+ assert_equal '', $stderr.string
+ ensure
+ $stderr = the_real_stderr
+ end
+
+ def silence_warnings(&block)
+ Sass::Util.silence_warnings(&block)
+ end
+
+ def assert_raise_message(klass, message)
+ yield
+ rescue Exception => e
+ assert_instance_of(klass, e)
+ assert_equal(message, e.message)
+ else
+ flunk "Expected exception #{klass}, none raised"
+ end
+
+ def assert_raise_line(line)
+ yield
+ rescue Sass::SyntaxError => e
+ assert_equal(line, e.sass_line)
+ else
+ flunk "Expected exception on line #{line}, none raised"
+ end
+end
+
+module PublicApiLinter
+ def lint_api(api_class, duck_type_class)
+ define_method :test_lint_instance do
+ assert lint_instance.is_a?(duck_type_class)
+ end
+ api_class.instance_methods.each do |meth|
+ define_method :"test_has_#{meth}" do
+ assert lint_instance.respond_to?(meth),
+ "#{duck_type_class.name} does not implement #{meth}"
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/CHANGELOG.md
b/backends/css/gems/sass-3.4.9/vendor/listen/CHANGELOG.md
new file mode 100644
index 0000000..877461f
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/CHANGELOG.md
@@ -0,0 +1 @@
+# Moved to [Github releases](https://github.com/guard/listen/releases) page.
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/CONTRIBUTING.md
b/backends/css/gems/sass-3.4.9/vendor/listen/CONTRIBUTING.md
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/CONTRIBUTING.md
rename to backends/css/gems/sass-3.4.9/vendor/listen/CONTRIBUTING.md
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/Gemfile
b/backends/css/gems/sass-3.4.9/vendor/listen/Gemfile
new file mode 100644
index 0000000..9609578
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/Gemfile
@@ -0,0 +1,20 @@
+source 'https://rubygems.org'
+
+gemspec
+
+gem 'rake'
+
+require 'rbconfig'
+gem 'wdm', '>= 0.1.0' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+
+group :development do
+ gem 'guard-rspec'
+ gem 'yard'
+ gem 'redcarpet'
+ gem 'pimpmychangelog'
+end
+
+group :test do
+ gem 'rspec'
+ gem 'coveralls', :require => false
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/Guardfile
b/backends/css/gems/sass-3.4.9/vendor/listen/Guardfile
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/Guardfile
rename to backends/css/gems/sass-3.4.9/vendor/listen/Guardfile
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/LICENSE
b/backends/css/gems/sass-3.4.9/vendor/listen/LICENSE
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/LICENSE
rename to backends/css/gems/sass-3.4.9/vendor/listen/LICENSE
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/README.md
b/backends/css/gems/sass-3.4.9/vendor/listen/README.md
new file mode 100644
index 0000000..ff0ec33
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/README.md
@@ -0,0 +1,349 @@
+# Listen [![Gem Version](https://badge.fury.io/rb/listen.png)](http://badge.fury.io/rb/listen) [![Build
Status](https://secure.travis-ci.org/guard/listen.png?branch=master)](http://travis-ci.org/guard/listen)
[![Dependency Status](https://gemnasium.com/guard/listen.png)](https://gemnasium.com/guard/listen) [![Code
Climate](https://codeclimate.com/github/guard/listen.png)](https://codeclimate.com/github/guard/listen)
[![Coverage
Status](https://coveralls.io/repos/guard/listen/badge.png?branch=master)](https://coveralls.io/r/guard/listen)
+
+The Listen gem listens to file modifications and notifies you about the changes.
+
+## Features
+
+* Works everywhere!
+* Supports watching multiple directories from a single listener.
+* OS-specific adapters for Mac OS X 10.6+, Linux, *BSD and Windows.
+* Automatic fallback to polling if OS-specific adapter doesn't work.
+* Detects file modification, addition and removal.
+* File content checksum comparison for modifications made under the same second.
+* Allows supplying regexp-patterns to ignore and filter paths for better results.
+* Tested on all Ruby environments via [Travis CI](https://travis-ci.org/guard/listen).
+
+## Pending features
+
+Still not implemented, pull requests are welcome.
+
+* Symlinks support. [#25](https://github.com/guard/listen/issues/25)
+* Signal handling. [#105](https://github.com/guard/listen/issues/105)
+* Non-recursive directory scanning. [#111](https://github.com/guard/listen/issues/111)
+
+## Install
+
+### Using Bundler
+
+The simplest way to install Listen is to use Bundler.
+
+Add Listen to your Gemfile:
+
+```ruby
+group :development do
+ gem 'listen'
+end
+```
+
+and install it by running Bundler:
+
+```bash
+$ bundle
+```
+
+### Install the gem with RubyGems
+
+```bash
+$ gem install listen
+```
+
+### On Windows
+
+If your are on Windows and using Ruby MRI >= 1.9.2 you can try to use the
[`wdm`](https://github.com/Maher4Ever/wdm) instead of polling.
+Please add the following to your Gemfile:
+
+```ruby
+require 'rbconfig'
+gem 'wdm', '>= 0.1.0' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+```
+
+## Usage
+
+There are **two ways** to use Listen:
+
+1. Block API: Call `Listen.to`/`Listen.to!` with either a single directory or multiple directories, then
define the `change` callback in a block.
+2. "Object" API: Create a `listener` object and use it in a chainable way.
+
+### Block API
+
+``` ruby
+# Listen to a single directory.
+Listen.to('dir/path/to/listen', :filter => /\.rb$/, :ignore => %r{ignored/path/}) do |modified, added,
removed|
+ # ...
+end
+
+# Listen to multiple directories.
+Listen.to('dir/to/awesome_app', 'dir/to/other_app', :filter => /\.rb$/, :latency => 0.1) do |modified,
added, removed|
+ # ...
+end
+```
+
+### "Object" API
+
+``` ruby
+listener = Listen.to('dir/path/to/listen')
+listener = listener.ignore(%r{^ignored/path/})
+listener = listener.filter(/\.rb$/)
+listener = listener.latency(0.5)
+listener = listener.force_polling(true)
+listener = listener.polling_fallback_message(false)
+listener = listener.force_adapter(Listen::Adapters::Linux)
+listener = listener.change(&callback)
+listener.start
+```
+
+**Note**: All the "Object" API methods except `start`/`start!` return the listener
+and are thus chainable:
+
+``` ruby
+Listen.to('dir/path/to/listen')
+ .ignore(%r{^ignored/path/})
+ .filter(/\.rb$/)
+ .latency(0.5)
+ .force_polling(true)
+ .polling_fallback_message('custom message')
+ .change(&callback)
+ .start
+```
+
+### Pause/Unpause
+
+Listener can also easily be paused/unpaused:
+
+``` ruby
+listener = Listen.to('dir/path/to/listen')
+listener.start # non-blocking mode
+listener.pause # stop listening to changes
+listener.paused? # => true
+listener.unpause # start listening to changes again
+listener.stop # stop completely the listener
+```
+
+## Changes callback
+
+Changes to the listened-to directories gets reported back to the user in a callback.
+The registered callback gets invoked, when there are changes, with **three** parameters:
+`modified_paths`, `added_paths` and `removed_paths` in that particular order.
+
+You can register a callback in two ways. The first way is by passing a block when calling
+the `Listen.to`/`Listen.to!` method or when initializing a listener object:
+
+```ruby
+Listen.to('path/to/app') do |modified, added, removed|
+ # This block will be called when there are changes.
+end
+
+# or ...
+
+listener = Listen::Listener.new('path/to/app') do |modified, added, removed|
+ # This block will be called when there are changes.
+end
+
+```
+
+The second way to register a callback is by calling the `#change` method on a
+listener passing it a block:
+
+```ruby
+# Create a callback
+callback = Proc.new do |modified, added, removed|
+ # This proc will be called when there are changes.
+end
+
+listener = Listen.to('dir')
+listener.change(&callback) # convert the callback to a block and register it
+
+listener.start
+```
+
+### Paths in callbacks
+
+Listeners invoke callbacks passing them absolute paths by default:
+
+```ruby
+# Assume someone changes the 'style.css' file in '/home/user/app/css' after creating
+# the listener.
+Listen.to('/home/user/app/css') do |modified, added, removed|
+ modified.inspect # => ['/home/user/app/css/style.css']
+end
+```
+
+#### Relative paths in callbacks
+
+When creating a listener for a **single** path (more specifically a `Listen::Listener` instance),
+you can pass `:relative_paths => true` as an option to get relative paths in
+your callback:
+
+```ruby
+# Assume someone changes the 'style.css' file in '/home/user/app/css' after creating
+# the listener.
+Listen.to('/home/user/app/css', :relative_paths => true) do |modified, added, removed|
+ modified.inspect # => ['style.css']
+end
+```
+
+Passing the `:relative_paths => true` option won't work when listening to multiple
+directories:
+
+```ruby
+# Assume someone changes the 'style.css' file in '/home/user/app/css' after creating
+# the listener.
+Listen.to('/home/user/app/css', '/home/user/app/js', :relative_paths => true) do |modified, added, removed|
+ modified.inspect # => ['/home/user/app/css/style.css']
+end
+```
+
+## Options
+
+All the following options can be set through the `Listen.to`/`Listen.to!` params
+or via ["Object" API](#object-api) methods:
+
+```ruby
+:ignore => %r{app/CMake/}, /\.pid$/ # Ignore a list of paths (root directory or sub-dir)
+ # default: See DEFAULT_IGNORED_DIRECTORIES and
DEFAULT_IGNORED_EXTENSIONS in Listen::DirectoryRecord
+
+:filter => /\.rb$/, /\.coffee$/ # Filter files to listen to via a regexps list.
+ # default: none
+
+:latency => 0.5 # Set the delay (**in seconds**) between checking for changes
+ # default: 0.25 sec (1.0 sec for polling)
+
+:force_adapter => Listen::Adapters::Linux # Force the use of a particular adapter class
+ # default: none
+
+:force_polling => true # Force the use of the polling adapter
+ # default: none
+
+:polling_fallback_message => 'custom message' # Set a custom polling fallback message (or disable it with
false)
+ # default: "Listen will be polling for changes. Learn more at
https://github.com/guard/listen#polling-fallback."
+
+:relative_paths => true # Enable the use of relative paths in the callback.
+ # default: false
+```
+
+### Note on the patterns for ignoring and filtering paths
+
+Just like the unix convention of beginning absolute paths with the
+directory-separator (forward slash `/` in unix) and with no prefix for relative paths,
+Listen doesn't prefix relative paths (to the watched directory) with a directory-separator.
+
+Therefore make sure _NOT_ to prefix your regexp-patterns for filtering or ignoring paths
+with a directory-separator, otherwise they won't work as expected.
+
+As an example: to ignore the `build` directory in a C-project, use `%r{build/}`
+and not `%r{/build/}`.
+
+Use `#filter!` and `#ignore!` methods to overwrites default patterns.
+
+## Blocking listening to changes
+
+Calling `Listen.to` with a block doesn't block the current thread. If you want
+to block the current thread instead until the listener is stopped (which needs
+to be done from another thread), you can use `Listen.to!`.
+
+Similarly, if you're using the "Object" API, you can use `#start!` instead of `#start` to block the
+current thread until the listener is stopped.
+
+Here is an example of using a listener in the blocking mode:
+
+```ruby
+Listen.to!('dir/path/to/listen') # block execution
+
+# Code here will not run until the listener is stopped
+
+```
+
+Here is an example of using a listener started with the "Object" API in blocking mode:
+
+```ruby
+listener = Listen.to('dir/path/to/listen')
+listener.start! # block execution
+
+# Code here will not run until the listener is stopped
+
+```
+
+**Note**: Using the `Listen.to!` helper-method with or without a callback-block
+will always start the listener right away and block execution of the current thread.
+
+## Listen adapters
+
+The Listen gem has a set of adapters to notify it when there are changes.
+There are 4 OS-specific adapters to support Mac, Linux, *BSD and Windows.
+These adapters are fast as they use some system-calls to implement the notifying function.
+
+There is also a polling adapter which is a cross-platform adapter and it will
+work on any system. This adapter is unfortunately slower than the rest of the adapters.
+
+The Listen gem will choose the best and working adapter for your machine automatically. If you
+want to force the use of the polling adapter, either use the `:force_polling` option
+while initializing the listener or call the `#force_polling` method on your listener
+before starting it.
+
+It is also possible to force the use of a particular adapter, by using the `:force_adapter`
+option. This option skips the usual adapter choosing mechanism and uses the given
+adapter class instead. The adapter choosing mechanism requires write permission
+to your watched directories and will needlessly load code, which isn't always desirable.
+
+## Polling fallback
+
+When a OS-specific adapter doesn't work the Listen gem automatically falls back to the polling adapter.
+Here are some things you could try to avoid the polling fallback:
+
+* [Update your Dropbox client](http://www.dropbox.com/downloading) (if used).
+* Increase latency. (Please [open an issue](https://github.com/guard/listen/issues/new)
+if you think that default is too low.)
+* Move or rename the listened folder.
+* Update/reboot your OS.
+
+If your application keeps using the polling-adapter and you can't figure out why, feel free to [open an
issue](https://github.com/guard/listen/issues/new) (and be sure to [give all the
details](https://github.com/guard/listen/blob/master/CONTRIBUTING.md)).
+
+## Development [![Dependency
Status](https://gemnasium.com/guard/listen.png?branch=master)](https://gemnasium.com/guard/listen)
+
+* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/listen/master/frames).
+* Source hosted at [GitHub](https://github.com/guard/listen).
+
+Pull requests are very welcome! Please try to follow these simple rules if applicable:
+
+* Please create a topic branch for every separate change you make.
+* Make sure your patches are well tested. All specs must pass on [Travis
CI](https://travis-ci.org/guard/listen).
+* Update the [Yard](http://yardoc.org/) documentation.
+* Update the [README](https://github.com/guard/listen/blob/master/README.md).
+* Update the [CHANGELOG](https://github.com/guard/listen/blob/master/CHANGELOG.md) for noteworthy changes
(don't forget to run `bundle exec pimpmychangelog` and watch the magic happen)!
+* Please **do not change** the version number.
+
+For questions please join us in our [Google group](http://groups.google.com/group/guard-dev) or on
+`#guard` (irc.freenode.net).
+
+## Acknowledgments
+
+* [Michael Kessler (netzpirat)][] for having written the [initial
specs](https://github.com/guard/listen/commit/1e457b13b1bb8a25d2240428ce5ed488bafbed1f).
+* [Travis Tilley (ttilley)][] for this awesome work on [fssm][] & [rb-fsevent][].
+* [Nathan Weizenbaum (nex3)][] for [rb-inotify][], a thorough inotify wrapper.
+* [Mathieu Arnold (mat813)][] for [rb-kqueue][], a simple kqueue wrapper.
+* [stereobooster][] for [rb-fchange][], windows support wouldn't exist without him.
+* [Yehuda Katz (wycats)][] for [vigilo][], that has been a great source of inspiration.
+
+## Authors
+
+* [Thibaud Guillaume-Gentil][] ([ thibaudgg](http://twitter.com/thibaudgg))
+* [Maher Sallam][] ([ mahersalam](http://twitter.com/mahersalam))
+
+## Contributors
+
+[https://github.com/guard/listen/contributors](https://github.com/guard/listen/contributors)
+
+[Thibaud Guillaume-Gentil]: https://github.com/thibaudgg
+[Maher Sallam]: https://github.com/Maher4Ever
+[Michael Kessler (netzpirat)]: https://github.com/netzpirat
+[Travis Tilley (ttilley)]: https://github.com/ttilley
+[fssm]: https://github.com/ttilley/fssm
+[rb-fsevent]: https://github.com/thibaudgg/rb-fsevent
+[Mathieu Arnold (mat813)]: https://github.com/mat813
+[Nathan Weizenbaum (nex3)]: https://github.com/nex3
+[rb-inotify]: https://github.com/nex3/rb-inotify
+[stereobooster]: https://github.com/stereobooster
+[rb-fchange]: https://github.com/stereobooster/rb-fchange
+[rb-kqueue]: https://github.com/mat813/rb-kqueue
+[Yehuda Katz (wycats)]: https://github.com/wycats
+[vigilo]: https://github.com/wycats/vigilo
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/Rakefile
b/backends/css/gems/sass-3.4.9/vendor/listen/Rakefile
new file mode 100644
index 0000000..87da91c
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/Rakefile
@@ -0,0 +1,5 @@
+require 'bundler/gem_tasks'
+require 'rspec/core/rake_task'
+
+RSpec::Core::RakeTask.new(:spec)
+task :default => :spec
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/Vagrantfile
b/backends/css/gems/sass-3.4.9/vendor/listen/Vagrantfile
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/Vagrantfile
rename to backends/css/gems/sass-3.4.9/vendor/listen/Vagrantfile
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen.rb
new file mode 100644
index 0000000..9b3fdf2
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen.rb
@@ -0,0 +1,54 @@
+require 'listen/turnstile'
+require 'listen/listener'
+require 'listen/directory_record'
+require 'listen/adapter'
+
+module Listen
+
+ module Adapters
+ Adapter::ADAPTERS.each do |adapter|
+ require "listen/adapters/#{adapter.downcase}"
+ end
+ end
+
+ # Listens to file system modifications on a either single directory or multiple directories.
+ # When calling this method, the current thread is not blocked.
+ #
+ # @param (see Listen::Listener#new)
+ #
+ # @yield [modified, added, removed] the changed files
+ # @yieldparam [Array<String>] modified the list of modified files
+ # @yieldparam [Array<String>] added the list of added files
+ # @yieldparam [Array<String>] removed the list of removed files
+ #
+ # @return [Listen::Listener] the file listener if no block given
+ #
+ def self.to(*args, &block)
+ listener = _init_listener(*args, &block)
+
+ block ? listener.start : listener
+ end
+
+ # Listens to file system modifications on a either single directory or multiple directories.
+ # When calling this method, the current thread is blocked.
+ #
+ # @param (see Listen::Listener#new)
+ #
+ # @yield [modified, added, removed] the changed files
+ # @yieldparam [Array<String>] modified the list of modified files
+ # @yieldparam [Array<String>] added the list of added files
+ # @yieldparam [Array<String>] removed the list of removed files
+ #
+ # @since 1.0.0
+ #
+ def self.to!(*args, &block)
+ _init_listener(*args, &block).start!
+ end
+
+ # @private
+ #
+ def self._init_listener(*args, &block)
+ Listener.new(*args, &block)
+ end
+
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapter.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapter.rb
new file mode 100644
index 0000000..4a0abec
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapter.rb
@@ -0,0 +1,327 @@
+require 'rbconfig'
+require 'thread'
+require 'set'
+require 'fileutils'
+
+module Listen
+ class Adapter
+ attr_accessor :directories, :callback, :stopped, :paused,
+ :mutex, :changed_directories, :turnstile, :latency,
+ :worker, :worker_thread, :poller_thread
+
+ # The list of existing optimized adapters.
+ OPTIMIZED_ADAPTERS = %w[Darwin Linux BSD Windows]
+
+ # The list of existing fallback adapters.
+ FALLBACK_ADAPTERS = %w[Polling]
+
+ # The list of all existing adapters.
+ ADAPTERS = OPTIMIZED_ADAPTERS + FALLBACK_ADAPTERS
+
+ # The default delay between checking for changes.
+ DEFAULT_LATENCY = 0.25
+
+ # The default warning message when falling back to polling adapter.
+ POLLING_FALLBACK_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ Listen will be polling for changes. Learn more at https://github.com/guard/listen#polling-fallback.
+ EOS
+
+ # Selects the appropriate adapter implementation for the
+ # current OS and initializes it.
+ #
+ # @param [String, Array<String>] directories the directories to watch
+ # @param [Hash] options the adapter options
+ # @option options [Boolean] force_polling to force polling or not
+ # @option options [String, Boolean] polling_fallback_message to change polling fallback message or
remove it
+ # @option options [Float] latency the delay between checking for changes in seconds
+ #
+ # @yield [changed_directories, options] callback the callback called when a change happens
+ # @yieldparam [Array<String>] changed_directories the changed directories
+ # @yieldparam [Hash] options callback options (like recursive: true)
+ #
+ # @return [Listen::Adapter] the chosen adapter
+ #
+ def self.select_and_initialize(directories, options = {}, &callback)
+ forced_adapter_class = options.delete(:force_adapter)
+ force_polling = options.delete(:force_polling)
+
+ if forced_adapter_class
+ forced_adapter_class.load_dependent_adapter
+ return forced_adapter_class.new(directories, options, &callback)
+ end
+
+ return Adapters::Polling.new(directories, options, &callback) if force_polling
+
+ OPTIMIZED_ADAPTERS.each do |adapter|
+ namespaced_adapter = Adapters.const_get(adapter)
+ if namespaced_adapter.send(:usable_and_works?, directories, options)
+ return namespaced_adapter.new(directories, options, &callback)
+ end
+ end
+
+ self.warn_polling_fallback(options)
+ Adapters::Polling.new(directories, options, &callback)
+ end
+
+ # Initializes the adapter.
+ #
+ # @param [String, Array<String>] directories the directories to watch
+ # @param [Hash] options the adapter options
+ # @option options [Float] latency the delay between checking for changes in seconds
+ #
+ # @yield [changed_directories, options] callback Callback called when a change happens
+ # @yieldparam [Array<String>] changed_directories the changed directories
+ # @yieldparam [Hash] options callback options (like recursive: true)
+ #
+ # @return [Listen::Adapter] the adapter
+ #
+ def initialize(directories, options = {}, &callback)
+ @directories = Array(directories)
+ @callback = callback
+ @stopped = true
+ @paused = false
+ @mutex = Mutex.new
+ @changed_directories = Set.new
+ @turnstile = Turnstile.new
+ @latency = options.fetch(:latency, default_latency)
+ @worker = initialize_worker
+ end
+
+ # Starts the adapter and don't block the current thread.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start
+ mutex.synchronize do
+ return unless stopped
+ @stopped = false
+ end
+
+ start_worker
+ start_poller
+ end
+
+ # Starts the adapter and block the current thread.
+ #
+ # @since 1.0.0
+ #
+ def start!
+ start
+ blocking_thread.join
+ end
+
+ # Stops the adapter.
+ #
+ def stop
+ mutex.synchronize do
+ return if stopped
+ @stopped = true
+ turnstile.signal # ensure no thread is blocked
+ end
+
+ worker.stop if worker
+ Thread.kill(worker_thread) if worker_thread
+ if poller_thread
+ poller_thread.kill
+ poller_thread.join
+ end
+ end
+
+ # Pauses the adapter.
+ #
+ def pause
+ @paused = true
+ end
+
+ # Unpauses the adapter.
+ #
+ def unpause
+ @paused = false
+ end
+
+ # Returns whether the adapter is started or not.
+ #
+ # @return [Boolean] whether the adapter is started or not
+ #
+ def started?
+ !stopped
+ end
+
+ # Returns whether the adapter is paused or not.
+ #
+ # @return [Boolean] whether the adapter is paused or not
+ #
+ def paused?
+ paused
+ end
+
+ # Blocks the main thread until the poll thread
+ # runs the callback.
+ #
+ def wait_for_callback
+ turnstile.wait unless paused
+ end
+
+ # Blocks the main thread until N changes are
+ # detected.
+ #
+ def wait_for_changes(threshold = 0)
+ changes = 0
+
+ loop do
+ mutex.synchronize { changes = changed_directories.size }
+
+ return if paused || stopped
+ return if changes >= threshold
+
+ sleep(latency)
+ end
+ end
+
+ # Checks if the adapter is usable and works on the current OS.
+ #
+ # @param [String, Array<String>] directories the directories to watch
+ # @param [Hash] options the adapter options
+ # @option options [Float] latency the delay between checking for changes in seconds
+ #
+ # @return [Boolean] whether the adapter is usable and work or not
+ #
+ def self.usable_and_works?(directories, options = {})
+ usable? && Array(directories).all? { |d| works?(d, options) }
+ end
+
+ # Checks if the adapter is usable on target OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ load_dependent_adapter if RbConfig::CONFIG['target_os'] =~ target_os_regex
+ end
+
+ # Load the adapter gem
+ #
+ # @return [Boolean] whether loaded or not
+ #
+ def self.load_dependent_adapter
+ return true if @loaded
+ require adapter_gem
+ return @loaded = true
+ rescue LoadError
+ false
+ end
+
+ # Runs a tests to determine if the adapter can actually pick up
+ # changes in a given directory and returns the result.
+ #
+ # @note This test takes some time depending on the adapter latency.
+ #
+ # @param [String, Pathname] directory the directory to watch
+ # @param [Hash] options the adapter options
+ # @option options [Float] latency the delay between checking for changes in seconds
+ #
+ # @return [Boolean] whether the adapter works or not
+ #
+ def self.works?(directory, options = {})
+ work = false
+ test_file = "#{directory}/.listen_test"
+ callback = lambda { |*| work = true }
+ adapter = self.new(directory, options, &callback)
+ adapter.start
+
+ FileUtils.touch(test_file)
+
+ t = Thread.new { sleep(adapter.latency * 5); adapter.stop }
+
+ adapter.wait_for_callback
+ work
+ ensure
+ Thread.kill(t) if t
+ FileUtils.rm(test_file, :force => true)
+ adapter.stop if adapter && adapter.started?
+ end
+
+ # Runs the callback and passes it the changes if there are any.
+ #
+ def report_changes
+ changed_dirs = nil
+
+ mutex.synchronize do
+ return if @changed_directories.empty?
+ changed_dirs = @changed_directories.to_a
+ @changed_directories.clear
+ end
+
+ callback.call(changed_dirs, {})
+ turnstile.signal
+ end
+
+ private
+
+ # The default delay between checking for changes.
+ #
+ # @note This method can be overriden on a per-adapter basis.
+ #
+ def default_latency
+ DEFAULT_LATENCY
+ end
+
+ # The thread on which the main thread should wait
+ # when the adapter has been started in blocking mode.
+ #
+ # @note This method can be overriden on a per-adapter basis.
+ #
+ def blocking_thread
+ worker_thread
+ end
+
+ # Initialize the adpater' specific worker.
+ #
+ # @note Each adapter must override this method
+ # to initialize its own @worker.
+ #
+ def initialize_worker
+ nil
+ end
+
+ # Should start the worker in a new thread.
+ #
+ # @note Each adapter must override this method
+ # to start its worker on a new @worker_thread thread.
+ #
+ def start_worker
+ raise NotImplementedError, "#{self.class} cannot respond to: #{__method__}"
+ end
+
+ # This method starts a new thread which poll for changes detected by
+ # the adapter and report them back to the user.
+ #
+ def start_poller
+ @poller_thread = Thread.new { poll_changed_directories }
+ end
+
+ # Warn of polling fallback unless the :polling_fallback_message
+ # has been set to false.
+ #
+ # @param [String] warning an existing warning message
+ # @param [Hash] options the adapter options
+ # @option options [Boolean] polling_fallback_message to change polling fallback message or remove it
+ #
+ def self.warn_polling_fallback(options)
+ return if options[:polling_fallback_message] == false
+
+ warning = options[:polling_fallback_message] || POLLING_FALLBACK_MESSAGE
+ Kernel.warn "[Listen warning]:\n#{warning.gsub(/^(.*)/, ' \1')}"
+ end
+
+ # Polls changed directories and reports them back when there are changes.
+ #
+ # @note This method can be overriden on a per-adapter basis.
+ #
+ def poll_changed_directories
+ until stopped
+ sleep(latency)
+ report_changes
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/bsd.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/bsd.rb
new file mode 100644
index 0000000..743c8ea
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/bsd.rb
@@ -0,0 +1,75 @@
+module Listen
+ module Adapters
+
+ # Listener implementation for BSD's `kqueue`.
+ #
+ class BSD < Adapter
+ # Watched kqueue events
+ #
+ # @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
+ # @see https://github.com/nex3/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
+ #
+ EVENTS = [:delete, :write, :extend, :attrib, :link, :rename, :revoke]
+
+ def self.target_os_regex; /freebsd/i; end
+ def self.adapter_gem; 'rb-kqueue'; end
+
+ private
+
+ # Initializes a kqueue Queue and adds a watcher for each files in
+ # the directories passed to the adapter.
+ #
+ # @return [INotify::Notifier] initialized kqueue
+ #
+ # @see Listen::Adapter#initialize_worker
+ #
+ def initialize_worker
+ require 'find'
+
+ callback = lambda do |event|
+ path = event.watcher.path
+ mutex.synchronize do
+ # kqueue watches everything, but Listen only needs the
+ # directory where stuffs happens.
+ @changed_directories << (File.directory?(path) ? path : File.dirname(path))
+
+ # If it is a directory, and it has a write flag, it means a
+ # file has been added so find out which and deal with it.
+ # No need to check for removed files, kqueue will forget them
+ # when the vfs does.
+ if File.directory?(path) && event.flags.include?(:write)
+ queue = event.watcher.queue
+ Find.find(path) do |file|
+ unless queue.watchers.detect { |k,v| v.path == file.to_s }
+ queue.watch_file(file, *EVENTS, &callback)
+ end
+ end
+ end
+ end
+ end
+
+ KQueue::Queue.new.tap do |queue|
+ directories.each do |directory|
+ Find.find(directory) do |path|
+ queue.watch_file(path, *EVENTS, &callback)
+ end
+ end
+ end
+ end
+
+ # Starts the worker in a new thread.
+ #
+ # @see Listen::Adapter#start_worker
+ #
+ def start_worker
+ @worker_thread = Thread.new do
+ until stopped
+ worker.poll
+ sleep(latency)
+ end
+ end
+ end
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/darwin.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/darwin.rb
new file mode 100644
index 0000000..ad0a955
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/darwin.rb
@@ -0,0 +1,48 @@
+module Listen
+ module Adapters
+
+ # Adapter implementation for Mac OS X `FSEvents`.
+ #
+ class Darwin < Adapter
+ LAST_SEPARATOR_REGEX = /\/$/
+
+
+ def self.target_os_regex; /darwin(1.+)?$/i; end
+ def self.adapter_gem; 'rb-fsevent'; end
+
+ private
+
+ # Initializes a FSEvent worker and adds a watcher for
+ # each directory passed to the adapter.
+ #
+ # @return [FSEvent] initialized worker
+ #
+ # @see Listen::Adapter#initialize_worker
+ #
+ def initialize_worker
+ FSEvent.new.tap do |worker|
+ worker.watch(directories.dup, :latency => latency) do |changes|
+ next if paused
+
+ mutex.synchronize do
+ changes.each { |path| @changed_directories << path.sub(LAST_SEPARATOR_REGEX, '') }
+ end
+ end
+ end
+ end
+
+ # Starts the worker in a new thread and sleep 0.1 second.
+ #
+ # @see Listen::Adapter#start_worker
+ #
+ def start_worker
+ @worker_thread = Thread.new { worker.run }
+ # The FSEvent worker needs some time to start up. Turnstiles can't
+ # be used to wait for it as it runs in a loop.
+ # TODO: Find a better way to block until the worker starts.
+ sleep 0.1
+ end
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/linux.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/linux.rb
new file mode 100644
index 0000000..b926465
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/linux.rb
@@ -0,0 +1,81 @@
+module Listen
+ module Adapters
+
+ # Listener implementation for Linux `inotify`.
+ #
+ class Linux < Adapter
+ # Watched inotify events
+ #
+ # @see http://www.tin.org/bin/man.cgi?section=7&topic=inotify
+ # @see https://github.com/nex3/rb-inotify/blob/master/lib/rb-inotify/notifier.rb#L99-L177
+ #
+ EVENTS = [:recursive, :attrib, :create, :delete, :move, :close_write]
+
+ # The message to show when the limit of inotify watchers is not enough
+ #
+ INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ Listen error: unable to monitor directories for changes.
+
+ Please head to https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers
+ for information on how to solve this issue.
+ EOS
+
+ def self.target_os_regex; /linux/i; end
+ def self.adapter_gem; 'rb-inotify'; end
+
+ # Initializes the Adapter.
+ #
+ # @see Listen::Adapter#initialize
+ #
+ def initialize(directories, options = {}, &callback)
+ super
+ rescue Errno::ENOSPC
+ abort(INOTIFY_LIMIT_MESSAGE)
+ end
+
+ private
+
+ # Initializes a INotify worker and adds a watcher for
+ # each directory passed to the adapter.
+ #
+ # @return [INotify::Notifier] initialized worker
+ #
+ # @see Listen::Adapter#initialize_worker
+ #
+ def initialize_worker
+ callback = lambda do |event|
+ if paused || (
+ # Event on root directory
+ event.name == ""
+ ) || (
+ # INotify reports changes to files inside directories as events
+ # on the directories themselves too.
+ #
+ # @see http://linux.die.net/man/7/inotify
+ event.flags.include?(:isdir) and (event.flags & [:close, :modify]).any?
+ )
+ # Skip all of these!
+ next
+ end
+
+ mutex.synchronize do
+ @changed_directories << File.dirname(event.absolute_name)
+ end
+ end
+
+ INotify::Notifier.new.tap do |worker|
+ directories.each { |dir| worker.watch(dir, *EVENTS, &callback) }
+ end
+ end
+
+ # Starts the worker in a new thread.
+ #
+ # @see Listen::Adapter#start_worker
+ #
+ def start_worker
+ @worker_thread = Thread.new { worker.run }
+ end
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/polling.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/polling.rb
new file mode 100644
index 0000000..77b6525
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/polling.rb
@@ -0,0 +1,58 @@
+module Listen
+ module Adapters
+
+ DEFAULT_POLLING_LATENCY = 1.0
+
+ # Polling Adapter that works cross-platform and
+ # has no dependencies. This is the adapter that
+ # uses the most CPU processing power and has higher
+ # file IO than the other implementations.
+ #
+ class Polling < Adapter
+ private
+
+ # The default delay between checking for changes.
+ #
+ # @see Listen::Adapter#default_latency
+ #
+ def default_latency
+ 1.0
+ end
+
+ # The thread on which the main thread should wait
+ # when the adapter has been started in blocking mode.
+ #
+ # @see Listen::Adapter#blocking_thread
+ #
+ def blocking_thread
+ poller_thread
+ end
+
+ # @see Listen::Adapter#start_worker
+ #
+ # @see Listen::Adapter#start_worker
+ #
+ def start_worker
+ # The polling adapter has no worker! Sad panda! :'(
+ end
+
+ # Poll listener directory for file system changes.
+ #
+ # @see Listen::Adapter#poll_changed_directories
+ #
+ def poll_changed_directories
+ until stopped
+ next if paused
+
+ start = Time.now.to_f
+ callback.call(directories.dup, :recursive => true)
+ turnstile.signal
+ nap_time = latency - (Time.now.to_f - start)
+ sleep(nap_time) if nap_time > 0
+ end
+ rescue Interrupt
+ end
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/windows.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/windows.rb
new file mode 100644
index 0000000..3c470bf
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/adapters/windows.rb
@@ -0,0 +1,91 @@
+require 'set'
+require 'rubygems'
+
+module Listen
+ module Adapters
+
+ # Adapter implementation for Windows `wdm`.
+ #
+ class Windows < Adapter
+
+ BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
+ Please add the following to your Gemfile to avoid polling for changes:
+ require 'rbconfig'
+ gem 'wdm', '>= 0.1.0' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+ EOS
+
+ def self.target_os_regex; /mswin|mingw/i; end
+ def self.adapter_gem; 'wdm'; end
+
+ # Checks if the adapter is usable on target OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ super if mri? && at_least_ruby_1_9?
+ end
+
+ # Load the adapter gem
+ #
+ # @return [Boolean] whether required or not
+ #
+ def self.load_dependent_adapter
+ super
+ rescue Gem::LoadError
+ Kernel.warn BUNDLER_DECLARE_GEM
+ end
+
+ private
+
+ # Checks if Ruby engine is MRI.
+ #
+ # @return [Boolean]
+ #
+ def self.mri?
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
+ end
+
+ # Checks if Ruby engine is MRI.
+ #
+ # @return [Boolean]
+ #
+ def self.at_least_ruby_1_9?
+ Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('1.9.2')
+ end
+
+ # Initializes a WDM monitor and adds a watcher for
+ # each directory passed to the adapter.
+ #
+ # @return [WDM::Monitor] initialized worker
+ #
+ # @see Listen::Adapter#initialize_worker
+ #
+ def initialize_worker
+ callback = Proc.new do |change|
+ next if paused
+
+ mutex.synchronize do
+ @changed_directories << File.dirname(change.path)
+ end
+ end
+
+ WDM::Monitor.new.tap do |worker|
+ directories.each { |dir| worker.watch_recursively(dir, &callback) }
+ end
+ end
+
+ # Start the worker in a new thread and sleep 0.1 second.
+ #
+ # @see Listen::Adapter#start_worker
+ #
+ def start_worker
+ @worker_thread = Thread.new { worker.run! }
+ # Wait for the worker to start. This is needed to avoid a deadlock
+ # when stopping immediately after starting.
+ sleep 0.1
+ end
+
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/directory_record.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/directory_record.rb
new file mode 100644
index 0000000..3729d51
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/directory_record.rb
@@ -0,0 +1,406 @@
+require 'set'
+require 'find'
+require 'digest/sha1'
+
+module Listen
+
+ # The directory record stores information about
+ # a directory and keeps track of changes to
+ # the structure of its childs.
+ #
+ class DirectoryRecord
+ attr_reader :directory, :paths, :sha1_checksums
+
+ # The default list of directories that get ignored by the listener.
+ DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn bundle log tmp vendor]
+
+ # The default list of files that get ignored by the listener.
+ DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
+
+ # Defines the used precision based on the type of mtime returned by the
+ # system (whether its in milliseconds or just seconds)
+ #
+ begin
+ HIGH_PRECISION_SUPPORTED = File.mtime(__FILE__).to_f.to_s[-2..-1] != '.0'
+ rescue
+ HIGH_PRECISION_SUPPORTED = false
+ end
+
+ # Data structure used to save meta data about a path
+ #
+ MetaData = Struct.new(:type, :mtime)
+
+ # Class methods
+ #
+ class << self
+
+ # Creates the ignoring patterns from the default ignored
+ # directories and extensions. It memoizes the generated patterns
+ # to avoid unnecessary computation.
+ #
+ def generate_default_ignoring_patterns
+ @@default_ignoring_patterns ||= Array.new.tap do |default_patterns|
+ # Add directories
+ ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
+ default_patterns << %r{^(?:#{ignored_directories.join('|')})/}
+
+ # Add extensions
+ ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
+ default_patterns << %r{(?:#{ignored_extensions.join('|')})$}
+ end
+ end
+ end
+
+ # Initializes a directory record.
+ #
+ # @option [String] directory the directory to keep track of
+ #
+ def initialize(directory)
+ raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
+
+ @directory, @sha1_checksums = File.expand_path(directory), Hash.new
+ @ignoring_patterns, @filtering_patterns = Set.new, Set.new
+
+ @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
+ end
+
+ # Returns the ignoring patterns in the record to know
+ # which paths should be ignored.
+ #
+ # @return [Array<Regexp>] the ignoring patterns
+ #
+ def ignoring_patterns
+ @ignoring_patterns.to_a
+ end
+
+ # Returns the filtering patterns in the record to know
+ # which paths should be stored.
+ #
+ # @return [Array<Regexp>] the filtering patterns
+ #
+ def filtering_patterns
+ @filtering_patterns.to_a
+ end
+
+ # Adds ignoring patterns to the record.
+ #
+ # @example Ignore some paths
+ # ignore %r{^ignored/path/}, /man/
+ #
+ # @param [Regexp] regexps a list of patterns for ignoring paths
+ #
+ def ignore(*regexps)
+ @ignoring_patterns.merge(regexps).reject! { |r| r.nil? }
+ end
+
+ # Replaces ignoring patterns in the record.
+ #
+ # @example Ignore only these paths
+ # ignore! %r{^ignored/path/}, /man/
+ #
+ # @param [Regexp] regexps a list of patterns for ignoring paths
+ #
+ def ignore!(*regexps)
+ @ignoring_patterns.replace(regexps).reject! { |r| r.nil? }
+ end
+
+ # Adds filtering patterns to the record.
+ #
+ # @example Filter some files
+ # filter /\.txt$/, /.*\.zip/
+ #
+ # @param [Regexp] regexps a list of patterns for filtering files
+ #
+ def filter(*regexps)
+ @filtering_patterns.merge(regexps).reject! { |r| r.nil? }
+ end
+
+ # Replaces filtering patterns in the record.
+ #
+ # @example Filter only these files
+ # filter! /\.txt$/, /.*\.zip/
+ #
+ # @param [Regexp] regexps a list of patterns for filtering files
+ #
+ def filter!(*regexps)
+ @filtering_patterns.replace(regexps).reject! { |r| r.nil? }
+ end
+
+ # Returns whether a path should be ignored or not.
+ #
+ # @param [String] path the path to test
+ #
+ # @return [Boolean]
+ #
+ def ignored?(path)
+ path = relative_to_base(path)
+ @ignoring_patterns.any? { |pattern| pattern =~ path }
+ end
+
+ # Returns whether a path should be filtered or not.
+ #
+ # @param [String] path the path to test
+ #
+ # @return [Boolean]
+ #
+ def filtered?(path)
+ # When no filtering patterns are set, ALL files are stored.
+ return true if @filtering_patterns.empty?
+
+ path = relative_to_base(path)
+ @filtering_patterns.any? { |pattern| pattern =~ path }
+ end
+
+ # Finds the paths that should be stored and adds them
+ # to the paths' hash.
+ #
+ def build
+ @paths = Hash.new { |h, k| h[k] = Hash.new }
+ important_paths { |path| insert_path(path) }
+ end
+
+ # Detects changes in the passed directories, updates
+ # the record with the new changes and returns the changes.
+ #
+ # @param [Array] directories the list of directories to scan for changes
+ # @param [Hash] options
+ # @option options [Boolean] recursive scan all sub-directories recursively
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
+ #
+ # @return [Hash<Array>] the changes
+ #
+ def fetch_changes(directories, options = {})
+ @changes = { :modified => [], :added => [], :removed => [] }
+ directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
+
+ directories.each do |directory|
+ next unless directory[ directory] # Path is or inside directory
+
+ detect_modifications_and_removals(directory, options)
+ detect_additions(directory, options)
+ end
+
+ @changes
+ end
+
+ # Converts an absolute path to a path that's relative to the base directory.
+ #
+ # @param [String] path the path to convert
+ #
+ # @return [String] the relative path
+ #
+ def relative_to_base(path)
+ path = path.dup
+ regexp = "\\A#{Regexp.quote directory}(#{File::SEPARATOR}|\\z)"
+ if path.respond_to?(:force_encoding)
+ path.force_encoding("BINARY")
+ regexp.force_encoding("BINARY")
+ end
+ if path.sub!(Regexp.new(regexp), '')
+ path
+ end
+ end
+
+ private
+
+ # Detects modifications and removals recursively in a directory.
+ #
+ # @note Modifications detection begins by checking the modification time (mtime)
+ # of files and then by checking content changes (using SHA1-checksum)
+ # when the mtime of files is not changed.
+ #
+ # @param [String] directory the path to analyze
+ # @param [Hash] options
+ # @option options [Boolean] recursive scan all sub-directories recursively
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
+ #
+ def detect_modifications_and_removals(directory, options = {})
+ paths[directory].each do |basename, meta_data|
+ path = File.join(directory, basename)
+ case meta_data.type
+ when 'Dir'
+ detect_modification_or_removal_for_dir(path, options)
+ when 'File'
+ detect_modification_or_removal_for_file(path, meta_data, options)
+ end
+ end
+ end
+
+ def detect_modification_or_removal_for_dir(path, options)
+
+ # Directory still exists
+ if File.directory?(path)
+ detect_modifications_and_removals(path, options) if options[:recursive]
+
+ # Directory has been removed
+ else
+ detect_modifications_and_removals(path, options)
+ @paths[File.dirname(path)].delete(File.basename(path))
+ @paths.delete("#{File.dirname(path)}/#{File.basename(path)}")
+ end
+ end
+
+ def detect_modification_or_removal_for_file(path, meta_data, options)
+ # File still exists
+ if File.exist?(path)
+ detect_modification(path, meta_data, options)
+
+ # File has been removed
+ else
+ removal_detected(path, meta_data, options)
+ end
+ end
+
+ def detect_modification(path, meta_data, options)
+ new_mtime = mtime_of(path)
+
+ # First check if we are in the same second (to update checksums)
+ # before checking the time difference
+ if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
+ modification_detected(path, meta_data, new_mtime, options)
+ end
+ end
+
+ def modification_detected(path, meta_data, new_mtime, options)
+ # Update the sha1 checksum of the file
+ update_sha1_checksum(path)
+
+ # Update the meta data of the file
+ meta_data.mtime = new_mtime
+ @paths[File.dirname(path)][File.basename(path)] = meta_data
+
+ @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
+ end
+
+ def removal_detected(path, meta_data, options)
+ @paths[File.dirname(path)].delete(File.basename(path))
+ @sha1_checksums.delete(path)
+ @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
+ end
+
+ # Detects additions in a directory.
+ #
+ # @param [String] directory the path to analyze
+ # @param [Hash] options
+ # @option options [Boolean] recursive scan all sub-directories recursively
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
+ #
+ def detect_additions(directory, options = {})
+ # Don't process removed directories
+ return unless File.exist?(directory)
+
+ Find.find(directory) do |path|
+ next if path == @directory
+
+ if File.directory?(path)
+ # Add a trailing slash to directories when checking if a directory is
+ # ignored to optimize finding them as Find.find doesn't.
+ if ignored?(path + File::SEPARATOR) || (directory != path && (!options[:recursive] &&
existing_path?(path)))
+ Find.prune # Don't look any further into this directory.
+ else
+ insert_path(path)
+ end
+ elsif !ignored?(path) && filtered?(path) && !existing_path?(path)
+ if File.file?(path)
+ @changes[:added] << (options[:relative_paths] ? relative_to_base(path) : path)
+ insert_path(path)
+ end
+ end
+ end
+ end
+
+ # Returns whether or not a file's content has been modified by
+ # comparing the SHA1-checksum to a stored one.
+ # Ensure that the SHA1-checksum is inserted to the sha1_checksums
+ # array for later comparaison if false.
+ #
+ # @param [String] path the file path
+ #
+ def content_modified?(path)
+ return false unless File.ftype(path) == 'file'
+ @sha1_checksum = sha1_checksum(path)
+ if sha1_checksums[path] == @sha1_checksum || !sha1_checksums.key?(path)
+ update_sha1_checksum(path)
+ false
+ else
+ true
+ end
+ end
+
+ # Inserts a SHA1-checksum path in @SHA1-checksums hash.
+ #
+ # @param [String] path the SHA1-checksum path to insert in @sha1_checksums.
+ #
+ def update_sha1_checksum(path)
+ if @sha1_checksum ||= sha1_checksum(path)
+ @sha1_checksums[path] = @sha1_checksum
+ @sha1_checksum = nil
+ end
+ end
+
+ # Returns the SHA1-checksum for the file path.
+ #
+ # @param [String] path the file path
+ #
+ def sha1_checksum(path)
+ Digest::SHA1.file(path).to_s
+ rescue
+ nil
+ end
+
+ # Traverses the base directory looking for paths that should
+ # be stored; thus paths that are filtered or not ignored.
+ #
+ # @yield [path] an important path
+ #
+ def important_paths
+ Find.find(directory) do |path|
+ next if path == directory
+
+ if File.directory?(path)
+ # Add a trailing slash to directories when checking if a directory is
+ # ignored to optimize finding them as Find.find doesn't.
+ if ignored?(path + File::SEPARATOR)
+ Find.prune # Don't look any further into this directory.
+ else
+ yield(path)
+ end
+ elsif !ignored?(path) && filtered?(path)
+ yield(path)
+ end
+ end
+ end
+
+ # Inserts a path with its type (Dir or File) in paths hash.
+ #
+ # @param [String] path the path to insert in @paths.
+ #
+ def insert_path(path)
+ meta_data = MetaData.new
+ meta_data.type = File.directory?(path) ? 'Dir' : 'File'
+ meta_data.mtime = mtime_of(path) unless meta_data.type == 'Dir' # mtimes of dirs are not used yet
+ @paths[File.dirname(path)][File.basename(path)] = meta_data
+ rescue Errno::ENOENT
+ end
+
+ # Returns whether or not a path exists in the paths hash.
+ #
+ # @param [String] path the path to check
+ #
+ # @return [Boolean]
+ #
+ def existing_path?(path)
+ paths[File.dirname(path)][File.basename(path)] != nil
+ end
+
+ # Returns the modification time of a file based on the precision defined by the system
+ #
+ # @param [String] file the file for which the mtime must be returned
+ #
+ # @return [Fixnum, Float] the mtime of the file
+ #
+ def mtime_of(file)
+ File.lstat(file).mtime.send(HIGH_PRECISION_SUPPORTED ? :to_f : :to_i)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/listener.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/listener.rb
new file mode 100644
index 0000000..b8bd506
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/listener.rb
@@ -0,0 +1,323 @@
+require 'pathname'
+
+module Listen
+ class Listener
+ attr_reader :directories, :directories_records, :block, :adapter, :adapter_options, :use_relative_paths
+
+ BLOCKING_PARAMETER_DEPRECATION_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ The blocking parameter of Listen::Listener#start is deprecated.\n
+ Please use Listen::Adapter#start for a non-blocking listener and Listen::Listener#start! for a
blocking one.
+ EOS
+
+ RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE = "The relative_paths option doesn't work when
listening to multiple diretories."
+
+ # Initializes the directories listener.
+ #
+ # @param [String] directory the directories to listen to
+ # @param [Hash] options the listen options
+ # @option options [Regexp] ignore a pattern for ignoring paths
+ # @option options [Regexp] filter a pattern for filtering paths
+ # @option options [Float] latency the delay between checking for changes in seconds
+ # @option options [Boolean] relative_paths whether or not to use relative-paths in the callback
+ # @option options [Boolean] force_polling whether to force the polling adapter or not
+ # @option options [String, Boolean] polling_fallback_message to change polling fallback message or
remove it
+ # @option options [Class] force_adapter force the use of this adapter class, skipping usual adapter
selection
+ #
+ # @yield [modified, added, removed] the changed files
+ # @yieldparam [Array<String>] modified the list of modified files
+ # @yieldparam [Array<String>] added the list of added files
+ # @yieldparam [Array<String>] removed the list of removed files
+ #
+ def initialize(*args, &block)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ directories = args.flatten
+ initialize_directories_and_directories_records(directories)
+ initialize_relative_paths_usage(options)
+ @block = block
+
+ ignore(*options.delete(:ignore))
+ filter(*options.delete(:filter))
+
+ @adapter_options = options
+ end
+
+ # Starts the listener by initializing the adapter and building
+ # the directory record concurrently, then it starts the adapter to watch
+ # for changes. The current thread is not blocked after starting.
+ #
+ # @see Listen::Listener#start!
+ #
+ def start(deprecated_blocking = nil)
+ Kernel.warn "[Listen warning]:\n#{BLOCKING_PARAMETER_DEPRECATION_MESSAGE}" unless
deprecated_blocking.nil?
+ setup
+ adapter.start
+ end
+
+ # Starts the listener by initializing the adapter and building
+ # the directory record concurrently, then it starts the adapter to watch
+ # for changes. The current thread is blocked after starting.
+ #
+ # @see Listen::Listener#start
+ #
+ # @since 1.0.0
+ #
+ def start!
+ setup
+ adapter.start!
+ end
+
+ # Stops the listener.
+ #
+ def stop
+ adapter && adapter.stop
+ end
+
+ # Pauses the listener.
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def pause
+ adapter.pause
+ self
+ end
+
+ # Unpauses the listener.
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def unpause
+ build_directories_records
+ adapter.unpause
+ self
+ end
+
+ # Returns whether the listener is paused or not.
+ #
+ # @return [Boolean] adapter paused status
+ #
+ def paused?
+ !!adapter && adapter.paused?
+ end
+
+ # Adds ignoring patterns to the listener.
+ #
+ # @param (see Listen::DirectoryRecord#ignore)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ # @see Listen::DirectoryRecord#ignore
+ #
+ def ignore(*regexps)
+ directories_records.each { |r| r.ignore(*regexps) }
+ self
+ end
+
+ # Replaces ignoring patterns in the listener.
+ #
+ # @param (see Listen::DirectoryRecord#ignore!)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ # @see Listen::DirectoryRecord#ignore!
+ #
+ def ignore!(*regexps)
+ directories_records.each { |r| r.ignore!(*regexps) }
+ self
+ end
+
+ # Adds filtering patterns to the listener.
+ #
+ # @param (see Listen::DirectoryRecord#filter)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ # @see Listen::DirectoryRecord#filter
+ #
+ def filter(*regexps)
+ directories_records.each { |r| r.filter(*regexps) }
+ self
+ end
+
+ # Replaces filtering patterns in the listener.
+ #
+ # @param (see Listen::DirectoryRecord#filter!)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ # @see Listen::DirectoryRecord#filter!
+ #
+ def filter!(*regexps)
+ directories_records.each { |r| r.filter!(*regexps) }
+ self
+ end
+
+ # Sets the latency for the adapter. This is a helper method
+ # to simplify changing the latency directly from the listener.
+ #
+ # @example Wait 0.5 seconds each time before checking changes
+ # latency 0.5
+ #
+ # @param [Float] seconds the amount of delay, in seconds
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def latency(seconds)
+ @adapter_options[:latency] = seconds
+ self
+ end
+
+ # Sets whether the use of the polling adapter
+ # should be forced or not.
+ #
+ # @example Forcing the use of the polling adapter
+ # force_polling true
+ #
+ # @param [Boolean] value whether to force the polling adapter or not
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def force_polling(value)
+ @adapter_options[:force_polling] = value
+ self
+ end
+
+ # Sets whether to force the use of a particular adapter, rather than
+ # going through usual adapter selection process on start.
+ #
+ # @example Force use of Linux polling
+ # force_adapter Listen::Adapters::Linux
+ #
+ # @param [Class] adapter class to use for file system event notification.
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def force_adapter(adapter_class)
+ @adapter_options[:force_adapter] = adapter_class
+ self
+ end
+
+ # Sets whether the paths in the callback should be
+ # relative or absolute.
+ #
+ # @example Enabling relative paths in the callback
+ # relative_paths true
+ #
+ # @param [Boolean] value whether to enable relative paths in the callback or not
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def relative_paths(value)
+ @use_relative_paths = value
+ self
+ end
+
+ # Defines a custom polling fallback message or disable it.
+ #
+ # @example Disabling the polling fallback message
+ # polling_fallback_message false
+ #
+ # @param [String, Boolean] value to change polling fallback message or remove it
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def polling_fallback_message(value)
+ @adapter_options[:polling_fallback_message] = value
+ self
+ end
+
+ # Sets the callback that gets called on changes.
+ #
+ # @example Assign a callback to be called on changes
+ # callback = lambda { |modified, added, removed| ... }
+ # change &callback
+ #
+ # @param [Proc] block the callback proc
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def change(&block) # modified, added, removed
+ @block = block
+ self
+ end
+
+ # Runs the callback passing it the changes if there are any.
+ #
+ # @param (see Listen::DirectoryRecord#fetch_changes)
+ #
+ # @see Listen::DirectoryRecord#fetch_changes
+ #
+ def on_change(directories, options = {})
+ changes = fetch_records_changes(directories, options)
+ unless changes.values.all? { |paths| paths.empty? }
+ block.call(changes[:modified], changes[:added], changes[:removed])
+ end
+ rescue => ex
+ Kernel.warn "[Listen warning]: Change block raise an execption: #{$!}"
+ Kernel.warn "Backtrace:\n\t#{ex.backtrace.join("\n\t")}"
+ end
+
+ private
+
+ # Initializes the directories to watch as well as the directories records.
+ #
+ # @see Listen::DirectoryRecord
+ #
+ def initialize_directories_and_directories_records(directories)
+ @directories = directories.map { |d| Pathname.new(d).realpath.to_s }
+ @directories_records = directories.map { |d| DirectoryRecord.new(d) }
+ end
+
+ # Initializes whether or not using relative paths.
+ #
+ def initialize_relative_paths_usage(options)
+ if directories.size > 1 && options[:relative_paths]
+ Kernel.warn "[Listen warning]: #{RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE}"
+ end
+ @use_relative_paths = directories.one? && options.delete(:relative_paths) { false }
+ end
+
+ # Build the directory record concurrently and initialize the adapter.
+ #
+ def setup
+ t = Thread.new { build_directories_records }
+ @adapter = initialize_adapter
+ t.join
+ end
+
+ # Initializes an adapter passing it the callback and adapters' options.
+ #
+ def initialize_adapter
+ callback = lambda { |changed_directories, options| self.on_change(changed_directories, options) }
+ Adapter.select_and_initialize(directories, adapter_options, &callback)
+ end
+
+ # Build the watched directories' records.
+ #
+ def build_directories_records
+ directories_records.each { |r| r.build }
+ end
+
+ # Returns the sum of all the changes to the directories records
+ #
+ # @param (see Listen::DirectoryRecord#fetch_changes)
+ #
+ # @return [Hash] the changes
+ #
+ def fetch_records_changes(directories_to_search, options)
+ directories_records.inject({}) do |h, r|
+ # directory records skips paths outside their range, so passing the
+ # whole `directories` array is not a problem.
+ record_changes = r.fetch_changes(directories_to_search, options.merge(:relative_paths =>
use_relative_paths))
+
+ if h.empty?
+ h.merge!(record_changes)
+ else
+ h.each { |k, v| h[k] += record_changes[k] }
+ end
+
+ h
+ end
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/turnstile.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/turnstile.rb
new file mode 100644
index 0000000..a0dc696
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/turnstile.rb
@@ -0,0 +1,32 @@
+module Listen
+
+ # Allows two threads to wait on eachother.
+ #
+ # @note Only two threads can be used with this Turnstile
+ # because of the current implementation.
+ class Turnstile
+ attr_accessor :queue
+
+ # Initialize the turnstile.
+ #
+ def initialize
+ # Until Ruby offers semahpores, only queues can be used
+ # to implement a turnstile.
+ @queue = Queue.new
+ end
+
+ # Blocks the current thread until a signal is received.
+ #
+ def wait
+ queue.pop if queue.num_waiting == 0
+ end
+
+ # Unblocks the waiting thread if any.
+ #
+ def signal
+ queue.push(:dummy) if queue.num_waiting == 1
+ end
+
+ end
+
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/version.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/version.rb
new file mode 100644
index 0000000..fec74bf
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/lib/listen/version.rb
@@ -0,0 +1,3 @@
+module Listen
+ VERSION = '1.3.1'
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/listen.gemspec
b/backends/css/gems/sass-3.4.9/vendor/listen/listen.gemspec
new file mode 100644
index 0000000..2e0f17e
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/listen.gemspec
@@ -0,0 +1,28 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path('../lib', __FILE__)
+require 'listen/version'
+
+Gem::Specification.new do |s|
+ s.name = 'listen'
+ s.version = Listen::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ['Thibaud Guillaume-Gentil', 'Maher Sallam']
+ s.email = ['thibaud thibaud me', 'maher sallam me']
+ s.homepage = 'https://github.com/guard/listen'
+ s.license = 'MIT'
+ s.summary = 'Listen to file modifications'
+ s.description = 'The Listen gem listens to file modifications and notifies you about the changes. Works
everywhere!'
+
+ s.required_rubygems_version = '>= 1.3.6'
+ s.rubyforge_project = 'listen'
+
+ s.add_dependency 'rb-fsevent', '>= 0.9.3'
+ s.add_dependency 'rb-inotify', '>= 0.9'
+ s.add_dependency 'rb-kqueue', '>= 0.2'
+
+ s.add_development_dependency 'bundler'
+ s.add_development_dependency 'rspec'
+
+ s.files = Dir.glob('{lib}/**/*') + %w[CHANGELOG.md LICENSE README.md]
+ s.require_path = 'lib'
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapter_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapter_spec.rb
new file mode 100644
index 0000000..241c2a6
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapter_spec.rb
@@ -0,0 +1,149 @@
+require 'spec_helper'
+
+describe Listen::Adapter do
+ subject { described_class.new('dir') }
+
+ describe '#initialize' do
+ it 'sets the latency to the default one' do
+ subject.latency.should eq described_class::DEFAULT_LATENCY
+ end
+
+ it 'accepts a single directory to watch' do
+ described_class.new('dir').directories = %w{dir}
+ end
+
+ it 'accepts multiple directories to watch' do
+ described_class.new(%w{dir1 dir2}).directories.should eq %w{dir1 dir2}
+ end
+ end
+
+ describe ".select_and_initialize" do
+ before do
+ Listen::Adapter::OPTIMIZED_ADAPTERS.each do |adapter|
+ Listen::Adapters.const_get(adapter).stub(:usable_and_works?) { false }
+ end
+ end
+
+ context "with force_adapter option" do
+ it "returns an instance of the given adapter class" do
+ expected_adapter = Object.new
+ adapter_class = double('adapter_class')
+ options = {:force_adapter => adapter_class}
+
+ adapter_class.should_receive(:load_dependent_adapter)
+ adapter_class.should_receive(:new).with('dir', options) { expected_adapter }
+
+ adapter = described_class.select_and_initialize('dir', options)
+ adapter.should be(expected_adapter)
+ end
+ end
+
+ context "with no specific adapter usable" do
+ it "returns Listen::Adapters::Polling instance" do
+ Kernel.stub(:warn)
+ Listen::Adapters::Polling.should_receive(:new).with('dir', {})
+ described_class.select_and_initialize('dir')
+ end
+
+ it 'warns with the default polling fallback message' do
+ Kernel.should_receive(:warn).with(/#{Listen::Adapter::POLLING_FALLBACK_MESSAGE}/)
+ described_class.select_and_initialize('dir')
+ end
+
+ context "with custom polling_fallback_message option" do
+ it "warns with the custom polling fallback message" do
+ Kernel.should_receive(:warn).with(/custom/)
+ described_class.select_and_initialize('dir', :polling_fallback_message => 'custom')
+ end
+ end
+
+ context "with polling_fallback_message to false" do
+ it "doesn't warn with a polling fallback message" do
+ Kernel.should_not_receive(:warn)
+ described_class.select_and_initialize('dir', :polling_fallback_message => false)
+ end
+ end
+ end
+
+ Listen::Adapter::OPTIMIZED_ADAPTERS.each do |adapter|
+ adapter_class = Listen::Adapters.const_get(adapter)
+
+ context "on #{adapter}" do
+ before { adapter_class.stub(:usable_and_works?) { true } }
+
+ it "uses Listen::Adapters::#{adapter}" do
+ adapter_class.should_receive(:new).with('dir', {})
+ described_class.select_and_initialize('dir')
+ end
+
+ context 'when the use of the polling adapter is forced' do
+ it 'uses Listen::Adapters::Polling' do
+ Listen::Adapters::Polling.should_receive(:new).with('dir', {})
+ described_class.select_and_initialize('dir', :force_polling => true)
+ end
+ end
+ end
+ end
+ end
+
+ describe '.load_dependend_adapter' do
+ after(:each) { described_class.instance_variable_set('@loaded', nil) }
+
+ it 'returns true (success) even if the adapter_gem has already been required' do
+ described_class.stub(:adapter_gem => 'already_loaded_gem')
+ described_class.stub(:require => false)
+
+ described_class.load_dependent_adapter.should be_true
+ end
+
+ it 'returns false (failure) if the adapter_gem cannot be required' do
+ described_class.stub(:adapter_gem => 'unloadable_gem')
+ described_class.stub(:require) do
+ raise LoadError.new('no such file to load -- unloadable_gem')
+ end
+
+ described_class.load_dependent_adapter.should be_false
+ end
+ end
+
+ Listen::Adapter::OPTIMIZED_ADAPTERS.each do |adapter|
+ adapter_class = Listen::Adapters.const_get(adapter)
+ if adapter_class.usable?
+ describe '.usable_and_works?' do
+ it 'checks if the adapter is usable' do
+ adapter_class.stub(:works?)
+ adapter_class.should_receive(:usable?)
+ adapter_class.usable_and_works?('dir')
+ end
+
+ context 'with one directory' do
+ it 'tests if that directory actually work' do
+ fixtures do |path|
+ adapter_class.should_receive(:works?).with(path, anything).and_return(true)
+ adapter_class.usable_and_works?(path)
+ end
+ end
+ end
+
+ context 'with multiple directories' do
+ it 'tests if each directory passed does actually work' do
+ fixtures(3) do |path1, path2, path3|
+ adapter_class.should_receive(:works?).exactly(3).times.with do |path, options|
+ [path1, path2, path3].include? path
+ end.and_return(true)
+ adapter_class.usable_and_works?([path1, path2, path3])
+ end
+ end
+ end
+ end
+
+ describe '.works?' do
+ it 'does work' do
+ fixtures do |path|
+ adapter_class.works?(path).should be_true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/bsd_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/bsd_spec.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/bsd_spec.rb
rename to backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/bsd_spec.rb
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/darwin_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/darwin_spec.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/darwin_spec.rb
rename to backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/darwin_spec.rb
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/linux_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/linux_spec.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/linux_spec.rb
rename to backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/linux_spec.rb
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/polling_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/polling_spec.rb
new file mode 100644
index 0000000..33b98b3
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/polling_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe Listen::Adapters::Polling do
+ subject { described_class.new('dir') }
+
+ it_should_behave_like 'a filesystem adapter'
+
+ describe '#initialize' do
+ it 'sets the latency to the default polling one' do
+ subject.latency.should eq Listen::Adapters::DEFAULT_POLLING_LATENCY
+ end
+ end
+
+ describe '#poll' do
+ let(:listener) { double(Listen::Listener) }
+ let(:callback) { lambda { |changed_directories, options| @called = true;
listener.on_change(changed_directories, options) } }
+
+ after { subject.stop }
+
+ context 'with one directory to watch' do
+ subject { Listen::Adapters::Polling.new('dir', {}, &callback) }
+
+ it 'calls listener.on_change' do
+ listener.should_receive(:on_change).at_least(1).times.with(['dir'], :recursive => true)
+ subject.start
+ subject.wait_for_callback
+ end
+
+ it 'calls listener.on_change continuously' do
+ subject.latency = 0.001
+ listener.should_receive(:on_change).at_least(10).times.with(['dir'], :recursive => true)
+ subject.start
+ 10.times { subject.wait_for_callback }
+ end
+
+ it "doesn't call listener.on_change if paused" do
+ subject.paused = true
+ subject.start
+ subject.wait_for_callback
+ @called.should be_nil
+ end
+ end
+
+ context 'with multiple directories to watch' do
+ subject { Listen::Adapters::Polling.new(%w{dir1 dir2}, {}, &callback) }
+
+ it 'calls listener.on_change' do
+ listener.should_receive(:on_change).at_least(1).times.with(%w{dir1 dir2}, :recursive => true)
+ subject.start
+ subject.wait_for_callback
+ end
+
+ it 'calls listener.on_change continuously' do
+ subject.latency = 0.001
+ listener.should_receive(:on_change).at_least(10).times.with(%w{dir1 dir2}, :recursive => true)
+ subject.start
+ 10.times { subject.wait_for_callback }
+ end
+
+ it "doesn't call listener.on_change if paused" do
+ subject.paused = true
+ subject.start
+ subject.wait_for_callback
+ @called.should be_nil
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/windows_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/windows_spec.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/windows_spec.rb
rename to backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/adapters/windows_spec.rb
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/directory_record_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/directory_record_spec.rb
new file mode 100644
index 0000000..e3b7964
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/directory_record_spec.rb
@@ -0,0 +1,1250 @@
+# encoding: UTF-8
+require 'spec_helper'
+
+describe Listen::DirectoryRecord do
+ let(:base_directory) { File.dirname(__FILE__) }
+
+ subject { described_class.new(base_directory) }
+
+ describe '.generate_default_ignoring_patterns' do
+ it 'creates regexp patterns from the default ignored directories and extensions' do
+ described_class.generate_default_ignoring_patterns.should include(
+ %r{^(?:\.rbx|\.bundle|\.git|\.svn|bundle|log|tmp|vendor)/},
+ %r{(?:\.DS_Store)$}
+ )
+ end
+
+ it 'memoizes the generated results' do
+ described_class.generate_default_ignoring_patterns.should equal
described_class.generate_default_ignoring_patterns
+ end
+ end
+
+ describe '#initialize' do
+ it 'sets the base directory' do
+ subject.directory.should eq base_directory
+ end
+
+ it 'sets the base directory expanded' do
+ cd File.dirname(base_directory)
+ subject = described_class.new(File.basename(base_directory))
+ subject.directory.should eq base_directory
+ end
+
+ it 'sets the default ignoring patterns' do
+ subject.ignoring_patterns.should =~ described_class.generate_default_ignoring_patterns
+ end
+
+ it 'sets the default filtering patterns' do
+ subject.filtering_patterns.should eq []
+ end
+
+ it 'raises an error when the passed path does not exist' do
+ expect { described_class.new('no way I exist') }.to raise_error(ArgumentError)
+ end
+
+ it 'raises an error when the passed path is not a directory' do
+ expect { described_class.new(__FILE__) }.to raise_error(ArgumentError)
+ end
+ end
+
+ describe '#ignore' do
+ it 'adds the passed paths to the list of ignored paths in the record' do
+ subject.ignore(%r{^\.old/}, %r{\.pid$}, nil)
+ subject.ignoring_patterns.should include(%r{^\.old/}, %r{\.pid$})
+ subject.ignoring_patterns.should_not include(nil)
+ end
+ end
+
+ describe '#ignore!' do
+ it 'replace the ignored paths in the record' do
+ subject.ignore!(%r{^\.old/}, %r{\.pid$}, nil)
+ subject.ignoring_patterns.should eq [%r{^\.old/}, %r{\.pid$}]
+ end
+ end
+
+ describe '#filter' do
+ it 'adds the passed regexps to the list of filters that determine the stored paths' do
+ subject.filter(%r{\.(?:jpe?g|gif|png)}, %r{\.(?:mp3|ogg|a3c)}, nil)
+ subject.filtering_patterns.should include(%r{\.(?:jpe?g|gif|png)}, %r{\.(?:mp3|ogg|a3c)})
+ subject.filtering_patterns.should_not include(nil)
+ end
+ end
+
+ describe '#filter!' do
+ it 'replaces the passed regexps in the list of filters that determine the stored paths' do
+ subject.filter!(%r{\.(?:jpe?g|gif|png)}, %r{\.(?:mp3|ogg|a3c)})
+ subject.filtering_patterns.should have(2).regexps
+ subject.filtering_patterns.should =~ [%r{\.(?:mp3|ogg|a3c)}, %r{\.(?:jpe?g|gif|png)}]
+ end
+ end
+
+ describe '#ignored?' do
+ before { subject.stub(:relative_to_base) { |path| path } }
+
+ it 'tests paths relative to the base directory' do
+ subject.should_receive(:relative_to_base).with('file.txt')
+ subject.ignored?('file.txt')
+ end
+
+ it 'returns true when the passed path is a default ignored path' do
+ subject.ignored?('tmp/some_process.pid').should be_true
+ subject.ignored?('dir/.DS_Store').should be_true
+ subject.ignored?('.git/config').should be_true
+ end
+
+ it 'returns false when the passed path is not a default ignored path' do
+ subject.ignored?('nested/tmp/some_process.pid').should be_false
+ subject.ignored?('nested/.git').should be_false
+ subject.ignored?('dir/.DS_Store/file').should be_false
+ subject.ignored?('file.git').should be_false
+ end
+
+ it 'returns true when the passed path is ignored' do
+ subject.ignore(%r{\.pid$})
+ subject.ignored?('dir/some_process.pid').should be_true
+ end
+
+ it 'returns false when the passed path is not ignored' do
+ subject.ignore(%r{\.pid$})
+ subject.ignored?('dir/some_file.txt').should be_false
+ end
+ end
+
+ describe '#filtered?' do
+ before { subject.stub(:relative_to_base) { |path| path } }
+
+ context 'when no filtering patterns are set' do
+ it 'returns true for any path' do
+ subject.filtered?('file.txt').should be_true
+ end
+ end
+
+ context 'when filtering patterns are set' do
+ before { subject.filter(%r{\.(?:jpe?g|gif|png)}) }
+
+ it 'tests paths relative to the base directory' do
+ subject.should_receive(:relative_to_base).with('file.txt')
+ subject.filtered?('file.txt')
+ end
+
+ it 'returns true when the passed path is filtered' do
+ subject.filter(%r{\.(?:jpe?g|gif|png)})
+ subject.filtered?('dir/picture.jpeg').should be_true
+ end
+
+ it 'returns false when the passed path is not filtered' do
+ subject.filter(%r{\.(?:jpe?g|gif|png)})
+ subject.filtered?('dir/song.mp3').should be_false
+ end
+ end
+ end
+
+ describe '#build' do
+ it 'stores all files' do
+ fixtures do |path|
+ touch 'file.rb'
+ mkdir 'a_directory'
+ touch 'a_directory/file.txt'
+
+ record = described_class.new(path)
+ record.build
+
+ record.paths[path]['file.rb'].type.should eq 'File'
+ record.paths[path]['a_directory'].type.should eq 'Dir'
+ record.paths["#{path}/a_directory"]['file.txt'].type.should eq 'File'
+ end
+ end
+
+ context 'with ignored path set' do
+ it 'does not store ignored directory or its childs' do
+ fixtures do |path|
+ mkdir 'ignored_directory'
+ mkdir 'ignored_directory/child_directory'
+ touch 'ignored_directory/file.txt'
+
+ record = described_class.new(path)
+ record.ignore %r{^ignored_directory/}
+ record.build
+
+ record.paths[path]['/a_ignored_directory'].should be_nil
+ record.paths["#{path}/a_ignored_directory"]['child_directory'].should be_nil
+ record.paths["#{path}/a_ignored_directory"]['file.txt'].should be_nil
+ end
+ end
+
+ it 'does not store ignored files' do
+ fixtures do |path|
+ touch 'ignored_file.rb'
+
+ record = described_class.new(path)
+ record.ignore %r{^ignored_file.rb$}
+ record.build
+
+ record.paths[path]['ignored_file.rb'].should be_nil
+ end
+ end
+ end
+
+ context 'with filters set' do
+ it 'only stores filterd files' do
+ fixtures do |path|
+ touch 'file.rb'
+ touch 'file.zip'
+ mkdir 'a_directory'
+ touch 'a_directory/file.txt'
+ touch 'a_directory/file.rb'
+
+ record = described_class.new(path)
+ record.filter(/\.txt$/, /.*\.zip/)
+ record.build
+
+ record.paths[path]['file.rb'].should be_nil
+ record.paths[path]['file.zip'].type.should eq 'File'
+ record.paths[path]['a_directory'].type.should eq 'Dir'
+ record.paths["#{path}/a_directory"]['file.txt'].type.should eq 'File'
+ record.paths["#{path}/a_directory"]['file.rb'].should be_nil
+ end
+ end
+ end
+ end
+
+ describe '#relative_to_base' do
+ it 'removes the path of the base-directory from the passed path' do
+ path = 'dir/to/app/file.rb'
+ subject.relative_to_base(File.join(base_directory, path)).should eq path
+ end
+
+ it 'returns nil when the passed path is not inside the base-directory' do
+ subject.relative_to_base('/tmp/some_random_path').should be_nil
+ end
+
+ it 'works with non UTF-8 paths' do
+ path = "tmp/\xE4\xE4"
+ subject.relative_to_base(File.join(base_directory, path))
+ end
+
+ context 'when there are utf-8 chars in base directory' do
+ before do
+ fixtures do |path|
+ mkdir '目录'
+ @dir = described_class.new(path + '/目录')
+ @dir.build
+ end
+ end
+
+ it 'works' do
+ path = File.join @dir.directory, 'tmp/file.rb'
+ @dir.relative_to_base path
+ end
+ end
+
+ context 'when containing regexp characters in the base directory' do
+ before do
+ fixtures do |path|
+ mkdir 'a_directory$'
+ @dir = described_class.new(path + '/a_directory$')
+ @dir.build
+ end
+ end
+
+ it 'removes the path of the base-directory from the passed path' do
+ path = 'dir/to/app/file.rb'
+ @dir.relative_to_base(File.join(@dir.directory, path)).should eq path
+ end
+
+ it 'returns nil when the passed path is not inside the base-directory' do
+ @dir.relative_to_base('/tmp/some_random_path').should be_nil
+ end
+ end
+ end
+
+ describe '#fetch_changes' do
+ context 'with single file changes' do
+ context 'when a file is created' do
+ it 'detects the added file' do
+ fixtures do |path|
+ modified, added, removed = changes(path) do
+ touch 'new_file.rb'
+ end
+
+ added.should =~ %w(new_file.rb)
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ it 'stores the added file in the record' do
+ fixtures do |path|
+ changes(path) do
+ @record.paths.should be_empty
+
+ touch 'new_file.rb'
+ end
+
+ @record.paths[path]['new_file.rb'].should_not be_nil
+ end
+ end
+
+ context 'given a new created directory' do
+ it 'detects the added file' do
+ fixtures do |path|
+ modified, added, removed = changes(path) do
+ mkdir 'a_directory'
+ touch 'a_directory/new_file.rb'
+ end
+
+ added.should =~ %w(a_directory/new_file.rb)
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ it 'stores the added directory and file in the record' do
+ fixtures do |path|
+ changes(path) do
+ @record.paths.should be_empty
+
+ mkdir 'a_directory'
+ touch 'a_directory/new_file.rb'
+ end
+
+ @record.paths[path]['a_directory'].should_not be_nil
+ @record.paths["#{path}/a_directory"]['new_file.rb'].should_not be_nil
+ end
+ end
+ end
+
+ context 'given an existing directory' do
+ context 'with recursive option set to true' do
+ it 'detects the added file' do
+ fixtures do |path|
+ mkdir 'a_directory'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ touch 'a_directory/new_file.rb'
+ end
+
+ added.should =~ %w(a_directory/new_file.rb)
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ context 'with an ignored directory' do
+ it "doesn't detect the added file" do
+ fixtures do |path|
+ mkdir 'ignored_directory'
+
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive =>
true) do
+ touch 'ignored_directory/new_file.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ it "doesn't detect the added file when it's asked to fetch the changes of the ignored
directory"do
+ fixtures do |path|
+ mkdir 'ignored_directory'
+
+ modified, added, removed = changes(path, :paths => ["#{path}/ignored_directory"], :ignore
=> %r{^ignored_directory/}, :recursive => true) do
+ touch 'ignored_directory/new_file.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+
+ context 'with recursive option set to false' do
+ it "doesn't detect deeply-nested added files" do
+ fixtures do |path|
+ mkdir 'a_directory'
+
+ modified, added, removed = changes(path, :recursive => false) do
+ touch 'a_directory/new_file.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects the added file' do
+ fixtures do |path|
+ mkdir_p 'a_directory/subdirectory'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ touch 'a_directory/subdirectory/new_file.rb'
+ end
+
+ added.should =~ %w(a_directory/subdirectory/new_file.rb)
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ context 'with an ignored directory' do
+ it "doesn't detect added files in neither the directory nor the subdirectory" do
+ fixtures do |path|
+ mkdir_p 'ignored_directory/subdirectory'
+
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive =>
true) do
+ touch 'ignored_directory/new_file.rb'
+ touch 'ignored_directory/subdirectory/new_file.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a file is modified' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ touch 'existing_file.txt'
+
+ modified, added, removed = changes(path) do
+ small_time_difference
+ touch 'existing_file.txt'
+ end
+
+ added.should be_empty
+ modified.should =~ %w(existing_file.txt)
+ removed.should be_empty
+ end
+ end
+
+ context 'during the same second at which we are checking for changes' do
+ before { ensure_same_second }
+
+ # The following test can only be run on systems that report
+ # modification times in milliseconds.
+ it 'always detects the modified file the first time', :if =>
described_class::HIGH_PRECISION_SUPPORTED do
+ fixtures do |path|
+ touch 'existing_file.txt'
+
+ modified, added, removed = changes(path) do
+ small_time_difference
+ touch 'existing_file.txt'
+ end
+
+ added.should be_empty
+ modified.should =~ %w(existing_file.txt)
+ removed.should be_empty
+ end
+ end
+
+ context 'when a file is created and then checked for modifications at the same second - #27' do
+ # This issue was the result of checking a file for content changes when
+ # the mtime and the checking time are the same. In this case there
+ # is no checksum saved, so the file was reported as being changed.
+ it 'does not report any changes' do
+ fixtures do |path|
+ touch 'a_file.rb'
+
+ modified, added, removed = changes(path)
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+
+ it 'detects the modified file the second time if the content have changed' do
+ fixtures do |path|
+ touch 'existing_file.txt'
+
+ # Set sha1 path checksum
+ changes(path) do
+ touch 'existing_file.txt'
+ end
+
+ changes(path) do
+ small_time_difference
+ touch 'existing_file.txt'
+ end
+
+ modified, added, removed = changes(path, :use_last_record => true) do
+ open('existing_file.txt', 'w') { |f| f.write('foo') }
+ end
+
+ added.should be_empty
+ modified.should =~ %w(existing_file.txt)
+ removed.should be_empty
+ end
+ end
+
+ it "doesn't checksum the contents of local sockets (#85)", :unless => windows? do
+ require 'socket'
+ fixtures do |path|
+ Digest::SHA1.should_not_receive(:file)
+ socket_path = File.join(path, "unix_domain_socket")
+ server = UNIXServer.new(socket_path)
+ modified, added, removed = changes(path) do
+ t = Thread.new do
+ client = UNIXSocket.new(socket_path)
+ client.write("foo")
+ end
+ t.join
+ end
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ it "doesn't detects the modified file the second time if just touched - #62", :unless =>
described_class::HIGH_PRECISION_SUPPORTED do
+ fixtures do |path|
+ touch 'existing_file.txt'
+
+ # Set sha1 path checksum
+ changes(path) do
+ touch 'existing_file.txt'
+ end
+
+ changes(path, :use_last_record => true) do
+ small_time_difference
+ open('existing_file.txt', 'w') { |f| f.write('foo') }
+ end
+
+ modified, added, removed = changes(path, :use_last_record => true) do
+ touch 'existing_file.txt'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ it "adds the path in the paths checksums if just touched - #62" do
+ fixtures do |path|
+ touch 'existing_file.txt'
+
+ changes(path) do
+ small_time_difference
+ touch 'existing_file.txt'
+ end
+
+ @record.sha1_checksums["#{path}/existing_file.txt"].should_not be_nil
+ end
+ end
+
+ it "deletes the path from the paths checksums" do
+ fixtures do |path|
+ touch 'unnecessary.txt'
+
+ changes(path) do
+ @record.sha1_checksums["#{path}/unnecessary.txt"] = 'foo'
+
+ rm 'unnecessary.txt'
+ end
+
+ @record.sha1_checksums["#{path}/unnecessary.txt"].should be_nil
+ end
+ end
+ end
+
+ context 'given a hidden file' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ touch '.hidden'
+
+ modified, added, removed = changes(path) do
+ small_time_difference
+ touch '.hidden'
+ end
+
+ added.should be_empty
+ modified.should =~ %w(.hidden)
+ removed.should be_empty
+ end
+ end
+ end
+
+ context 'given a file mode change' do
+ it 'does not detect the mode change' do
+ fixtures do |path|
+ touch 'run.rb'
+
+ modified, added, removed = changes(path) do
+ small_time_difference
+ chmod 0777, 'run.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+
+ context 'given an existing directory' do
+ context 'with recursive option set to true' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/existing_file.txt'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ small_time_difference
+ touch 'a_directory/existing_file.txt'
+ end
+
+ added.should be_empty
+ modified.should =~ %w(a_directory/existing_file.txt)
+ removed.should be_empty
+ end
+ end
+ end
+
+ context 'with recursive option set to false' do
+ it "doesn't detects the modified file" do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/existing_file.txt'
+
+ modified, added, removed = changes(path, :recursive => false) do
+ small_time_difference
+ touch 'a_directory/existing_file.txt'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ mkdir_p 'a_directory/subdirectory'
+ touch 'a_directory/subdirectory/existing_file.txt'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ small_time_difference
+ touch 'a_directory/subdirectory/existing_file.txt'
+ end
+
+ added.should be_empty
+ modified.should =~ %w(a_directory/subdirectory/existing_file.txt)
+ removed.should be_empty
+ end
+ end
+
+ context 'with an ignored subdirectory' do
+ it "doesn't detect the modified files in neither the directory nor the subdirectory" do
+ fixtures do |path|
+ mkdir_p 'ignored_directory/subdirectory'
+ touch 'ignored_directory/existing_file.txt'
+ touch 'ignored_directory/subdirectory/existing_file.txt'
+
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive =>
true) do
+ touch 'ignored_directory/existing_file.txt'
+ touch 'ignored_directory/subdirectory/existing_file.txt'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a file is moved' do
+ it 'detects the file movement' do
+ fixtures do |path|
+ touch 'move_me.txt'
+
+ modified, added, removed = changes(path) do
+ mv 'move_me.txt', 'new_name.txt'
+ end
+
+ added.should =~ %w(new_name.txt)
+ modified.should be_empty
+ removed.should =~ %w(move_me.txt)
+ end
+ end
+
+ context 'given an existing directory' do
+ context 'with recursive option set to true' do
+ it 'detects the file movement into the directory' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'move_me.txt'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ mv 'move_me.txt', 'a_directory/move_me.txt'
+ end
+
+ added.should =~ %w(a_directory/move_me.txt)
+ modified.should be_empty
+ removed.should =~ %w(move_me.txt)
+ end
+ end
+
+ it 'detects a file movement out of the directory' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/move_me.txt'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
+ end
+
+ added.should =~ %w(i_am_here.txt)
+ modified.should be_empty
+ removed.should =~ %w(a_directory/move_me.txt)
+ end
+ end
+
+ it 'detects a file movement between two directories' do
+ fixtures do |path|
+ mkdir 'from_directory'
+ touch 'from_directory/move_me.txt'
+ mkdir 'to_directory'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
+ end
+
+ added.should =~ %w(to_directory/move_me.txt)
+ modified.should be_empty
+ removed.should =~ %w(from_directory/move_me.txt)
+ end
+ end
+ end
+
+ context 'with recursive option set to false' do
+ it "doesn't detect the file movement into the directory" do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'move_me.txt'
+
+ modified, added, removed = changes(path, :recursive => false) do
+ mv 'move_me.txt', 'a_directory/move_me.txt'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should =~ %w(move_me.txt)
+ end
+ end
+
+ it "doesn't detect a file movement out of the directory" do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/move_me.txt'
+
+ modified, added, removed = changes(path, :recursive => false) do
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
+ end
+
+ added.should =~ %w(i_am_here.txt)
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ it "doesn't detect a file movement between two directories" do
+ fixtures do |path|
+ mkdir 'from_directory'
+ touch 'from_directory/move_me.txt'
+ mkdir 'to_directory'
+
+ modified, added, removed = changes(path, :recursive => false) do
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects a file movement between two subdirectories' do
+ fixtures do |path|
+ mkdir_p 'a_directory/subdirectory'
+ mkdir_p 'b_directory/subdirectory'
+ touch 'a_directory/subdirectory/move_me.txt'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ mv 'a_directory/subdirectory/move_me.txt', 'b_directory/subdirectory'
+ end
+
+ added.should =~ %w(b_directory/subdirectory/move_me.txt)
+ modified.should be_empty
+ removed.should =~ %w(a_directory/subdirectory/move_me.txt)
+ end
+ end
+
+ context 'with an ignored subdirectory' do
+ it "doesn't detect the file movement between subdirectories" do
+ fixtures do |path|
+ mkdir_p 'a_ignored_directory/subdirectory'
+ mkdir_p 'b_ignored_directory/subdirectory'
+ touch 'a_ignored_directory/subdirectory/move_me.txt'
+
+ modified, added, removed = changes(path, :ignore => %r{^(?:a|b)_ignored_directory/},
:recursive => true) do
+ mv 'a_ignored_directory/subdirectory/move_me.txt', 'b_ignored_directory/subdirectory'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+
+ context 'with all paths passed as params' do
+ it 'detects the file movement into the directory' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'move_me.txt'
+
+ modified, added, removed = changes(path, :recursive => false, :paths => [path,
"#{path}/a_directory"]) do
+ mv 'move_me.txt', 'a_directory/move_me.txt'
+ end
+
+ added.should =~ %w(a_directory/move_me.txt)
+ modified.should be_empty
+ removed.should =~ %w(move_me.txt)
+ end
+ end
+
+ it 'detects a file moved outside of a directory' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/move_me.txt'
+
+ modified, added, removed = changes(path, :recursive => false, :paths => [path,
"#{path}/a_directory"]) do
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
+ end
+
+ added.should =~ %w(i_am_here.txt)
+ modified.should be_empty
+ removed.should =~ %w(a_directory/move_me.txt)
+ end
+ end
+
+ it 'detects a file movement between two directories' do
+ fixtures do |path|
+ mkdir 'from_directory'
+ touch 'from_directory/move_me.txt'
+ mkdir 'to_directory'
+
+ modified, added, removed = changes(path, :recursive => false, :paths => [path,
"#{path}/from_directory", "#{path}/to_directory"]) do
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
+ end
+
+ added.should =~ %w(to_directory/move_me.txt)
+ modified.should be_empty
+ removed.should =~ %w(from_directory/move_me.txt)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a file is deleted' do
+ it 'detects the file removal' do
+ fixtures do |path|
+ touch 'unnecessary.txt'
+
+ modified, added, removed = changes(path) do
+ rm 'unnecessary.txt'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should =~ %w(unnecessary.txt)
+ end
+ end
+
+ it "deletes the file from the record" do
+ fixtures do |path|
+ touch 'unnecessary.txt'
+
+ changes(path) do
+ @record.paths[path]['unnecessary.txt'].should_not be_nil
+
+ rm 'unnecessary.txt'
+ end
+
+ @record.paths[path]['unnecessary.txt'].should be_nil
+ end
+ end
+
+ it "deletes the path from the paths checksums" do
+ fixtures do |path|
+ touch 'unnecessary.txt'
+
+ changes(path) do
+ @record.sha1_checksums["#{path}/unnecessary.txt"] = 'foo'
+
+ rm 'unnecessary.txt'
+ end
+
+ @record.sha1_checksums["#{path}/unnecessary.txt"].should be_nil
+ end
+ end
+
+ context 'given an existing directory' do
+ context 'with recursive option set to true' do
+ it 'detects the file removal' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/do_not_use.rb'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ rm 'a_directory/do_not_use.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should =~ %w(a_directory/do_not_use.rb)
+ end
+ end
+ end
+
+ context 'with recursive option set to false' do
+ it "doesn't detect the file removal" do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/do_not_use.rb'
+
+ modified, added, removed = changes(path, :recursive => false) do
+ rm 'a_directory/do_not_use.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects the file removal in subdirectories' do
+ fixtures do |path|
+ mkdir_p 'a_directory/subdirectory'
+ touch 'a_directory/subdirectory/do_not_use.rb'
+
+ modified, added, removed = changes(path, :recursive => true) do
+ rm 'a_directory/subdirectory/do_not_use.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should =~ %w(a_directory/subdirectory/do_not_use.rb)
+ end
+ end
+
+ context 'with an ignored subdirectory' do
+ it "doesn't detect files removals in neither the directory nor its subdirectories" do
+ fixtures do |path|
+ mkdir_p 'ignored_directory/subdirectory'
+ touch 'ignored_directory/do_not_use.rb'
+ touch 'ignored_directory/subdirectory/do_not_use.rb'
+
+ modified, added, removed = changes(path, :ignore => %r{^ignored_directory/}, :recursive =>
true) do
+ rm 'ignored_directory/do_not_use.rb'
+ rm 'ignored_directory/subdirectory/do_not_use.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+ end
+ end
+
+ context 'multiple file operations' do
+ it 'detects the added files' do
+ fixtures do |path|
+ modified, added, removed = changes(path) do
+ touch 'a_file.rb'
+ touch 'b_file.rb'
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+ end
+
+ added.should =~ %w(a_file.rb b_file.rb a_directory/a_file.rb a_directory/b_file.rb)
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+
+ it 'detects the modified files' do
+ fixtures do |path|
+ touch 'a_file.rb'
+ touch 'b_file.rb'
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ modified, added, removed = changes(path) do
+ small_time_difference
+ touch 'b_file.rb'
+ touch 'a_directory/a_file.rb'
+ end
+
+ added.should be_empty
+ modified.should =~ %w(b_file.rb a_directory/a_file.rb)
+ removed.should be_empty
+ end
+ end
+
+ it 'detects the removed files' do
+ fixtures do |path|
+ touch 'a_file.rb'
+ touch 'b_file.rb'
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ modified, added, removed = changes(path) do
+ rm 'b_file.rb'
+ rm 'a_directory/a_file.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should =~ %w(b_file.rb a_directory/a_file.rb)
+ end
+ end
+ end
+
+ context 'single directory operations' do
+ it 'detects a moved directory' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ mkdir 'a_directory/nested'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+ touch 'a_directory/nested/c_file.rb'
+
+ modified, added, removed = changes(path) do
+ mv 'a_directory', 'renamed'
+ end
+
+ added.should =~ %w(renamed/a_file.rb renamed/b_file.rb renamed/nested/c_file.rb)
+ modified.should be_empty
+ removed.should =~ %w(a_directory/a_file.rb a_directory/b_file.rb a_directory/nested/c_file.rb)
+ end
+ end
+
+ it 'detects a removed directory' do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ modified, added, removed = changes(path) do
+ rm_rf 'a_directory'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should =~ %w(a_directory/a_file.rb a_directory/b_file.rb)
+ end
+ end
+
+ it "deletes the directory from the record" do
+ fixtures do |path|
+ mkdir 'a_directory'
+ touch 'a_directory/file.rb'
+
+ changes(path) do
+ @record.paths.should have(2).paths
+ @record.paths[path]['a_directory'].should_not be_nil
+ @record.paths["#{path}/a_directory"]['file.rb'].should_not be_nil
+
+ rm_rf 'a_directory'
+ end
+
+ @record.paths.should have(1).paths
+ @record.paths[path]['a_directory'].should be_nil
+ @record.paths["#{path}/a_directory"]['file.rb'].should be_nil
+ end
+ end
+
+ context 'with nested paths' do
+ it 'detects removals without crashing - #18' do
+ fixtures do |path|
+ mkdir_p 'a_directory/subdirectory'
+ touch 'a_directory/subdirectory/do_not_use.rb'
+
+ modified, added, removed = changes(path) do
+ rm_r 'a_directory'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should =~ %w(a_directory/subdirectory/do_not_use.rb)
+ end
+ end
+ end
+ end
+
+ context 'with a path outside the directory for which a record is made' do
+ it "skips that path and doesn't check for changes" do
+ fixtures do |path|
+ modified, added, removed = changes(path, :paths => ['some/where/outside']) do
+ @record.should_not_receive(:detect_additions)
+ @record.should_not_receive(:detect_modifications_and_removals)
+
+ touch 'new_file.rb'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+
+ context 'with the relative_paths option set to false' do
+ it 'returns full paths in the changes hash' do
+ fixtures do |path|
+ touch 'a_file.rb'
+ touch 'b_file.rb'
+
+ modified, added, removed = changes(path, :relative_paths => false) do
+ small_time_difference
+ rm 'a_file.rb'
+ touch 'b_file.rb'
+ touch 'c_file.rb'
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ end
+
+ added.should =~ ["#{path}/c_file.rb", "#{path}/a_directory/a_file.rb"]
+ modified.should =~ ["#{path}/b_file.rb"]
+ removed.should =~ ["#{path}/a_file.rb"]
+ end
+ end
+ end
+
+ context 'within a directory containing unreadble paths - #32' do
+ it 'detects changes more than a second apart' do
+ fixtures do |path|
+ touch 'unreadable_file.txt'
+ chmod 000, 'unreadable_file.txt'
+
+ modified, added, removed = changes(path) do
+ small_time_difference
+ touch 'unreadable_file.txt'
+ end
+
+ added.should be_empty
+ modified.should =~ %w(unreadable_file.txt)
+ removed.should be_empty
+ end
+ end
+
+ context 'with multiple changes within the same second' do
+ before { ensure_same_second }
+
+ it 'does not detect changes even if content changes', :unless =>
described_class::HIGH_PRECISION_SUPPORTED do
+ fixtures do |path|
+ touch 'unreadable_file.txt'
+
+ modified, added, removed = changes(path) do
+ open('unreadable_file.txt', 'w') { |f| f.write('foo') }
+ chmod 000, 'unreadable_file.txt'
+ end
+
+ added.should be_empty
+ modified.should be_empty
+ removed.should be_empty
+ end
+ end
+ end
+ end
+
+ context 'within a directory containing a removed file - #39' do
+ it 'does not raise an exception when hashing a removed file' do
+
+ # simulate a race condition where the file is removed after the
+ # change event is tracked, but before the hash is calculated
+ Digest::SHA1.should_receive(:file).twice.and_raise(Errno::ENOENT)
+
+ fixtures do |path|
+ lambda {
+ touch 'removed_file.txt'
+ changes(path) { touch 'removed_file.txt' }
+ }.should_not raise_error
+ end
+ end
+ end
+
+ context 'within a directory containing a unix domain socket file', :unless => windows? do
+ it 'does not raise an exception when hashing a unix domain socket file' do
+ fixtures do |path|
+ require 'socket'
+ UNIXServer.new('unix_domain_socket.sock')
+ lambda { changes(path){} }.should_not raise_error
+ end
+ end
+ end
+
+ context 'with symlinks', :unless => windows? do
+ it 'looks at symlinks not their targets' do
+ fixtures do |path|
+ touch 'target'
+ symlink 'target', 'symlink'
+
+ record = described_class.new(path)
+ record.build
+
+ sleep 1
+ touch 'target'
+
+ record.fetch_changes([path], :relative_paths => true)[:modified].should == ['target']
+ end
+ end
+
+ it 'handles broken symlinks' do
+ fixtures do |path|
+ symlink 'target', 'symlink'
+
+ record = described_class.new(path)
+ record.build
+
+ sleep 1
+ rm 'symlink'
+ symlink 'new-target', 'symlink'
+ record.fetch_changes([path], :relative_paths => true)
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/listener_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/listener_spec.rb
new file mode 100644
index 0000000..58991a8
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/listener_spec.rb
@@ -0,0 +1,258 @@
+require 'spec_helper'
+
+describe Listen::Listener do
+ let(:adapter) { double(Listen::Adapter, :start => true).as_null_object }
+ let(:watched_directories) { [File.dirname(__FILE__), File.expand_path('../..', __FILE__)] }
+
+ before do
+ Listen::Adapter.stub(:select_and_initialize) { adapter }
+ # Don't build a record of the files inside the base directory.
+ Listen::DirectoryRecord.any_instance.stub(:build)
+ Kernel.stub(:warn)
+ end
+ subject { described_class.new(watched_directories) }
+
+ it_should_behave_like 'a listener to changes on a file-system'
+
+ describe '#initialize' do
+ context 'listening to a single directory' do
+ let(:watched_directory) { File.dirname(__FILE__) }
+ let(:watched_directories) { nil }
+ subject { described_class.new(watched_directory) }
+
+ it 'sets the directories' do
+ subject.directories.should eq [watched_directory]
+ end
+
+ context 'with no options' do
+ it 'sets the option for using relative paths in the callback to false' do
+ subject.instance_variable_get(:@use_relative_paths).should eq false
+ end
+ end
+
+ context 'with :relative_paths => false' do
+ it 'sets the option for using relative paths in the callback to false' do
+ listener = described_class.new(watched_directory, :relative_paths => false)
+ listener.instance_variable_get(:@use_relative_paths).should eq false
+ end
+ end
+
+ context 'with :relative_paths => true' do
+ it 'sets the option for using relative paths in the callback to true' do
+ listener = described_class.new(watched_directory, :relative_paths => true)
+ listener.instance_variable_get(:@use_relative_paths).should eq true
+ end
+ end
+ end
+
+ context 'listening to multiple directories' do
+ subject { described_class.new(watched_directories) }
+
+ it 'sets the directories' do
+ subject.directories.should eq watched_directories
+ end
+
+ context 'with no options' do
+ it 'sets the option for using relative paths in the callback to false' do
+ subject.instance_variable_get(:@use_relative_paths).should eq false
+ end
+ end
+
+ context 'with :relative_paths => false' do
+ it 'sets the option for using relative paths in the callback to false' do
+ listener = described_class.new(watched_directories, :relative_paths => false)
+ listener.instance_variable_get(:@use_relative_paths).should eq false
+ end
+ end
+
+ context 'with :relative_paths => true' do
+ it 'warns' do
+ Kernel.should_receive(:warn).with("[Listen warning]:
#{Listen::Listener::RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE}")
+ listener = described_class.new(watched_directories, :relative_paths => true)
+ end
+
+ it 'sets the option for using relative paths in the callback to false' do
+ listener = described_class.new(watched_directories, :relative_paths => true)
+ listener.instance_variable_get(:@use_relative_paths).should eq false
+ end
+ end
+ end
+
+ context 'with a directory' do
+ let(:watched_directory) { File.dirname(__FILE__) }
+ it 'converts the passed path into an absolute path - #21' do
+ described_class.new(File.join(watched_directory, '..')).directories.should eq
[File.expand_path('..', watched_directory)]
+ end
+ end
+
+ context 'with custom options' do
+ let(:watched_directory) { File.dirname(__FILE__) }
+ let(:adapter_class) { double('adapter class') }
+
+ let(:options) do
+ {
+ :ignore => /\.ssh/, :filter => [/.*\.rb/, /.*\.md/],
+ :latency => 0.5, :force_polling => true, :relative_paths => true,
+ :force_adapter => adapter_class
+ }
+ end
+ subject { described_class.new(watched_directory, options) }
+
+ it 'passes the custom ignored paths to the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.ignoring_patterns.should include /\.ssh/
+ end
+ end
+
+ it 'passes the custom filters to the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.filtering_patterns.should =~ [/.*\.rb/,/.*\.md/]
+ end
+ end
+
+ it 'sets adapter_options' do
+ subject.instance_variable_get(:@adapter_options).should eq(:latency => 0.5, :force_polling => true,
:force_adapter => adapter_class)
+ end
+ end
+ end
+
+ describe '#start' do
+ it 'selects and initializes an adapter' do
+ Listen::Adapter.should_receive(:select_and_initialize).with(watched_directories, {}) { adapter }
+ subject.start
+ end
+
+ it 'builds the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.should_receive(:build)
+ end
+ subject.start
+ end
+ end
+
+ context 'with a started listener' do
+ before do
+ subject.stub(:initialize_adapter) { adapter }
+ subject.start
+ end
+
+ describe '#unpause' do
+ it 'rebuilds the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.should_receive(:build)
+ end
+ subject.unpause
+ end
+ end
+ end
+
+ describe '#ignore'do
+ it 'delegates the work to the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.should_receive(:ignore).with 'some_directory'
+ end
+ subject.ignore 'some_directory'
+ end
+ end
+
+ describe '#ignore!'do
+ it 'delegates the work to the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.should_receive(:ignore!).with 'some_directory'
+ end
+ subject.ignore! 'some_directory'
+ end
+ end
+
+ describe '#filter' do
+ it 'delegates the work to the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.should_receive(:filter).with /\.txt$/
+ end
+ subject.filter /\.txt$/
+ end
+ end
+
+ describe '#filter!' do
+ it 'delegates the work to the directory record' do
+ subject.directories_records.each do |directory_record|
+ directory_record.should_receive(:filter!).with /\.txt$/
+ end
+ subject.filter! /\.txt$/
+ end
+ end
+
+ describe '#on_change' do
+ let(:directories) { %w{dir1 dir2 dir3} }
+ let(:changes) { {:modified => [], :added => [], :removed => []} }
+ let(:callback) { Proc.new { @called = true } }
+
+ before do
+ @called = false
+ subject.stub(:fetch_records_changes => changes)
+ end
+
+ it 'fetches the changes of all directories records' do
+ subject.unstub(:fetch_records_changes)
+
+ subject.directories_records.each do |record|
+ record.should_receive(:fetch_changes).with(directories, an_instance_of(Hash)).and_return(changes)
+ end
+ subject.on_change(directories)
+ end
+
+ context "with a callback raising an exception" do
+ let(:callback) { Proc.new { raise 'foo' } }
+
+ before do
+ subject.change(&callback)
+ subject.stub(:fetch_records_changes => { :modified => ['foo'], :added => [], :removed => [] } )
+ end
+
+ it "stops the adapter and warns" do
+ Kernel.should_receive(:warn).with("[Listen warning]: Change block raise an execption: foo")
+ Kernel.should_receive(:warn).with(/^Backtrace:.*/)
+ subject.on_change(directories)
+ end
+
+ end
+
+ context 'with no changes to report' do
+ if RUBY_VERSION[/^1.8/]
+ it 'does not run the callback' do
+ subject.change(&callback)
+ subject.on_change(directories)
+ @called.should be_false
+ end
+ else
+ it 'does not run the callback' do
+ callback.should_not_receive(:call)
+ subject.change(&callback)
+ subject.on_change(directories)
+ end
+ end
+ end
+
+ context 'with changes to report' do
+ let(:changes) do
+ {
+ :modified => %w{path1}, :added => [], :removed => %w{path2}
+ }
+ end
+
+ if RUBY_VERSION[/^1.8/]
+ it 'runs the callback passing it the changes' do
+ subject.change(&callback)
+ subject.on_change(directories)
+ @called.should be_true
+ end
+ else
+ it 'runs the callback passing it the changes' do
+ callback.should_receive(:call).with(changes[:modified], changes[:added], changes[:removed])
+ subject.change(&callback)
+ subject.on_change(directories)
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/turnstile_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/turnstile_spec.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/turnstile_spec.rb
rename to backends/css/gems/sass-3.4.9/vendor/listen/spec/listen/turnstile_spec.rb
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen_spec.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen_spec.rb
new file mode 100644
index 0000000..a5be1f5
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/listen_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe Listen do
+ describe '#to' do
+ let(:listener) { double(Listen::Listener) }
+ let(:listener_class) { Listen::Listener }
+ before { listener_class.stub(:new => listener) }
+
+ context 'with one path to listen to' do
+ context 'without options' do
+ it 'creates an instance of Listener' do
+ listener_class.should_receive(:new).with('/path')
+ described_class.to('/path')
+ end
+ end
+
+ context 'with options' do
+ it 'creates an instance of Listener with the passed params' do
+ listener_class.should_receive(:new).with('/path', :filter => '**/*')
+ described_class.to('/path', :filter => '**/*')
+ end
+ end
+
+ context 'without a block' do
+ it 'returns the listener' do
+ described_class.to('/path', :filter => '**/*').should eq listener
+ end
+ end
+
+ context 'with a block' do
+ it 'starts the listener after creating it' do
+ listener.should_receive(:start)
+ described_class.to('/path', :filter => '**/*') { |modified, added, removed| }
+ end
+ end
+ end
+
+ context 'with multiple paths to listen to' do
+ context 'without options' do
+ it 'creates an instance of Listener' do
+ listener_class.should_receive(:new).with('path1', 'path2')
+ described_class.to('path1', 'path2')
+ end
+ end
+
+ context 'with options' do
+ it 'creates an instance of Listener with the passed params' do
+ listener_class.should_receive(:new).with('path1', 'path2', :filter => '**/*')
+ described_class.to('path1', 'path2', :filter => '**/*')
+ end
+ end
+
+ context 'without a block' do
+ it 'returns a Listener instance created with the passed params' do
+ described_class.to('path1', 'path2', :filter => '**/*').should eq listener
+ end
+ end
+
+ context 'with a block' do
+ it 'starts a Listener instance after creating it with the passed params' do
+ listener.should_receive(:start)
+ described_class.to('path1', 'path2', :filter => '**/*') { |modified, added, removed| }
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/spec_helper.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/spec_helper.rb
new file mode 100644
index 0000000..78590ba
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/spec_helper.rb
@@ -0,0 +1,25 @@
+require 'rubygems'
+require 'coveralls'
+Coveralls.wear!
+
+require 'listen'
+
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
+
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+ config.color_enabled = true
+ config.order = :random
+ config.filter_run :focus => true
+ config.treat_symbols_as_metadata_keys_with_true_values = true
+ config.run_all_when_everything_filtered = true
+ config.filter_run_excluding :broken => true
+ config.fail_fast = true
+end
+
+def test_latency
+ 0.1
+end
+
+# Crash loud in tests!
+Thread.abort_on_exception = true
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/adapter_helper.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/adapter_helper.rb
new file mode 100644
index 0000000..5efed33
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/adapter_helper.rb
@@ -0,0 +1,666 @@
+# Adapter watch
+#
+# @param [Listen::Listener] listener the adapter listener
+# @param [String] path the path to watch
+#
+def watch(listener, expected_changes, *paths)
+ sleep 0.05 # allow file/creation to be done (!)
+
+ callback = lambda do |changed_directories, options|
+ @called = true
+ listener.on_change(changed_directories)
+ end
+ @adapter = Listen::Adapter.select_and_initialize(paths, { :latency => test_latency }, &callback)
+ @adapter.stub(:start_poller) { nil }
+
+ forced_stop = false
+ prevent_deadlock = lambda do
+ sleep(10)
+ puts 'Forcing stop'
+ @adapter.stop
+ forced_stop = true
+ end
+
+ @adapter.start
+
+ yield
+
+ t = Thread.new(&prevent_deadlock)
+ @adapter.wait_for_changes(expected_changes)
+
+ unless forced_stop
+ Thread.kill(t)
+ @adapter.report_changes
+ end
+ensure
+ unless forced_stop
+ Thread.kill(t) if t
+ @adapter.stop
+ end
+end
+
+shared_examples_for 'a filesystem adapter' do
+ subject { described_class.new(File.dirname(__FILE__), &Proc.new {}) }
+
+ describe '#start' do
+ before { Kernel.stub(:warn) }
+ after { subject.stop }
+
+ it 'do not block the current thread after starting the workers' do
+ @called = false
+ t = Thread.new { subject.start; @called = true }
+ sleep(test_latency * 3)
+ Thread.kill(t) if t
+ @called.should be_true
+ end
+
+ context 'with the blocking hash option set to false' do
+ subject { described_class.new(File.dirname(__FILE__), { :blocking => false }, &Proc.new {}) }
+
+ it 'does not block the current thread after starting the workers' do
+ @called = false
+ t = Thread.new { subject.start; @called = true }
+ sleep(test_latency * 3)
+ Thread.kill(t) if t
+ @called.should be_true
+ end
+ end
+ end
+
+ describe '#start!' do
+ before { Kernel.stub(:warn) }
+ after { subject.stop }
+
+ it 'blocks the current thread after starting the workers' do
+ @called = false
+ t = Thread.new { subject.start!; @called = true }
+ sleep(test_latency * 3)
+ Thread.kill(t) if t
+ @called.should be_false
+ end
+
+ context 'with the blocking hash option set to false' do
+ subject { described_class.new(File.dirname(__FILE__), { :blocking => true }, &Proc.new {}) }
+
+ it 'blocks the current thread after starting the workers' do
+ @called = false
+ t = Thread.new { subject.start!; @called = true }
+ sleep(test_latency * 3)
+ Thread.kill(t) if t
+ @called.should be_false
+ end
+ end
+ end
+
+ describe '#started?' do
+ context 'with a new adapter' do
+ it 'returns false' do
+ subject.should_not be_started
+ end
+ end
+
+ context 'with a stopped adapter' do
+ before { subject.start; subject.stop }
+
+ it 'returns false' do
+ subject.should_not be_started
+ end
+ end
+
+ context 'with a started adapter' do
+ before { subject.start }
+ after { subject.stop }
+
+ it 'returns true' do
+ subject.should be_started
+ end
+ end
+ end
+end
+
+shared_examples_for 'an adapter that call properly listener#on_change' do |*args|
+ options = (args.first && args.first.is_a?(Hash)) ? args.first : {}
+ let(:listener) { double(Listen::Listener) }
+ before { described_class.stub(:works?) { true } }
+
+ context 'single file operations' do
+ context 'when a file is created' do
+ it 'detects the added file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ watch(listener, 1, path) do
+ touch 'new_file.rb'
+ end
+ end
+ end
+
+ context 'given a symlink', :unless => windows? do
+ it 'detects the added file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'new_file.rb'
+
+ watch(listener, 1, path) do
+ ln_s 'new_file.rb', 'new_file_symlink.rb'
+ end
+ end
+ end
+ end
+
+ context 'given a new created directory' do
+ it 'detects the added file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
+ end
+
+ watch(listener, 2, path) do
+ mkdir 'a_directory'
+ # Needed for INotify, because of :recursive rb-inotify custom flag?
+ sleep 0.05
+ touch 'a_directory/new_file.rb'
+ end
+ end
+ end
+ end
+
+ context 'given an existing directory' do
+ it 'detects the added file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory")
+ end
+
+ mkdir 'a_directory'
+
+ watch(listener, 1, path) do
+ touch 'a_directory/new_file.rb'
+ end
+ end
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects the added file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory")
+ end
+
+ mkdir_p 'a_directory/subdirectory'
+
+ watch(listener, 1, path) do
+ touch 'a_directory/subdirectory/new_file.rb'
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a file is modified' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'existing_file.txt'
+
+ watch(listener, 1, path) do
+ touch 'existing_file.txt'
+ end
+ end
+ end
+
+ context 'given a symlink', :unless => windows? do
+ it 'detects the modified file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'existing_file.rb'
+ ln_s 'existing_file.rb', 'existing_file_symlink.rb'
+
+ watch(listener, 1, path) do
+ touch 'existing_file.rb'
+ end
+ end
+ end
+ end
+
+ context 'given a hidden file' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch '.hidden'
+
+ watch(listener, 1, path) do
+ touch '.hidden'
+ end
+ end
+ end
+ end
+
+ context 'given a file mode change', :unless => windows? do
+ it 'does not detect the mode change' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'run.rb'
+
+ watch(listener, 1, path) do
+ chmod 0777, 'run.rb'
+ end
+ end
+ end
+ end
+
+ context 'given an existing directory' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory")
+ end
+
+ mkdir 'a_directory'
+ touch 'a_directory/existing_file.txt'
+
+ watch(listener, 1, path) do
+ touch 'a_directory/existing_file.txt'
+ end
+ end
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects the modified file' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory")
+ end
+
+ mkdir_p 'a_directory/subdirectory'
+ touch 'a_directory/subdirectory/existing_file.txt'
+
+ watch(listener, 1, path) do
+ touch 'a_directory/subdirectory/new_file.rb'
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a file is moved' do
+ it 'detects the file move' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'move_me.txt'
+
+ watch(listener, 1, path) do
+ mv 'move_me.txt', 'new_name.txt'
+ end
+ end
+ end
+
+ context 'given a symlink', :unless => windows? do
+ it 'detects the file move' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'move_me.rb'
+ ln_s 'move_me.rb', 'move_me_symlink.rb'
+
+ watch(listener, 1, path) do
+ mv 'move_me_symlink.rb', 'new_symlink.rb'
+ end
+ end
+ end
+ end
+
+ context 'given an existing directory' do
+ it 'detects the file move into the directory' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
+ end
+
+ mkdir 'a_directory'
+ touch 'move_me.txt'
+
+ watch(listener, 2, path) do
+ mv 'move_me.txt', 'a_directory/move_me.txt'
+ end
+ end
+ end
+
+ it 'detects a file move out of the directory' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
+ end
+
+ mkdir 'a_directory'
+ touch 'a_directory/move_me.txt'
+
+ watch(listener, 2, path) do
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
+ end
+ end
+ end
+
+ it 'detects a file move between two directories' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/from_directory", "#{path}/to_directory")
+ end
+
+ mkdir 'from_directory'
+ touch 'from_directory/move_me.txt'
+ mkdir 'to_directory'
+
+ watch(listener, 2, path) do
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
+ end
+ end
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects files movements between subdirectories' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory", "#{path}/b_directory/subdirectory")
+ end
+
+ mkdir_p 'a_directory/subdirectory'
+ mkdir_p 'b_directory/subdirectory'
+ touch 'a_directory/subdirectory/move_me.txt'
+
+ watch(listener, 2, path) do
+ mv 'a_directory/subdirectory/move_me.txt', 'b_directory/subdirectory'
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a file is deleted' do
+ it 'detects the file removal' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'unnecessary.txt'
+
+ watch(listener, 1, path) do
+ rm 'unnecessary.txt'
+ end
+ end
+ end
+
+ context 'given a symlink', :unless => windows? do
+ it 'detects the file removal' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ touch 'unnecessary.rb'
+ ln_s 'unnecessary.rb', 'unnecessary_symlink.rb'
+
+ watch(listener, 1, path) do
+ rm 'unnecessary_symlink.rb'
+ end
+ end
+ end
+ end
+
+ context 'given an existing directory' do
+ it 'detects the file removal' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory")
+ end
+
+ mkdir 'a_directory'
+ touch 'a_directory/do_not_use.rb'
+
+ watch(listener, 1, path) do
+ rm 'a_directory/do_not_use.rb'
+ end
+ end
+ end
+ end
+
+ context 'given a directory with subdirectories' do
+ it 'detects the file removal' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory")
+ end
+
+ mkdir_p 'a_directory/subdirectory'
+ touch 'a_directory/subdirectory/do_not_use.rb'
+
+ watch(listener, 1, path) do
+ rm 'a_directory/subdirectory/do_not_use.rb'
+ end
+ end
+ end
+ end
+ end
+ end
+
+ context 'multiple file operations' do
+ it 'detects the added files' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
+ end
+
+ watch(listener, 2, path) do
+ touch 'a_file.rb'
+ touch 'b_file.rb'
+ mkdir 'a_directory'
+ # Needed for INotify, because of :recursive rb-inotify custom flag?
+ # Also needed for the osx adapter
+ sleep 0.05
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+ end
+ end
+ end
+
+ it 'detects the modified files' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
+ end
+
+ touch 'a_file.rb'
+ touch 'b_file.rb'
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ watch(listener, 2, path) do
+ touch 'b_file.rb'
+ touch 'a_directory/a_file.rb'
+ end
+ end
+ end
+
+ it 'detects the removed files' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
+ end
+
+ touch 'a_file.rb'
+ touch 'b_file.rb'
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ watch(listener, 2, path) do
+ rm 'b_file.rb'
+ rm 'a_directory/a_file.rb'
+ end
+ end
+ end
+ end
+
+ context 'single directory operations' do
+ it 'detects a moved directory' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
+
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ watch(listener, 1, path) do
+ mv 'a_directory', 'renamed'
+ end
+ end
+ end
+
+ it 'detects a removed directory' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
+ end
+
+ mkdir 'a_directory'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ watch(listener, 2, path) do
+ rm_rf 'a_directory'
+ end
+ end
+ end
+ end
+
+ context "paused adapter" do
+ context 'when a file is created' do
+ it "doesn't detects the added file" do
+ fixtures do |path|
+ watch(listener, 1, path) do # The expected changes param is set to one!
+ @adapter.paused = true
+ touch 'new_file.rb'
+ end
+ @called.should be_nil
+ end
+ end
+ end
+ end
+
+ context "when multiple directories are listened to" do
+ context 'when files are added to one of multiple directories' do
+ it 'detects added files' do
+ fixtures(2) do |path1, path2|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path2)
+ end
+
+ watch(listener, 1, path1, path2) do
+ touch "#{path2}/new_file.rb"
+ end
+ end
+ end
+ end
+
+ context 'when files are added to multiple directories' do
+ it 'detects added files' do
+ fixtures(2) do |path1, path2|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path1, path2)
+ end
+
+ watch(listener, 2, path1, path2) do
+ touch "#{path1}/new_file.rb"
+ touch "#{path2}/new_file.rb"
+ end
+ end
+ end
+ end
+
+ context 'given a new and an existing directory on multiple directories' do
+ it 'detects the added file' do
+ fixtures(2) do |path1, path2|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path2, "#{path1}/a_directory", "#{path2}/b_directory")
+ end
+
+ mkdir "#{path1}/a_directory"
+
+ watch(listener, 3, path1, path2) do
+ mkdir "#{path2}/b_directory"
+ # Needed for INotify
+ sleep 0.05
+ touch "#{path1}/a_directory/new_file.rb"
+ touch "#{path2}/b_directory/new_file.rb"
+ end
+ end
+ end
+ end
+
+ context 'when a file is moved between the multiple watched directories' do
+ it 'detects the movements of the file' do
+ fixtures(3) do |path1, path2, path3|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path1}/from_directory", path2, "#{path3}/to_directory")
+ end
+
+ mkdir "#{path1}/from_directory"
+ touch "#{path1}/from_directory/move_me.txt"
+ mkdir "#{path3}/to_directory"
+
+ watch(listener, 3, path1, path2, path3) do
+ mv "#{path1}/from_directory/move_me.txt", "#{path2}/move_me.txt"
+ mv "#{path2}/move_me.txt", "#{path3}/to_directory/move_me.txt"
+ end
+ end
+ end
+ end
+
+ context 'when files are deleted from the multiple watched directories' do
+ it 'detects the files removal' do
+ fixtures(2) do |path1, path2|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path1, path2)
+ end
+
+ touch "#{path1}/unnecessary.txt"
+ touch "#{path2}/unnecessary.txt"
+
+ watch(listener, 2, path1, path2) do
+ rm "#{path1}/unnecessary.txt"
+ rm "#{path2}/unnecessary.txt"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/directory_record_helper.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/directory_record_helper.rb
new file mode 100644
index 0000000..71bdf60
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/directory_record_helper.rb
@@ -0,0 +1,57 @@
+# Prepares a record for the test and fetches changes
+# afterwards.
+#
+# @param [String] root_path the path to watch
+# @param [Hash] options
+# @option options [Array<string>] :paths optional paths fetch changes for
+# @option options [Boolean] :use_last_record allow the use of an already
+# created record, handy for ordered tests.
+#
+# @return [Array, Array, Array] the changes
+#
+def changes(root_path, options = {})
+ unless @record || options[:use_last_record]
+ @record = Listen::DirectoryRecord.new(root_path)
+ @record.filter(options.delete(:filter)) if options[:filter]
+ @record.ignore(options.delete(:ignore)) if options[:ignore]
+
+ # Build the record after adding the filtering and ignoring patterns
+ @record.build
+ end
+
+ yield if block_given?
+
+ paths = options.delete(:paths) || [root_path]
+ options[:recursive] = true if options[:recursive].nil?
+
+ changes = @record.fetch_changes(paths, { :relative_paths => true }.merge(options))
+
+ [changes[:modified], changes[:added], changes[:removed]]
+end
+
+# Generates a small time difference before performing a time sensitive
+# task (like comparing mtimes of files).
+#
+# @note Modification time for files only includes the milliseconds on Linux with MRI > 1.9.2
+# and platform that support it (OS X 10.8 not included),
+# that's why we generate a difference that's greater than 1 second.
+#
+def small_time_difference
+ t = Time.now
+ diff = t.to_f - t.to_i
+
+ sleep(1.05 - diff)
+end
+
+# Ensures that the test runs at almost the same second at which
+# changes are being checked.
+#
+def ensure_same_second
+ t = Time.now
+ diff = t.to_f - t.to_i
+
+ # We are not at the end of a second
+ if diff >= (1 - Listen::Adapter::DEFAULT_LATENCY)
+ sleep(1.05 - diff)
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/fixtures_helper.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/fixtures_helper.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/spec/support/fixtures_helper.rb
rename to backends/css/gems/sass-3.4.9/vendor/listen/spec/support/fixtures_helper.rb
diff --git a/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/listeners_helper.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/listeners_helper.rb
new file mode 100644
index 0000000..62992ac
--- /dev/null
+++ b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/listeners_helper.rb
@@ -0,0 +1,179 @@
+shared_examples_for 'a listener to changes on a file-system' do
+ describe '#start' do
+ before do
+ subject.stub(:initialize_adapter) { adapter }
+ end
+
+ it 'starts the adapter' do
+ adapter.should_receive(:start)
+ subject.start
+ end
+
+ context 'with the blocking deprecated param set to true' do
+ it 'displays a deprecation notice' do
+ Kernel.should_receive(:warn).with(/#{Listen::Listener::BLOCKING_PARAMETER_DEPRECATION_MESSAGE}/)
+ subject.start(true)
+ end
+ end
+
+ context 'with the blocking deprecated param set to false' do
+ it 'displays a deprecation notice' do
+ Kernel.should_receive(:warn).with(/#{Listen::Listener::BLOCKING_PARAMETER_DEPRECATION_MESSAGE}/)
+ subject.start(false)
+ end
+ end
+ end
+
+ describe '#start!' do
+ before do
+ subject.stub(:initialize_adapter) { adapter }
+ end
+
+ it 'starts the adapter' do
+ adapter.should_receive(:start!)
+ subject.start!
+ end
+
+ it 'passes the blocking param to the adapter' do
+ adapter.should_receive(:start!)
+ subject.start!
+ end
+ end
+
+ context 'with a started listener' do
+ before do
+ subject.start
+ end
+
+ describe '#stop' do
+ it "stops adapter" do
+ adapter.should_receive(:stop)
+ subject.stop
+ end
+ end
+
+ describe '#pause' do
+ it 'sets adapter.paused to true' do
+ adapter.should_receive(:pause)
+ subject.pause
+ end
+
+ it 'returns the same listener to allow chaining' do
+ subject.pause.should equal subject
+ end
+ end
+
+ describe '#unpause' do
+ it 'sets adapter.paused to false' do
+ adapter.should_receive(:unpause)
+ subject.unpause
+ end
+
+ it 'returns the same listener to allow chaining' do
+ subject.unpause.should equal subject
+ end
+ end
+
+ describe '#paused?' do
+ it 'returns false when there is no adapter' do
+ subject.instance_variable_set(:@adapter, nil)
+ subject.should_not be_paused
+ end
+
+ it 'returns true when adapter is paused' do
+ adapter.should_receive(:paused?) { true }
+ subject.should be_paused
+ end
+
+ it 'returns false when adapter is not paused' do
+ adapter.should_receive(:paused?) { false }
+ subject.should_not be_paused
+ end
+ end
+ end
+
+ describe '#change' do
+ it 'sets the callback block' do
+ callback = lambda { |modified, added, removed| }
+ subject.change(&callback)
+ subject.instance_variable_get(:@block).should eq callback
+ end
+
+ it 'returns the same listener to allow chaining' do
+ subject.change(&Proc.new{}).should equal subject
+ end
+ end
+
+ describe '#ignore' do
+ it 'returns the same listener to allow chaining' do
+ subject.ignore('some_directory').should equal subject
+ end
+ end
+
+ describe '#ignore!' do
+ it 'returns the same listener to allow chaining' do
+ subject.ignore!('some_directory').should equal subject
+ end
+ end
+
+ describe '#filter' do
+ it 'returns the same listener to allow chaining' do
+ subject.filter(/\.txt$/).should equal subject
+ end
+ end
+
+ describe '#filter!' do
+ it 'returns the same listener to allow chaining' do
+ subject.filter!(/\.txt$/).should equal subject
+ end
+ end
+
+ describe '#latency' do
+ it 'sets the latency to @adapter_options' do
+ subject.latency(0.7)
+ subject.instance_variable_get(:@adapter_options).should eq(:latency => 0.7)
+ end
+
+ it 'returns the same listener to allow chaining' do
+ subject.latency(0.7).should equal subject
+ end
+ end
+
+ describe '#force_polling' do
+ it 'sets force_polling to @adapter_options' do
+ subject.force_polling(false)
+ subject.instance_variable_get(:@adapter_options).should eq(:force_polling => false)
+ end
+
+ it 'returns the same listener to allow chaining' do
+ subject.force_polling(true).should equal subject
+ end
+ end
+
+ describe '#relative_paths' do
+ it 'sets the relative paths option for paths in the callback' do
+ subject.relative_paths(true)
+ subject.instance_variable_get(:@use_relative_paths).should be_true
+ end
+
+ it 'returns the same listener to allow chaining' do
+ subject.relative_paths(true).should equal subject
+ end
+ end
+
+ describe '#polling_fallback_message' do
+ it 'sets custom polling fallback message to @adapter_options' do
+ subject.polling_fallback_message('custom message')
+ subject.instance_variable_get(:@adapter_options).should eq(:polling_fallback_message => 'custom
message')
+ end
+
+ it 'sets polling fallback message to false in @adapter_options' do
+ subject.polling_fallback_message(false)
+ subject.instance_variable_get(:@adapter_options).should eq(:polling_fallback_message => false)
+ end
+
+ it 'returns the same listener to allow chaining' do
+ subject.polling_fallback_message('custom message').should equal subject
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/platform_helper.rb
b/backends/css/gems/sass-3.4.9/vendor/listen/spec/support/platform_helper.rb
similarity index 100%
rename from backends/css/gems/sass-3.2.12/vendor/listen/spec/support/platform_helper.rb
rename to backends/css/gems/sass-3.4.9/vendor/listen/spec/support/platform_helper.rb
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]