[gnome-code-assistance] [backends/css] New css backend
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-code-assistance] [backends/css] New css backend
- Date: Wed, 13 Nov 2013 18:53:20 +0000 (UTC)
commit aa70ac4a9e66a3f5c0262fb448c200872162a9db
Author: Jesse van den Kieboom <jessevdk gmail com>
Date: Wed Nov 13 19:52:29 2013 +0100
[backends/css] New css backend
backends/Makefile.am | 4 +
backends/css/Makefile.am | 18 +
backends/css/app.rb | 67 +
backends/css/css.in | 6 +
backends/css/deps.mf | 393 +++
backends/css/gems/sass-3.2.12/.yardopts | 11 +
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/REVISION | 1 +
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 +
.../css/gems/sass-3.2.12/extra/update_watch.rb | 13 +
backends/css/gems/sass-3.2.12/init.rb | 18 +
backends/css/gems/sass-3.2.12/lib/sass.rb | 95 +
.../css/gems/sass-3.2.12/lib/sass/cache_stores.rb | 15 +
.../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 +++++
.../css/gems/sass-3.2.12/lib/sass/importers.rb | 22 +
.../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 +
.../gems/sass-3.2.12/lib/sass/plugin/generic.rb | 15 +
.../css/gems/sass-3.2.12/lib/sass/plugin/merb.rb | 48 +
.../css/gems/sass-3.2.12/lib/sass/plugin/rack.rb | 60 +
.../css/gems/sass-3.2.12/lib/sass/plugin/rails.rb | 47 +
.../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/root.rb | 7 +
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 +
backends/css/gems/sass-3.2.12/lib/sass/scss.rb | 16 +
.../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 +
.../gems/sass-3.2.12/lib/sass/scss/script_lexer.rb | 15 +
.../sass-3.2.12/lib/sass/scss/script_parser.rb | 25 +
.../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 +
.../gems/sass-3.2.12/lib/sass/tree/content_node.rb | 9 +
.../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/rails/init.rb | 1 +
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 +++
.../gems/sass-3.2.12/test/sass/data/hsl-rgb.txt | 319 ++
.../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 +++++++++
.../test_staleness_check_across_importers.css | 1 +
.../test_staleness_check_across_importers.scss | 1 +
.../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 +
.../gems/sass-3.2.12/test/sass/mock_importer.rb | 49 +
.../sass-3.2.12/test/sass/more_results/more1.css | 9 +
.../sass/more_results/more1_with_line_comments.css | 26 +
.../test/sass/more_results/more_import.css | 29 +
.../test/sass/more_templates/_more_partial.sass | 2 +
.../test/sass/more_templates/more1.sass | 23 +
.../test/sass/more_templates/more_import.sass | 11 +
.../css/gems/sass-3.2.12/test/sass/plugin_test.rb | 550 ++++
.../css/gems/sass-3.2.12/test/sass/results/alt.css | 4 +
.../gems/sass-3.2.12/test/sass/results/basic.css | 9 +
.../test/sass/results/cached_import_option.css | 3 +
.../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/compressed.css | 1 +
.../sass-3.2.12/test/sass/results/expanded.css | 19 +
.../sass-3.2.12/test/sass/results/filename_fn.css | 3 +
.../css/gems/sass-3.2.12/test/sass/results/if.css | 3 +
.../gems/sass-3.2.12/test/sass/results/import.css | 31 +
.../test/sass/results/import_charset.css | 5 +
.../test/sass/results/import_charset_1_8.css | 5 +
.../test/sass/results/import_charset_ibm866.css | 5 +
.../test/sass/results/import_content.css | 1 +
.../sass-3.2.12/test/sass/results/line_numbers.css | 49 +
.../gems/sass-3.2.12/test/sass/results/mixins.css | 95 +
.../sass-3.2.12/test/sass/results/multiline.css | 24 +
.../gems/sass-3.2.12/test/sass/results/nested.css | 22 +
.../gems/sass-3.2.12/test/sass/results/options.css | 1 +
.../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/results/scss_importee.css | 2 +
.../results/subdir/nested_subdir/nested_subdir.css | 1 +
.../test/sass/results/subdir/subdir.css | 3 +
.../gems/sass-3.2.12/test/sass/results/units.css | 11 +
.../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 ++++++++++++
.../gems/sass-3.2.12/test/sass/scss/test_helper.rb | 37 +
.../templates/_cached_import_option_partial.scss | 1 +
.../test/sass/templates/_double_import_loop2.sass | 1 +
.../test/sass/templates/_filename_fn_import.scss | 11 +
.../sass/templates/_imported_charset_ibm866.sass | 4 +
.../sass/templates/_imported_charset_utf8.sass | 4 +
.../test/sass/templates/_imported_content.sass | 3 +
.../sass-3.2.12/test/sass/templates/_partial.sass | 2 +
.../templates/_same_name_different_partiality.scss | 1 +
.../gems/sass-3.2.12/test/sass/templates/alt.sass | 16 +
.../sass-3.2.12/test/sass/templates/basic.sass | 23 +
.../sass-3.2.12/test/sass/templates/bork1.sass | 2 +
.../sass-3.2.12/test/sass/templates/bork2.sass | 2 +
.../sass-3.2.12/test/sass/templates/bork3.sass | 2 +
.../sass-3.2.12/test/sass/templates/bork4.sass | 2 +
.../sass-3.2.12/test/sass/templates/bork5.sass | 3 +
.../test/sass/templates/cached_import_option.scss | 3 +
.../sass-3.2.12/test/sass/templates/compact.sass | 17 +
.../sass-3.2.12/test/sass/templates/complex.sass | 305 ++
.../test/sass/templates/compressed.sass | 15 +
.../test/sass/templates/double_import_loop1.sass | 1 +
.../sass-3.2.12/test/sass/templates/expanded.sass | 17 +
.../test/sass/templates/filename_fn.scss | 18 +
.../gems/sass-3.2.12/test/sass/templates/if.sass | 11 +
.../sass-3.2.12/test/sass/templates/import.sass | 12 +
.../test/sass/templates/import_charset.sass | 9 +
.../test/sass/templates/import_charset_1_8.sass | 6 +
.../test/sass/templates/import_charset_ibm866.sass | 11 +
.../test/sass/templates/import_content.sass | 4 +
.../sass-3.2.12/test/sass/templates/importee.less | 2 +
.../sass-3.2.12/test/sass/templates/importee.sass | 19 +
.../test/sass/templates/line_numbers.sass | 13 +
.../test/sass/templates/mixin_bork.sass | 5 +
.../sass-3.2.12/test/sass/templates/mixins.sass | 76 +
.../sass-3.2.12/test/sass/templates/multiline.sass | 20 +
.../sass-3.2.12/test/sass/templates/nested.sass | 25 +
.../test/sass/templates/nested_bork1.sass | 2 +
.../test/sass/templates/nested_bork2.sass | 2 +
.../test/sass/templates/nested_bork3.sass | 2 +
.../test/sass/templates/nested_bork4.sass | 2 +
.../test/sass/templates/nested_import.sass | 2 +
.../test/sass/templates/nested_mixin_bork.sass | 6 +
.../sass-3.2.12/test/sass/templates/options.sass | 2 +
.../test/sass/templates/parent_ref.sass | 25 +
.../sass/templates/same_name_different_ext.sass | 2 +
.../sass/templates/same_name_different_ext.scss | 1 +
.../templates/same_name_different_partiality.scss | 1 +
.../sass-3.2.12/test/sass/templates/script.sass | 101 +
.../test/sass/templates/scss_import.scss | 11 +
.../test/sass/templates/scss_importee.scss | 1 +
.../test/sass/templates/single_import_loop.sass | 1 +
.../subdir/nested_subdir/_nested_partial.sass | 2 +
.../subdir/nested_subdir/nested_subdir.sass | 3 +
.../test/sass/templates/subdir/subdir.sass | 6 +
.../sass-3.2.12/test/sass/templates/units.sass | 11 +
.../gems/sass-3.2.12/test/sass/templates/warn.sass | 3 +
.../test/sass/templates/warn_imported.sass | 4 +
.../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 ++
.../gems/sass-3.2.12/vendor/listen/CONTRIBUTING.md | 38 +
.../css/gems/sass-3.2.12/vendor/listen/Gemfile | 30 +
.../css/gems/sass-3.2.12/vendor/listen/Guardfile | 8 +
.../css/gems/sass-3.2.12/vendor/listen/LICENSE | 20 +
.../css/gems/sass-3.2.12/vendor/listen/README.md | 315 ++
.../css/gems/sass-3.2.12/vendor/listen/Rakefile | 47 +
.../css/gems/sass-3.2.12/vendor/listen/Vagrantfile | 96 +
.../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 ++
.../vendor/listen/spec/listen/adapters/bsd_spec.rb | 36 +
.../listen/spec/listen/adapters/darwin_spec.rb | 37 +
.../listen/spec/listen/adapters/linux_spec.rb | 47 +
.../listen/spec/listen/adapters/polling_spec.rb | 68 +
.../listen/spec/listen/adapters/windows_spec.rb | 30 +
.../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 ++
.../vendor/listen/spec/listen/turnstile_spec.rb | 56 +
.../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/fixtures_helper.rb | 29 +
.../vendor/listen/spec/support/listeners_helper.rb | 156 +
.../vendor/listen/spec/support/platform_helper.rb | 15 +
backends/css/mkdeps.py | 25 +
.../css/org.gnome.CodeAssist.v1.css.service.in | 3 +
configure.ac | 46 +
285 files changed, 39432 insertions(+), 0 deletions(-)
---
diff --git a/backends/Makefile.am b/backends/Makefile.am
index 5130d20..7fd7626 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -42,4 +42,8 @@ if BACKENDS_SH_ENABLE
include backends/sh/Makefile.am
endif
+if BACKENDS_CSS_ENABLE
+include backends/css/Makefile.am
+endif
+
GITIGNOREDEPS += backends/Makefile.am
diff --git a/backends/css/Makefile.am b/backends/css/Makefile.am
new file mode 100644
index 0000000..c0d3ce9
--- /dev/null
+++ b/backends/css/Makefile.am
@@ -0,0 +1,18 @@
+cssbackenddir = $(GCA_RBBACKENDS_DIR)/css
+cssbackend_DATA = \
+ backends/css/app.rb
+
+cssbackendexecdir = $(GCA_BACKENDS_EXEC_DIR)
+cssbackendexec_SCRIPTS = \
+ backends/css/css
+
+cssbackendservicedir = $(datadir)/dbus-1/services
+cssbackendservice_DATA = \
+ backends/css/org.gnome.CodeAssist.v1.css.service
+
+if RUBY_SASS
+else
+include backends/css/deps.mf
+endif
+
+GITIGNOREDEPS += backends/css/Makefile.am
diff --git a/backends/css/app.rb b/backends/css/app.rb
new file mode 100644
index 0000000..86747dc
--- /dev/null
+++ b/backends/css/app.rb
@@ -0,0 +1,67 @@
+# gnome code assistance ruby backend
+# Copyright (C) 2013 Jesse van den Kieboom <jessevdk gnome org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+require 'gnome/codeassistance/transport'
+
+oursass = File.join(File.dirname(__FILE__), 'gems', 'sass-3.2.12', 'init.rb')
+
+if FileTest.exist?(oursass)
+ require oursass
+else
+ require 'sass'
+end
+
+
+module Gnome::CodeAssistance
+ module Css
+ class Service < Service
+ @@language = 'css'
+
+ def parse(doc, options)
+ doc.diagnostics = []
+
+ f = File.new(doc.data_path, 'r')
+
+ begin
+ parser = Sass::SCSS::CssParser.new(f.read(), doc.path)
+ parser.parse()
+ rescue Sass::SyntaxError => e
+ doc.diagnostics = [make_diagnostic(e)]
+ end
+
+ f.close
+ end
+
+ def make_diagnostic(e)
+ loc = SourceLocation.new(e.sass_line, 0)
+ Diagnostic.new(Diagnostic::Severity::ERROR, [], [loc.to_range], e.to_s)
+ end
+ end
+
+ class Document < Document
+ include Services::Diagnostics
+ end
+
+ class Application
+ def self.run()
+ Transport.new(Service, Document).run()
+ end
+ end
+ end
+end
+
+# ex:ts=4:et:
diff --git a/backends/css/css.in b/backends/css/css.in
new file mode 100644
index 0000000..5d79a29
--- /dev/null
+++ b/backends/css/css.in
@@ -0,0 +1,6 @@
+#!/usr/bin/env @RUBY_BASE@
+
+$:.unshift('@GCA_RBBACKENDS_ROOT_EX@')
+
+require 'gnome/codeassistance/css/app'
+Gnome::CodeAssistance::Css::Application.run()
diff --git a/backends/css/deps.mf b/backends/css/deps.mf
new file mode 100644
index 0000000..255e6e8
--- /dev/null
+++ b/backends/css/deps.mf
@@ -0,0 +1,393 @@
+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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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
+
diff --git a/backends/css/gems/sass-3.2.12/.yardopts b/backends/css/gems/sass-3.2.12/.yardopts
new file mode 100644
index 0000000..1d14215
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/.yardopts
@@ -0,0 +1,11 @@
+--readme README.md
+--markup markdown
+--markup-provider maruku
+--default-return ""
+--title "Sass Documentation"
+--query 'object.type != :classvariable'
+--query 'object.type != :constant || @api && @api.text == "public"'
+--hide-void-return
+--protected
+--no-private
+--no-highlight
diff --git a/backends/css/gems/sass-3.2.12/CONTRIBUTING b/backends/css/gems/sass-3.2.12/CONTRIBUTING
new file mode 100644
index 0000000..be466d3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/CONTRIBUTING
@@ -0,0 +1,3 @@
+Contributions are welcomed. Please see the following sites for guidelines:
+
+ http://sass-lang.com/development.html#contributing
diff --git a/backends/css/gems/sass-3.2.12/MIT-LICENSE b/backends/css/gems/sass-3.2.12/MIT-LICENSE
new file mode 100644
index 0000000..6b739c9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2006-2013 Hampton Catlin, Nathan 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.2.12/README.md b/backends/css/gems/sass-3.2.12/README.md
new file mode 100644
index 0000000..ef80d18
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/README.md
@@ -0,0 +1,201 @@
+# Sass [![Gem Version](https://badge.fury.io/rb/sass.png)](http://badge.fury.io/rb/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
+
+ 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/docs/yardoc/).
+
+## 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://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#variables_
+[nested]: http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#nested_rules_
+[mixins]: http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#mixins
+[imports]: http://beta.sass-lang.com/docs/yardoc/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://beta.sass-lang.com/docs/yardoc/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.
+
+ 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.
+
+ $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.
+
+ @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://beta.sass-lang.com/docs/yardoc/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.
+
+## 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/).
+
+[Nathan Weizenbaum](http://nex-3.com) is the primary developer and architect of
+Sass. His 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 boy-genius). Nathan 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 the Software Architect for
+[Caring.com](http://caring.com), a website devoted to the 34 Million caregivers
+whose parents are sick or elderly, that uses Haml and Sass.
+
+If you use this software, you must pay Hampton a compliment. And
+buy Nathan some jelly beans. 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.2.12/REVISION
new file mode 100644
index 0000000..54798d6
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/REVISION
@@ -0,0 +1 @@
+(release)
diff --git a/backends/css/gems/sass-3.2.12/Rakefile b/backends/css/gems/sass-3.2.12/Rakefile
new file mode 100644
index 0000000..b9440c9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/Rakefile
@@ -0,0 +1,347 @@
+# ----- 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
+
+# ----- 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::Builder.new(SASS_GEMSPEC).build
+ 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 Rubyforge."
+task :release => [:check_release, :package] do
+ name = File.read(scope("VERSION_NAME")).strip
+ version = File.read(scope("VERSION")).strip
+ sh %{rubyforge add_release sass sass "#{name} (v#{version})" pkg/sass-#{version}.gem}
+ sh %{rubyforge add_file sass sass "#{name} (v#{version})" pkg/sass-#{version}.tar.gz}
+ 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
+ sh %{rubyforge add_release sass sass "Bleeding Edge (v#{version})" pkg/sass-#{version}.gem}
+ 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 --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.2.12/VERSION b/backends/css/gems/sass-3.2.12/VERSION
new file mode 100644
index 0000000..275e51e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/VERSION
@@ -0,0 +1 @@
+3.2.12
diff --git a/backends/css/gems/sass-3.2.12/VERSION_DATE b/backends/css/gems/sass-3.2.12/VERSION_DATE
new file mode 100644
index 0000000..81d9ff3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/VERSION_DATE
@@ -0,0 +1 @@
+05 October 2013 01:29:11 UTC
diff --git a/backends/css/gems/sass-3.2.12/VERSION_NAME b/backends/css/gems/sass-3.2.12/VERSION_NAME
new file mode 100644
index 0000000..b0c8d03
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/VERSION_NAME
@@ -0,0 +1 @@
+Media Mark
diff --git a/backends/css/gems/sass-3.2.12/bin/sass b/backends/css/gems/sass-3.2.12/bin/sass
new file mode 100755
index 0000000..0e5279d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/bin/sass
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+# The command line Sass parser.
+
+THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+require File.dirname(THIS_FILE) + '/../lib/sass'
+require 'sass/exec'
+
+opts = Sass::Exec::Sass.new(ARGV)
+opts.parse!
diff --git a/backends/css/gems/sass-3.2.12/bin/sass-convert b/backends/css/gems/sass-3.2.12/bin/sass-convert
new file mode 100755
index 0000000..10f67d7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/bin/sass-convert
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+
+THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+require File.dirname(THIS_FILE) + '/../lib/sass'
+require 'sass/exec'
+
+opts = Sass::Exec::SassConvert.new(ARGV)
+opts.parse!
diff --git a/backends/css/gems/sass-3.2.12/bin/scss b/backends/css/gems/sass-3.2.12/bin/scss
new file mode 100755
index 0000000..5d4d4ae
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/bin/scss
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+# The command line Sass parser.
+
+THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+require File.dirname(THIS_FILE) + '/../lib/sass'
+require 'sass/exec'
+
+opts = Sass::Exec::Scss.new(ARGV)
+opts.parse!
diff --git a/backends/css/gems/sass-3.2.12/extra/update_watch.rb
b/backends/css/gems/sass-3.2.12/extra/update_watch.rb
new file mode 100644
index 0000000..73489a1
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/extra/update_watch.rb
@@ -0,0 +1,13 @@
+require 'rubygems'
+require 'sinatra'
+require 'json'
+set :port, 3124
+set :environment, :production
+enable :lock
+Dir.chdir(File.dirname(__FILE__) + "/..")
+
+post "/" do
+ puts "Recieved payload!"
+ puts "Rev: #{`git name-rev HEAD`.strip}"
+ system %{rake handle_update --trace REF=#{JSON.parse(params["payload"])["ref"].inspect}}
+end
diff --git a/backends/css/gems/sass-3.2.12/init.rb b/backends/css/gems/sass-3.2.12/init.rb
new file mode 100644
index 0000000..5a3bceb
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/init.rb
@@ -0,0 +1,18 @@
+begin
+ require File.join(File.dirname(__FILE__), 'lib', 'sass') # From here
+rescue LoadError
+ begin
+ require 'sass' # From gem
+ rescue LoadError => e
+ # gems:install may be run to install Haml with the skeleton plugin
+ # but not the gem itself installed.
+ # Don't die if this is the case.
+ raise e unless defined?(Rake) &&
+ (Rake.application.top_level_tasks.include?('gems') ||
+ Rake.application.top_level_tasks.include?('gems:install'))
+ end
+end
+
+# Load Sass.
+# Sass may be undefined if we're running gems:install.
+require 'sass/plugin' if defined?(Sass)
diff --git a/backends/css/gems/sass-3.2.12/lib/sass.rb b/backends/css/gems/sass-3.2.12/lib/sass.rb
new file mode 100644
index 0000000..63af120
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass.rb
@@ -0,0 +1,95 @@
+dir = File.dirname(__FILE__)
+$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
+
+# This is necessary to set so that the Haml code that tries to load Sass
+# knows that Sass is indeed loading,
+# even if there's some crazy autoload stuff going on.
+SASS_BEGUN_TO_LOAD = true unless defined?(SASS_BEGUN_TO_LOAD)
+
+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
+ # 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 ||= ENV['SASS_PATH'] ?
+ ENV['SASS_PATH'].split(Sass::Util.windows? ? ';' : ':') : []
+ 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.
+ #
+ # @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}
+ # @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.
+ #
+ # @return [String] The compiled CSS.
+ #
+ # @overload compile_file(filename, css_filename, options = {})
+ # Write the compiled CSS to a file.
+ #
+ # @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'
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/cache_stores.rb
b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores.rb
new file mode 100644
index 0000000..62259b3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores.rb
@@ -0,0 +1,15 @@
+require 'stringio'
+
+module Sass
+ # Sass cache stores are in charge of storing cached information,
+ # especially parse trees for Sass documents.
+ #
+ # User-created importers must inherit from {CacheStores::Base}.
+ module CacheStores
+ end
+end
+
+require 'sass/cache_stores/base'
+require 'sass/cache_stores/filesystem'
+require 'sass/cache_stores/memory'
+require 'sass/cache_stores/chain'
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/cache_stores/base.rb
b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores/base.rb
new file mode 100644
index 0000000..25e6472
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 obj [Object] The object 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.2.12/lib/sass/cache_stores/chain.rb
b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores/chain.rb
new file mode 100644
index 0000000..d67086f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores/chain.rb
@@ -0,0 +1,33 @@
+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|
+ next unless obj = c.retrieve(key, sha)
+ @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.2.12/lib/sass/cache_stores/filesystem.rb
b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores/filesystem.rb
new file mode 100644
index 0000000..221ec4a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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
+ File.unlink path_to(key)
+ 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)
+ # return unless File.writable?(File.dirname(@cache_location))
+ # return if File.exists?(@cache_location) && !File.writable?(@cache_location)
+ compiled_filename = path_to(key)
+ # return if File.exists?(File.dirname(compiled_filename)) &&
!File.writable?(File.dirname(compiled_filename))
+ # return if File.exists?(compiled_filename) && !File.writable?(compiled_filename)
+ FileUtils.mkdir_p(File.dirname(compiled_filename))
+ Sass::Util.atomic_create_and_write_file(compiled_filename) 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.2.12/lib/sass/cache_stores/memory.rb
b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores/memory.rb
new file mode 100644
index 0000000..65dcf68
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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.2.12/lib/sass/cache_stores/null.rb
b/backends/css/gems/sass-3.2.12/lib/sass/cache_stores/null.rb
new file mode 100644
index 0000000..3bf56ca
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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.2.12/lib/sass/callbacks.rb
b/backends/css/gems/sass-3.2.12/lib/sass/callbacks.rb
new file mode 100644
index 0000000..f58b1ba
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/callbacks.rb
@@ -0,0 +1,66 @@
+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.2.12/lib/sass/css.rb b/backends/css/gems/sass-3.2.12/lib/sass/css.rb
new file mode 100644
index 0000000..fa5532f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/css.rb
@@ -0,0 +1,409 @@
+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) do |msg, line|
+ raise Sass::SyntaxError.new(msg, :line => line)
+ end
+ 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]).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, 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
+
+ unless rest.empty?
+ child.parsed_rules = make_seq(*rest)
+ current_rule << child
+ else
+ current_rule.children += child.children
+ 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
+
+ unless rest.empty?
+ rest.unshift Sass::Selector::Parent.new
+ child.parsed_rules = make_sseq(sseq.subject?, *rest)
+ current_rule << child
+ else
+ current_rule.children += child.children
+ 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)
+ 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 rule [Tree::RuleNode] The candidate for flattening
+ 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 == child.children
+ 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.2.12/lib/sass/engine.rb
b/backends/css/gems/sass-3.2.12/lib/sass/engine.rb
new file mode 100644
index 0000000..5e87407
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/engine.rb
@@ -0,0 +1,928 @@
+require 'set'
+require 'digest/sha1'
+require 'sass/cache_stores'
+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/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/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::Node, Script::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::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
+ include Sass::Util
+
+ # 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
+ 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 _render unless @options[:quiet]
+ Sass::Util.silence_sass_warnings {_render}
+ 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 ||= @options[:quiet] ?
+ Sass::Util.silence_sass_warnings {_to_tree} :
+ _to_tree
+ 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
+
+ # 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)
+ return if seen.include?(key = [ options[:filename], @options[:importer]])
+ 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
+ rendered = _to_tree.render
+ return rendered if ruby1_8?
+ begin
+ # Try to convert the result to the original encoding,
+ # but if that doesn't work fall back on UTF-8
+ rendered = rendered.encode(source_encoding)
+ rescue EncodingError
+ end
+ rendered.gsub(Regexp.new('\A charset "(.*?)"'.encode(source_encoding)),
+ "@charset \"#{source_encoding.name}\"".encode(source_encoding))
+ end
+
+ def _to_tree
+ 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
+
+ check_encoding!
+
+ if @options[:syntax] == :scss
+ root = Sass::SCSS::Parser.new(@template, @options[:filename]).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, @original_encoding = check_sass_encoding(@template) do |msg, line|
+ raise Sass::SyntaxError.new(msg, :line => line)
+ end
+ end
+
+ def tabulate(string)
+ tab_str = nil
+ comment_tab_str = nil
+ first = true
+ lines = []
+ string.gsub(/\r\n|\r|\n/, "\n").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, tab_str.size, @options[:filename], [])
+ end
+ lines
+ end
+
+ def try_comment(line, last, tab_str, comment_tab_str, index)
+ 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
+ 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 += ["\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))
+ else
+ name, value = line.text.scan(PROPERTY_OLD)[0]
+ raise SyntaxError.new("Invalid property: \"#{line.text}\".",
+ :line => @line) if name.nil? || value.nil?
+ parse_property(name, parse_interp(name), value, :old, line)
+ 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]))
+ 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))
+ 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(/[:\*\.]|\#(?!\{)/)
+ parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
+
+ unless res = parser.parse_interp_ident
+ return Tree::RuleNode.new(parse_interp(line.text))
+ end
+ res.unshift(hack_char) if hack_char
+ if comment = scanner.scan(Sass::SCSS::RX::COMMENT)
+ res << comment
+ end
+
+ name = line.text[0...scanner.pos]
+ if scanner.scan(/\s*:(?:\s|$)/)
+ parse_property(name, res, scanner.rest, :new, line)
+ else
+ res.pop if comment
+ Tree::RuleNode.new(res + parse_interp(scanner.rest))
+ end
+ end
+
+ def parse_property(name, parsed_name, value, prop, line)
+ if value.strip.empty?
+ expr = Sass::Script::String.new("")
+ else
+ expr = parse_script(value, :offset => line.offset + line.text.index(value))
+ end
+ node = Tree::PropNode.new(parse_interp(name), expr, prop)
+ 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, default = 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
+
+ expr = parse_script(value, :offset => line.offset + line.text.index(value))
+
+ Tree::VariableNode.new(name, expr, default)
+ 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, line.offset, :filename => @filename)
+ end
+ value = 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 then :silent elsif loud then :loud else :normal end
+ Tree::CommentNode.new(value, type)
+ else
+ Tree::RuleNode.new(parse_interp(line.text))
+ end
+ end
+
+ def parse_directive(parent, line, root)
+ directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
+ offset = directive.size + whitespace.size + 1 if whitespace
+
+ # If value begins with url( or ",
+ # it's a CSS @import rule and we don't want to touch it.
+ case directive
+ when 'import'
+ parse_import(line, value, offset)
+ when 'mixin'
+ parse_mixin_definition(line)
+ when 'content'
+ parse_content_directive(line)
+ when 'include'
+ parse_mixin_include(line, root)
+ when 'function'
+ parse_function(line, root)
+ when 'for'
+ parse_for(line, root, value)
+ when 'each'
+ parse_each(line, root, value)
+ when 'else'
+ parse_else(parent, line, value)
+ when 'while'
+ raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
+ Tree::WhileNode.new(parse_script(value, :offset => offset))
+ when 'if'
+ raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
+ Tree::IfNode.new(parse_script(value, :offset => offset))
+ when 'debug'
+ 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))
+ when 'extend'
+ 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
+ Tree::ExtendNode.new(parse_interp(value, offset), optional)
+ when 'warn'
+ 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))
+ when 'return'
+ 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))
+ when 'charset'
+ 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)
+ when 'media'
+ parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
+ Tree::MediaNode.new(parser.parse_media_query_list.to_a)
+ else
+ 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
+ end
+
+ def parse_for(line, root, text)
+ var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
+
+ if var.nil? # scan failed, try to figure out why for error message
+ if text !~ /^[^\s]+/
+ expected = "variable name"
+ elsif text !~ /^[^\s]+\s+from\s+.+/
+ expected = "'from <expr>'"
+ else
+ expected = "'to <expr>' or 'through <expr>'"
+ end
+ raise SyntaxError.new("Invalid for directive '@for #{text}': 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(line, root, text)
+ var, list_expr = text.scan(/^([^\s]+)\s+in\s+(.+)$/).first
+
+ if var.nil? # scan failed, try to figure out why for error message
+ if text !~ /^[^\s]+/
+ expected = "variable name"
+ elsif text !~ /^[^\s]+\s+from\s+.+/
+ expected = "'in <expr>'"
+ end
+ raise SyntaxError.new("Invalid for directive '@each #{text}': expected #{expected}.")
+ end
+ raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
+
+ var = var[1..-1]
+ parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr))
+ Tree::EachNode.new(var, parsed_list)
+ end
+
+ def parse_else(parent, line, text)
+ previous = parent.children.last
+ raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
+
+ if text
+ if text !~ /^if\s+(.+)/
+ raise SyntaxError.new("Invalid else directive '@else #{text}': 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(line, 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
+
+ return values
+ end
+
+ def parse_import_arg(scanner, offset)
+ return if scanner.eos?
+
+ if scanner.match?(/url\(/i)
+ script_parser = Sass::Script::Parser.new(scanner, @line, offset, @options)
+ str = script_parser.parse_string
+ media_parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
+ media = media_parser.parse_media_query_list
+ return Tree::CssImportNode.new(str, media.to_a)
+ end
+
+ unless str = scanner.scan(Sass::SCSS::RX::STRING)
+ return Tree::ImportNode.new(scanner.scan(/[^,;]+/))
+ end
+
+ val = scanner[1] || scanner[2]
+ scanner.scan(/\s*/)
+ if !scanner.match?(/[,;]|$/)
+ media_parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
+ media = media_parser.parse_media_query_list
+ Tree::CssImportNode.new(str || uri, media.to_a)
+ elsif val =~ /^(https?:)?\/\//
+ Tree::CssImportNode.new("url(#{val})")
+ else
+ Tree::ImportNode.new(val)
+ end
+ 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, offset, @options).
+ parse_mixin_definition_arglist
+ Tree::MixinDefNode.new(name, args, splat)
+ end
+
+ CONTENT_RE = /^ content\s*(.+)?$/
+ def parse_content_directive(line)
+ trailing = line.text.scan(CONTENT_RE).first.first
+ raise SyntaxError.new("Invalid content directive. Trailing characters found: \"#{trailing}\".") unless
trailing.nil?
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.",
+ :line => line.index + 1) unless line.children.empty?
+ Tree::ContentNode.new
+ 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 = Script::Parser.new(arg_string.strip, @line, offset, @options).
+ parse_mixin_include_arglist
+ Tree::MixinNode.new(name, args, keywords, splat)
+ end
+
+ FUNCTION_RE = /^ function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
+ def parse_function(line, root)
+ 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, 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] || 0
+ 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 silent ? "//" : "/* */" if content.empty?
+ content.last.gsub!(%r{ ?\*/ *$}, '')
+ content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
+ content.first.gsub!(/^ /, '') 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
+
+ # It's important that this have strings (at least)
+ # at the beginning, the end, and between each Script::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 % 2 == 1
+ res << "\\" * (escapes - 1) << '#{'
+ else
+ res << "\\" * [0, escapes - 1].max
+ res << Script::Parser.new(
+ scan, line, offset + scan.pos - scan.matched_size, options).
+ parse_interpolated
+ end
+ end
+ res << rest
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/environment.rb
b/backends/css/gems/sass-3.2.12/lib/sass/environment.rb
new file mode 100644
index 0000000..ec1800c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/environment.rb
@@ -0,0 +1,101 @@
+require 'set'
+
+module Sass
+ # 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
+ # The enclosing environment,
+ # or nil if this is the global environment.
+ #
+ # @return [Environment]
+ attr_reader :parent
+ attr_reader :options
+ attr_writer :caller
+ attr_writer :content
+
+ # @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) || {}
+ 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 environmnet. This is naturally only set
+ # for mixin body environments with content passed in.
+ # @return {Environment?}
+ def content
+ @content || (@parent && @parent.content)
+ end
+
+ private
+
+ class << self
+ private
+ UNDERSCORE, DASH = '_', '-'
+
+ # Note: when updating this,
+ # update sass/yard/inherited_hash.rb as well.
+ def inherited_hash(name)
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
+ def #{name}(name)
+ _#{name}(name.tr(UNDERSCORE, DASH))
+ end
+
+ def _#{name}(name)
+ (@#{name}s && @#{name}s[name]) || @parent && @parent._#{name}(name)
+ end
+ protected :_#{name}
+
+ def set_#{name}(name, value)
+ name = name.tr(UNDERSCORE, DASH)
+ @#{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.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(UNDERSCORE, DASH)] = value
+ end
+RUBY
+ end
+ end
+
+ # variable
+ # Script::Literal
+ inherited_hash :var
+ # mixin
+ # Sass::Callable
+ inherited_hash :mixin
+ # function
+ # Sass::Callable
+ inherited_hash :function
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/error.rb b/backends/css/gems/sass-3.2.12/lib/sass/error.rb
new file mode 100644
index 0000000..c72bfe6
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/error.rb
@@ -0,0 +1,201 @@
+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 = self.message.split("\n")
+ msg = lines[0] + lines[1..-1].
+ map {|l| "\n" + (" " * "Syntax error: ".size) + l}.join
+ "Syntax 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 options [{Symbol => Object}] The options passed to {Sass::Engine#initialize}
+ # @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, options)
+ raise e unless options[:full_exception]
+
+ header = header_string(e, options)
+
+ <<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, options)
+ unless e.is_a?(Sass::SyntaxError) && e.sass_line && e.sass_template
+ return "#{e.class}: #{e.message}"
+ end
+
+ line_offset = options[:line] || 1
+ 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.2.12/lib/sass/exec.rb b/backends/css/gems/sass-3.2.12/lib/sass/exec.rb
new file mode 100644
index 0000000..5096ecb
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/exec.rb
@@ -0,0 +1,707 @@
+require 'optparse'
+require 'fileutils'
+
+module Sass
+ # This module handles the various Sass executables (`sass` and `sass-convert`).
+ module Exec
+ # An abstract class that encapsulates the executable code for all three executables.
+ class Generic
+ # @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!
+ begin
+ parse
+ rescue Exception => e
+ raise e if @options[:trace] || e.is_a?(SystemExit)
+
+ $stderr.print "#{e.class}: " unless e.class == RuntimeError
+ $stderr.puts "#{e.message}"
+ $stderr.puts " Use --trace for backtrace."
+ exit 1
+ end
+ exit 0
+ 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
+ return (exception.message.scan(/:(\d+)/).first || ["??"]).first if exception.is_a?(::SyntaxError)
+ (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)
+ opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
+ @options[:input] = $stdin
+ end
+
+ opts.on('--trace', :NONE, 'Show a full traceback on error') do
+ @options[:trace] = true
+ end
+
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.') do
+ @options[:unix_newlines] = true if ::Sass::Util.windows?
+ end
+
+ opts.on_tail("-?", "-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+
+ opts.on_tail("-v", "--version", "Print version") do
+ puts("Sass #{::Sass.version[:string]}")
+ exit
+ 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
+ output ||= args.shift || $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?
+ return "\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
+
+ # The `sass` executable.
+ class Sass < Generic
+ attr_reader :default_syntax
+
+ # @param args [Array<String>] The command-line arguments
+ def initialize(args)
+ super
+ @options[:for_engine] = {
+ :load_paths => ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
+ }
+ @default_syntax = :sass
+ end
+
+ protected
+
+ # Tells optparse how to parse the arguments.
+ #
+ # @param opts [OptionParser]
+ def set_opts(opts)
+ super
+
+ opts.banner = <<END
+Usage: #{default_syntax} [options] [INPUT] [OUTPUT]
+
+Description:
+ Converts SCSS or Sass files to CSS.
+
+Options:
+END
+
+ 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 syntax.') do
+ @options[:for_engine][:syntax] = :sass
+ end
+ end
+ 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
+ opts.on('--update', 'Compile files or directories to CSS.',
+ 'Locations are set like --watch.') do
+ @options[:update] = 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
+ opts.on('--poll', 'Check for file changes manually, rather than relying on the OS.',
+ 'Only meaningful for --watch.') do
+ @options[:poll] = true
+ end
+ opts.on('-f', '--force', 'Recompile all Sass files, even if the CSS file is newer.',
+ 'Only meaningful for --update.') do
+ @options[:force] = 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('-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('--precision NUMBER_OF_DIGITS', Integer,
+ 'How many digits of precision to use when outputting decimal numbers. Defaults to 3.') do
|precision|
+ ::Sass::Script::Number.precision = precision
+ end
+ opts.on('-q', '--quiet', 'Silence warnings and status messages during compilation.') do
+ @options[:for_engine][:quiet] = true
+ end
+ opts.on('--compass', 'Make Compass imports available and load project configuration.') do
+ @options[:compass] = true
+ end
+ opts.on('-g', '--debug-info',
+ 'Emit extra information in the generated CSS 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
+ opts.on('-i', '--interactive',
+ 'Run an interactive SassScript shell.') do
+ @options[:interactive] = true
+ end
+ opts.on('-I', '--load-path PATH', 'Add 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('--cache-location PATH', 'The path to put cached 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][:cache] = false
+ end
+
+ encoding_desc = if ::Sass::Util.ruby1_8?
+ 'Does not work in ruby 1.8.'
+ else
+ 'Specify the default encoding for Sass files.'
+ end
+ opts.on('-E 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,
+ # 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
+ @options[:for_engine][:filename] = @options[:filename]
+ @options[:for_engine][:css_filename] = @options[:output] if @options[:output].is_a?(String)
+
+ begin
+ 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)
+
+ write_output(engine.render, output)
+ rescue ::Sass::SyntaxError => e
+ raise e if @options[:trace]
+ raise e.sass_backtrace_str("standard input")
+ end
+ end
+
+ private
+
+ 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] += Compass.configuration.sass_load_paths
+ end
+
+ def interactive
+ require 'sass/repl'
+ ::Sass::Repl.new(@options).run
+ end
+
+ 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]
+
+ 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
+
+ dirs, files = @args.map {|name| split_colon_path(name)}.
+ partition {|i, _| File.directory? i}
+ files.map! {|from, to| [from, to || from.gsub(/\.[^.]*?$/, '.css')]}
+ dirs.map! {|from, to| [from, to || from]}
+ ::Sass::Plugin.options[:template_location] = dirs
+
+ ::Sass::Plugin.on_updated_stylesheet do |_, css|
+ if File.exists? css
+ puts_action :overwrite, :yellow, css
+ else
+ puts_action :create, :green, css
+ 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_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
+
+ 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)
+ return ::Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
+ end
+ end
+
+ class Scss < Sass
+ # @param args [Array<String>] The command-line arguments
+ def initialize(args)
+ super
+ @default_syntax = :scss
+ end
+ end
+
+ # The `sass-convert` executable.
+ class SassConvert < Generic
+ # @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, Sass, and SCSS files.
+ E.g. converts from SCSS to Sass,
+ or converts from CSS to SCSS (adding appropriate nesting).
+
+Options:
+END
+
+ 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('-R', '--recursive',
+ 'Convert all the files in a directory. Requires --from and --to.') do
+ @options[:recursive] = true
+ 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('--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
+
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
+ @options[:for_engine][:read_cache] = false
+ end
+
+ unless ::Sass::Util.ruby1_8?
+ opts.on('-E encoding', 'Specify the default encoding for Sass and CSS files.') do |encoding|
+ Encoding.default_external = encoding
+ end
+ end
+
+ super
+ 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]
+ raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)" if
File.directory?(input)
+ output = @options[:output]
+ output = input if @options[:in_place]
+ process_file(input, output)
+ end
+
+ private
+
+ def process_directory
+ unless input = @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]
+ raise "Error: '#{ options[:input]}' is not a directory" unless File.directory?(@options[:input])
+ if @options[:output] && File.exists?(@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.exists?(output)
+ puts_action :overwrite, :yellow, output
+ else
+ puts_action :create, :green, output
+ end
+
+ input = open_file(f)
+ process_file(input, output)
+ end
+ end
+
+ def process_file(input, output)
+ if input.is_a?(File)
+ @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.is_a?(File)
+ @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.is_a?(File)
+ ::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
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/importers.rb
b/backends/css/gems/sass-3.2.12/lib/sass/importers.rb
new file mode 100644
index 0000000..1561a2d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/importers.rb
@@ -0,0 +1,22 @@
+module Sass
+ # Sass importers are in charge of taking paths passed to ` import`
+ # and finding the appropriate Sass code for those paths.
+ # By default, this code is always loaded from the filesystem,
+ # but importers could be added to load from a database or over HTTP.
+ #
+ # Each importer is in charge of a single load path
+ # (or whatever the corresponding notion is for the backend).
+ # Importers can be placed in the {file:SASS_REFERENCE.md#load_paths-option `:load_paths` array}
+ # alongside normal filesystem paths.
+ #
+ # When resolving an ` import`, Sass will go through the load paths
+ # looking for an importer that successfully imports the path.
+ # Once one is found, the imported file is used.
+ #
+ # User-created importers must inherit from {Importers::Base}.
+ module Importers
+ end
+end
+
+require 'sass/importers/base'
+require 'sass/importers/filesystem'
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/importers/base.rb
b/backends/css/gems/sass-3.2.12/lib/sass/importers/base.rb
new file mode 100644
index 0000000..e18ebbd
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/importers/base.rb
@@ -0,0 +1,139 @@
+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`.
+ # In addition to the standard `_dump` and `_load` methods,
+ # importers can define `_before_dump`, `_after_dump`, `_around_dump`,
+ # and `_after_load` methods as per {Sass::Util#dump} and {Sass::Util#load}.
+ #
+ # @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
+
+ # 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
+ end
+ end
+end
+
+
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/importers/filesystem.rb
b/backends/css/gems/sass-3.2.12/lib/sass/importers/filesystem.rb
new file mode 100644
index 0000000..516b90f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/importers/filesystem.rb
@@ -0,0 +1,190 @@
+require 'pathname'
+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)
+ @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)
+ root.eql?(other.root)
+ 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(%r{^\./}, ''), s]}
+ end
+
+ def escape_glob_characters(name)
+ name.gsub(/[\*\[\]\{\}\?]/) do |char|
+ "\\#{char}"
+ end
+ end
+
+ REDUNDANT_DIRECTORY = %r{#{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' can be in native File::ALT_SEPARATOR form
+ dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
+
+ found = possible_files(remove_root(name)).map do |f, s|
+ path = (dir == "." || Pathname.new(f).absolute?) ? f : "#{dir}/#{f}"
+ Dir[path].map do |full_path|
+ full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR)
+ [full_path, 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 = Pathname.new(dir)
+ if options[:_line]
+ # 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 {|(f, _)| " " +
Pathname.new(f).relative_path_from(relative_to).to_s}.join("\n")
+ Sass::Util.sass_warn <<WARNING
+WARNING: On line #{options[:_line]}#{" of #{options[:filename]}" if options[:filename]}:
+ It's not clear which file to import for '@import "#{name}"'.
+ Candidates:
+#{candidates}
+ For now I'll choose #{File.basename found.first.first}.
+ This will be an error in future versions of Sass.
+WARNING
+ 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)
+
+ options[:syntax] = syntax
+ options[:filename] = full_filename
+ options[:importer] = self
+ Sass::Engine.new(File.read(full_filename), options)
+ end
+
+ def join(base, path)
+ Pathname.new(base).join(path).to_s
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/logger.rb
b/backends/css/gems/sass-3.2.12/lib/sass/logger.rb
new file mode 100644
index 0000000..acc7437
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/logger.rb
@@ -0,0 +1,15 @@
+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.2.12/lib/sass/logger/base.rb
b/backends/css/gems/sass-3.2.12/lib/sass/logger/base.rb
new file mode 100644
index 0000000..c586c06
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/logger/base.rb
@@ -0,0 +1,32 @@
+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)
+ self._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.2.12/lib/sass/logger/log_level.rb
b/backends/css/gems/sass-3.2.12/lib/sass/logger/log_level.rb
new file mode 100644
index 0000000..c953bcf
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/logger/log_level.rb
@@ -0,0 +1,49 @@
+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
+
+ def log_levels
+ @log_levels ||= {}
+ end
+
+ def log_levels=(levels)
+ @log_levels = 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.2.12/lib/sass/media.rb b/backends/css/gems/sass-3.2.12/lib/sass/media.rb
new file mode 100644
index 0000000..dba3aa3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/media.rb
@@ -0,0 +1,213 @@
+# 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::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::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::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::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::Node>>]
+ attr_accessor :expressions
+
+ # @param modifier [Array<String, Sass::Script::Node>] See \{#modifier}
+ # @param type [Array<String, Sass::Script::Node>] See \{#type}
+ # @param expressions [Array<Array<String, Sass::Script::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
+ return 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::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::Node) ? c.deep_copy : c},
+ type.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
+ expressions.map {|e| e.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}})
+ end
+ end
+
+ # Converts an interpolation array to source.
+ #
+ # @param [Array<String, Sass::Script::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 do |r|
+ next r if r.is_a?(String)
+ "\#{#{r.to_sass(options)}}"
+ end.join
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin.rb
b/backends/css/gems/sass-3.2.12/lib/sass/plugin.rb
new file mode 100644
index 0000000..0e037d8
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/plugin.rb
@@ -0,0 +1,132 @@
+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
+ include Sass::Util
+ 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)
+ require 'sass/plugin/rails'
+elsif defined?(Merb::Plugins)
+ require 'sass/plugin/merb'
+else
+ require 'sass/plugin/generic'
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/compiler.rb
b/backends/css/gems/sass-3.2.12/lib/sass/plugin/compiler.rb
new file mode 100644
index 0000000..feabe20
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/plugin/compiler.rb
@@ -0,0 +1,406 @@
+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 Sass::Util
+ include Configuration
+ extend Sass::Callbacks
+
+ # Creates a new compiler.
+ #
+ # @param options [{Symbol => Object}]
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ def initialize(options = {})
+ self.options.merge!(options)
+ end
+
+ # 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 [individual_files]
+ # @yieldparam individual_files [<(String, String)>]
+ # Individual files to be updated, in addition to the directories
+ # specified in the options.
+ # The first element of each pair is the source file,
+ # the second is the target CSS file.
+ define_callback :updating_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]
+ # @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 :updated_stylesheet
+
+ # Register a callback to be run before a single stylesheet is updated.
+ # The callback is only run if the stylesheet is guaranteed to be 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]
+ # @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 :updating_stylesheet
+
+ def on_updating_stylesheet_with_deprecation_warning(&block)
+ Sass::Util.sass_warn("Sass::Compiler#on_updating_stylesheet callback is deprecated and will be removed
in a future release. Use Sass::Compiler#on_updated_stylesheet instead, which is run after stylesheet
compilation.")
+ on_updating_stylesheet_without_deprecation_warning(&block)
+ end
+ alias_method :on_updating_stylesheet_without_deprecation_warning, :on_updating_stylesheet
+ alias_method :on_updating_stylesheet, :on_updating_stylesheet_with_deprecation_warning
+
+ # 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.
+ #
+ # @yield [filename]
+ # @yieldparam filename [String]
+ # The location of the CSS file that was deleted.
+ define_callback :deleting_css
+
+ # 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 = [])
+ individual_files = individual_files.dup
+ Sass::Plugin.checked_for_updates = true
+ staleness_checker = StalenessChecker.new(engine_options)
+
+ 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 = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
+ css = css_filename(name, css_location)
+ individual_files << [file, css]
+ end
+ end
+
+ run_updating_stylesheets individual_files
+
+ individual_files.each do |file, css|
+ if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
+ update_stylesheet(file, css)
+ else
+ run_not_updating_stylesheet(file, css)
+ end
+ end
+ 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)>]
+ # A list of files to watch 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 watch(individual_files = [])
+ update_stylesheets(individual_files)
+
+ load_listen!
+
+ template_paths = template_locations # cache the locations
+ individual_files_hash = individual_files.inject({}) do |h, files|
+ parent = File.dirname(files.first)
+ (h[parent] ||= []) << files unless template_paths.include?(parent)
+ h
+ end
+ directories = template_paths + individual_files_hash.keys +
+ [{:relative_paths => true}]
+
+ # TODO: Keep better track of what depends on what
+ # so we don't have to run a global update every time anything changes.
+ listener = Listen::MultiListener.new(*directories) do |modified, added, removed|
+ modified.each do |f|
+ parent = File.dirname(f)
+ if files = individual_files_hash[parent]
+ next unless files.first == f
+ else
+ next unless f =~ /\.s[ac]ss$/
+ end
+ run_template_modified(f)
+ end
+
+ added.each do |f|
+ parent = File.dirname(f)
+ if files = individual_files_hash[parent]
+ next unless files.first == f
+ else
+ next unless f =~ /\.s[ac]ss$/
+ end
+ run_template_created(f)
+ end
+
+ removed.each do |f|
+ parent = File.dirname(f)
+ if files = individual_files_hash[parent]
+ next unless files.first == f
+ try_delete_css files[1]
+ else
+ next unless f =~ /\.s[ac]ss$/
+ try_delete_css f.gsub(/\.s[ac]ss$/, '.css')
+ end
+ run_template_deleted(f)
+ end
+
+ update_stylesheets(individual_files)
+ end
+
+ # The native windows listener is much slower than the polling
+ # option, according to
https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e#commitcomment-1295118
+ listener.force_polling(true) if @options[:poll] || Sass::Util.windows?
+
+ begin
+ listener.start
+ rescue Exception => e
+ raise e unless e.is_a?(Interrupt)
+ end
+ 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)
+ opts
+ end
+
+ # Compass expects this to exist
+ def stylesheet_needs_update?(css_file, template_file)
+ StalenessChecker.stylesheet_needs_update?(css_file, template_file)
+ end
+
+ private
+
+ def load_listen!
+ if defined?(gem)
+ begin
+ gem 'listen', '~> 0.7'
+ require 'listen'
+ rescue Gem::LoadError
+ dir = Sass::Util.scope("vendor/listen/lib")
+ $LOAD_PATH.unshift dir
+ begin
+ require 'listen'
+ rescue LoadError => e
+ e.message << "\n" <<
+ if File.exists?(scope(".git"))
+ 'Run "git submodule update --init" to get the recommended version.'
+ else
+ 'Run "gem install listen" to get it.'
+ end
+ raise e
+ end
+ end
+ else
+ begin
+ require 'listen'
+ rescue LoadError => e
+ dir = Sass::Util.scope("vendor/listen/lib")
+ if $LOAD_PATH.include?(dir)
+ raise e unless File.exists?(scope(".git"))
+ e.message << "\n" <<
+ 'Run "git submodule update --init" to get the recommended version.'
+ else
+ $LOAD_PATH.unshift dir
+ retry
+ end
+ end
+ end
+ end
+
+ def update_stylesheet(filename, css)
+ dir = File.dirname(css)
+ unless File.exists?(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)
+ result = Sass::Engine.for_file(filename, engine_opts).render
+ rescue Exception => e
+ compilation_error_occured = true
+ run_compilation_error e, filename, css
+ result = Sass::SyntaxError.exception_to_css(e, options)
+ else
+ run_updating_stylesheet filename, css
+ end
+
+ write_file(css, result)
+ run_updated_stylesheet(filename, css) unless compilation_error_occured
+ end
+
+ def write_file(css, content)
+ flag = 'w'
+ flag = 'wb' if Sass::Util.windows? && options[:unix_newlines]
+ File.open(css, flag) do |file|
+ file.set_encoding(content.encoding) unless Sass::Util.ruby1_8?
+ file.print(content)
+ end
+ end
+
+ def try_delete_css(css)
+ return unless File.exists?(css)
+ run_deleting_css css
+ File.delete css
+ 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}/#{name}".gsub(/\.s[ac]ss$/, '.css')
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/configuration.rb
b/backends/css/gems/sass-3.2.12/lib/sass/plugin/configuration.rb
new file mode 100644
index 0000000..e6d65cd
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/plugin/configuration.rb
@@ -0,0 +1,123 @@
+# 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 Sass
+ module Plugin
+ 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
+
+ # Sets the options hash.
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
+ # See {Sass::Plugin::Configuration#reset!}
+ # @deprecated Instead, modify the options hash in-place.
+ # @param value [{Symbol => Object}] The options hash
+ def options=(value)
+ Sass::Util.sass_warn("Setting Sass::Plugin.options is deprecated " +
+ "and will be removed in a future release.")
+ options.merge!(value)
+ 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
+ options[:css_location] ?
+ [[File.join(options[:css_location], 'sass'), options[:css_location]]] : []
+ 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.2.12/lib/sass/plugin/generic.rb
new file mode 100644
index 0000000..3e82d2d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/plugin/generic.rb
@@ -0,0 +1,15 @@
+# The reason some options are declared here rather than in sass/plugin/configuration.rb
+# is that otherwise they'd clobber the Rails-specific options.
+# Since Rails' options are lazy-loaded in Rails 3,
+# they're reverse-merged with the default options
+# so that user configuration is preserved.
+# This means that defaults that differ from Rails'
+# must be declared here.
+
+unless defined?(Sass::GENERIC_LOADED)
+ Sass::GENERIC_LOADED = true
+
+ Sass::Plugin.options.merge!(:css_location => './public/stylesheets',
+ :always_update => false,
+ :always_check => true)
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/merb.rb
b/backends/css/gems/sass-3.2.12/lib/sass/plugin/merb.rb
new file mode 100644
index 0000000..f2b0a03
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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.2.12/lib/sass/plugin/rack.rb
new file mode 100644
index 0000000..1a93265
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/plugin/rack.rb
@@ -0,0 +1,60 @@
+module Sass
+ module Plugin
+ # Rack middleware for compiling Sass code.
+ #
+ # ## Activate
+ #
+ # require 'sass/plugin/rack'
+ # use Sass::Plugin::Rack
+ #
+ # ## Customize
+ #
+ # Sass::Plugin.options.merge(
+ # :cache_location => './tmp/sass-cache',
+ # :never_update => environment != :production,
+ # :full_exception => environment != :production)
+ #
+ # {file:SASS_REFERENCE.md#options See the Reference for more options}.
+ #
+ # ## Use
+ #
+ # Put your Sass files in `public/stylesheets/sass`.
+ # Your CSS will be generated in `public/stylesheets`,
+ # and regenerated every request if necessary.
+ # The locations and frequency {file:SASS_REFERENCE.md#options can be customized}.
+ # That's all there is to it!
+ class Rack
+ # The delay, in seconds, between update checks.
+ # Useful when many resources are requested for a single page.
+ # `nil` means no delay at all.
+ #
+ # @return [Float]
+ attr_accessor :dwell
+
+ # Initialize the middleware.
+ #
+ # @param app [#call] The Rack application
+ # @param dwell [Float] See \{#dwell}
+ def initialize(app, dwell = 1.0)
+ @app = app
+ @dwell = dwell
+ @check_after = Time.now.to_f
+ end
+
+ # Process a request, checking the Sass stylesheets for changes
+ # and updating them if necessary.
+ #
+ # @param env The Rack request environment
+ # @return [(#to_i, {String => String}, Object)] The Rack response
+ def call(env)
+ if @dwell.nil? || Time.now.to_f > @check_after
+ Sass::Plugin.check_for_updates
+ @check_after = Time.now.to_f + @dwell if @dwell
+ end
+ @app.call(env)
+ end
+ end
+ end
+end
+
+require 'sass/plugin'
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/rails.rb
b/backends/css/gems/sass-3.2.12/lib/sass/plugin/rails.rb
new file mode 100644
index 0000000..2ced021
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/plugin/rails.rb
@@ -0,0 +1,47 @@
+unless defined?(Sass::RAILS_LOADED)
+ Sass::RAILS_LOADED = true
+
+ module Sass::Plugin::Configuration
+ # Different default options in a rails envirionment.
+ def default_options
+ return @default_options if @default_options
+ opts = {
+ :quiet => Sass::Util.rails_env != "production",
+ :full_exception => Sass::Util.rails_env != "production",
+ :cache_location => Sass::Util.rails_root + '/tmp/sass-cache'
+ }
+
+ opts.merge!(
+ :always_update => false,
+ :template_location => Sass::Util.rails_root + '/public/stylesheets/sass',
+ :css_location => Sass::Util.rails_root + '/public/stylesheets',
+ :always_check => Sass::Util.rails_env == "development")
+
+ @default_options = opts.freeze
+ end
+ end
+
+ Sass::Plugin.options.reverse_merge!(Sass::Plugin.default_options)
+
+ # Rails 3.1 loads and handles Sass all on its own
+ if defined?(ActionController::Metal)
+ # 3.1 > Rails >= 3.0
+ require 'sass/plugin/rack'
+ Rails.configuration.middleware.use(Sass::Plugin::Rack)
+ elsif defined?(ActionController::Dispatcher) &&
+ defined?(ActionController::Dispatcher.middleware)
+ # Rails >= 2.3
+ require 'sass/plugin/rack'
+ ActionController::Dispatcher.middleware.use(Sass::Plugin::Rack)
+ else
+ module ActionController
+ class Base
+ alias_method :sass_old_process, :process
+ def process(*args)
+ Sass::Plugin.check_for_updates
+ sass_old_process(*args)
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/plugin/staleness_checker.rb
b/backends/css/gems/sass-3.2.12/lib/sass/plugin/staleness_checker.rb
new file mode 100644
index 0000000..74b1459
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 automaticaly 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.new 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.2.12/lib/sass/railtie.rb
b/backends/css/gems/sass-3.2.12/lib/sass/railtie.rb
new file mode 100644
index 0000000..a6c7535
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/railtie.rb
@@ -0,0 +1,9 @@
+# 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'
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/repl.rb b/backends/css/gems/sass-3.2.12/lib/sass/repl.rb
new file mode 100644
index 0000000..7301de9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 |e|
+ puts "\tfrom #{e}"
+ 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.2.12/lib/sass/root.rb
new file mode 100644
index 0000000..31e19c5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/root.rb
@@ -0,0 +1,7 @@
+module Sass
+ # The root directory of the Sass source tree.
+ # This may be overridden by the package manager
+ # if the lib directory is separated from the main source tree.
+ # @api public
+ ROOT_DIR = File.expand_path(File.join(__FILE__, "../../.."))
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script.rb
new file mode 100644
index 0000000..44228b5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script.rb
@@ -0,0 +1,39 @@
+require 'sass/script/node'
+require 'sass/script/variable'
+require 'sass/script/funcall'
+require 'sass/script/operation'
+require 'sass/script/literal'
+require 'sass/script/parser'
+
+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*(.+?)(!(?i:default))?$/
+
+ # 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::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
+
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/arg_list.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/arg_list.rb
new file mode 100644
index 0000000..e3e3e20
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/arg_list.rb
@@ -0,0 +1,52 @@
+module Sass::Script
+ # 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<Literal>] See \{List#value}.
+ # @param keywords [Hash<String, Literal>] See \{#keywords}
+ # @param separator [String] See \{List#separator}.
+ def initialize(value, keywords, separator)
+ super(value, separator)
+ @keywords = keywords
+ end
+
+ # The keyword arguments attached to this list.
+ #
+ # @return [Hash<String, Literal>]
+ def keywords
+ @keywords_accessed = true
+ @keywords
+ end
+
+ # @see Node#children
+ def children
+ super + @keywords.values
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = super
+ node.instance_variable_set('@keywords',
+ Sass::Util.map_hash(@keywords) {|k, v| [k, v.deep_copy]})
+ node
+ end
+
+ protected
+
+ # @see Node#_perform
+ def _perform(environment)
+ self
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/bool.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/bool.rb
new file mode 100644
index 0000000..90fa39a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/bool.rb
@@ -0,0 +1,18 @@
+require 'sass/script/literal'
+
+module Sass::Script
+ # A SassScript object representing a boolean (true or false) value.
+ class Bool < Literal
+ # 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.2.12/lib/sass/script/color.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/color.rb
new file mode 100644
index 0000000..70c125f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/color.rb
@@ -0,0 +1,606 @@
+require 'sass/script/literal'
+
+module Sass::Script
+ # 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 < Literal
+ class << self; include Sass::Util; end
+
+ # A hash from color names to `[red, green, blue]` value arrays.
+ COLOR_NAMES = map_vals({
+ 'aliceblue' => 0xf0f8ff,
+ 'antiquewhite' => 0xfaebd7,
+ 'aqua' => 0x00ffff,
+ 'aquamarine' => 0x7fffd4,
+ 'azure' => 0xf0ffff,
+ 'beige' => 0xf5f5dc,
+ 'bisque' => 0xffe4c4,
+ 'black' => 0x000000,
+ 'blanchedalmond' => 0xffebcd,
+ 'blue' => 0x0000ff,
+ 'blueviolet' => 0x8a2be2,
+ 'brown' => 0xa52a2a,
+ 'burlywood' => 0xdeb887,
+ 'cadetblue' => 0x5f9ea0,
+ 'chartreuse' => 0x7fff00,
+ 'chocolate' => 0xd2691e,
+ 'coral' => 0xff7f50,
+ 'cornflowerblue' => 0x6495ed,
+ 'cornsilk' => 0xfff8dc,
+ 'crimson' => 0xdc143c,
+ 'cyan' => 0x00ffff,
+ 'darkblue' => 0x00008b,
+ 'darkcyan' => 0x008b8b,
+ 'darkgoldenrod' => 0xb8860b,
+ 'darkgray' => 0xa9a9a9,
+ 'darkgrey' => 0xa9a9a9,
+ 'darkgreen' => 0x006400,
+ 'darkkhaki' => 0xbdb76b,
+ 'darkmagenta' => 0x8b008b,
+ 'darkolivegreen' => 0x556b2f,
+ 'darkorange' => 0xff8c00,
+ 'darkorchid' => 0x9932cc,
+ 'darkred' => 0x8b0000,
+ 'darksalmon' => 0xe9967a,
+ 'darkseagreen' => 0x8fbc8f,
+ 'darkslateblue' => 0x483d8b,
+ 'darkslategray' => 0x2f4f4f,
+ 'darkslategrey' => 0x2f4f4f,
+ 'darkturquoise' => 0x00ced1,
+ 'darkviolet' => 0x9400d3,
+ 'deeppink' => 0xff1493,
+ 'deepskyblue' => 0x00bfff,
+ 'dimgray' => 0x696969,
+ 'dimgrey' => 0x696969,
+ 'dodgerblue' => 0x1e90ff,
+ 'firebrick' => 0xb22222,
+ 'floralwhite' => 0xfffaf0,
+ 'forestgreen' => 0x228b22,
+ 'fuchsia' => 0xff00ff,
+ 'gainsboro' => 0xdcdcdc,
+ 'ghostwhite' => 0xf8f8ff,
+ 'gold' => 0xffd700,
+ 'goldenrod' => 0xdaa520,
+ 'gray' => 0x808080,
+ 'green' => 0x008000,
+ 'greenyellow' => 0xadff2f,
+ 'honeydew' => 0xf0fff0,
+ 'hotpink' => 0xff69b4,
+ 'indianred' => 0xcd5c5c,
+ 'indigo' => 0x4b0082,
+ 'ivory' => 0xfffff0,
+ 'khaki' => 0xf0e68c,
+ 'lavender' => 0xe6e6fa,
+ 'lavenderblush' => 0xfff0f5,
+ 'lawngreen' => 0x7cfc00,
+ 'lemonchiffon' => 0xfffacd,
+ 'lightblue' => 0xadd8e6,
+ 'lightcoral' => 0xf08080,
+ 'lightcyan' => 0xe0ffff,
+ 'lightgoldenrodyellow' => 0xfafad2,
+ 'lightgreen' => 0x90ee90,
+ 'lightgray' => 0xd3d3d3,
+ 'lightgrey' => 0xd3d3d3,
+ 'lightpink' => 0xffb6c1,
+ 'lightsalmon' => 0xffa07a,
+ 'lightseagreen' => 0x20b2aa,
+ 'lightskyblue' => 0x87cefa,
+ 'lightslategray' => 0x778899,
+ 'lightslategrey' => 0x778899,
+ 'lightsteelblue' => 0xb0c4de,
+ 'lightyellow' => 0xffffe0,
+ 'lime' => 0x00ff00,
+ 'limegreen' => 0x32cd32,
+ 'linen' => 0xfaf0e6,
+ 'magenta' => 0xff00ff,
+ 'maroon' => 0x800000,
+ 'mediumaquamarine' => 0x66cdaa,
+ 'mediumblue' => 0x0000cd,
+ 'mediumorchid' => 0xba55d3,
+ 'mediumpurple' => 0x9370db,
+ 'mediumseagreen' => 0x3cb371,
+ 'mediumslateblue' => 0x7b68ee,
+ 'mediumspringgreen' => 0x00fa9a,
+ 'mediumturquoise' => 0x48d1cc,
+ 'mediumvioletred' => 0xc71585,
+ 'midnightblue' => 0x191970,
+ 'mintcream' => 0xf5fffa,
+ 'mistyrose' => 0xffe4e1,
+ 'moccasin' => 0xffe4b5,
+ 'navajowhite' => 0xffdead,
+ 'navy' => 0x000080,
+ 'oldlace' => 0xfdf5e6,
+ 'olive' => 0x808000,
+ 'olivedrab' => 0x6b8e23,
+ 'orange' => 0xffa500,
+ 'orangered' => 0xff4500,
+ 'orchid' => 0xda70d6,
+ 'palegoldenrod' => 0xeee8aa,
+ 'palegreen' => 0x98fb98,
+ 'paleturquoise' => 0xafeeee,
+ 'palevioletred' => 0xdb7093,
+ 'papayawhip' => 0xffefd5,
+ 'peachpuff' => 0xffdab9,
+ 'peru' => 0xcd853f,
+ 'pink' => 0xffc0cb,
+ 'plum' => 0xdda0dd,
+ 'powderblue' => 0xb0e0e6,
+ 'purple' => 0x800080,
+ 'red' => 0xff0000,
+ 'rosybrown' => 0xbc8f8f,
+ 'royalblue' => 0x4169e1,
+ 'saddlebrown' => 0x8b4513,
+ 'salmon' => 0xfa8072,
+ 'sandybrown' => 0xf4a460,
+ 'seagreen' => 0x2e8b57,
+ 'seashell' => 0xfff5ee,
+ 'sienna' => 0xa0522d,
+ 'silver' => 0xc0c0c0,
+ 'skyblue' => 0x87ceeb,
+ 'slateblue' => 0x6a5acd,
+ 'slategray' => 0x708090,
+ 'slategrey' => 0x708090,
+ 'snow' => 0xfffafa,
+ 'springgreen' => 0x00ff7f,
+ 'steelblue' => 0x4682b4,
+ 'tan' => 0xd2b48c,
+ 'teal' => 0x008080,
+ 'thistle' => 0xd8bfd8,
+ 'tomato' => 0xff6347,
+ 'turquoise' => 0x40e0d0,
+ 'violet' => 0xee82ee,
+ 'wheat' => 0xf5deb3,
+ 'white' => 0xffffff,
+ 'whitesmoke' => 0xf5f5f5,
+ 'yellow' => 0xffff00,
+ 'yellowgreen' => 0x9acd32
+ }) {|color| (0..2).map {|n| color >> (n << 3) & 0xff}.reverse}
+
+ # A hash from `[red, green, blue]` value arrays to color names.
+ COLOR_NAMES_REVERSE = map_hash(hash_to_a(COLOR_NAMES)) {|k, v| [v, k]}
+
+ # Constructs an RGB or HSL color object,
+ # optionally with an alpha channel.
+ #
+ # The RGB values must be between 0 and 255.
+ # The saturation and lightness values must be between 0 and 100.
+ # The alpha value must be between 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.
+ #
+ # @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)
+ # 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
+ # @raise [ArgumentError] if not enough attributes are specified
+ def initialize(attrs, 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
+ 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
+ end
+
+ [:red, :green, :blue].each do |k|
+ next if @attrs[k].nil?
+ @attrs[k] = @attrs[k].to_i
+ Sass::Util.check_range("#{k.to_s.capitalize} value", 0..255, @attrs[k])
+ end
+
+ [:saturation, :lightness].each do |k|
+ next if @attrs[k].nil?
+ value = Number.new(@attrs[k], ['%']) # Get correct unit for error messages
+ @attrs[k] = Sass::Util.check_range("#{k.to_s.capitalize}", 0..100, value, '%')
+ end
+
+ @attrs[:alpha] = Sass::Util.check_range("Alpha channel", 0..1, @attrs[:alpha])
+ 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]
+ 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 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
+
+ # The SassScript `==` operation.
+ # **Note that this returns a {Sass::Script::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Bool] True if this literal is the same as the other,
+ # false otherwise
+ def eq(other)
+ Sass::Script::Bool.new(
+ other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha)
+ 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, :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.
+ #
+ # {Literal}
+ # : See {Literal#plus}.
+ #
+ # @param other [Literal] 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::Number) || other.is_a?(Sass::Script::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.
+ #
+ # {Literal}
+ # : See {Literal#minus}.
+ #
+ # @param other [Literal] 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::Number) || other.is_a?(Sass::Script::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::Number) || other.is_a?(Sass::Script::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.
+ #
+ # {Literal}
+ # : See {Literal#div}.
+ #
+ # @param other [Literal] 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::Number) || other.is_a?(Sass::Script::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::Number) || other.is_a?(Sass::Script::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 rgba_str if alpha?
+ return smallest if options[:style] == :compressed
+ return COLOR_NAMES_REVERSE[rgb] if COLOR_NAMES_REVERSE[rgb]
+ 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
+
+ private
+
+ def smallest
+ small_hex_str = hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
+ return small_hex_str unless (color = COLOR_NAMES_REVERSE[rgb]) &&
+ color.size <= small_hex_str.size
+ return color
+ 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 = []
+ for i in (0...3)
+ 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
+ return 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.2.12/lib/sass/script/css_lexer.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/css_lexer.rb
new file mode 100644
index 0000000..464fcf4
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/css_lexer.rb
@@ -0,0 +1,29 @@
+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
+ return unless uri = scan(URI)
+ return [:string, Script::String.new(uri)]
+ end
+
+ return unless scan(STRING)
+ [:string, Script::String.new((@scanner[1] || @scanner[2]).gsub(/\\(['"])/, '\1'), :string)]
+ end
+
+ def important
+ return unless s = scan(IMPORTANT)
+ [:raw, s]
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/css_parser.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/css_parser.rb
new file mode 100644
index 0000000..968b94b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/css_parser.rb
@@ -0,0 +1,31 @@
+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
+ return number unless tok = try_tok(:string)
+ return tok.value unless @lexer.peek && @lexer.peek.type == :begin_interpolation
+ 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.2.12/lib/sass/script/funcall.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/funcall.rb
new file mode 100644
index 0000000..bceea0a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/funcall.rb
@@ -0,0 +1,237 @@
+require 'sass/script/functions'
+
+module Sass
+ module Script
+ # A SassScript parse node representing a function call.
+ #
+ # A function call either calls one of the functions in {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<Script::Node>]
+ attr_reader :args
+
+ # The keyword arguments to the function.
+ #
+ # @return [{String => Script::Node}]
+ attr_reader :keywords
+
+ # The splat argument for this function, if one exists.
+ #
+ # @return [Script::Node?]
+ attr_accessor :splat
+
+ # @param name [String] See \{#name}
+ # @param args [Array<Script::Node>] See \{#args}
+ # @param splat [Script::Node] See \{#splat}
+ # @param keywords [{String => Script::Node}] See \{#keywords}
+ def initialize(name, args, keywords, splat)
+ @name = name
+ @args = args
+ @keywords = keywords
+ @splat = 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).
+ map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
+ if self.splat
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
+ splat = "#{splat}#{self.splat.inspect}..."
+ end
+ "#{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::List) && arg.separator == :comma
+ sass
+ end
+
+ args = @args.map(&arg_to_sass).join(', ')
+ keywords = Sass::Util.hash_to_a(@keywords).
+ map {|k, v| "$#{dasherize(k, opts)}: #{arg_to_sass[v]}"}.join(', ')
+ if self.splat
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
+ splat = "#{splat}#{arg_to_sass[self.splat]}..."
+ end
+ "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
+ end
+
+ # Returns the arguments to the function.
+ #
+ # @return [Array<Node>]
+ # @see Node#children
+ def children
+ res = @args + @keywords.values
+ res << @splat if @splat
+ res
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@args', args.map {|a| a.deep_copy})
+ node.instance_variable_set('@keywords', Hash[keywords.map {|k, v| [k, v.deep_copy]}])
+ node
+ end
+
+ protected
+
+ # Evaluates the function call.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Literal] 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 = @args.map {|a| a.perform(environment)}
+ splat = @splat.perform(environment) if @splat
+ if fn = environment.function(@name)
+ keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
+ return perform_sass_fn(fn, args, keywords, splat)
+ end
+
+ ruby_name = @name.tr('-', '_')
+ args = construct_ruby_args(ruby_name, args, splat, environment)
+
+ unless Functions.callable?(ruby_name)
+ opts(to_literal(args))
+ else
+ opts(Functions::EvaluationContext.new(environment.options).send(ruby_name, *args))
+ end
+ rescue ArgumentError => 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
+
+ # 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_literal(args)
+ Script::String.new("#{name}(#{args.join(', ')})")
+ end
+
+ private
+
+ def construct_ruby_args(name, args, splat, environment)
+ args += splat.to_a if splat
+
+ # If variable arguments were passed, there won't be any explicit keywords.
+ if splat.is_a?(Sass::Script::ArgList)
+ kwargs_size = splat.keywords.size
+ splat.keywords_accessed = false
+ else
+ kwargs_size = @keywords.size
+ end
+
+ unless signature = Functions.signature(name.to_sym, args.size, kwargs_size)
+ return args if @keywords.empty?
+ raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
+ end
+ keywords = splat.is_a?(Sass::Script::ArgList) ? splat.keywords :
+ Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
+
+ # 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
+
+ args = args + signature.args[args.size..-1].map do |argname|
+ if keywords.has_key?(argname)
+ keywords.delete(argname)
+ else
+ raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
+ end
+ end
+
+ if keywords.size > 0
+ if signature.var_kwargs
+ args << keywords
+ 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, keywords, splat)
+ Sass::Tree::Visitors::Perform.perform_arguments(function, args, keywords, splat) do |env|
+ 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
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/functions.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/functions.rb
new file mode 100644
index 0000000..b3b2458
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/functions.rb
@@ -0,0 +1,1543 @@
+module Sass::Script
+ # Methods in this module are accessible from the SassScript context.
+ # For example, you can write
+ #
+ # $color: hsl(120deg, 100%, 50%)
+ #
+ # and it will call {Sass::Script::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 {Color} from red, green, and blue values.
+ #
+ # \{#rgba rgba($red, $green, $blue, $alpha)}
+ # : Creates a {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($color-1, $color-2, \[$weight\])}
+ # : Mixes two colors together.
+ #
+ # ## HSL Functions
+ #
+ # \{#hsl hsl($hue, $saturation, $lightness)}
+ # : Creates a {Color} from hue, saturation, and lightness values.
+ #
+ # \{#hsla hsla($hue, $saturation, $lightness, $alpha)}
+ # : Creates a {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.
+ #
+ # ## Number Functions
+ #
+ # \{#percentage percentage($value)}
+ # : Converts a unitless number to a percentage.
+ #
+ # \{#round round($value)}
+ # : Rounds a number to the nearest whole number.
+ #
+ # \{#ceil ceil($value)}
+ # : Rounds a number up to the next whole number.
+ #
+ # \{#floor floor($value)}
+ # : Rounds a number down to the previous whole number.
+ #
+ # \{#abs abs($value)}
+ # : 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.
+ #
+ # ## List Functions {#list-functions}
+ #
+ # \{#length length($list)}
+ # : Returns the length of a list.
+ #
+ # \{#nth nth($list, $n)}
+ # : Returns a specific 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.
+ #
+ # ## Introspection Functions
+ #
+ # \{#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($number-1, $number-2)}
+ # : Returns whether two numbers can be added, subtracted, or compared.
+ #
+ # ## Miscellaneous Functions
+ #
+ # \{#if if($condition, $if-true, $if-false)}
+ # : Returns one of two values, depending on whether or not `$condition` is
+ # true.
+ #
+ # ## 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::String.new(string.value.reverse)
+ # end
+ # declare :reverse, :args => [: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 {Sass::Script::Literal} objects.
+ # Literal objects are also expected to be returned.
+ # This means that Ruby values must be unwrapped and wrapped.
+ #
+ # Most Literal objects support the {Sass::Script::Literal#value value} accessor
+ # for getting their Ruby values.
+ # Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
+ # {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::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 {Literal} objects within functions,
+ # be aware that it's not safe to call {Literal#to_s #to_s}
+ # (or other methods that use the string representation)
+ # on those objects without first setting {Node#options= the #options attribute}.
+ module Functions
+ @signatures = {}
+
+ # A class representing a Sass function signature.
+ #
+ # @attr args [Array<Symbol>] The names of the arguments to the function.
+ # @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, :var_args, :var_kwargs)
+
+ # 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 {Sass::Script::Literal}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 = {})
+ @signatures[method_name] ||= []
+ @signatures[method_name] << Signature.new(
+ args.map {|s| s.to_s},
+ options[:var_args],
+ options[:var_kwargs])
+ 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 [Number] The number of unnamed arguments the function was passed.
+ # @param kwarg_arity [Number] 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|
+ return signature if signature.args.size == arg_arity + kwarg_arity
+ next unless signature.args.size < 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 signature.args.size > 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 -= (signature.args.size - t_arg_arity)
+ t_arg_arity = signature.args.size
+ end
+
+ if ( t_arg_arity == signature.args.size || t_arg_arity > signature.args.size &&
signature.var_args ) &&
+ (t_kwarg_arity == 0 || t_kwarg_arity > 0 &&
signature.var_kwargs)
+ return signature
+ end
+ end
+ @signatures[method_name].first
+ 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
+
+ # The options hash for the {Sass::Engine} that is processing the function call
+ #
+ # @return [{Symbol => Object}]
+ attr_reader :options
+
+ # @param options [{Symbol => Object}] See \{#options}
+ def initialize(options)
+ @options = 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::Literal] 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.
+ def assert_type(value, type, name = nil)
+ return if value.is_a?(Sass::Script.const_get(type))
+ err = "#{value.inspect} is not a #{type.to_s.downcase}"
+ err = "$#{name.to_s.gsub('_', '-')}: " + err if name
+ raise ArgumentError.new(err)
+ 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 {Color} object from red, green, and blue values.
+ #
+ # @see #rgba
+ # @overload rgb($red, $green, $blue)
+ # @param $red [Number] The amount of red in the color. Must be between 0 and
+ # 255 inclusive, or between `0%` and `100%` inclusive
+ # @param $green [Number] The amount of green in the color. Must be between 0
+ # and 255 inclusive, or between `0%` and `100%` inclusive
+ # @param $blue [Number] The amount of blue in the color. Must be between 0
+ # and 255 inclusive, or between `0%` and `100%` inclusive
+ # @return [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.new([[red, :red], [green, :green], [blue, :blue]].map do |(c, name)|
+ v = c.value
+ if c.numerator_units == ["%"] && c.denominator_units.empty?
+ v = Sass::Util.check_range("$#{name}: Color value", 0..100, c, '%')
+ v * 255 / 100.0
+ else
+ Sass::Util.check_range("$#{name}: Color value", 0..255, c)
+ end
+ end)
+ end
+ declare :rgb, [:red, :green, :blue]
+
+ # Creates a {Color} from red, green, blue, and alpha values.
+ # @see #rgb
+ #
+ # @overload rgba($red, $green, $blue, $alpha)
+ # @param $red [Number] The amount of red in the color. Must be between 0
+ # and 255 inclusive
+ # @param $green [Number] The amount of green in the color. Must be between
+ # 0 and 255 inclusive
+ # @param $blue [Number] The amount of blue in the color. Must be between 0
+ # and 255 inclusive
+ # @param $alpha [Number] The opacity of the color. Must be between 0 and 1
+ # inclusive
+ # @return [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 [Color] The color whose opacity will be changed.
+ # @param $alpha [Number] The new opacity of the color. Must be between 0
+ # and 1 inclusive
+ # @return [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
+
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
+ 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 {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 [Number] The hue of the color. Should be between 0 and 360
+ # degrees, inclusive
+ # @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
+ # @return [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.new(1))
+ end
+ declare :hsl, [:hue, :saturation, :lightness]
+
+ # Creates a {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 [Number] The hue of the color. Should be between 0 and 360
+ # degrees, inclusive
+ # @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 opacity of the color. Must be between 0 and 1,
+ # inclusive
+ # @return [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
+
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
+
+ h = hue.value
+ s = Sass::Util.check_range('Saturation', 0..100, saturation, '%')
+ l = Sass::Util.check_range('Lightness', 0..100, lightness, '%')
+
+ 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 [Color]
+ # @return [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
+ Sass::Script::Number.new(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 [Color]
+ # @return [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
+ Sass::Script::Number.new(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 [Color]
+ # @return [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
+ Sass::Script::Number.new(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 [Color]
+ # @return [Number] The hue component, between 0deg and 360deg
+ # @raise [ArgumentError] if `$color` isn't a color
+ def hue(color)
+ assert_type color, :Color, :color
+ Sass::Script::Number.new(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 [Color]
+ # @return [Number] The saturation component, between 0% and 100%
+ # @raise [ArgumentError] if `$color` isn't a color
+ def saturation(color)
+ assert_type color, :Color, :color
+ Sass::Script::Number.new(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 [Color]
+ # @return [Number] The lightness component, between 0% and 100%
+ # @raise [ArgumentError] if `$color` isn't a color
+ def lightness(color)
+ assert_type color, :Color, :color
+ Sass::Script::Number.new(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 [Color]
+ # @return [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::String) && a.type == :identifier &&
+ a.value =~ /^[a-zA-Z]+\s*=/
+ end
+ # Support the proprietary MS alpha() function
+ return Sass::Script::String.new("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
+ Sass::Script::Number.new(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 [Color]
+ # @return [Number] The alpha component, between 0 and 1
+ # @raise [ArgumentError] if `$color` isn't a color
+ def opacity(color)
+ return Sass::Script::String.new("opacity(#{color})") if color.is_a?(Sass::Script::Number)
+ assert_type color, :Color, :color
+ Sass::Script::Number.new(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 [Color]
+ # @param $amount [Number] The amount to increase the opacity by, between 0
+ # and 1
+ # @return [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 [Color]
+ # @param $amount [Number] The amount to decrease the opacity by, between 0
+ # and 1
+ # @return [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 [Color]
+ # @param $amount [Number] The amount to increase the lightness by, between
+ # `0%` and `100%`
+ # @return [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 [Color]
+ # @param $amount [Number] The amount to dencrease the lightness by, between
+ # `0%` and `100%`
+ # @return [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 [Color]
+ # @param $amount [Number] The amount to increase the saturation by, between
+ # `0%` and `100%`
+ # @return [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 Sass::Script::String.new("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 [Color]
+ # @param $amount [Number] The amount to decrease the saturation by, between
+ # `0%` and `100%`
+ # @return [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%), 060deg) => hsl(60, 30%, 90%)
+ # adjust-hue(#811, 45deg) => #886a11
+ # @overload adjust_hue($color, $degrees)
+ # @param $color [Color]
+ # @param $degrees [Number] The number of degrees to rotate the hue
+ # @return [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 [Color]
+ # @return [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')
+ Sass::Script::String.new("##{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)
+ # @overload adjust_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness],
[$alpha])
+ # @param $color [Color]
+ # @param $red [Number] The adjustment to make on the red component, between
+ # -255 and 255 inclusive
+ # @param $green [Number] The adjustment to make on the green component,
+ # between -255 and 255 inclusive
+ # @param $blue [Number] The adjustment to make on the blue component, between
+ # -255 and 255 inclusive
+ # @param $hue [Number] The adjustment to make on the hue component, in
+ # degrees
+ # @param $saturation [Number] The adjustment to make on the saturation
+ # component, between `-100%` and `100%` inclusive
+ # @param $lightness [Number] The adjustment to make on the lightness
+ # component, between `-100%` and `100%` inclusive
+ # @param $alpha [Number] The adjustment to make on the alpha component,
+ # between -1 and 1 inclusive
+ # @return [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)|
+
+ next unless val = kwargs.delete(name)
+ 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)
+ # @overload scale_color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha])
+ # @param $color [Color]
+ # @param $red [Number]
+ # @param $green [Number]
+ # @param $blue [Number]
+ # @param $saturation [Number]
+ # @param $lightness [Number]
+ # @param $alpha [Number]
+ # @return [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|
+
+ next unless val = kwargs.delete(name)
+ assert_type val, :Number, name
+ if !(val.numerator_units == ['%'] && val.denominator_units.empty?)
+ raise ArgumentError.new("$#{name}: Amount #{val} must be a % (e.g. #{val.value}%)")
+ else
+ Sass::Util.check_range("$#{name}: Amount", -100..100, val, '%')
+ end
+
+ 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)
+ # @overload change_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness],
[$alpha])
+ # @param $color [Color]
+ # @param $red [Number] The new red component for the color, within 0 and 255
+ # inclusive
+ # @param $green [Number] The new green component for the color, within 0 and
+ # 255 inclusive
+ # @param $blue [Number] The new blue component for the color, within 0 and
+ # 255 inclusive
+ # @param $hue [Number] The new hue component for the color, in degrees
+ # @param $saturation [Number] The new saturation component for the color,
+ # between `0%` and `100%` inclusive
+ # @param $lightness [Number] The new lightness component for the color,
+ # within `0%` and `100%` inclusive
+ # @param $alpha [Number] The new alpha component for the color, within 0 and
+ # 1 inclusive
+ # @return [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(%w[red green blue hue saturation lightness alpha]) do |name, max|
+ next unless val = kwargs.delete(name)
+ assert_type val, :Number, name
+ [name.to_sym, val.value]
+ 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($color-1, $color-2, $weight: 50%)
+ # @param $color-1 [Color]
+ # @param $color-2 [Color]
+ # @param $weight [Number] The relative weight of each color. Closer to `0%`
+ # gives more weight to `$color`, closer to `100%` gives more weight to
+ # `$color2`
+ # @return [Color]
+ # @raise [ArgumentError] if `$weight` is out of bounds or any parameter is
+ # the wrong type
+ def mix(color_1, color_2, weight = Number.new(50))
+ assert_type color_1, :Color, :color_1
+ assert_type color_2, :Color, :color_2
+ 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 color_1", -1 indicates "only use color_2", 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 color_1.
+ # 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 color_1 is renormalized to be within [0, 1]
+ # and the weight of color_2 is given by 1 minus the weight of color_1.
+ p = (weight.value/100.0).to_f
+ w = p*2 - 1
+ a = color_1.alpha - color_2.alpha
+
+ w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0
+ w2 = 1 - w1
+
+ rgb = color_1.rgb.zip(color_2.rgb).map {|v1, v2| v1*w1 + v2*w2}
+ alpha = color_1.alpha*p + color_2.alpha*(1-p)
+ Color.new(rgb + [alpha])
+ end
+ declare :mix, [:color_1, :color_2]
+ declare :mix, [:color_1, :color_2, :weight]
+
+ # Converts a color to grayscale. This is identical to `desaturate(color,
+ # 100%)`.
+ #
+ # @see #desaturate
+ # @overload grayscale($color)
+ # @param $color [Color]
+ # @return [Color]
+ # @raise [ArgumentError] if `$color` isn't a color
+ def grayscale(color)
+ return Sass::Script::String.new("grayscale(#{color})") if color.is_a?(Sass::Script::Number)
+ desaturate color, Number.new(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 [Color]
+ # @return [Color]
+ # @raise [ArgumentError] if `$color` isn't a color
+ def complement(color)
+ adjust_hue color, Number.new(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 [Color]
+ # @return [Color]
+ # @raise [ArgumentError] if `$color` isn't a color
+ def invert(color)
+ return Sass::Script::String.new("invert(#{color})") if color.is_a?(Sass::Script::Number)
+
+ 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 [String]
+ # @return [String]
+ # @raise [ArgumentError] if `$string` isn't a string
+ def unquote(string)
+ if string.is_a?(Sass::Script::String)
+ Sass::Script::String.new(string.value, :identifier)
+ 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 [String]
+ # @return [String]
+ # @raise [ArgumentError] if `$string` isn't a string
+ def quote(string)
+ assert_type string, :String, :string
+ Sass::Script::String.new(string.value, :string)
+ end
+ declare :quote, [: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 [Literal] The value to inspect
+ # @return [String] The unquoted string name of the value's type
+ def type_of(value)
+ Sass::Script::String.new(value.class.name.gsub(/Sass::Script::/,'').downcase)
+ end
+ declare :type_of, [:value]
+
+ # 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 [Number]
+ # @return [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
+ Sass::Script::String.new(number.unit_str, :string)
+ end
+ declare :unit, [:number]
+
+ # Returns whether a number has units.
+ #
+ # @example
+ # unitless(100) => true
+ # unitless(100px) => false
+ # @overload unitless($number)
+ # @param $number [Number]
+ # @return [Bool]
+ # @raise [ArgumentError] if `$number` isn't a number
+ def unitless(number)
+ assert_type number, :Number, :number
+ Sass::Script::Bool.new(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($number-1, $number-2)
+ # @param $number-1 [Number]
+ # @param $number-2 [Number]
+ # @return [Bool]
+ # @raise [ArgumentError] if either parameter is the wrong type
+ def comparable(number_1, number_2)
+ assert_type number_1, :Number, :number_1
+ assert_type number_2, :Number, :number_2
+ Sass::Script::Bool.new(number_1.comparable_to?(number_2))
+ end
+ declare :comparable, [:number_1, :number_2]
+
+ # Converts a unitless number to a percentage.
+ #
+ # @example
+ # percentage(0.2) => 20%
+ # percentage(100px / 50px) => 200%
+ # @overload percentage($value)
+ # @param $value [Number]
+ # @return [Number]
+ # @raise [ArgumentError] if `$value` isn't a unitless number
+ def percentage(value)
+ unless value.is_a?(Sass::Script::Number) && value.unitless?
+ raise ArgumentError.new("$value: #{value.inspect} is not a unitless number")
+ end
+ Sass::Script::Number.new(value.value * 100, ['%'])
+ end
+ declare :percentage, [:value]
+
+ # Rounds a number to the nearest whole number.
+ #
+ # @example
+ # round(10.4px) => 10px
+ # round(10.6px) => 11px
+ # @overload round($value)
+ # @param $value [Number]
+ # @return [Number]
+ # @raise [ArgumentError] if `$value` isn't a number
+ def round(value)
+ numeric_transformation(value) {|n| n.round}
+ end
+ declare :round, [:value]
+
+ # Rounds a number up to the next whole number.
+ #
+ # @example
+ # ceil(10.4px) => 11px
+ # ceil(10.6px) => 11px
+ # @overload ceil($value)
+ # @param $value [Number]
+ # @return [Number]
+ # @raise [ArgumentError] if `$value` isn't a number
+ def ceil(value)
+ numeric_transformation(value) {|n| n.ceil}
+ end
+ declare :ceil, [:value]
+
+ # Rounds a number down to the previous whole number.
+ #
+ # @example
+ # floor(10.4px) => 10px
+ # floor(10.6px) => 10px
+ # @overload floor($value)
+ # @param $value [Number]
+ # @return [Number]
+ # @raise [ArgumentError] if `$value` isn't a number
+ def floor(value)
+ numeric_transformation(value) {|n| n.floor}
+ end
+ declare :floor, [:value]
+
+ # Returns the absolute value of a number.
+ #
+ # @example
+ # abs(10px) => 10px
+ # abs(-10px) => 10px
+ # @overload abs($value)
+ # @param $value [Number]
+ # @return [Number]
+ # @raise [ArgumentError] if `$value` isn't a number
+ def abs(value)
+ numeric_transformation(value) {|n| n.abs}
+ end
+ declare :abs, [:value]
+
+ # 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 [[Number]]
+ # @return [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 [[Number]]
+ # @return [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.
+ #
+ # @example
+ # length(10px) => 1
+ # length(10px 20px 30px) => 3
+ # @overload length($list)
+ # @param $list [Literal]
+ # @return [Number]
+ def length(list)
+ Sass::Script::Number.new(list.to_a.size)
+ end
+ declare :length, [:list]
+
+ # 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.
+ #
+ # @example
+ # nth(10px 20px 30px, 1) => 10px
+ # nth((Helvetica, Arial, sans-serif), 3) => sans-serif
+ # @overload nth($list, $n)
+ # @param $list [Literal]
+ # @param $n [Number] The index of the item to get
+ # @return [Literal]
+ # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length
+ # of `$list`
+ def nth(list, n)
+ assert_type n, :Number, :n
+ if !n.int?
+ raise ArgumentError.new("List index #{n} must be an integer")
+ elsif n.to_i < 1
+ raise ArgumentError.new("List index #{n} must be greater than or equal to 1")
+ elsif list.to_a.size == 0
+ raise ArgumentError.new("List index is #{n} but list has no items")
+ elsif n.to_i > (size = list.to_a.size)
+ raise ArgumentError.new("List index is #{n} but list is only #{size} item#{'s' if size != 1} long")
+ end
+
+ list.to_a[n.to_i - 1]
+ 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 [Literal]
+ # @param $list2 [Literal]
+ # @param $separator [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 [List]
+ def join(list1, list2, separator = Sass::Script::String.new("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
+ sep1 = list1.separator if list1.is_a?(Sass::Script::List) && !list1.value.empty?
+ sep2 = list2.separator if list2.is_a?(Sass::Script::List) && !list2.value.empty?
+ Sass::Script::List.new(
+ list1.to_a + list2.to_a,
+ if separator.value == 'auto'
+ sep1 || sep2 || :space
+ else
+ separator.value.to_sym
+ end)
+ 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 [Literal]
+ # @param $val [Literal]
+ # @param $separator [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 [List]
+ def append(list, val, separator = Sass::Script::String.new("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 = list.separator if list.is_a?(Sass::Script::List)
+ Sass::Script::List.new(
+ list.to_a + [val],
+ if separator.value == 'auto'
+ sep || :space
+ else
+ separator.value.to_sym
+ end)
+ 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 [[Literal]]
+ # @return [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(new_list_value.map{|list| List.new(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 false instead.
+ #
+ # Note that unlike some languages, the first item in a Sass list is number
+ # 1, the second number 2, and so forth.
+ #
+ # @example
+ # index(1px solid red, solid) => 2
+ # index(1px solid red, dashed) => false
+ # @overload index($list, $value)
+ # @param $list [Literal]
+ # @param $value [Literal]
+ # @return [Number, Bool] The 1-based index of `$value` in `$list`, or
+ # `false`
+ def index(list, value)
+ index = list.to_a.index {|e| e.eq(value).to_bool }
+ if index
+ Number.new(index + 1)
+ else
+ Bool.new(false)
+ end
+ end
+ declare :index, [:list, :value]
+
+ # 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 [Literal] Whether the `$if-true` or `$if-false` will be
+ # returned
+ # @param $if-true [Literal]
+ # @param $if-false [Literal]
+ # @return [Literal] `$if-true` or `$if-false`
+ def if(condition, if_true, if_false)
+ if condition.to_bool
+ if_true
+ else
+ if_false
+ end
+ end
+ declare :if, [:condition, :if_true, :if_false]
+
+ # This function only exists as a workaround for IE7's [`content: counter`
+ # bug][bug]. It works identically to any other plain-CSS function, except it
+ # avoids adding spaces between the argument commas.
+ #
+ # [bug]: http://jes.st/2013/ie7s-css-breaking-content-counter-bug/
+ #
+ # @example
+ # counter(item, ".") => counter(item,".")
+ # @overload counter($args...)
+ # @return [String]
+ def counter(*args)
+ Sass::Script::String.new("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: counters`
+ # bug][bug]. It works identically to any other plain-CSS function, except it
+ # avoids adding spaces between the argument commas.
+ #
+ # [bug]: http://jes.st/2013/ie7s-css-breaking-content-counter-bug/
+ #
+ # @example
+ # counters(item, ".") => counters(item,".")
+ # @overload counters($args...)
+ # @return [String]
+ def counters(*args)
+ Sass::Script::String.new("counters(#{args.map {|a| a.to_s(options)}.join(',')})")
+ end
+ declare :counters, [], :var_args => true
+
+ 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::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
+ end
+
+ def _adjust(color, amount, attr, range, op, units = "")
+ assert_type color, :Color, :color
+ assert_type amount, :Number, :amount
+ Sass::Util.check_range('Amount', range, amount, units)
+
+ # TODO: is it worth restricting here,
+ # or should we do so in the Color constructor itself,
+ # and allow clipping in rgb() et al?
+ color.with(attr => Sass::Util.restrict(
+ color.send(attr).send(op, amount.value), range))
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/interpolation.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/interpolation.rb
new file mode 100644
index 0000000..53902b1
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/interpolation.rb
@@ -0,0 +1,79 @@
+module Sass::Script
+ # A SassScript object representing `#{}` interpolation outside a string.
+ #
+ # @see StringInterpolation
+ class Interpolation < Node
+ # Interpolation in a property is of the form `before #{mid} after`.
+ #
+ # @param before [Node] The SassScript before the interpolation
+ # @param mid [Node] The SassScript within the interpolation
+ # @param after [Node] The SassScript after the interpolation
+ # @param wb [Boolean] Whether there was whitespace between `before` and `#{`
+ # @param wa [Boolean] Whether there was whitespace between `}` and `after`
+ # @param originally_text [Boolean]
+ # Whether the original format of the interpolation was plain text,
+ # not an interpolation.
+ # This is used when converting back to SassScript.
+ def initialize(before, mid, after, wb, wa, originally_text = false)
+ @before = before
+ @mid = mid
+ @after = after
+ @whitespace_before = wb
+ @whitespace_after = wa
+ @originally_text = originally_text
+ 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::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)
+ res << (val.is_a?(Sass::Script::String) ? val.value : val.to_s)
+ res << " " if @after && @whitespace_after
+ res << @after.perform(environment).to_s if @after
+ opts(Sass::Script::String.new(res))
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/lexer.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/lexer.rb
new file mode 100644
index 0000000..352c4a3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/lexer.rb
@@ -0,0 +1,348 @@
+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.
+ #
+ # `line`: \[`Fixnum`\]
+ # : The line of the source file on which the token appears.
+ #
+ # `offset`: \[`Fixnum`\]
+ # : The number of bytes into the line the SassScript token appeared.
+ #
+ # `pos`: \[`Fixnum`\]
+ # : The scanner position at which the SassScript token appeared.
+ Token = Struct.new(:type, :value, :line, :offset, :pos)
+
+ # The line number of the lexer's current position.
+ #
+ # @return [Fixnum]
+ attr_reader :line
+
+ # The number of bytes into the current line
+ # of the lexer's current position.
+ #
+ # @return [Fixnum]
+ attr_reader :offset
+
+ # 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)",
+ :bool => "boolean (e.g. true, false)",
+ })
+
+ # 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+/}
+
+ # 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 => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
+ :color => HEXCOLOR,
+ :bool => /(true|false)\b/,
+ :null => /null\b/,
+ :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) +
"(?!#{NMCHAR}|\Z)")})})},
+ :op => %r{(#{Regexp.union(*OP_NAMES)})},
+ }
+
+ class << self
+ private
+ def string_re(open, close)
+ /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/
+ 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('"', '"'),
+ [:single, false] => string_re("'", "'"),
+ [:double, true] => string_re('', '"'),
+ [:single, true] => string_re('', "'"),
+ [:uri, false] => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
+ [:uri, 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}\)|#\{)/,
+ [:url_prefix, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
+ [:domain, false] => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
+ [:domain, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
+ }
+
+ # @param str [String, StringScanner] The source text to lex
+ # @param line [Fixnum] The line on which the SassScript appears.
+ # Used for error reporting
+ # @param offset [Fixnum] The number of characters in on which the SassScript appears.
+ # Used for error reporting
+ # @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
+ return 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!
+ @scanner.pos = @tok.pos if @tok
+ 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?
+ return unless value = token
+ type, val, size = value
+ size ||= @scanner.matched_size
+
+ val.line = @line if val.is_a?(Script::Node)
+ Token.new(type, val, @line,
+ current_position - size, @scanner.pos - 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_type = @interpolation_stack.pop)
+ return string(interp_type, true)
+ end
+
+ variable || string(:double, false) || string(:single, false) || number ||
+ color || bool || null || 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)
+ return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
+ if @scanner[2] == '#{' #'
+ @scanner.pos -= 2 # Don't actually consume the #{
+ @interpolation_stack << re
+ end
+ str =
+ if re == :uri
+ Script::String.new("#{'url(' unless open}#{ scanner[1]}#{')' unless @scanner[2] == '#{'}")
+ else
+ Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
+ end
+ [:string, str]
+ end
+
+ def number
+ return unless scan(REGULAR_EXPRESSIONS[:number])
+ value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
+ value = -value if @scanner[1]
+ [:number, Script::Number.new(value, Array(@scanner[4]))]
+ end
+
+ def color
+ return unless s = scan(REGULAR_EXPRESSIONS[:color])
+ raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
+Colors must have either three or six digits: '#{s}'
+MESSAGE
+ value = s.scan(/^#(..?)(..?)(..?)$/).first.
+ map {|num| num.ljust(2, num).to_i(16)}
+ [:color, Script::Color.new(value)]
+ end
+
+ def bool
+ return unless s = scan(REGULAR_EXPRESSIONS[:bool])
+ [:bool, Script::Bool.new(s == 'true')]
+ end
+
+ def null
+ return unless scan(REGULAR_EXPRESSIONS[:null])
+ [:null, Script::Null.new]
+ end
+
+ def special_fun
+ return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
+ str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
+ c = str2.count("\n")
+ old_line = @line
+ old_offset = @offset
+ @line += c
+ @offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
+ [:special_fun,
+ Sass::Util.merge_adjacent_strings(
+ [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
+ str1.size + str2.size]
+ end
+
+ def special_val
+ return unless scan(/!important/i)
+ [:string, Script::String.new("!important")]
+ end
+
+ def ident_op
+ return unless op = scan(REGULAR_EXPRESSIONS[:ident_op])
+ [OPERATORS[op]]
+ end
+
+ def op
+ return unless op = scan(REGULAR_EXPRESSIONS[:op])
+ @interpolation_stack << nil if op == :begin_interpolation
+ [OPERATORS[op]]
+ end
+
+ def raw(rx)
+ return unless val = scan(rx)
+ [:raw, val]
+ end
+
+ def scan(re)
+ return unless str = @scanner.scan(re)
+ c = str.count("\n")
+ @line += c
+ @offset = (c == 0 ? @offset + str.size : str[/\n(.*)/, 1].size)
+ str
+ end
+
+ def current_position
+ @offset + 1
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/list.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/list.rb
new file mode 100644
index 0000000..1f3dfd9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/list.rb
@@ -0,0 +1,85 @@
+module Sass::Script
+ # A SassScript object representing a CSS list.
+ # This includes both comma-separated lists and space-separated lists.
+ class List < Literal
+ # The Ruby array containing the contents of the list.
+ #
+ # @return [Array<Literal>]
+ attr_reader :value
+ alias_method :children, :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<Literal>] See \{#value}
+ # @param separator [String] See \{#separator}
+ def initialize(value, separator)
+ super(value)
+ @separator = separator
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ node = dup
+ node.instance_variable_set('@value', value.map {|c| c.deep_copy})
+ node
+ end
+
+ # @see Node#eq
+ def eq(other)
+ Sass::Script::Bool.new(
+ other.is_a?(List) && self.value == other.value &&
+ self.separator == other.separator)
+ end
+
+ # @see Node#to_s
+ def to_s(opts = {})
+ raise Sass::SyntaxError.new("() isn't a valid CSS value.") if value.empty?
+ return 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 Node#to_sass
+ def to_sass(opts = {})
+ return "()" if value.empty?
+ precedence = Sass::Script::Parser.precedence_of(separator)
+ value.reject {|e| e.is_a?(Null)}.map do |v|
+ if v.is_a?(List) && 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.join(sep_str(nil))
+ end
+
+ # @see Node#inspect
+ def inspect
+ "(#{to_sass})"
+ end
+
+ protected
+
+ # @see Node#_perform
+ def _perform(environment)
+ list = Sass::Script::List.new(
+ value.map {|e| e.perform(environment)},
+ separator)
+ list.options = self.options
+ list
+ end
+
+ private
+
+ def sep_str(opts = self.options)
+ return ' ' if separator == :space
+ return ',' if opts && opts[:style] == :compressed
+ return ', '
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/literal.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/literal.rb
new file mode 100644
index 0000000..daef468
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/literal.rb
@@ -0,0 +1,221 @@
+module Sass::Script
+ # 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 Literal < Node
+ require 'sass/script/string'
+ require 'sass/script/number'
+ require 'sass/script/color'
+ require 'sass/script/bool'
+ require 'sass/script/null'
+ require 'sass/script/list'
+ require 'sass/script/arg_list'
+
+ # Returns the Ruby value of the literal.
+ # The type of this value varies based on the subclass.
+ #
+ # @return [Object]
+ attr_reader :value
+
+ # Creates a new literal.
+ #
+ # @param value [Object] The object for \{#value}
+ def initialize(value = nil)
+ @value = value
+ super()
+ end
+
+ # Returns an empty array.
+ #
+ # @return [Array<Node>] empty
+ # @see Node#children
+ def children
+ []
+ end
+
+ # @see Node#deep_copy
+ def deep_copy
+ dup
+ end
+
+ # 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 literal was created
+ # outside of the parser and \{#to\_s} was called on it
+ def options
+ opts = super
+ return opts if opts
+ 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 literal within a custom Sass function without first
+ setting the #option attribute.
+MSG
+ end
+
+ # The SassScript `==` operation.
+ # **Note that this returns a {Sass::Script::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Bool] True if this literal is the same as the other,
+ # false otherwise
+ def eq(other)
+ Sass::Script::Bool.new(self.class == other.class && self.value == other.value)
+ end
+
+ # The SassScript `!=` operation.
+ # **Note that this returns a {Sass::Script::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Bool] False if this literal is the same as the other,
+ # true otherwise
+ def neq(other)
+ Sass::Script::Bool.new(!eq(other).to_bool)
+ end
+
+ # The SassScript `==` operation.
+ # **Note that this returns a {Sass::Script::Bool} object,
+ # not a Ruby boolean**.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Bool] True if this literal is the same as the other,
+ # false otherwise
+ def unary_not
+ Sass::Script::Bool.new(!to_bool)
+ end
+
+ # The SassScript `=` operation
+ # (used for proprietary MS syntax like `alpha(opacity=20)`).
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Script::String] A string containing both literals
+ # separated by `"="`
+ def single_eq(other)
+ Sass::Script::String.new("#{self.to_s}=#{other.to_s}")
+ end
+
+ # The SassScript `+` operation.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Script::String] A string containing both literals
+ # without any separation
+ def plus(other)
+ if other.is_a?(Sass::Script::String)
+ return Sass::Script::String.new(self.to_s + other.value, other.type)
+ end
+ Sass::Script::String.new(self.to_s + other.to_s)
+ end
+
+ # The SassScript `-` operation.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Script::String] A string containing both literals
+ # separated by `"-"`
+ def minus(other)
+ Sass::Script::String.new("#{self.to_s}-#{other.to_s}")
+ end
+
+ # The SassScript `/` operation.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Script::String] A string containing both literals
+ # separated by `"/"`
+ def div(other)
+ Sass::Script::String.new("#{self.to_s}/#{other.to_s}")
+ end
+
+ # The SassScript unary `+` operation (e.g. `+$a`).
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Script::String] A string containing the literal
+ # preceded by `"+"`
+ def unary_plus
+ Sass::Script::String.new("+#{self.to_s}")
+ end
+
+ # The SassScript unary `-` operation (e.g. `-$a`).
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Script::String] A string containing the literal
+ # preceded by `"-"`
+ def unary_minus
+ Sass::Script::String.new("-#{self.to_s}")
+ end
+
+ # The SassScript unary `/` operation (e.g. `/$a`).
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Script::String] A string containing the literal
+ # preceded by `"/"`
+ def unary_div
+ Sass::Script::String.new("/#{self.to_s}")
+ end
+
+ # @return [String] A readable representation of the literal
+ 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 literal is equivalent to `other`
+ def ==(other)
+ eq(other).to_bool
+ end
+
+ # @return [Fixnum] The integer value of this literal
+ # @raise [Sass::SyntaxError] if this literal isn't an integer
+ def to_i
+ raise Sass::SyntaxError.new("#{self.inspect} is not an integer.")
+ end
+
+ # @raise [Sass::SyntaxError] if this literal isn't an integer
+ def assert_int!; to_i; end
+
+ # Returns the value of this literal as a list.
+ # Single literals are considered the same as single-element lists.
+ #
+ # @return [Array<Literal>] The of this literal as a list
+ def to_a
+ [self]
+ end
+
+ # Returns the string representation of this literal
+ # as it would be output to the CSS document.
+ #
+ # @return [String]
+ def to_s(opts = {})
+ raise Sass::SyntaxError.new("[BUG] All subclasses of Sass::Literal must implement #to_s.")
+ 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 literal.
+ #
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
+ # @return [Literal] This literal
+ def _perform(environment)
+ self
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/node.rb
new file mode 100644
index 0000000..bac6dd2
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/node.rb
@@ -0,0 +1,99 @@
+module Sass::Script
+ # 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
+
+ # 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 [Literal] 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 {Literal} 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 [Literal] 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 literal and returns it
+ #
+ # @param literal [Literal]
+ # @return [Literal]
+ def opts(literal)
+ literal.options = options
+ literal
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/null.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/null.rb
new file mode 100644
index 0000000..6f5b45f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/null.rb
@@ -0,0 +1,37 @@
+require 'sass/script/literal'
+
+module Sass::Script
+ # A SassScript object representing a null value.
+ class Null < Literal
+ # Creates a new null literal.
+ def initialize
+ super nil
+ 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.2.12/lib/sass/script/number.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/number.rb
new file mode 100644
index 0000000..1eac160
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/number.rb
@@ -0,0 +1,453 @@
+require 'sass/script/literal'
+
+module Sass::Script
+ # 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 < Literal
+ # 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
+
+ # Handles the deprecation warning for the PRECISION constant
+ # This can be removed in 3.2.
+ def self.const_missing(const)
+ if const == :PRECISION
+ Sass::Util.sass_warn("Sass::Script::Number::PRECISION is deprecated and will be removed in a future
release. Use Sass::Script::Number.precision_factor instead.")
+ const_set(:PRECISION, self.precision_factor)
+ else
+ super
+ end
+ 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 [Array<String>] See \{#numerator\_units}
+ # @param denominator_units [Array<String>] See \{#denominator\_units}
+ def initialize(value, numerator_units = NO_UNITS, denominator_units = NO_UNITS)
+ 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.
+ #
+ # {Literal}
+ # : See {Literal#plus}.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Literal] 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.
+ #
+ # {Literal}
+ # : See {Literal#minus}.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Literal] 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.
+ #
+ # {Literal}
+ # : See {Literal#div}.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Literal] The result of the operation
+ def div(other)
+ if other.is_a? Number
+ res = operate(other, :/)
+ if self.original && other.original
+ res.original = "#{self.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 any units
+ def mod(other)
+ if other.is_a?(Number)
+ unless other.unitless?
+ raise Sass::UnitConversionError.new("Cannot modulo by a number with units: #{other.inspect}.")
+ end
+ operate(other, :%)
+ else
+ raise NoMethodError.new(nil, :mod)
+ end
+ end
+
+ # The SassScript `==` operation.
+ #
+ # @param other [Literal] The right-hand side of the operator
+ # @return [Boolean] Whether this number is equal to the other object
+ def eq(other)
+ return Sass::Script::Bool.new(false) unless other.is_a?(Sass::Script::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 Sass::Script::Bool.new(false)
+ end
+
+ Sass::Script::Bool.new(this.value == other.value)
+ 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 = {})
+ value = self.class.round(self.value)
+ unitless? ? value.to_s : "#{value}#{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?
+ return value
+ 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
+
+ # @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?
+ self.value
+ else
+ self.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)
+ begin
+ operate(other, :+)
+ true
+ rescue Sass::UnitConversionError
+ false
+ end
+ 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 * self.precision_factor).round / self.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.detect(&method(:convertable?)))
+ @value /= conversion_factor(d, u)
+ @denominator_units.delete_at(i)
+ @numerator_units.delete_at(@numerator_units.index(u))
+ end
+ end
+ end
+
+ # A hash of unit names to their index in the conversion table
+ CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4, "px" => 5
}
+ CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 , 96
], # in
+ [ nil, 1, 2.36220473, 10, 28.3464567,
37.795275591], # cm
+ [ nil, nil, 1, 4.23333333, 12 , 16
], # pc
+ [ nil, nil, nil, 1, 2.83464567,
3.7795275591], # mm
+ [ nil, nil, nil, nil, 1 ,
1.3333333333], # pt
+ [ nil, nil, nil, nil, nil , 1
]] # px
+
+ def conversion_factor(from_unit, to_unit)
+ res = CONVERSION_TABLE[CONVERTABLE_UNITS[from_unit]][CONVERTABLE_UNITS[to_unit]]
+ return 1.0 / conversion_factor(to_unit, from_unit) if res.nil?
+ res
+ end
+
+ def convertable?(units)
+ Array(units).all? {|u| CONVERTABLE_UNITS.include?(u)}
+ 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
+ return units1.map do |u|
+ next u unless j = units2.index(u)
+ units2.delete_at(j)
+ nil
+ end.compact, units2
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/operation.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/operation.rb
new file mode 100644
index 0000000..8827c26
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/operation.rb
@@ -0,0 +1,110 @@
+require 'set'
+require 'sass/script/string'
+require 'sass/script/number'
+require 'sass/script/color'
+require 'sass/script/functions'
+require 'sass/script/unary_operation'
+require 'sass/script/interpolation'
+require 'sass/script/string_interpolation'
+
+module Sass::Script
+ # 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 [Script::Node] The parse-tree node
+ # for the right-hand side of the operator
+ # @param operand2 [Script::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 {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; " #{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 [Literal] The SassScript object that is the value of the operation
+ # @raise [Sass::SyntaxError] if the operation is undefined for the operands
+ def _perform(environment)
+ literal1 = @operand1.perform(environment)
+
+ # Special-case :and and :or to support short-circuiting.
+ if @operator == :and
+ return literal1.to_bool ? @operand2.perform(environment) : literal1
+ elsif @operator == :or
+ return literal1.to_bool ? literal1 : @operand2.perform(environment)
+ end
+
+ literal2 = @operand2.perform(environment)
+
+ if (literal1.is_a?(Null) || literal2.is_a?(Null)) && @operator != :eq && @operator != :neq
+ raise Sass::SyntaxError.new("Invalid null operation: \"#{literal1.inspect} #{ operator}
#{literal2.inspect}\".")
+ end
+
+ begin
+ opts(literal1.send(@operator, literal2))
+ rescue NoMethodError => e
+ raise e unless e.name.to_s == @operator.to_s
+ raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{ operator} #{literal2}\".")
+ end
+ end
+
+ private
+
+ def operand_to_sass(op, side, opts)
+ return "(#{op.to_sass(opts)})" if op.is_a?(List)
+ 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.2.12/lib/sass/script/parser.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/parser.rb
new file mode 100644
index 0000000..da3a7ef
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/parser.rb
@@ -0,0 +1,495 @@
+require 'sass/script/lexer'
+
+module Sass
+ module Script
+ # The parser for SassScript.
+ # It parses a string of code into a tree of {Script::Node}s.
+ class Parser
+ # The line number of the parser's current position.
+ #
+ # @return [Fixnum]
+ def line
+ @lexer.line
+ end
+
+ # @param str [String, StringScanner] The source text to parse
+ # @param line [Fixnum] The line on which the SassScript appears.
+ # Used for error reporting
+ # @param offset [Fixnum] The number of characters in on which the SassScript appears.
+ # Used for error reporting
+ # @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.
+ #
+ # @return [Script::Node] The root node of the parse tree
+ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
+ def parse_interpolated
+ expr = assert_expr :expr
+ assert_tok :end_interpolation
+ expr.options = @options
+ expr
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
+ raise e
+ end
+
+ # Parses a SassScript expression.
+ #
+ # @return [Script::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 [#include?(String)] A set of strings that delimit the expression.
+ # @return [Script::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::Node>, {String => Script::Node}, Script::Node)]
+ # The root nodes of the positional arguments, keyword arguments, and
+ # splat argument. 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 = 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
+ return args, keywords, 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::Node>, Script::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::Node>, Script::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::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::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}"
+ 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}) and return interp
+ return unless e = #{sub}
+ while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
+ if interp = try_op_before_interp(tok, e)
+ return interp unless other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect},
interp)
+ return other_interp
+ end
+
+ line = @lexer.line
+ e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
+ e.line = line
+ 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) and return interp
+ line = @lexer.line
+ op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
+ op.line = line
+ op
+ end
+RUBY
+ end
+ end
+
+ private
+
+ # @private
+ def lexer_class; Lexer; end
+
+ def expr
+ line = @lexer.line
+ return unless e = interpolation
+ list = node(List.new([e], :comma), line)
+ while tok = try_tok(:comma)
+ if interp = try_op_before_interp(tok, list)
+ return interp unless other_interp = try_ops_after_interp([:comma], :expr, interp)
+ return other_interp
+ end
+ list.value << assert_expr(:interpolation)
+ end
+ list.value.size == 1 ? list.value.first : 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 = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
+ str.line = @lexer.line
+ interp = Script::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text)
+ interp.line = @lexer.line
+ interpolation(interp)
+ end
+
+ def try_ops_after_interp(ops, name, prev = nil)
+ return unless @lexer.after_interpolation?
+ return unless op = try_tok(*ops)
+ interp = try_op_before_interp(op, prev) and return interp
+
+ wa = @lexer.whitespace?
+ str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
+ str.line = @lexer.line
+ interp = Script::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text)
+ interp.line = @lexer.line
+ return interp
+ end
+
+ def interpolation(first = space)
+ e = first
+ while interp = try_tok(:begin_interpolation)
+ wb = @lexer.whitespace?(interp)
+ line = @lexer.line
+ mid = parse_interpolated
+ wa = @lexer.whitespace?
+ e = Script::Interpolation.new(e, mid, space, wb, wa)
+ e.line = line
+ end
+ e
+ end
+
+ def space
+ line = @lexer.line
+ return unless e = or_expr
+ arr = [e]
+ while e = or_expr
+ arr << e
+ end
+ arr.size == 1 ? arr.first : node(List.new(arr, :space), line)
+ 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 = Color::COLOR_NAMES[name.value.downcase]
+ return node(Color.new(color))
+ end
+ node(Script::String.new(name.value, :identifier))
+ end
+
+ def funcall
+ return raw unless tok = try_tok(:funcall)
+ args, keywords, splat = fn_arglist || [[], {}]
+ assert_tok(:rparen)
+ node(Script::Funcall.new(tok.value, args, keywords, splat))
+ 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 = Script::Variable.new(c.value)
+ if try_tok(:colon)
+ val = assert_expr(:space)
+ must_have_default = true
+ elsif must_have_default
+ raise SyntaxError.new("Required argument #{var.inspect} must come before any optional
arguments.")
+ elsif try_tok(:splat)
+ splat = var
+ break
+ 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)
+ return unless e = send(subexpr)
+
+ args = []
+ keywords = {}
+ loop do
+ if @lexer.peek && @lexer.peek.type == :colon
+ name = e
+ @lexer.expected!("comma") unless name.is_a?(Variable)
+ assert_tok(:colon)
+ value = assert_expr(subexpr, description)
+
+ if keywords[name.underscored_name]
+ raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
+ end
+
+ keywords[name.underscored_name] = value
+ else
+ if !keywords.empty?
+ raise SyntaxError.new("Positional arguments must come before keyword arguments.")
+ end
+
+ return args, keywords, e if try_tok(:splat)
+ args << e
+ end
+
+ return args, keywords unless try_tok(:comma)
+ e = assert_expr(subexpr, description)
+ end
+ end
+
+ def raw
+ return special_fun unless tok = try_tok(:raw)
+ node(Script::String.new(tok.value))
+ end
+
+ def special_fun
+ return paren unless tok = try_tok(:special_fun)
+ first = node(Script::String.new(tok.value.first))
+ Sass::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
+ Script::Interpolation.new(
+ l, i, r && node(Script::String.new(r)),
+ false, false)
+ end
+ end
+
+ def paren
+ return variable unless try_tok(:lparen)
+ was_in_parens = @in_parens
+ @in_parens = true
+ line = @lexer.line
+ e = expr
+ assert_tok(:rparen)
+ return e || node(List.new([], :space), line)
+ ensure
+ @in_parens = was_in_parens
+ end
+
+ def variable
+ return string unless c = try_tok(:const)
+ node(Variable.new(*c.value))
+ end
+
+ def string
+ return number unless first = try_tok(:string)
+ return first.value unless try_tok(:begin_interpolation)
+ line = @lexer.line
+ mid = parse_interpolated
+ last = assert_expr(:string)
+ interp = StringInterpolation.new(first.value, mid, last)
+ interp.line = line
+ interp
+ end
+
+ def number
+ return literal unless tok = try_tok(:number)
+ num = tok.value
+ num.original = num.to_s unless @in_parens
+ num
+ end
+
+ def literal
+ (t = try_tok(:color, :bool, :null)) && (return t.value)
+ 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",
+ }
+
+ def assert_expr(name, expected = nil)
+ (e = send(name)) && (return e)
+ @lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
+ end
+
+ def assert_tok(*names)
+ (t = try_tok(*names)) && (return t)
+ @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
+ end
+
+ def try_tok(*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
+
+ def node(node, line = @lexer.line)
+ node.line = line
+ node
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/string.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/string.rb
new file mode 100644
index 0000000..1048b86
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/string.rb
@@ -0,0 +1,51 @@
+require 'sass/script/literal'
+
+module Sass::Script
+ # A SassScript object representing a CSS string *or* a CSS identifier.
+ class String < Literal
+ # 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
+
+ # 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 Literal#plus
+ def plus(other)
+ other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
+ Sass::Script::String.new(self.value + other_str, self.type)
+ end
+
+ # @see Node#to_s
+ def to_s(opts = {})
+ if @type == :identifier
+ return @value.gsub(/\n\s*/, " ")
+ end
+
+ return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}
+ return "'#{value.gsub("'", "\\'")}'" if opts[:quote] == %q{'}
+ return "\"#{value}\"" unless value.include?('"')
+ return "'#{value}'" unless value.include?("'")
+ "\"#{value.gsub('"', "\\\"")}\"" #'
+ end
+
+ # @see Node#to_sass
+ def to_sass(opts = {})
+ to_s
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/string_interpolation.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/string_interpolation.rb
new file mode 100644
index 0000000..6baba12
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/string_interpolation.rb
@@ -0,0 +1,103 @@
+module Sass::Script
+ # 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::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::String) ? mid.value : mid.to_s)
+ res << @after.perform(environment).value
+ opts(Sass::Script::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.2.12/lib/sass/script/unary_operation.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/unary_operation.rb
new file mode 100644
index 0000000..efa78c7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/unary_operation.rb
@@ -0,0 +1,69 @@
+module Sass::Script
+ # 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 = 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 [Literal] 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}"
+ literal = @operand.perform(environment)
+ literal.send(operator)
+ rescue NoMethodError => e
+ raise e unless e.name.to_s == operator.to_s
+ raise Sass::SyntaxError.new("Undefined unary operation: \"#{ operator} #{literal}\".")
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/script/variable.rb
b/backends/css/gems/sass-3.2.12/lib/sass/script/variable.rb
new file mode 100644
index 0000000..56ae9db
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/script/variable.rb
@@ -0,0 +1,58 @@
+module Sass
+ module Script
+ # 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 [Literal] The SassScript object that is the value of the variable
+ # @raise [Sass::SyntaxError] if the variable is undefined
+ def _perform(environment)
+ raise SyntaxError.new("Undefined variable: \"$#{name}\".") unless val = environment.var(name)
+ if val.is_a?(Number)
+ val = val.dup
+ val.original = nil
+ end
+ return val
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss.rb b/backends/css/gems/sass-3.2.12/lib/sass/scss.rb
new file mode 100644
index 0000000..215fe95
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/scss.rb
@@ -0,0 +1,16 @@
+require 'sass/scss/rx'
+require 'sass/scss/script_lexer'
+require 'sass/scss/script_parser'
+require 'sass/scss/parser'
+require 'sass/scss/static_parser'
+require 'sass/scss/css_parser'
+
+module Sass
+ # SCSS is the CSS syntax for Sass.
+ # It parses into the same syntax tree as Sass,
+ # and generates the same sort of output CSS.
+ #
+ # This module contains code for the parsing of SCSS.
+ # The evaluation is handled by the broader {Sass} module.
+ module SCSS; end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss/css_parser.rb
b/backends/css/gems/sass-3.2.12/lib/sass/scss/css_parser.rb
new file mode 100644
index 0000000..4591324
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/scss/css_parser.rb
@@ -0,0 +1,36 @@
+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; 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, space)
+ expected('expression (e.g. 1px, bold)');
+ 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.2.12/lib/sass/scss/parser.rb
b/backends/css/gems/sass-3.2.12/lib/sass/scss/parser.rb
new file mode 100644
index 0000000..a790455
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/scss/parser.rb
@@ -0,0 +1,1179 @@
+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
+ # @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.
+ # @param line [Fixnum] The line on which the source string appeared,
+ # if it's part of another document.
+ def initialize(str, filename, line = 1)
+ @template = str
+ @filename = filename
+ @line = line
+ @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 @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::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 @scanner.eos?
+ ql
+ 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 @scanner.eos?
+ condition
+ end
+
+ private
+
+ include Sass::SCSS::RX
+
+ 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))
+ 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 =~ /^\/\//
+ loud = !silent && text =~ %r{^/[/*]!}
+ line = @line - text.count("\n")
+
+ if silent
+ value = [text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */']
+ else
+ value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
+ value.unshift(@scanner.
+ string[0 scanner pos]
+ reverse[/.*?\*\/(.*?)($|\Z)/, 1].
+ reverse.gsub(/[^\s]/, ' '))
+ end
+
+ type = if silent then :silent elsif loud then :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]
+
+ PREFIXED_DIRECTIVES = Set[:supports]
+
+ def directive
+ return unless tok(/@/)
+ name = tok!(IDENT)
+ ss
+
+ if dir = special_directive(name)
+ return dir
+ elsif dir = prefixed_directive(name)
+ return dir
+ end
+
+ # Most at-rules take expressions (e.g. @import),
+ # but some (e.g. @page) take selector-like arguments.
+ # Some take no arguments at all.
+ val = expr || selector
+ val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"]
+ directive_body(val)
+ end
+
+ def directive_body(value)
+ node = node(Sass::Tree::DirectiveNode.new(value))
+
+ if tok(/\{/)
+ node.has_children = true
+ block_contents(node, :directive)
+ tok!(/\}/)
+ end
+
+ node
+ end
+
+ def special_directive(name)
+ sym = name.gsub('-', '_').to_sym
+ DIRECTIVES.include?(sym) && send("#{sym}_directive")
+ end
+
+ def prefixed_directive(name)
+ sym = name.gsub(/^-[a-z0-9]+-/i, '').gsub('-', '_').to_sym
+ PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name)
+ end
+
+ def mixin_directive
+ name = tok! IDENT
+ args, splat = sass_script(:parse_mixin_definition_arglist)
+ ss
+ block(node(Sass::Tree::MixinDefNode.new(name, args, splat)), :directive)
+ end
+
+ def include_directive
+ name = tok! IDENT
+ args, keywords, splat = sass_script(:parse_mixin_include_arglist)
+ ss
+ include_node = node(Sass::Tree::MixinNode.new(name, args, keywords, splat))
+ if tok?(/\{/)
+ include_node.has_children = true
+ block(include_node, :directive)
+ else
+ include_node
+ end
+ end
+
+ def content_directive
+ ss
+ node(Sass::Tree::ContentNode.new)
+ end
+
+ def function_directive
+ name = tok! IDENT
+ args, splat = sass_script(:parse_function_definition_arglist)
+ ss
+ block(node(Sass::Tree::FunctionNode.new(name, args, splat)), :function)
+ end
+
+ def return_directive
+ node(Sass::Tree::ReturnNode.new(sass_script(:parse)))
+ end
+
+ def debug_directive
+ node(Sass::Tree::DebugNode.new(sass_script(:parse)))
+ end
+
+ def warn_directive
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)))
+ end
+
+ def for_directive
+ 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)), :directive)
+ end
+
+ def each_directive
+ tok!(/\$/)
+ var = tok! IDENT
+ ss
+
+ tok!(/in/)
+ list = sass_script(:parse)
+ ss
+
+ block(node(Sass::Tree::EachNode.new(var, list)), :directive)
+ end
+
+ def while_directive
+ expr = sass_script(:parse)
+ ss
+ block(node(Sass::Tree::WhileNode.new(expr)), :directive)
+ end
+
+ def if_directive
+ expr = sass_script(:parse)
+ ss
+ node = block(node(Sass::Tree::IfNode.new(expr)), :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)
+ return unless tok(/@else/)
+ ss
+ else_node = block(
+ Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
+ :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
+ err("Invalid CSS: @else must come after @if")
+ end
+
+ def extend_directive
+ selector = expr!(:selector_sequence)
+ optional = tok(OPTIONAL)
+ ss
+ node(Sass::Tree::ExtendNode.new(selector, !!optional))
+ end
+
+ def import_directive
+ values = []
+
+ loop do
+ values << expr!(:import_arg)
+ break if use_css_import?
+ break unless tok(/,/)
+ ss
+ end
+
+ return values
+ end
+
+ def import_arg
+ line = @line
+ return unless (str = tok(STRING)) || (uri = tok?(/url\(/i))
+ if uri
+ str = sass_script(:parse_string)
+ media = media_query_list
+ ss
+ return node(Tree::CssImportNode.new(str, media.to_a))
+ end
+
+ path = @scanner[1] || @scanner[2]
+ ss
+
+ media = media_query_list
+ if path =~ /^(https?:)?\/\// || media || use_css_import?
+ node = Sass::Tree::CssImportNode.new(str, media.to_a)
+ else
+ node = Sass::Tree::ImportNode.new(path.strip)
+ end
+ node.line = line
+ node
+ end
+
+ def use_css_import?; false; end
+
+ def media_directive
+ block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a)), :directive)
+ end
+
+ # http://www.w3.org/TR/css3-mediaqueries/#syntax
+ def media_query_list
+ return unless query = media_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
+ return unless expr = media_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 media_expr
+ interp = interpolation and return interp
+ return unless tok(/\(/)
+ res = ['(']
+ ss
+ res << sass_script(:parse)
+
+ if tok(/:/)
+ res << ': '
+ ss
+ res << sass_script(:parse)
+ end
+ res << tok!(/\)/)
+ ss
+ res
+ end
+
+ def charset_directive
+ tok! STRING
+ name = @scanner[1] || @scanner[2]
+ ss
+ node(Sass::Tree::CharsetNode.new(name))
+ 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
+ res = ["@-moz-document "]
+ loop do
+ res << str{ss} << expr!(:moz_document_function)
+ break unless c = tok(/,/)
+ res << c
+ end
+ directive_body(res.flatten)
+ end
+
+ def moz_document_function
+ return unless val = interp_uri || _interp_string(:url_prefix) ||
+ _interp_string(:domain) || function(!:allow_var) || interpolation
+ ss
+ val
+ end
+
+ # http://www.w3.org/TR/css3-conditional/
+ def supports_directive(name)
+ condition = expr!(:supports_condition)
+ node = node(Sass::Tree::SupportsNode.new(name, condition))
+
+ tok!(/\{/)
+ node.has_children = true
+ block_contents(node, :directive)
+ tok!(/\}/)
+
+ node
+ 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
+ return unless cond = supports_condition_in_parens
+ return cond unless op = tok(/and|or/i)
+ begin
+ ss
+ cond = Sass::Supports::Operator.new(
+ cond, expr!(:supports_condition_in_parens), op)
+ end while op = tok(/and|or/i)
+ cond
+ end
+
+ def supports_condition_in_parens
+ interp = supports_interpolation and return 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
+ return unless interp = interpolation
+ ss
+ Sass::Supports::Interpolation.new(interp)
+ end
+
+ def variable
+ return unless tok(/\$/)
+ name = tok!(IDENT)
+ ss; tok!(/:/); ss
+
+ expr = sass_script(:parse)
+ guarded = tok(DEFAULT)
+ node(Sass::Tree::VariableNode.new(name, expr, guarded))
+ 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
+ return unless rules = selector_sequence
+ block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :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)
+ return child_or_array.has_children
+ end
+
+ # This is a nasty hack, and the only place in the parser
+ # that requires a large amount of backtracking.
+ # The reason is that we can't figure out if certain strings
+ # are declarations or rulesets with fixed finite lookahead.
+ # For example, "foo:bar baz baz baz..." could be either a property
+ # or a selector.
+ #
+ # To handle this, we simply check if it works as a property
+ # (which is the most common case)
+ # and, if it doesn't, try it as a ruleset.
+ #
+ # We could eke some more efficiency out of this
+ # by handling some easy cases (first token isn't an identifier,
+ # no colon after the identifier, whitespace after the colon),
+ # but I'm not sure the gains would be worth the added complexity.
+ def declaration_or_ruleset
+ old_use_property_exception, @use_property_exception =
+ @use_property_exception, false
+ decl_err = catch_error do
+ decl = declaration
+ unless decl && decl.has_children
+ # We want an exception if it's not there,
+ # but we don't want to consume if it is
+ tok!(/[;}]/) unless tok?(/[;}]/)
+ end
+ return decl
+ end
+
+ ruleset_err = catch_error {return ruleset}
+ rethrow(@use_property_exception ? decl_err : ruleset_err)
+ ensure
+ @use_property_exception = old_use_property_exception
+ end
+
+ def selector_sequence
+ if sel = tok(STATIC_SELECTOR, true)
+ return [sel]
+ end
+
+ rules = []
+ return unless v = selector
+ rules.concat v
+
+ ws = ''
+ while tok(/,/)
+ ws << str {ss}
+ if v = selector
+ rules << ',' << ws
+ rules.concat v
+ ws = ''
+ end
+ end
+ rules
+ end
+
+ def selector
+ return unless sel = _selector
+ sel.to_a
+ end
+
+ def selector_comma_sequence
+ return unless sel = _selector
+ selectors = [sel]
+ ws = ''
+ while tok(/,/)
+ ws << str{ss}
+ if sel = _selector
+ selectors << sel
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) if ws.include?("\n")
+ ws = ''
+ end
+ end
+ Selector::CommaSequence.new(selectors)
+ end
+
+ def _selector
+ # The combinator here allows the "> E" hack
+ return unless val = combinator || simple_selector_sequence
+ 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
+ Selector::Sequence.new(res.compact)
+ 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 = res.flatten
+ res = res.join '' if res.all? {|e| e.is_a?(String)}
+ res
+ end
+
+ def simple_selector_sequence
+ # Returning expr by default allows for stuff like
+ # http://www.w3.org/TR/css3-animations/#keyframes-
+ return expr(!:allow_var) unless e = element_name || id_selector ||
+ class_selector || placeholder_selector || attrib || pseudo ||
+ parent_selector || interpolation_selector
+ res = [e]
+
+ # The tok(/\*/) allows the "E*" hack
+ while v = id_selector || class_selector || placeholder_selector || attrib ||
+ pseudo || interpolation_selector ||
+ (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(/!/))
+ end
+
+ def parent_selector
+ return unless tok(/&/)
+ Selector::Parent.new
+ end
+
+ def class_selector
+ return unless tok(/\./)
+ @expected = "class name"
+ Selector::Class.new(merge(expr!(:interp_ident)))
+ end
+
+ def id_selector
+ return unless tok(/#(?!\{)/)
+ @expected = "id name"
+ Selector::Id.new(merge(expr!(:interp_name)))
+ end
+
+ def placeholder_selector
+ return unless tok(/%/)
+ @expected = "placeholder name"
+ Selector::Placeholder.new(merge(expr!(:interp_ident)))
+ end
+
+ def element_name
+ ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
+ return unless ns || name
+
+ if name == '*'
+ Selector::Universal.new(merge(ns))
+ else
+ Selector::Element.new(merge(name), merge(ns))
+ end
+ end
+
+ def qualified_name(allow_star_name=false)
+ return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
+ return nil, name unless tok(/\|/)
+
+ return name, expr!(:interp_ident) unless allow_star_name
+ @expected = "identifier or *"
+ return name, interp_ident || tok!(/\*/)
+ end
+
+ def interpolation_selector
+ return unless script = interpolation
+ Selector::Interpolation.new(script)
+ end
+
+ def attrib
+ return unless tok(/\[/)
+ ss
+ ns, name = attrib_name!
+ ss
+
+ if op = tok(/=/) ||
+ tok(INCLUDES) ||
+ tok(DASHMATCH) ||
+ tok(PREFIXMATCH) ||
+ tok(SUFFIXMATCH) ||
+ tok(SUBSTRINGMATCH)
+ @expected = "identifier or string"
+ ss
+ val = interp_ident || expr!(:interp_string)
+ ss
+ end
+ flags = interp_ident || interp_string
+ tok!(/\]/)
+
+ Selector::Attribute.new(merge(name), merge(ns), op, merge(val), merge(flags))
+ end
+
+ def attrib_name!
+ if name_or_ns = interp_ident
+ # E, E|E
+ if tok(/\|(?!=)/)
+ ns = name_or_ns
+ name = interp_ident
+ else
+ name = name_or_ns
+ end
+ else
+ # *|E or |E
+ ns = [tok(/\*/) || ""]
+ tok!(/\|/)
+ name = expr!(:interp_ident)
+ end
+ return ns, name
+ end
+
+ def pseudo
+ return unless s = tok(/::?/)
+ @expected = "pseudoclass or pseudoelement"
+ name = expr!(:interp_ident)
+ if tok(/\(/)
+ ss
+ arg = expr!(:pseudo_arg)
+ while tok(/,/)
+ arg << ',' << str{ss}
+ arg.concat expr!(:pseudo_arg)
+ end
+ tok!(/\)/)
+ end
+ Selector::Pseudo.new(s == ':' ? :class : :element, merge(name), merge(arg))
+ end
+
+ def pseudo_arg
+ # In the CSS spec, every pseudo-class/element either takes a pseudo
+ # expression or a selector comma sequence as an argument. However, we
+ # don't want to have to know which takes which, so we handle both at
+ # once.
+ #
+ # However, there are some ambiguities between the two. For instance, "n"
+ # could start a pseudo expression like "n+1", or it could start a
+ # selector like "n|m". In order to handle this, we must regrettably
+ # backtrack.
+ expr, sel = nil, nil
+ pseudo_err = catch_error do
+ expr = pseudo_expr
+ next if tok?(/[,)]/)
+ expr = nil
+ expected '")"'
+ end
+
+ return expr if expr
+ sel_err = catch_error {sel = selector}
+ return sel if sel
+ rethrow pseudo_err if pseudo_err
+ rethrow sel_err if sel_err
+ return
+ end
+
+ def pseudo_expr
+ return unless e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
+ interp_string || tok(IDENT) || interpolation
+ res = [e, str{ss}]
+ while e = tok(PLUS) || tok(/[-*]/) || tok(NUMBER) ||
+ interp_string || tok(IDENT) || interpolation
+ res << e << str{ss}
+ end
+ res
+ end
+
+ def declaration
+ # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
+ if s = tok(/[:\*\.]|\#(?!\{)/)
+ @use_property_exception = s !~ /[\.\#]/
+ name = [s, str{ss}, *expr!(:interp_ident)]
+ else
+ return unless name = interp_ident
+ name = [name] if name.is_a?(String)
+ end
+ if comment = tok(COMMENT)
+ name << comment
+ end
+ ss
+
+ tok!(/:/)
+ space, value = value!
+ ss
+ require_block = tok?(/\{/)
+
+ node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
+
+ return node unless require_block
+ nested_properties! node, space
+ end
+
+ def value!
+ space = !str {ss}.empty?
+ @use_property_exception ||= space || !tok?(IDENT)
+
+ return true, Sass::Script::String.new("") if tok?(/\{/)
+ # 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)
+ return space, Sass::Script::String.new(val.strip)
+ end
+ return space, sass_script(:parse)
+ end
+
+ def nested_properties!(node, space)
+ err(<<MESSAGE) unless space
+Invalid CSS: a space is required between a property and its definition
+when it has other properties nested beneath it.
+MESSAGE
+
+ @use_property_exception = true
+ @expected = 'expression (e.g. 1px, bold) or "{"'
+ block(node, :property)
+ end
+
+ def expr(allow_var = true)
+ return unless t = term(allow_var)
+ res = [t, str{ss}]
+
+ while (o = operator) && (t = term(allow_var))
+ res << o << t << str{ss}
+ end
+
+ res.flatten
+ end
+
+ def term(allow_var)
+ if e = tok(NUMBER) ||
+ interp_uri ||
+ function(allow_var) ||
+ interp_string ||
+ tok(UNICODERANGE) ||
+ interp_ident ||
+ tok(HEXCOLOR) ||
+ (allow_var && var_expr)
+ return e
+ end
+
+ return unless op = tok(/[+-]/)
+ @expected = "number or function"
+ return [op, tok(NUMBER) || function(allow_var) ||
+ (allow_var && var_expr) || expr!(:interpolation)]
+ end
+
+ def function(allow_var)
+ return unless name = tok(FUNCTION)
+ 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::Variable.new(tok!(IDENT))
+ var.line = line
+ var
+ end
+
+ def interpolation
+ return unless tok(INTERP_START)
+ sass_script(:parse_interpolated)
+ end
+
+ def interp_string
+ _interp_string(:double) || _interp_string(:single)
+ end
+
+ def interp_uri
+ _interp_string(:uri)
+ end
+
+ def _interp_string(type)
+ return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
+ 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)
+ return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
+ res = [val]
+ while val = tok(NAME) || interpolation
+ res << val
+ end
+ res
+ end
+
+ def interp_ident_or_var
+ (id = interp_ident) and return id
+ (var = var_expr) and return [var]
+ end
+
+ def interp_name
+ interp_ident NAME
+ end
+
+ def str
+ @strs.push ""
+ yield
+ @strs.last
+ ensure
+ @strs.pop
+ end
+
+ def str?
+ pos = @scanner.pos
+ line = @line
+ @strs.push ""
+ throw_error {yield} && @strs.last
+ rescue Sass::SyntaxError
+ @scanner.pos = pos
+ @line = line
+ nil
+ ensure
+ @strs.pop
+ end
+
+ def node(node)
+ node.line = @line
+ node
+ end
+
+ @sass_script_parser = Class.new(Sass::Script::Parser)
+ @sass_script_parser.send(:include, ScriptParser)
+ # @private
+ def self.sass_script_parser; @sass_script_parser; end
+
+ def sass_script(*args)
+ parser = self.class.sass_script_parser.new(@scanner, @line,
+ @scanner.pos - (@scanner string[0 scanner pos] rindex("\n") || 0))
+ 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
+ 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))",
+ :pseudo_arg => "expression (e.g. fr, 2n+1)",
+ :interp_ident => "identifier",
+ :interp_name => "identifier",
+ :qualified_name => "identifier",
+ :expr => "expression (e.g. 1px, bold)",
+ :_selector => "selector",
+ :selector_comma_sequence => "selector",
+ :simple_selector_sequence => "selector",
+ :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))",
+ }
+
+ TOK_NAMES = Sass::Util.to_hash(
+ Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
+ merge(IDENT => "identifier", /[;}]/ => '";"')
+
+ def tok?(rx)
+ @scanner.match?(rx)
+ end
+
+ def expr!(name)
+ (e = send(name)) && (return e)
+ expected(EXPR_NAMES[name] || name.to_s)
+ end
+
+ def tok!(rx)
+ (t = tok(rx)) && (return t)
+ name = TOK_NAMES[rx]
+
+ unless name
+ # Display basic regexps as plain old strings
+ string = rx.source.gsub(/\\(.)/, '\1')
+ name = rx.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
+ expected = @expected
+ if catch(:_sass_parser_error) {yield; false}
+ @scanner.pos = pos
+ @line = line
+ @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
+ @line += res.count(NEWLINE)
+ @expected = nil
+ if ! strs empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
+ @strs.each {|s| s << res}
+ end
+ res
+ end
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss/rx.rb
b/backends/css/gems/sass-3.2.12/lib/sass/scss/rx.rb
new file mode 100644
index 0000000..96eb2e1
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/scss/rx.rb
@@ -0,0 +1,133 @@
+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}
+ return 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 =~ /[ -\/:-~]/
+ return "\\#{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 = /[0-9]+|[0-9]*\.[0-9]+/
+ 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 = /\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\//
+ SINGLE_LINE_COMMENT = /\/\/.*(\n[ \t]*\/\/.*)*/
+
+ CDO = quote("<!--")
+ CDC = quote("-->")
+ INCLUDES = quote("~=")
+ DASHMATCH = quote("|=")
+ PREFIXMATCH = quote("^=")
+ SUFFIXMATCH = quote("$=")
+ SUBSTRINGMATCH = quote("*=")
+
+ HASH = /##{NAME}/
+
+ IMPORTANT = /!#{W}important/i
+ DEFAULT = /!#{W}default/i
+
+ NUMBER = /#{NUM}(?:#{IDENT}|%)?/
+
+ 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_HYPHEN_INTERP = /-(#\{)/
+ STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
+ STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{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}){0,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.2.12/lib/sass/scss/script_lexer.rb
new file mode 100644
index 0000000..777485f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/scss/script_lexer.rb
@@ -0,0 +1,15 @@
+module Sass
+ module SCSS
+ # A mixin for subclasses of {Sass::Script::Lexer}
+ # that makes them usable by {SCSS::Parser} to parse SassScript.
+ # In particular, the lexer doesn't support `!` for a variable prefix.
+ module ScriptLexer
+ private
+
+ def variable
+ return [:raw, "!important"] if scan(Sass::SCSS::RX::IMPORTANT)
+ _variable(Sass::SCSS::RX::VARIABLE)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss/script_parser.rb
b/backends/css/gems/sass-3.2.12/lib/sass/scss/script_parser.rb
new file mode 100644
index 0000000..e5f64b2
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/scss/script_parser.rb
@@ -0,0 +1,25 @@
+module Sass
+ module SCSS
+ # A mixin for subclasses of {Sass::Script::Parser}
+ # that makes them usable by {SCSS::Parser} to parse SassScript.
+ # In particular, the parser won't raise an error
+ # when there's more content in the lexer once lexing is done.
+ # In addition, the parser doesn't support `!` for a variable prefix.
+ module ScriptParser
+ private
+
+ # @private
+ def lexer_class
+ klass = Class.new(super)
+ klass.send(:include, ScriptLexer)
+ klass
+ end
+
+ # Instead of raising an error when the parser is done,
+ # rewind the StringScanner so that it hasn't consumed the final token.
+ def assert_done
+ @lexer.unpeek!
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/scss/static_parser.rb
b/backends/css/gems/sass-3.2.12/lib/sass/scss/static_parser.rb
new file mode 100644
index 0000000..9d473cd
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/scss/static_parser.rb
@@ -0,0 +1,54 @@
+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
+
+ private
+
+ def moz_document_function
+ return unless val = tok(URI) || tok(URL_PREFIX) || tok(DOMAIN) ||
+ function(!:allow_var)
+ ss
+ [val]
+ end
+
+ def variable; nil; end
+ def script_value; nil; end
+ def interpolation; nil; end
+ def var_expr; nil; end
+ def interp_string; s = tok(STRING) and [s]; end
+ def interp_uri; s = tok(URI) and [s]; end
+ def interp_ident(ident = IDENT); s = tok(ident) and [s]; end
+ def use_css_import?; true; end
+
+ def special_directive(name)
+ return unless %w[media import charset -moz-document].include?(name)
+ super
+ 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.2.12/lib/sass/selector.rb
b/backends/css/gems/sass-3.2.12/lib/sass/selector.rb
new file mode 100644
index 0000000..9e4cfea
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/selector.rb
@@ -0,0 +1,452 @@
+require 'sass/selector/simple'
+require 'sass/selector/abstract_sequence'
+require 'sass/selector/comma_sequence'
+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 seelctor,
+ # 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.
+ #
+ # @type [Fixnum]
+ 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
+ # @see Selector#to_a
+ def to_a
+ ["&"]
+ 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 [Array<String, Sass::Script::Node>]
+ attr_reader :name
+
+ # @param name [Array<String, Sass::Script::Node>] The class name
+ def initialize(name)
+ @name = name
+ end
+
+ # @see Selector#to_a
+ def to_a
+ [".", * name]
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ SPECIFICITY_BASE
+ end
+ end
+
+ # An id selector (e.g. `#foo`).
+ class Id < Simple
+ # The id name.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_reader :name
+
+ # @param name [Array<String, Sass::Script::Node>] The id name
+ def initialize(name)
+ @name = name
+ end
+
+ # @see Selector#to_a
+ def to_a
+ ["#", * 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) && self.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 [Array<String, Sass::Script::Node>]
+ attr_reader :name
+
+ # @param name [Array<String, Sass::Script::Node>] The placeholder name
+ def initialize(name)
+ @name = name
+ end
+
+ # @see Selector#to_a
+ def to_a
+ ["%", * 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 [Array<String, Sass::Script::Node>, nil]
+ attr_reader :namespace
+
+ # @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
+ def initialize(namespace)
+ @namespace = namespace
+ end
+
+ # @see Selector#to_a
+ def to_a
+ @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 [Array<String, Sass::Script::Node>]
+ attr_reader :name
+
+ # The selector namespace.
+ # `nil` means the default namespace,
+ # `[""]` means no namespace,
+ # `["*"]` means any namespace.
+ #
+ # @return [Array<String, Sass::Script::Node>, nil]
+ attr_reader :namespace
+
+ # @param name [Array<String, Sass::Script::Node>] The element name
+ # @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
+ def initialize(name, namespace)
+ @name = name
+ @namespace = namespace
+ end
+
+ # @see Selector#to_a
+ def to_a
+ @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
+
+ # Selector interpolation (`#{}` in Sass).
+ class Interpolation < Simple
+ # The script to run.
+ #
+ # @return [Sass::Script::Node]
+ attr_reader :script
+
+ # @param script [Sass::Script::Node] The script to run
+ def initialize(script)
+ @script = script
+ end
+
+ # @see Selector#to_a
+ def to_a
+ [ script]
+ end
+
+ # Always raises an exception.
+ #
+ # @raise [Sass::SyntaxError] Interpolation selectors should be resolved before unification
+ # @see Selector#unify
+ def unify(sels)
+ raise Sass::SyntaxError.new("[BUG] Cannot unify interpolation selectors.")
+ end
+ end
+
+ # An attribute selector (e.g. `[href^="http://"]`).
+ class Attribute < Simple
+ # The attribute name.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_reader :name
+
+ # The attribute namespace.
+ # `nil` means the default namespace,
+ # `[""]` means no namespace,
+ # `["*"]` means any namespace.
+ #
+ # @return [Array<String, Sass::Script::Node>, nil]
+ attr_reader :namespace
+
+ # The matching operator, e.g. `"="` or `"^="`.
+ #
+ # @return [String]
+ attr_reader :operator
+
+ # The right-hand side of the operator.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_reader :value
+
+ # Flags for the attribute selector (e.g. `i`).
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_reader :flags
+
+ # @param name [Array<String, Sass::Script::Node>] The attribute name
+ # @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
+ # @param operator [String] The matching operator, e.g. `"="` or `"^="`
+ # @param value [Array<String, Sass::Script::Node>] See \{#value}
+ # @param value [Array<String, Sass::Script::Node>] See \{#flags}
+ def initialize(name, namespace, operator, value, flags)
+ @name = name
+ @namespace = namespace
+ @operator = operator
+ @value = value
+ @flags = flags
+ end
+
+ # @see Selector#to_a
+ def to_a
+ res = ["["]
+ res.concat(@namespace) << "|" if @namespace
+ res.concat @name
+ (res << @operator).concat @value if @value
+ (res << " ").concat @flags if @flags
+ res << "]"
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ SPECIFICITY_BASE
+ end
+ end
+
+ # A pseudoclass (e.g. `:visited`) or pseudoelement (e.g. `::first-line`) selector.
+ # It can have arguments (e.g. `:nth-child(2n+1)`).
+ class Pseudo < Simple
+ # Some psuedo-class-syntax selectors are actually considered
+ # pseudo-elements and must be treated differently. This is a list of such
+ # selectors
+ #
+ # @return [Array<String>]
+ ACTUALLY_ELEMENTS = %w[after before first-line first-letter]
+
+ # 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 [Array<String, Sass::Script::Node>]
+ attr_reader :name
+
+ # The argument to the selector,
+ # or `nil` if no argument was given.
+ #
+ # This may include SassScript nodes that will be run during resolution.
+ # Note that this should not include SassScript nodes
+ # after resolution has taken place.
+ #
+ # @return [Array<String, Sass::Script::Node>, nil]
+ attr_reader :arg
+
+ # @param type [Symbol] See \{#type}
+ # @param name [Array<String, Sass::Script::Node>] The name of the selector
+ # @param arg [nil, Array<String, Sass::Script::Node>] The argument to the selector,
+ # or nil if no argument was given
+ def initialize(type, name, arg)
+ @syntactic_type = type
+ @name = name
+ @arg = arg
+ 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?(name.first) ? :element : syntactic_type
+ end
+
+ # @see Selector#to_a
+ def to_a
+ res = [syntactic_type == :class ? ":" : "::"] + @name
+ (res << "(").concat(Sass::Util.strip_string_array(@arg)) << ")" if @arg
+ res
+ end
+
+ # Returns `nil` if this is a pseudoelement selector
+ # and `sels` contains a pseudoelement selector different than this one.
+ #
+ # @see Selector#unify
+ def unify(sels)
+ return if type == :element && sels.any? do |sel|
+ sel.is_a?(Pseudo) && sel.type == :element &&
+ (sel.name != self.name || sel.arg != self.arg)
+ end
+ super
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ type == :class ? SPECIFICITY_BASE : 1
+ end
+ end
+
+ # A pseudoclass selector whose argument is itself a selector
+ # (e.g. `:not(.foo)` or `:-moz-all(.foo, .bar)`).
+ class SelectorPseudoClass < Simple
+ # The name of the pseudoclass.
+ #
+ # @return [String]
+ attr_reader :name
+
+ # The selector argument.
+ #
+ # @return [Selector::Sequence]
+ attr_reader :selector
+
+ # @param [String] The name of the pseudoclass
+ # @param [Selector::CommaSequence] The selector argument
+ def initialize(name, selector)
+ @name = name
+ @selector = selector
+ end
+
+ # @see Selector#to_a
+ def to_a
+ [":", @name, "("] + @selector.to_a + [")"]
+ end
+
+ # @see AbstractSequence#specificity
+ def specificity
+ SPECIFICITY_BASE
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/selector/abstract_sequence.rb
b/backends/css/gems/sass-3.2.12/lib/sass/selector/abstract_sequence.rb
new file mode 100644
index 0000000..9b67800
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/selector/abstract_sequence.rb
@@ -0,0 +1,94 @@
+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_a`
+ # method that returns an array of strings and script nodes.
+ 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 == self.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? {|m| m.is_a?(AbstractSequence) ? m.has_placeholder? : m.is_a?(Placeholder)}
+ end
+
+ # Converts the selector into a string. This is the standard selector
+ # string, along with any SassScript interpolation that may exist.
+ #
+ # @return [String]
+ def to_s
+ to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
+ end
+
+ # Returns the specificity of the selector as an integer. The base is given
+ # by {Sass::Selector::SPECIFICITY_BASE}.
+ #
+ # @return [Fixnum]
+ def specificity
+ _specificity(members)
+ end
+
+ protected
+
+ def _specificity(arr)
+ spec = 0
+ arr.map {|m| spec += m.is_a?(String) ? 0 : m.specificity}
+ spec
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/selector/comma_sequence.rb
b/backends/css/gems/sass-3.2.12/lib/sass/selector/comma_sequence.rb
new file mode 100644
index 0000000..0b70140
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/selector/comma_sequence.rb
@@ -0,0 +1,92 @@
+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
+ # @return [CommaSequence] This selector, with parent references resolved
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
+ def resolve_parent_refs(super_cseq)
+ if super_cseq.nil?
+ if @members.any? do |sel|
+ sel.members.any? do |sel_or_op|
+ sel_or_op.is_a?(SimpleSequence) && sel_or_op.members.any? {|ssel| ssel.is_a?(Parent)}
+ end
+ end
+ raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing
character '&'.")
+ end
+ return self
+ end
+
+ CommaSequence.new(
+ super_cseq.members.map do |super_seq|
+ @members.map {|seq| seq.resolve_parent_refs(super_seq)}
+ end.flatten)
+ 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.
+ # @return [CommaSequence] A copy of this selector,
+ # with extensions made according to `extends`
+ def do_extend(extends, parent_directives)
+ CommaSequence.new(members.map do |seq|
+ extended = seq.do_extend(extends, parent_directives)
+ # First Law of Extend: the result of extending a selector should
+ # always contain the base selector.
+ #
+ # See https://github.com/nex3/sass/issues/324.
+ extended.unshift seq unless seq.has_placeholder? || extended.include?(seq)
+ extended
+ end.flatten)
+ 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 Simple#to_a
+ def to_a
+ arr = Sass::Util.intersperse(@members.map {|m| m.to_a}, ", ").flatten
+ arr.delete("\n")
+ arr
+ end
+
+ private
+
+ def _hash
+ members.hash
+ end
+
+ def _eql?(other)
+ other.class == self.class && other.members.eql?(self.members)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/selector/sequence.rb
b/backends/css/gems/sass-3.2.12/lib/sass/selector/sequence.rb
new file mode 100644
index 0000000..c172961
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/selector/sequence.rb
@@ -0,0 +1,507 @@
+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_seq [Sequence] The parent selector sequence
+ # @return [Sequence] This selector, with parent references resolved
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
+ def resolve_parent_refs(super_seq)
+ members = @members.dup
+ nl = (members.first == "\n" && members.shift)
+ unless members.any? do |seq_or_op|
+ seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
+ end
+ old_members, members = members, []
+ members << nl if nl
+ members << SimpleSequence.new([Parent.new], false)
+ members += old_members
+ end
+
+ Sequence.new(
+ members.map do |seq_or_op|
+ next seq_or_op unless seq_or_op.is_a?(SimpleSequence)
+ seq_or_op.resolve_parent_refs(super_seq)
+ end.flatten)
+ end
+
+ # Non-destructively extends this selector with the extensions specified in a hash
+ # (which should come from {Sass::Tree::Visitors::Cssize}).
+ #
+ # @overload def do_extend(extends, parent_directives)
+ # @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.
+ # @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, seen = Set.new)
+ 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, seen)
+ choices = extended.map {|seq| seq.members}
+ choices.unshift([sseq_or_op]) unless extended.any? {|seq| seq.superselector?(sseq_or_op)}
+ choices
+ end
+ weaves = Sass::Util.paths(extended_not_expanded).map {|path| weave(path)}
+ Sass::Util.flatten(trim(weaves), 1).map {|p| Sequence.new(p)}
+ 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
+ # (.bar .foo).superselector?(.foo) #=> false
+ # @param sseq [SimpleSequence]
+ # @return [Boolean]
+ def superselector?(sseq)
+ return false unless members.size == 1
+ members.last.superselector?(sseq)
+ end
+
+ # @see Simple#to_a
+ def to_a
+ ary = @members.map {|seq_or_op| seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op}
+ Sass::Util.intersperse(ary, " ").flatten.compact
+ 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
+
+ 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, .D .A .B`.
+ #
+ # @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. These prefixes are
+ # `befores`, while the remaining parenthesized suffixes is `afters`.
+ befores = [[]]
+ afters = path.dup
+
+ until afters.empty?
+ current = afters.shift.dup
+ last_current = [current.pop]
+ befores = Sass::Util.flatten(befores.map do |before|
+ next [] unless sub = subweave(before, current)
+ sub.map {|seqs| seqs + last_current}
+ end, 1)
+ end
+ return befores
+ 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
+ return unless init = merge_initial_ops(seq1, seq2)
+ return unless fin = merge_final_ops(seq1, seq2)
+ 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
+ return (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.
+ 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.members, sel2.subject?)
+ 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.members, tilde_sel.subject?)
+ 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
+ return unless merged = sel1.unify(sel2.members, sel2.subject?)
+ 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
+
+ # 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
+ return 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) 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)
+ 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]
+ 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<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 seqses 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|
+ max_spec = _sources(seq1).map {|seq| seq.specificity}.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? {|seq2| _specificity(seq2) >= max_spec && _superselector?(seq2, seq1)}
+ end
+ end
+ end
+ result
+ end
+
+ def _hash
+ members.reject {|m| m == "\n"}.hash
+ end
+
+ def _eql?(other)
+ other.members.reject {|m| m == "\n"}.eql?(self.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.2.12/lib/sass/selector/simple.rb
b/backends/css/gems/sass-3.2.12/lib/sass/selector/simple.rb
new file mode 100644
index 0000000..ab60005
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/selector/simple.rb
@@ -0,0 +1,119 @@
+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
+
+ # Returns a representation of the node
+ # as an array of strings and potentially {Sass::Script::Node}s
+ # (if there's interpolation in the selector).
+ # When the interpolation is resolved and the strings are joined together,
+ # this will be the string representation of this node.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ def to_a
+ Sass::Util.abstract(self)
+ end
+
+ # Returns a string representation of the node.
+ # This is basically the selector string.
+ #
+ # @return [String]
+ def inspect
+ to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
+ end
+
+ # @see \{#inspect}
+ # @return [String]
+ def to_s
+ inspect
+ 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 ||= to_a.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 == self.hash && other.to_a.eql?(to_a)
+ 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 self.is_a?(Pseudo) || self.is_a?(SelectorPseudoClass)
+ sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && (sels.last.type == :element)}
+ else
+ sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) || sel.is_a?(SelectorPseudoClass)}
+ end
+ return sels + [self] unless i
+ return sels[0...i] + [self] + sels[i..-1]
+ end
+
+ protected
+
+ # 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 == ['*']
+ return ns1 || ns2, true
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/selector/simple_sequence.rb
b/backends/css/gems/sass-3.2.12/lib/sass/selector/simple_sequence.rb
new file mode 100644
index 0000000..a703e3f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/selector/simple_sequence.rb
@@ -0,0 +1,212 @@
+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 {#do_extend} process.
+ #
+ # @return {Set<Sequence>}
+ attr_accessor :sources
+
+ # @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 - [base]).
+ select {|sel| sel.is_a?(Pseudo) && sel.type == :element}
+ end
+
+ # Returns the non-base, non-pseudo-class 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 sources [Set<Sequence>]
+ def initialize(selectors, subject, sources = Set.new)
+ @members = selectors
+ @subject = subject
+ @sources = sources
+ end
+
+ # Resolves the {Parent} selectors within this selector
+ # by replacing them with the given parent selector,
+ # handling commas appropriately.
+ #
+ # @param super_seq [Sequence] The parent selector sequence
+ # @return [Array<SimpleSequence>] This selector, with parent references resolved.
+ # This is an array because the parent selector is itself a {Sequence}
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
+ def resolve_parent_refs(super_seq)
+ # Parent selector only appears as the first selector in the sequence
+ return [self] unless @members.first.is_a?(Parent)
+
+ return super_seq.members if @members.size == 1
+ unless super_seq.members.last.is_a?(SimpleSequence)
+ raise Sass::SyntaxError.new("Invalid parent selector: " + super_seq.to_a.join)
+ end
+
+ super_seq.members[0...-1] +
+ [SimpleSequence.new(super_seq.members.last.members + @members[1..-1], subject?)]
+ end
+
+ # Non-destrucively extends this selector with the extensions specified in a hash
+ # (which should come from {Sass::Tree::Visitors::Cssize}).
+ #
+ # @overload def do_extend(extends, parent_directives)
+ # @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.
+ # @return [Array<Sequence>] A list of selectors generated
+ # by extending this selector with `extends`.
+ # @see CommaSequence#do_extend
+ def do_extend(extends, parent_directives, seen = Set.new)
+ Sass::Util.group_by_to_a(extends.get(members.to_set)) {|ex, _| ex.extender}.map do |seq, group|
+ sels = group.map {|_, s| s}.flatten
+ # If A { extend B} and C {...},
+ # seq is A, sels is B, and self is C
+
+ self_without_sel = Sass::Util.array_minus(self.members, sels)
+ group.each {|e, _| e.result = :failed_to_unify unless e.result == :succeeded}
+ next unless unified = seq.members.last.unify(self_without_sel, subject?)
+ group.each {|e, _| e.result = :succeeded}
+ next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none?
+ new_seq = Sequence.new(seq.members[0...-1] + [unified])
+ new_seq.add_sources!(sources + [seq])
+ [sels, new_seq]
+ end.compact.map do |sels, seq|
+ seen.include?(sels) ? [] : seq.do_extend(extends, parent_directives, seen + [sels])
+ end.flatten.uniq
+ end
+
+ # Unifies this selector with another {SimpleSequence}'s {SimpleSequence#members members array},
+ # returning another `SimpleSequence`
+ # that matches both this selector and the input selector.
+ #
+ # @param sels [Array<Simple>] A {SimpleSequence}'s {SimpleSequence#members members array}
+ # @param subject [Boolean] Whether the {SimpleSequence} being merged is a subject.
+ # @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(sels, other_subject)
+ return unless sseq = members.inject(sels) do |member, sel|
+ return unless member
+ sel.unify(member)
+ end
+ 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 sseq [SimpleSequence]
+ # @return [Boolean]
+ def superselector?(sseq)
+ (base.nil? || base.eql?(sseq.base)) &&
+ pseudo_elements.eql?(sseq.pseudo_elements) &&
+ rest.subset?(sseq.rest)
+ end
+
+ # @see Simple#to_a
+ def to_a
+ res = @members.map {|sel| sel.to_a}.flatten
+ res << '!' if subject?
+ res
+ 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
+
+ # Return a copy of this simple sequence with `sources` merged into the
+ # {#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 true if Sass::Util.subsequence?(dirs1, dirs2)
+
+ Sass::Util.sass_warn <<WARNING
+DEPRECATION WARNING on line #{extend.node.line}#{" of #{extend.node.filename}" if extend.node.filename}:
+ @extending an outer selector from within #{extend.directives.last.name} is deprecated.
+ You may only @extend selectors within the same directive.
+ This will be an error in Sass 3.3.
+ It can only work once @extend is supported natively in the browser.
+WARNING
+ return false
+ end
+
+ def _hash
+ [base, Sass::Util.set_hash(rest)].hash
+ end
+
+ def _eql?(other)
+ other.base.eql?(self.base) && other.pseudo_elements == pseudo_elements &&
+ Sass::Util.set_eql?(other.rest, self.rest) && other.subject? == self.subject?
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/shared.rb
b/backends/css/gems/sass-3.2.12/lib/sass/shared.rb
new file mode 100644
index 0000000..54bd9bd
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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.strip, 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.2.12/lib/sass/supports.rb
b/backends/css/gems/sass-3.2.12/lib/sass/supports.rb
new file mode 100644
index 0000000..7c14b29
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/supports.rb
@@ -0,0 +1,229 @@
+# 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 env [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)
+ return str
+ end
+
+ def right_parens(str)
+ return "(#{str})" if @right.is_a?(Negation) || @right.is_a?(Operator)
+ return 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)
+ return str
+ end
+ end
+
+ # A declaration condition (e.g. `(feature: value)`).
+ class Declaration < Condition
+ # The feature name.
+ #
+ # @param [Sass::Script::Node]
+ attr_accessor :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.
+ #
+ # @param [Sass::Script::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.
+ #
+ # @param [Sass::Script::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)
+ val = value.perform(env)
+ @resolved_value = val.is_a?(Sass::Script::String) ? val.value : val.to_s
+ 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.2.12/lib/sass/tree/charset_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/charset_node.rb
new file mode 100644
index 0000000..65b6306
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/charset_node.rb
@@ -0,0 +1,22 @@
+module Sass::Tree
+ # A static node representing an unproccessed 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.2.12/lib/sass/tree/comment_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/comment_node.rb
new file mode 100644
index 0000000..ec38bbd
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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::Node}s representing `#{}`-interpolation
+ # if this is a loud comment.
+ #
+ # @return [Array<String, Sass::Script::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::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.2.12/lib/sass/tree/content_node.rb
new file mode 100644
index 0000000..3f6528f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/content_node.rb
@@ -0,0 +1,9 @@
+module Sass
+ module Tree
+ # A node representing the placement within a mixin of the include statement's content.
+ #
+ # @see Sass::Tree
+ class ContentNode < Node
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/css_import_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/css_import_node.rb
new file mode 100644
index 0000000..7dbd25b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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::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::Node}s
+ # representing `#{}`-interpolation. Any adjacent strings will be merged
+ # together.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_accessor :query
+
+ # The media query for this rule, without any unresolved interpolation. It's
+ # only set once {Tree::Node#perform} has been called.
+ #
+ # @return [Sass::Media::QueryList]
+ attr_accessor :resolved_query
+
+ # @param uri [String, Sass::Script::Node] See \{#uri}
+ # @param query [Array<String, Sass::Script::Node>] See \{#query}
+ def initialize(uri, query = nil)
+ @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.2.12/lib/sass/tree/debug_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/debug_node.rb
new file mode 100644
index 0000000..e3c9fe2
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Node] The expression to print
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/directive_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/directive_node.rb
new file mode 100644
index 0000000..8f73283
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/directive_node.rb
@@ -0,0 +1,42 @@
+module Sass::Tree
+ # A static node representing an unproccessed 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::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
+
+ # @param value [Array<String, Sass::Script::Node>] See \{#value}
+ def initialize(value)
+ @value = value
+ 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
+ value.first.gsub(/ .*$/, '')
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/each_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/each_node.rb
new file mode 100644
index 0000000..5e3c071
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 name of the loop variable.
+ # @return [String]
+ attr_reader :var
+
+ # The parse tree for the list.
+ # @param [Script::Node]
+ attr_accessor :list
+
+ # @param var [String] The name of the loop variable
+ # @param list [Script::Node] The parse tree for the list
+ def initialize(var, list)
+ @var = var
+ @list = list
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/extend_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/extend_node.rb
new file mode 100644
index 0000000..fadc739
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/extend_node.rb
@@ -0,0 +1,36 @@
+require 'sass/tree/node'
+
+module Sass::Tree
+ # A static node reprenting 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::Node}s
+ # representing `#{}`-interpolation.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_accessor :selector
+
+ # Whether the ` extend` is allowed to match no selectors or not.
+ #
+ # @return [Boolean]
+ def optional?; @optional; end
+
+ # @param selector [Array<String, Sass::Script::Node>]
+ # The CSS selector to extend,
+ # interspersed with {Sass::Script::Node}s
+ # representing `#{}`-interpolation.
+ # @param optional [Boolean] See \{#optional}
+ def initialize(selector, optional)
+ @selector = selector
+ @optional = optional
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/for_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/for_node.rb
new file mode 100644
index 0000000..9bb8d25
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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::Node]
+ attr_accessor :from
+
+ # The parse tree for the final expression.
+ # @return [Script::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::Node] See \{#from}
+ # @param to [Script::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.2.12/lib/sass/tree/function_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/function_node.rb
new file mode 100644
index 0000000..d812c8f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/function_node.rb
@@ -0,0 +1,34 @@
+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::Node>]
+ attr_accessor :args
+
+ # The splat argument for this function, if one exists.
+ #
+ # @return [Script::Node?]
+ attr_accessor :splat
+
+ # @param name [String] The function name
+ # @param args [Array<(Script::Node, Script::Node)>] The arguments for the function.
+ # @param splat [Script::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.2.12/lib/sass/tree/if_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/if_node.rb
new file mode 100644
index 0000000..a7b8906
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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([self.expr, self.else, self.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.2.12/lib/sass/tree/import_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/import_node.rb
new file mode 100644
index 0000000..557dd12
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/import_node.rb
@@ -0,0 +1,75 @@
+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|
+ if f = p.find(@imported_filename, options_for_importer)
+ return f
+ end
+ 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 => self.line, :filename => @filename)
+ end
+
+ def options_for_importer
+ @options.merge(:_line => line)
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/media_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/media_node.rb
new file mode 100644
index 0000000..f3b74f4
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/media_node.rb
@@ -0,0 +1,58 @@
+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::Node}s
+ # representing `#{}`-interpolation. Any adjacent strings will be merged
+ # together.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_accessor :query
+
+ # The media query for this rule, without any unresolved interpolation. It's
+ # only set once {Tree::Node#perform} has been called.
+ #
+ # @return [Sass::Media::QueryList]
+ attr_accessor :resolved_query
+
+ # @see RuleNode#tabs
+ attr_accessor :tabs
+
+ # @see RuleNode#group_end
+ attr_accessor :group_end
+
+ # @param query [Array<String, Sass::Script::Node>] See \{#query}
+ def initialize(query)
+ @query = query
+ @tabs = 0
+ 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
+
+ # @see Node#bubbles?
+ def bubbles?; true; end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/mixin_def_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/mixin_def_node.rb
new file mode 100644
index 0000000..9a08b59
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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::Node, Script::Node)>]
+ attr_accessor :args
+
+ # The splat argument for this mixin, if one exists.
+ #
+ # @return [Script::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::Node, Script::Node)>] See \{#args}
+ # @param splat [Script::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.2.12/lib/sass/tree/mixin_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/mixin_node.rb
new file mode 100644
index 0000000..a34dca6
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/mixin_node.rb
@@ -0,0 +1,39 @@
+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::Node>]
+ attr_accessor :args
+
+ # A hash from keyword argument names to values.
+ # @return [{String => Script::Node}]
+ attr_accessor :keywords
+
+ # The splat argument for this mixin, if one exists.
+ #
+ # @return [Script::Node?]
+ attr_accessor :splat
+
+ # @param name [String] The name of the mixin
+ # @param args [Array<Script::Node>] See \{#args}
+ # @param splat [Script::Node] See \{#splat}
+ # @param keywords [{String => Script::Node}] See \{#keywords}
+ def initialize(name, args, keywords, splat)
+ @name = name
+ @args = args
+ @keywords = keywords
+ @splat = splat
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/node.rb
new file mode 100644
index 0000000..d22f792
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/node.rb
@@ -0,0 +1,196 @@
+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 the same structure as the Sass document:
+ # rules and properties are nested beneath one another.
+ # 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
+
+ # The child nodes of this node.
+ #
+ # @return [Array<Tree::Node>]
+ attr_accessor :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 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, nil] The resulting CSS
+ # @see Sass::Tree
+ def to_s
+ Sass::Tree::Visitors::ToCss.visit(self)
+ 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.2.12/lib/sass/tree/prop_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/prop_node.rb
new file mode 100644
index 0000000..43461d7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/prop_node.rb
@@ -0,0 +1,152 @@
+module Sass::Tree
+ # A static node reprenting a CSS property.
+ #
+ # @see Sass::Tree
+ class PropNode < Node
+ # The name of the property,
+ # interspersed with {Sass::Script::Node}s
+ # representing `#{}`-interpolation.
+ # Any adjacent strings will be merged together.
+ #
+ # @return [Array<String, Sass::Script::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::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
+
+ # @param name [Array<String, Sass::Script::Node>] See \{#name}
+ # @param value [Sass::Script::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
+ return "" if @prop_syntax == :new || !value.is_a?(Sass::Script::String) || !value.value.empty?
+ "\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::Operation)
+ return val_to_sass_concat(node, opts) unless node.operator == :comma
+
+ Sass::Script::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::Operation)
+ return val_to_sass_div(node, opts) unless node.operator == :space
+
+ Sass::Script::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::Operation) && node.operator == :div &&
+ node.operand1.is_a?(Sass::Script::Number) &&
+ node.operand2.is_a?(Sass::Script::Number) &&
+ (!node.operand1.original || !node.operand2.original)
+ return node
+ end
+
+ Sass::Script::String.new("(#{node.to_sass(opts)})")
+ end
+
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/return_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/return_node.rb
new file mode 100644
index 0000000..daff6bb
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/return_node.rb
@@ -0,0 +1,18 @@
+module Sass
+ module Tree
+ # A dynamic node representing returning from a function.
+ #
+ # @see Sass::Tree
+ class ReturnNode < Node
+ # The expression to return.
+ # @type [Script::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Node] The expression to return
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/root_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/root_node.rb
new file mode 100644
index 0000000..9a04ad2
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/root_node.rb
@@ -0,0 +1,28 @@
+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.
+ # @see #to_s
+ def render
+ 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.to_s
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/rule_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/rule_node.rb
new file mode 100644
index 0000000..779e67c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/rule_node.rb
@@ -0,0 +1,132 @@
+require 'pathname'
+require 'uri'
+
+module Sass::Tree
+ # A static node reprenting 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::Node}s
+ # representing `#{}`-interpolation.
+ # Any adjacent strings will be merged together.
+ #
+ # @return [Array<String, Sass::Script::Node>]
+ attr_accessor :rule
+
+ # The CSS selector for this rule,
+ # without any unresolved interpolation
+ # but with parent references still intact.
+ # It's only set once {Tree::Node#perform} has been called.
+ #
+ # @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::Cssize} 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
+
+ # 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 [Array<String>]
+ attr_accessor :stack_trace
+
+ # @param rule [Array<String, Sass::Script::Node>]
+ # The CSS rule. See \{#rule}
+ def initialize(rule)
+ merged = Sass::Util.merge_adjacent_strings(rule)
+ @rule = Sass::Util.strip_string_array(merged)
+ @tabs = 0
+ try_to_parse_non_interpolated_rules
+ 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://" + URI.escape(File.expand_path(filename))),
+ :line => self.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, '', 1)
+ @parsed_rules = parser.parse_selector rescue nil
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/supports_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/supports_node.rb
new file mode 100644
index 0000000..0dc8458
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/supports_node.rb
@@ -0,0 +1,51 @@
+module Sass::Tree
+ # A static node representing a ` supports` rule.
+ # ` supports` rules behave differently from other directives
+ # in that when they're nested within rules,
+ # they bubble up to top-level.
+ #
+ # @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
+
+ # @see RuleNode#tabs
+ attr_accessor :tabs
+
+ # @see RuleNode#group_end
+ attr_accessor :group_end
+
+ # @param condition [Sass::Supports::Condition] See \{#condition}
+ def initialize(name, condition)
+ @name = name
+ @condition = condition
+ @tabs = 0
+ 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
+
+ # @see Node#bubbles?
+ def bubbles?; true; end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/trace_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/trace_node.rb
new file mode 100644
index 0000000..0000127
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/trace_node.rb
@@ -0,0 +1,32 @@
+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 mixin [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.2.12/lib/sass/tree/variable_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/variable_node.rb
new file mode 100644
index 0000000..0cb6ac6
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/variable_node.rb
@@ -0,0 +1,30 @@
+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::Node]
+ attr_accessor :expr
+
+ # Whether this is a guarded variable assignment (`!default`).
+ # @return [Boolean]
+ attr_reader :guarded
+
+ # @param name [String] The name of the variable
+ # @param expr [Script::Node] See \{#expr}
+ # @param guarded [Boolean] See \{#guarded}
+ def initialize(name, expr, guarded)
+ @name = name
+ @expr = expr
+ @guarded = guarded
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/base.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/base.rb
new file mode 100644
index 0000000..c104bad
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/base.rb
@@ -0,0 +1,75 @@
+# 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)
+ method = "visit_#{node_name node}"
+ if self.respond_to?(method, true)
+ self.send(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
+
+ NODE_NAME_RE = /.*::(.*?)Node$/
+
+ # Returns the name of a node as used in the `visit_*` method.
+ #
+ # @param [Tree::Node] node The node.
+ # @return [String] The name.
+ def node_name(node)
+ @@node_names ||= {}
+ @@node_names[node.class.name] ||= node.class.name.gsub(NODE_NAME_RE, '\\1').downcase
+ 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.2.12/lib/sass/tree/visitors/check_nesting.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/check_nesting.rb
new file mode 100644
index 0000000..a12ee46
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/check_nesting.rb
@@ -0,0 +1,147 @@
+# 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("invalid_#{node_name @parent}_child?", @parent, node) ||
+ try_send("invalid_#{node_name node}_parent?", @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
+ @parent = parent unless is_any_of?(parent, SCRIPT_NODES) ||
+ (parent.bubbles? && !old_parent.is_a?(Sass::Tree::RootNode))
+ @parents.push parent
+ super
+ ensure
+ @parent = old_parent
+ @parents.pop
+ 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
+ ] + 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 = [Sass::Tree::CommentNode, Sass::Tree::PropNode, Sass::Tree::MixinNode] +
CONTROL_NODES
+ 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::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
+
+ def is_any_of?(val, classes)
+ for c in classes
+ return true if val.is_a?(c)
+ end
+ return 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.2.12/lib/sass/tree/visitors/convert.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/convert.rb
new file mode 100644
index 0000000..56c290f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/convert.rb
@@ -0,0 +1,316 @@
+# 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?
+ (@format == :sass ? "\n" : " {\n") + super.join.rstrip + (@format == :sass ? "\n" : "\n#{ @tab_chars *
(@tabs-1)}}\n")
+ 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)
+ content = 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
+
+ content =
+ unless content.include?("\n")
+ content
+ else
+ 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.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"
+ content
+ end
+ content
+ end
+
+ def visit_debug(node)
+ "#{tab_str} debug #{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)
+ "#{tab_str} each $#{dasherize(node.var)} in #{node.list.to_sass(@options)}#{yield}"
+ end
+
+ def visit_extend(node)
+ "#{tab_str} extend #{selector_to_src(node.selector).lstrip}#{semi}#{" !optional" if node.optional?}\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 #{media_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::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::List) && arg.separator == :comma
+ sass
+ end
+
+ unless node.args.empty? && node.keywords.empty? && node.splat.nil?
+ args = node.args.map(&arg_to_sass).join(", ")
+ keywords = Sass::Util.hash_to_a(node.keywords).
+ map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"}.join(', ')
+ if node.splat
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
+ splat = "#{splat}#{arg_to_sass[node.splat]}..."
+ end
+ arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
+ 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)
+ if @format == :sass
+ name = selector_to_sass(node.rule)
+ name = "\\" + name if name[0] == ?:
+ name.gsub(/^/, tab_str) + yield
+ elsif @format == :scss
+ name = selector_to_scss(node.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)}#{' !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
+
+ private
+
+ def interp_to_src(interp)
+ interp.map do |r|
+ next r if r.is_a?(String)
+ "\#{#{r.to_sass(@options)}}"
+ end.join
+ end
+
+ # Like interp_to_src, but removes the unnecessary `#{}` around the keys and
+ # values in media expressions.
+ def media_interp_to_src(interp)
+ Sass::Util.enum_with_index(interp).map do |r, i|
+ next r if r.is_a?(String)
+ before, after = interp[i-1], interp[i+1]
+ if before.is_a?(String) && after.is_a?(String) &&
+ ((before[-1] == ?( && after[0] == ?:) ||
+ (before =~ /:\s*/ && after[0] == ?)))
+ r.to_sass(@options)
+ else
+ "\#{#{r.to_sass(@options)}}"
+ end
+ end.join
+ 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.2.12/lib/sass/tree/visitors/cssize.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/cssize.rb
new file mode 100644
index 0000000..3ac8e48
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/cssize.rb
@@ -0,0 +1,229 @@
+# 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]
+ attr_reader :parent
+
+ def initialize
+ @parent_directives = []
+ @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 = super.flatten
+ parent
+ end
+ end
+
+ MERGEABLE_DIRECTIVES = [Sass::Tree::MediaNode]
+
+ # 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)
+ if parent.is_a?(Sass::Tree::DirectiveNode)
+ if MERGEABLE_DIRECTIVES.any? {|klass| parent.is_a?(klass)}
+ old_parent_directive = @parent_directives.pop
+ end
+ @parent_directives.push parent
+ end
+
+ old_parent, @parent = @parent, parent
+ yield
+ ensure
+ @parent_directives.pop if parent.is_a?(Sass::Tree::DirectiveNode)
+ @parent_directives.push old_parent_directive if old_parent_directive
+ @parent = old_parent
+ 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 = Sass::Util.extract!(node.children) do |c|
+ c.is_a?(Sass::Tree::DirectiveNode) && !c.is_a?(Sass::Tree::MediaNode) &&
+ c.resolved_value =~ /^ import /i
+ end
+ charset_and_index = Sass::Util.ruby1_8? &&
+ node.children.each_with_index.find {|c, _| c.is_a?(Sass::Tree::CharsetNode)}
+ if charset_and_index
+ index = charset_and_index.last
+ node.children = node.children[0..index] + imports + node.children[index+1..-1]
+ else
+ node.children = imports + node.children
+ 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)
+ node.resolved_selector.members.each do |seq|
+ if seq.members.size > 1
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: 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.to_a.join}: invalid selector")
+ elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend parent selectors")
+ end
+
+ sel = sseq.members
+ parent.resolved_rules.members.each do |member|
+ if !member.members.last.is_a?(Sass::Selector::SimpleSequence)
+ raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
+ end
+
+ @extends[sel] = Extend.new(member, sel, node, @parent_directives.dup, :not_found)
+ end
+ end
+
+ []
+ end
+
+ # Modifies exception backtraces to include the imported file.
+ def visit_import(node)
+ # Don't use #visit_children to avoid adding the import node to the list of parents.
+ node.children.map {|c| visit(c)}.flatten
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.children.first.filename)
+ e.add_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ # Bubbles the ` media` directive up through RuleNodes
+ # and merges it with other ` media` directives.
+ def visit_media(node)
+ yield unless bubble(node)
+ media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
+ node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
+ media = media.select {|n| n.resolved_query = n.resolved_query.merge(node.resolved_query)}
+ (node.children.empty? ? [] : [node]) + media
+ end
+
+ # Bubbles the ` supports` directive up through RuleNodes.
+ def visit_supports(node)
+ yield unless bubble(node)
+ node
+ end
+
+ # Asserts that all the traced children are valid in their new location.
+ def visit_trace(node)
+ # Don't use #visit_children to avoid adding the trace node to the list of parents.
+ node.children.map {|c| visit(c)}.flatten
+ 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
+
+ # Resolves parent references and nested selectors,
+ # and updates the indentation of the rule node based on the nesting level.
+ def visit_rule(node)
+ parent_resolved_rules = parent.is_a?(Sass::Tree::RuleNode) ? parent.resolved_rules : nil
+ # It's possible for resolved_rules to be set if we've duplicated this node during @media bubbling
+ node.resolved_rules ||= node.parsed_rules.resolve_parent_refs(parent_resolved_rules)
+
+ yield
+
+ rules = node.children.select {|c| c.is_a?(Sass::Tree::RuleNode) || c.bubbles?}
+ props = node.children.reject {|c| c.is_a?(Sass::Tree::RuleNode) || c.bubbles? || c.invisible?}
+
+ unless props.empty?
+ node.children = props
+ rules.each {|r| r.tabs += 1} if node.style == :nested
+ rules.unshift(node)
+ end
+
+ rules.last.group_end = true unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty?
+
+ rules
+ end
+
+ private
+
+ def bubble(node)
+ return unless parent.is_a?(Sass::Tree::RuleNode)
+ new_rule = parent.dup
+ new_rule.children = node.children
+ node.children = with_parent(node) {Array(visit(new_rule))}
+ # If the last child is actually the end of the group,
+ # the parent's cssize will set it properly
+ node.children.last.group_end = false unless node.children.empty?
+ true
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/deep_copy.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/deep_copy.rb
new file mode 100644
index 0000000..2962e83
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/deep_copy.rb
@@ -0,0 +1,102 @@
+# 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_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::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::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::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::Node) ? c.deep_copy : c}
+ yield
+ end
+
+ def visit_media(node)
+ node.query = node.query.map {|c| c.is_a?(Sass::Script::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.2.12/lib/sass/tree/visitors/extend.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/extend.rb
new file mode 100644
index 0000000..9e8b62a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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?
+ warn = "\"#{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
+
+ Sass::Util.sass_warn <<WARN
+WARNING on line #{ex.node.line}#{" of #{ex.node.filename}" if ex.node.filename}: #{warn}
+ #{reason}
+ This will be an error in future releases of Sass.
+ Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
+WARN
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/perform.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/perform.rb
new file mode 100644
index 0000000..52960f3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/perform.rb
@@ -0,0 +1,446 @@
+# A visitor for converting a dynamic Sass tree into a static Sass tree.
+class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
+ # @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 self.visit(root, environment = Sass::Environment.new)
+ new(environment).send(:visit, root)
+ end
+
+ # @api private
+ def self.perform_arguments(callable, args, keywords, splat)
+ desc = "#{callable.type.capitalize} #{callable.name}"
+ downcase_desc = "#{callable.type} #{callable.name}"
+
+ 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
+
+ if args.size > callable.args.size && !callable.splat
+ takes = callable.args.size
+ passed = args.size
+ raise Sass::SyntaxError.new(
+ "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
+ end
+
+ splat_sep = :comma
+ if splat
+ args += splat.to_a
+ splat_sep = splat.separator if splat.is_a?(Sass::Script::List)
+ # If the splat argument exists, there won't be any keywords passed in
+ # manually, so we can safely overwrite rather than merge here.
+ keywords = splat.keywords if splat.is_a?(Sass::Script::ArgList)
+ end
+
+ keywords = keywords.dup
+ env = Sass::Environment.new(callable.environment)
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
+ if value && keywords.include?(var.underscored_name)
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} both by position and by name.")
+ end
+
+ value ||= keywords.delete(var.underscored_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::ArgList.new(rest, keywords.dup, splat_sep)
+ arg_list.options = env.options
+ env.set_local_var(callable.splat.name, arg_list)
+ end
+
+ yield env
+ rescue Exception => 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
+
+ protected
+
+ def initialize(env)
+ @environment = env
+ # Stack trace information, including mixin includes and imports.
+ @stack = []
+ end
+
+ # If an exception is raised, this adds proper metadata to the backtrace.
+ def visit(node)
+ 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)
+ res = res.value if res.is_a?(Sass::Script::String)
+ 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
+
+ # Runs the child nodes once for each value in the list.
+ def visit_each(node)
+ list = node.list.perform(@environment)
+
+ with_environment Sass::Environment.new(@environment) do
+ list.to_a.map do |v|
+ @environment.set_local_var(node.var, v)
+ 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.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)
+ range = Range.new(from.to_i, to.to_i, node.exclusive)
+
+ with_environment Sass::Environment.new(@environment) do
+ range.map do |i|
+ @environment.set_local_var(node.var,
+ Sass::Script::Number.new(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
+ yield
+ node.children
+ 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?
+ return Sass::Tree::CssImportNode.resolved("url(#{path})")
+ end
+ file = node.imported_file
+ handle_import_loop!(node) if @stack.any? {|e| e[:filename] == file.options[:filename]}
+
+ begin
+ @stack.push(:filename => node.filename, :line => node.line)
+ root = file.to_tree
+ Sass::Tree::Visitors::CheckNesting.visit(root)
+ node.children = root.children.map {|c| visit(c)}.flatten
+ node
+ 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
+ ensure
+ @stack.pop unless path
+ 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)
+ include_loop = true
+ handle_include_loop!(node) if @stack.any? {|e| e[:name] == node.name}
+ include_loop = false
+
+ @stack.push(:filename => node.filename, :line => node.line, :name => node.name)
+ raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin =
@environment.mixin(node.name)
+
+ 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_hash(node.keywords) {|k, v| [k, v.perform(@environment)]}
+ splat = node.splat.perform(@environment) if node.splat
+
+ self.class.perform_arguments(mixin, args, keywords, splat) do |env|
+ env.caller = Sass::Environment.new(@environment)
+ env.content = node.children 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
+ rescue Sass::SyntaxError => e
+ unless include_loop
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
+ e.add_backtrace(:line => node.line)
+ end
+ raise e
+ ensure
+ @stack.pop unless include_loop
+ end
+
+ def visit_content(node)
+ return [] unless content = @environment.content
+ @stack.push(:filename => node.filename, :line => node.line, :name => '@content')
+ trace_node = Sass::Tree::TraceNode.from_node('@content', node)
+ with_environment(@environment.caller) {trace_node.children = content.map {|c| visit(c.dup)}.flatten}
+ trace_node
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:mixin => '@content', :line => node.line)
+ e.add_backtrace(:line => node.line)
+ raise e
+ ensure
+ @stack.pop if content
+ 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
+ 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)
+ rule = node.rule
+ rule = rule.map {|e| e.is_a?(String) && e != ' ' ? e.strip : e} if node.style == :compressed
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
+ node.parsed_rules ||= parser.parse_selector
+ if node.options[:trace_selectors]
+ @stack.push(:filename => node.filename, :line => node.line)
+ node.stack_trace = stack_trace
+ @stack.pop
+ end
+ yield
+ end
+
+ # Loads the new variable value into the environment.
+ def visit_variable(node)
+ var = @environment.var(node.name)
+ return [] if node.guarded && var && !var.null?
+ val = node.expr.perform(@environment)
+ @environment.set_var(node.name, val)
+ []
+ end
+
+ # Prints the expression to STDERR with a stylesheet trace.
+ def visit_warn(node)
+ @stack.push(:filename => node.filename, :line => node.line)
+ res = node.expr.perform(@environment)
+ res = res.value if res.is_a?(Sass::Script::String)
+ msg = "WARNING: #{res}\n "
+ msg << stack_trace.join("\n ") << "\n"
+ Sass::Util.sass_warn msg
+ []
+ ensure
+ @stack.pop
+ end
+
+ # Runs the child nodes until the continuation expression becomes false.
+ def visit_while(node)
+ children = []
+ with_environment Sass::Environment.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)
+ yield
+ end
+
+ def visit_media(node)
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, 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
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, node.line)
+ node.resolved_query ||= parser.parse_media_query_list
+ end
+ yield
+ end
+
+ private
+
+ def stack_trace
+ trace = []
+ stack = @stack.map {|e| e.dup}.reverse
+ stack.each_cons(2) {|(e1, e2)| e1[:caller] = e2[:name]; [e1, e2]}
+ stack.each_with_index do |entry, i|
+ msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
+ msg << " of #{entry[:filename] || "an unknown file"}"
+ msg << ", in `#{entry[:caller]}'" if entry[:caller]
+ trace << msg
+ end
+ trace
+ end
+
+ def run_interp_no_strip(text)
+ text.map do |r|
+ next r if r.is_a?(String)
+ val = r.perform(@environment)
+ # Interpolated strings should never render with quotes
+ next val.value if val.is_a?(Sass::Script::String)
+ val.to_s
+ end.join
+ end
+
+ def run_interp(text)
+ run_interp_no_strip(text).strip
+ end
+
+ def handle_include_loop!(node)
+ msg = "An @include loop has been found:"
+ content_count = 0
+ mixins = @stack.reverse.map {|s| s[:name]}.compact.select do |s|
+ if s == '@content'
+ content_count += 1
+ false
+ elsif content_count > 0
+ content_count -= 1
+ false
+ else
+ true
+ end
+ end
+
+ return unless mixins.include?(node.name)
+ raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
+
+ msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
+ " #{m1} includes #{m2}"
+ end.join("\n")
+ raise Sass::SyntaxError.new(msg)
+ end
+
+ def handle_import_loop!(node)
+ msg = "An @import loop has been found:"
+ files = @stack.map {|s| s[: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.2.12/lib/sass/tree/visitors/set_options.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/set_options.rb
new file mode 100644
index 0000000..3cef777
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/set_options.rb
@@ -0,0 +1,125 @@
+# 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_debug(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::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
+ 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
+ yield
+ end
+
+ def visit_mixin(node)
+ node.args.each {|a| a.options = @options}
+ node.keywords.each {|k, v| v.options = @options}
+ yield
+ end
+
+ def visit_prop(node)
+ node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::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::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::Node)}
+ yield
+ end
+
+ def visit_media(node)
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Node)}
+ yield
+ end
+
+ def visit_cssimport(node)
+ node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::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.2.12/lib/sass/tree/visitors/to_css.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/to_css.rb
new file mode 100644
index 0000000..9571d09
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/tree/visitors/to_css.rb
@@ -0,0 +1,228 @@
+# A visitor for converting a Sass tree into CSS.
+class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
+ protected
+
+ def initialize
+ @tabs = 0
+ end
+
+ def visit(node)
+ super
+ rescue Sass::SyntaxError => e
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
+ raise e
+ end
+
+ def with_tabs(tabs)
+ old_tabs, @tabs = @tabs, tabs
+ yield
+ ensure
+ @tabs = old_tabs
+ end
+
+ def visit_root(node)
+ result = String.new
+ node.children.each do |child|
+ next if child.invisible?
+ child_str = visit(child)
+ result << child_str + (node.style == :compressed ? '' : "\n")
+ end
+ result.rstrip!
+ return "" if result.empty?
+ result << "\n"
+ unless Sass::Util.ruby1_8? || result.ascii_only?
+ if node.children.first.is_a?(Sass::Tree::CharsetNode)
+ begin
+ encoding = node.children.first.name
+ # Default to big-endian encoding, because we have to decide somehow
+ encoding << 'BE' if encoding =~ /\Autf-(16|32)\Z/i
+ result = result.encode(Encoding.find(encoding))
+ rescue EncodingError
+ end
+ end
+
+ result = "@charset \"#{result.encoding.name}\";#{
+ node.style == :compressed ? '' : "\n"
+ }".encode(result.encoding) + result
+ end
+ result
+ rescue Sass::SyntaxError => e
+ e.sass_template ||= node.template
+ raise e
+ end
+
+ def visit_charset(node)
+ "@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)
+ content.gsub!(%r{^(\s*)//(.*)$}) {|md| "#{$1}/*#{$2} */"} if node.type == :silent
+ content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) &&
node.type != :loud
+ content
+ end
+
+ def visit_directive(node)
+ was_in_directive = @in_directive
+ tab_str = ' ' * @tabs
+ return tab_str + node.resolved_value + ";" unless node.has_children
+ return tab_str + node.resolved_value + " {}" if node.children.empty?
+ @in_directive = @in_directive || !node.is_a?(Sass::Tree::MediaNode)
+ result = if node.style == :compressed
+ "#{node.resolved_value}{"
+ else
+ "#{tab_str}#{node.resolved_value} {" + (node.style == :compact ? ' ' : "\n")
+ end
+ 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) {result << visit(child) << ' '}
+ else
+ result[-1] = "\n" if was_prop
+ rendered = with_tabs(@tabs + 1) {visit(child).dup}
+ rendered = rendered.lstrip if first
+ result << rendered.rstrip + "\n"
+ end
+ was_prop = child.is_a?(Sass::Tree::PropNode)
+ first = false
+ elsif node.style == :compressed
+ result << (was_prop ? ";" : "") << with_tabs(0) {visit(child)}
+ was_prop = child.is_a?(Sass::Tree::PropNode)
+ else
+ result << with_tabs(@tabs + 1) {visit(child)} + "\n"
+ end
+ end
+ result.rstrip + if node.style == :compressed
+ "}"
+ else
+ (node.style == :expanded ? "\n" : " ") + "}\n"
+ end
+ ensure
+ @in_directive = was_in_directive
+ end
+
+ def visit_media(node)
+ str = with_tabs(@tabs + node.tabs) {visit_directive(node)}
+ str.gsub!(/\n\Z/, '') unless node.style == :compressed || node.group_end
+ str
+ 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)
+ if node.style == :compressed
+ "#{tab_str}#{node.resolved_name}:#{node.resolved_value}"
+ else
+ "#{tab_str}#{node.resolved_name}: #{node.resolved_value};"
+ end
+ end
+
+ 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 = [:nested, :expanded].include?(node.style) ? [rule_indent, ''] : ['',
rule_indent]
+
+ joined_rules = node.resolved_rules.members.map do |seq|
+ next if seq.has_placeholder?
+ rule_part = seq.to_a.join
+ 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.sub!(/\A\s*/, per_rule_indent)
+ joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
+ total_rule = total_indent << joined_rules
+
+ to_return = ''
+ old_spaces = ' ' * @tabs
+ if node.style != :compressed
+ if node.options[:debug_info] && ! in_directive
+ to_return << visit(debug_info_rule(node.debug_info, node.options)) << "\n"
+ elsif node.options[:trace_selectors]
+ to_return << "#{old_spaces}/* "
+ to_return << node.stack_trace.join("\n #{old_spaces}")
+ to_return << " */\n"
+ elsif node.options[:line_comments]
+ to_return << "#{old_spaces}/* line #{node.line}"
+
+ if node.filename
+ relative_filename = if node.options[:css_filename]
+ begin
+ Pathname.new(node.filename).relative_path_from(
+ Pathname.new(File.dirname(node.options[:css_filename]))).to_s
+ rescue ArgumentError
+ nil
+ end
+ end
+ relative_filename ||= node.filename
+ to_return << ", #{relative_filename}"
+ end
+
+ to_return << " */\n"
+ end
+ end
+
+ if node.style == :compact
+ properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(' ')}
+ to_return << "#{total_rule} { #{properties} }#{"\n" if node.group_end}"
+ elsif node.style == :compressed
+ properties = with_tabs(0) {node.children.map {|a| visit(a)}.join(';')}
+ to_return << "#{total_rule}{#{properties}}"
+ else
+ properties = with_tabs(@tabs + 1) {node.children.map {|a| visit(a)}.join("\n")}
+ end_props = (node.style == :expanded ? "\n" + old_spaces : ' ')
+ to_return << "#{total_rule} {\n#{properties}#{end_props}}#{"\n" if node.group_end}"
+ end
+
+ to_return
+ end
+ 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::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.2.12/lib/sass/tree/warn_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/warn_node.rb
new file mode 100644
index 0000000..a7d2e8f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Node] The expression to print
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/tree/while_node.rb
b/backends/css/gems/sass-3.2.12/lib/sass/tree/while_node.rb
new file mode 100644
index 0000000..31b2ec7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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::Node]
+ attr_accessor :expr
+
+ # @param expr [Script::Node] See \{#expr}
+ def initialize(expr)
+ @expr = expr
+ super()
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/util.rb b/backends/css/gems/sass-3.2.12/lib/sass/util.rb
new file mode 100644
index 0000000..483bf83
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/util.rb
@@ -0,0 +1,930 @@
+require 'erb'
+require 'set'
+require 'enumerator'
+require 'stringio'
+require 'rbconfig'
+require 'thread'
+
+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 = ::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)
+ 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)
+ to_hash(hash.map {|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)
+ to_hash(hash.map {|k, v| [k, yield(v)]})
+ 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)
+ # Using &block here completely hoses performance on 1.8.
+ to_hash(hash.map {|k, v| yield k, v})
+ 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
+
+ # 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
+
+ # 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)
+ return hash.sort_by {|k, v| k}
+ end
+
+ # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed
+ # order. Unlike [#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, &block)
+ return enum.group_by(&block).to_a unless ruby1_8?
+ order = {}
+ arr = []
+ enum.group_by do |e|
+ res = block[e]
+ unless order.include?(res)
+ order[res] = order.size
+ end
+ res
+ end.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 a string description of the character that caused an
+ # `Encoding::UndefinedConversionError`.
+ #
+ # @param [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::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::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(/^(.*?):(-?.*?)(?::.*`(.+)')?$/).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
+
+ # 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
+
+ @@silence_warnings = false
+ # 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)
+ return 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)
+ return 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 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}")
+ return ActionView::Template.const_get(name.to_s)
+ end
+
+ ## Cross-OS Compatibility
+
+ # Whether or not this is running on Windows.
+ #
+ # @return [Boolean]
+ def windows?
+ RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i
+ end
+
+ # Whether or not this is running on IronRuby.
+ #
+ # @return [Boolean]
+ def ironruby?
+ RUBY_ENGINE == "ironruby"
+ end
+
+ # Whether or not this is running on Rubinius.
+ #
+ # @return [Boolean]
+ def rbx?
+ RUBY_ENGINE == "rbx"
+ end
+
+ # Whether or not this is running on JRuby.
+ #
+ # @return [Boolean]
+ def 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, &block)
+ path = path.gsub('\\', '/') if windows?
+ Dir.glob(path, &block)
+ 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?
+ Sass::Util::RUBY_VERSION[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.
+ ironruby? || (Sass::Util::RUBY_VERSION[0] == 1 && Sass::Util::RUBY_VERSION[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?
+ ruby1_8? && Sass::Util::RUBY_VERSION[2] < 7
+ end
+
+ # Wehter or not this is running under JRuby 1.6 or lower.
+ def jruby1_6?
+ jruby? && jruby_version[0] == 1 && jruby_version[1] < 7
+ end
+
+ # Whether or not this is running under MacRuby.
+ #
+ # @return [Boolean]
+ def macruby?
+ RUBY_ENGINE == 'macruby'
+ end
+
+ # Checks that the encoding of a string is valid in Ruby 1.9
+ # and cleans up potential encoding gotchas like the UTF-8 BOM.
+ # If it's not, yields an error string describing the invalid character
+ # and the line on which it occurrs.
+ #
+ # @param str [String] The string of which to check the encoding
+ # @yield [msg] A block in which an encoding error can be raised.
+ # Only yields if there is an encoding error
+ # @yieldparam msg [String] The error message to be raised
+ # @return [String] `str`, potentially with encoding gotchas like BOMs removed
+ def check_encoding(str)
+ if ruby1_8?
+ return str.gsub(/\A\xEF\xBB\xBF/, '') # Get rid of the UTF-8 BOM
+ elsif str.valid_encoding?
+ # Get rid of the Unicode BOM if possible
+ if str.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?$/
+ return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
+ else
+ return str
+ end
+ end
+
+ encoding = str.encoding
+ newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
+ str.force_encoding("binary").split(newlines).each_with_index do |line, i|
+ begin
+ line.encode(encoding)
+ rescue Encoding::UndefinedConversionError => e
+ yield <<MSG.rstrip, i + 1
+Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}
+MSG
+ end
+ end
+ return str
+ end
+
+ # Like {\#check\_encoding}, but also checks for a ` charset` declaration
+ # at the beginning of the file and uses that encoding if it exists.
+ #
+ # The Sass encoding rules are simple.
+ # If a ` charset` declaration exists,
+ # we assume that that's the original encoding of the document.
+ # Otherwise, we use whatever encoding Ruby has.
+ # Then we convert that to UTF-8 to process internally.
+ # The UTF-8 end result is what's returned by this method.
+ #
+ # @param str [String] The string of which to check the encoding
+ # @yield [msg] A block in which an encoding error can be raised.
+ # Only yields if there is an encoding error
+ # @yieldparam msg [String] The error message to be raised
+ # @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`
+ def check_sass_encoding(str, &block)
+ return check_encoding(str, &block), nil if ruby1_8?
+ # We allow any printable ASCII characters but double quotes in the charset decl
+ bin = str.dup.force_encoding("BINARY")
+ encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
+ re = Sass::Util::CHARSET_REGEXPS[enc]
+ re && bin =~ re
+ end
+ charset, bom = $1, $2
+ if charset
+ charset = charset.force_encoding(encoding).encode("UTF-8")
+ if endianness = encoding[/[BL]E$/]
+ begin
+ Encoding.find(charset + endianness)
+ charset << endianness
+ rescue ArgumentError # Encoding charset + endianness doesn't exist
+ end
+ end
+ str.force_encoding(charset)
+ elsif bom
+ str.force_encoding(encoding)
+ end
+
+ str = check_encoding(str, &block)
+ return str.encode("UTF-8"), str.encoding
+ end
+
+ unless ruby1_8?
+ # @private
+ def _enc(string, encoding)
+ string.encode(encoding).force_encoding("BINARY")
+ end
+
+ # We could automatically add in any non-ASCII-compatible encodings here,
+ # but there's not really a good way to do that
+ # without manually checking that each encoding
+ # encodes all ASCII characters properly,
+ # which takes long enough to affect the startup time of the CLI.
+ ENCODINGS_TO_CHECK = %w[UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE]
+
+ CHARSET_REGEXPS = Hash.new do |h, e|
+ h[e] =
+ begin
+ # /\A(?:\uFEFF)? charset "(.*?)"|\A(\uFEFF)/
+ Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
+ _enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
+ _enc("\uFEFF", e)})/)
+ rescue Encoding::ConverterNotFoundError => _
+ nil # JRuby on Java 5 doesn't support UTF-32
+ rescue
+ # /\A charset "(.*?)"/
+ Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
+ end
+ 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
+
+ # 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 = []
+ return arr.map do |e|
+ next e.gsub('{', '{{') if e.is_a?(String)
+ values << e
+ next "{#{values.count - 1}}"
+ end.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
+
+ ## 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, &block)
+ super unless args.empty? && block.nil?
+ @set.include?(name)
+ end
+ end
+
+ # 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.
+ # @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)
+ require 'tempfile'
+ tmpfile = Tempfile.new(File.basename(filename), File.dirname(filename))
+ tmp_path = tmpfile.path
+ tmpfile.binmode if tmpfile.respond_to?(:binmode)
+ result = yield tmpfile
+ File.rename tmpfile.path, filename
+ 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
+
+ private
+
+ # 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)
+ 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
+ return c
+ end
+
+ # 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)
+ 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]
+ return lcs_backtrace(c, x, y, i-1, j, &block)
+ end
+ end
+end
+
+require 'sass/util/multibyte_string_scanner'
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/util/multibyte_string_scanner.rb
b/backends/css/gems/sass-3.2.12/lib/sass/util/multibyte_string_scanner.rb
new file mode 100644
index 0000000..7938f6c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/util/multibyte_string_scanner.rb
@@ -0,0 +1,155 @@
+require 'strscan'
+
+if Sass::Util.ruby1_8?
+ Sass::Util::MultibyteStringScanner = StringScanner
+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.2.12/lib/sass/util/subset_map.rb
b/backends/css/gems/sass-3.2.12/lib/sass/util/subset_map.rb
new file mode 100644
index 0000000..b8076f5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/util/subset_map.rb
@@ -0,0 +1,109 @@
+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 assocations 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|
+ next unless subsets = @hash[k]
+ 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]}
+ return 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.2.12/lib/sass/util/test.rb
b/backends/css/gems/sass-3.2.12/lib/sass/util/test.rb
new file mode 100644
index 0000000..b97842a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/util/test.rb
@@ -0,0 +1,10 @@
+module Sass
+ module Util
+ module Test
+ def skip(msg = nil, bt = caller)
+ super if defined?(super)
+ return
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/lib/sass/version.rb
b/backends/css/gems/sass-3.2.12/lib/sass/version.rb
new file mode 100644
index 0000000..4dcd587
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/lib/sass/version.rb
@@ -0,0 +1,126 @@
+require 'date'
+
+# This is necessary for loading Sass when Haml is required in Rails 3.
+# Once the split is complete, we can remove it.
+require File.dirname(__FILE__) + '/../sass'
+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
+ include Sass::Util
+
+ # 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
+ def version
+ return @@version if defined?(@@version)
+
+ numbers = File.read(scope('VERSION')).strip.split('.').
+ map {|n| n =~ /^[0-9]+$/ ? n.to_i : n}
+ name = File.read(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
+
+ private
+
+ def revision_number
+ if File.exists?(scope('REVISION'))
+ rev = File.read(scope('REVISION')).strip
+ return rev unless rev =~ /^([a-f0-9]+|\(.*\))$/ || rev == '(unknown)'
+ end
+
+ return unless File.exists?(scope('.git/HEAD'))
+ rev = File.read(scope('.git/HEAD')).strip
+ return rev unless rev =~ /^ref: (.*)$/
+
+ ref_name = $1
+ ref_file = scope(".git/#{ref_name}")
+ info_file = scope(".git/info/refs")
+ return File.read(ref_file).strip if File.exists?(ref_file)
+ return unless File.exists?(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
+ return nil
+ end
+
+ def version_date
+ return unless File.exists?(scope('VERSION_DATE'))
+ return DateTime.parse(File.read(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.2.12/rails/init.rb
new file mode 100644
index 0000000..13d5baa
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/rails/init.rb
@@ -0,0 +1 @@
+Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')
diff --git a/backends/css/gems/sass-3.2.12/test/Gemfile b/backends/css/gems/sass-3.2.12/test/Gemfile
new file mode 100644
index 0000000..eff8805
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/Gemfile
@@ -0,0 +1,3 @@
+source :gemcutter
+
+gem 'rake'
diff --git a/backends/css/gems/sass-3.2.12/test/Gemfile.lock b/backends/css/gems/sass-3.2.12/test/Gemfile.lock
new file mode 100644
index 0000000..ef36ebe
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/Gemfile.lock
@@ -0,0 +1,10 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ rake (0.9.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ rake
diff --git a/backends/css/gems/sass-3.2.12/test/sass/cache_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/cache_test.rb
new file mode 100755
index 0000000..11cee69
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/cache_test.rb
@@ -0,0 +1,89 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+require 'sass/engine'
+
+class CacheTest < Test::Unit::TestCase
+ @@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.exists?("#{@@cache_dir}/asdf/foo.scssc")
+ end
+
+ def test_file_cache_reads_a_file
+ file_store = Sass::CacheStores::Filesystem.new(@@cache_dir)
+ assert !File.exists?("#{@@cache_dir}/asdf/foo.scssc")
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
+ assert File.exists?("#{@@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.exists?("#{@@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.exists?("#{@@cache_dir}/asdf/foo.scssc")
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
+ assert File.exists?("#{@@cache_dir}/asdf/foo.scssc")
+ assert_nil file_store.retrieve("asdf/foo.scssc", "differentsha1")
+ assert !File.exists?("#{@@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.exists?("#{@@cache_dir}/asdf/foo.scssc")
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
+ assert File.exists?("#{@@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.exists?("#{@@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
+
+ class Unmarshalable
+ def _dump(_)
+ raise 'Unmarshalable'
+ end
+ end
+
+ def test_cache_node_with_unmarshalable_option
+ engine = Sass::Engine.new("foo {a: b + c}",
+ :syntax => :scss, :object => Unmarshalable.new, :filename => 'file.scss',
+ :importer => Sass::Importers::Filesystem.new(absolutize('templates')))
+ engine.to_tree
+ end
+
+ private
+ def root_node
+ Sass::Engine.new(<<-SCSS, :syntax => :scss).to_tree
+ @mixin color($c) { color: $c}
+ div { @include color(red); }
+ SCSS
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/callbacks_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/callbacks_test.rb
new file mode 100755
index 0000000..b0a2081
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 < Test::Unit::TestCase
+ 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.2.12/test/sass/conversion_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/conversion_test.rb
new file mode 100755
index 0000000..c14a565
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/conversion_test.rb
@@ -0,0 +1,1760 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+
+class ConversionTest < Test::Unit::TestCase
+ 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_scss_to_sass "@media screen", "@media screen {}"
+ assert_scss_to_scss "@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_sass_to_scss <<SCSS, <<SASS
+// foo
+// bar
+// baz
+// bang
+
+foo bar {
+ a: b;
+}
+SCSS
+// foo
+// bar
+// baz
+// bang
+
+foo bar
+ a: b
+SASS
+ 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_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_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_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_debug
+ assert_renders <<SASS, <<SCSS
+foo
+ @debug 12px
+ bar: baz
+SASS
+foo {
+ @debug 12px;
+ 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
+SASS
+a {
+ @each $number in 1px 2px 3px 4px {
+ b: $number;
+ }
+}
+
+c {
+ @each $str in foo, bar, baz, bang {
+ d: $str;
+ }
+}
+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_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_sass_to_scss <<SCSS, <<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
+$media1: screen
+$media2: print
+$var: -webkit-min-device-pixel-ratio
+$val: 20
+
+ media \#{$media1} and ($var + "-foo": $val + 5), only \#{$media2}
+ a: b
+SASS
+
+ assert_scss_to_sass <<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_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_scss_to_sass <<SASS, <<SCSS
+foo
+ @extend .bar !optional
+SASS
+foo {
+ @extend .bar !optional;
+}
+SCSS
+ end
+
+ def test_mixin_var_args
+ assert_scss_to_sass <<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_function_var_args
+ assert_scss_to_sass <<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
+
+ ## 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_scss_to_sass <<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_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
+
+ 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.2.12/test/sass/css2sass_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/css2sass_test.rb
new file mode 100755
index 0000000..b018f27
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/css2sass_test.rb
@@ -0,0 +1,439 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/css'
+
+class CSS2SassTest < Test::Unit::TestCase
+ 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
+ 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_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.2.12/test/sass/data/hsl-rgb.txt
new file mode 100644
index 0000000..2e9e470
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/data/hsl-rgb.txt
@@ -0,0 +1,319 @@
+hsl(0, 100%, 50%)
+hsl(60, 100%, 50%)
+hsl(120, 100%, 50%)
+hsl(180, 100%, 50%)
+hsl(240, 100%, 50%)
+hsl(300, 100%, 50%)
+====
+rgb(255, 0, 0)
+rgb(255, 255, 0)
+rgb(0, 255, 0)
+rgb(0, 255, 255)
+rgb(0, 0, 255)
+rgb(255, 0, 255)
+
+hsl(-360, 100%, 50%)
+hsl(-300, 100%, 50%)
+hsl(-240, 100%, 50%)
+hsl(-180, 100%, 50%)
+hsl(-120, 100%, 50%)
+hsl(-60, 100%, 50%)
+====
+rgb(255, 0, 0)
+rgb(255, 255, 0)
+rgb(0, 255, 0)
+rgb(0, 255, 255)
+rgb(0, 0, 255)
+rgb(255, 0, 255)
+
+hsl(360, 100%, 50%)
+hsl(420, 100%, 50%)
+hsl(480, 100%, 50%)
+hsl(540, 100%, 50%)
+hsl(600, 100%, 50%)
+hsl(660, 100%, 50%)
+====
+rgb(255, 0, 0)
+rgb(255, 255, 0)
+rgb(0, 255, 0)
+rgb(0, 255, 255)
+rgb(0, 0, 255)
+rgb(255, 0, 255)
+
+hsl(6120, 100%, 50%)
+hsl(-9660, 100%, 50%)
+hsl(99840, 100%, 50%)
+hsl(-900, 100%, 50%)
+hsl(-104880, 100%, 50%)
+hsl(2820, 100%, 50%)
+====
+rgb(255, 0, 0)
+rgb(255, 255, 0)
+rgb(0, 255, 0)
+rgb(0, 255, 255)
+rgb(0, 0, 255)
+rgb(255, 0, 255)
+
+hsl(0, 100%, 50%)
+hsl(12, 100%, 50%)
+hsl(24, 100%, 50%)
+hsl(36, 100%, 50%)
+hsl(48, 100%, 50%)
+hsl(60, 100%, 50%)
+hsl(72, 100%, 50%)
+hsl(84, 100%, 50%)
+hsl(96, 100%, 50%)
+hsl(108, 100%, 50%)
+hsl(120, 100%, 50%)
+====
+rgb(255, 0, 0)
+rgb(255, 51, 0)
+rgb(255, 102, 0)
+rgb(255, 153, 0)
+rgb(255, 204, 0)
+rgb(255, 255, 0)
+rgb(204, 255, 0)
+rgb(153, 255, 0)
+rgb(102, 255, 0)
+rgb(51, 255, 0)
+rgb(0, 255, 0)
+
+hsl(120, 100%, 50%)
+hsl(132, 100%, 50%)
+hsl(144, 100%, 50%)
+hsl(156, 100%, 50%)
+hsl(168, 100%, 50%)
+hsl(180, 100%, 50%)
+hsl(192, 100%, 50%)
+hsl(204, 100%, 50%)
+hsl(216, 100%, 50%)
+hsl(228, 100%, 50%)
+hsl(240, 100%, 50%)
+====
+rgb(0, 255, 0)
+rgb(0, 255, 51)
+rgb(0, 255, 102)
+rgb(0, 255, 153)
+rgb(0, 255, 204)
+rgb(0, 255, 255)
+rgb(0, 204, 255)
+rgb(0, 153, 255)
+rgb(0, 102, 255)
+rgb(0, 51, 255)
+rgb(0, 0, 255)
+
+hsl(240, 100%, 50%)
+hsl(252, 100%, 50%)
+hsl(264, 100%, 50%)
+hsl(276, 100%, 50%)
+hsl(288, 100%, 50%)
+hsl(300, 100%, 50%)
+hsl(312, 100%, 50%)
+hsl(324, 100%, 50%)
+hsl(336, 100%, 50%)
+hsl(348, 100%, 50%)
+hsl(360, 100%, 50%)
+====
+rgb(0, 0, 255)
+rgb(51, 0, 255)
+rgb(102, 0, 255)
+rgb(153, 0, 255)
+rgb(204, 0, 255)
+rgb(255, 0, 255)
+rgb(255, 0, 204)
+rgb(255, 0, 153)
+rgb(255, 0, 102)
+rgb(255, 0, 51)
+rgb(255, 0, 0)
+
+hsl(0, 20%, 50%)
+hsl(0, 60%, 50%)
+hsl(0, 100%, 50%)
+====
+rgb(153, 102, 102)
+rgb(204, 51, 51)
+rgb(255, 0, 0)
+
+hsl(60, 20%, 50%)
+hsl(60, 60%, 50%)
+hsl(60, 100%, 50%)
+====
+rgb(153, 153, 102)
+rgb(204, 204, 51)
+rgb(255, 255, 0)
+
+hsl(120, 20%, 50%)
+hsl(120, 60%, 50%)
+hsl(120, 100%, 50%)
+====
+rgb(102, 153, 102)
+rgb(51, 204, 51)
+rgb(0, 255, 0)
+
+hsl(180, 20%, 50%)
+hsl(180, 60%, 50%)
+hsl(180, 100%, 50%)
+====
+rgb(102, 153, 153)
+rgb(51, 204, 204)
+rgb(0, 255, 255)
+
+hsl(240, 20%, 50%)
+hsl(240, 60%, 50%)
+hsl(240, 100%, 50%)
+====
+rgb(102, 102, 153)
+rgb(51, 51, 204)
+rgb(0, 0, 255)
+
+hsl(300, 20%, 50%)
+hsl(300, 60%, 50%)
+hsl(300, 100%, 50%)
+====
+rgb(153, 102, 153)
+rgb(204, 51, 204)
+rgb(255, 0, 255)
+
+hsl(0, 100%, 0%)
+hsl(0, 100%, 10%)
+hsl(0, 100%, 20%)
+hsl(0, 100%, 30%)
+hsl(0, 100%, 40%)
+hsl(0, 100%, 50%)
+hsl(0, 100%, 60%)
+hsl(0, 100%, 70%)
+hsl(0, 100%, 80%)
+hsl(0, 100%, 90%)
+hsl(0, 100%, 100%)
+====
+rgb(0, 0, 0)
+rgb(51, 0, 0)
+rgb(102, 0, 0)
+rgb(153, 0, 0)
+rgb(204, 0, 0)
+rgb(255, 0, 0)
+rgb(255, 51, 51)
+rgb(255, 102, 102)
+rgb(255, 153, 153)
+rgb(255, 204, 204)
+rgb(255, 255, 255)
+
+hsl(60, 100%, 0%)
+hsl(60, 100%, 10%)
+hsl(60, 100%, 20%)
+hsl(60, 100%, 30%)
+hsl(60, 100%, 40%)
+hsl(60, 100%, 50%)
+hsl(60, 100%, 60%)
+hsl(60, 100%, 70%)
+hsl(60, 100%, 80%)
+hsl(60, 100%, 90%)
+hsl(60, 100%, 100%)
+====
+rgb(0, 0, 0)
+rgb(51, 51, 0)
+rgb(102, 102, 0)
+rgb(153, 153, 0)
+rgb(204, 204, 0)
+rgb(255, 255, 0)
+rgb(255, 255, 51)
+rgb(255, 255, 102)
+rgb(255, 255, 153)
+rgb(255, 255, 204)
+rgb(255, 255, 255)
+
+hsl(120, 100%, 0%)
+hsl(120, 100%, 10%)
+hsl(120, 100%, 20%)
+hsl(120, 100%, 30%)
+hsl(120, 100%, 40%)
+hsl(120, 100%, 50%)
+hsl(120, 100%, 60%)
+hsl(120, 100%, 70%)
+hsl(120, 100%, 80%)
+hsl(120, 100%, 90%)
+hsl(120, 100%, 100%)
+====
+rgb(0, 0, 0)
+rgb(0, 51, 0)
+rgb(0, 102, 0)
+rgb(0, 153, 0)
+rgb(0, 204, 0)
+rgb(0, 255, 0)
+rgb(51, 255, 51)
+rgb(102, 255, 102)
+rgb(153, 255, 153)
+rgb(204, 255, 204)
+rgb(255, 255, 255)
+
+hsl(180, 100%, 0%)
+hsl(180, 100%, 10%)
+hsl(180, 100%, 20%)
+hsl(180, 100%, 30%)
+hsl(180, 100%, 40%)
+hsl(180, 100%, 50%)
+hsl(180, 100%, 60%)
+hsl(180, 100%, 70%)
+hsl(180, 100%, 80%)
+hsl(180, 100%, 90%)
+hsl(180, 100%, 100%)
+====
+rgb(0, 0, 0)
+rgb(0, 51, 51)
+rgb(0, 102, 102)
+rgb(0, 153, 153)
+rgb(0, 204, 204)
+rgb(0, 255, 255)
+rgb(51, 255, 255)
+rgb(102, 255, 255)
+rgb(153, 255, 255)
+rgb(204, 255, 255)
+rgb(255, 255, 255)
+
+hsl(240, 100%, 0%)
+hsl(240, 100%, 10%)
+hsl(240, 100%, 20%)
+hsl(240, 100%, 30%)
+hsl(240, 100%, 40%)
+hsl(240, 100%, 50%)
+hsl(240, 100%, 60%)
+hsl(240, 100%, 70%)
+hsl(240, 100%, 80%)
+hsl(240, 100%, 90%)
+hsl(240, 100%, 100%)
+====
+rgb(0, 0, 0)
+rgb(0, 0, 51)
+rgb(0, 0, 102)
+rgb(0, 0, 153)
+rgb(0, 0, 204)
+rgb(0, 0, 255)
+rgb(51, 51, 255)
+rgb(102, 102, 255)
+rgb(153, 153, 255)
+rgb(204, 204, 255)
+rgb(255, 255, 255)
+
+hsl(300, 100%, 0%)
+hsl(300, 100%, 10%)
+hsl(300, 100%, 20%)
+hsl(300, 100%, 30%)
+hsl(300, 100%, 40%)
+hsl(300, 100%, 50%)
+hsl(300, 100%, 60%)
+hsl(300, 100%, 70%)
+hsl(300, 100%, 80%)
+hsl(300, 100%, 90%)
+hsl(300, 100%, 100%)
+====
+rgb(0, 0, 0)
+rgb(51, 0, 51)
+rgb(102, 0, 102)
+rgb(153, 0, 153)
+rgb(204, 0, 204)
+rgb(255, 0, 255)
+rgb(255, 51, 255)
+rgb(255, 102, 255)
+rgb(255, 153, 255)
+rgb(255, 204, 255)
+rgb(255, 255, 255)
diff --git a/backends/css/gems/sass-3.2.12/test/sass/engine_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/engine_test.rb
new file mode 100755
index 0000000..a325167
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/engine_test.rb
@@ -0,0 +1,3243 @@
+#!/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::String.new(@options[name.value.to_sym].to_s)
+ end
+end
+
+class SassEngineTest < Test::Unit::TestCase
+ 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" => "Cannot modulo by a number with units: 2c.",
+ "$a: 2px + #ccc" => "Cannot add a number with units (2px) to a color (#cccccc).",
+ "$a: #ccc + 2px" => "Cannot add a number with units (2px) to a color (#cccccc).",
+ "& 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.",
+ '@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\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"a\"\n\n\"a\" may only be used
at the beginning of a compound selector.", 2],
+ "foo\n &1\n b: c" => ["Invalid CSS after \"&\": expected \"{\", was \"1\"\n\n\"1\" may only be used
at the beginning of a compound selector.", 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\"",
+
+ # 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],
+ }
+
+ 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_warning <<WARNING do
+WARNING: On line 1 of test_import_same_name_different_ext_inline.sass:
+ 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
+ For now I'll choose same_name_different_ext.sass.
+ This will be an error in future versions of Sass.
+WARNING
+ options = {:load_paths => [File.dirname(__FILE__) + '/templates/']}
+ munge_filename options
+ result = Sass::Engine.new("@import 'same_name_different_ext'", options).render
+ assert_equal(<<CSS, result)
+.foo {
+ ext: sass; }
+CSS
+ end
+ end
+
+ def test_import_same_name_different_partiality
+ assert_warning <<WARNING do
+WARNING: On line 1 of test_import_same_name_different_partiality_inline.sass:
+ 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
+ For now I'll choose _same_name_different_partiality.scss.
+ This will be an error in future versions of Sass.
+WARNING
+ options = {:load_paths => [File.dirname(__FILE__) + '/templates/']}
+ munge_filename options
+ result = Sass::Engine.new("@import 'same_name_different_partiality'", options).render
+ assert_equal(<<CSS, result)
+.foo {
+ partial: yes; }
+CSS
+ 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_basic_mixin_loop_exception
+ render <<SASS
+ mixin foo
+ @include foo
+ include foo
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_equal("An @include loop has been found: foo includes itself", err.message)
+ assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 2)
+ end
+
+ def test_double_mixin_loop_exception
+ render <<SASS
+ mixin foo
+ @include bar
+ mixin bar
+ @include foo
+ include foo
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_equal(<<MESSAGE.rstrip, err.message)
+An @include loop has been found:
+ foo includes bar
+ bar includes foo
+MESSAGE
+ assert_hash_has(err.sass_backtrace[0], :mixin => "bar", :line => 4)
+ assert_hash_has(err.sass_backtrace[1], :mixin => "foo", :line => 2)
+ end
+
+ def test_deep_mixin_loop_exception
+ render <<SASS
+ mixin foo
+ @include bar
+
+ mixin bar
+ @include baz
+
+ mixin baz
+ @include foo
+
+ include foo
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_equal(<<MESSAGE.rstrip, err.message)
+An @include loop has been found:
+ foo includes bar
+ bar includes baz
+ baz includes foo
+MESSAGE
+ assert_hash_has(err.sass_backtrace[0], :mixin => "baz", :line => 8)
+ assert_hash_has(err.sass_backtrace[1], :mixin => "bar", :line => 5)
+ assert_hash_has(err.sass_backtrace[2], :mixin => "foo", :line => 2)
+ end
+
+ def test_mixin_loop_with_content
+ render <<SASS
+=foo
+ @content
+=bar
+ +foo
+ +bar
++bar
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_equal("An @include loop has been found: bar includes itself", err.message)
+ assert_hash_has(err.sass_backtrace[0], :mixin => "@content", :line => 5)
+ end
+
+ def test_basic_import_loop_exception
+ import = filename_for_test
+ importer = MockImporter.new
+ importer.add_import(import, "@import '#{import}'")
+
+ engine = Sass::Engine.new("@import '#{import}'", :filename => import,
+ :load_paths => [importer])
+
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
+An @import loop has been found: #{import} imports itself
+ERR
+ 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).split("\n")[0..15].join("\n"))
+/*
+Syntax 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
+ opts = {:full_exception => true}
+ render(<<SASS, opts)
+=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, opts).split("\n")[0..13].join("\n"))
+/*
+Syntax 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
+ opts = {:full_exception => true}
+ render(<<SASS, opts)
+.filler
+ stuff: "stuff!"
+
+a: b
+
+.more.filler
+ a: b
+SASS
+ rescue Sass::SyntaxError => e
+ assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e, opts).split("\n")[0..11].join("\n"))
+/*
+Syntax 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 url(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 url(//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.exists?(sassc_file)
+ renders_correctly "import", { :style => :compact, :load_paths => [File.dirname(__FILE__) + "/templates"]
}
+ assert File.exists?(sassc_file)
+ end
+
+ def test_sass_pathname_import
+ sassc_file = sassc_path("importee")
+ assert !File.exists?(sassc_file)
+ renders_correctly("import",
+ :style => :compact,
+ :load_paths => [Pathname.new(File.dirname(__FILE__) + "/templates")])
+ assert File.exists?(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.exists?(sassc_path("importee"))
+ renders_correctly("import", {
+ :style => :compact, :cache => false,
+ :load_paths => [File.dirname(__FILE__) + "/templates"],
+ })
+ assert !File.exists?(sassc_path("importee"))
+ end
+
+ def test_import_in_rule
+ assert_equal(<<CSS, render(<<SASS, :load_paths => [File.dirname(__FILE__) + '/templates/']))
+.foo #foo {
+ background-color: #bbaaff; }
+
+.bar {
+ a: b; }
+ .bar #foo {
+ background-color: #bbaaff; }
+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("white {\n color: white; }\n\nblack {\n color: black; }\n", render(<<SASS))
+=foo($a: #FFF)
+ :color $a
+white
+ +foo
+black
+ +foo(#000)
+SASS
+ assert_equal(<<CSS, render(<<SASS))
+one {
+ color: white;
+ padding: 1px;
+ margin: 4px; }
+
+two {
+ color: white;
+ padding: 2px;
+ margin: 5px; }
+
+three {
+ color: white;
+ 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: white;
+ padding: 1px;
+ margin: 4px; }
+
+two {
+ color: white;
+ padding: 2px;
+ margin: 5px; }
+
+three {
+ color: white;
+ 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_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
+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_variable_reassignment
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: 1;
+ c: 2; }
+CSS
+$a: 1
+a
+ b: $a
+ $a: 2
+ c: $a
+SASS
+ end
+
+ def test_variable_scope
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b-1: c;
+ b-2: c;
+ d: 12; }
+
+b {
+ d: 17; }
+CSS
+$i: 12
+a
+ @for $i from 1 through 2
+ b-\#{$i}: c
+ d: $i
+
+=foo
+ $i: 17
+
+b
+ +foo
+ d: $i
+SASS
+ end
+
+ def test_hyphen_underscore_insensitive_variables
+ assert_equal(<<CSS, render(<<SASS))
+a {
+ b: c; }
+
+d {
+ e: 13;
+ f: foobar; }
+CSS
+$var-hyphen: 12
+$var_under: foo
+
+a
+ $var_hyphen: 1 + $var_hyphen
+ $var-under: $var-under + bar
+ b: c
+
+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_raise(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_doesnt_deep_unquote_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_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
+
+ # Regression tests
+
+ 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_tricky_mixin_loop_exception
+ render <<SASS
+ mixin foo($a)
+ @if $a
+ @include foo(false)
+ @include foo(true)
+ @else
+ a: b
+
+a
+ @include foo(true)
+SASS
+ assert(false, "Exception not raised")
+ rescue Sass::SyntaxError => err
+ assert_equal("An @include loop has been found: foo includes itself", err.message)
+ assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
+ 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
+
+ # Encodings
+
+ unless Sass::Util.ruby1_8?
+ def test_encoding_error
+ 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
+ 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_same_charset_as_encoding
+ assert_renders_encoded(<<CSS, <<SASS)
+ charset "UTF-8";
+fóó {
+ a: b; }
+CSS
+ charset "utf-8"
+fóó
+ a: b
+SASS
+ end
+
+ def test_different_charset_than_encoding
+ assert_renders_encoded(<<CSS.force_encoding("IBM866"), <<SASS)
+ charset "IBM866";
+fóó {
+ a: b; }
+CSS
+ charset "ibm866"
+fóó
+ a: b
+SASS
+ end
+
+ def test_different_encoding_than_system
+ assert_renders_encoded(<<CSS.encode("IBM866"), <<SASS.encode("IBM866"))
+ charset "IBM866";
+тАЬ {
+ a: b; }
+CSS
+тАЬ
+ a: b
+SASS
+ end
+
+ def test_multibyte_charset
+ assert_renders_encoded(<<CSS.encode("UTF-16LE"), <<SASS.encode("UTF-16LE").force_encoding("UTF-8"))
+ charset "UTF-16LE";
+fóó {
+ a: b; }
+CSS
+ charset "utf-16le"
+fóó
+ a: b
+SASS
+ end
+
+ def test_multibyte_charset_without_endian_specifier
+ assert_renders_encoded(<<CSS.encode("UTF-32BE"), <<SASS.encode("UTF-32BE").force_encoding("UTF-8"))
+ charset "UTF-32BE";
+fóó {
+ a: b; }
+CSS
+ charset "utf-32"
+fóó
+ a: b
+SASS
+ end
+
+ def test_utf8_bom
+ assert_renders_encoded(<<CSS, <<SASS.force_encoding("BINARY"))
+ charset "UTF-8";
+fóó {
+ a: b; }
+CSS
+\uFEFFfóó
+ a: b
+SASS
+ end
+
+ def test_utf16le_bom
+ assert_renders_encoded(<<CSS.encode("UTF-16LE"), <<SASS.encode("UTF-16LE").force_encoding("BINARY"))
+ charset "UTF-16LE";
+fóó {
+ a: b; }
+CSS
+\uFEFFfóó
+ a: b
+SASS
+ end
+
+ def test_utf32be_bom
+ assert_renders_encoded(<<CSS.encode("UTF-32BE"), <<SASS.encode("UTF-32BE").force_encoding("BINARY"))
+ charset "UTF-32BE";
+fóó {
+ a: b; }
+CSS
+\uFEFFfóó
+ a: b
+SASS
+ end
+
+ # Encoding Regression Test
+
+ def test_multibyte_prop_name
+ assert_equal(<<CSS, render(<<SASS))
+ charset "UTF-8";
+#bar {
+ cölor: blue; }
+CSS
+#bar
+ cölor: blue
+SASS
+ end
+
+ def test_multibyte_and_interpolation
+ assert_equal(<<CSS, render(<<SCSS, :syntax => :scss))
+#bar {
+ background: a 0%; }
+CSS
+#bar {
+ //
+ background: \#{a} 0%;
+}
+SCSS
+ end
+ 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_deprecated_PRECISION
+ assert_warning(<<END) {assert_equal 100_000.0, Sass::Script::Number::PRECISION}
+Sass::Script::Number::PRECISION is deprecated and will be removed in a future release. Use
Sass::Script::Number.precision_factor instead.
+END
+ end
+
+ def test_changing_precision
+ old_precision = Sass::Script::Number.precision
+ begin
+ Sass::Script::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::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
+
+ 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.2.12/test/sass/exec_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/exec_test.rb
new file mode 100755
index 0000000..1f3e113
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 < Test::Unit::TestCase
+ 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 -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.2.12/test/sass/extend_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/extend_test.rb
new file mode 100755
index 0000000..fb24ce9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/extend_test.rb
@@ -0,0 +1,1482 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+
+class ExtendTest < Test::Unit::TestCase
+ 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
+ assert_unification '.foo#baz', '#bar { extend .foo}', '.foo#baz'
+ 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
+ assert_unification 'ns1|*.foo', 'ns2|* { extend .foo}', 'ns1|*.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
+ assert_unification 'ns1|a.foo', 'ns2|* { extend .foo}', 'ns1|a.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
+ assert_unification 'ns1|*.foo', 'ns2|a { extend .foo}', 'ns1|*.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
+ assert_unification 'a.foo', 'h1 { extend .foo}', 'a.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
+ assert_unification 'ns1|a.foo', 'ns2|a { extend .foo}', 'ns1|a.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
+ assert_unification '::foo.baz', '::bar { extend .baz}', '::foo.baz'
+ end
+
+ assert_extend_doesnt_match('::foo(2n+1)', '.baz', :failed_to_unify, 2) do
+ assert_unification '::foo.baz', '::foo(2n+1) { extend .baz}', '::foo.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_unification ':not(.foo).baz', ':not(.bar) { extend .baz}', ':not(.foo).baz, :not(.foo):not(.bar)'
+ assert_unification ':not(.foo).baz', ':not(.foo) { extend .baz}', ':not(.foo)'
+ assert_unification ':not([a=b]).baz', ':not([a = b]) { extend .baz}', ':not([a=b])'
+ 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
+
+ ## 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
+ assert_extends '.foo', '.baz { extend .foo.bar}', '.foo'
+ 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
+ assert_extends 'a.foo#bar', 'h1.baz { extend .foo}', 'a.foo#bar'
+ end
+
+ assert_extend_doesnt_match('.bang#baz', '.foo', :failed_to_unify, 2) do
+ assert_extends 'a.foo#bar', '.bang#baz { extend .foo}', 'a.foo#bar'
+ 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
+ assert_extends 'baz.foo', 'foo bar { extend .foo}', 'baz.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_another_nested_extender_with_early_child_selectors_doesnt_subseq_them
+ 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_raise(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)
+.bar, .foo {
+ a: b; }
+
+.foo, .bar {
+ 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)
+.baz, .bar, .foo {
+ a: b; }
+
+.foo, .baz, .bar {
+ c: d; }
+
+.bar, .foo, .baz {
+ 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_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
+ assert_equal <<CSS, render(<<SCSS)
+a.baz.bar {
+ color: blue; }
+CSS
+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_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_warning(<<WARN) {assert_equal(<<CSS, render(<<SCSS))}
+DEPRECATION WARNING on line 3 of test_extend_out_of_media_inline.scss:
+ @extending an outer selector from within @media is deprecated.
+ You may only @extend selectors within the same directive.
+ This will be an error in Sass 3.3.
+ It can only work once @extend is supported natively in the browser.
+WARN
+.foo {
+ a: b; }
+CSS
+.foo {a: b}
+ media screen {
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_out_of_unknown_directive
+ assert_warning(<<WARN) {assert_equal(<<CSS, render(<<SCSS))}
+DEPRECATION WARNING on line 3 of test_extend_out_of_unknown_directive_inline.scss:
+ @extending an outer selector from within @flooblehoof is deprecated.
+ You may only @extend selectors within the same directive.
+ This will be an error in Sass 3.3.
+ It can only work once @extend is supported natively in the browser.
+WARN
+.foo {
+ a: b; }
+
+ flooblehoof {}
+CSS
+.foo {a: b}
+ flooblehoof {
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_out_of_nested_directives
+ assert_warning(<<WARN) {assert_equal(<<CSS, render(<<SCSS))}
+DEPRECATION WARNING on line 4 of test_extend_out_of_nested_directives_inline.scss:
+ @extending an outer selector from within @flooblehoof is deprecated.
+ You may only @extend selectors within the same directive.
+ This will be an error in Sass 3.3.
+ It can only work once @extend is supported natively in the browser.
+WARN
+ media screen {
+ .foo {
+ a: b; }
+
+ @flooblehoof {} }
+CSS
+ 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_warning(<<WARN) {assert_equal(<<CSS, render(<<SCSS))}
+DEPRECATION WARNING on line 4 of test_extend_within_and_without_media_inline.scss:
+ @extending an outer selector from within @media is deprecated.
+ You may only @extend selectors within the same directive.
+ This will be an error in Sass 3.3.
+ It can only work once @extend is supported natively in the browser.
+WARN
+.foo {
+ a: b; }
+
+ media screen {
+ .foo, .bar {
+ c: d; } }
+CSS
+.foo {a: b}
+ media screen {
+ .foo {c: d}
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_within_and_without_unknown_directive
+ assert_warning(<<WARN) {assert_equal(<<CSS, render(<<SCSS))}
+DEPRECATION WARNING on line 4 of test_extend_within_and_without_unknown_directive_inline.scss:
+ @extending an outer selector from within @flooblehoof is deprecated.
+ You may only @extend selectors within the same directive.
+ This will be an error in Sass 3.3.
+ It can only work once @extend is supported natively in the browser.
+WARN
+.foo {
+ a: b; }
+
+ flooblehoof {
+ .foo, .bar {
+ c: d; } }
+CSS
+.foo {a: b}
+ flooblehoof {
+ .foo {c: d}
+ .bar { extend .foo}
+}
+SCSS
+ end
+
+ def test_extend_within_and_without_nested_directives
+ assert_warning(<<WARN) {assert_equal(<<CSS, render(<<SCSS))}
+DEPRECATION WARNING on line 5 of test_extend_within_and_without_nested_directives_inline.scss:
+ @extending an outer selector from within @flooblehoof is deprecated.
+ You may only @extend selectors within the same directive.
+ This will be an error in Sass 3.3.
+ It can only work once @extend is supported natively in the browser.
+WARN
+ media screen {
+ .foo {
+ a: b; }
+
+ @flooblehoof {
+ .foo, .bar {
+ c: d; } } }
+CSS
+ media screen {
+ .foo {a: b}
+ @flooblehoof {
+ .foo {c: d}
+ .bar { extend .foo}
+ }
+}
+SCSS
+ end
+
+ def test_extend_with_subject_transfers_subject_to_extender
+ 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
+
+ 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
+ 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
+ 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
+ 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
+ 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_warning(<<WARN) {assert_equal("", render(<<SCSS))}
+WARNING on line 1 of test_extend_warns_when_extendee_doesnt_exist_inline.scss: ".foo" failed to @extend
".bar".
+ The selector ".bar" was not found.
+ This will be an error in future releases of Sass.
+ Use "@extend .bar !optional" if the extend should be able to fail.
+WARN
+.foo { extend .bar}
+SCSS
+ end
+
+ def test_extend_warns_when_extension_fails
+ assert_warning(<<WARN) {assert_equal(<<CSS, render(<<SCSS))}
+WARNING on line 2 of test_extend_warns_when_extension_fails_inline.scss: "b.foo" failed to @extend ".bar".
+ No selectors matching ".bar" could be unified with "b.foo".
+ This will be an error in future releases of Sass.
+ Use "@extend .bar !optional" if the extend should be able to fail.
+WARN
+a.bar {
+ a: b; }
+CSS
+a.bar {a: b}
+b.foo { extend .bar}
+SCSS
+ end
+
+ def test_extend_does_not_warn_when_one_extension_fails_but_others_dont
+ assert_no_warning {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_does_not_warn_when_extendee_doesnt_exist
+ assert_no_warning {assert_equal("", render(<<SCSS))}
+.foo { extend .bar !optional}
+SCSS
+ end
+
+ def test_optional_extend_does_not_warn_when_extension_fails
+ assert_no_warning {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_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)
+ warn = "\"#{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_warning(<<WARNING) {yield}
+WARNING on line #{line} of #{filename_for_test syntax}: #{warn}
+ #{reason}
+ This will be an error in future releases of Sass.
+ Use "@extend #{target} !optional" if the extend should be able to fail.
+WARNING
+ end
+
+ def assert_unification(selector, extension, unified)
+ # 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_extends(selector, extension, result)
+ assert_equal <<CSS, render(<<SCSS)
+#{result} {
+ a: b; }
+CSS
+#{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.2.12/test/sass/fixtures/test_staleness_check_across_importers.css
new file mode 100644
index 0000000..77b6225
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.css
@@ -0,0 +1 @@
+.pear { color: green; }
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.2.12/test/sass/fixtures/test_staleness_check_across_importers.scss
new file mode 100644
index 0000000..1468ac9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.scss
@@ -0,0 +1 @@
+ import "apple";
diff --git a/backends/css/gems/sass-3.2.12/test/sass/functions_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/functions_test.rb
new file mode 100755
index 0000000..7498f7a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/functions_test.rb
@@ -0,0 +1,1139 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/script'
+
+module Sass::Script::Functions
+ def no_kw_args
+ Sass::Script::String.new("no-kw-args")
+ end
+
+ def only_var_args(*args)
+ Sass::Script::String.new("only-var-args("+args.map{|a| a.plus(Sass::Script::Number.new(1)).to_s
}.join(", ")+")")
+ end
+ declare :only_var_args, [], :var_args => true
+
+ def only_kw_args(kwargs)
+ Sass::Script::String.new("only-kw-args(" + kwargs.keys.map {|a| a.to_s}.sort.join(", ") + ")")
+ end
+ declare :only_kw_args, [], :var_kwargs => true
+end
+
+module Sass::Script::Functions::UserFunctions
+ def call_options_on_new_literal
+ str = Sass::Script::String.new("foo")
+ str.options[:foo]
+ str
+ end
+
+ def user_defined
+ Sass::Script::String.new("I'm a user-defined string!")
+ end
+
+ def _preceding_underscore
+ Sass::Script::String.new("I'm another user-defined string!")
+ end
+end
+
+module Sass::Script::Functions
+ include Sass::Script::Functions::UserFunctions
+end
+
+class SassFunctionTest < Test::Unit::TestCase
+ # 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_checks_bounds
+ assert_error_message("Saturation -114 must be between 0% and 100% for `hsl'", "hsl(10, -114, 12)");
+ assert_error_message("Lightness 256% must be between 0% and 100% for `hsl'", "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_checks_bounds
+ assert_error_message("Saturation -114 must be between 0% and 100% for `hsla'", "hsla(10, -114, 12, 1)");
+ assert_error_message("Lightness 256% must be between 0% and 100% for `hsla'", "hsla(10, 10, 256%, 0)");
+ assert_error_message("Alpha channel -0.1 must be between 0 and 1 for `hsla'", "hsla(10, 10, 10, -0.1)");
+ assert_error_message("Alpha channel 1.1 must be between 0 and 1 for `hsla'", "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_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($value: 0.5)"))
+ end
+
+ def test_percentage_checks_types
+ assert_error_message("$value: 25px is not a unitless number for `percentage'", "percentage(25px)")
+ assert_error_message("$value: #cccccc is not a unitless number for `percentage'", "percentage(#ccc)")
+ assert_error_message("$value: \"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($value: 5.49px)"))
+
+ 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($value: 4.8px)"))
+
+ 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($value: 4.8px)"))
+
+ 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($value: 5px)"))
+
+ 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_tests_bounds
+ assert_error_message("$red: Color value 256 must be between 0 and 255 for `rgb'",
+ "rgb(256, 1, 1)")
+ assert_error_message("$green: Color value 256 must be between 0 and 255 for `rgb'",
+ "rgb(1, 256, 1)")
+ assert_error_message("$blue: Color value 256 must be between 0 and 255 for `rgb'",
+ "rgb(1, 1, 256)")
+ assert_error_message("$green: Color value 256 must be between 0 and 255 for `rgb'",
+ "rgb(1, 256, 257)")
+ assert_error_message("$red: Color value -1 must be between 0 and 255 for `rgb'",
+ "rgb(-1, 1, 1)")
+ end
+
+ def test_rgb_test_percent_bounds
+ assert_error_message("$red: Color value 100.1% must be between 0% and 100% for `rgb'",
+ "rgb(100.1%, 0, 0)")
+ assert_error_message("$green: Color value -0.1% must be between 0% and 100% for `rgb'",
+ "rgb(0, -0.1%, 0)")
+ assert_error_message("$blue: Color value 101% must be between 0% and 100% for `rgb'",
+ "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_tests_bounds
+ assert_error_message("$red: Color value 256 must be between 0 and 255 for `rgba'",
+ "rgba(256, 1, 1, 0.3)")
+ assert_error_message("$green: Color value 256 must be between 0 and 255 for `rgba'",
+ "rgba(1, 256, 1, 0.3)")
+ assert_error_message("$blue: Color value 256 must be between 0 and 255 for `rgba'",
+ "rgba(1, 1, 256, 0.3)")
+ assert_error_message("$green: Color value 256 must be between 0 and 255 for `rgba'",
+ "rgba(1, 256, 257, 0.3)")
+ assert_error_message("$red: Color value -1 must be between 0 and 255 for `rgba'",
+ "rgba(-1, 1, 1, 0.3)")
+ assert_error_message("Alpha channel -0.2 must be between 0 and 1 for `rgba'",
+ "rgba(1, 1, 1, -0.2)")
+ assert_error_message("Alpha channel 1.2 must be between 0 and 1 for `rgba'",
+ "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_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("rgba(0, 0, 0, 0)", evaluate("fade_out(rgba(0, 0, 0, 0.2), 0.2)"))
+ assert_equal("rgba(0, 0, 0, 0)", 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("$saturation: Amount 80 must be a % (e.g. 80%) for `scale-color'",
+ "scale-color(blue, $saturation: 80)")
+ assert_error_message("$alpha: Amount 0.5 must be a % (e.g. 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($color-1: transparentize(#f00, 1), $color-2: #00f,
$weight: 100%)"))
+ end
+
+ def test_mix_tests_types
+ assert_error_message("$color-1: \"foo\" is not a color for `mix'", "mix(\"foo\", #f00, 10%)")
+ assert_error_message("$color-2: \"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_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_options_on_new_literals_fails
+ assert_error_message(<<MSG, "call-options-on-new-literal()")
+The #options attribute is not set on this Sass::Script::String.
+ This error is probably occurring because #to_s was called
+ on this literal within a custom Sass function without first
+ setting the #option 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)"))
+ 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($number-1: 100px, $number-2: 3em)"))
+ assert_error_message("$number-1: #ff0000 is not a number for `comparable'", "comparable(#f00, 1px)")
+ assert_error_message("$number-2: #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)"))
+ 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), 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 greater than or equal to 1 for `nth'", "nth(foo, 0)")
+ assert_error_message("List index -10 must be greater than or equal to 1 for `nth'", "nth(foo, -10)")
+ assert_error_message("List index 1.5 must be an 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)")
+ 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)")
+ 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)")
+ 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)"))
+ end
+
+ def test_index
+ 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("false", evaluate("index(1px solid blue, 1em)"))
+ assert_equal("false", evaluate("index(1px solid blue, notfound)"))
+ assert_equal("false", evaluate("index(1px, #00f)"))
+ 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)"))
+ 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
+
+ ## Regression Tests
+
+ def test_saturation_bounds
+ assert_equal "#fbfdff", evaluate("hsl(hue(#fbfdff), saturation(#fbfdff), lightness(#fbfdff))")
+ end
+
+ private
+
+ def evaluate(value)
+ result = perform(value)
+ assert_kind_of Sass::Script::Literal, result
+ return result.to_s
+ end
+
+ def perform(value)
+ Sass::Script::Parser.parse(value, 0, 0).perform(Sass::Environment.new)
+ 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.2.12/test/sass/importer_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/importer_test.rb
new file mode 100755
index 0000000..bdf63e1
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/importer_test.rb
@@ -0,0 +1,192 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require File.dirname(__FILE__) + '/test_helper'
+
+require 'sass/plugin'
+
+class ImporterTest < Test::Unit::TestCase
+
+ class FruitImporter < Sass::Importers::Base
+ def find(name, context = nil)
+ if name =~ %r{fruits/(\w+)(\.s[ac]ss)?}
+ fruit = $1
+ color = case $1
+ 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
+ end
+
+ def key(name, context)
+ [self.class.name, name]
+ 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 fixture_dir
+ File.join(File.dirname(__FILE__), "fixtures")
+ end
+
+ def fixture_file(path)
+ File.join(fixture_dir, path)
+ end
+
+ def test_absolute_files_across_template_locations
+ importer = Sass::Importers::Filesystem.new(absolutize 'templates')
+ assert_not_nil importer.mtime(absolutize('more_templates/more1.sass'), {})
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/logger_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/logger_test.rb
new file mode 100755
index 0000000..7e0811d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/logger_test.rb
@@ -0,0 +1,58 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require 'pathname'
+
+class LoggerTest < Test::Unit::TestCase
+
+ 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.2.12/test/sass/mock_importer.rb
new file mode 100644
index 0000000..312476d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/mock_importer.rb
@@ -0,0 +1,49 @@
+class MockImporter < Sass::Importers::Base
+ def initialize(name = "mock")
+ @name = name
+ @imports = Hash.new({})
+ end
+
+ def find_relative(uri, base, options)
+ nil
+ end
+
+ def find(uri, options)
+ contents = @imports[uri][:contents]
+ return unless contents
+ options[:syntax] = @imports[uri][:syntax]
+ options[:filename] = uri
+ options[:importer] = self
+ @imports[uri][:engine] = Sass::Engine.new(contents, options)
+ end
+
+ def mtime(uri, options)
+ @imports[uri][:mtime]
+ end
+
+ def key(uri, options)
+ ["mock", uri]
+ end
+
+ def to_s
+ @name
+ end
+
+ # Methods for testing
+
+ def add_import(uri, contents, syntax = :scss, mtime = Time.now - 10)
+ @imports[uri] = {
+ :contents => contents,
+ :mtime => mtime,
+ :syntax => syntax
+ }
+ end
+
+ def touch(uri)
+ @imports[uri][:mtime] = Time.now
+ end
+
+ def engine(uri)
+ @imports[uri][:engine]
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_results/more1.css
b/backends/css/gems/sass-3.2.12/test/sass/more_results/more1.css
new file mode 100644
index 0000000..b0d1182
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/more_results/more1.css
@@ -0,0 +1,9 @@
+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; }
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.2.12/test/sass/more_results/more1_with_line_comments.css
new file mode 100644
index 0000000..f31dbca
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/more_results/more1_with_line_comments.css
@@ -0,0 +1,26 @@
+/* line 3, ../more_templates/more1.sass */
+body {
+ font: Arial;
+ background: blue; }
+
+/* line 7, ../more_templates/more1.sass */
+#page {
+ width: 700px;
+ height: 100; }
+ /* line 10, ../more_templates/more1.sass */
+ #page #header {
+ height: 300px; }
+ /* line 12, ../more_templates/more1.sass */
+ #page #header h1 {
+ font-size: 50px;
+ color: blue; }
+
+/* line 18, ../more_templates/more1.sass */
+#content.user.show #container.top #column.left {
+ width: 100px; }
+/* line 20, ../more_templates/more1.sass */
+#content.user.show #container.top #column.right {
+ width: 600px; }
+/* line 22, ../more_templates/more1.sass */
+#content.user.show #container.bottom {
+ background: brown; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_results/more_import.css
b/backends/css/gems/sass-3.2.12/test/sass/more_results/more_import.css
new file mode 100644
index 0000000..7f4d5a7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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: #bbaaff; }
+
+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.2.12/test/sass/more_templates/_more_partial.sass
new file mode 100644
index 0000000..bef627d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/more_templates/_more_partial.sass
@@ -0,0 +1,2 @@
+#foo
+ :background-color #baf
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_templates/more1.sass
b/backends/css/gems/sass-3.2.12/test/sass/more_templates/more1.sass
new file mode 100644
index 0000000..71117bf
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/more_templates/more1.sass
@@ -0,0 +1,23 @@
+
+
+body
+ :font Arial
+ :background blue
+
+#page
+ :width 700px
+ :height 100
+ #header
+ :height 300px
+ h1
+ :font-size 50px
+ :color blue
+
+#content.user.show
+ #container.top
+ #column.left
+ :width 100px
+ #column.right
+ :width 600px
+ #container.bottom
+ :background brown
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/more_templates/more_import.sass
b/backends/css/gems/sass-3.2.12/test/sass/more_templates/more_import.sass
new file mode 100644
index 0000000..8bb8430
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/more_templates/more_import.sass
@@ -0,0 +1,11 @@
+$preconst: hello
+
+=premixin
+ pre-mixin: here
+
+ import importee, basic, basic.css, ../results/complex.css, more_partial
+
+nonimported
+ :myconst $preconst
+ :otherconst $postconst
+ +postmixin
diff --git a/backends/css/gems/sass-3.2.12/test/sass/plugin_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/plugin_test.rb
new file mode 100755
index 0000000..14e9c86
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/plugin_test.rb
@@ -0,0 +1,550 @@
+#!/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::String.new(filename)
+ end
+
+ def whatever
+ custom = options[:custom]
+ whatever = custom && custom[:whatever]
+ Sass::Script::String.new(whatever || "incorrect")
+ end
+end
+
+class SassPluginTest < Test::Unit::TestCase
+ @@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
+ FileUtils.mkdir_p tempfile_loc
+ FileUtils.mkdir_p tempfile_loc(nil,"more_")
+ set_plugin_opts
+ check_for_updates!
+ reset_mtimes
+ end
+
+ def teardown
+ clean_up_sassc
+ Sass::Plugin.reset!
+ FileUtils.rm_r tempfile_loc
+ 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"))
+/*
+Syntax 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"))
+/*
+Syntax 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"))
+/*
+Syntax 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"))
+/*
+Syntax 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_nonfull_exception_handling
+ old_full_exception = Sass::Plugin.options[:full_exception]
+ Sass::Plugin.options[:full_exception] = false
+
+ File.delete(tempfile_loc('bork1'))
+ assert_raise(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.exists?(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_updating_stylesheets_callback
+ # Should run even when there's nothing to update
+ Sass::Plugin.options[:template_location] = nil
+ assert_callback :updating_stylesheets, []
+ end
+
+ def test_updating_stylesheets_callback_with_never_update
+ Sass::Plugin.options[:never_update] = true
+ assert_no_callback :updating_stylesheets
+ end
+
+ 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 {|f| File.utime(atime, mtime, f)}
+ end
+
+ def template_loc(name = nil, prefix = nil)
+ if name
+ scss = absolutize "#{prefix}templates/#{name}.scss"
+ File.exists?(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
+ )
+ 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.2.12/test/sass/results/alt.css
new file mode 100644
index 0000000..8484343
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/alt.css
@@ -0,0 +1,4 @@
+h1 { float: left; width: 274px; height: 75px; margin: 0; background-repeat: no-repeat; background-image:
none; }
+h1 a:hover, h1 a:visited { color: green; }
+h1 b:hover { color: red; background-color: green; }
+h1 const { nosp: 3; sp: 3; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/basic.css
b/backends/css/gems/sass-3.2.12/test/sass/results/basic.css
new file mode 100644
index 0000000..b0d1182
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/basic.css
@@ -0,0 +1,9 @@
+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; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/cached_import_option.css
b/backends/css/gems/sass-3.2.12/test/sass/results/cached_import_option.css
new file mode 100644
index 0000000..331c0e7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/cached_import_option.css
@@ -0,0 +1,3 @@
+partial { value: correct; }
+
+main { value: correct; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/compact.css
b/backends/css/gems/sass-3.2.12/test/sass/results/compact.css
new file mode 100644
index 0000000..3c33e8b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/compact.css
@@ -0,0 +1,5 @@
+#main { width: 15em; color: blue; }
+#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/complex.css
b/backends/css/gems/sass-3.2.12/test/sass/results/complex.css
new file mode 100644
index 0000000..9fcb03f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/complex.css
@@ -0,0 +1,86 @@
+body { margin: 0; font: 0.85em "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; color: white;
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: white; background: #00a4e4; }
+#menu .contests a:link, #menu .contests a:visited { color: white; 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: white; 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.2.12/test/sass/results/compressed.css
new file mode 100644
index 0000000..7c62786
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/compressed.css
@@ -0,0 +1 @@
+#main{width:15em;color:blue}#main p{border-style:dotted;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/expanded.css
b/backends/css/gems/sass-3.2.12/test/sass/results/expanded.css
new file mode 100644
index 0000000..b42e076
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/expanded.css
@@ -0,0 +1,19 @@
+#main {
+ width: 15em;
+ color: blue;
+}
+#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.2.12/test/sass/results/filename_fn.css
new file mode 100644
index 0000000..e55d9a7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/filename_fn.css
@@ -0,0 +1,3 @@
+filename { imported: /test/sass/templates/_filename_fn_import.scss; }
+
+filename { local: /test/sass/templates/filename_fn.scss; local-mixin: /test/sass/templates/filename_fn.scss;
local-function: /test/sass/templates/filename_fn.scss; imported-mixin:
/test/sass/templates/_filename_fn_import.scss; imported-function:
/test/sass/templates/_filename_fn_import.scss; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/if.css
b/backends/css/gems/sass-3.2.12/test/sass/results/if.css
new file mode 100644
index 0000000..31e8813
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/if.css
@@ -0,0 +1,3 @@
+a { branch: if; }
+
+b { branch: else; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import.css
b/backends/css/gems/sass-3.2.12/test/sass/results/import.css
new file mode 100644
index 0000000..87ae3c8
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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: #bbaaff; }
+
+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.2.12/test/sass/results/import_charset.css
new file mode 100644
index 0000000..d7184a3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/import_charset.css
@@ -0,0 +1,5 @@
+ charset "UTF-8";
+ import url(foo.css);
+.foo { a: b; }
+
+.bar { a: щ; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import_charset_1_8.css
b/backends/css/gems/sass-3.2.12/test/sass/results/import_charset_1_8.css
new file mode 100644
index 0000000..59cb744
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/import_charset_1_8.css
@@ -0,0 +1,5 @@
+ charset "IBM866";
+ import url(foo.css);
+.foo { a: b; }
+
+.bar { a: �; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import_charset_ibm866.css
b/backends/css/gems/sass-3.2.12/test/sass/results/import_charset_ibm866.css
new file mode 100644
index 0000000..59cb744
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/import_charset_ibm866.css
@@ -0,0 +1,5 @@
+ charset "IBM866";
+ import url(foo.css);
+.foo { a: b; }
+
+.bar { a: �; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/import_content.css
b/backends/css/gems/sass-3.2.12/test/sass/results/import_content.css
new file mode 100644
index 0000000..c67e3f9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/import_content.css
@@ -0,0 +1 @@
+a { b: c; }
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/line_numbers.css
b/backends/css/gems/sass-3.2.12/test/sass/results/line_numbers.css
new file mode 100644
index 0000000..3c657f9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/line_numbers.css
@@ -0,0 +1,49 @@
+/* line 1, ../templates/line_numbers.sass */
+foo {
+ bar: baz; }
+
+/* line 6, ../templates/importee.sass */
+imported {
+ otherconst: 12;
+ myconst: goodbye; }
+ /* line 5, ../templates/line_numbers.sass */
+ imported squggle {
+ blat: bang; }
+
+/* line 3, ../templates/basic.sass */
+body {
+ font: Arial;
+ background: blue; }
+
+/* line 7, ../templates/basic.sass */
+#page {
+ width: 700px;
+ height: 100; }
+ /* line 10, ../templates/basic.sass */
+ #page #header {
+ height: 300px; }
+ /* line 12, ../templates/basic.sass */
+ #page #header h1 {
+ font-size: 50px;
+ color: blue; }
+
+/* line 18, ../templates/basic.sass */
+#content.user.show #container.top #column.left {
+ width: 100px; }
+/* line 20, ../templates/basic.sass */
+#content.user.show #container.top #column.right {
+ width: 600px; }
+/* line 22, ../templates/basic.sass */
+#content.user.show #container.bottom {
+ background: brown; }
+
+/* line 13, ../templates/importee.sass */
+midrule {
+ inthe: middle; }
+
+/* line 12, ../templates/line_numbers.sass */
+umph {
+ foo: bar; }
+ /* line 18, ../templates/importee.sass */
+ umph baz {
+ blat: bang; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/mixins.css
b/backends/css/gems/sass-3.2.12/test/sass/results/mixins.css
new file mode 100644
index 0000000..969d2a3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/mixins.css
@@ -0,0 +1,95 @@
+#main {
+ width: 15em;
+ color: blue;
+}
+#main p {
+ border-top-width: 2px;
+ border-top-color: #ffcc00;
+ border-left-width: 1px;
+ border-left-color: black;
+ -moz-border-radius: 10px;
+ border-style: dotted;
+ border-width: 2px;
+}
+#main .cool {
+ width: 100px;
+}
+
+#left {
+ border-top-width: 2px;
+ border-top-color: #ffcc00;
+ border-left-width: 1px;
+ border-left-color: black;
+ -moz-border-radius: 10px;
+ font-size: 2em;
+ font-weight: bold;
+ float: left;
+}
+
+#right {
+ border-top-width: 2px;
+ border-top-color: #ffcc00;
+ border-left-width: 1px;
+ border-left-color: black;
+ -moz-border-radius: 10px;
+ color: red;
+ font-size: 20px;
+ float: right;
+}
+
+.bordered {
+ border-top-width: 2px;
+ border-top-color: #ffcc00;
+ border-left-width: 1px;
+ border-left-color: black;
+ -moz-border-radius: 10px;
+}
+
+.complex {
+ color: red;
+ font-size: 20px;
+ text-decoration: none;
+}
+.complex:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+* html .complex {
+ height: 1px;
+ color: red;
+ font-size: 20px;
+}
+
+.more-complex {
+ color: red;
+ 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: red;
+ font-size: 20px;
+}
+.more-complex a:hover {
+ text-decoration: underline;
+ color: red;
+ font-size: 20px;
+ border-top-width: 2px;
+ border-top-color: #ffcc00;
+ border-left-width: 1px;
+ border-left-color: black;
+ -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.2.12/test/sass/results/multiline.css
new file mode 100644
index 0000000..8fa9535
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/multiline.css
@@ -0,0 +1,24 @@
+#main,
+#header {
+ height: 50px; }
+ #main div,
+ #header div {
+ width: 100px; }
+ #main div a span,
+ #main div em span,
+ #header div a span,
+ #header div em span {
+ color: pink; }
+
+#one div.nested,
+#one span.nested,
+#one p.nested,
+#two div.nested,
+#two span.nested,
+#two p.nested,
+#three div.nested,
+#three span.nested,
+#three p.nested {
+ font-weight: bold;
+ border-color: red;
+ display: block; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/nested.css
b/backends/css/gems/sass-3.2.12/test/sass/results/nested.css
new file mode 100644
index 0000000..6200883
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/nested.css
@@ -0,0 +1,22 @@
+#main {
+ width: 15em;
+ color: blue; }
+ #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.2.12/test/sass/results/options.css
new file mode 100644
index 0000000..628f4c3
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/options.css
@@ -0,0 +1 @@
+foo { style: compact; }
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/parent_ref.css
b/backends/css/gems/sass-3.2.12/test/sass/results/parent_ref.css
new file mode 100644
index 0000000..4506709
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/parent_ref.css
@@ -0,0 +1,13 @@
+a { color: black; }
+a:hover { color: red; }
+
+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.2.12/test/sass/results/script.css
b/backends/css/gems/sass-3.2.12/test/sass/results/script.css
new file mode 100644
index 0000000..570385c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/script.css
@@ -0,0 +1,16 @@
+#main { content: Hello\!; qstr: 'Quo"ted"!'; hstr: Hyph-en\!; width: 30em; background-color: black; color:
#ffffaa; short-color: #112233; 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 #112233"; 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.2.12/test/sass/results/scss_import.css
b/backends/css/gems/sass-3.2.12/test/sass/results/scss_import.css
new file mode 100644
index 0000000..87ae3c8
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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: #bbaaff; }
+
+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.2.12/test/sass/results/scss_importee.css
new file mode 100644
index 0000000..c9fcf41
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/scss_importee.css
@@ -0,0 +1,2 @@
+scss {
+ imported: yes; }
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.2.12/test/sass/results/subdir/nested_subdir/nested_subdir.css
new file mode 100644
index 0000000..7aadcfe
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/subdir/nested_subdir/nested_subdir.css
@@ -0,0 +1 @@
+#pi { width: 314px; }
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/subdir/subdir.css
b/backends/css/gems/sass-3.2.12/test/sass/results/subdir/subdir.css
new file mode 100644
index 0000000..e404728
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/subdir/subdir.css
@@ -0,0 +1,3 @@
+#nested { relative: true; }
+
+#subdir { font-size: 20px; font-weight: bold; }
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/units.css
b/backends/css/gems/sass-3.2.12/test/sass/results/units.css
new file mode 100644
index 0000000..affb36c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/results/units.css
@@ -0,0 +1,11 @@
+b {
+ foo: 5px;
+ bar: 24px;
+ baz: 66.66667%;
+ many-units: 32em;
+ mm: 15mm;
+ pc: 2pc;
+ pt: -72pt;
+ inches: 2in;
+ more-inches: 3.5in;
+ mixed: 2.04167in; }
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/warn.css
b/backends/css/gems/sass-3.2.12/test/sass/results/warn.css
new file mode 100644
index 0000000..e69de29
diff --git a/backends/css/gems/sass-3.2.12/test/sass/results/warn_imported.css
b/backends/css/gems/sass-3.2.12/test/sass/results/warn_imported.css
new file mode 100644
index 0000000..e69de29
diff --git a/backends/css/gems/sass-3.2.12/test/sass/script_conversion_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/script_conversion_test.rb
new file mode 100755
index 0000000..31538eb
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/script_conversion_test.rb
@@ -0,0 +1,299 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/engine'
+
+class SassScriptConversionTest < Test::Unit::TestCase
+ 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_equal "#aabbcc", render("#abc")
+ assert_equal "blue", render("#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_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 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_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.2.12/test/sass/script_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/script_test.rb
new file mode 100755
index 0000000..0730b90
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/script_test.rb
@@ -0,0 +1,591 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sass/engine'
+
+module Sass::Script::Functions::UserFunctions
+ def assert_options(val)
+ val.options[:foo]
+ Sass::Script::String.new("Options defined!")
+ end
+
+ def arg_error
+ assert_options
+ end
+end
+
+module Sass::Script::Functions
+ include Sass::Script::Functions::UserFunctions
+end
+
+class SassScriptTest < Test::Unit::TestCase
+ include Sass::Script
+
+ def test_color_checks_input
+ assert_raise_message(ArgumentError, "Blue value -1 must be between 0 and 255") {Color.new([1, 2, -1])}
+ assert_raise_message(ArgumentError, "Red value 256 must be between 0 and 255") {Color.new([256, 2, 3])}
+ end
+
+ def test_color_checks_rgba_input
+ assert_raise_message(ArgumentError, "Alpha channel 1.1 must be between 0 and 1") {Color.new([1, 2, 3,
1.1])}
+ assert_raise_message(ArgumentError, "Alpha channel -0.1 must be between 0 and 1") {Color.new([1, 2, 3,
-0.1])}
+ end
+
+ def test_string_escapes
+ assert_equal "'", resolve("\"'\"")
+ assert_equal '"', resolve("\"\\\"\"")
+ assert_equal "\\\\", resolve("\"\\\\\"")
+ assert_equal "\\02fa", resolve("\"\\02fa\"")
+
+ assert_equal "'", resolve("'\\''")
+ assert_equal '"', resolve("'\"'")
+ assert_equal "\\\\", resolve("'\\\\'")
+ assert_equal "\\02fa", resolve("'\\02fa'")
+ end
+
+ def test_color_names
+ assert_equal "white", resolve("white")
+ assert_equal "white", resolve("#ffffff")
+ assert_equal "#fffffe", resolve("white - #000001")
+ end
+
+ def test_rgba_color_literals
+ assert_equal Sass::Script::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::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::Color.new([1, 2, 3]), eval("rgba(1, 2, 3, 1)")
+ assert_equal Sass::Script::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("#00f", :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)
+ 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::String.new("foo"), eval("foo")
+ assert_equal Sass::Script::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_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::String.new("foo-bar")))
+ assert_equal "url(foo-bar baz)", resolve("url($foo $bar)", {}, env('foo' =>
Sass::Script::String.new("foo-bar"), 'bar' => Sass::Script::String.new("baz")))
+ assert_equal "url(foo baz)", resolve("url(foo $bar)", {}, env('bar' => Sass::Script::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::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)')
+ assert_not_equal eval('1, 2, 3'), eval('1 2 3')
+ assert_not_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 "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::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_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")
+ assert_equal "true", resolve("1.1cm == 11mm")
+ 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_colors_with_wrong_number_of_digits
+ assert_raise_message(Sass::SyntaxError,
+ "Colors must have either three or six digits: '#0'") {eval("#0")}
+ assert_raise_message(Sass::SyntaxError,
+ "Colors must have either three or six digits: '#12'") {eval("#12")}
+ assert_raise_message(Sass::SyntaxError,
+ "Colors must have either three or six digits: '#abcd'") {eval("#abcd")}
+ assert_raise_message(Sass::SyntaxError,
+ "Colors must have either three or six digits: '#abcdE'") {eval("#abcdE")}
+ assert_raise_message(Sass::SyntaxError,
+ "Colors must have either three or six digits: '#abcdEFA'") {eval("#abcdEFA")}
+ 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_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_raise(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::Bool.new(false)))
+ assert_equal "true", resolve("$ie or $undef", {}, env('ie' => Sass::Script::Bool.new(true)))
+ end
+
+ # Regression Tests
+
+ 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_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
+
+ private
+
+ def resolve(str, opts = {}, environment = env)
+ munge_filename opts
+ val = eval(str, opts, environment)
+ assert_kind_of Sass::Script::Literal, val
+ val.is_a?(Sass::Script::String) ? val.value : val.to_s
+ end
+
+ def assert_unquoted(str, opts = {}, environment = env)
+ munge_filename opts
+ val = eval(str, opts, environment)
+ assert_kind_of Sass::Script::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::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 test_number_printing
+ assert_equal "1", eval("1")
+ assert_equal "1", eval("1.0")
+ assert_equal "1.121", eval("1.1214")
+ assert_equal "1.122", eval("1.1215")
+ assert_equal "Infinity", eval("1.0/0.0")
+ assert_equal "-Infinity", eval("-1.0/0.0")
+ assert_equal "NaN", eval("0.0/0.0")
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/scss/css_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/scss/css_test.rb
new file mode 100755
index 0000000..79c43d9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/scss/css_test.rb
@@ -0,0 +1,1093 @@
+#!/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 < Test::Unit::TestCase
+ 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 'foo.css';"
+ assert_parses '@import url("foo.css");'
+ assert_parses "@import url('foo.css');"
+ assert_parses '@import url(foo.css);'
+ end
+
+ def test_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_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
+
+ ## Selectors
+
+ # Taken from http://dev.w3.org/csswg/selectors4/#overview
+ def test_summarized_selectors
+ 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.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-match(n of selector)')
+ assert_selector_parses('E:nth-last-match(n of selector)')
+ assert_selector_parses('E:column(selector)')
+ assert_selector_parses('E:nth-column(n)')
+ assert_selector_parses('E:nth-last-column(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')
+ assert_selector_parses('E! > F')
+
+ assert_selector_parses('E /ns|foo/ F')
+ assert_selector_parses('E /*|foo/ F')
+ 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('.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-match(n of selector)')
+ assert_selector_parses(':nth-last-match(n of selector)')
+ assert_selector_parses(':column(selector)')
+ assert_selector_parses(':nth-column(n)')
+ assert_selector_parses(':nth-last-column(n)')
+ 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-match(n of <sel>)')
+ assert_selector_can_contain_selectors(':nth-last-match(n of <sel>)')
+ assert_selector_can_contain_selectors(':column(<sel>)')
+ assert_selector_can_contain_selectors(':-moz-any(<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_selector_parses('0%')
+ assert_selector_parses('60%')
+ assert_selector_parses('100%')
+ assert_selector_parses('12px')
+ assert_selector_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
+
+ ## 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
+
+ ## Regressions
+
+ 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
+ end
+
+ def render(scss, options = {})
+ tree = Sass::SCSS::CssParser.new(scss, options[:filename]).parse
+ tree.options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
+ tree.render
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/scss/rx_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/scss/rx_test.rb
new file mode 100755
index 0000000..e5cb59a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 < Test::Unit::TestCase
+ 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-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"
+ 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)
+ assert_not_nil(match = rx.match(str))
+ assert_equal str.size, match[0].size
+ end
+
+ def assert_no_match(rx, str)
+ match = rx.match(str)
+ assert_not_equal str.size, match && match[0].size
+ end
+
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/scss/scss_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/scss/scss_test.rb
new file mode 100755
index 0000000..4ef8203
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/scss/scss_test.rb
@@ -0,0 +1,2043 @@
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+require File.dirname(__FILE__) + '/test_helper'
+
+class ScssTest < Test::Unit::TestCase
+ 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_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_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;
+ }
+}
+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_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_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_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; }
+CSS
+foo {
+ .bar {a: b}
+ :baz {c: d}
+ bang:bop {e: f}}
+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
+ 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
+
+ ## 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:1px {
+ bip: bop }}
+SCSS
+ assert(false, "Expected syntax error")
+ rescue Sass::SyntaxError => e
+ assert_equal <<MESSAGE, e.message
+Invalid CSS: a space is required between a property and its definition
+when it has other properties nested beneath it.
+MESSAGE
+ assert_equal 2, e.sass_line
+ 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
+
+ ## 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_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_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_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: \#{$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
+
+ ## Interpolation
+
+ def test_basic_selector_interpolation
+ assert_equal <<CSS, render(<<SCSS)
+foo 3 baz {
+ a: b; }
+CSS
+foo \#{1 + 2} 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
+ 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_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
+
+ ## Errors
+
+ 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 ": expected "{", was "<fail>; }"', 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 {a: b}"
+
+"&.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 "& {a: b}"
+
+"&" 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 "& {a: b}"
+
+"&" 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
+
+ # Regression
+
+ 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").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.2.12/test/sass/scss/test_helper.rb
new file mode 100644
index 0000000..11e177d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/scss/test_helper.rb
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../../test_helper'
+require 'sass/engine'
+
+module ScssTestHelper
+ def assert_parses(scss)
+ assert_equal scss.rstrip, render(scss).rstrip
+ end
+
+ def assert_not_parses(expected, scss)
+ raise "Template must include <err> where an error is expected" unless scss.include?("<err>")
+
+ after, was = scss.split("<err>")
+ line = after.count("\n") + 1
+
+ after.gsub!(/\s*\n\s*$/, '')
+ after.gsub!(/.*\n/, '')
+ after = "..." + after[-15..-1] if after.size > 18
+
+ was.gsub!(/^\s*\n\s*/, '')
+ was.gsub!(/\n.*/, '')
+ was = was[0...15] + "..." if was.size > 18
+
+ to_render = scss.sub("<err>", "")
+ render(to_render)
+ assert(false, "Expected syntax error for:\n#{to_render}\n")
+ rescue Sass::SyntaxError => err
+ assert_equal("Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"",
+ err.message)
+ assert_equal line, err.sass_line
+ end
+
+ def render(scss, options = {})
+ options[:syntax] ||= :scss
+ munge_filename options
+ Sass::Engine.new(scss, options).render
+ 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.2.12/test/sass/templates/_cached_import_option_partial.scss
new file mode 100644
index 0000000..e4f9d3c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_cached_import_option_partial.scss
@@ -0,0 +1 @@
+partial {value: whatever()}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_double_import_loop2.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/_double_import_loop2.sass
new file mode 100644
index 0000000..efa4eb5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_double_import_loop2.sass
@@ -0,0 +1 @@
+ import "double_import_loop1"
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_filename_fn_import.scss
b/backends/css/gems/sass-3.2.12/test/sass/templates/_filename_fn_import.scss
new file mode 100644
index 0000000..4a1eec4
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_filename_fn_import.scss
@@ -0,0 +1,11 @@
+ mixin imported-mixin {
+ imported-mixin: filename();
+}
+
+ function imported-function() {
+ @return filename();
+}
+
+filename {
+ imported: filename();
+}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_ibm866.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_ibm866.sass
new file mode 100644
index 0000000..b679317
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_ibm866.sass
@@ -0,0 +1,4 @@
+ charset "IBM866"
+
+.bar
+ a: �
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_utf8.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_utf8.sass
new file mode 100644
index 0000000..cecdc5b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_charset_utf8.sass
@@ -0,0 +1,4 @@
+ charset "UTF-8"
+
+.bar
+ a: щ
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_content.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_content.sass
new file mode 100644
index 0000000..65c4e33
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_imported_content.sass
@@ -0,0 +1,3 @@
+ mixin foo
+ a
+ @content
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_partial.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/_partial.sass
new file mode 100644
index 0000000..bef627d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_partial.sass
@@ -0,0 +1,2 @@
+#foo
+ :background-color #baf
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/_same_name_different_partiality.scss
b/backends/css/gems/sass-3.2.12/test/sass/templates/_same_name_different_partiality.scss
new file mode 100644
index 0000000..a04e83a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/_same_name_different_partiality.scss
@@ -0,0 +1 @@
+.foo {partial: yes}
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/alt.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/alt.sass
new file mode 100644
index 0000000..cbcb648
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/alt.sass
@@ -0,0 +1,16 @@
+h1
+ :float left
+ :width 274px
+ height: 75px
+ margin: 0
+ background:
+ repeat: no-repeat
+ :image none
+ a:hover, a:visited
+ color: green
+ b:hover
+ color: red
+ :background-color green
+ const
+ nosp: 1 + 2
+ sp : 1 + 2
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/basic.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/basic.sass
new file mode 100644
index 0000000..5d4bf61
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/basic.sass
@@ -0,0 +1,23 @@
+
+
+body
+ :font Arial
+ :background blue
+
+#page
+ :width 700px
+ :height 100
+ #header
+ :height 300px
+ h1
+ :font-size 50px
+ :color blue
+
+#content.user.show
+ #container.top
+ #column.left
+ :width 100px
+ #column.right
+ :width 600px
+ #container.bottom
+ :background brown
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork1.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/bork1.sass
new file mode 100644
index 0000000..70ba9db
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/bork1.sass
@@ -0,0 +1,2 @@
+bork
+ :bork $bork
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork2.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/bork2.sass
new file mode 100644
index 0000000..462afb5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/bork2.sass
@@ -0,0 +1,2 @@
+bork
+ :bork: bork;
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork3.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/bork3.sass
new file mode 100644
index 0000000..9d0fb70
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/bork3.sass
@@ -0,0 +1,2 @@
+bork
+ bork:
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork4.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/bork4.sass
new file mode 100644
index 0000000..75610d9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/bork4.sass
@@ -0,0 +1,2 @@
+
+bork: blah
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/bork5.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/bork5.sass
new file mode 100644
index 0000000..df156d7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/bork5.sass
@@ -0,0 +1,3 @@
+bork
+ /* foo */
+ :bork $bork
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/cached_import_option.scss
b/backends/css/gems/sass-3.2.12/test/sass/templates/cached_import_option.scss
new file mode 100644
index 0000000..3ade543
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/cached_import_option.scss
@@ -0,0 +1,3 @@
+ import "cached_import_option_partial";
+
+main {value: whatever()}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/compact.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/compact.sass
new file mode 100644
index 0000000..e37f86e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/compact.sass
@@ -0,0 +1,17 @@
+#main
+ :width 15em
+ :color #0000ff
+ p
+ :border
+ :style dotted
+ /* Nested comment
+ More nested stuff
+ :width 2px
+ .cool
+ :width 100px
+
+#left
+ :font
+ :size 2em
+ :weight bold
+ :float left
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/complex.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/complex.sass
new file mode 100644
index 0000000..e3c3301
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/complex.sass
@@ -0,0 +1,305 @@
+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
+ :style solid
+ :color #ff8500
+
+#header
+ :height 75px
+ :padding 0
+ h1
+ :float left
+ :width 274px
+ :height 75px
+ :margin 0
+ :background
+ :image url(/images/global_logo.gif)
+ /* Crazy nested comment
+ :repeat no-repeat
+ :text-indent -9999px
+ .status
+ :float right
+ :padding
+ :top .5em
+ :left .5em
+ :right .5em
+ :bottom 0
+ p
+ :float left
+ :margin
+ :top 0
+ :right 0.5em
+ :bottom 0
+ :left 0
+ ul
+ :float left
+ :margin 0
+ :padding 0
+ li
+ :list-style-type none
+ :display inline
+ :margin 0 5px
+ a:link, a:visited
+ :color #ff8500
+ :text-decoration none
+ a:hover
+ :text-decoration underline
+ .search
+ :float right
+ :clear right
+ :margin 12px 0 0 0
+ form
+ :margin 0
+ 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
+ .contests
+ ul
+ :margin 0 5px 0 0
+ :padding 0
+ li
+ :list-style-type none
+ :margin 0 5px
+ :padding 5px 5px 0 5px
+ :display inline
+ :font-size 1.1em
+ // This comment is properly indented
+ :color #fff
+ :background #00a4e4
+ a:link, a:visited
+ :color #fff
+ :text-decoration none
+ :font-weight bold
+ a:hover
+ :text-decoration underline
+
+//General content information
+#content
+ :clear both
+ .container
+ :clear both
+ .column
+ :float left
+ .right
+ :float right
+ a:link, a:visited
+ :color #93d700
+ :text-decoration none
+ a:hover
+ :text-decoration underline
+
+// A hard tab:
+
+
+#content
+ p, div
+ :width 40em
+ li, dt, dd
+ :color #ddffdd
+ :background-color #4792bb
+ .container.video
+ .column.left
+ :width 200px
+ .box
+ :margin-top 10px
+ p
+ :margin 0 1em auto 1em
+ .box.participants
+ img
+ :float left
+ :margin 0 1em auto 1em
+ :border 1px solid #6e000d
+ :style solid
+ 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
+ // And also...
+ Multiline comments that don't output!
+ Snazzy, no?
+ :border
+ :top
+ :width 5px
+ :style solid
+ :color #a20013
+ :right
+ :width 1px
+ :style dotted
+ .column.middle
+ :width 500px
+ .column.right
+ :width 200px
+ .box
+ :margin-top 0
+ p
+ :margin 0 1em auto 1em
+ .column
+ p
+ :margin-top 0
+
+#content.contests
+ .container.information
+ .column.right
+ .box
+ :margin 1em 0
+ .box.videos
+ .thumbnail img
+ :width 200px
+ :height 150px
+ :margin-bottom 5px
+ a:link, a:visited
+ :color #93d700
+ :text-decoration none
+ a:hover
+ :text-decoration underline
+ .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
+ 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
+ table
+ :width 100
+ td
+ :padding 1em
+ :width 25
+ :vertical-align top
+ p
+ :margin 0 0 5px 0
+ a:link, a:visited
+ :color #93d700
+ :text-decoration none
+ a:hover
+ :text-decoration underline
+ .thumbnail
+ :float left
+ img
+ :width 80px
+ :height 60px
+ :margin 0 10px 0 0
+ :border 1px solid #6e000d
+
+#content
+ .container.comments
+ .column
+ :margin-top 15px
+ .column.left
+ :width 600px
+ .box
+ ol
+ :margin 0
+ :padding 0
+ li
+ :list-style-type none
+ :padding 10px
+ :margin 0 0 1em 0
+ :background #6e000d
+ :border-top 5px solid #a20013
+ div
+ :margin-bottom 1em
+ ul
+ :text-align right
+ li
+ :display inline
+ :border none
+ :padding 0
+ .column.right
+ :width 290px
+ :padding-left 10px
+ 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
+ .box
+ textarea
+ :width 290px
+ :height 100px
+ :border none
+
+#footer
+ :margin-top 10px
+ :padding 1.2em 1.5em
+ :background #ff8500
+ ul
+ :margin 0
+ :padding 0
+ :list-style-type none
+ li
+ :display inline
+ :margin 0 0.5em
+ :color #440008
+ ul.links
+ :float left
+ a:link, a:visited
+ :color #440008
+ :text-decoration none
+ a:hover
+ :text-decoration underline
+ 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/templates/compressed.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/compressed.sass
new file mode 100644
index 0000000..675fea4
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/compressed.sass
@@ -0,0 +1,15 @@
+#main
+ :width 15em
+ :color #0000ff
+ p
+ :border
+ :style dotted
+ :width 2px
+ .cool
+ :width 100px
+
+#left
+ :font
+ :size 2em
+ :weight bold
+ :float left
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/double_import_loop1.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/double_import_loop1.sass
new file mode 100644
index 0000000..5477384
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/double_import_loop1.sass
@@ -0,0 +1 @@
+ import "double_import_loop2"
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/expanded.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/expanded.sass
new file mode 100644
index 0000000..e37f86e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/expanded.sass
@@ -0,0 +1,17 @@
+#main
+ :width 15em
+ :color #0000ff
+ p
+ :border
+ :style dotted
+ /* Nested comment
+ More nested stuff
+ :width 2px
+ .cool
+ :width 100px
+
+#left
+ :font
+ :size 2em
+ :weight bold
+ :float left
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/filename_fn.scss
b/backends/css/gems/sass-3.2.12/test/sass/templates/filename_fn.scss
new file mode 100644
index 0000000..e43d508
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/filename_fn.scss
@@ -0,0 +1,18 @@
+ import "filename_fn_import";
+
+ mixin local-mixin {
+ local-mixin: filename();
+}
+
+ function local-function() {
+ @return filename();
+}
+
+filename {
+ local: filename();
+ @include local-mixin;
+ local-function: local-function();
+
+ @include imported-mixin;
+ imported-function: imported-function();
+}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/if.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/if.sass
new file mode 100644
index 0000000..787bff0
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/if.sass
@@ -0,0 +1,11 @@
+a
+ @if true
+ branch: if
+ @else
+ branch: else
+
+b
+ @if false
+ branch: if
+ @else
+ branch: else
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/import.sass
new file mode 100644
index 0000000..512e4a2
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/import.sass
@@ -0,0 +1,12 @@
+$preconst: hello
+
+=premixin
+ pre-mixin: here
+
+ import importee.sass, scss_importee, "basic.sass", basic.css, ../results/complex.css
+ import partial.sass
+
+nonimported
+ :myconst $preconst
+ :otherconst $postconst
+ +postmixin
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset.sass
new file mode 100644
index 0000000..b85447c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset.sass
@@ -0,0 +1,9 @@
+.foo
+ a: b
+
+ import "foo.css"
+
+// Even though the imported file is in IBM866,
+// since the root file is in UTF-8/ASCII
+// the output will end up being UTF-8.
+ import "imported_charset_ibm866"
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_1_8.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_1_8.sass
new file mode 100644
index 0000000..8a89b82
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_1_8.sass
@@ -0,0 +1,6 @@
+.foo
+ a: b
+
+ import "foo.css"
+
+ import "imported_charset_ibm866"
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_ibm866.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_ibm866.sass
new file mode 100644
index 0000000..164e702
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/import_charset_ibm866.sass
@@ -0,0 +1,11 @@
+ charset "IBM866"
+
+.foo
+ a: b
+
+ import "foo.css"
+
+// Even though the imported file is in UTF-8,
+// since the root file is in IBM866
+// the output will end up being IBM866.
+ import "imported_charset_utf8"
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/import_content.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/import_content.sass
new file mode 100644
index 0000000..2ef8ad9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/import_content.sass
@@ -0,0 +1,4 @@
+ import imported_content
+
+ include foo
+ b: c
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/importee.less
b/backends/css/gems/sass-3.2.12/test/sass/templates/importee.less
new file mode 100644
index 0000000..ac03a0e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/importee.less
@@ -0,0 +1,2 @@
+.foo {a: b}
+.bar () {c: d}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/importee.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/importee.sass
new file mode 100644
index 0000000..ddc8da8
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/importee.sass
@@ -0,0 +1,19 @@
+$postconst: goodbye
+
+=postmixin
+ post-mixin: here
+
+imported
+ :otherconst $preconst
+ :myconst $postconst
+ +premixin
+
+ import basic
+
+midrule
+ :inthe middle
+
+=crazymixin
+ foo: bar
+ baz
+ blat: bang
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/line_numbers.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/line_numbers.sass
new file mode 100644
index 0000000..2afc103
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/line_numbers.sass
@@ -0,0 +1,13 @@
+foo
+ bar: baz
+
+=premixin
+ squggle
+ blat: bang
+
+$preconst: 12
+
+ import importee
+
+umph
+ +crazymixin
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/mixin_bork.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/mixin_bork.sass
new file mode 100644
index 0000000..844acb9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/mixin_bork.sass
@@ -0,0 +1,5 @@
+=outer-mixin
+ +error-mixin
+
+foo
+ +outer-mixin
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/mixins.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/mixins.sass
new file mode 100644
index 0000000..633a626
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/mixins.sass
@@ -0,0 +1,76 @@
+$yellow: #fc0
+
+=bordered
+ :border
+ :top
+ :width 2px
+ :color $yellow
+ :left
+ :width 1px
+ :color #000
+ -moz-border-radius: 10px
+
+=header-font
+ :color #f00
+ :font
+ :size 20px
+
+=compound
+ +header-font
+ +bordered
+
+=complex
+ +header-font
+ text:
+ decoration: none
+ &:after
+ content: "."
+ display: block
+ height: 0
+ clear: both
+ visibility: hidden
+ * html &
+ height: 1px
+ +header-font
+=deep
+ a:hover
+ :text-decoration underline
+ +compound
+
+
+#main
+ :width 15em
+ :color #0000ff
+ p
+ +bordered
+ :border
+ :style dotted
+ :width 2px
+ .cool
+ :width 100px
+
+#left
+ +bordered
+ :font
+ :size 2em
+ :weight bold
+ :float left
+
+#right
+ +bordered
+ +header-font
+ :float right
+
+.bordered
+ +bordered
+
+.complex
+ +complex
+
+.more-complex
+ +complex
+ +deep
+ display: inline
+ -webkit-nonsense:
+ top-right: 1px
+ bottom-left: 1px
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/multiline.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/multiline.sass
new file mode 100644
index 0000000..83140e9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/multiline.sass
@@ -0,0 +1,20 @@
+#main,
+#header
+ height: 50px
+ div
+ width: 100px
+ a,
+ em
+ span
+ color: pink
+
+#one,
+#two,
+#three
+ div.nested,
+ span.nested,
+ p.nested
+ :font
+ :weight bold
+ :border-color red
+ :display block
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/nested.sass
new file mode 100644
index 0000000..a9ee4e0
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/nested.sass
@@ -0,0 +1,25 @@
+#main
+ :width 15em
+ :color #0000ff
+ p
+ :border
+ :style dotted
+ /* Nested comment
+ More nested stuff
+ :width 2px
+ .cool
+ :width 100px
+
+#left
+ :font
+ :size 2em
+ :weight bold
+ :float left
+
+#right
+ .header
+ :border-style solid
+ .body
+ :border-style dotted
+ .footer
+ :border-style dashed
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork1.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork1.sass
new file mode 100644
index 0000000..638496e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork1.sass
@@ -0,0 +1,2 @@
+
+ import bork1
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork2.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork2.sass
new file mode 100644
index 0000000..28b0bc8
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork2.sass
@@ -0,0 +1,2 @@
+
+ import bork2
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork3.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork3.sass
new file mode 100644
index 0000000..eeccd66
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork3.sass
@@ -0,0 +1,2 @@
+
+ import bork3
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork4.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork4.sass
new file mode 100644
index 0000000..173c947
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_bork4.sass
@@ -0,0 +1,2 @@
+
+ import bork4
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_import.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_import.sass
new file mode 100644
index 0000000..24b48e5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_import.sass
@@ -0,0 +1,2 @@
+.foo
+ @import basic
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/nested_mixin_bork.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_mixin_bork.sass
new file mode 100644
index 0000000..f79fdc7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/nested_mixin_bork.sass
@@ -0,0 +1,6 @@
+
+
+=error-mixin
+ width: 1px * 1em
+
+ import mixin_bork
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/options.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/options.sass
new file mode 100644
index 0000000..7e9d3c4
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/options.sass
@@ -0,0 +1,2 @@
+foo
+ style: option("style")
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/parent_ref.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/parent_ref.sass
new file mode 100644
index 0000000..70cafb8
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/parent_ref.sass
@@ -0,0 +1,25 @@
+a
+ :color #000
+ &:hover
+ :color #f00
+
+p, div
+ :width 100em
+ & foo
+ :width 10em
+ &:hover, bar
+ :height 20em
+
+#cool
+ :border
+ :style solid
+ :width 2em
+ .ie7 &, .ie6 &
+ :content string("Totally not cool.")
+ .firefox &
+ :content string("Quite cool.")
+
+.wow, .snazzy
+ :font-family fantasy
+ &:hover, &:visited
+ :font-weight bold
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.sass
new file mode 100644
index 0000000..c00a8b5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.sass
@@ -0,0 +1,2 @@
+.foo
+ 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.2.12/test/sass/templates/same_name_different_ext.scss
new file mode 100644
index 0000000..38090ce
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_ext.scss
@@ -0,0 +1 @@
+.foo {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.2.12/test/sass/templates/same_name_different_partiality.scss
new file mode 100644
index 0000000..8ba7d06
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/same_name_different_partiality.scss
@@ -0,0 +1 @@
+.foo {partial: no}
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/script.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/script.sass
new file mode 100644
index 0000000..3b3c85f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/script.sass
@@ -0,0 +1,101 @@
+$width: 10em + 20
+$color: #00ff98
+$main_text: #ffa
+$num: 10
+$dec: 10.2
+$dec_0: 99.0
+$neg: -10
+$esc: 10\+12
+$str: Hello\!
+$qstr: "Quo\"ted\"!"
+$hstr: Hyph-en\!
+$space: #{5 + 4} hi there
+$percent: 11%
+$complex: 1px/1em
+
+#main
+ :content $str
+ :qstr $qstr
+ :hstr $hstr
+ :width $width
+ :background-color #000
+ :color $main_text
+ :short-color #123
+ :named-color olive
+ :con "foo" bar ($space "boom")
+ :con2 "noquo" quo
+ #sidebar
+ :background-color $color
+ :num
+ :normal $num
+ :dec $dec
+ :dec0 $dec_0
+ :neg $neg
+ :esc $esc
+ :many 1 + 2 + 3
+ :order 1 + 2 * 3
+ :complex ((1 + 2) + 15)+#3a8b9f + (hi+(1 +1+ 2)* 4)
+
+#plus
+ :num
+ :num 5+2
+ :num-un 10em + 15em
+ :num-un2 10 + 13em
+ :num-neg 10 + -.13
+ :str 100 * 1px
+ :col 13 + #aaa
+ :perc $percent + 20%
+ :str
+ :str "hi" + "\ there"
+ :str2 "hi" + " there"
+ :col "14em solid " + #123
+ :num "times: " + 13
+ :col
+ :num #f02 + 123.5
+ :col #12A + #405162
+
+#minus
+ :num
+ :num 912 - 12
+ :col
+ :num #fffffa - 5.2
+ :col #abcdef - #fedcba
+ :unary
+ :num -1
+ :const -$neg
+ :paren -(5 + 6)
+ :two --12
+ :many --------12
+ :crazy -----(5 + ---$neg)
+
+#times
+ :num
+ :num 2 * 3.5
+ :col 2 * #3a4b5c
+ :col
+ :num #12468a * 0.5
+ :col #121212 * #020304
+
+#div
+ :num
+ :num (10 / 3.0)
+ :num2 (10 / 3)
+ :col
+ :num #12468a / 2
+ :col #abcdef / #0f0f0f
+ :comp $complex * 1em
+
+#mod
+ :num
+ :num 17 % 3
+ :col
+ :col #5f6e7d % #10200a
+ :num #aaabac % 3
+
+#const
+ :escaped
+ :quote \$foo \!bar
+ :default $str !important
+
+#regression
+ :a (3 + 2) - 1
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/scss_import.scss
b/backends/css/gems/sass-3.2.12/test/sass/templates/scss_import.scss
new file mode 100644
index 0000000..f64d80b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/scss_import.scss
@@ -0,0 +1,11 @@
+$preconst: hello;
+
+ mixin premixin {pre-mixin: here}
+
+ import "importee.sass", "scss_importee", "basic.sass", "basic.css", "../results/complex.css";
+ import "partial.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.2.12/test/sass/templates/scss_importee.scss
new file mode 100644
index 0000000..df49e68
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/scss_importee.scss
@@ -0,0 +1 @@
+scss {imported: yes}
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/single_import_loop.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/single_import_loop.sass
new file mode 100644
index 0000000..db50397
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/single_import_loop.sass
@@ -0,0 +1 @@
+ import "single_import_loop"
\ 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.2.12/test/sass/templates/subdir/nested_subdir/_nested_partial.sass
new file mode 100644
index 0000000..04008f6
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/_nested_partial.sass
@@ -0,0 +1,2 @@
+#nested
+ :relative true
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.2.12/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
new file mode 100644
index 0000000..aae9eeb
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/nested_subdir/nested_subdir.sass
@@ -0,0 +1,3 @@
+#pi
+ :width 314px
+
\ No newline at end of file
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/subdir.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/subdir.sass
new file mode 100644
index 0000000..8fff002
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/subdir/subdir.sass
@@ -0,0 +1,6 @@
+ import nested_subdir/nested_partial.sass
+
+#subdir
+ :font
+ :size 20px
+ :weight bold
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/units.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/units.sass
new file mode 100644
index 0000000..47b7744
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/units.sass
@@ -0,0 +1,11 @@
+b
+ :foo 0.5 * 10px
+ :bar 10zzz * 12px / 5zzz
+ :baz percentage(12.0px / 18px)
+ :many-units 10.0zzz / 3yyy * 12px / 5zzz * 3yyy / 3px * 4em
+ :mm 5mm + 1cm
+ :pc 1pc + 12pt
+ :pt 72pt - 2in
+ :inches 1in + 2.54cm
+ :more-inches 1in + ((72pt * 2in) + (36pt * 1in)) / 2.54cm
+ :mixed (1 + (1em * 6px / 3in)) * 4in / 2em
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/warn.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/warn.sass
new file mode 100644
index 0000000..514c44a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/warn.sass
@@ -0,0 +1,3 @@
+ warn "In the main file"
+ import warn_imported.sass
++emits-a-warning
diff --git a/backends/css/gems/sass-3.2.12/test/sass/templates/warn_imported.sass
b/backends/css/gems/sass-3.2.12/test/sass/templates/warn_imported.sass
new file mode 100644
index 0000000..493bd8a
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/templates/warn_imported.sass
@@ -0,0 +1,4 @@
+ warn "Imported"
+
+=emits-a-warning
+ @warn "In an imported mixin"
diff --git a/backends/css/gems/sass-3.2.12/test/sass/test_helper.rb
b/backends/css/gems/sass-3.2.12/test/sass/test_helper.rb
new file mode 100644
index 0000000..919806e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/test_helper.rb
@@ -0,0 +1,8 @@
+test_dir = File.dirname(__FILE__)
+$:.unshift test_dir unless $:.include?(test_dir)
+
+class Test::Unit::TestCase
+ def absolutize(file)
+ File.expand_path("#{File.dirname(__FILE__)}/#{file}")
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/test/sass/util/multibyte_string_scanner_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/util/multibyte_string_scanner_test.rb
new file mode 100755
index 0000000..bc0db14
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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 < Test::Unit::TestCase
+ 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.2.12/test/sass/util/subset_map_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/util/subset_map_test.rb
new file mode 100755
index 0000000..b595394
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/util/subset_map_test.rb
@@ -0,0 +1,91 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../test_helper'
+
+class SubsetMapTest < Test::Unit::TestCase
+ 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_raise(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.2.12/test/sass/util_test.rb
b/backends/css/gems/sass-3.2.12/test/sass/util_test.rb
new file mode 100755
index 0000000..dc176fc
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/sass/util_test.rb
@@ -0,0 +1,361 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../test_helper'
+require 'pathname'
+require 'tmpdir'
+
+class UtilTest < Test::Unit::TestCase
+ 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_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_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_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(["(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 {}'"))
+
+ 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
+ end
+
+ def test_abstract
+ assert_raise_message(NotImplementedError,
+ "UtilTest::FooBar must implement #foo") {FooBar.new.foo}
+ 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
+ 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.2.12/test/test_helper.rb
b/backends/css/gems/sass-3.2.12/test/test_helper.rb
new file mode 100644
index 0000000..386fa58
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/test/test_helper.rb
@@ -0,0 +1,80 @@
+lib_dir = File.dirname(__FILE__) + '/../lib'
+
+require 'test/unit'
+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)
+Encoding.default_external = 'UTF-8' if defined?(Encoding)
+
+module Sass::Script::Functions
+ def option(name)
+ Sass::Script::String.new(@options[name.value.to_sym].to_s)
+ end
+end
+
+class Test::Unit::TestCase
+ def munge_filename(opts = {})
+ return if opts.has_key?(:filename)
+ opts[:filename] = filename_for_test(opts[:syntax] || :sass)
+ end
+
+ def filename_for_test(syntax = :sass)
+ test_name = caller.
+ map {|c| Sass::Util.caller_info(c)[2]}.
+ compact.
+ map {|c| c.sub(/^(block|rescue) in /, '')}.
+ find {|c| c =~ /^test_/}
+ "#{test_name}_inline.#{syntax}"
+ end
+
+ def clean_up_sassc
+ path = File.dirname(__FILE__) + "/../.sass-cache"
+ 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
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/CHANGELOG.md
b/backends/css/gems/sass-3.2.12/vendor/listen/CHANGELOG.md
new file mode 100644
index 0000000..6efc036
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/CHANGELOG.md
@@ -0,0 +1,228 @@
+## 0.7.3 - February 24, 2013
+
+### Bug fix
+
+- [#88] Update wdm dependency. (fixed by [ mrbinky3000][])
+- [#78] Depend on latest rb-inotify. (fixed by [ mbj][])
+
+## 0.7.2 - January 11, 2013
+
+### Bug fix
+
+- [#76] Exception on filename which is not in UTF-8. (fixed by [ piotr-sokolowski][])
+
+## 0.7.1 - January 6, 2013
+
+### Bug fix
+
+- [#75] Default high precision off if the mtime call fails. (fixed by [ zanker][])
+
+## 0.7.0 - December 29, 2012
+
+### Bug fixes
+
+- [#73] Rescue Errno::EOPNOTSUPP on sha1_checksum generation. (fixed by [ thibaudgg][])
+
+### New feature
+
+- Add support for *BSD with rb-kqueue. ([ mat813][])
+
+## 0.6.0 - November 21, 2012
+
+### New feature
+
+- Add bang versions for filter and ignore listener methods. ([ tarsolya][])
+
+## 0.5.3 - October 3, 2012
+
+### Bug fixes
+
+- [#65] Fix ruby warning in adapter.rb. (fixed by [ vongruenigen][])
+- [#64] ENXIO raised when hashing UNIX domain socket file. (fixed by [ sunaku][])
+
+## 0.5.2 - Septemper 23, 2012
+
+### Bug fix
+
+- [#62] Fix double change callback with polling adapter. (fixed by [ thibaudgg][])
+
+## 0.5.1 - Septemper 18, 2012
+
+### Bug fix
+
+- [#61] Fix a synchronisation bug that caused constant fallback to polling. (fixed by [ Maher4Ever][])
+
+## 0.5.0 - Septemper 1, 2012
+
+### New features
+
+- Add a dependency manager to handle platform-specific gems. So there is no need anymore to install
+ extra gems which will never be used on the user system. ([ Maher4Ever][])
+- Add a manual reporting mode to the adapters. ([ Maher4Ever][])
+
+### Improvements
+
+- [#28] Enhance the speed of detecting changes on Windows by using the [WDM][] library. ([ Maher4Ever][])
+
+## 0.4.7 - June 27, 2012
+
+### Bug fixes
+
+- Increase latency to 0.25, to avoid useless polling fallback. (fixed by [ thibaudgg][])
+- Change watched inotify events, to avoid duplication callback. (fixed by [ thibaudgg][])
+- [#41](https://github.com/guard/listen/issues/41) Use lstat instead of stat when calculating mtime. (fixed
by [ ebroder][])
+
+## 0.4.6 - June 20, 2012
+
+### Bug fix
+
+- [#39](https://github.com/guard/listen/issues/39) Fix digest race condition. (fixed by [ dkubb][])
+
+## 0.4.5 - June 13, 2012
+
+### Bug fix
+
+- [#39](https://github.com/guard/listen/issues/39) Rescue Errno::ENOENT when path inserted doesn't exist.
(reported by [ textgoeshere][], fixed by [ thibaudgg][] and [ rymai][])
+
+## 0.4.4 - June 8, 2012
+
+### Bug fixes
+
+- ~~[#39](https://github.com/guard/listen/issues/39) Non-existing path insertion bug. (reported by [
textgoeshere][], fixed by [ thibaudgg][])~~
+- Fix relative path for directories containing special characters. (reported by [ napcs][], fixed by [
netzpirat][])
+
+## 0.4.3 - June 6, 2012
+
+### Bug fixes
+
+- [#24](https://github.com/guard/listen/issues/24) Fail gracefully when the inotify limit is not enough for
Listen to function. (reported by [ daemonza][], fixed by [ Maher4Ever][])
+- [#32](https://github.com/guard/listen/issues/32) Fix a crash when trying to calculate the checksum of
unreadable files. (reported by [ nex3][], fixed by [ Maher4Ever][])
+
+### Improvements
+
+- Add `#relative_paths` method to listeners. ([ Maher4Ever][])
+- Add `#started?` query-method to adapters. ([ Maher4Ever][])
+- Dynamically detect the mtime precision used on a system. ([ Maher4Ever][] with help from [ nex3][])
+
+## 0.4.2 - May 1, 2012
+
+### Bug fixes
+
+- [#21](https://github.com/guard/listen/issues/21) Issues when listening to changes in relative paths.
(reported by [ akerbos][], fixed by [ Maher4Ever][])
+- [#27](https://github.com/guard/listen/issues/27) Wrong reports for files modifications. (reported by [
cobychapple][], fixed by [ Maher4Ever][])
+- Fix segmentation fault when profiling on Windows. ([ Maher4Ever][])
+- Fix redundant watchers on Windows. ([ Maher4Ever][])
+
+### Improvements
+
+- [#17](https://github.com/guard/listen/issues/17) Use regexp-patterns with the `ignore` method instead of
supplying paths. (reported by [ fny][], added by [ Maher4Ever][])
+- Speed improvement when listening to changes in directories with ignored paths. ([ Maher4Ever][])
+- Added `.rbx` and `.svn` to ignored directories. ([ Maher4Ever][])
+
+## 0.4.1 - April 15, 2012
+
+### Bug fix
+
+- [#18](https://github.com/guard/listen/issues/18) Listener crashes when removing directories with nested
paths. (reported by [ daemonza][], fixed by [ Maher4Ever][])
+
+## 0.4.0 - April 9, 2012
+
+### New features
+
+- Add `wait_for_callback` method to all adapters. ([ Maher4Ever][])
+- Add `Listen::MultiListener` class to listen to multiple directories at once. ([ Maher4Ever][])
+- Allow passing multiple directories to the `Listen.to` method. ([ Maher4Ever][])
+- Add `blocking` option to `Listen#start` which can be used to disable blocking the current thread upon
starting. ([ Maher4Ever][])
+- Use absolute-paths in callbacks by default instead of relative-paths. ([ Maher4Ever][])
+- Add `relative_paths` option to `Listen::Listener` to retain the old functionality. ([ Maher4Ever][])
+
+### Improvements
+
+- Encapsulate thread spawning in the linux-adapter. ([ Maher4Ever][])
+- Encapsulate thread spawning in the darwin-adapter. ([ Maher4Ever][] with [ scottdavis][] help)
+- Encapsulate thread spawning in the windows-adapter. ([ Maher4Ever][])
+- Fix linux-adapter bug where Listen would report file-modification events on the parent-directory. ([
Maher4Ever][])
+
+### Change
+
+- Remove `wait_until_listening` as adapters doesn't need to run inside threads anymore ([ Maher4Ever][])
+
+## 0.3.3 - March 6, 2012
+
+### Improvement
+
+- Improve pause/unpause. ([ thibaudgg][])
+
+## 0.3.2 - March 4, 2012
+
+### New feature
+
+- Add pause/unpause listener's methods. ([ thibaudgg][])
+
+## 0.3.1 - February 22, 2012
+
+### Bug fix
+
+- [#9](https://github.com/guard/listen/issues/9) Ignore doesn't seem to work. (reported by [ markiz][],
fixed by [ thibaudgg][])
+
+## 0.3.0 - February 21, 2012
+
+### New features
+
+- Add automatic fallback to polling if system adapter doesn't work (like a DropBox folder). ([ thibaudgg][])
+- Add latency and force_polling options. ([ Maher4Ever][])
+
+## 0.2.0 - February 13, 2012
+
+### New features
+
+- Add checksum comparaison support for detecting consecutive file modifications made during the same second.
([ thibaudgg][])
+- Add rb-fchange support. ([ thibaudgg][])
+- Add rb-inotify support. ([ thibaudgg][] with [ Maher4Ever][] help)
+- Add rb-fsevent support. ([ thibaudgg][])
+- Add non-recursive diff with multiple directories support. ([ thibaudgg][])
+- Ignore .DS_Store by default. ([ thibaudgg][])
+
+## 0.1.0 - January 28, 2012
+
+- First version with only a polling adapter and basic features set (ignore & filter). ([ thibaudgg][])
+
+<!--- The following link definition list is generated by PimpMyChangelog --->
+[#9]: https://github.com/guard/listen/issues/9
+[#17]: https://github.com/guard/listen/issues/17
+[#18]: https://github.com/guard/listen/issues/18
+[#21]: https://github.com/guard/listen/issues/21
+[#24]: https://github.com/guard/listen/issues/24
+[#27]: https://github.com/guard/listen/issues/27
+[#28]: https://github.com/guard/listen/issues/28
+[#32]: https://github.com/guard/listen/issues/32
+[#41]: https://github.com/guard/listen/issues/41
+[#61]: https://github.com/guard/listen/issues/61
+[#62]: https://github.com/guard/listen/issues/62
+[#64]: https://github.com/guard/listen/issues/64
+[#65]: https://github.com/guard/listen/issues/65
+[#73]: https://github.com/guard/listen/issues/73
+[#75]: https://github.com/guard/listen/issues/75
+[#76]: https://github.com/guard/listen/issues/76
+[ Maher4Ever]: https://github.com/Maher4Ever
+[ dkubb]: https://github.com/dkubb
+[ ebroder]: https://github.com/ebroder
+[ akerbos]: https://github.com/akerbos
+[ cobychapple]: https://github.com/cobychapple
+[ daemonza]: https://github.com/daemonza
+[ fny]: https://github.com/fny
+[ markiz]: https://github.com/markiz
+[ mat813]: https://github.com/mat813
+[ napcs]: https://github.com/napcs
+[ netzpirat]: https://github.com/netzpirat
+[ nex3]: https://github.com/nex3
+[ piotr-sokolowski]: https://github.com/piotr-sokolowski
+[ rymai]: https://github.com/rymai
+[ scottdavis]: https://github.com/scottdavis
+[ sunaku]: https://github.com/sunaku
+[ textgoeshere]: https://github.com/textgoeshere
+[ thibaudgg]: https://github.com/thibaudgg
+[ tarsolya]: https://github.com/tarsolya
+[ vongruenigen]: https://github.com/vongruenigen
+[ zanker]: https://github.com/zanker
+[WDM]: https://github.com/Maher4Ever/wdm
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/CONTRIBUTING.md
b/backends/css/gems/sass-3.2.12/vendor/listen/CONTRIBUTING.md
new file mode 100644
index 0000000..8db7f5b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/CONTRIBUTING.md
@@ -0,0 +1,38 @@
+Contribute to Listen
+===================
+
+File an issue
+-------------
+
+You can report bugs and feature requests to [GitHub Issues](https://github.com/guard/listen/issues).
+
+**Please don't ask question in the issue tracker**, instead ask them in our
+[Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net).
+
+Try to figure out where the issue belongs to: Is it an issue with Listen itself or with Guard?
+
+When you file a bug, please try to follow these simple rules if applicable:
+
+* Make sure you run Listen with `bundle exec` first.
+* Add your `Guardfile` (if used) and `Gemfile` to the issue.
+* Make sure that the issue is reproducible with your description.
+
+**It's most likely that your bug gets resolved faster if you provide as much information as possible!**
+
+Development
+-----------
+
+* 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 run with `rake spec` must pass.
+* 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.
+* 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).
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/Gemfile
b/backends/css/gems/sass-3.2.12/vendor/listen/Gemfile
new file mode 100644
index 0000000..e92d8ff
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/Gemfile
@@ -0,0 +1,30 @@
+require 'rbconfig'
+
+source :rubygems
+
+gemspec
+
+gem 'rake'
+
+gem 'rb-kqueue', '~> 0.2' if RbConfig::CONFIG['target_os'] =~ /freebsd/i
+gem 'rb-fsevent', '~> 0.9.1' if RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
+gem 'rb-inotify', '~> 0.9.0' if RbConfig::CONFIG['target_os'] =~ /linux/i
+gem 'wdm', '~> 0.0.3' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+
+group :development do
+ platform :ruby do
+ gem 'coolline'
+ end
+
+ gem 'guard'
+ gem 'guard-rspec'
+ gem 'yard'
+ gem 'redcarpet'
+ gem 'pry'
+
+ gem 'vagrant'
+end
+
+group :test do
+ gem 'rspec'
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/Guardfile
b/backends/css/gems/sass-3.2.12/vendor/listen/Guardfile
new file mode 100644
index 0000000..f5a4cd7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/Guardfile
@@ -0,0 +1,8 @@
+guard :rspec, :all_on_start => false, :all_after_pass => false do
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
+ watch('spec/support/adapter_helper.rb') { "spec/listen/adapters" }
+ watch('spec/support/listener_helper.rb') { "spec/listen/listener_spec.rb" }
+ watch('spec/support/fixtures_helper.rb') { "spec" }
+ watch('spec/spec_helper.rb') { "spec" }
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/LICENSE
b/backends/css/gems/sass-3.2.12/vendor/listen/LICENSE
new file mode 100644
index 0000000..9c5c40f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2013 Thibaud Guillaume-Gentil
+
+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.2.12/vendor/listen/README.md
b/backends/css/gems/sass-3.2.12/vendor/listen/README.md
new file mode 100644
index 0000000..6ce84a4
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/README.md
@@ -0,0 +1,315 @@
+# Listen [![Build
Status](https://secure.travis-ci.org/guard/listen.png?branch=master)](http://travis-ci.org/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.
+* 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](http://travis-ci.org/guard/listen).
+
+## Install
+
+``` bash
+gem install listen
+```
+
+## Usage
+
+There are **two ways** to use Listen:
+
+1. Call `Listen.to` with either a single directory or multiple directories, then define the `change`
callback in a block.
+2. Create a `listener` object and use it in an (ARel style) chainable way.
+
+Feel free to give your feeback via [Listen issues](https://github.com/guard/listen/issues)
+
+### 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.change(&callback)
+listener.start # blocks execution!
+```
+
+### 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 # blocks execution!
+```
+
+### Pause/Unpause
+
+Listener can also easily be paused/unpaused:
+
+``` ruby
+listener = Listen.to('dir/path/to/listen')
+listener.start(false) # non-blocking mode
+listener.pause # stop listening to changes
+listener.paused? # => true
+listener.unpause
+listener.stop
+```
+
+## Listening to changes on multiple directories
+
+The Listen gem provides the `MultiListener` class to watch multiple directories and
+handle their changes from a single listener:
+
+```ruby
+listener = Listen::MultiListener.new('app/css', 'app/js')
+listener.latency(0.5)
+
+# Configure the listener to your needs...
+
+listener.start # blocks execution!
+````
+
+For an easier access, the `Listen.to` method can also be used to create a multi-listener:
+
+``` ruby
+listener = Listen.to('app/css', 'app/js')
+ .ignore(%r{^vendor/}) # both js/vendor and css/vendor will be ignored
+ .change(&assets_callback)
+
+listener.start # blocks execution!
+```
+
+## 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` 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 be calling the `change` method on any
+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 # blocks execution
+```
+
+### 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 listeneing 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
+
+These options can be set through `Listen.to` params or via methods (see the "Object" API)
+
+```ruby
+:filter => /\.rb$/, /\.coffee$/ # Filter files to listen to via a regexps list.
+ # default: none
+
+: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
+
+:latency => 0.5 # Set the delay (**in seconds**) between checking for changes
+ # default: 0.25 sec (1.0 sec for polling)
+
+:relative_paths => true # Enable the use of relative paths in the callback.
+ # default: false
+
+: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: "WARNING: Listen fallen back to polling, learn
more at https://github.com/guard/listen#fallback."
+```
+
+### The patterns for filtering and ignoring 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.
+
+### Non-blocking listening to changes
+
+Starting a listener blocks the current thread by default. That means any code after the
+`start` call won't be run until the listener is stopped (which needs to be done from another thread).
+
+For advanced usage there is an option to disable this behavior and have the listener start working
+in the background without blocking. To enable non-blocking listening the `start` method of
+the listener (be it `Listener` or `MultiListener`) needs to be called with `false` as a parameter.
+
+Here is an example of using a listener in the non-blocking mode:
+
+```ruby
+listener = Listen.to('dir/path/to/listen')
+listener.start(false) # doesn't block execution
+
+# Code here will run immediately after starting the listener
+
+```
+
+**note**: Using the `Listen.to` helper-method with a callback-block will always
+block execution. See the "Block API" section for an example.
+
+## Listen adapters
+
+The Listen gem has a set of adapters to notify it when there are changes.
+There are 3 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.
+
+## 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).
+
+## 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 run with `rake spec:portability` must pass.
+* Update the [Yard](http://yardoc.org/) documentation.
+* Update the README.
+* Update the CHANGELOG for noteworthy changes.
+* 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).
+
+## Acknowledgment
+
+* [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
+[Yehuda Katz (wycats)]: https://github.com/wycats
+[vigilo]: https://github.com/wycats/vigilo
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/Rakefile
b/backends/css/gems/sass-3.2.12/vendor/listen/Rakefile
new file mode 100644
index 0000000..f2ef7f5
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/Rakefile
@@ -0,0 +1,47 @@
+require 'bundler/gem_tasks'
+require 'rspec/core/rake_task'
+
+RSpec::Core::RakeTask.new(:spec)
+task :default => :spec
+
+require 'rbconfig'
+namespace(:spec) do
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/i
+ desc "Run all specs on multiple ruby versions (requires pik)"
+ task(:portability) do
+ %w[187 192 161].each do |version|
+ system "cmd /c echo -----------#{version}------------ & " +
+ "pik use #{version} & " +
+ "bundle install & " +
+ "bundle exec rspec spec"
+ end
+ end
+ else
+ desc "Run all specs on multiple ruby versions (requires rvm)"
+ task(:portability) do
+ travis_config_file = File.expand_path("../.travis.yml", __FILE__)
+ begin
+ travis_options ||= YAML::load_file(travis_config_file)
+ rescue => ex
+ puts "Travis config file '#{travis_config_file}' could not be found: #{ex.message}"
+ return
+ end
+
+ travis_options['rvm'].each do |version|
+ system <<-BASH
+ bash -c 'source ~/.rvm/scripts/rvm;
+ rvm #{version};
+ ruby_version_string_size=`ruby -v | wc -m`
+ echo;
+ for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done
+ echo;
+ echo "`ruby -v`";
+ for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done
+ echo;
+ RBXOPT="-Xrbc.db" bundle install;
+ RBXOPT="-Xrbc.db" bundle exec rspec spec -f doc 2>&1;'
+ BASH
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/Vagrantfile
b/backends/css/gems/sass-3.2.12/vendor/listen/Vagrantfile
new file mode 100644
index 0000000..ad1aa97
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/Vagrantfile
@@ -0,0 +1,96 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant::Config.run do |config|
+ # All Vagrant configuration is done here. The most common configuration
+ # options are documented and commented below. For a complete reference,
+ # please see the online documentation at vagrantup.com.
+
+ # Every Vagrant virtual environment requires a box to build off of.
+ config.vm.box = "lucid32"
+
+ # The url from where the 'config.vm.box' box will be fetched if it
+ # doesn't already exist on the user's system.
+ # config.vm.box_url = "http://domain.com/path/to/above.box"
+
+ # Boot with a GUI so you can see the screen. (Default is headless)
+ # config.vm.boot_mode = :gui
+
+ # Assign this VM to a host-only network IP, allowing you to access it
+ # via the IP. Host-only networks can talk to the host machine as well as
+ # any other machines on the same network, but cannot be accessed (through this
+ # network interface) by any external networks.
+ # config.vm.network :hostonly, "33.33.33.10"
+
+ # Assign this VM to a bridged network, allowing you to connect directly to a
+ # network using the host's network device. This makes the VM appear as another
+ # physical device on your network.
+ # config.vm.network :bridged
+
+ # Forward a port from the guest to the host, which allows for outside
+ # computers to access the VM, whereas host only networking does not.
+ # config.vm.forward_port 80, 8080
+
+ # Share an additional folder to the guest VM. The first argument is
+ # an identifier, the second is the path on the guest to mount the
+ # folder, and the third is the path on the host to the actual folder.
+ # config.vm.share_folder "v-data", "/vagrant_data", "../data"
+
+ # Enable provisioning with Puppet stand alone. Puppet manifests
+ # are contained in a directory path relative to this Vagrantfile.
+ # You will need to create the manifests directory and a manifest in
+ # the file lucid32.pp in the manifests_path directory.
+ #
+ # An example Puppet manifest to provision the message of the day:
+ #
+ # # group { "puppet":
+ # # ensure => "present",
+ # # }
+ # #
+ # # File { owner => 0, group => 0, mode => 0644 }
+ # #
+ # # file { '/etc/motd':
+ # # content => "Welcome to your Vagrant-built virtual machine!
+ # # Managed by Puppet.\n"
+ # # }
+ #
+ # config.vm.provision :puppet do |puppet|
+ # puppet.manifests_path = "manifests"
+ # puppet.manifest_file = "lucid32.pp"
+ # end
+
+ # Enable provisioning with chef solo, specifying a cookbooks path (relative
+ # to this Vagrantfile), and adding some recipes and/or roles.
+ #
+ # config.vm.provision :chef_solo do |chef|
+ # chef.cookbooks_path = "cookbooks"
+ # chef.add_recipe "mysql"
+ # chef.add_role "web"
+ #
+ # # You may also specify custom JSON attributes:
+ # chef.json = { :mysql_password => "foo" }
+ # end
+
+ # Enable provisioning with chef server, specifying the chef server URL,
+ # and the path to the validation key (relative to this Vagrantfile).
+ #
+ # The Opscode Platform uses HTTPS. Substitute your organization for
+ # ORGNAME in the URL and validation key.
+ #
+ # If you have your own Chef Server, use the appropriate URL, which may be
+ # HTTP instead of HTTPS depending on your configuration. Also change the
+ # validation key to validation.pem.
+ #
+ # config.vm.provision :chef_client do |chef|
+ # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
+ # chef.validation_key_path = "ORGNAME-validator.pem"
+ # end
+ #
+ # If you're using the Opscode platform, your validator client is
+ # ORGNAME-validator, replacing ORGNAME with your organization name.
+ #
+ # IF you have your own Chef Server, the default validation client name is
+ # chef-validator, unless you changed the configuration.
+ #
+ # chef.validation_client_name = "ORGNAME-validator"
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen.rb
new file mode 100644
index 0000000..d29d527
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen.rb
@@ -0,0 +1,40 @@
+module Listen
+
+ autoload :Turnstile, 'listen/turnstile'
+ autoload :Listener, 'listen/listener'
+ autoload :MultiListener, 'listen/multi_listener'
+ autoload :DirectoryRecord, 'listen/directory_record'
+ autoload :DependencyManager, 'listen/dependency_manager'
+ autoload :Adapter, 'listen/adapter'
+
+ module Adapters
+ autoload :Darwin, 'listen/adapters/darwin'
+ autoload :Linux, 'listen/adapters/linux'
+ autoload :BSD, 'listen/adapters/bsd'
+ autoload :Windows, 'listen/adapters/windows'
+ autoload :Polling, 'listen/adapters/polling'
+ end
+
+ # Listens to filesystem modifications on a either single directory or multiple directories.
+ #
+ # @param (see Listen::Listener#new)
+ # @param (see Listen::MultiListener#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 = if args.length == 1 || ! args[1].is_a?(String)
+ Listener.new(*args, &block)
+ else
+ MultiListener.new(*args, &block)
+ end
+
+ block ? listener.start : listener
+ end
+
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapter.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapter.rb
new file mode 100644
index 0000000..0a7811f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapter.rb
@@ -0,0 +1,214 @@
+require 'rbconfig'
+require 'thread'
+require 'set'
+require 'fileutils'
+
+module Listen
+ class Adapter
+ attr_accessor :directories, :latency, :paused
+
+ # The default delay between checking for changes.
+ DEFAULT_LATENCY = 0.25
+
+ # The default warning message when there is a missing dependency.
+ MISSING_DEPENDENCY_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ For a better performance, it's recommended that you satisfy the missing dependency.
+ EOS
+
+ # The default warning message when falling back to polling adapter.
+ POLLING_FALLBACK_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ Listen will be polling 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_dirs, options] callback Callback called when a change happens
+ # @yieldparam [Array<String>] changed_dirs 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)
+ return Adapters::Polling.new(directories, options, &callback) if options.delete(:force_polling)
+
+ warning = ''
+
+ begin
+ if Adapters::Darwin.usable_and_works?(directories, options)
+ return Adapters::Darwin.new(directories, options, &callback)
+ elsif Adapters::Linux.usable_and_works?(directories, options)
+ return Adapters::Linux.new(directories, options, &callback)
+ elsif Adapters::BSD.usable_and_works?(directories, options)
+ return Adapters::BSD.new(directories, options, &callback)
+ elsif Adapters::Windows.usable_and_works?(directories, options)
+ return Adapters::Windows.new(directories, options, &callback)
+ end
+ rescue DependencyManager::Error => e
+ warning += e.message + "\n" + MISSING_DEPENDENCY_MESSAGE
+ end
+
+ unless options[:polling_fallback_message] == false
+ warning += options[:polling_fallback_message] || POLLING_FALLBACK_MESSAGE
+ Kernel.warn "[Listen warning]:\n" + warning.gsub(/^(.*)/, ' \1')
+ end
+
+ 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
+ # @option options [Boolean] report_changes whether or not to automatically report changes (run the
callback)
+ #
+ # @yield [changed_dirs, options] callback Callback called when a change happens
+ # @yieldparam [Array<String>] changed_dirs 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
+ @paused = false
+ @mutex = Mutex.new
+ @changed_dirs = Set.new
+ @turnstile = Turnstile.new
+ @latency ||= DEFAULT_LATENCY
+ @latency = options[:latency] if options[:latency]
+ @report_changes = options[:report_changes].nil? ? true : options[:report_changes]
+ end
+
+ # Starts the adapter.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ @stop = false
+ end
+
+ # Stops the adapter.
+ #
+ def stop
+ @stop = true
+ @turnstile.signal # ensure no thread is blocked
+ end
+
+ # Returns whether the adapter is statred or not
+ #
+ # @return [Boolean] whether the adapter is started or not
+ #
+ def started?
+ @stop.nil? ? false : ! stop
+ 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(goal = 0)
+ changes = 0
+
+ loop do
+ @mutex.synchronize { changes = @changed_dirs.size }
+
+ return if @paused || @stop
+ return if changes >= goal
+
+ sleep(@latency)
+ end
+ end
+
+ # Checks if the adapter is usable on the current OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ load_depenencies
+ dependencies_loaded?
+ 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 usable and work or not
+ #
+ def self.usable_and_works?(directories, options = {})
+ usable? && Array(directories).all? { |d| works?(d, options) }
+ 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 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(false)
+
+ 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) if File.exists?(test_file)
+ 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_dirs.empty?
+ changed_dirs = @changed_dirs.to_a
+ @changed_dirs.clear
+ end
+
+ @callback.call(changed_dirs, {})
+ @turnstile.signal
+ end
+
+ private
+
+ # Polls changed directories and reports them back
+ # when there are changes.
+ #
+ def poll_changed_dirs
+ until @stop
+ sleep(@latency)
+ report_changes
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/bsd.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/bsd.rb
new file mode 100644
index 0000000..8a8a6a9
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/bsd.rb
@@ -0,0 +1,112 @@
+module Listen
+ module Adapters
+
+ # Listener implementation for BSD's `kqueue`.
+ #
+ class BSD < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'rb-kqueue', '~> 0.2'
+
+ # 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 ]
+
+ # Initializes the Adapter. See {Listen::Adapter#initialize} for
+ # more info.
+ #
+ def initialize(directories, options = {}, &callback)
+ super
+ @kqueue = init_kqueue
+ end
+
+ # Starts the adapter.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ @mutex.synchronize do
+ return if @stop == false
+ super
+ end
+
+ @kqueue_thread = Thread.new do
+ until @stop
+ @kqueue.poll
+ sleep(@latency)
+ end
+ end
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+
+ @kqueue_thread.join if blocking
+ end
+
+ # Stops the adapter.
+ #
+ def stop
+ @mutex.synchronize do
+ return if @stop == true
+ super
+ end
+
+ @kqueue.stop
+ Thread.kill(@kqueue_thread) if @kqueue_thread
+ @poll_thread.join if @poll_thread
+ end
+
+ # Checks if the adapter is usable on the current OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ return false unless RbConfig::CONFIG['target_os'] =~ /freebsd/i
+ super
+ 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
+ #
+ def init_kqueue
+ 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_dirs << (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 file, kqueue will forget them
+ # when the vfs does..
+ if File.directory?(path) && !(event.flags & [:write]).empty?
+ 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
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/darwin.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/darwin.rb
new file mode 100644
index 0000000..016cd3c
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/darwin.rb
@@ -0,0 +1,85 @@
+module Listen
+ module Adapters
+
+ # Adapter implementation for Mac OS X `FSEvents`.
+ #
+ class Darwin < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'rb-fsevent', '~> 0.9'
+
+ LAST_SEPARATOR_REGEX = /\/$/
+
+ # Initializes the Adapter. See {Listen::Adapter#initialize} for more info.
+ #
+ def initialize(directories, options = {}, &callback)
+ super
+ @worker = init_worker
+ end
+
+ # Starts the adapter.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ @mutex.synchronize do
+ return if @stop == false
+ super
+ end
+
+ @worker_thread = Thread.new { @worker.run }
+
+ # The FSEvent worker needs sometime to startup. 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
+
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+ @worker_thread.join if blocking
+ end
+
+ # Stops the adapter.
+ #
+ def stop
+ @mutex.synchronize do
+ return if @stop == true
+ super
+ end
+
+ @worker.stop
+ @worker_thread.join if @worker_thread
+ @poll_thread.join if @poll_thread
+ end
+
+ # Checks if the adapter is usable on the current OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ return false unless RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
+ super
+ end
+
+ private
+
+ # Initializes a FSEvent worker and adds a watcher for
+ # each directory passed to the adapter.
+ #
+ # @return [FSEvent] initialized worker
+ #
+ def init_worker
+ FSEvent.new.tap do |worker|
+ worker.watch(@directories.dup, :latency => @latency) do |changes|
+ next if @paused
+ @mutex.synchronize do
+ changes.each { |path| @changed_dirs << path.sub(LAST_SEPARATOR_REGEX, '') }
+ end
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/linux.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/linux.rb
new file mode 100644
index 0000000..f5beb97
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/linux.rb
@@ -0,0 +1,113 @@
+module Listen
+ module Adapters
+
+ # Listener implementation for Linux `inotify`.
+ #
+ class Linux < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'rb-inotify', '~> 0.9'
+
+ # 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 = %w[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
+
+ # Initializes the Adapter. See {Listen::Adapter#initialize} for more info.
+ #
+ def initialize(directories, options = {}, &callback)
+ super
+ @worker = init_worker
+ rescue Errno::ENOSPC
+ abort(INOTIFY_LIMIT_MESSAGE)
+ end
+
+ # Starts the adapter.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ @mutex.synchronize do
+ return if @stop == false
+ super
+ end
+
+ @worker_thread = Thread.new { @worker.run }
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+
+ @worker_thread.join if blocking
+ end
+
+ # Stops the adapter.
+ #
+ def stop
+ @mutex.synchronize do
+ return if @stop == true
+ super
+ end
+
+ @worker.stop
+ Thread.kill(@worker_thread) if @worker_thread
+ @poll_thread.join if @poll_thread
+ end
+
+ # Checks if the adapter is usable on the current OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ return false unless RbConfig::CONFIG['target_os'] =~ /linux/i
+ super
+ end
+
+ private
+
+ # Initializes a INotify worker and adds a watcher for
+ # each directory passed to the adapter.
+ #
+ # @return [INotify::Notifier] initialized worker
+ #
+ def init_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] != []
+ )
+ # Skip all of these!
+ next
+ end
+
+ @mutex.synchronize do
+ @changed_dirs << File.dirname(event.absolute_name)
+ end
+ end
+
+ INotify::Notifier.new.tap do |worker|
+ @directories.each do |directory|
+ worker.watch(directory, *EVENTS.map(&:to_sym), &callback)
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/polling.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/polling.rb
new file mode 100644
index 0000000..4f494ab
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/polling.rb
@@ -0,0 +1,67 @@
+module Listen
+ module Adapters
+
+ # The default delay between checking for changes.
+ 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 that the other implementations.
+ #
+ class Polling < Adapter
+ extend DependencyManager
+
+ # Initialize the Adapter. See {Listen::Adapter#initialize} for more info.
+ #
+ def initialize(directories, options = {}, &callback)
+ @latency ||= DEFAULT_POLLING_LATENCY
+ super
+ end
+
+ # Start the adapter.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ @mutex.synchronize do
+ return if @stop == false
+ super
+ end
+
+ @poll_thread = Thread.new { poll }
+ @poll_thread.join if blocking
+ end
+
+ # Stop the adapter.
+ #
+ def stop
+ @mutex.synchronize do
+ return if @stop == true
+ super
+ end
+
+ @poll_thread.join
+ end
+
+ private
+
+ # Poll listener directory for file system changes.
+ #
+ def poll
+ until @stop
+ 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.2.12/vendor/listen/lib/listen/adapters/windows.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/windows.rb
new file mode 100644
index 0000000..228bfd7
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/adapters/windows.rb
@@ -0,0 +1,87 @@
+require 'set'
+
+module Listen
+ module Adapters
+
+ # Adapter implementation for Windows `wdm`.
+ #
+ class Windows < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'wdm', '~> 0.1'
+
+ # Initializes the Adapter. See {Listen::Adapter#initialize} for more info.
+ #
+ def initialize(directories, options = {}, &callback)
+ super
+ @worker = init_worker
+ end
+
+ # Starts the adapter.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ @mutex.synchronize do
+ return if @stop == false
+ super
+ end
+
+ @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
+
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+
+ @worker_thread.join if blocking
+ end
+
+ # Stops the adapter.
+ #
+ def stop
+ @mutex.synchronize do
+ return if @stop == true
+ super
+ end
+
+ @worker.stop
+ @worker_thread.join if @worker_thread
+ @poll_thread.join if @poll_thread
+ end
+
+ # Checks if the adapter is usable on the current OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ return false unless RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+ super
+ end
+
+ private
+
+ # Initializes a WDM monitor and adds a watcher for
+ # each directory passed to the adapter.
+ #
+ # @return [WDM::Monitor] initialized worker
+ #
+ def init_worker
+ callback = Proc.new do |change|
+ next if @paused
+ @mutex.synchronize do
+ @changed_dirs << File.dirname(change.path)
+ end
+ end
+
+ WDM::Monitor.new.tap do |worker|
+ @directories.each { |d| worker.watch_recursively(d, &callback) }
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/dependency_manager.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/dependency_manager.rb
new file mode 100644
index 0000000..d63a09d
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/dependency_manager.rb
@@ -0,0 +1,126 @@
+require 'set'
+
+module Listen
+
+ # The dependency-manager offers a simple DSL which allows
+ # classes to declare their gem dependencies and load them when
+ # needed.
+ # It raises a user-friendly exception when the dependencies
+ # can't be loaded which has the install command in the message.
+ #
+ module DependencyManager
+
+ GEM_LOAD_MESSAGE = <<-EOS.gsub(/^ {6}/, '')
+ Missing dependency '%s' (version '%s')!
+ EOS
+
+ GEM_INSTALL_COMMAND = <<-EOS.gsub(/^ {6}/, '')
+ Please run the following to satisfy the dependency:
+ gem install --version '%s' %s
+ EOS
+
+ BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
+ Please add the following to your Gemfile to satisfy the dependency:
+ gem '%s', '%s'
+ EOS
+
+ Dependency = Struct.new(:name, :version)
+
+ # The error raised when a dependency can't be loaded.
+ class Error < StandardError; end
+
+ # A list of all loaded dependencies in the dependency manager.
+ @_loaded_dependencies = Set.new
+
+ # class methods
+ class << self
+
+ # Initializes the extended class.
+ #
+ # @param [Class] the class for which some dependencies must be managed
+ #
+ def extended(base)
+ base.class_eval do
+ @_dependencies = Set.new
+ end
+ end
+
+ # Adds a loaded dependency to a list so that it doesn't have
+ # to be loaded again by another classes.
+ #
+ # @param [Dependency] dependency
+ #
+ def add_loaded(dependency)
+ @_loaded_dependencies << dependency
+ end
+
+ # Returns whether the dependency is alread loaded or not.
+ #
+ # @param [Dependency] dependency
+ # @return [Boolean]
+ #
+ def already_loaded?(dependency)
+ @_loaded_dependencies.include?(dependency)
+ end
+
+ # Clears the list of loaded dependencies.
+ #
+ def clear_loaded
+ @_loaded_dependencies.clear
+ end
+ end
+
+ # Registers a new dependency.
+ #
+ # @param [String] name the name of the gem
+ # @param [String] version the version of the gem
+ #
+ def dependency(name, version)
+ @_dependencies << Dependency.new(name, version)
+ end
+
+ # Loads the registered dependencies.
+ #
+ # @raise DependencyManager::Error if the dependency can't be loaded.
+ #
+ def load_depenencies
+ @_dependencies.each do |dependency|
+ begin
+ next if DependencyManager.already_loaded?(dependency)
+ gem(dependency.name, dependency.version)
+ require(dependency.name)
+ DependencyManager.add_loaded(dependency)
+ @_dependencies.delete(dependency)
+ rescue Gem::LoadError
+ args = [dependency.name, dependency.version]
+ command = if running_under_bundler?
+ BUNDLER_DECLARE_GEM % args
+ else
+ GEM_INSTALL_COMMAND % args.reverse
+ end
+ message = GEM_LOAD_MESSAGE % args
+
+ raise Error.new(message + command)
+ end
+ end
+ end
+
+ # Returns whether all the dependencies has been loaded or not.
+ #
+ # @return [Boolean]
+ #
+ def dependencies_loaded?
+ @_dependencies.empty?
+ end
+
+ private
+
+ # Returns whether we are running under bundler or not
+ #
+ # @return [Boolean]
+ #
+ def running_under_bundler?
+ !!(File.exists?('Gemfile') && ENV['BUNDLE_GEMFILE'])
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/directory_record.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/directory_record.rb
new file mode 100644
index 0000000..acfaa55
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/directory_record.rb
@@ -0,0 +1,371 @@
+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
+
+ DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
+
+ 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 = directory
+ @ignoring_patterns = Set.new
+ @filtering_patterns = Set.new
+ @sha1_checksums = Hash.new
+
+ @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
+ end
+
+ # Returns the ignoring patterns in the record
+ #
+ # @return [Array<Regexp>] the ignoring patterns
+ #
+ def ignoring_patterns
+ @ignoring_patterns.to_a
+ end
+
+ # Returns the filtering patterns used 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] regexp a pattern for ignoring paths
+ #
+ def ignore(*regexps)
+ @ignoring_patterns.merge(regexps)
+ end
+
+ # Replaces ignoring patterns in the record.
+ #
+ # @example Ignore only these paths
+ # ignore! %r{^ignored/path/}, /man/
+ #
+ # @param [Regexp] regexp a pattern for ignoring paths
+ #
+ def ignore!(*regexps)
+ @ignoring_patterns.replace(regexps)
+ end
+
+ # Adds filtering patterns to the listener.
+ #
+ # @example Filter some files
+ # ignore /\.txt$/, /.*\.zip/
+ #
+ # @param [Regexp] regexp a pattern for filtering paths
+ #
+ def filter(*regexps)
+ @filtering_patterns.merge(regexps)
+ end
+
+ # Replaces filtering patterns in the listener.
+ #
+ # @example Filter only these files
+ # ignore /\.txt$/, /.*\.zip/
+ #
+ # @param [Regexp] regexp a pattern for filtering paths
+ #
+ def filter!(*regexps)
+ @filtering_patterns.replace(regexps)
+ 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 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)
+ return nil unless path[ directory]
+ path = path.force_encoding("BINARY") if path.respond_to?(:force_encoding)
+ path.sub(%r{^#{Regexp.quote(@directory)}#{File::SEPARATOR}?}, '')
+ 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'
+ if File.directory?(path)
+ detect_modifications_and_removals(path, options) if options[:recursive]
+ else
+ detect_modifications_and_removals(path, { :recursive => true }.merge(options))
+ @paths[directory].delete(basename)
+ @paths.delete("#{directory}/#{basename}")
+ end
+ when 'File'
+ if File.exist?(path)
+ 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
+ # Update the sha1 checksum of the file
+ insert_sha1_checksum(path)
+
+ # Update the meta data of the file
+ meta_data.mtime = new_mtime
+ @paths[directory][basename] = meta_data
+
+ @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
+ end
+ else
+ @paths[directory].delete(basename)
+ @sha1_checksums.delete(path)
+ @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
+ end
+ end
+ end
+ 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)
+ @sha1_checksum = sha1_checksum(path)
+ if @sha1_checksums[path] == @sha1_checksum || ! sha1_checksums key?(path)
+ insert_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 insert_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 Errno::EACCES, Errno::ENOENT, Errno::ENXIO, Errno::EOPNOTSUPP
+ nil
+ end
+
+ # Traverses the base directory looking for paths that should
+ # be stored; thus paths that are filters 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.2.12/vendor/listen/lib/listen/listener.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/listener.rb
new file mode 100644
index 0000000..80ec227
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/listener.rb
@@ -0,0 +1,225 @@
+require 'pathname'
+
+module Listen
+ class Listener
+ attr_reader :directory, :directory_record, :adapter
+
+ # The default value for using relative paths in the callback.
+ DEFAULT_TO_RELATIVE_PATHS = false
+
+ # Initializes the directory listener.
+ #
+ # @param [String] directory the directory 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
+ #
+ # @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(directory, options = {}, &block)
+ @block = block
+ @directory = Pathname.new(directory).realpath.to_s
+ @directory_record = DirectoryRecord.new(@directory)
+ @use_relative_paths = DEFAULT_TO_RELATIVE_PATHS
+
+ @use_relative_paths = options.delete(:relative_paths) if options[:relative_paths]
+ @directory_record.ignore(*options.delete(:ignore)) if options[:ignore]
+ @directory_record.filter(*options.delete(:filter)) if options[: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.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ t = Thread.new { @directory_record.build }
+ @adapter = initialize_adapter
+ t.join
+ @adapter.start(blocking)
+ end
+
+ # Stops the listener.
+ #
+ def stop
+ @adapter.stop
+ end
+
+ # Pauses the listener.
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def pause
+ @adapter.paused = true
+ self
+ end
+
+ # Unpauses the listener.
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def unpause
+ @directory_record.build
+ @adapter.paused = false
+ self
+ end
+
+ # Returns whether the listener is paused or not.
+ #
+ # @return [Boolean] adapter paused status
+ #
+ def paused?
+ !! adapter && @adapter.paused == true
+ end
+
+ # Adds ignoring patterns to the listener.
+ #
+ # @param (see Listen::DirectoryRecord#ignore)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def ignore(*regexps)
+ @directory_record.ignore(*regexps)
+ self
+ end
+
+ # Replaces ignoring patterns in the listener.
+ #
+ # @param (see Listen::DirectoryRecord#ignore!)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def ignore!(*regexps)
+ @directory_record.ignore!(*regexps)
+ self
+ end
+
+ # Adds filtering patterns to the listener.
+ #
+ # @param (see Listen::DirectoryRecord#filter)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def filter(*regexps)
+ @directory_record.filter(*regexps)
+ self
+ end
+
+ # Replacing filtering patterns in the listener.
+ #
+ # @param (see Listen::DirectoryRecord#filter!)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def filter!(*regexps)
+ @directory_record.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 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 of 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)
+ #
+ def on_change(directories, options = {})
+ changes = @directory_record.fetch_changes(directories, options.merge(
+ :relative_paths => @use_relative_paths
+ ))
+ unless changes.values.all? { |paths| paths.empty? }
+ @block.call(changes[:modified],changes[:added],changes[:removed])
+ end
+ end
+
+ private
+
+ # Initializes an adapter passing it the callback and adapters' options.
+ #
+ def initialize_adapter
+ callback = lambda { |changed_dirs, options| self.on_change(changed_dirs, options) }
+ Adapter.select_and_initialize(@directory, @adapter_options, &callback)
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/multi_listener.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/multi_listener.rb
new file mode 100644
index 0000000..4ff08ca
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/multi_listener.rb
@@ -0,0 +1,143 @@
+module Listen
+ class MultiListener < Listener
+ attr_reader :directories, :directories_records, :adapter
+
+ # Initializes the multiple directories listener.
+ #
+ # @param [String] directories 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] 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
+ #
+ # @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
+
+ @block = block
+ @directories = directories.map { |d| Pathname.new(d).realpath.to_s }
+ @directories_records = @directories.map { |d| DirectoryRecord.new(d) }
+
+ ignore(*options.delete(:ignore)) if options[:ignore]
+ filter(*options.delete(:filter)) if options[: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.
+ #
+ # @param [Boolean] blocking whether or not to block the current thread after starting
+ #
+ def start(blocking = true)
+ t = Thread.new { @directories_records.each { |r| r.build } }
+ @adapter = initialize_adapter
+ t.join
+ @adapter.start(blocking)
+ end
+
+ # Unpauses the listener.
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def unpause
+ @directories_records.each { |r| r.build }
+ @adapter.paused = false
+ self
+ end
+
+ # Adds ignored paths to the listener.
+ #
+ # @param (see Listen::DirectoryRecord#ignore)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def ignore(*paths)
+ @directories_records.each { |r| r.ignore(*paths) }
+ self
+ end
+
+ # Replaces ignored paths in the listener.
+ #
+ # @param (see Listen::DirectoryRecord#ignore!)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def ignore!(*paths)
+ @directories_records.each { |r| r.ignore!(*paths) }
+ self
+ end
+
+ # Adds file filters to the listener.
+ #
+ # @param (see Listen::DirectoryRecord#filter)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def filter(*regexps)
+ @directories_records.each { |r| r.filter(*regexps) }
+ self
+ end
+
+ # Replaces file filters in the listener.
+ #
+ # @param (see Listen::DirectoryRecord#filter!)
+ #
+ # @return [Listen::Listener] the listener
+ #
+ def filter!(*regexps)
+ @directories_records.each { |r| r.filter!(*regexps) }
+ self
+ end
+
+ # Runs the callback passing it the changes if there are any.
+ #
+ # @param (see Listen::DirectoryRecord#fetch_changes)
+ #
+ def on_change(directories_to_search, options = {})
+ changes = fetch_records_changes(directories_to_search, options)
+ unless changes.values.all? { |paths| paths.empty? }
+ @block.call(changes[:modified],changes[:added],changes[:removed])
+ end
+ end
+
+ private
+
+ # Initializes an adapter passing it the callback and adapters' options.
+ #
+ def initialize_adapter
+ callback = lambda { |changed_dirs, options| self.on_change(changed_dirs, options) }
+ Adapter.select_and_initialize(@directories, @adapter_options, &callback)
+ 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 =>
DEFAULT_TO_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.2.12/vendor/listen/lib/listen/turnstile.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/turnstile.rb
new file mode 100644
index 0000000..5e395d0
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/turnstile.rb
@@ -0,0 +1,28 @@
+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
+
+ # Initialize the turnstile.
+ #
+ def initialize
+ # Until ruby offers semahpores, only queues can be used
+ # to implement a turnstile.
+ @q = Queue.new
+ end
+
+ # Blocks the current thread until a signal is received.
+ #
+ def wait
+ @q.pop if @q.num_waiting == 0
+ end
+
+ # Unblocks the waiting thread if there is one.
+ #
+ def signal
+ @q.push :dummy if @q.num_waiting == 1
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/version.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/version.rb
new file mode 100644
index 0000000..3b23921
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/lib/listen/version.rb
@@ -0,0 +1,3 @@
+module Listen
+ VERSION = '0.7.3'
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/listen.gemspec
b/backends/css/gems/sass-3.2.12/vendor/listen/listen.gemspec
new file mode 100644
index 0000000..e1d84ea
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/listen.gemspec
@@ -0,0 +1,22 @@
+# -*- 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.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_development_dependency 'bundler'
+
+ s.files = Dir.glob('{lib}/**/*') + %w[CHANGELOG.md LICENSE README.md]
+ s.require_path = 'lib'
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapter_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapter_spec.rb
new file mode 100644
index 0000000..8c7b19e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapter_spec.rb
@@ -0,0 +1,183 @@
+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::Adapters::Darwin.stub(:usable_and_works?) { false }
+ Listen::Adapters::Linux.stub(:usable_and_works?) { false }
+ Listen::Adapters::BSD.stub(:usable_and_works?) { false }
+ Listen::Adapters::Windows.stub(:usable_and_works?) { false }
+ 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 'when the dependencies of an adapter are not satisfied' do
+ before do
+ Listen::Adapters::Darwin.stub(:usable_and_works?).and_raise(Listen::DependencyManager::Error)
+ Listen::Adapters::Linux.stub(:usable_and_works?).and_raise(Listen::DependencyManager::Error)
+ Listen::Adapters::BSD.stub(:usable_and_works?).and_raise(Listen::DependencyManager::Error)
+ Listen::Adapters::Windows.stub(:usable_and_works?).and_raise(Listen::DependencyManager::Error)
+ end
+
+ it 'invites the user to satisfy the dependencies of the adapter in the warning' do
+ Kernel.should_receive(:warn).with(/#{Listen::Adapter::MISSING_DEPENDENCY_MESSAGE}/)
+ described_class.select_and_initialize('dir')
+ end
+ 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
+
+ context "on Mac OX >= 10.6" do
+ before { Listen::Adapters::Darwin.stub(:usable_and_works?) { true } }
+
+ it "uses Listen::Adapters::Darwin" do
+ Listen::Adapters::Darwin.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
+
+ context "on Linux" do
+ before { Listen::Adapters::Linux.stub(:usable_and_works?) { true } }
+
+ it "uses Listen::Adapters::Linux" do
+ Listen::Adapters::Linux.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
+
+ context "on BSD" do
+ before { Listen::Adapters::BSD.stub(:usable_and_works?) { true } }
+
+ it "uses Listen::Adapters::BSD" do
+ Listen::Adapters::BSD.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
+
+ context "on Windows" do
+ before { Listen::Adapters::Windows.stub(:usable_and_works?) { true } }
+
+ it "uses Listen::Adapters::Windows" do
+ Listen::Adapters::Windows.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
+
+ [Listen::Adapters::Darwin, Listen::Adapters::Linux,
+ Listen::Adapters::BSD, Listen::Adapters::Windows].each do
+ |adapter_class|
+ if adapter_class.usable?
+ describe '.usable?' do
+ it 'checks the dependencies' do
+ adapter_class.should_receive(:load_depenencies)
+ adapter_class.should_receive(:dependencies_loaded?)
+ adapter_class.usable?
+ end
+ end
+
+ 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.2.12/vendor/listen/spec/listen/adapters/bsd_spec.rb
new file mode 100644
index 0000000..ec02917
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/bsd_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Listen::Adapters::BSD do
+ if bsd?
+ if Listen::Adapters::BSD.usable?
+ it "is usable on BSD" do
+ described_class.should be_usable
+ end
+
+ it_should_behave_like 'a filesystem adapter'
+ it_should_behave_like 'an adapter that call properly listener#on_change'
+ else
+ it "isn't usable on BSD with #{RbConfig::CONFIG['RUBY_INSTALL_NAME']}" do
+ described_class.should_not be_usable
+ end
+ end
+ end
+
+ if linux?
+ it "isn't usable on Linux" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if mac?
+ it "isn't usable on Mac OS X" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if windows?
+ it "isn't usable on Windows" do
+ described_class.should_not be_usable
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/darwin_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/darwin_spec.rb
new file mode 100644
index 0000000..dabee67
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/darwin_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Listen::Adapters::Darwin do
+ if mac?
+ if Listen::Adapters::Darwin.usable?
+ it "is usable on Mac OS X >= 10.6" do
+ described_class.should be_usable
+ end
+
+ it_should_behave_like 'a filesystem adapter'
+ it_should_behave_like 'an adapter that call properly listener#on_change'
+ else
+ it "isn't usable on Mac OS X with #{RbConfig::CONFIG['RUBY_INSTALL_NAME']}" do
+ described_class.should_not be_usable
+ end
+ end
+
+ end
+
+ if windows?
+ it "isn't usable on Windows" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if linux?
+ it "isn't usable on Linux" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if bsd?
+ it "isn't usable on BSD" do
+ described_class.should_not be_usable
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/linux_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/linux_spec.rb
new file mode 100644
index 0000000..ff119aa
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/linux_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Listen::Adapters::Linux do
+ if linux?
+ if Listen::Adapters::Linux.usable?
+ it "is usable on Linux" do
+ described_class.should be_usable
+ end
+
+ it_should_behave_like 'a filesystem adapter'
+ it_should_behave_like 'an adapter that call properly listener#on_change'
+
+ describe '#initialize' do
+ context 'when the inotify limit for watched files is not enough' do
+ before { INotify::Notifier.any_instance.should_receive(:watch).and_raise(Errno::ENOSPC) }
+
+ it 'fails gracefully' do
+ described_class.any_instance.should_receive(:abort).with(described_class::INOTIFY_LIMIT_MESSAGE)
+ described_class.new(File.dirname(__FILE__))
+ end
+ end
+ end
+ else
+ it "isn't usable on Linux with #{RbConfig::CONFIG['RUBY_INSTALL_NAME']}" do
+ described_class.should_not be_usable
+ end
+ end
+ end
+
+ if bsd?
+ it "isn't usable on BSD" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if mac?
+ it "isn't usable on Mac OS X" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if windows?
+ it "isn't usable on Windows" do
+ described_class.should_not be_usable
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/polling_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/polling_spec.rb
new file mode 100644
index 0000000..d53b580
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/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) { mock(Listen::Listener) }
+ let(:callback) { lambda { |changed_dirs, options| @called = true; listener.on_change(changed_dirs,
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(false)
+ 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(false)
+ 10.times { subject.wait_for_callback }
+ end
+
+ it "doesn't call listener.on_change if paused" do
+ subject.paused = true
+ subject.start(false)
+ 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(false)
+ 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(false)
+ 10.times { subject.wait_for_callback }
+ end
+
+ it "doesn't call listener.on_change if paused" do
+ subject.paused = true
+ subject.start(false)
+ 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.2.12/vendor/listen/spec/listen/adapters/windows_spec.rb
new file mode 100644
index 0000000..b9cd969
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/adapters/windows_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Listen::Adapters::Windows do
+ if windows? && Listen::Adapters::Windows.usable?
+ it "is usable on Windows" do
+ described_class.should be_usable
+ end
+
+ it_should_behave_like 'a filesystem adapter'
+ it_should_behave_like 'an adapter that call properly listener#on_change'
+ end
+
+ if mac?
+ it "isn't usable on Mac OS X" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if bsd?
+ it "isn't usable on BSD" do
+ described_class.should_not be_usable
+ end
+ end
+
+ if linux?
+ it "isn't usable on Linux" do
+ described_class.should_not be_usable
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/dependency_manager_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/dependency_manager_spec.rb
new file mode 100644
index 0000000..f1d3203
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/dependency_manager_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe Listen::DependencyManager do
+ let(:dependency) { Listen::DependencyManager::Dependency.new('listen', '~> 0.0.1') }
+
+ subject { Class.new { extend Listen::DependencyManager } }
+
+ before { described_class.clear_loaded }
+
+ describe '.add_loaded' do
+ it 'adds a dependency to the list of loaded dependencies' do
+ described_class.add_loaded dependency
+ described_class.already_loaded?(dependency).should be_true
+ end
+ end
+
+ describe '.already_loaded?' do
+ it 'returns false when a dependency is not in the list of loaded dependencies' do
+ described_class.already_loaded?(dependency).should be_false
+ end
+
+ it 'returns true when a dependency is in the list of loaded dependencies' do
+ described_class.add_loaded dependency
+ described_class.already_loaded?(dependency).should be_true
+ end
+ end
+
+ describe '.clear_loaded' do
+ it 'clears the whole list of loaded dependencies' do
+ described_class.add_loaded dependency
+ described_class.already_loaded?(dependency).should be_true
+ described_class.clear_loaded
+ described_class.already_loaded?(dependency).should be_false
+ end
+ end
+
+ describe '#dependency' do
+ it 'registers a new dependency for the managed class' do
+ subject.dependency 'listen', '~> 0.0.1'
+ subject.dependencies_loaded?.should be_false
+ end
+ end
+
+ describe '#load_depenencies' do
+ before { subject.dependency 'listen', '~> 0.0.1' }
+
+ context 'when dependencies can be loaded' do
+ before { subject.stub(:gem, :require) }
+
+ it 'loads all the registerd dependencies' do
+ subject.load_depenencies
+ subject.dependencies_loaded?.should be_true
+ end
+ end
+
+ context 'when dependencies can not be loaded' do
+ it 'raises an error' do
+ expect {
+ subject.load_depenencies
+ }.to raise_error(described_class::Error)
+ end
+
+ context 'when running under bundler' do
+ before { subject.should_receive(:running_under_bundler?).and_return(true) }
+
+ it 'includes the Gemfile declaration to satisfy the dependency' do
+ begin
+ subject.load_depenencies
+ rescue described_class::Error => e
+ e.message.should include("gem 'listen', '~> 0.0.1'")
+ end
+ end
+ end
+
+ context 'when not running under bundler' do
+ before { subject.should_receive(:running_under_bundler?).and_return(false) }
+
+ it 'includes the command to install the dependency' do
+ begin
+ subject.load_depenencies
+ rescue described_class::Error => e
+ e.message.should include("gem install --version '~> 0.0.1' listen")
+ end
+ end
+ end
+ end
+ end
+
+ describe '#dependencies_loaded?' do
+ it 'return false when dependencies are not loaded' do
+ subject.dependency 'listen', '~> 0.0.1'
+ subject.dependencies_loaded?.should be_false
+ end
+
+ it 'return true when dependencies are loaded' do
+ subject.stub(:gem, :require)
+
+ subject.dependency 'listen', '~> 0.0.1'
+ subject.load_depenencies
+ subject.dependencies_loaded?.should be_true
+ end
+
+ it 'return true when there are no dependencies to load' do
+ subject.dependencies_loaded?.should be_true
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/directory_record_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/directory_record_spec.rb
new file mode 100644
index 0000000..d7a64b6
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/directory_record_spec.rb
@@ -0,0 +1,1225 @@
+# 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|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 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$})
+ subject.ignoring_patterns.should include(%r{^\.old/}, %r{\.pid$})
+ end
+ end
+
+ describe '#ignore!' do
+ it 'replace the ignored paths in the record' do
+ subject.ignore!(%r{^\.old/}, %r{\.pid$})
+ subject.ignoring_patterns.should =~ [%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)})
+ subject.filtering_patterns.should include(%r{\.(?:jpe?g|gif|png)}, %r{\.(?:mp3|ogg|a3c)})
+ 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 =~ [%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 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
+ sleep 1.5 # make a difference in the mtime of the file
+ 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
+ sleep 0.3 # make sure the mtime is changed a bit
+ 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 "doesn't detects the modified file the second time if the content haven't changed" do
+ fixtures do |path|
+ touch 'existing_file.txt'
+
+ changes(path) do
+ touch 'existing_file.txt'
+ 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 '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
+ small_time_difference
+
+ changes(path) do
+ 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 detects the modified file the second time if just touched - #62" do
+ fixtures do |path|
+ touch 'existing_file.txt'
+ # Set sha1 path checksum
+ changes(path) do
+ touch 'existing_file.txt'
+ end
+ small_time_difference
+
+ changes(path, :use_last_record => true) do
+ 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'
+ small_time_difference
+
+ changes(path) do
+ 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'
+ sleep 1.5 # make a difference in the mtime of the file
+
+ modified, added, removed = changes(path) do
+ 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'
+
+ small_time_difference
+
+ modified, added, removed = changes(path) do
+ 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'
+ touch 'a_directory/a_file.rb'
+ touch 'a_directory/b_file.rb'
+
+ modified, added, removed = changes(path) do
+ mv 'a_directory', 'renamed'
+ end
+
+ added.should =~ %w(renamed/a_file.rb renamed/b_file.rb)
+ modified.should be_empty
+ removed.should =~ %w(a_directory/a_file.rb a_directory/b_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
+ sleep 1.1
+ 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)
+
+ lambda {
+ fixtures do |path|
+ file = 'removed_file.txt'
+ touch file
+ changes(path) { touch file }
+ end
+ }.should_not raise_error(Errno::ENOENT)
+ end
+ end
+
+ context 'within a directory containing a unix domain socket file' 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(Errno::ENXIO)
+ 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.2.12/vendor/listen/spec/listen/listener_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/listener_spec.rb
new file mode 100644
index 0000000..cf82f1b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/listener_spec.rb
@@ -0,0 +1,169 @@
+require 'spec_helper'
+
+describe Listen::Listener do
+ let(:adapter) { mock(Listen::Adapter, :start => true).as_null_object }
+ let(:watched_directory) { File.dirname(__FILE__) }
+
+ subject { described_class.new(watched_directory) }
+
+ before do
+ Listen::Adapter.stub(:select_and_initialize) { adapter }
+ # Don't build a record of the files inside the base directory.
+ subject.directory_record.stub(:build)
+ end
+
+ it_should_behave_like 'a listener to changes on a file-system'
+
+ describe '#initialize' do
+ context 'with no options' do
+ it 'sets the directory' do
+ subject.directory.should eq watched_directory
+ end
+
+ it 'converts the passed path into an absolute path - #21' do
+ described_class.new(File.join(watched_directory, '..')).directory.should eq File.expand_path('..',
watched_directory)
+ end
+
+ it 'sets the option for using relative paths in the callback to the default one' do
+ subject.instance_variable_get(:@use_relative_paths).should eq
described_class::DEFAULT_TO_RELATIVE_PATHS
+ end
+ end
+
+ context 'with custom options' do
+ subject { described_class.new(watched_directory, :ignore => /\.ssh/, :filter => [/.*\.rb/,/.*\.md/],
+ :latency => 0.5, :force_polling => true, :relative_paths => true) }
+
+ it 'passes the custom ignored paths to the directory record' do
+ subject.directory_record.ignoring_patterns.should include /\.ssh/
+ end
+
+ it 'passes the custom filters to the directory record' do
+ subject.directory_record.filtering_patterns.should =~ [/.*\.rb/,/.*\.md/]
+ end
+
+ it 'sets the cutom option for using relative paths in the callback' do
+ subject.instance_variable_get(:@use_relative_paths).should be_true
+ end
+
+ it 'sets adapter_options' do
+ subject.instance_variable_get(:@adapter_options).should eq(:latency => 0.5, :force_polling => true)
+ end
+ end
+ end
+
+ describe '#start' do
+ it 'selects and initializes an adapter' do
+ Listen::Adapter.should_receive(:select_and_initialize).with(watched_directory, {}) { adapter }
+ subject.start
+ end
+
+ it 'builds the directory record' do
+ subject.directory_record.should_receive(:build)
+ 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.directory_record.should_receive(:build)
+ subject.unpause
+ end
+ end
+ end
+
+ describe '#ignore'do
+ it 'delegates the work to the directory record' do
+ subject.directory_record.should_receive(:ignore).with 'some_directory'
+ subject.ignore 'some_directory'
+ end
+ end
+
+ describe '#ignore!'do
+ it 'delegates the work to the directory record' do
+ subject.directory_record.should_receive(:ignore!).with 'some_directory'
+ subject.ignore! 'some_directory'
+ end
+ end
+
+ describe '#filter' do
+ it 'delegates the work to the directory record' do
+ subject.directory_record.should_receive(:filter).with /\.txt$/
+ subject.filter /\.txt$/
+ end
+ end
+
+ describe '#filter!' do
+ it 'delegates the work to the directory record' do
+ subject.directory_record.should_receive(:filter!).with /\.txt$/
+ 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.directory_record.stub(:fetch_changes => changes)
+ end
+
+ it 'fetches the changes of the directory record' do
+ subject.directory_record.should_receive(:fetch_changes).with(
+ directories, hash_including(:relative_paths => described_class::DEFAULT_TO_RELATIVE_PATHS)
+ )
+ subject.on_change(directories)
+ end
+
+ context 'with relative paths option set to true' do
+ subject { described_class.new(watched_directory, :relative_paths => true) }
+
+ it 'fetches the changes of the directory record' do
+ subject.directory_record.should_receive(:fetch_changes).with(directories,
hash_including(:relative_paths => true))
+ 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) { {:modified => %w{path1}, :added => [], :removed => %w{path2}} }
+
+ 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/multi_listener_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/multi_listener_spec.rb
new file mode 100644
index 0000000..32e1c89
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/multi_listener_spec.rb
@@ -0,0 +1,174 @@
+require 'spec_helper'
+
+describe Listen::MultiListener do
+ let(:adapter) { mock(Listen::Adapter, :start => true).as_null_object }
+ let(:watched_directories) { [File.dirname(__FILE__), File.expand_path('../..', __FILE__)] }
+
+ subject { described_class.new(*watched_directories) }
+
+ 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)
+ end
+
+ it_should_behave_like 'a listener to changes on a file-system'
+
+ describe '#initialize' do
+ context 'with no options' do
+ it 'sets the directories' do
+ subject.directories.should =~ watched_directories
+ end
+
+ it 'converts the passed paths into absolute paths - #21' do
+ paths = watched_directories.map { |d| File.join(d, '..') }
+ described_class.new(*paths).directories.should =~ watched_directories.map{ |d|
File.expand_path('..', d) }
+ end
+ end
+
+ context 'with custom options' do
+ subject do
+ args = watched_directories << {:ignore => /\.ssh/, :filter => [/.*\.rb/,/.*\.md/], :latency => 0.5,
:force_polling => true}
+ described_class.new(*args)
+ end
+
+ it 'passes the custom ignored paths to each directory record' do
+ subject.directories_records.each do |r|
+ r.ignoring_patterns.should include /\.ssh/
+ end
+ end
+
+ it 'passes the custom filters to each directory record' do
+ subject.directories_records.each do |r|
+ r.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)
+ 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 all directories records' do
+ subject.directories_records.each do |r|
+ r.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 all directories records' do
+ subject.directories_records.each do |r|
+ r.should_receive(:build)
+ end
+ subject.unpause
+ end
+ end
+ end
+
+ describe '#ignore' do
+ it 'delegates the work to each directory record' do
+ subject.directories_records.each do |r|
+ r.should_receive(:ignore).with 'some_directory'
+ end
+ subject.ignore 'some_directory'
+ end
+ end
+
+ describe '#ignore!' do
+ it 'delegates the work to each directory record' do
+ subject.directories_records.each do |r|
+ r.should_receive(:ignore!).with 'some_directory'
+ end
+ subject.ignore! 'some_directory'
+ end
+ end
+
+ describe '#filter' do
+ it 'delegates the work to each directory record' do
+ subject.directories_records.each do |r|
+ r.should_receive(:filter).with /\.txt$/
+ end
+ subject.filter /\.txt$/
+ end
+ end
+
+ describe '#filter!' do
+ it 'delegates the work to each directory record' do
+ subject.directories_records.each do |r|
+ r.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, hash_including(:relative_paths => described_class::DEFAULT_TO_RELATIVE_PATHS)
+ ).and_return(changes)
+ end
+ subject.on_change(directories)
+ 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) { {:modified => %w{path1}, :added => [], :removed => %w{path2}} }
+
+ 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.2.12/vendor/listen/spec/listen/turnstile_spec.rb
new file mode 100644
index 0000000..178a3b4
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen/turnstile_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+def run_in_two_threads(proc1, proc2)
+ t1 = Thread.new &proc1
+ sleep test_latency # t1 must run before t2
+ t2 = Thread.new { proc2.call; Thread.kill t1 }
+ t2.join(test_latency * 2)
+ensure
+ Thread.kill t1 if t1
+ Thread.kill t2 if t2
+end
+
+describe Listen::Turnstile do
+ before { @called = false }
+
+ describe '#wait' do
+ context 'without a signal' do
+ it 'blocks one thread indefinitely' do
+ run_in_two_threads lambda {
+ subject.wait
+ @called = true
+ }, lambda {
+ sleep test_latency
+ }
+ @called.should be_false
+ end
+ end
+
+ context 'with a signal' do
+ it 'blocks one thread until it recieves a signal from another thread' do
+ run_in_two_threads lambda {
+ subject.wait
+ @called = true
+ }, lambda {
+ subject.signal
+ sleep test_latency
+ }
+ @called.should be_true
+ end
+ end
+ end
+
+ describe '#signal' do
+ context 'without a wait-call before' do
+ it 'does nothing' do
+ run_in_two_threads lambda {
+ subject.signal
+ @called = true
+ }, lambda {
+ sleep test_latency
+ }
+ @called.should be_true
+ end
+ end
+ end
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen_spec.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen_spec.rb
new file mode 100644
index 0000000..9e35ecf
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/listen_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Listen do
+ describe '#to' do
+ context 'with one path to listen to' do
+ let(:listener) { mock(Listen::Listener) }
+ let(:listener_class) { Listen::Listener }
+
+ before { listener_class.stub(:new => listener) }
+
+ context 'without options' do
+ it 'creates an instance of Listner' do
+ listener_class.should_receive(:new).with('/path')
+ described_class.to('/path')
+ end
+ end
+
+ context 'with options' do
+ it 'creates an instance of Listner 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 listner 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
+ let(:multi_listener) { mock(Listen::MultiListener) }
+ let(:multi_listener_class) { Listen::MultiListener }
+
+ before { multi_listener_class.stub(:new => multi_listener) }
+
+ context 'without options' do
+ it 'creates an instance of MultiListner' do
+ multi_listener_class.should_receive(:new).with('path1', 'path2')
+ described_class.to('path1', 'path2')
+ end
+ end
+
+ context 'with options' do
+ it 'creates an instance of MultiListner with the passed params' do
+ multi_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 MultiListener instance created with the passed params' do
+ described_class.to('path1', 'path2', :filter => '**/*').should eq multi_listener
+ end
+ end
+
+ context 'with a block' do
+ it 'starts a MultiListener instance after creating it with the passed params' do
+ multi_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.2.12/vendor/listen/spec/spec_helper.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/spec_helper.rb
new file mode 100644
index 0000000..853253e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/spec_helper.rb
@@ -0,0 +1,21 @@
+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.2.12/vendor/listen/spec/support/adapter_helper.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/adapter_helper.rb
new file mode 100644
index 0000000..9784749
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/adapter_helper.rb
@@ -0,0 +1,629 @@
+# Adapter watch
+#
+# @param [Listen::Listener] listener the adapter listener
+# @param [String] path the path to watch
+#
+def watch(listener, expected_changes, *paths)
+ callback = lambda { |changed_dirs, options| @called = true; listener.on_change(changed_dirs) }
+ @adapter = Listen::Adapter.select_and_initialize(paths, { :report_changes => false, :latency =>
test_latency }, &callback)
+
+ forced_stop = false
+ prevent_deadlock = Proc.new { sleep(10); puts "Forcing stop"; @adapter.stop; forced_stop = true }
+
+ @adapter.start(false)
+
+ 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
+ after { subject.stop }
+
+ context 'with the blocking param set to true' do
+ it 'blocks the current thread after starting the workers' do
+ @called = false
+ t = Thread.new { subject.start(true); @called = true }
+ sleep(test_latency * 3)
+ Thread.kill(t) if t
+ @called.should be_false
+ end
+ end
+
+ context 'with the blocking param set to false' do
+ it 'does not block the current thread after starting the workers' do
+ @called = false
+ t = Thread.new { subject.start(false); @called = true }
+ sleep(test_latency * 3)
+ Thread.kill(t) if t
+ @called.should be_true
+ 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(false); subject.stop }
+
+ it 'returns false' do
+ subject.should_not be_started
+ end
+ end
+
+ context 'with a started adapter' do
+ before { subject.start(false) }
+ 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) { mock(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.2.12/vendor/listen/spec/support/directory_record_helper.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/directory_record_helper.rb
new file mode 100644
index 0000000..404e81e
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/directory_record_helper.rb
@@ -0,0 +1,55 @@
+# 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,
+# 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.5 - (diff < 0.5 ? diff : 0.4) )
+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
+
+ if diff > 0.1 # We are not at the beginning of a second
+ sleep 1.1 - diff # 1.1 is used instead of 1 to account for the processing time (estimated at 0.1 sec)
+ 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.2.12/vendor/listen/spec/support/fixtures_helper.rb
new file mode 100644
index 0000000..a885ac6
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/fixtures_helper.rb
@@ -0,0 +1,29 @@
+require 'tmpdir'
+
+include FileUtils
+
+# Prepares temporary fixture-directories and
+# cleans them afterwards.
+#
+# @param [Fixnum] number_of_directories the number of fixture-directories to make
+#
+# @yield [path1, path2, ...] the empty fixture-directories
+# @yieldparam [String] path the path to a fixture directory
+#
+def fixtures(number_of_directories = 1)
+ current_pwd = pwd
+ paths = 1.upto(number_of_directories).map do
+ File.expand_path(File.join(pwd, "spec/.fixtures/#{Time.now.to_f.to_s.sub('.', '') + rand(9999).to_s}"))
+ end
+
+ # Create the dirs
+ paths.each { |p| mkdir_p(p) }
+
+ cd(paths.first) if number_of_directories == 1
+
+ yield(*paths)
+
+ensure
+ cd current_pwd
+ paths.map { |p| rm_rf(p) if File.exists?(p) }
+end
diff --git a/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/listeners_helper.rb
b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/listeners_helper.rb
new file mode 100644
index 0000000..2ff969b
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/listeners_helper.rb
@@ -0,0 +1,156 @@
+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 param set to false' do
+ it 'passes the blocking param to the adapter' do
+ adapter.should_receive(:start).with(false)
+ subject.start(false)
+ end
+ 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(:paused=).with(true)
+ 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(:paused=).with(false)
+ 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.2.12/vendor/listen/spec/support/platform_helper.rb
new file mode 100644
index 0000000..480b21f
--- /dev/null
+++ b/backends/css/gems/sass-3.2.12/vendor/listen/spec/support/platform_helper.rb
@@ -0,0 +1,15 @@
+def mac?
+ RbConfig::CONFIG['target_os'] =~ /darwin/i
+end
+
+def linux?
+ RbConfig::CONFIG['target_os'] =~ /linux/i
+end
+
+def bsd?
+ RbConfig::CONFIG['target_os'] =~ /freebsd/i
+end
+
+def windows?
+ RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+end
diff --git a/backends/css/mkdeps.py b/backends/css/mkdeps.py
new file mode 100644
index 0000000..a322791
--- /dev/null
+++ b/backends/css/mkdeps.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+import subprocess, os, re
+
+perdir = {}
+
+ret = subprocess.check_output(['git', 'ls-tree', 'HEAD', '--name-only', '-r', '--', 'gems']).splitlines()
+
+for r in ret:
+ dname = os.path.dirname(r)
+
+ if dname in perdir:
+ perdir[dname].append(r)
+ else:
+ perdir[dname] = [r]
+
+for dname in perdir:
+ vname = 'csssass_{0}'.format(re.sub('[/.-]', '_', dname))
+
+ print('{0}dir = $(GCA_RBBACKENDS_DIR)/css/{1}'.format(vname, dname))
+ print('{0}_DATA = \\'.format(vname))
+ print("\tbackends/css/{0}".format(" \\\n\tbackends/css/".join(perdir[dname])))
+ print('')
+
+# vi:ts=4:et
diff --git a/backends/css/org.gnome.CodeAssist.v1.css.service.in
b/backends/css/org.gnome.CodeAssist.v1.css.service.in
new file mode 100644
index 0000000..e193079
--- /dev/null
+++ b/backends/css/org.gnome.CodeAssist.v1.css.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.CodeAssist.v1.css
+Exec= backendexecdir@/css --transport dbus
diff --git a/configure.ac b/configure.ac
index cf9f57c..01d692a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -232,6 +232,49 @@ AM_CONDITIONAL(BACKENDS_RUBY_ENABLE, test "x$enable_ruby" = "xyes")
dnl ================================================================
+dnl css backend configuration
+dnl ================================================================
+AC_ARG_ENABLE([css],
+ AS_HELP_STRING([--enable-css],[enable css backend]),
+ [enable_css=$enableval],
+ [enable_css=auto])
+
+AC_MSG_CHECKING([css backend])
+
+if test "x$enable_css" = "xauto"; then
+ if test "x$RUBY" = "x"; then
+ AC_MSG_RESULT([no (requires ruby 2.0)])
+ enable_css=no
+ else
+ AC_MSG_RESULT([yes])
+ enable_css=yes
+ fi
+elif test "x$enable_css" != "xno"; then
+ if test "x$RUBY" = "x"; then
+ AC_MSG_ERROR([no (requires ruby 2.0)])
+ else
+ AC_MSG_RESULT([yes])
+ enable_css=yes
+ fi
+else
+ AC_MSG_RESULT([no])
+fi
+
+ruby_sass=
+
+if test "x$enable_css" = "xyes"; then
+ $RUBY -rsass -e '' 2>/dev/null
+ if test $? -eq 0 ; then
+ ruby_sass=yes
+ fi
+fi
+
+color_enable_var("$enable_css", [enable_css_msg])
+
+AM_CONDITIONAL(BACKENDS_CSS_ENABLE, test "x$enable_css" = "xyes")
+AM_CONDITIONAL(RUBY_SASS, test "x$ruby_sass" = "xyes")
+
+dnl ================================================================
dnl js support
dnl ================================================================
AC_PATH_PROG([GJS],[gjs])
@@ -498,6 +541,8 @@ backends/js/org.gnome.CodeAssist.v1.js.service
backends/js/js
backends/sh/org.gnome.CodeAssist.v1.sh.service
backends/sh/sh
+backends/css/org.gnome.CodeAssist.v1.css.service
+backends/css/css
])
AC_OUTPUT
@@ -520,5 +565,6 @@ Configuration:
ruby: $enable_ruby_msg
javascript: $enable_js_msg
shell: $enable_sh_msg
+ css: $enable_css_msg
"
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]