pax_global_header00006660000000000000000000000064122236654520014521gustar00rootroot0000000000000052 comment=6d00b417184ee8a7f7c1e5de311942e141256ac4 sass-3.2.12/000077500000000000000000000000001222366545200125575ustar00rootroot00000000000000sass-3.2.12/.gitignore000066400000000000000000000001271222366545200145470ustar00rootroot00000000000000/.yardoc /coverage /doc /pkg /test/rails /.sass-cache /.haml /.sass /site *.rbc /.sass sass-3.2.12/.gitmodules000066400000000000000000000001331222366545200147310ustar00rootroot00000000000000[submodule "vendor/listen"] path = vendor/listen url = git://github.com/guard/listen.git sass-3.2.12/.travis.yml000066400000000000000000000003031222366545200146640ustar00rootroot00000000000000rvm: - 1.8.7 - 1.9.2 - 1.9.3 gemfile: test/Gemfile env: - MATHN=true - MATHN=false branches: only: - master - stable notifications: irc: {channels: "irc.freenode.org#sass"} sass-3.2.12/.yardopts000066400000000000000000000005071222366545200144270ustar00rootroot00000000000000--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 sass-3.2.12/CONTRIBUTING000066400000000000000000000002011222366545200144020ustar00rootroot00000000000000Contributions are welcomed. Please see the following sites for guidelines: http://sass-lang.com/development.html#contributing sass-3.2.12/MIT-LICENSE000066400000000000000000000021161222366545200142130ustar00rootroot00000000000000Copyright (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. sass-3.2.12/README.md000066400000000000000000000151501222366545200140400ustar00rootroot00000000000000# 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__. sass-3.2.12/Rakefile000066400000000000000000000227741222366545200142400ustar00rootroot00000000000000# ----- 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] 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 < :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 < 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 sass-3.2.12/TODO000066400000000000000000000026771222366545200132630ustar00rootroot00000000000000# -*- mode: org -*- #+STARTUP: nofold * Documentation Redo tutorial? Syntax highlighting? * Code Keep track of error offsets everywhere Use this to show error location in messages Just clean up SassScript syntax errors in general Lexer errors in particular are icky See in particular error changes made in c07b5c8 ** Sass Benchmark the effects of storing the raw template in sassc If it's expensive, overload RootNode dumping/loading to dup and set @template to nil Then fall back on reading from actual file Make Rack middleware the default for Rails and Merb versions that support it CSS superset Classes are mixins Can refer to specific property values? Syntax? Pull in Compass watcher stuff Internationalization Particularly word constituents in Regexps Optimization http://csstidy.sourceforge.net/ http://developer.yahoo.com/yui/compressor/ Also comma-folding identical rules where possible Multiple levels 0: No optimization 1: Nothing that changes doc structure No comma-folding 2: Anything that keeps functionality identical to O2 (default) 3: Assume order of rules doesn't matter Comma-fold even if there are intervening rules that might interfere CSS3 Add (optional) support for http://www.w3.org/TR/css3-values/#calc Cross-unit arithmetic should compile into this Should we use "mod" in Sass for consistency? sass-3.2.12/VERSION000066400000000000000000000000071222366545200136240ustar00rootroot000000000000003.2.12 sass-3.2.12/VERSION_NAME000066400000000000000000000000131222366545200144210ustar00rootroot00000000000000Media Mark sass-3.2.12/bin/000077500000000000000000000000001222366545200133275ustar00rootroot00000000000000sass-3.2.12/bin/sass000077500000000000000000000003621222366545200142270ustar00rootroot00000000000000#!/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! sass-3.2.12/bin/sass-convert000077500000000000000000000003311222366545200157010ustar00rootroot00000000000000#!/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! sass-3.2.12/bin/scss000077500000000000000000000003621222366545200142310ustar00rootroot00000000000000#!/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! sass-3.2.12/doc-src/000077500000000000000000000000001222366545200141115ustar00rootroot00000000000000sass-3.2.12/doc-src/FAQ.md000066400000000000000000000026001222366545200150400ustar00rootroot00000000000000# Frequently Asked Questions * Table of contents {:toc} ## Can I use a variable from my controller in my Sass file? {#q-ruby-code} No. Sass files aren't views. They're compiled once into static CSS files, then left along until they're changed and need to be compiled again. Not only don't you want to be running a full request cycle every time someone requests a stylesheet, but it's not a great idea to put much logic in there anyway due to how browsers handle them. If you really need some sort of dynamic CSS, you can define your own {Sass::Script::Functions Sass functions} using Ruby that can access the database or other configuration. *Be aware when doing this that Sass files are by default only compiled once and then served statically.* 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 `:custom` option} to pass in data that {Sass::Script::Functions::EvaluationContext#options can be accessed} from your Sass functions. # You still haven't answered my question! Sorry! Try looking at the [Sass](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html) reference, If you can't find an answer there, feel free to ask in `#sass` on irc.freenode.net or send an email to the [mailing list](http://groups.google.com/group/sass-lang). sass-3.2.12/doc-src/INDENTED_SYNTAX.md000066400000000000000000000122111222366545200167300ustar00rootroot00000000000000# Sass Indented Syntax * Table of contents {:toc} Sass's indented syntax (also known simply as "Sass") is designed to provide a more concise and, for some, more aesthetically appealing alternative to the CSS-based SCSS syntax. It's not compatible with CSS; instead of using `{` and `}` to delimit blocks of styles, it uses indentation, and instead of using semicolons to separate statements it uses newlines. This usually leads to substantially less text when saying the same thing. Each statement in Sass, such as property declarations and selectors, must be placed on its own line. In addition, everything that would be within `{` and `}` after a statement must be on a new line and indented one level deeper than that statement. For example, this CSS: #main { color: blue; font-size: 0.3em; } would be this Sass: #main color: blue font-size: 0.3em Similarly, this SCSS: #main { color: blue; font-size: 0.3em; a { font: { weight: bold; family: serif; } &:hover { background-color: #eee; } } } would be this Sass: #main color: blue font-size: 0.3em a font: weight: bold family: serif &:hover background-color: #eee ## Sass Syntax Differences In general, most CSS and SCSS syntax works straightforwardly in Sass by using newlines instead of semicolons and indentation instead of braces. However, there are some cases where there are differences or subtleties, which are detailed below. ## Property Syntax The indented syntax supports two ways of declaring CSS properties. The first is just like CSS, except without the semicolon. The second, however, places the colon *before* the property name. For example: #main :color blue :font-size 0.3em By default, both ways may be used. However, the {file:SASS_REFERENCE.md#property_syntax-option `:property_syntax` option} may be used to specify that only one property syntax is allowed. ### Multiline Selectors Normally in the indented syntax, a single selector must take up a single line. There is one exception, however: selectors can contain newlines as long as they only appear after commas. For example: .users #userTab, .posts #postTab width: 100px height: 30px ### Comments Like everything else in the indented syntax, comments are line-based. This means that they don't work the same way as in SCSS. They must take up an entire line, and they also encompass all text nested beneath them. Like SCSS, the indented syntax supports two kinds of comments. Comments beginning with `/*` are preserved in the CSS output, although unlike SCSS they don't require a closing `*/`. Comments beginning with `//` are removed entirely. For example: /* This comment will appear in the CSS output. This is nested beneath the comment, so it's part of it body color: black // This comment will not appear in the CSS output. This is nested beneath the comment as well, so it also won't appear a color: green is compiled to: /* This comment will appear in the CSS output. * This is nested beneath the comment, * so it's part of it */ body { color: black; } a { color: green; } ### `@import` The `@import` directive in Sass does not require quotes, although they may be used. For example, this SCSS: @import "themes/dark"; @import "font.sass"; would be this Sass: @import themes/dark @import font.sass ### Mixin Directives Sass supports shorthands for the `@mixin` and `@include` directives. Instead of writing `@mixin`, you can use the character `=`; instead of writing `@include`, you can use the character `+`. For example: =large-text font: family: Arial size: 20px weight: bold color: #ff0000 h1 +large-text is the same as: @mixin large-text font: family: Arial size: 20px weight: bold color: #ff0000 h1 @include large-text ## Deprecated Syntax Since the indented syntax has been around for a while, previous versions have made some syntactic decisions that have since been changed. Some of the old syntax still works, though, so it's documented here. **Note that this syntax is not recommended for use in new Sass files**. It will print a warning if it's used, and it will be removed in a future version. ### `=` for Properties and Variables `=` used to be used instead of `:` when setting variables and when setting properties to SassScript values. It has slightly different semantics than `:`; see {file:SASS_CHANGELOG.md#3-0-0-sass-script-context this changelog entry} for details. ### `||=` for Default Variables `||=` used to be used instead of `:` when setting the default value of a variable. The `!default` flag was not used. The variable value has the same semantics as `=`; see {file:SASS_CHANGELOG.md#3-0-0-sass-script-context this changelog entry} for details. ### `!` Prefix for Variables `!` used to be used as the variable prefix instead of `$`. This had no difference in functionality; it was a purely aesthetic change. sass-3.2.12/doc-src/SASS_CHANGELOG.md000066400000000000000000002665301222366545200165670ustar00rootroot00000000000000# Sass Changelog * Table of contents {:toc} ## 3.2.12 * Add a couple missing `require`s, fixing some load errors, especially when using the command-line interface. * Tune up some heuristics for eliminating redundant generated selectors. This will prevent some selector elimination in cases where multi-layered `@extend` is being used and where it seems intuitively like selectors shouldn't be eliminated. ## 3.2.11 * Fix `@extend`'s semantics with respect to pseudo-elements. They are no longer treated identically to pseudo-classes. * A more understandable error is now provided when the `-E` option is passed to the Sass command line in ruby 1.8 * Fixed a bug in the output of lists containing unary plus or minus operations during sass <=> scss conversion. * Avoid the [IE7 `content: counter` bug][cc bug] with `content: counters` as well. * Fix some thread-safety issues. ## 3.2.10 * Use the Sass logger infrastructure for `@debug` directives. * When printing a Sass error into a CSS comment, escape `*/` so the comment doesn't end prematurely. * Preserve the `!` in `/*! ... */`-style comments. * Fix a bug where selectors were being incorrectly trimmed when using `@extend`. * Fix a bug where `sass --unix-newlines` and `sass-convert --in-place` are not working on Windows (thanks [SATO Kentaro](http://www.ranvis.com)). ## 3.2.9 * Fix a bug where `@extend`s would occasionally cause a selector to be generated with the incorrect specificity. * Avoid loading [listen](http://github.com/guard/listen) v1.0, even if it's installed as a Gem (see [issue 719](https://github.com/nex3/sass/issues/719)). * Update the bundled version of [listen](http://github.com/guard/listen) to 0.7.3. * Automatically avoid the [IE7 `content: counter` bug][cc bug]. [cc bug]: http://jes.st/2013/ie7s-css-breaking-content-counter-bug/ ## 3.2.8 * Fix some edge cases where redundant selectors were emitted when using `@extend`. * Fix a bug where comma-separated lists with interpolation could lose elements. * Fix a bug in `sass-convert` where lists being passed as arguments to functions or mixins would lose their surrounding parentheses. * Fix a bug in `sass-convert` where `null` wasn't being converted correctly. * Fix a bug where multiple spaces in a string literal would sometimes be folded together. * `sass` and `sass-convert` won't create an empty file before writing to it. This fixes a flash of unstyled content when using LiveReload and similar tools. * Fix a case where a corrupted cache could produce fatal errors on some versions of Ruby. * Fix a case where a mixin loop error would be incorrectly reported when using `@content`. ## 3.2.7 * The \{Sass::Script::Functions#index `index`} and \{Sass::Script::Functions#zip `zip`} functions now work like all other list functions and treat individual values as single-element lists. * Avoid stack overflow errors caused by very long function or mixin argument lists. * Emit relative paths when using the `--line-comments` flag of the `sass` executable. * Fix a case where very long numbers would cause the SCSS parser to take exponential time. ## 3.2.6 * Support for Rubinius 2.0.0.rc1. All tests pass in 1.8 mode. 1.9 mode has some tests blocked on [Rubinius issue 2139](https://github.com/rubinius/rubinius/issues/2139). * Support for JRuby 1.7.2. * Support for symlinked executables. Thanks to [Yin-So Chen](http://yinsochen.com/). * Support for bubbling `@supports` queries in the indented syntax. * Fix an incorrect warning when using `@extend` from within nested `@media` queries. * Update the bundled version of [listen](http://github.com/guard/listen) to 0.7.2. ## 3.2.5 * Fix a bug where bogus `@extend` warnings were being generated. * Fix an `@import` bug on Windows. Thanks to [Darryl Miles](https://github.com/dlmiles). * Ruby 2.0.0-preview compatibility. Thanks to [Eric Saxby](http://www.livinginthepast.org/). * Fix incorrect line numbering when using DOS line endings with the indented syntax. ## 3.2.4 * Fix imports from `.jar` files in JRuby. Thanks to [Alex Hvostov](https://github.com/argv-minus-one). * Allow comments within `@import` statements in SCSS. * Fix a parsing performance bug where long decimals would occasionally take many minutes to parse. ## 3.2.3 * `sass --watch` no longer crashs when a file in a watched directory is deleted. * Allow `@extend` within bubbling nodes such as `@media`. * Fix various JRuby incompatibilities and test failures. * Work around a performance bug that arises from using `@extend` with deeply-nested selectors. ## 3.2.2 * Add a `--poll` option to force `sass --watch` to use the polling backend to [Listen](https://github.com/guard/listen). * Fix some error reporting bugs related to `@import`. * Treat [protocol-relative URLs][pru] in `@import`s as static URLs, just like `http` and `https` URLs. * Improve the error message for misplaced simple selectors. * Fix an option-handling bug that was causing errors with the Compass URL helpers. * Fix a performance issue with `@import` that only appears when ActiveSupport is loaded. * Fix flushing of actions to stdout. Thanks to [Russell Davis] (http://github.com/russelldavis). * Fix the documentation for the `max()` function. * Fix a `@media` parsing bug. [pru]: http://paulirish.com/2010/the-protocol-relative-url/ ### Deprecations -- Must Read! * Sass will now print a warning when it encounters a single `@import` statement that tries to import more than one file. For example, if you have `@import "screen"` and both `screen.scss` and `_screen.scss` exist, a warning will be printed. This will become an error in future versions of Sass. ## 3.2.1 (15 August 2012) * Fix a buggy interaction with Pow and Capybara that caused `EOFError`s. ## 3.2.0 (10 August 2012) ### `@content` A mixin include can now accept a block of content ({file:SASS_REFERENCE.md#mixin-content Reference Documentation}). The style block will be passed to the mixin and can be placed at the point @content is used. E.g.: @mixin iphone { @media only screen and (max-width: 480px) { @content; } } @include iphone { body { color: red } } Or in `.sass` syntax: =iphone @media only screen and (max-width: 480px) @content +iphone body color: red Produces: @media only screen and (max-width: 480px) { body { color: red } } Note that the contents passed to the mixin are evaluated in the scope they are used, not the scope of the mixin. {file:SASS_REFERENCE.md#variable_scope_and_content_blocks More on variable scoping.} ### Placeholder Selectors: `%foo` Sass supports a new, special type of selector called a "placeholder selector". These look like class and id selectors, except the `#` or `.` is replaced by `%`. They're meant to be used with the {file:SASS_REFERENCE.md#extend `@extend` directive}, when you want to write styles to be extended but you don't want the base styles to appear in the CSS. On its own, a placeholder selector just causes a ruleset not to be rendered. For example: // This ruleset won't be rendered on its own. #context a%extreme { color: blue; font-weight: bold; font-size: 2em; } However, placeholder selectors can be extended, just like classes and ids. The extended selectors will be generated, but the base placeholder selector will not. For example: .notice { @extend %extreme; } Is compiled to: #context a.notice { color: blue; font-weight: bold; font-size: 2em; } ### Variable Arguments Mixins and functions now both support variable arguments. When defining a mixin or function, you can add `...` after the final argument to have it accept an unbounded number of arguments and package them into a list. When calling a mixin or function, you can add `...` to expand the final argument (if it's a list) so that each value is passed as a separate argument. For example: @mixin box-shadow($shadows...) { // $shadows is a list of all arguments passed to box-shadow -moz-box-shadow: $shadows; -webkit-box-shadow: $shadows; box-shadow: $shadows; } // This is the same as "@include spacing(1, 2, 3);" $values: 1, 2, 3; @include spacing($values...); Finally, if a variable argument list is passed directly on to another mixin or function, it will also pass along any keyword arguments. This means that you can wrap a pre-existing mixin or function and add new functionality without changing the call signature. ### Directive Interpolation `#{}` interpolation is now allowed in all plain CSS directives (such as `@font-face`, `@keyframes`, and of course `@media`). In addition, `@media` gets some special treatment. In addition to allowing `#{}` interpolation, expressions may be used directly in media feature queries. This means that you can write e.g.: $media: screen; $feature: -webkit-min-device-pixel-ratio; $value: 1.5; @media #{$media} and ($feature: $value) { ... } This is intended to allow authors to easily write mixins that make use of `@media` and other directives dynamically. ### Smaller Improvements * Mixins and functions may now be defined in a nested context, for example within `@media` rules. This also allows files containing them to be imported in such contexts. * Previously, only the `:-moz-any` selector was supported; this has been expanded to support any vendor prefix, as well as the plain `:any` selector. * All proposed [CSS4 selectors](http://dev.w3.org/csswg/selectors4/) are now supported, including reference selectors (e.g. `.foo /attr/ .bar`) and subject selectors (e.g. `.foo!`). * Sass now supports a global list of load paths, accessible via {Sass.load_paths}. This allows plugins and libraries to easily register their Sass files such that they're accessible to all {Sass::Engine} instances. * `Sass.load_paths` is initialized to the value of the `SASS_PATH` environment variable. This variable should contain a colon-separated list of load paths (semicolon-separated on Windows). * In certain cases, redundant selectors used to be created as a result of a single rule having multiple `@extend`s. That redundancy has been eliminated. * Redundant selectors were also sometimes created by nested selectors using `@extend`. That redundancy has been eliminated as well. * There is now much more comprehensive support for using `@extend` alongside CSS3 selector combinators (`+`, `~`, and `>`). These combinators will now be merged as much as possible. * The full set of [extended color keywords](http://www.w3.org/TR/css3-color/#svg-color) are now supported by Sass. They may be used to refer to color objects, and colors will render using those color names when appropriate. * Sass 3.2 adds the \{Sass::Script::Functions#ie_hex_str `ie-hex-str`} function which returns a hex string for a color suitable for use with IE filters. * Sass 3.2 adds the \{Sass::Script::Functions#min `min`} and \{Sass::Script::Functions#max `max`} functions, which return the minimum and maximum of several values. * Sass functions are now more strict about how keyword arguments can be passed. * Decimal numbers now default to five digits of precision after the decimal point. * The \{Sass::Script::Functions::EvaluationContext.options options hash} available to Sass functions now contains the filename of the file that the function was executed in, rather than the top-level file. ### Backwards Incompatibilities -- Must Read! #### `@extend` Warnings Any `@extend` that doesn't match any selectors in the document will now print a warning. These warnings will become errors in future versions of Sass. This will help protect against typos and make it clearer why broken styles aren't working. For example: h1.notice {color: red} a.important {@extend .notice} This will print a warning, since the only use of `.notice` can't be merged with `a`. You can declare that you don't want warnings for a specific `@extend` by using the `!optional` flag. For example: h1.notice {color: red} a.important {@extend .notice !optional} This will not print a warning. #### Smaller Incompatibilities * Parent selectors followed immediately by identifiers (e.g. `&foo`) are fully disallowed. They were deprecated in 3.1.8. * `#{}` interpolation is now allowed in all comments. * The `!` flag may not be used with `//` comments (e.g. `//!`). * `#{}` interpolation is now disallowed in all `@import` statements except for those using `url()`. * `sass-convert` no longer supports converting files from LessCSS. ## 3.1.21 (10 August 2012) * Preserve single-line comments that are embedded within multi-line comments. * Preserve newlines in nested selectors when those selectors are used multiple times in the same document. * Allow tests to be run without the `LANG` environment variable set. * Update the bundled version of [Listen](https://github.com/guard/listen) to 0.4.7. * Sass will now convert `px` to other absolute units using the conversion ratio of `96px == 1in` as dictated by the [CSS Spec](http://www.w3.org/TR/CSS21/syndata.html#length-units) ## 3.1.20 * Don't crash if a UTF encoding isn't found. Thanks to [Andrew Garbutt](http://github.com/techsplicer). * Properly watch files recursively with `sass --watch`. Thanks to [Sébastien Tisserant](https://github.com/sebweaver). * Fix the documentation for the \{Sass::Script::Functions#append append()} function. * Support the `saturate()`, `opacity()`, and `invert()` functions when used as in the [Filter Effects][filter] spec. * Support MacRuby. Thanks to [Will Glynn](http://github.com/delta407). [filter]: https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html ## 3.1.19 * Fix an `uninitialized constant Sass::Exec::Sass::Util` error when using the command-line tool. * Allow `@extend` within directives such as `@media` as long as it only extends selectors that are within the same directive. ## 3.1.18 * Ruby 2.0 compatibility. Thanks to [Jeremy Kemper](https://github.com/jeremy). ### Deprecations -- Must Read! * Deprecate the use of `@extend` within directives such as `@media`. This has never worked correctly, and now it's officially deprecated. It will be an error in 3.2. ## 3.1.17 * Don't crash when calling `#inspect` on an internal Sass tree object in Ruby 1.9. * Fix some bugs in `sass --watch` introduced in 3.1.16. Thanks to [Maher Sallam](https://github.com/Maher4Ever). * Support bare interpolation in the value portion of attribute selectors (e.g. `[name=#{$value}]`). * Support keyword arguments for the `invert()` function. * Handle backslash-separated paths better on Windows. * Fix `rake install` on Ruby 1.9. * Properly convert nested `@if` statements with `sass-convert`. ## 3.1.16 * Fix some bugs in `sass-convert` selector parsing when converting from CSS. * Substantially improve compilation performance on Ruby 1.8. * Support the `@-moz-document` directive's non-standard `url-prefix` and `domain` function syntax. * Support the [`@supports` directive](http://www.w3.org/TR/css3-conditional/#at-supports). * Fix a performance issue when using `/*! */` comments with the Rails asset pipeline. * Support `-moz-element`. * Properly handle empty lists in `sass-convert`. * Move from [FSSM](https://github.com/ttilley/fssm) to [Listen](https://github.com/guard/listen) for file-system monitoring. ## 3.1.15 * Support extending multiple comma-separated selectors (e.g. `@extend .foo, .bar`). This is just a terser way to write multiple `@extend`s (e.g. `@extend .foo; @extend .bar`). This wasn't previously intended to work, but it did in the indented syntax only. * Avoid more stack overflows when there are import loops in files. * Update the bundled [FSSM](https://github.com/ttilley/fssm) to version 0.2.8.1. * Make the `grayscale` function work with `-webkit-filter`. * Provide a better error message for selectors beginning with `/` in the indented syntax. * Flush standard output after printing notifications in `sass --watch`. * Fix variable definitions in the REPL. ## 3.1.14 * Fix a typo that was causing crashes on Ruby 1.9. ## 3.1.13 * Fix a smattering of subtle bugs that would crop up when using multibyte character sets. * Fix a bug when using `@extend` with selectors containing newlines. * Make boolean operators short-circuit. * Remove unnecessary whitespace in selectors in `:compressed` mode. * Don't output debug info within non-`@media` directives. * Make sure `:after` and `:before` selectors end up on the end of selectors resulting from `@extend`. * Fix a bug when using imports containing invalid path characters on Windows. * Bubble CSS `@import` statements to the top of stylesheets. ## 3.1.12 * Compatibility with the `mathn` library (thanks to [Thomas Walpole](https://github.com/twalpole)). * Fix some infinite loops with mixins that were previously uncaught. * Catch infinite `@import` loops. * Fix a deprecation warning in `sass --update` and `--watch` (thanks to [Marcel Köppen](https://github.com/Marzelpan)). * Don't make `$important` a special pre-initialized variable. * Fix exponential parsing time of certain complex property values and selectors. * Properly merge `@media` directives with comma-separated queries. E.g. `@media foo, bar { @media baz { ... } }` now becomes `@media foo and baz, bar and baz { ... }`. ## 3.1.11 * Allow control directives (such as `@if`) to be nested beneath properties. * Allow property names to begin with a hyphen followed by interpolation (e.g. `-#{...}`). * Fix a parsing error with interpolation in comma-separated lists. * Make `--cache-store` with with `--update`. * Properly report `ArgumentError`s that occur within user-defined functions. * Don't crash on JRuby if the underlying Java doesn't support every Unicode encoding. * Add new `updated_stylesheet` callback, which is run after each stylesheet has been successfully compiled. Thanks to [Christian Peters](https://github.com/ChristianPeters). * Allow absolute paths to be used in an importer with a different root. * Don't destructively modify the options when running `Sass::Plugin.force_update`. * Prevent Regexp buffer overflows when parsing long strings (thanks to [Agworld](https://github.com/Agworld). ### Deprecations -- Must Read! * The `updating_stylesheet` is deprecated and will be removed in a future release. Use the new `updated_stylesheet` callback instead. ## 3.1.10 * Fix another aspect of the 3.1.8 regression relating to `+`. ## 3.1.9 * Fix a regression in 3.1.8 that broke the `+` combinator in selectors. * Deprecate the loud-comment flag when used with silent comments (e.g. `//!`). Using it with multi-line comments (e.g. `/*!`) still works. ## 3.1.8 * Deprecate parent selectors followed immediately by identifiers (e.g. `&foo`). This should never have worked, since it violates the rule of `&` only being usable where an element selector would. * Add a `--force` option to the `sass` executable which makes `--update` always compile all stylesheets, even if the CSS is newer. * Disallow semicolons at the end of `@import` directives in the indented syntax. * Don't error out when being used as a library without requiring `fileutil`. * Don't crash when Compass-style sprite imports are used with `StalenessChecker` (thanks to [Matthias Bauer](https://github.com/moeffju)). * The numeric precision of numbers in Sass can now be set using the `--precision` option to the command line. Additionally, the default number of digits of precision in Sass output can now be changed by setting `Sass::Script::Number.precision` to an integer (defaults to 3). Since this value can now be changed, the `PRECISION` constant in `Sass::Script::Number` has been deprecated. In the unlikely event that you were using it in your code, you should now use `Sass::Script::Number.precision_factor` instead. * Don't crash when running `sass-convert` with selectors with two commas in a row. * Explicitly require Ruby >= 1.8.7 (thanks [Eric Mason](https://github.com/ericmason)). * Properly validate the nesting of elements in imported stylesheets. * Properly compile files in parent directories with `--watch` and `--update`. * Properly null out options in mixin definitions before caching them. This fixes a caching bug that has been plaguing some Rails 3.1 users. ## 3.1.7 * Don't crash when doing certain operations with `@function`s. ## 3.1.6 * The option `:trace_selectors` can now be used to emit a full trace before each selector. This can be helpful for in-browser debugging of stylesheet imports and mixin includes. This option supersedes the `:line_comments` option and is superseded by the `:debug_info` option. * Fix a bug where long `@if`/`@else` chains would cause exponential slowdown under some circumstances. ## 3.1.5 * Updated the vendored FSSM version, which will avoid segfaults on OS X Lion when using `--watch`. ## 3.1.4 * Sass no longer unnecessarily caches the sass options hash. This allows objects that cannot be marshaled to be placed into the options hash. ## 3.1.3 * Sass now logs message thru a logger object which can be changed to provide integration with other frameworks' logging infrastructure. ## 3.1.2 * Fix some issues that were breaking Sass when running within Rubinius. * Fix some issues that were affecting Rails 3.1 integration. * New function `zip` allows several lists to be combined into one list of lists. For example: `zip(1px 1px 3px, solid dashed solid, red green blue)` becomes `1px solid red, 1px dashed green, 3px solid blue` * New function `index` returns the list index of a value within a list. For example: `index(1px solid red, solid)` returns `2`. When the value is not found `false` is returned. ## 3.1.1 * Make sure `Sass::Plugin` is loaded at the correct time in Rails 3. ## 3.1.0 * Add an {Sass::Script::Functions#invert `invert` function} that takes the inverse of colors. * A new sass function called `if` can be used to emit one of two values based on the truth value of the first argument. For example, `if(true, 1px, 2px)` returns `1px` and `if(false, 1px, 2px)` returns `2px`. * Compass users can now use the `--compass` flag to make the Compass libraries available for import. This will also load the Compass project configuration if run from the project root. * Many performance optimizations have been made by [thedarkone](http://github.com/thedarkone). * Allow selectors to contain extra commas to make them easier to modify. Extra commas will be removed when the selectors are converted to CSS. * `@import` may now be used within CSS or `@media` rules. The imported file will be treated as though it were nested within the rule. Files with mixins may not be imported in nested contexts. * If a comment starts with `!`, that comment will now be interpolated (`#{...}` will be replaced with the resulting value of the expression inside) and the comment will always be printed out in the generated CSS file -- even with compressed output. This is useful for adding copyright notices to your stylesheets. * A new executable named `scss` is now available. It is exactly like the `sass` executable except it defaults to assuming input is in the SCSS syntax. Both programs will use the source file's extension to determine the syntax where possible. ### Sass-based Functions While it has always been possible to add functions to Sass with Ruby, this release adds the ability to define new functions within Sass files directly. For example: $grid-width: 40px; $gutter-width: 10px; @function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; } #sidebar { width: grid-width(5); } Becomes: #sidebar { width: 240px; } ### Keyword Arguments Both mixins and Sass functions now support the ability to pass in keyword arguments. For example, with mixins: @mixin border-radius($value, $moz: true, $webkit: true, $css3: true) { @if $moz { -moz-border-radius: $value } @if $webkit { -webkit-border-radius: $value } @if $css3 { border-radius: $value } } @include border-radius(10px, $webkit: false); And with functions: p { color: hsl($hue: 180, $saturation: 78%, $lightness: 57%); } Keyword arguments are of the form `$name: value` and come after normal arguments. They can be used for either optional or required arguments. For mixins, the names are the same as the argument names for the mixins. For functions, the names are defined along with the functions. The argument names for the built-in functions are listed {Sass::Script::Functions in the function documentation}. Sass functions defined in Ruby can use the {Sass::Script::Functions.declare} method to declare the names of the arguments they take. #### New Keyword Functions The new keyword argument functionality enables new Sass color functions that use keywords to encompass a large amount of functionality in one function. * The {Sass::Script::Functions#adjust_color adjust-color} function works like the old `lighten`, `saturate`, and `adjust-hue` methods. It increases and/or decreases the values of a color's properties by fixed amounts. For example, `adjust-color($color, $lightness: 10%)` is the same as `lighten($color, 10%)`: it returns `$color` with its lightness increased by 10%. * The {Sass::Script::Functions#scale_color scale_color} function is similar to {Sass::Script::Functions#adjust adjust}, but instead of increasing and/or decreasing a color's properties by fixed amounts, it scales them fluidly by percentages. The closer the percentage is to 100% (or -100%), the closer the new property value will be to its maximum (or minimum). For example, `scale-color(hsl(120, 70, 80), $lightness: 50%)` will change the lightness from 80% to 90%, because 90% is halfway between 80% and 100%. Similarly, `scale-color(hsl(120, 70, 50), $lightness: 50%)` will change the lightness from 50% to 75%. * The {Sass::Script::Functions#change_color change-color} function simply changes a color's properties regardless of their old values. For example `change-color($color, $lightness: 10%)` returns `$color` with 10% lightness, and `change-color($color, $alpha: 0.7)` returns color with opacity 0.7. Each keyword function accepts `$hue`, `$saturation`, `$value`, `$red`, `$green`, `$blue`, and `$alpha` keywords, with the exception of `scale-color()` which doesn't accept `$hue`. These keywords modify the respective properties of the given color. Each keyword function can modify multiple properties at once. For example, `adjust-color($color, $lightness: 15%, $saturation: -10%)` both lightens and desaturates `$color`. HSL properties cannot be modified at the same time as RGB properties, though. ### Lists Lists are now a first-class data type in Sass, alongside strings, numbers, colors, and booleans. They can be assigned to variables, passed to mixins, and used in CSS declarations. Just like the other data types (except booleans), Sass lists look just like their CSS counterparts. They can be separated either by spaces (e.g. `1px 2px 0 10px`) or by commas (e.g. `Helvetica, Arial, sans-serif`). In addition, individual values count as single-item lists. Lists won't behave any differently in Sass 3.1 than they did in 3.0. However, you can now do more with them using the new [list functions](Sass/Script/Functions.html#list-functions): * The {Sass::Script::Functions#nth `nth($list, $n)` function} returns the nth item in a list. For example, `nth(1px 2px 10px, 2)` returns the second item, `2px`. Note that lists in Sass start at 1, not at 0 like they do in some other languages. * The {Sass::Script::Functions#join `join($list1, $list2)` function} joins together two lists into one. For example, `join(1px 2px, 10px 5px)` returns `1px 2px 10px 5px`. * The {Sass::Script::Functions#append `append($list, $val)` function} appends values to the end of a list. For example, `append(1px 2px, 10px)` returns `1px 2px 10px`. * The {Sass::Script::Functions#join `length($list)` function} returns the length of a list. For example, `length(1px 2px 10px 5px)` returns `4`. For more details about lists see {file:SASS_REFERENCE.md#lists the reference}. #### `@each` There's also a new directive that makes use of lists. The {file:SASS_REFERENCE.md#each-directive `@each` directive} assigns a variable to each item in a list in turn, like `@for` does for numbers. This is useful for writing a bunch of similar styles without having to go to the trouble of creating a mixin. For example: @each $animal in puma, sea-slug, egret, salamander { .#{$animal}-icon { background-image: url('/images/#{$animal}.png'); } } is compiled to: .puma-icon { background-image: url('/images/puma.png'); } .sea-slug-icon { background-image: url('/images/sea-slug.png'); } .egret-icon { background-image: url('/images/egret.png'); } .salamander-icon { background-image: url('/images/salamander.png'); } ### `@media` Bubbling Modern stylesheets often use `@media` rules to target styles at certain sorts of devices, screen resolutions, or even orientations. They're also useful for print and aural styling. Unfortunately, it's annoying and repetitive to break the flow of a stylesheet and add a `@media` rule containing selectors you've already written just to tweak the style a little. Thus, Sass 3.1 now allows you to nest `@media` rules within selectors. It will automatically bubble them up to the top level, putting all the selectors on the way inside the rule. For example: .sidebar { width: 300px; @media screen and (orientation: landscape) { width: 500px; } } is compiled to: .sidebar { width: 300px; } @media screen and (orientation: landscape) { .sidebar { width: 500px; } } You can also nest `@media` directives within one another. The queries will then be combined using the `and` operator. For example: @media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } } is compiled to: @media screen and (orientation: landscape) { .sidebar { width: 500px; } } ### Nested `@import` The `@import` statement can now be nested within other structures such as CSS rules and `@media` rules. For example: @media print { @import "print"; } This imports `print.scss` and places all rules so imported within the `@media print` block. This makes it easier to create stylesheets for specific media or sections of the document and distributing those stylesheets across multiple files. ### Backwards Incompatibilities -- Must Read! * When `@import` is given a path without `.sass`, `.scss`, or `.css` extension, and no file exists at that path, it will now throw an error. The old behavior of becoming a plain-CSS `@import` was deprecated and has now been removed. * Get rid of the `--rails` flag for the `sass` executable. This flag hasn't been necessary since Rails 2.0. Existing Rails 2.0 installations will continue to work. * Removed deprecated support for ! prefixed variables. Use $ to prefix variables now. * Removed the deprecated css2sass executable. Use sass-convert now. * Removed support for the equals operator in variable assignment. Use : now. * Removed the sass2 mode from sass-convert. Users who have to migrate from sass2 should install Sass 3.0 and quiet all deprecation warnings before installing Sass 3.1. ### Sass Internals * It is now possible to define a custom importer that can be used to find imports using different import semantics than the default filesystem importer that Sass provides. For instance, you can use this to generate imports on the fly, look them up from a database, or implement different file naming conventions. See the {Sass::Importers::Base Importer Base class} for more information. * It is now possible to define a custom cache store to allow for efficient caching of Sass files using alternative cache stores like memcached in environments where a writable filesystem is not available or where the cache need to be shared across many servers for dynamically generated stylesheet environments. See the {Sass::CacheStores::Base CacheStore Base class} for more information. ## 3.0.26 (Unreleased) * Fix a performance bug in large SCSS stylesheets with many nested selectors. This should dramatically decrease compilation time of such stylesheets. * Upgrade the bundled FSSM to version 0.2.3. This means `sass --watch` will work out of the box with Rubinius. ## 3.0.25 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.25). * When displaying a Sass error in an imported stylesheet, use the imported stylesheet's contents rather than the top-level stylesheet. * Fix a bug that caused some lines with non-ASCII characters to be ignored in Ruby 1.8. * Fix a bug where boolean operators (`and`, `or`, and `not`) wouldn't work at the end of a line in a multiline SassScript expression. * When using `sass --update`, only update individual files when they've changed. ## 3.0.24 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.24). * Raise an error when `@else` appears without an `@if` in SCSS. * Fix some cases where `@if` rules were causing the line numbers in error reports to become incorrect. ## 3.0.23 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.23). * Fix the error message for unloadable modules when running the executables under Ruby 1.9.2. ### `@charset` Change The behavior of `@charset` has changed in version 3.0.23 in order to work around a bug in Safari, where `@charset` declarations placed anywhere other than the beginning of the document cause some CSS rules to be ignored. This change also makes `@charset`s in imported files behave in a more useful way. #### Ruby 1.9 When using Ruby 1.9, which keeps track of the character encoding of the Sass document internally, `@charset` directive in the Sass stylesheet and any stylesheets it imports are no longer directly output to the generated CSS. They're still used for determining the encoding of the input and output stylesheets, but they aren't rendered in the same way other directives are. Instead, Sass adds a single `@charset` directive at the beginning of the output stylesheet if necessary, whether or not the input stylesheet had a `@charset` directive. It will add this directive if and only if the output stylesheet contains non-ASCII characters. By default, the declared charset will be UTF-8, but if the Sass stylesheet declares a different charset then that will be used instead if possible. One important consequence of this scheme is that it's possible for a Sass file to import partials with different encodings (e.g. one encoded as UTF-8 and one as IBM866). The output will then be UTF-8, unless the importing stylesheet declares a different charset. #### Ruby 1.8 Ruby 1.8 doesn't have good support for encodings, so it uses a simpler but less accurate scheme for figuring out what `@charset` declaration to use for the output stylesheet. It just takes the first `@charset` declaration to appear in the stylesheet or any of its imports and moves it to the beginning of the document. This means that under Ruby 1.8 it's *not* safe to import files with different encodings. ## 3.0.22 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.22). * Remove `vendor/sass`, which snuck into the gem by mistake and was causing trouble for Heroku users (thanks to [Jacques Crocker](http://railsjedi.com/)). * `sass-convert` now understands better when it's acceptable to remove parentheses from expressions. ## 3.0.21 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.21). * Fix the permissions errors for good. * Fix more `#options` attribute errors. ## 3.0.20 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.20). * Fix some permissions errors. * Fix `#options` attribute errors when CSS functions were used with commas. ## 3.0.19 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.19). * Make the alpha value for `rgba` colors respect {Sass::Script::Number::PRECISION}. * Remove all newlines in selectors in `:compressed` mode. * Make color names case-insensitive. * Properly detect SCSS files when using `sass -c`. * Remove spaces after commas in `:compressed` mode. * Allow the `--unix-newlines` flag to work on Unix, where it's a no-op. ## 3.0.18 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.18). * Don't require `rake` in the gemspec, for bundler compatibility under JRuby. Thanks to [Gordon McCreight](http://www.gmccreight.com/blog). * Add a command-line option `--stop-on-error` that causes Sass to exit when a file fails to compile using `--watch` or `--update`. * Fix a bug in `haml_tag` that would allow duplicate attributes to be added and make `data-` attributes not work. * Get rid of the annoying RDoc errors on install. * Disambiguate references to the `Rails` module when `haml-rails` is installed. * Allow `@import` in SCSS to import multiple files in the same `@import` rule. ## 3.0.17 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.17). * Disallow `#{}` interpolation in `@media` queries or unrecognized directives. This was never allowed, but now it explicitly throws an error rather than just producing invalid CSS. * Make `sass --watch` not throw an error when passed a single file or directory. * Understand that mingw counts as Windows. * Make `sass --update` return a non-0 exit code if one or more files being updated contained an error. ## 3.0.16 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.16). * Fix a bug where certain sorts of comments would get improperly rendered in the `:compact` style. * Always allow a trailing `*/` in loud comments in the indented syntax. * Fix a performance issue with SCSS parsing in rare cases. Thanks to [Chris Eppstein](http://chriseppstein.github.com). * Use better heuristics for figuring out when someone might be using the wrong syntax with `sass --watch`. ## 3.0.15 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.15). * Fix a bug where `sass --watch` and `sass --update` were completely broken. * Allow `@import`ed values to contain commas. ## 3.0.14 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.14). * Properly parse paths with drive letters on Windows (e.g. `C:\Foo\Bar.sass`) in the Sass executable. * Compile Sass files in a deterministic order. * Fix a bug where comments after `@if` statements in SCSS weren't getting passed through to the output document. ## 3.0.13 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.13). ## CSS `@import` Directives Sass is now more intelligent about when to compile `@import` directives to plain CSS. Any of the following conditions will cause a literal CSS `@import`: * Importing a path with a `.css` extension (e.g. `@import "foo.css"`). * Importing a path with a media type (e.g. `@import "foo" screen;`). * Importing an HTTP path (e.g. `@import "http://foo.com/style.css"`). * Importing any URL (e.g. `@import url(foo)`). The former two conditions always worked, but the latter two are new. ## `-moz-calc` Support The new [`-moz-calc()` function](http://hacks.mozilla.org/2010/06/css3-calc/) in Firefox 4 will now be properly parsed by Sass. `calc()` was already supported, but because the parsing rules are different than for normal CSS functions, this had to be expanded to include `-moz-calc`. In anticipation of wider browser support, in fact, *any* function named `-*-calc` (such as `-webkit-calc` or `-ms-calc`) will be parsed the same as the `calc` function. ## `:-moz-any` Support The [`:-moz-any` pseudoclass selector](http://hacks.mozilla.org/2010/05/moz-any-selector-grouping/) is now parsed by Sass. ## `--require` Flag The Sass command-line executable can now require Ruby files using the `--require` flag (or `-r` for short). ## Rails Support Make sure the default Rails options take precedence over the default non-Rails options. This makes `./script/server --daemon` work again. ### Rails 3 Support Support for Rails 3 versions prior to beta 4 has been removed. Upgrade to Rails 3.0.0.beta4 if you haven't already. ## 3.0.12 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.12). ## Rails 3 Support Apparently the last version broke in new and exciting ways under Rails 3, due to the inconsistent load order caused by certain combinations of gems. 3.0.12 hacks around that inconsistency, and *should* be fully Rails 3-compatible. ### Deprecated: Rails 3 Beta 3 Haml's support for Rails 3.0.0.beta.3 has been deprecated. Haml 3.0.13 will only support 3.0.0.beta.4. ## 3.0.11 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.11). There were no changes made to Haml between versions 3.0.10 and 3.0.11. ## Rails 3 Support Make sure Sass *actually* regenerates stylesheets under Rails 3. The fix in 3.0.10 didn't work because the Rack stack we were modifying wasn't reloaded at the proper time. ## Bug Fixes * Give a decent error message when `--recursive` is used in `sass-convert` without a directory. ## 3.0.10 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.10). ### Appengine-JRuby Support The way we determine the location of the Haml installation no longer breaks the version of JRuby used by [`appengine-jruby`](http://code.google.com/p/appengine-jruby/). ### Rails 3 Support Sass will regenerate stylesheets under Rails 3 even when no controllers are being accessed. ### Other Improvements * When using `sass-convert --from sass2 --to sass --recursive`, suggest the use of `--in-place` as well. ## 3.0.9 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.9). There were no changes made to Sass between versions 3.0.8 and 3.0.9. A bug in Gemcutter caused the gem to be uploaded improperly. ## 3.0.8 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.8). * Fix a bug with Rails versions prior to Rails 3. ## 3.0.7 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.7). ### Encoding Support Sass 3.0.7 adds support for `@charset` for declaring the encoding of a stylesheet. For details see {file:SASS_REFERENCE.md#encodings the reference}. The `sass` and `sass-convert` executables also now take an `-E` option for specifying the encoding of Sass/SCSS/CSS files. ### Bug Fixes * When compiling a file named `.sass` but with SCSS syntax specified, use the latter (and vice versa). * Fix a bug where interpolation would cause some selectors to render improperly. * If a line in a Sass comment starts with `*foo`, render it as `*foo` rather than `* *foo`. ## 3.0.6 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.6). There were no changes made to Sass between versions 3.0.5 and 3.0.6. ## 3.0.5 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.5). ### `#{}` Interpolation in Properties Previously, using `#{}` in some places in properties would cause a syntax error. Now it can be used just about anywhere. Note that when `#{}` is used near operators like `/`, those operators are treated as plain CSS rather than math operators. For example: p { $font-size: 12px; $line-height: 30px; font: #{$font-size}/#{$line-height}; } is compiled to: p { font: 12px/30px; } This is useful, since normally {file:SASS_REFERENCE.md#division-and-slash a slash with variables is treated as division}. ### Recursive Mixins Mixins that include themselves will now print much more informative error messages. For example: @mixin foo {@include bar} @mixin bar {@include foo} @include foo will print: An @include loop has been found: foo includes bar bar includes foo Although it was previously possible to use recursive mixins without causing infinite looping, this is now disallowed, since there's no good reason to do it. ### Rails 3 Support Fix Sass configuration under Rails 3. Thanks [Dan Cheail](http://github.com/codeape). ### `sass --no-cache` Make the `--no-cache` flag properly forbid Sass from writing `.sass-cache` files. ## 3.0.4 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.4). * Raise an informative error when function arguments have a mispaced comma, as in `foo(bar, )`. * Fix a performance problem when using long function names such as `-moz-linear-gradient`. ## 3.0.3 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.3). ### Rails 3 Support Make sure Sass is loaded properly when using Rails 3 along with non-Rails-3-compatible plugins like some versions of `will_paginate`. Also, In order to make some Rails loading errors like the above easier to debug, Sass will now raise an error if `Rails.root` is `nil` when Sass is loading. Previously, this would just cause the paths to be mis-set. ### Merb Support Merb, including 1.1.0 as well as earlier versions, should *really* work with this release. ### Bug Fixes * Raise an informative error when mixin arguments have a mispaced comma, as in `@include foo(bar, )`. * Make sure SassScript subtraction happens even when nothing else dynamic is going on. * Raise an error when colors are used with the wrong number of digits. ## 3.0.2 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.2). ### Merb 1.1.0 Support Fixed a bug inserting the Sass plugin into the Merb 1.1.0 Rack application. ### Bug Fixes * Allow identifiers to begin with multiple underscores. * Don't raise an error when using `haml --rails` with older Rails versions. ## 3.0.1 [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.1). ### Installation in Rails `haml --rails` is no longer necessary for installing Sass in Rails. Now all you need to do is add `gem "haml"` to the Gemfile for Rails 3, or add `config.gem "haml"` to `config/environment.rb` for previous versions. `haml --rails` will still work, but it has been deprecated and will print an error message. It will not work in the next version of Sass. ### Rails 3 Beta Integration * Make sure manually importing the Sass Rack plugin still works with Rails, even though it's not necessary now. * Allow Sass to be configured in Rails even when it's being lazy-loaded. ### `:template_location` Methods The {file:SASS_REFERENCE.md#template_location-option `:template_location` option} can be either a String, a Hash, or an Array. This makes it difficult to modify or use with confidence. Thus, three new methods have been added for handling it: * {Sass::Plugin::Configuration#template_location_array Sass::Plugin#template_location_array} -- Returns the template locations and CSS locations formatted as an array. * {Sass::Plugin::Configuration#add_template_location Sass::Plugin#add_template_location} -- Converts the template location option to an array and adds a new location. * {Sass::Plugin::Configuration#remove_template_location Sass::Plugin#remove_template_location} -- Converts the template location option to an array and removes an existing location. ## 3.0.0 {#3-0-0} [Tagged on GitHub](http://github.com/nex3/sass/commit/3.0.0). ### Deprecations -- Must Read! {#3-0-0-deprecations} * Using `=` for SassScript properties and variables is deprecated, and will be removed in Sass 3.2. Use `:` instead. See also [this changelog entry](#3-0-0-sass-script-context) * Because of the above, property values using `:` will be parsed more thoroughly than they were before. Although all valid CSS3 properties as well as most hacks and proprietary syntax should be supported, it's possible that some properties will break. If this happens, please report it to [the Sass mailing list](http://groups.google.com/group/haml). * In addition, setting the default value of variables with `||=` is now deprecated and will be removed in Sass 3.2. Instead, add `!default` to the end of the value. See also [this changelog entry](#3-0-0-default-flag) * The `!` prefix for variables is deprecated, and will be removed in Sass 3.2. Use `$` as a prefix instead. See also [this changelog entry](#3-0-0-dollar-prefix). * The `css2sass` command-line tool has been deprecated, and will be removed in Sass 3.2. Use the new `sass-convert` tool instead. See also [this changelog entry](#3-0-0-sass-convert). * Selector parent references using `&` can now only be used where element names are valid. This is because Sass 3 fully parses selectors to support the new [`@extend` directive](#3-0-0-extend), and it's possible that the `&` could be replaced by an element name. ### SCSS (Sassy CSS) Sass 3 introduces a new syntax known as SCSS which is fully compatible with the syntax of CSS3, while still supporting the full power of Sass. This means that every valid CSS3 stylesheet is a valid SCSS file with the same meaning. In addition, SCSS understands most CSS hacks and vendor-specific syntax, such as [IE's old `filter` syntax](http://msdn.microsoft.com/en-us/library/ms533754%28VS.85%29.aspx). SCSS files use the `.scss` extension. They can import `.sass` files, and vice-versa. Their syntax is fully described in the {file:SASS_REFERENCE.md Sass reference}; if you're already familiar with Sass, though, you may prefer the {file:SCSS_FOR_SASS_USERS.md intro to SCSS for Sass users}. Since SCSS is a much more approachable syntax for those new to Sass, it will be used as the default syntax for the reference, as well as for most other Sass documentation. The indented syntax will continue to be fully supported, however. Sass files can be converted to SCSS using the new `sass-convert` command-line tool. For example: # Convert a Sass file to SCSS $ sass-convert style.sass style.scss **Note that if you're converting a Sass file written for Sass 2**, you should use the `--from sass2` flag. For example: # Convert a Sass file to SCSS $ sass-convert --from sass2 style.sass style.scss # Convert all Sass files to SCSS $ sass-convert --recursive --in-place --from sass2 --to scss stylesheets/ ### Syntax Changes {#3-0-0-syntax-changes} #### SassScript Context {#3-0-0-sass-script-context} The `=` character is no longer required for properties that use SassScript (that is, variables and operations). All properties now use SassScript automatically; this means that `:` should be used instead. Variables should also be set with `:`. For example, what used to be // Indented syntax .page color = 5px + 9px should now be // Indented syntax .page color: 5px + 9px This means that SassScript is now an extension of the CSS3 property syntax. All valid CSS3 properties are valid SassScript, and will compile without modification (some invalid properties work as well, such as Microsoft's proprietary `filter` syntax). This entails a few changes to SassScript to make it fully CSS3-compatible, which are detailed below. This also means that Sass will now be fully parsing all property values, rather than passing them through unchanged to the CSS. Although care has been taken to support all valid CSS3, as well as hacks and proprietary syntax, it's possible that a property that worked in Sass 2 won't work in Sass 3. If this happens, please report it to [the Sass mailing list](http://groups.google.com/group/haml). Note that if `=` is used, SassScript will be interpreted as backwards-compatibly as posssible. In particular, the changes listed below don't apply in an `=` context. The `sass-convert` command-line tool can be used to upgrade Sass files to the new syntax using the `--in-place` flag. For example: # Upgrade style.sass: $ sass-convert --in-place style.sass # Upgrade all Sass files: $ sass-convert --recursive --in-place --from sass2 --to sass stylesheets/ ##### Quoted Strings Quoted strings (e.g. `"foo"`) in SassScript now render with quotes. In addition, unquoted strings are no longer deprecated, and render without quotes. This means that almost all strings that had quotes in Sass 2 should not have quotes in Sass 3. Although quoted strings render with quotes when used with `:`, they do not render with quotes when used with `#{}`. This allows quoted strings to be used for e.g. selectors that are passed to mixins. Strings can be forced to be quoted and unquoted using the new \{Sass::Script::Functions#unquote unquote} and \{Sass::Script::Functions#quote quote} functions. ##### Division and `/` Two numbers separated by a `/` character are allowed as property syntax in CSS, e.g. for the `font` property. SassScript also uses `/` for division, however, which means it must decide what to do when it encounters numbers separated by `/`. For CSS compatibility, SassScript does not perform division by default. However, division will be done in almost all cases where division is intended. In particular, SassScript will perform division in the following three situations: 1. If the value, or any part of it, is stored in a variable. 2. If the value is surrounded by parentheses. 3. If the value is used as part of another arithmetic expression. For example: p font: 10px/8px $width: 1000px width: $width/2 height: (500px/2) margin-left: 5px + 8px/2px is compiled to: p { font: 10px/8px; width: 500px; height: 250px; margin-left: 9px; } ##### Variable Defaults Since `=` is no longer used for variable assignment, assigning defaults to variables with `||=` no longer makes sense. Instead, the `!default` flag should be added to the end of the variable value. This syntax is meant to be similar to CSS's `!important` flag. For example: $var: 12px !default; #### Variable Prefix Character {#3-0-0-dollar-prefix} The Sass variable character has been changed from `!` to the more aesthetically-appealing `$`. For example, what used to be !width = 13px .icon width = !width should now be $width: 13px .icon width: $width The `sass-convert` command-line tool can be used to upgrade Sass files to the new syntax using the `--in-place` flag. For example: # Upgrade style.sass: $ sass-convert --in-place style.sass # Upgrade all Sass files: $ sass-convert --recursive --in-place --from sass2 --to sass stylesheets/ `!` may still be used, but it's deprecated and will print a warning. It will be removed in the next version of Sass, 3.2. #### Variable and Mixin Names SassScript variable and mixin names may now contain hyphens. In fact, they may be any valid CSS3 identifier. For example: $prettiest-color: #542FA9 =pretty-text color: $prettiest-color In order to allow frameworks like [Compass](http://compass-style.org) to use hyphens in variable names while maintaining backwards-compatibility, variables and mixins using hyphens may be referred to with underscores, and vice versa. For example: $prettiest-color: #542FA9 .pretty // Using an underscore instead of a hyphen works color: $prettiest_color #### Single-Quoted Strings SassScript now supports single-quoted strings. They behave identically to double-quoted strings, except that single quotes need to be backslash-escaped and double quotes do not. #### Mixin Definition and Inclusion Sass now supports the `@mixin` directive as a way of defining mixins (like `=`), as well as the `@include` directive as a way of including them (like `+`). The old syntax is *not* deprecated, and the two are fully compatible. For example: @mixin pretty-text color: $prettiest-color a @include pretty-text is the same as: =pretty-text color: $prettiest-color a +pretty-text #### Sass Properties New-style properties (with the colon after the name) in indented syntax now allow whitespace before the colon. For example: foo color : blue #### Sass `@import` The Sass `@import` statement now allows non-CSS files to be specified with quotes, for similarity with the SCSS syntax. For example, `@import "foo.sass"` will now import the `foo.sass` file, rather than compiling to `@import "foo.sass";`. ### `@extend` {#3-0-0-extend} There are often cases when designing a page when one class should have all the styles of another class, as well as its own specific styles. The most common way of handling this is to use both the more general class and the more specific class in the HTML. For example, suppose we have a design for a normal error and also for a serious error. We might write our markup like so:
Oh no! You've been hacked!
And our styles like so: .error { border: 1px #f00; background-color: #fdd; } .seriousError { border-width: 3px; } Unfortunately, this means that we have to always remember to use `.error` with `.seriousError`. This is a maintenance burden, leads to tricky bugs, and can bring non-semantic style concerns into the markup. The `@extend` directive avoids these problems by telling Sass that one selector should inherit the styles of another selector. For example: .error { border: 1px #f00; background-color: #fdd; } .seriousError { @extend .error; border-width: 3px; } This means that all styles defined for `.error` are also applied to `.seriousError`, in addition to the styles specific to `.seriousError`. In effect, everything with class `.seriousError` also has class `.error`. Other rules that use `.error` will work for `.seriousError` as well. For example, if we have special styles for errors caused by hackers: .error.intrusion { background-image: url("/image/hacked.png"); } Then `
` will have the `hacked.png` background image as well. #### How it Works `@extend` works by inserting the extending selector (e.g. `.seriousError`) anywhere in the stylesheet that the extended selector (.e.g `.error`) appears. Thus the example above: .error { border: 1px #f00; background-color: #fdd; } .error.intrusion { background-image: url("/image/hacked.png"); } .seriousError { @extend .error; border-width: 3px; } is compiled to: .error, .seriousError { border: 1px #f00; background-color: #fdd; } .error.intrusion, .seriousError.intrusion { background-image: url("/image/hacked.png"); } .seriousError { border-width: 3px; } When merging selectors, `@extend` is smart enough to avoid unnecessary duplication, so something like `.seriousError.seriousError` gets translated to `.seriousError`. In addition, it won't produce selectors that can't match anything, like `#main#footer`. See also {file:SASS_REFERENCE.md#extend the `@extend` reference documentation}. ### Colors SassScript color values are much more powerful than they were before. Support was added for alpha channels, and most of Chris Eppstein's [compass-colors](http://chriseppstein.github.com/compass-colors) plugin was merged in, providing color-theoretic functions for modifying colors. One of the most interesting of these functions is {Sass::Script::Functions#mix mix}, which mixes two colors together. This provides a much better way of combining colors and creating themes than standard color arithmetic. #### Alpha Channels Sass now supports colors with alpha channels, constructed via the {Sass::Script::Functions#rgba rgba} and {Sass::Script::Functions#hsla hsla} functions. Alpha channels are unaffected by color arithmetic. However, the {Sass::Script::Functions#opacify opacify} and {Sass::Script::Functions#transparentize transparentize} functions allow colors to be made more and less opaque, respectively. Sass now also supports functions that return the values of the {Sass::Script::Functions#red red}, {Sass::Script::Functions#blue blue}, {Sass::Script::Functions#green green}, and {Sass::Script::Functions#alpha alpha} components of colors. #### HSL Colors Sass has many new functions for using the HSL values of colors. For an overview of HSL colors, check out [the CSS3 Spec](http://www.w3.org/TR/css3-color/#hsl-color). All these functions work just as well on RGB colors as on colors constructed with the {Sass::Script::Functions#hsl hsl} function. * The {Sass::Script::Functions#lighten lighten} and {Sass::Script::Functions#darken darken} functions adjust the lightness of a color. * The {Sass::Script::Functions#saturate saturate} and {Sass::Script::Functions#desaturate desaturate} functions adjust the saturation of a color. * The {Sass::Script::Functions#adjust_hue adjust-hue} function adjusts the hue of a color. * The {Sass::Script::Functions#hue hue}, {Sass::Script::Functions#saturation saturation}, and {Sass::Script::Functions#lightness lightness} functions return the corresponding HSL values of the color. * The {Sass::Script::Functions#grayscale grayscale} function converts a color to grayscale. * The {Sass::Script::Functions#complement complement} function returns the complement of a color. ### Other New Functions Several other new functions were added to make it easier to have more flexible arguments to mixins and to enable deprecation of obsolete APIs. * {Sass::Script::Functions#type_of `type-of`} -- Returns the type of a value. * {Sass::Script::Functions#unit `unit`} -- Returns the units associated with a number. * {Sass::Script::Functions#unitless `unitless`} -- Returns whether a number has units or not. * {Sass::Script::Functions#comparable `comparable`} -- Returns whether two numbers can be added or compared. ### Watching for Updates {#3-0-0-watch} The `sass` command-line utility has a new flag: `--watch`. `sass --watch` monitors files or directories for updated Sass files and compiles those files to CSS automatically. This will allow people not using Ruby or [Compass](http://compass-style.org) to use Sass without having to manually recompile all the time. Here's the syntax for watching a directory full of Sass files: sass --watch app/stylesheets:public/stylesheets This will watch every Sass file in `app/stylesheets`. Whenever one of them changes, the corresponding CSS file in `public/stylesheets` will be regenerated. Any files that import that file will be regenerated, too. The syntax for watching individual files is the same: sass --watch style.sass:out.css You can also omit the output filename if you just want it to compile to name.css. For example: sass --watch style.sass This will update `style.css` whenever `style.sass` changes. You can list more than one file and/or directory, and all of them will be watched: sass --watch foo/style:public/foo bar/style:public/bar sass --watch screen.sass print.sass awful-hacks.sass:ie.css sass --watch app/stylesheets:public/stylesheets public/stylesheets/test.sass File and directory watching is accessible from Ruby, using the {Sass::Plugin::Compiler#watch Sass::Plugin#watch} function. #### Bulk Updating Another new flag for the `sass` command-line utility is `--update`. It checks a group of Sass files to see if their CSS needs to be updated, and updates if so. The syntax for `--update` is just like watch: sass --update app/stylesheets:public/stylesheets sass --update style.sass:out.css sass --watch screen.sass print.sass awful-hacks.sass:ie.css In fact, `--update` work exactly the same as `--watch`, except that it doesn't continue watching the files after the first check. ### `sass-convert` (née `css2sass`) {#3-0-0-sass-convert} The `sass-convert` tool, which used to be known as `css2sass`, has been greatly improved in various ways. It now uses a full-fledged CSS3 parser, so it should be able to handle any valid CSS3, as well as most hacks and proprietary syntax. `sass-convert` can now convert between Sass and SCSS. This is normally inferred from the filename, but it can also be specified using the `--from` and `--to` flags. For example: $ generate-sass | sass-convert --from sass --to scss | consume-scss It's also now possible to convert a file in-place -- that is, overwrite the old file with the new file. This is useful for converting files in the [Sass 2 syntax](#3-0-0-deprecations) to the new Sass 3 syntax, e.g. by doing `sass-convert --in-place --from sass2 style.sass`. #### `--recursive` The `--recursive` option allows `sass-convert` to convert an entire directory of files. `--recursive` requires both the `--from` and `--to` flags to be specified. For example: # Convert all .sass files in stylesheets/ to SCSS. # "sass2" means that these files are assumed to use the Sass 2 syntax. $ sass-convert --recursive --from sass2 --to scss stylesheets/ #### `--dasherize` The `--dasherize` options converts all underscores to hyphens, which are now allowed as part of identifiers in Sass. Note that since underscores may still be used in place of hyphens when referring to mixins and variables, this won't cause any backwards-incompatibilities. #### Convert Less to SCSS `sass-convert` can also convert [Less](http://lesscss.org) files to SCSS (or the indented syntax, although I anticipate less interest in that). For example: # Convert all .less files in the current directory into .scss files sass-convert --from less --to scss --recursive . This is done using the Less parser, so it requires that the `less` RubyGem be installed. ##### Incompatibilities Because of the reasonably substantial differences between Sass and Less, there are some things that can't be directly translated, and one feature that can't be translated at all. In the tests I've run on open-source Less stylesheets, none of these have presented issues, but it's good to be aware of them. First, Less doesn't distinguish fully between mixins and selector inheritance. In Less, all classes and some other selectors may be used as mixins, alongside more Sass-like mixins. If a class is being used as a mixin, it may also be used directly in the HTML, so it's not safe to translate it into a Sass mixin. What `sass-convert` does instead is leave the class in the stylesheet as a class, and use {file:SASS_REFERENCE.md#extend `@extend`} rather than {file:SASS_REFERENCE.md#including_a_mixin `@include`} to take on the styles of that class. Although `@extend` and mixins work quite differently, using `@extend` here doesn't actually seem to make a difference in practice. Another issue with Less mixins is that Less allows nested selectors (such as `.body .button` or `.colors > .teal`) to be used as a means of "namespacing" mixins. Sass's `@extend` doesn't work that way, so it does away with the namespacing and just extends the base class (so `.colors > .teal` becomes simply `@extend .teal`). In practice, this feature doesn't seem to be widely-used, but `sass-convert` will print a warning and leave a comment when it encounters it just in case. Finally, Less has the ability to directly access variables and property values defined in other selectors, which Sass does not support. Whenever such an accessor is used, `sass-convert` will print a warning and comment it out in the SCSS output. Like namespaced mixins, though, this does not seem to be a widely-used feature. ### `@warn` Directive A new directive `@warn` has been added that allows Sass libraries to emit warnings. This can be used to issue deprecation warnings, discourage sloppy use of mixins, etc. `@warn` takes a single argument: a SassScript expression that will be displayed on the console along with a stylesheet trace for locating the warning. For example: @mixin blue-text { @warn "The blue-text mixin is deprecated. Use new-blue-text instead."; color: #00f; } Warnings may be silenced with the new `--quiet` command line option, or the corresponding {file:SASS_REFERENCE.md#quiet-option `:quiey` Sass option}. This option will also affect warnings printed by Sass itself. Warnings are off by default in the Rails, Rack, and Merb production environments. ### Sass::Plugin API {Sass::Plugin} now has a large collection of callbacks that allow users to run code when various actions are performed. For example: Sass::Plugin.on_updating_stylesheet do |template, css| puts "#{template} has been compiled to #{css}!" end For a full list of callbacks and usage notes, see the {Sass::Plugin} documentation. {Sass::Plugin} also has a new method, {Sass::Plugin#force_update_stylesheets force_update_stylesheets}. This works just like {Sass::Plugin#update_stylesheets}, except that it doesn't check modification times and doesn't use the cache; all stylesheets are always compiled anew. ### Output Formatting Properties with a value and *also* nested properties are now rendered with the nested properties indented. For example: margin: auto top: 10px bottom: 20px is now compiled to: margin: auto; margin-top: 10px; margin-bottom: 20px; #### `:compressed` Style When the `:compressed` style is used, colors will be output as the minimal possible representation. This means whichever is smallest of the HTML4 color name and the hex representation (shortened to the three-letter version if possible). ### Stylesheet Updating Speed Several caching layers were added to Sass's stylesheet updater. This means that it should run significantly faster. This benefit will be seen by people using Sass in development mode with Rails, Rack, and Merb, as well as people using `sass --watch` from the command line, and to a lesser (but still significant) extent `sass --update`. Thanks to [thedarkone](http://github.com/thedarkone). ### Error Backtraces Numerous bugs were fixed with the backtraces given for Sass errors, especially when importing files and using mixins. All imports and mixins will now show up in the Ruby backtrace, with the proper filename and line number. In addition, when the `sass` executable encounters an error, it now prints the filename where the error occurs, as well as a backtrace of Sass imports and mixins. ### Ruby 1.9 Support * Sass and `css2sass` now produce more descriptive errors when given a template with invalid byte sequences for that template's encoding, including the line number and the offending character. * Sass and `css2sass` now accept Unicode documents with a [byte-order-mark](http://en.wikipedia.org/wiki/Byte_order_mark). ### Firebug Support A new {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} has been added that emits line-number and filename information to the CSS file in a browser-readable format. This can be used with the new [FireSass Firebug extension](https://addons.mozilla.org/en-US/firefox/addon/103988) to report the Sass filename and line number for generated CSS files. This is also available via the `--debug-info` command-line flag. ### Minor Improvements * If a CSS or Sass function is used that has the name of a color, it will now be parsed as a function rather than as a color. For example, `fuchsia(12)` now renders as `fuchsia(12)` rather than `fuchsia 12`, and `tealbang(12)` now renders as `tealbang(12)` rather than `teal bang(12)`. * The Sass Rails and Merb plugins now use Rack middleware by default. * Haml is now compatible with the [Rip](http://hellorip.com/) package management system. Thanks to [Josh Peek](http://joshpeek.com/). * Indented-syntax `/*` comments may now include `*` on lines beyond the first. * A {file:SASS_REFERENCE.md#read_cache-option `:read_cache`} option has been added to allow the Sass cache to be read from but not written to. * Stylesheets are no longer checked during each request when running tests in Rails. This should speed up some tests significantly. ## 2.2.24 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.24). * Parent references -- the `&` character -- may only be placed at the beginning of simple selector sequences in Sass 3. Placing them elsewhere is deprecated in 2.2.24 and will print a warning. For example, `foo &.bar` is allowed, but `foo .bar&` is not. ## 2.2.23 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.23). * Don't crash when `rake gems` is run in Rails with Sass installed. Thanks to [Florian Frank](http://github.com/flori). * When raising a file-not-found error, add a list of load paths that were checked. * If an import isn't found for a cached Sass file and the {file:SASS_REFERENCE.md#full_exception `:full_exception option`} is enabled, print the full exception rather than raising it. * Fix a bug with a weird interaction with Haml, DataMapper, and Rails 3 that caused some tag helpers to go into infinite recursion. ## 2.2.22 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.22). * Add a railtie so Haml and Sass will be automatically loaded in Rails 3. Thanks to [Daniel Neighman](http://pancakestacks.wordpress.com/). * Make loading the gemspec not crash on read-only filesystems like Heroku's. ## 2.2.21 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.21). * Fix a few bugs in the git-revision-reporting in {Sass::Version#version}. In particular, it will still work if `git gc` has been called recently, or if various files are missing. * Always use `__FILE__` when reading files within the Haml repo in the `Rakefile`. According to [this bug report](http://github.com/carlhuda/bundler/issues/issue/44), this should make Sass work better with Bundler. ## 2.2.20 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.20). * If the cache file for a given Sass file is corrupt because it doesn't have enough content, produce a warning and read the Sass file rather than letting the exception bubble up. This is consistent with other sorts of sassc corruption handling. * Calls to `defined?` shouldn't interfere with Rails' autoloading in very old versions (1.2.x). ## 2.2.19 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.18). There were no changes made to Sass between versions 2.2.18 and 2.2.19. ## 2.2.18 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.18). * Use `Rails.env` rather than `RAILS_ENV` when running under Rails 3.0. Thanks to [Duncan Grazier](http://duncangrazier.com/). * Support `:line_numbers` as an alias for {file:SASS_REFERENCE.md#line_numbers-option `:line_comments`}, since that's what the docs have said forever. Similarly, support `--line-numbers` as a command-line option. * Add a `--unix-newlines` flag to all executables for outputting Unix-style newlines on Windows. * Add a {file:SASS_REFERENCE.md#unix_newlines-option `:unix_newlines` option} for {Sass::Plugin} for outputting Unix-style newlines on Windows. * Fix the `--cache-location` flag, which was previously throwing errors. Thanks to [tav](http://tav.espians.com/). * Allow comments at the beginning of the document to have arbitrary indentation, just like comments elsewhere. Similarly, comment parsing is a little nicer than before. ## 2.2.17 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.16). * When the {file:SASS_REFERENCE.md#full_exception-option `:full_exception` option} is false, raise the error in Ruby code rather than swallowing it and printing something uninformative. * Fixed error-reporting when something goes wrong when loading Sass using the `sass` executable. This used to raise a NameError because `Sass::SyntaxError` wasn't defined. Now it'll raise the correct exception instead. * Report the filename in warnings about selectors without properties. * `nil` values for Sass options are now ignored, rather than raising errors. * Fix a bug that appears when Plugin template locations have multiple trailing slashes. Thanks to [Jared Grippe](http://jaredgrippe.com/). ### Must Read! * When `@import` is given a filename without an extension, the behavior of rendering a CSS `@import` if no Sass file is found is deprecated. In future versions, `@import foo` will either import the template or raise an error. ## 2.2.16 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.16). * Fixed a bug where modules containing user-defined Sass functions weren't made available when simply included in {Sass::Script::Functions} ({Sass::Script::Functions Functions} needed to be re-included in {Sass::Script::Functions::EvaluationContext Functions::EvaluationContext}). Now the module simply needs to be included in {Sass::Script::Functions}. ## 2.2.15 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.15). * Added {Sass::Script::Color#with} for a way of setting color channels that's easier than manually constructing a new color and is forwards-compatible with alpha-channel colors (to be introduced in Sass 2.4). * Added a missing require in Sass that caused crashes when it was being run standalone. ## 2.2.14 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.14). * All Sass functions now raise explicit errors if their inputs are of the incorrect type. * Allow the SassScript `rgb()` function to take percentages in addition to numerical values. * Fixed a bug where SassScript strings with `#` followed by `#{}` interpolation didn't evaluate the interpolation. ### SassScript Ruby API These changes only affect people defining their own Sass functions using {Sass::Script::Functions}. * Sass::Script::Color#value attribute is deprecated. Use {Sass::Script::Color#rgb} instead. The returned array is now frozen as well. * Add an `assert_type` function that's available to {Sass::Script::Functions}. This is useful for typechecking the inputs to functions. ### Rack Support Sass 2.2.14 includes Rack middleware for running Sass, meaning that all Rack-enabled frameworks can now use Sass. To activate this, just add require 'sass/plugin/rack' use Sass::Plugin::Rack to your `config.ru`. See the {Sass::Plugin::Rack} documentation for more details. ## 2.2.13 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.13). There were no changes made to Sass between versions 2.2.12 and 2.2.13. ## 2.2.12 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.12). * Fix a stupid bug introduced in 2.2.11 that broke the Sass Rails plugin. ## 2.2.11 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.11). * Added a note to errors on properties that could be pseudo-classes (e.g. `:focus`) indicating that they should be backslash-escaped. * Automatically interpret properties that could be pseudo-classes as such if {file:SASS_REFERENCE.md.html#property_syntax-option `:property_syntax`} is set to `:new`. * Fixed `css2sass`'s generation of pseudo-classes so that they're backslash-escaped. * Don't crash if the Haml plugin skeleton is installed and `rake gems:install` is run. * Don't use `RAILS_ROOT` directly. This no longer exists in Rails 3.0. Instead abstract this out as `Haml::Util.rails_root`. This changes makes Haml fully compatible with edge Rails as of this writing. * Make use of a Rails callback rather than a monkeypatch to check for stylesheet updates in Rails 3.0+. ## 2.2.10 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.10). * Add support for attribute selectors with spaces around the `=`. For example: a[href = http://google.com] color: blue ## 2.2.9 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.9). There were no changes made to Sass between versions 2.2.8 and 2.2.9. ## 2.2.8 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.8). There were no changes made to Sass between versions 2.2.7 and 2.2.8. ## 2.2.7 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.7). There were no changes made to Sass between versions 2.2.6 and 2.2.7. ## 2.2.6 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.6). * Don't crash when the `__FILE__` constant of a Ruby file is a relative path, as apparently happens sometimes in TextMate (thanks to [Karl Varga](http://github.com/kjvarga)). * Add "Sass" to the `--version` string for the executables. ## 2.2.5 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.5). There were no changes made to Sass between versions 2.2.4 and 2.2.5. ## 2.2.4 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.4). * Don't add `require 'rubygems'` to the top of init.rb when installed via `sass --rails`. This isn't necessary, and actually gets clobbered as soon as haml/template is loaded. * Document the previously-undocumented {file:SASS_REFERENCE.md#line-option `:line` option}, which allows the number of the first line of a Sass file to be set for error reporting. ## 2.2.3 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.3). Sass 2.2.3 prints line numbers for warnings about selectors with no properties. ## 2.2.2 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.2). Sass 2.2.2 is a minor bug-fix release. Notable changes include better parsing of mixin definitions and inclusions and better support for Ruby 1.9. ## 2.2.1 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.1). Sass 2.2.1 is a minor bug-fix release. ### Must Read! * It used to be acceptable to use `-` immediately following variable names, without any whitespace in between (for example, `!foo-!bar`). This is now deprecated, so that in the future variables with hyphens can be supported. Surround `-` with spaces. ## 2.2.0 [Tagged on GitHub](http://github.com/nex3/sass/commit/2.2.0). The 2.2 release marks a significant step in the evolution of the Sass language. The focus has been to increase the power of Sass to keep your stylesheets maintainable by allowing new forms of abstraction to be created within your stylesheets and the stylesheets provided by others that you can download and import into your own. The fundamental units of abstraction in Sass are variables and mixins. Please read below for a list of changes: ### Must Read! * Sass Comments (//) used to only comment out a single line. This was deprecated in 2.0.10 and starting in 2.2, Sass comments will comment out any lines indented under them. Upgrade to 2.0.10 in order to see deprecation warnings where this change affects you. * Implicit Strings within SassScript are now deprecated and will be removed in 2.4. For example: `border= !width solid #00F` should now be written as `border: #{!width} solid #00F` or as `border= !width "solid" #00F`. After upgrading to 2.2, you will see deprecation warnings if you have sass files that use implicit strings. ### Sass Syntax Changes #### Flexible Indentation The indentation of Sass documents is now flexible. The first indent that is detected will determine the indentation style for that document. Tabs and spaces may never be mixed, but within a document, you may choose to use tabs or a flexible number of spaces. #### Multiline Sass Comments Sass Comments (//) will now comment out whatever is indented beneath them. Previously they were single line when used at the top level of a document. Upgrading to the latest stable version will give you deprecation warnings if you have silent comments with indentation underneath them. #### Mixin Arguments Sass Mixins now accept any number of arguments. To define a mixin with arguments, specify the arguments as a comma-delimited list of variables like so: =my-mixin(!arg1, !arg2, !arg3) As before, the definition of the mixin is indented below the mixin declaration. The variables declared in the argument list may be used and will be bound to the values passed to the mixin when it is invoked. Trailing arguments may have default values as part of the declaration: =my-mixin(!arg1, !arg2 = 1px, !arg3 = blue) In the example above, the mixin may be invoked by passing 1, 2 or 3 arguments to it. A similar syntax is used to invoke a mixin that accepts arguments: div.foo +my-mixin(1em, 3px) When a mixin has no required arguments, the parenthesis are optional. The default values for mixin arguments are evaluated in the global context at the time when the mixin is invoked, they may also reference the previous arguments in the declaration. For example: !default_width = 30px =my-fancy-mixin(!width = !default_width, !height = !width) width= !width height= !height .default-box +my-fancy-mixin .square-box +my-fancy-mixin(50px) .rectangle-box +my-fancy-mixin(25px, 75px) !default_width = 10px .small-default-box +my-fancy-mixin compiles to: .default-box { width: 30px; height: 30px; } .square-box { width: 50px; height: 50px; } .rectangle-box { width: 25px; height: 75px; } .small-default-box { width: 10px; height: 10px; } ### Sass, Interactive The sass command line option -i now allows you to quickly and interactively experiment with SassScript expressions. The value of the expression you enter will be printed out after each line. Example: $ sass -i >> 5px 5px >> 5px + 10px 15px >> !five_pixels = 5px 5px >> !five_pixels + 10px 15px ### SassScript The features of SassScript have been greatly enhanced with new control directives, new fundamental data types, and variable scoping. #### New Data Types SassScript now has four fundamental data types: 1. Number 2. String 3. Boolean (New in 2.2) 4. Colors #### More Flexible Numbers Like JavaScript, SassScript numbers can now change between floating point and integers. No explicit casting or decimal syntax is required. When a number is emitted into a CSS file it will be rounded to the nearest thousandth, however the internal representation maintains much higher precision. #### Improved Handling of Units While Sass has long supported numbers with units, it now has a much deeper understanding of them. The following are examples of legal numbers in SassScript: 0, 1000, 6%, -2px, 5pc, 20em, or 2foo. Numbers of the same unit may always be added and subtracted. Numbers that have units that Sass understands and finds comparable, can be combined, taking the unit of the first number. Numbers that have non-comparable units may not be added nor subtracted -- any attempt to do so will cause an error. However, a unitless number takes on the unit of the other number during a mathematical operation. For example: >> 3mm + 4cm 43mm >> 4cm + 3mm 4.3cm >> 3cm + 2in 8.08cm >> 5foo + 6foo 11foo >> 4% + 5px SyntaxError: Incompatible units: 'px' and '%'. >> 5 + 10px 15px Sass allows compound units to be stored in any intermediate form, but will raise an error if you try to emit a compound unit into your css file. >> !em_ratio = 1em / 16px 0.063em/px >> !em_ratio * 32px 2em >> !em_ratio * 40px 2.5em #### Colors A color value can be declared using a color name, hexadecimal, shorthand hexadecimal, the rgb function, or the hsl function. When outputting a color into css, the color name is used, if any, otherwise it is emitted as hexadecimal value. Examples: > #fff white >> white white >> #FFFFFF white >> hsl(180, 100, 100) white >> rgb(255, 255, 255) white >> #AAA #aaaaaa Math on color objects is performed piecewise on the rgb components. However, these operations rarely have meaning in the design domain (mostly they make sense for gray-scale colors). >> #aaa + #123 #bbccdd >> #333 * 2 #666666 #### Booleans Boolean objects can be created by comparison operators or via the `true` and `false` keywords. Booleans can be combined using the `and`, `or`, and `not` keywords. >> true true >> true and false false >> 5 < 10 true >> not (5 < 10) false >> not (5 < 10) or not (10 < 5) true >> 30mm == 3cm true >> 1px == 1em false #### Strings Unicode escapes are now allowed within SassScript strings. ### Control Directives New directives provide branching and looping within a sass stylesheet based on SassScript expressions. See the [Sass Reference](SASS_REFERENCE.md.html#control_directives) for complete details. #### @for The `@for` directive loops over a set of numbers in sequence, defining the current number into the variable specified for each loop. The `through` keyword means that the last iteration will include the number, the `to` keyword means that it will stop just before that number. @for !x from 1px through 5px .border-#{!x} border-width= !x compiles to: .border-1px { border-width: 1px; } .border-2px { border-width: 2px; } .border-3px { border-width: 3px; } .border-4px { border-width: 4px; } .border-5px { border-width: 5px; } #### @if / @else if / @else The branching directives `@if`, `@else if`, and `@else` let you select between several branches of sass to be emitted, based on the result of a SassScript expression. Example: !type = "monster" p @if !type == "ocean" color: blue @else if !type == "matador" color: red @else if !type == "monster" color: green @else color: black is compiled to: p { color: green; } #### @while The `@while` directive lets you iterate until a condition is met. Example: !i = 6 @while !i > 0 .item-#{!i} width = 2em * !i !i = !i - 2 is compiled to: .item-6 { width: 12em; } .item-4 { width: 8em; } .item-2 { width: 4em; } ### Variable Scoping The term "constant" has been renamed to "variable." Variables can be declared at any scope (a.k.a. nesting level) and they will only be visible to the code until the next outdent. However, if a variable is already defined in a higher level scope, setting it will overwrite the value stored previously. In this code, the `!local_var` variable is scoped and hidden from other higher level scopes or sibling scopes: .foo .bar !local_var = 1px width= !local_var .baz // this will raise an undefined variable error. width= !local_var // as will this width= !local_var In this example, since the `!global_var` variable is first declared at a higher scope, it is shared among all lower scopes: !global_var = 1px .foo .bar !global_var = 2px width= !global_var .baz width= !global_var width= !global_var compiles to: .foo { width: 2px; } .foo .bar { width: 2px; } .foo .baz { width: 2px; } ### Interpolation Interpolation has been added. This allows SassScript to be used to create dynamic properties and selectors. It also cleans up some uses of dynamic values when dealing with compound properties. Using interpolation, the result of a SassScript expression can be placed anywhere: !x = 1 !d = 3 !property = "border" div.#{!property} #{!property}: #{!x + !d}px solid #{!property}-color: blue is compiled to: div.border { border: 4px solid; border-color: blue; } ### Sass Functions SassScript defines some useful functions that are called using the normal CSS function syntax: p color = hsl(0, 100%, 50%) is compiled to: #main { color: #ff0000; } The following functions are provided: `hsl`, `percentage`, `round`, `ceil`, `floor`, and `abs`. You can define additional functions in ruby. See {Sass::Script::Functions} for more information. ### New Options #### `:line_comments` To aid in debugging, You may set the `:line_comments` option to `true`. This will cause the sass engine to insert a comment before each selector saying where that selector was defined in your sass code. #### `:template_location` The {Sass::Plugin} `:template_location` option now accepts a hash of sass paths to corresponding css paths. Please be aware that it is possible to import sass files between these separate locations -- they are not isolated from each other. ### Miscellaneous Features #### `@debug` Directive The `@debug` directive accepts a SassScript expression and emits the value of that expression to the terminal (stderr). Example: @debug 1px + 2px During compilation the following will be printed: Line 1 DEBUG: 3px #### Ruby 1.9 Support Sass now fully supports Ruby 1.9.1. #### Sass Cache By default, Sass caches compiled templates and [partials](SASS_REFERENCE.md.html#partials). This dramatically speeds up re-compilation of large collections of Sass files, and works best if the Sass templates are split up into separate files that are all [`@import`](SASS_REFERENCE.md.html#import)ed into one large file. Without a framework, Sass puts the cached templates in the `.sass-cache` directory. In Rails and Merb, they go in `tmp/sass-cache`. The directory can be customized with the [`:cache_location`](#cache_location-option) option. If you don't want Sass to use caching at all, set the [`:cache`](#cache-option) option to `false`. sass-3.2.12/doc-src/SASS_REFERENCE.md000066400000000000000000002063341222366545200165720ustar00rootroot00000000000000# Sass (Syntactically Awesome StyleSheets) * Table of contents {:toc} Sass is an extension of CSS that adds power and elegance to the basic language. It allows you to use [variables](#variables_), [nested rules](#nested_rules), [mixins](#mixins), [inline imports](#import), 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). ## Features * Fully CSS3-compatible * Language extensions such as variables, nesting, and mixins * Many {Sass::Script::Functions useful functions} for manipulating colors and other values * Advanced features like [control directives](#control_directives) for libraries * Well-formatted, customizable output * [Firebug integration](https://addons.mozilla.org/en-US/firefox/addon/103988) ## Syntax There are two syntaxes available for Sass. The first, known as SCSS (Sassy CSS) and used throughout this reference, is an extension of the syntax of CSS3. This means that every valid CSS3 stylesheet is a valid SCSS file with the same meaning. In addition, SCSS understands most CSS hacks and vendor-specific syntax, such as [IE's old `filter` syntax](http://msdn.microsoft.com/en-us/library/ms533754%28VS.85%29.aspx). This syntax is enhanced with the Sass features described below. Files using this syntax have the `.scss` extension. The second and older syntax, known as the indented syntax (or sometimes just "Sass"), provides a more concise way of writing CSS. It uses indentation rather than brackets to indicate nesting of selectors, and newlines rather than semicolons to separate properties. Some people find this to be easier to read and quicker to write than SCSS. The indented syntax has all the same features, although some of them have slightly different syntax; this is described in {file:INDENTED_SYNTAX.md the indented syntax reference}. Files using this syntax have the `.sass` extension. Either syntax can [import](#import) files written in the other. Files can be automatically converted from one syntax to the other using the `sass-convert` command line tool: # Convert Sass to SCSS $ sass-convert style.sass style.scss # Convert SCSS to Sass $ sass-convert style.scss style.sass ## Using Sass Sass can be used in three ways: as a command-line tool, as a standalone Ruby module, and as a plugin for any Rack-enabled framework, including Ruby on Rails and Merb. The first step for all of these is to install the Sass gem: gem install sass If you're using Windows, you may need to [install Ruby](http://rubyinstaller.org/download.html) first. To run Sass from the command line, just use sass input.scss output.css You can also tell Sass to watch the file and update the CSS every time the Sass file changes: sass --watch input.scss:output.css If you have a directory with many Sass files, you can also tell Sass to watch the entire directory: sass --watch app/sass:public/stylesheets Use `sass --help` for full documentation. Using Sass in Ruby code is very simple. After installing the Sass gem, you can use it by running `require "sass"` and using {Sass::Engine} like so: engine = Sass::Engine.new("#main {background-color: #0000ff}", :syntax => :scss) engine.render #=> "#main { background-color: #0000ff; }\n" ### Rack/Rails/Merb Plugin To enable Sass in Rails versions before Rails 3, add the following line to `environment.rb`: config.gem "sass" For Rails 3, instead add the following line to the Gemfile: gem "sass" To enable Sass in Merb, add the following line to `config/dependencies.rb`: dependency "merb-haml" To enable Sass in a Rack application, add require 'sass/plugin/rack' use Sass::Plugin::Rack to `config.ru`. Sass stylesheets don't work the same as views. They don't contain dynamic content, so the CSS only needs to be generated when the Sass file has been updated. By default, `.sass` and `.scss` files are placed in public/stylesheets/sass (this can be customized with the [`:template_location`](#template_location-option) option). Then, whenever necessary, they're compiled into corresponding CSS files in public/stylesheets. For instance, public/stylesheets/sass/main.scss would be compiled to public/stylesheets/main.css. ### Caching By default, Sass caches compiled templates and [partials](#partials). This dramatically speeds up re-compilation of large collections of Sass files, and works best if the Sass templates are split up into separate files that are all [`@import`](#import)ed into one large file. Without a framework, Sass puts the cached templates in the `.sass-cache` directory. In Rails and Merb, they go in `tmp/sass-cache`. The directory can be customized with the [`:cache_location`](#cache_location-option) option. If you don't want Sass to use caching at all, set the [`:cache`](#cache-option) option to `false`. ### Options Options can be set by setting the {Sass::Plugin::Configuration#options Sass::Plugin#options} hash in `environment.rb` in Rails or `config.ru` in Rack... Sass::Plugin.options[:style] = :compact ...or by setting the `Merb::Plugin.config[:sass]` hash in `init.rb` in Merb... Merb::Plugin.config[:sass][:style] = :compact ...or by passing an options hash to {Sass::Engine#initialize}. All relevant options are also available via flags to the `sass` and `scss` command-line executables. Available options are: {#style-option} `:style` : Sets the style of the CSS output. See [Output Style](#output_style). {#syntax-option} `:syntax` : The syntax of the input file, `:sass` for the indented syntax and `:scss` for the CSS-extension syntax. This is only useful when you're constructing {Sass::Engine} instances yourself; it's automatically set properly when using {Sass::Plugin}. Defaults to `:sass`. {#property_syntax-option} `:property_syntax` : Forces indented-syntax documents to use one syntax for properties. If the correct syntax isn't used, an error is thrown. `:new` forces the use of a colon after the property name. For example: `color: #0f3` or `width: $main_width`. `:old` forces the use of a colon before the property name. For example: `:color #0f3` or `:width $main_width`. By default, either syntax is valid. This has no effect on SCSS documents. {#cache-option} `:cache` : Whether parsed Sass files should be cached, allowing greater speed. Defaults to true. {#read_cache-option} `:read_cache` : If this is set and `:cache` is not, only read the Sass cache if it exists, don't write to it if it doesn't. {#cache_store-option} `:cache_store` : If this is set to an instance of a subclass of {Sass::CacheStores::Base}, that cache store will be used to store and retrieve cached compilation results. Defaults to a {Sass::CacheStores::Filesystem} that is initialized using the [`:cache_location` option](#cache_location-option). {#never_update-option} `:never_update` : Whether the CSS files should never be updated, even if the template file changes. Setting this to true may give small performance gains. It always defaults to false. Only has meaning within Rack, Ruby on Rails, or Merb. {#always_update-option} `:always_update` : Whether the CSS files should be updated every time a controller is accessed, as opposed to only when the template has been modified. Defaults to false. Only has meaning within Rack, Ruby on Rails, or Merb. {#always_check-option} `:always_check` : Whether a Sass template should be checked for updates every time a controller is accessed, as opposed to only when the server starts. If a Sass template has been updated, it will be recompiled and will overwrite the corresponding CSS file. Defaults to false in production mode, true otherwise. Only has meaning within Rack, Ruby on Rails, or Merb. {#poll-option} `:poll` : When true, always use the polling backend for {Sass::Plugin::Compiler#watch} rather than the native filesystem backend. {#full_exception-option} `:full_exception` : Whether an error in the Sass code should cause Sass to provide a detailed description within the generated CSS file. If set to true, the error will be displayed along with a line number and source snippet both as a comment in the CSS file and at the top of the page (in supported browsers). Otherwise, an exception will be raised in the Ruby code. Defaults to false in production mode, true otherwise. Only has meaning within Rack, Ruby on Rails, or Merb. {#template_location-option} `:template_location` : A path to the root sass template directory for your application. If a hash, `:css_location` is ignored and this option designates a mapping between input and output directories. May also be given a list of 2-element lists, instead of a hash. Defaults to `css_location + "/sass"`. Only has meaning within Rack, Ruby on Rails, or Merb. Note that if multiple template locations are specified, all of them are placed in the import path, allowing you to import between them. **Note that due to the many possible formats it can take, this option should only be set directly, not accessed or modified. Use the {Sass::Plugin::Configuration#template_location_array Sass::Plugin#template_location_array}, {Sass::Plugin::Configuration#add_template_location Sass::Plugin#add_template_location}, and {Sass::Plugin::Configuration#remove_template_location Sass::Plugin#remove_template_location} methods instead**. {#css_location-option} `:css_location` : The path where CSS output should be written to. This option is ignored when `:template_location` is a Hash. Defaults to `"./public/stylesheets"`. Only has meaning within Rack, Ruby on Rails, or Merb. {#cache_location-option} `:cache_location` : The path where the cached `sassc` files should be written to. Defaults to `"./tmp/sass-cache"` in Rails and Merb, or `"./.sass-cache"` otherwise. If the [`:cache_store` option](#cache_location-option) is set, this is ignored. {#unix_newlines-option} `:unix_newlines` : If true, use Unix-style newlines when writing files. Only has meaning on Windows, and only when Sass is writing the files (in Rack, Rails, or Merb, when using {Sass::Plugin} directly, or when using the command-line executable). {#filename-option} `:filename` : The filename of the file being rendered. This is used solely for reporting errors, and is automatically set when using Rack, Rails, or Merb. {#line-option} `:line` : The number of the first line of the Sass template. Used for reporting line numbers for errors. This is useful to set if the Sass template is embedded in a Ruby file. {#load_paths-option} `:load_paths` : An array of filesystem paths or importers which should be searched for Sass templates imported with the [`@import`](#import) directive. These may be strings, `Pathname` objects, or subclasses of {Sass::Importers::Base}. This defaults to the working directory and, in Rack, Rails, or Merb, whatever `:template_location` is. The load path is also informed by {Sass.load_paths} and the `SASS_PATH` environment variable. {#filesystem_importer-option} `:filesystem_importer` : A {Sass::Importers::Base} subclass used to handle plain string load paths. This should import files from the filesystem. It should be a Class object inheriting from {Sass::Importers::Base} with a constructor that takes a single string argument (the load path). Defaults to {Sass::Importers::Filesystem}. {#line_numbers-option} `:line_numbers` : When set to true, causes the line number and file where a selector is defined to be emitted into the compiled CSS as a comment. Useful for debugging, especially when using imports and mixins. This option may also be called `:line_comments`. Automatically disabled when using the `:compressed` output style or the `:debug_info`/`:trace_selectors` options. {#trace_selectors-option} `:trace_selectors` : When set to true, emit a full trace of imports and mixins before each selector. This can be helpful for in-browser debugging of stylesheet imports and mixin includes. This option supersedes the `:line_comments` option and is superseded by the `:debug_info` option. Automatically disabled when using the `:compressed` output style. {#debug_info-option} `:debug_info` : When set to true, causes the line number and file where a selector is defined to be emitted into the compiled CSS in a format that can be understood by the browser. Useful in conjunction with [the FireSass Firebug extension](https://addons.mozilla.org/en-US/firefox/addon/103988) for displaying the Sass filename and line number. Automatically disabled when using the `:compressed` output style. {#custom-option} `:custom` : An option that's available for individual applications to set to make data available to {Sass::Script::Functions custom Sass functions}. {#quiet-option} `:quiet` : When set to true, causes warnings to be disabled. ### Syntax Selection The Sass command-line tool will use the file extension to determine which syntax you are using, but there's not always a filename. The `sass` command-line program defaults to the indented syntax but you can pass the `--scss` option to it if the input should be interpreted as SCSS syntax. Alternatively, you can use the `scss` command-line program which is exactly like the `sass` program but it defaults to assuming the syntax is SCSS. ### Encodings When running on Ruby 1.9 and later, Sass is aware of the character encoding of documents. By default, Sass assumes that all stylesheets are encoded using whatever coding system your operating system defaults to. For many users this will be `UTF-8`, the de facto standard for the web. For some users, though, it may be a more local encoding. If you want to use a different encoding for your stylesheet than your operating system default, you can use the `@charset` declaration just like in CSS. Add `@charset "encoding-name";` at the beginning of the stylesheet (before any whitespace or comments) and Sass will interpret it as the given encoding. Note that whatever encoding you use, it must be convertible to Unicode. Sass will also respect any Unicode BOMs and non-ASCII-compatible Unicode encodings [as specified by the CSS spec](http://www.w3.org/TR/CSS2/syndata.html#charset), although this is *not* the recommended way to specify the character set for a document. Note that Sass does not support the obscure `UTF-32-2143`, `UTF-32-3412`, `EBCDIC`, `IBM1026`, and `GSM 03.38` encodings, since Ruby does not have support for them and they're highly unlikely to ever be used in practice. #### Output Encoding In general, Sass will try to encode the output stylesheet using the same encoding as the input stylesheet. In order for it to do this, though, the input stylesheet must have a `@charset` declaration; otherwise, Sass will default to encoding the output stylesheet as `UTF-8`. In addition, it will add a `@charset` declaration to the output if it's not plain ASCII. When other stylesheets with `@charset` declarations are `@import`ed, Sass will convert them to the same encoding as the main stylesheet. Note that Ruby 1.8 does not have good support for character encodings, and so Sass behaves somewhat differently when running under it than under Ruby 1.9 and later. In Ruby 1.8, Sass simply uses the first `@charset` declaration in the stylesheet or any of the other stylesheets it `@import`s. ## CSS Extensions ### Nested Rules Sass allows CSS rules to be nested within one another. The inner rule then only applies within the outer rule's selector. For example: #main p { color: #00ff00; width: 97%; .redbox { background-color: #ff0000; color: #000000; } } is compiled to: #main p { color: #00ff00; width: 97%; } #main p .redbox { background-color: #ff0000; color: #000000; } This helps avoid repetition of parent selectors, and makes complex CSS layouts with lots of nested selectors much simpler. For example: #main { width: 97%; p, div { font-size: 2em; a { font-weight: bold; } } pre { font-size: 3em; } } is compiled to: #main { width: 97%; } #main p, #main div { font-size: 2em; } #main p a, #main div a { font-weight: bold; } #main pre { font-size: 3em; } ### Referencing Parent Selectors: `&` Sometimes it's useful to use a nested rule's parent selector in other ways than the default. For instance, you might want to have special styles for when that selector is hovered over or for when the body element has a certain class. In these cases, you can explicitly specify where the parent selector should be inserted using the `&` character. For example: a { font-weight: bold; text-decoration: none; &:hover { text-decoration: underline; } body.firefox & { font-weight: normal; } } is compiled to: a { font-weight: bold; text-decoration: none; } a:hover { text-decoration: underline; } body.firefox a { font-weight: normal; } `&` will be replaced with the parent selector as it appears in the CSS. This means that if you have a deeply nested rule, the parent selector will be fully resolved before the `&` is replaced. For example: #main { color: black; a { font-weight: bold; &:hover { color: red; } } } is compiled to: #main { color: black; } #main a { font-weight: bold; } #main a:hover { color: red; } ### Nested Properties CSS has quite a few properties that are in "namespaces;" for instance, `font-family`, `font-size`, and `font-weight` are all in the `font` namespace. In CSS, if you want to set a bunch of properties in the same namespace, you have to type it out each time. Sass provides a shortcut for this: just write the namespace once, then nest each of the sub-properties within it. For example: .funky { font: { family: fantasy; size: 30em; weight: bold; } } is compiled to: .funky { font-family: fantasy; font-size: 30em; font-weight: bold; } The property namespace itself can also have a value. For example: .funky { font: 2px/3px { family: fantasy; size: 30em; weight: bold; } } is compiled to: .funky { font: 2px/3px; font-family: fantasy; font-size: 30em; font-weight: bold; } ### Placeholder Selectors: `%foo` Sass supports a special type of selector called a "placeholder selector". These look like class and id selectors, except the `#` or `.` is replaced by `%`. They're meant to be used with the [`@extend` directive](#extend); for more information see [`@extend`-Only Selectors](#placeholders). On their own, without any use of `@extend`, rulesets that use placeholder selectors will not be rendered to CSS. ## Comments: `/* */` and `//` {#comments} Sass supports standard multiline CSS comments with `/* */`, as well as single-line comments with `//`. The multiline comments are preserved in the CSS output where possible, while the single-line comments are removed. For example: /* This comment is * several lines long. * since it uses the CSS comment syntax, * it will appear in the CSS output. */ body { color: black; } // These comments are only one line long each. // They won't appear in the CSS output, // since they use the single-line comment syntax. a { color: green; } is compiled to: /* This comment is * several lines long. * since it uses the CSS comment syntax, * it will appear in the CSS output. */ body { color: black; } a { color: green; } When the first letter of a comment is `!`, the comment will be interpolated and always rendered into css output even in compressed output modes. This is useful for adding Copyright notices to your generated CSS. ## SassScript {#sassscript} In addition to the plain CSS property syntax, Sass supports a small set of extensions called SassScript. SassScript allows properties to use variables, arithmetic, and extra functions. SassScript can be used in any property value. SassScript can also be used to generate selectors and property names, which is useful when writing [mixins](#mixins). This is done via [interpolation](#interpolation_). ### Interactive Shell You can easily experiment with SassScript using the interactive shell. To launch the shell run the sass command-line with the `-i` option. At the prompt, enter any legal SassScript expression to have it evaluated and the result printed out for you: $ sass -i >> "Hello, Sassy World!" "Hello, Sassy World!" >> 1px + 1px + 1px 3px >> #777 + #777 #eeeeee >> #777 + #888 white ### Variables: `$` {#variables_} The most straightforward way to use SassScript is to use variables. Variables begin with dollar signs, and are set like CSS properties: $width: 5em; You can then refer to them in properties: #main { width: $width; } Variables are only available within the level of nested selectors where they're defined. If they're defined outside of any nested selectors, they're available everywhere. Variables used to use the prefix character `!`; this still works, but it's deprecated and prints a warning. `$` is the recommended syntax. Variables also used to be defined with `=` rather than `:`; this still works, but it's deprecated and prints a warning. `:` is the recommended syntax. ### Data Types SassScript supports six main data types: * numbers (e.g. `1.2`, `13`, `10px`) * strings of text, with and without quotes (e.g. `"foo"`, `'bar'`, `baz`) * colors (e.g. `blue`, `#04a3f9`, `rgba(255, 0, 0, 0.5)`) * booleans (e.g. `true`, `false`) * nulls (e.g. `null`) * lists of values, separated by spaces or commas (e.g. `1.5em 1em 0 2em`, `Helvetica, Arial, sans-serif`) SassScript also supports all other types of CSS property value, such as Unicode ranges and `!important` declarations. However, it has no special handling for these types. They're treated just like unquoted strings. #### Strings {#sass-script-strings} CSS specifies two kinds of strings: those with quotes, such as `"Lucida Grande"` or `'http://sass-lang.com'`, and those without quotes, such as `sans-serif` or `bold`. SassScript recognizes both kinds, and in general if one kind of string is used in the Sass document, that kind of string will be used in the resulting CSS. There is one exception to this, though: when using [`#{}` interpolation](#interpolation_), quoted strings are unquoted. This makes it easier to use e.g. selector names in [mixins](#mixins). For example: @mixin firefox-message($selector) { body.firefox #{$selector}:before { content: "Hi, Firefox users!"; } } @include firefox-message(".header"); is compiled to: body.firefox .header:before { content: "Hi, Firefox users!"; } It's also worth noting that when using the [deprecated `=` property syntax](#sassscript), all strings are interpreted as unquoted, regardless of whether or not they're written with quotes. #### Lists Lists are how Sass represents the values of CSS declarations like `margin: 10px 15px 0 0` or `font-face: Helvetica, Arial, sans-serif`. Lists are just a series of other values, separated by either spaces or commas. In fact, individual values count as lists, too: they're just lists with one item. On their own, lists don't do much, but the [Sass list functions](Sass/Script/Functions.html#list-functions) make them useful. The {Sass::Script::Functions#nth nth function} can access items in a list, the {Sass::Script::Functions#join join function} can join multiple lists together, and the {Sass::Script::Functions#append append function} can add items to lists. The [`@each` rule](#each-directive) can also add styles for each item in a list. In addition to containing simple values, lists can contain other lists. For example, `1px 2px, 5px 6px` is a two-item list containing the list `1px 2px` and the list `5px 6px`. If the inner lists have the same separator as the outer list, you'll need to use parentheses to make it clear where the inner lists start and stop. For example, `(1px 2px) (5px 6px)` is also a two-item list containing the list `1px 2px` and the list `5px 6px`. The difference is that the outer list is space-separated, where before it was comma-separated. When lists are turned into plain CSS, Sass doesn't add any parentheses, since CSS doesn't understand them. That means that `(1px 2px) (5px 6px)` and `1px 2px 5px 6px` will look the same when they become CSS. However, they aren't the same when they're Sass: the first is a list containing two lists, while the second is a list containing four numbers. Lists can also have no items in them at all. These lists are represented as `()`. They can't be output directly to CSS; if you try to do e.g. `font-family: ()`, Sass will raise an error. If a list contains empty lists or null values, as in `1px 2px () 3px` or `1px 2px null 3px`, the empty lists and null values will be removed before the containing list is turned into CSS. ### Operations All types support equality operations (`==` and `!=`). In addition, each type has its own operations that it has special support for. #### Number Operations SassScript supports the standard arithmetic operations on numbers (addition `+`, subtraction `-`, multiplication `*`, division `/`, and modulo `%`), and will automatically convert between units if it can: p { width: 1in + 8pt; } is compiled to: p { width: 1.111in; } Relational operators (`<`, `>`, `<=`, `>=`) are also supported for numbers, and equality operators (`==`, `!=`) are supported for all types. ##### Division and `/` {#division-and-slash} CSS allows `/` to appear in property values as a way of separating numbers. Since SassScript is an extension of the CSS property syntax, it must support this, while also allowing `/` to be used for division. This means that by default, if two numbers are separated by `/` in SassScript, then they will appear that way in the resulting CSS. However, there are three situations where the `/` will be interpreted as division. These cover the vast majority of cases where division is actually used. They are: 1. If the value, or any part of it, is stored in a variable. 2. If the value is surrounded by parentheses. 3. If the value is used as part of another arithmetic expression. For example: p { font: 10px/8px; // Plain CSS, no division $width: 1000px; width: $width/2; // Uses a variable, does division height: (500px/2); // Uses parentheses, does division margin-left: 5px + 8px/2px; // Uses +, does division } is compiled to: p { font: 10px/8px; width: 500px; height: 250px; margin-left: 9px; } If you want to use variables along with a plain CSS `/`, you can use `#{}` to insert them. For example: p { $font-size: 12px; $line-height: 30px; font: #{$font-size}/#{$line-height}; } is compiled to: p { font: 12px/30px; } #### Color Operations All arithmetic operations are supported for color values, where they work piecewise. This means that the operation is performed on the red, green, and blue components in turn. For example: p { color: #010203 + #040506; } computes `01 + 04 = 05`, `02 + 05 = 07`, and `03 + 06 = 09`, and is compiled to: p { color: #050709; } Often it's more useful to use {Sass::Script::Functions color functions} than to try to use color arithmetic to achieve the same effect. Arithmetic operations also work between numbers and colors, also piecewise. For example: p { color: #010203 * 2; } computes `01 * 2 = 02`, `02 * 2 = 04`, and `03 * 2 = 06`, and is compiled to: p { color: #020406; } Note that colors with an alpha channel (those created with the {Sass::Script::Functions#rgba rgba} or {Sass::Script::Functions#hsla hsla} functions) must have the same alpha value in order for color arithmetic to be done with them. The arithmetic doesn't affect the alpha value. For example: p { color: rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75); } is compiled to: p { color: rgba(255, 255, 0, 0.75); } The alpha channel of a color can be adjusted using the {Sass::Script::Functions#opacify opacify} and {Sass::Script::Functions#transparentize transparentize} functions. For example: $translucent-red: rgba(255, 0, 0, 0.5); p { color: opacify($translucent-red, 0.3); background-color: transparentize($translucent-red, 0.25); } is compiled to: p { color: rgba(255, 0, 0, 0.9); background-color: rgba(255, 0, 0, 0.25); } IE filters require all colors include the alpha layer, and be in the strict format of #AABBCCDD. You can more easily convert the color using the {Sass::Script::Functions#ie_hex_str ie_hex_str} function. For example: $translucent-red: rgba(255, 0, 0, 0.5); $green: #00ff00; div { filter: progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#{ie-hex-str($green)}', endColorstr='#{ie-hex-str($translucent-red)}'); } is compiled to: div { filter: progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr=#FF00FF00, endColorstr=#80FF0000); } #### String Operations The `+` operation can be used to concatenate strings: p { cursor: e + -resize; } is compiled to: p { cursor: e-resize; } Note that if a quoted string is added to an unquoted string (that is, the quoted string is to the left of the `+`), the result is a quoted string. Likewise, if an unquoted string is added to a quoted string (the unquoted string is to the left of the `+`), the result is an unquoted string. For example: p:before { content: "Foo " + Bar; font-family: sans- + "serif"; } is compiled to: p:before { content: "Foo Bar"; font-family: sans-serif; } By default, if two values are placed next to one another, they are concatenated with a space: p { margin: 3px + 4px auto; } is compiled to: p { margin: 7px auto; } Within a string of text, #{} style interpolation can be used to place dynamic values within the string: p:before { content: "I ate #{5 + 10} pies!"; } is compiled to: p:before { content: "I ate 15 pies!"; } Null values are treated as empty strings for string interpolation: $value: null; p:before { content: "I ate #{$value} pies!"; } is compiled to: p:before { content: "I ate pies!"; } #### Boolean Operations SassScript supports `and`, `or`, and `not` operators for boolean values. #### List Operations Lists don't support any special operations. Instead, they're manipulated using the [list functions](Sass/Script/Functions.html#list-functions). ### Parentheses Parentheses can be used to affect the order of operations: p { width: (1em + 2em) * 3; } is compiled to: p { width: 9em; } ### Functions SassScript defines some useful functions that are called using the normal CSS function syntax: p { color: hsl(0, 100%, 50%); } is compiled to: p { color: #ff0000; } #### Keyword Arguments Sass functions can also be called using explicit keyword arguments. The above example can also be written as: p { color: hsl($hue: 0, $saturation: 100%, $lightness: 50%); } While this is less concise, it can make the stylesheet easier to read. It also allows functions to present more flexible interfaces, providing many arguments without becoming difficult to call. Named arguments can be passed in any order, and arguments with default values can be omitted. Since the named arguments are variable names, underscores and dashes can be used interchangeably. See {Sass::Script::Functions} for a full listing of Sass functions and their argument names, as well as instructions on defining your own in Ruby. ### Interpolation: `#{}` {#interpolation_} You can also use SassScript variables in selectors and property names using #{} interpolation syntax: $name: foo; $attr: border; p.#{$name} { #{$attr}-color: blue; } is compiled to: p.foo { border-color: blue; } It's also possible to use `#{}` to put SassScript into property values. In most cases this isn't any better than using a variable, but using `#{}` does mean that any operations near it will be treated as plain CSS. For example: p { $font-size: 12px; $line-height: 30px; font: #{$font-size}/#{$line-height}; } is compiled to: p { font: 12px/30px; } ### Variable Defaults: `!default` You can assign to variables if they aren't already assigned by adding the `!default` flag to the end of the value. This means that if the variable has already been assigned to, it won't be re-assigned, but if it doesn't have a value yet, it will be given one. For example: $content: "First content"; $content: "Second content?" !default; $new_content: "First time reference" !default; #main { content: $content; new-content: $new_content; } is compiled to: #main { content: "First content"; new-content: "First time reference"; } Variables with `null` values are treated as unassigned by !default: $content: null; $content: "Non-null content" !default; #main { content: $content; } is compiled to: #main { content: "Non-null content"; } ## `@`-Rules and Directives {#directives} Sass supports all CSS3 `@`-rules, as well as some additional Sass-specific ones known as "directives." These have various effects in Sass, detailed below. See also [control directives](#control_directives) and [mixin directives](#mixins). ### `@import` {#import} Sass extends the CSS `@import` rule to allow it to import SCSS and Sass files. All imported SCSS and Sass files will be merged together into a single CSS output file. In addition, any variables or [mixins](#mixins) defined in imported files can be used in the main file. Sass looks for other Sass files in the current directory, and the Sass file directory under Rack, Rails, or Merb. Additional search directories may be specified using the [`:load_paths`](#load_paths-option) option, or the `--load-path` option on the command line. `@import` takes a filename to import. By default, it looks for a Sass file to import directly, but there are a few circumstances under which it will compile to a CSS `@import` rule: * If the file's extension is `.css`. * If the filename begins with `http://`. * If the filename is a `url()`. * If the `@import` has any media queries. If none of the above conditions are met and the extension is `.scss` or `.sass`, then the named Sass or SCSS file will be imported. If there is no extension, Sass will try to find a file with that name and the `.scss` or `.sass` extension and import it. For example, @import "foo.scss"; or @import "foo"; would both import the file `foo.scss`, whereas @import "foo.css"; @import "foo" screen; @import "http://foo.com/bar"; @import url(foo); would all compile to @import "foo.css"; @import "foo" screen; @import "http://foo.com/bar"; @import url(foo); It's also possible to import multiple files in one `@import`. For example: @import "rounded-corners", "text-shadow"; would import both the `rounded-corners` and the `text-shadow` files. Imports may contain `#{}` interpolation, but only with certain restrictions. It's not possible to dynamically import a Sass file based on a variable; interpolation is only for CSS imports. As such, it only works with `url()` imports. For example: $family: unquote("Droid+Sans"); @import url("http://fonts.googleapis.com/css?family=#{$family}"); would compile to @import url("http://fonts.googleapis.com/css?family=Droid+Sans"); #### Partials {#partials} If you have a SCSS or Sass file that you want to import but don't want to compile to a CSS file, you can add an underscore to the beginning of the filename. This will tell Sass not to compile it to a normal CSS file. You can then import these files without using the underscore. For example, you might have `_colors.scss`. Then no `_colors.css` file would be created, and you can do @import "colors"; and `_colors.scss` would be imported. Note that you may not include a partial and a non-partial with the same name in the same directory. For example, `_colors.scss` may not exist alongside `colors.scss`. #### Nested `@import` {#nested-import} Although most of the time it's most useful to just have `@import`s at the top level of the document, it is possible to include them within CSS rules and `@media` rules. Like a base-level `@import`, this includes the contents of the `@import`ed file. However, the imported rules will be nested in the same place as the original `@import`. For example, if `example.scss` contains .example { color: red; } then #main { @import "example"; } would compile to #main .example { color: red; } Directives that are only allowed at the base level of a document, like `@mixin` or `@charset`, are not allowed in files that are `@import`ed in a nested context. It's not possible to nest `@import` within mixins or control directives. ### `@media` {#media} `@media` directives in Sass behave just like they do in plain CSS, with one extra capability: they can be nested in CSS rules. If a `@media` directive appears within a CSS rule, it will be bubbled up to the top level of the stylesheet, putting all the selectors on the way inside the rule. This makes it easy to add media-specific styles without having to repeat selectors or break the flow of the stylesheet. For example: .sidebar { width: 300px; @media screen and (orientation: landscape) { width: 500px; } } is compiled to: .sidebar { width: 300px; } @media screen and (orientation: landscape) { .sidebar { width: 500px; } } `@media` queries can also be nested within one another. The queries will then be combined using the `and` operator. For example: @media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } } is compiled to: @media screen and (orientation: landscape) { .sidebar { width: 500px; } } Finally, `@media` queries can contain SassScript expressions (including variables, functions, and operators) in place of the feature names and feature values. For example: $media: screen; $feature: -webkit-min-device-pixel-ratio; $value: 1.5; @media #{$media} and ($feature: $value) { .sidebar { width: 500px; } } is compiled to: @media screen and (-webkit-min-device-pixel-ratio: 1.5) { .sidebar { width: 500px; } } ### `@extend` {#extend} There are often cases when designing a page when one class should have all the styles of another class, as well as its own specific styles. The most common way of handling this is to use both the more general class and the more specific class in the HTML. For example, suppose we have a design for a normal error and also for a serious error. We might write our markup like so:
Oh no! You've been hacked!
And our styles like so: .error { border: 1px #f00; background-color: #fdd; } .seriousError { border-width: 3px; } Unfortunately, this means that we have to always remember to use `.error` with `.seriousError`. This is a maintenance burden, leads to tricky bugs, and can bring non-semantic style concerns into the markup. The `@extend` directive avoids these problems by telling Sass that one selector should inherit the styles of another selector. For example: .error { border: 1px #f00; background-color: #fdd; } .seriousError { @extend .error; border-width: 3px; } is compiled to: .error, .seriousError { border: 1px #f00; background-color: #fdd; } .seriousError { border-width: 3px; } This means that all styles defined for `.error` are also applied to `.seriousError`, in addition to the styles specific to `.seriousError`. In effect, every element with class `.seriousError` also has class `.error`. Other rules that use `.error` will work for `.seriousError` as well. For example, if we have special styles for errors caused by hackers: .error.intrusion { background-image: url("/image/hacked.png"); } Then `
` will have the `hacked.png` background image as well. #### How it Works `@extend` works by inserting the extending selector (e.g. `.seriousError`) anywhere in the stylesheet that the extended selector (.e.g `.error`) appears. Thus the example above: .error { border: 1px #f00; background-color: #fdd; } .error.intrusion { background-image: url("/image/hacked.png"); } .seriousError { @extend .error; border-width: 3px; } is compiled to: .error, .seriousError { border: 1px #f00; background-color: #fdd; } .error.intrusion, .seriousError.intrusion { background-image: url("/image/hacked.png"); } .seriousError { border-width: 3px; } When merging selectors, `@extend` is smart enough to avoid unnecessary duplication, so something like `.seriousError.seriousError` gets translated to `.seriousError`. In addition, it won't produce selectors that can't match anything, like `#main#footer`. #### Extending Complex Selectors Class selectors aren't the only things that can be extended. It's possible to extend any selector involving only a single element, such as `.special.cool`, `a:hover`, or `a.user[href^="http://"]`. For example: .hoverlink { @extend a:hover; } Just like with classes, this means that all styles defined for `a:hover` are also applied to `.hoverlink`. For example: .hoverlink { @extend a:hover; } a:hover { text-decoration: underline; } is compiled to: a:hover, .hoverlink { text-decoration: underline; } Just like with `.error.intrusion` above, any rule that uses `a:hover` will also work for `.hoverlink`, even if they have other selectors as well. For example: .hoverlink { @extend a:hover; } .comment a.user:hover { font-weight: bold; } is compiled to: .comment a.user:hover, .comment .user.hoverlink { font-weight: bold; } #### Multiple Extends A single selector can extend more than one selector. This means that it inherits the styles of all the extended selectors. For example: .error { border: 1px #f00; background-color: #fdd; } .attention { font-size: 3em; background-color: #ff0; } .seriousError { @extend .error; @extend .attention; border-width: 3px; } is compiled to: .error, .seriousError { border: 1px #f00; background-color: #fdd; } .attention, .seriousError { font-size: 3em; background-color: #ff0; } .seriousError { border-width: 3px; } In effect, every element with class `.seriousError` also has class `.error` *and* class `.attention`. Thus, the styles defined later in the document take precedence: `.seriousError` has background color `#ff0` rather than `#fdd`, since `.attention` is defined later than `.error`. Multiple extends can also be written using a comma-separated list of selectors. For example, `@extend .error, .attention` is the same as `@extend .error; @extend.attention`. #### Chaining Extends It's possible for one selector to extend another selector that in turn extends a third. For example: .error { border: 1px #f00; background-color: #fdd; } .seriousError { @extend .error; border-width: 3px; } .criticalError { @extend .seriousError; position: fixed; top: 10%; bottom: 10%; left: 10%; right: 10%; } Now everything with class `.seriousError` also has class `.error`, and everything with class `.criticalError` has class `.seriousError` *and* class `.error`. It's compiled to: .error, .seriousError, .criticalError { border: 1px #f00; background-color: #fdd; } .seriousError, .criticalError { border-width: 3px; } .criticalError { position: fixed; top: 10%; bottom: 10%; left: 10%; right: 10%; } #### Selector Sequences Selector sequences, such as `.foo .bar` or `.foo + .bar`, currently can't be extended. However, it is possible for nested selectors themselves to use `@extend`. For example: #fake-links .link { @extend a; } a { color: blue; &:hover { text-decoration: underline; } } is compiled to a, #fake-links .link { color: blue; } a:hover, #fake-links .link:hover { text-decoration: underline; } ##### Merging Selector Sequences Sometimes a selector sequence extends another selector that appears in another sequence. In this case, the two sequences need to be merged. For example: #admin .tabbar a { font-weight: bold; } #demo .overview .fakelink { @extend a; } While it would technically be possible to generate all selectors that could possibly match either sequence, this would make the stylesheet far too large. The simple example above, for instance, would require ten selectors. Instead, Sass generates only selectors that are likely to be useful. When the two sequences being merged have no selectors in common, then two new selectors are generated: one with the first sequence before the second, and one with the second sequence before the first. For example: #admin .tabbar a { font-weight: bold; } #demo .overview .fakelink { @extend a; } is compiled to: #admin .tabbar a, #admin .tabbar #demo .overview .fakelink, #demo .overview #admin .tabbar .fakelink { font-weight: bold; } If the two sequences do share some selectors, then those selectors will be merged together and only the differences (if any still exist) will alternate. In this example, both sequences contain the id `#admin`, so the resulting selectors will merge those two ids: #admin .tabbar a { font-weight: bold; } #admin .overview .fakelink { @extend a; } This is compiled to: #admin .tabbar a, #admin .tabbar .overview .fakelink, #admin .overview .tabbar .fakelink { font-weight: bold; } #### `@extend`-Only Selectors {#placeholders} Sometimes you'll write styles for a class that you only ever want to `@extend`, and never want to use directly in your HTML. This is especially true when writing a Sass library, where you may provide styles for users to `@extend` if they need and ignore if they don't. If you use normal classes for this, you end up creating a lot of extra CSS when the stylesheets are generated, and run the risk of colliding with other classes that are being used in the HTML. That's why Sass supports "placeholder selectors" (for example, `%foo`). Placeholder selectors look like class and id selectors, except the `#` or `.` is replaced by `%`. They can be used anywhere a class or id could, and on their own they prevent rulesets from being rendered to CSS. For example: // This ruleset won't be rendered on its own. #context a%extreme { color: blue; font-weight: bold; font-size: 2em; } However, placeholder selectors can be extended, just like classes and ids. The extended selectors will be generated, but the base placeholder selector will not. For example: .notice { @extend %extreme; } Is compiled to: #context a.notice { color: blue; font-weight: bold; font-size: 2em; } #### The `!optional` Flag Normally when you extend a selector, it's an error if that `@extend` doesn't work. For example, if you write `a.important {@extend .notice}`, it's an error if there are no selectors that contain `.notice`. It's also an error if the only selector containing `.notice` is `h1.notice`, since `h1` conflicts with `a` and so no new selector would be generated. Sometimes, though, you want to allow an `@extend` not to produce any new selectors. To do so, just add the `!optional` flag after the selector. For example: a.important { @extend .notice !optional; } #### `@extend` in Directives There are some restrictions on the use of `@extend` within directives such as `@media`. Sass is unable to make CSS rules outside of the `@media` block apply to selectors inside it without creating a huge amount of stylesheet bloat by copying styles all over the place. This means that if you use `@extend` within `@media` (or other CSS directives), you may only extend selectors that appear within the same directive block. For example, the following works fine: @media print { .error { border: 1px #f00; background-color: #fdd; } .seriousError { @extend .error; border-width: 3px; } } But this is an error: .error { border: 1px #f00; background-color: #fdd; } @media print { .seriousError { // INVALID EXTEND: .error is used outside of the "@media print" directive @extend .error; border-width: 3px; } } Someday we hope to have `@extend` supported natively in the browser, which will allow it to be used within `@media` and other directives. ### `@debug` The `@debug` directive prints the value of a SassScript expression to the standard error output stream. It's useful for debugging Sass files that have complicated SassScript going on. For example: @debug 10em + 12em; outputs: Line 1 DEBUG: 22em ### `@warn` The `@warn` directive prints the value of a SassScript expression to the standard error output stream. It's useful for libraries that need to warn users of deprecations or recovering from minor mixin usage mistakes. There are two major distinctions between `@warn` and `@debug`: 1. You can turn warnings off with the `--quiet` command-line option or the `:quiet` Sass option. 2. A stylesheet trace will be printed out along with the message so that the user being warned can see where their styles caused the warning. Usage Example: @mixin adjust-location($x, $y) { @if unitless($x) { @warn "Assuming #{$x} to be in pixels"; $x: 1px * $x; } @if unitless($y) { @warn "Assuming #{$y} to be in pixels"; $y: 1px * $y; } position: relative; left: $x; top: $y; } ## Control Directives SassScript supports basic control directives for including styles only under some conditions or including the same style several times with variations. **Note that control directives are an advanced feature, and are not recommended in the course of day-to-day styling**. They exist mainly for use in [mixins](#mixins), particularly those that are part of libraries like [Compass](http://compass-style.org), and so require substantial flexibility. ### `@if` The `@if` directive takes a SassScript expression and uses the styles nested beneath it if the expression returns anything other than `false` or `null`: p { @if 1 + 1 == 2 { border: 1px solid; } @if 5 < 3 { border: 2px dotted; } @if null { border: 3px double; } } is compiled to: p { border: 1px solid; } The `@if` statement can be followed by several `@else if` statements and one `@else` statement. If the `@if` statement fails, the `@else if` statements are tried in order until one succeeds or the `@else` is reached. For example: $type: monster; p { @if $type == ocean { color: blue; } @else if $type == matador { color: red; } @else if $type == monster { color: green; } @else { color: black; } } is compiled to: p { color: green; } ### `@for` The `@for` directive repeatedly outputs a set of styles. For each repetition, a counter variable is used to adjust the output. The directive has two forms: `@for $var from through ` and `@for $var from to `. Note the difference in the keywords `through` and `to`. `$var` can be any variable name, like `$i`; `` and `` are SassScript expressions that should return integers. The `@for` statement sets `$var` to each successive number in the specified range and each time outputs the nested styles using that value of `$var`. For the form `from ... through`, the range *includes* the values of `` and ``, but the form `from ... to` runs up to *but not including* the value of ``. Using the `through` syntax, @for $i from 1 through 3 { .item-#{$i} { width: 2em * $i; } } is compiled to: .item-1 { width: 2em; } .item-2 { width: 4em; } .item-3 { width: 6em; } ### `@each` {#each-directive} The `@each` rule has the form `@each $var in `. `$var` can be any variable name, like `$length` or `$name`, and `` is a SassScript expression that returns a list. The `@each` rule sets `$var` to each item in the list, then outputs the styles it contains using that value of `$var`. For example: @each $animal in puma, sea-slug, egret, salamander { .#{$animal}-icon { background-image: url('/images/#{$animal}.png'); } } is compiled to: .puma-icon { background-image: url('/images/puma.png'); } .sea-slug-icon { background-image: url('/images/sea-slug.png'); } .egret-icon { background-image: url('/images/egret.png'); } .salamander-icon { background-image: url('/images/salamander.png'); } ### `@while` The `@while` directive takes a SassScript expression and repeatedly outputs the nested styles until the statement evaluates to `false`. This can be used to achieve more complex looping than the `@for` statement is capable of, although this is rarely necessary. For example: $i: 6; @while $i > 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; } is compiled to: .item-6 { width: 12em; } .item-4 { width: 8em; } .item-2 { width: 4em; } ## Mixin Directives {#mixins} Mixins allow you to define styles that can be re-used throughout the stylesheet without needing to resort to non-semantic classes like `.float-left`. Mixins can also contain full CSS rules, and anything else allowed elsewhere in a Sass document. They can even take [arguments](#mixin-arguments) which allows you to produce a wide variety of styles with very few mixins. ### Defining a Mixin: `@mixin` {#defining_a_mixin} Mixins are defined with the `@mixin` directive. It's followed by the name of the mixin and optionally the [arguments](#mixin-arguments), and a block containing the contents of the mixin. For example, the `large-text` mixin is defined as follows: @mixin large-text { font: { family: Arial; size: 20px; weight: bold; } color: #ff0000; } Mixins may also contain selectors, possibly mixed with properties. The selectors can even contain [parent references](#referencing_parent_selectors_). For example: @mixin clearfix { display: inline-block; &:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } * html & { height: 1px } } ### Including a Mixin: `@include` {#including_a_mixin} Mixins are included in the document with the `@include` directive. This takes the name of a mixin and optionally [arguments to pass to it](#mixin-arguments), and includes the styles defined by that mixin into the current rule. For example: .page-title { @include large-text; padding: 4px; margin-top: 10px; } is compiled to: .page-title { font-family: Arial; font-size: 20px; font-weight: bold; color: #ff0000; padding: 4px; margin-top: 10px; } Mixins may also be included outside of any rule (that is, at the root of the document) as long as they don't directly define any properties or use any parent references. For example: @mixin silly-links { a { color: blue; background-color: red; } } @include silly-links; is compiled to: a { color: blue; background-color: red; } Mixin definitions can also include other mixins. For example: @mixin compound { @include highlighted-background; @include header-text; } @mixin highlighted-background { background-color: #fc0; } @mixin header-text { font-size: 20px; } A mixin may not include itself, directly or indirectly. That is, mixin recursion is forbidden. Mixins that only define descendent selectors can be safely mixed into the top most level of a document. ### Arguments {#mixin-arguments} Mixins can take arguments SassScript values as arguments, which are given when the mixin is included and made available within the mixin as variables. When defining a mixin, the arguments are written as variable names separated by commas, all in parentheses after the name. Then when including the mixin, values can be passed in in the same manner. For example: @mixin sexy-border($color, $width) { border: { color: $color; width: $width; style: dashed; } } p { @include sexy-border(blue, 1in); } is compiled to: p { border-color: blue; border-width: 1in; border-style: dashed; } Mixins can also specify default values for their arguments using the normal variable-setting syntax. Then when the mixin is included, if it doesn't pass in that argument, the default value will be used instead. For example: @mixin sexy-border($color, $width: 1in) { border: { color: $color; width: $width; style: dashed; } } p { @include sexy-border(blue); } h1 { @include sexy-border(blue, 2in); } is compiled to: p { border-color: blue; border-width: 1in; border-style: dashed; } h1 { border-color: blue; border-width: 2in; border-style: dashed; } #### Keyword Arguments Mixins can also be included using explicit keyword arguments. For instance, we the above example could be written as: p { @include sexy-border($color: blue); } h1 { @include sexy-border($color: blue, $width: 2in); } While this is less concise, it can make the stylesheet easier to read. It also allows functions to present more flexible interfaces, providing many arguments without becoming difficult to call. Named arguments can be passed in any order, and arguments with default values can be omitted. Since the named arguments are variable names, underscores and dashes can be used interchangeably. #### Variable Arguments Sometimes it makes sense for a mixin to take an unknown number of arguments. For example, a mixin for creating box shadows might take any number of shadows as arguments. For these situations, Sass supports "variable arguments," which are arguments at the end of a mixin declaration that take all leftover arguments and package them up as a [list](#lists). These arguments look just like normal arguments, but are followed by `...`. For example: @mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; -webkit-box-shadow: $shadows; box-shadow: $shadows; } .shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); } is compiled to: .shadows { -moz-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999; -webkit-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999; box-shadow: 0px 4px 5px #666, 2px 6px 10px #999; } Variable arguments can also be used when calling a mixin. Using the same syntax, you can expand a list of values so that each value is passed as a separate argument. For example: @mixin colors($text, $background, $border) { color: $text; background-color: $background; border-color: $border; } $values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); } is compiled to: .primary { color: #ff0000; background-color: #00ff00; border-color: #0000ff; } You can use variable arguments to wrap a mixin and add additional styles without changing the argument signature of the mixin. If you do so, even keyword arguments will get passed through to the wrapped mixin. For example: @mixin wrapped-stylish-mixin($args...) { font-weight: bold; @include stylish-mixin($args...); } .stylish { // The $width argument will get passed on to "stylish-mixin" as a keyword @include wrapped-stylish-mixin(#00ff00, $width: 100px); } ### Passing Content Blocks to a Mixin {#mixin-content} It is possible to pass a block of styles to the mixin for placement within the styles included by the mixin. The styles will appear at the location of any `@content` directives found within the mixin. This makes is possible to define abstractions relating to the construction of selectors and directives. For example: @mixin apply-to-ie6-only { * html { @content; } } @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } Generates: * html #logo { background-image: url(/logo.gif); } The same mixins can be done in the `.sass` shorthand syntax: =apply-to-ie6-only * html @content +apply-to-ie6-only #logo background-image: url(/logo.gif) **Note:** when the `@content` directive is specified more than once or in a loop, the style block will be duplicated with each invocation. #### Variable Scope and Content Blocks The block of content passed to a mixin are evaluated in the scope where the block is defined, not in the scope of the mixin. This means that variables local to the mixin **cannot** be used within the passed style block and variables will resolve to the global value: $color: white; @mixin colors($color: blue) { background-color: $color; @content; border-color: $color; } .colors { @include colors { color: $color; } } Compiles to: .colors { background-color: blue; color: white; border-color: blue; } Additionally, this makes it clear that the variables and mixins that are used within the passed block are related to the other styles around where the block is defined. For example: #sidebar { $sidebar-width: 300px; width: $sidebar-width; @include smartphone { width: $sidebar-width / 3; } } ## Function Directives {#function_directives} It is possible to define your own functions in sass and use them in any value or script context. For example: $grid-width: 40px; $gutter-width: 10px; @function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; } #sidebar { width: grid-width(5); } Becomes: #sidebar { width: 240px; } As you can see functions can access any globally defined variables as well as accept arguments just like a mixin. A function may have several statements contained within it, and you must call `@return` to set the return value of the function. As with mixins, you can call Sass-defined functions using keyword arguments. In the above example we could have called the function like this: #sidebar { width: grid-width($n: 5); } It is recommended that you prefix your functions to avoid naming conflicts and so that readers of your stylesheets know they are not part of Sass or CSS. For example, if you work for ACME Corp, you might have named the function above `-acme-grid-width`. User-defined functions also support [variable arguments](#variable_arguments) in the same way as mixins. ## Output Style Although the default CSS style that Sass outputs is very nice and reflects the structure of the document, tastes and needs vary and so Sass supports several other styles. Sass allows you to choose between four different output styles by setting the [`:style` option](#style-option) or using the `--style` command-line flag. ### `:nested` Nested style is the default Sass style, because it reflects the structure of the CSS styles and the HTML document they're styling. Each property has its own line, but the indentation isn't constant. Each rule is indented based on how deeply it's nested. For example: #main { color: #fff; background-color: #000; } #main p { width: 10em; } .huge { font-size: 10em; font-weight: bold; text-decoration: underline; } Nested style is very useful when looking at large CSS files: it allows you to easily grasp the structure of the file without actually reading anything. ### `:expanded` Expanded is a more typical human-made CSS style, with each property and rule taking up one line. Properties are indented within the rules, but the rules aren't indented in any special way. For example: #main { color: #fff; background-color: #000; } #main p { width: 10em; } .huge { font-size: 10em; font-weight: bold; text-decoration: underline; } ### `:compact` Compact style takes up less space than Nested or Expanded. It also draws the focus more to the selectors than to their properties. Each CSS rule takes up only one line, with every property defined on that line. Nested rules are placed next to each other with no newline, while separate groups of rules have newlines between them. For example: #main { color: #fff; background-color: #000; } #main p { width: 10em; } .huge { font-size: 10em; font-weight: bold; text-decoration: underline; } ### `:compressed` Compressed style takes up the minimum amount of space possible, having no whitespace except that necessary to separate selectors and a newline at the end of the file. It also includes some other minor compressions, such as choosing the smallest representation for colors. It's not meant to be human-readable. For example: #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline} ## Extending Sass Sass provides a number of advanced customizations for users with unique requirements. Using these features requires a strong understanding of Ruby. ### Defining Custom Sass Functions Users can define their own Sass functions using the Ruby API. For more information, see the [source documentation](Sass/Script/Functions.html#adding_custom_functions). ### Cache Stores Sass caches parsed documents so that they can be reused without parsing them again unless they have changed. By default, Sass will write these cache files to a location on the filesystem indicated by [`:cache_location`](#cache_location-option). If you cannot write to the filesystem or need to share cache across ruby processes or machines, then you can define your own cache store and set the[`:cache_store` option](#cache_store-option). For details on creating your own cache store, please see the {Sass::CacheStores::Base source documentation}. ### Custom Importers 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 loaded from the {Sass::Importers::Filesystem filesystem}, but importers could be added to load from a database, over HTTP, or use a different file naming scheme than what Sass expects. 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 {Sass::Importers::Base}. sass-3.2.12/doc-src/SCSS_FOR_SASS_USERS.md000066400000000000000000000102621222366545200175270ustar00rootroot00000000000000# Intro to SCSS for Sass Users Sass 3 introduces a new syntax known as SCSS which is fully compatible with the syntax of CSS3, while still supporting the full power of Sass. This means that every valid CSS3 stylesheet is a valid SCSS file with the same meaning. In addition, SCSS understands most CSS hacks and vendor-specific syntax, such as [IE's old `filter` syntax](http://msdn.microsoft.com/en-us/library/ms533754%28VS.85%29.aspx). Since SCSS is a CSS extension, everything that works in CSS works in SCSS. This means that for a Sass user to understand it, they need only understand how the Sass extensions work. Most of these, such as variables, parent references, and directives work the same; the only difference is that SCSS requires semicolons and brackets instead of newlines and indentation. For example, a simple rule in Sass: #sidebar width: 30% background-color: #faa could be converted to SCSS just by adding brackets and semicolons: #sidebar { width: 30%; background-color: #faa; } In addition, SCSS is completely whitespace-insensitive. That means the above could also be written as: #sidebar {width: 30%; background-color: #faa} There are some differences that are slightly more complicated. These are detailed below. Note, though, that SCSS uses all the {file:SASS_CHANGELOG.md#3-0-0-syntax-changes syntax changes in Sass 3}, so make sure you understand those before going forward. ## Nested Selectors To nest selectors, simply define a new ruleset inside an existing ruleset: #sidebar { a { text-decoration: none; } } Of course, white space is insignificant and the last trailing semicolon is optional so you can also do it like this: #sidebar { a { text-decoration: none } } ## Nested Properties To nest properties, simply create a new property set after an existing property's colon: #footer { border: { width: 1px; color: #ccc; style: solid; } } This compiles to: #footer { border-width: 1px; border-color: #cccccc; border-style: solid; } ## Mixins A mixin is declared with the `@mixin` directive: @mixin rounded($amount) { -moz-border-radius: $amount; -webkit-border-radius: $amount; border-radius: $amount; } A mixin is used with the `@include` directive: .box { border: 3px solid #777; @include rounded(0.5em); } This syntax is also available in the indented syntax, although the old `=` and `+` syntax still works. This is rather verbose compared to the `=` and `+` characters used in Sass syntax. This is because the SCSS format is designed for CSS compatibility rather than conciseness, and creating new syntax when the CSS directive syntax already exists adds new syntax needlessly and could create incompatibilities with future versions of CSS. ## Comments Like Sass, SCSS supports both comments that are preserved in the CSS output and comments that aren't. However, SCSS's comments are significantly more flexible. It supports standard multiline CSS comments with `/* */`, which are preserved where possible in the output. These comments can have whatever formatting you like; Sass will do its best to format them nicely. SCSS also uses `//` for comments that are thrown away, like Sass. Unlike Sass, though, `//` comments in SCSS may appear anywhere and last only until the end of the line. For example: /* This comment is * several lines long. * since it uses the CSS comment syntax, * it will appear in the CSS output. */ body { color: black; } // These comments are only one line long each. // They won't appear in the CSS output, // since they use the single-line comment syntax. a { color: green; } is compiled to: /* This comment is * several lines long. * since it uses the CSS comment syntax, * it will appear in the CSS output. */ body { color: black; } a { color: green; } ## `@import` The `@import` directive in SCSS functions just like that in Sass, except that it takes a quoted string to import. For example, this Sass: @import themes/dark @import font.sass would be this SCSS: @import "themes/dark"; @import "font.sass"; sass-3.2.12/ext/000077500000000000000000000000001222366545200133575ustar00rootroot00000000000000sass-3.2.12/ext/extconf.rb000066400000000000000000000003701222366545200153520ustar00rootroot00000000000000root = File.expand_path("../..", __FILE__) File.open(File.expand_path("lib/sass/root.rb", root), "w") do |f| f << <<-RUBY module Sass ROOT_DIR = #{root.inspect} end RUBY end File.open('Makefile', 'w') { |f| f.puts("install:\n\t$(exit 0)") } sass-3.2.12/extra/000077500000000000000000000000001222366545200137025ustar00rootroot00000000000000sass-3.2.12/extra/update_watch.rb000066400000000000000000000005131222366545200166760ustar00rootroot00000000000000require '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 sass-3.2.12/init.rb000066400000000000000000000011131222366545200140430ustar00rootroot00000000000000begin 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) sass-3.2.12/lib/000077500000000000000000000000001222366545200133255ustar00rootroot00000000000000sass-3.2.12/lib/sass.rb000066400000000000000000000073041222366545200146270ustar00rootroot00000000000000dir = 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] 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' sass-3.2.12/lib/sass/000077500000000000000000000000001222366545200142765ustar00rootroot00000000000000sass-3.2.12/lib/sass/cache_stores.rb000066400000000000000000000006051222366545200172660ustar00rootroot00000000000000require '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' sass-3.2.12/lib/sass/cache_stores/000077500000000000000000000000001222366545200167405ustar00rootroot00000000000000sass-3.2.12/lib/sass/cache_stores/base.rb000066400000000000000000000071751222366545200202110ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/cache_stores/chain.rb000066400000000000000000000016531222366545200203540ustar00rootroot00000000000000module 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] 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 sass-3.2.12/lib/sass/cache_stores/filesystem.rb000066400000000000000000000037161222366545200214600ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/cache_stores/memory.rb000066400000000000000000000022511222366545200205750ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/cache_stores/null.rb000066400000000000000000000007741222366545200202470ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/callbacks.rb000066400000000000000000000035531222366545200165500ustar00rootroot00000000000000module 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 < "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] # @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] # @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] # @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 sass-3.2.12/lib/sass/engine.rb000066400000000000000000001005131222366545200160700ustar00rootroot00000000000000require '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` # : 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` # : 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 = < 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(< 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(< @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 '" else expected = "'to ' or 'through '" 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 '" 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 '.") 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 sass-3.2.12/lib/sass/environment.rb000066400000000000000000000055421222366545200171750ustar00rootroot00000000000000require '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 < 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] 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) <] 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 <] 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 = < 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 <>> 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] The command-line arguments def initialize(args) super @default_syntax = :scss end end # The `sass-convert` executable. class SassConvert < Generic # @param args [Array] 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 = < 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 sass-3.2.12/lib/sass/importers.rb000066400000000000000000000015631222366545200166540ustar00rootroot00000000000000module 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' sass-3.2.12/lib/sass/importers/000077500000000000000000000000001222366545200163225ustar00rootroot00000000000000sass-3.2.12/lib/sass/importers/base.rb000066400000000000000000000135331222366545200175660ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/importers/filesystem.rb000066400000000000000000000144741222366545200210450ustar00rootroot00000000000000require '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 <= 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 sass-3.2.12/lib/sass/media.rb000066400000000000000000000153371222366545200157130ustar00rootroot00000000000000# 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] attr_accessor :queries # @param queries [Array] 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] 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] 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] 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>] attr_accessor :expressions # @param modifier [Array] See \{#modifier} # @param type [Array] See \{#type} # @param expressions [Array>] 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] 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 sass-3.2.12/lib/sass/plugin.rb000066400000000000000000000113411222366545200161210ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/plugin/000077500000000000000000000000001222366545200155745ustar00rootroot00000000000000sass-3.2.12/lib/sass/plugin/compiler.rb000066400000000000000000000357101222366545200177410ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/plugin/configuration.rb000066400000000000000000000114731222366545200207760ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/plugin/generic.rb000066400000000000000000000011651222366545200175400ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/plugin/merb.rb000066400000000000000000000026111222366545200170460ustar00rootroot00000000000000unless 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 sass-3.2.12/lib/sass/plugin/rack.rb000066400000000000000000000034021222366545200170400ustar00rootroot00000000000000module 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' sass-3.2.12/lib/sass/plugin/rails.rb000066400000000000000000000030101222366545200172250ustar00rootroot00000000000000unless 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 sass-3.2.12/lib/sass/plugin/staleness_checker.rb000066400000000000000000000200551222366545200216100ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/railtie.rb000066400000000000000000000004541222366545200162570ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/repl.rb000066400000000000000000000024551222366545200155730ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/root.rb000066400000000000000000000004021222366545200156020ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/script.rb000066400000000000000000000027561222366545200161410ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/script/000077500000000000000000000000001222366545200156025ustar00rootroot00000000000000sass-3.2.12/lib/sass/script/arg_list.rb000066400000000000000000000025751222366545200177440ustar00rootroot00000000000000module 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] See \{List#value}. # @param keywords [Hash] 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] 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 sass-3.2.12/lib/sass/script/bool.rb000066400000000000000000000006231222366545200170630ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/script/color.rb000066400000000000000000000456071222366545200172610ustar00rootroot00000000000000require '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] 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] 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] 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 sass-3.2.12/lib/sass/script/css_lexer.rb000066400000000000000000000012111222366545200201110ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/script/css_parser.rb000066400000000000000000000014771222366545200203040ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/script/funcall.rb000066400000000000000000000220241222366545200175530ustar00rootroot00000000000000require '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] 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] 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] # @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 sass-3.2.12/lib/sass/script/functions.rb000066400000000000000000001622451222366545200201510ustar00rootroot00000000000000module 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] 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] 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 sass-3.2.12/lib/sass/script/interpolation.rb000066400000000000000000000053631222366545200210250ustar00rootroot00000000000000module 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] # @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 sass-3.2.12/lib/sass/script/lexer.rb000066400000000000000000000255721222366545200172610ustar00rootroot00000000000000require '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(<] 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] 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 sass-3.2.12/lib/sass/script/literal.rb000066400000000000000000000150061222366545200175650ustar00rootroot00000000000000module 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] 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(<] 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 sass-3.2.12/lib/sass/script/node.rb000066400000000000000000000050521222366545200170560ustar00rootroot00000000000000module 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] 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 sass-3.2.12/lib/sass/script/null.rb000066400000000000000000000011571222366545200171050ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/script/number.rb000066400000000000000000000357161222366545200174330ustar00rootroot00000000000000require '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] 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] 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] See \{#numerator\_units} # @param denominator_units [Array] 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] The numerator units to coerce this number into. # See {\#numerator\_units} # @param den_units [Array] 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 sass-3.2.12/lib/sass/script/operation.rb000066400000000000000000000070051222366545200201310ustar00rootroot00000000000000require '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] # @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 sass-3.2.12/lib/sass/script/parser.rb000066400000000000000000000372371222366545200174370ustar00rootroot00000000000000require '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, {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)] # 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)] # 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 < "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 sass-3.2.12/lib/sass/script/string.rb000066400000000000000000000025521222366545200174410ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/script/string_interpolation.rb000066400000000000000000000064621222366545200224140ustar00rootroot00000000000000module 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] # @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 sass-3.2.12/lib/sass/script/unary_operation.rb000066400000000000000000000040311222366545200213430ustar00rootroot00000000000000module 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] # @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 sass-3.2.12/lib/sass/script/variable.rb000066400000000000000000000026541222366545200177230ustar00rootroot00000000000000module 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] 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 sass-3.2.12/lib/sass/scss.rb000066400000000000000000000007261222366545200156030ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/scss/000077500000000000000000000000001222366545200152515ustar00rootroot00000000000000sass-3.2.12/lib/sass/scss/css_parser.rb000066400000000000000000000017571222366545200177540ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/scss/parser.rb000066400000000000000000001033201222366545200170710ustar00rootroot00000000000000require '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, 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(< 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 sass-3.2.12/lib/sass/scss/rx.rb000066400000000000000000000107501222366545200162320ustar00rootroot00000000000000module 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("") 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 sass-3.2.12/lib/sass/scss/script_lexer.rb000066400000000000000000000006461222366545200203070ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/scss/script_parser.rb000066400000000000000000000013321222366545200204550ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/scss/static_parser.rb000066400000000000000000000032601222366545200204420ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/selector.rb000066400000000000000000000332761222366545200164560ustar00rootroot00000000000000require '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] attr_reader :name # @param name [Array] 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] attr_reader :name # @param name [Array] 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] attr_reader :name # @param name [Array] 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, nil] attr_reader :namespace # @param namespace [Array, 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] attr_reader :name # The selector namespace. # `nil` means the default namespace, # `[""]` means no namespace, # `["*"]` means any namespace. # # @return [Array, nil] attr_reader :namespace # @param name [Array] The element name # @param namespace [Array, 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] attr_reader :name # The attribute namespace. # `nil` means the default namespace, # `[""]` means no namespace, # `["*"]` means any namespace. # # @return [Array, nil] attr_reader :namespace # The matching operator, e.g. `"="` or `"^="`. # # @return [String] attr_reader :operator # The right-hand side of the operator. # # @return [Array] attr_reader :value # Flags for the attribute selector (e.g. `i`). # # @return [Array] attr_reader :flags # @param name [Array] The attribute name # @param namespace [Array, nil] See \{#namespace} # @param operator [String] The matching operator, e.g. `"="` or `"^="` # @param value [Array] See \{#value} # @param value [Array] 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] 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] 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, nil] attr_reader :arg # @param type [Symbol] See \{#type} # @param name [Array] The name of the selector # @param arg [nil, Array] 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 sass-3.2.12/lib/sass/selector/000077500000000000000000000000001222366545200161165ustar00rootroot00000000000000sass-3.2.12/lib/sass/selector/abstract_sequence.rb000066400000000000000000000057251222366545200221470ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/selector/comma_sequence.rb000066400000000000000000000061361222366545200214350ustar00rootroot00000000000000module Sass module Selector # A comma-separated sequence of selectors. class CommaSequence < AbstractSequence # The comma-separated selector sequences # represented by this class. # # @return [Array] attr_reader :members # @param seqs [Array] 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] # 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 sass-3.2.12/lib/sass/selector/sequence.rb000066400000000000000000000504521222366545200202610ustar00rootroot00000000000000module 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>] attr_reader :members # @param seqs_and_ops [Array>] 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] # The directives containing this selector. # @return [Array] 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] 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>] A list of parenthesized selector groups. # @return [Array>] 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] # @param seq2 [Array] # @return [Array>] 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] # @param seq2 [Array] # @return [Array, 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] # @param seq2 [Array] # @return [Array>] # 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] 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] 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] # @param seq2 [Array] # @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] # @param seq2 [Array] # @return [Boolean] def parent_superselector?(seq1, seq2) base = Sass::Selector::SimpleSequence.new([Sass::Selector::Placeholder.new('')], 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>>] # @return [Array>>] 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 sass-3.2.12/lib/sass/selector/simple.rb000066400000000000000000000112401222366545200177320ustar00rootroot00000000000000module 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] 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] A {SimpleSequence}'s {SimpleSequence#members members array} # @return [Array, 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 sass-3.2.12/lib/sass/selector/simple_sequence.rb000066400000000000000000000200331222366545200216220ustar00rootroot00000000000000module 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] 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} 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] 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] See \{#members} # @param subject [Boolean] See \{#subject?} # @param sources [Set] 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] 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] # The directives containing this selector. # @return [Array] 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] 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] # @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 < 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 sass-3.2.12/lib/sass/tree/000077500000000000000000000000001222366545200152355ustar00rootroot00000000000000sass-3.2.12/lib/sass/tree/charset_node.rb000066400000000000000000000006521222366545200202230ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/comment_node.rb000066400000000000000000000044671222366545200202440ustar00rootroot00000000000000require '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] 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] 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 sass-3.2.12/lib/sass/tree/content_node.rb000066400000000000000000000003001222366545200202320ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/css_import_node.rb000066400000000000000000000033111222366545200207470ustar00rootroot00000000000000module 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] 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] 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 sass-3.2.12/lib/sass/tree/debug_node.rb000066400000000000000000000006011222366545200176520ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/directive_node.rb000066400000000000000000000023451222366545200205510ustar00rootroot00000000000000module 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] 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] 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 sass-3.2.12/lib/sass/tree/each_node.rb000066400000000000000000000010221222366545200174620ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/tree/extend_node.rb000066400000000000000000000020271222366545200200570ustar00rootroot00000000000000require '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] attr_accessor :selector # Whether the `@extend` is allowed to match no selectors or not. # # @return [Boolean] def optional?; @optional; end # @param selector [Array] # 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 sass-3.2.12/lib/sass/tree/for_node.rb000066400000000000000000000015541222366545200173620ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/tree/function_node.rb000066400000000000000000000016501222366545200204160ustar00rootroot00000000000000module 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] 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 sass-3.2.12/lib/sass/tree/if_node.rb000066400000000000000000000024611222366545200171700ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/tree/import_node.rb000066400000000000000000000042371222366545200201070ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/media_node.rb000066400000000000000000000030341222366545200176460ustar00rootroot00000000000000module 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] 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] 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 sass-3.2.12/lib/sass/tree/mixin_def_node.rb000066400000000000000000000020341222366545200205300ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/mixin_node.rb000066400000000000000000000020161222366545200177120ustar00rootroot00000000000000require '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] 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] 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 sass-3.2.12/lib/sass/tree/node.rb000066400000000000000000000141531222366545200165130ustar00rootroot00000000000000module 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] 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] 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 sass-3.2.12/lib/sass/tree/prop_node.rb000066400000000000000000000117701222366545200175550ustar00rootroot00000000000000module 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] 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] 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 sass-3.2.12/lib/sass/tree/return_node.rb000066400000000000000000000006011222366545200201030ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/root_node.rb000066400000000000000000000015151222366545200175540ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/rule_node.rb000066400000000000000000000101131222366545200175320ustar00rootroot00000000000000require '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] 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] attr_accessor :stack_trace # @param rule [Array] # 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 sass-3.2.12/lib/sass/tree/supports_node.rb000066400000000000000000000022741222366545200204730ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/trace_node.rb000066400000000000000000000015721222366545200176720ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/tree/variable_node.rb000066400000000000000000000013661222366545200203620ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/visitors/000077500000000000000000000000001222366545200171175ustar00rootroot00000000000000sass-3.2.12/lib/sass/tree/visitors/base.rb000066400000000000000000000051471222366545200203650ustar00rootroot00000000000000# 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] 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 sass-3.2.12/lib/sass/tree/visitors/check_nesting.rb000066400000000000000000000111641222366545200222530ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/tree/visitors/convert.rb000066400000000000000000000212131222366545200211230ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/tree/visitors/cssize.rb000066400000000000000000000201231222366545200207420ustar00rootroot00000000000000# 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] The selector being `@extend`ed. # @attr node [Sass::Tree::ExtendNode] The node that produced this extend. # @attr directives [Array] # 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 sass-3.2.12/lib/sass/tree/visitors/deep_copy.rb000066400000000000000000000041351222366545200214160ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/tree/visitors/extend.rb000066400000000000000000000043271222366545200207410ustar00rootroot00000000000000# 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 < 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 sass-3.2.12/lib/sass/tree/visitors/set_options.rb000066400000000000000000000052111222366545200220110ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/tree/visitors/to_css.rb000066400000000000000000000166151222366545200207470ustar00rootroot00000000000000# 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 sass-3.2.12/lib/sass/tree/warn_node.rb000066400000000000000000000005761222366545200175460ustar00rootroot00000000000000module 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 sass-3.2.12/lib/sass/tree/while_node.rb000066400000000000000000000005711222366545200177020ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/util.rb000066400000000000000000000770141222366545200156110ustar00rootroot00000000000000require '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] 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] # @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] # @return [Array] # # @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] 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 < _ 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' sass-3.2.12/lib/sass/util/000077500000000000000000000000001222366545200152535ustar00rootroot00000000000000sass-3.2.12/lib/sass/util/multibyte_string_scanner.rb000066400000000000000000000104361222366545200227210ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/util/subset_map.rb000066400000000000000000000067701222366545200177540ustar00rootroot00000000000000require '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 sass-3.2.12/lib/sass/util/test.rb000066400000000000000000000002351222366545200165570ustar00rootroot00000000000000module Sass module Util module Test def skip(msg = nil, bt = caller) super if defined?(super) return end end end end sass-3.2.12/lib/sass/version.rb000066400000000000000000000100311222366545200163030ustar00rootroot00000000000000require '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 sass-3.2.12/rails/000077500000000000000000000000001222366545200136715ustar00rootroot00000000000000sass-3.2.12/rails/init.rb000066400000000000000000000000771222366545200151650ustar00rootroot00000000000000Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb') sass-3.2.12/sass.gemspec000066400000000000000000000030201222366545200150700ustar00rootroot00000000000000require 'rubygems' # Note that Sass's gem-compilation process requires access to the filesystem. # This means that it cannot be automatically run by e.g. GitHub's gem system. # However, a build server automatically packages the master branch # every time it's pushed to; this is made available as a prerelease gem. SASS_GEMSPEC = Gem::Specification.new do |spec| spec.rubyforge_project = 'sass' spec.name = 'sass' spec.summary = "A powerful but elegant CSS compiler that makes CSS fun again." spec.version = File.read(File.dirname(__FILE__) + '/VERSION').strip spec.authors = ['Nathan Weizenbaum', 'Chris Eppstein', 'Hampton Catlin'] spec.email = 'sass-lang@googlegroups.com' spec.description = <<-END 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. END spec.required_ruby_version = '>= 1.8.7' spec.add_development_dependency 'yard', '>= 0.5.3' spec.add_development_dependency 'maruku', '>= 0.5.9' readmes = Dir['*'].reject{ |x| x =~ /(^|[^.a-z])[a-z]+/ || x == "TODO" } spec.executables = ['sass', 'sass-convert', 'scss'] spec.files = Dir['rails/init.rb', 'lib/**/*', 'vendor/**/*', 'bin/*', 'test/**/*', 'extra/**/*', 'Rakefile', 'init.rb', '.yardopts'] + readmes spec.homepage = 'http://sass-lang.com/' spec.has_rdoc = false spec.test_files = Dir['test/**/*_test.rb'] spec.license = "MIT" end sass-3.2.12/test/000077500000000000000000000000001222366545200135365ustar00rootroot00000000000000sass-3.2.12/test/.bundle/000077500000000000000000000000001222366545200150655ustar00rootroot00000000000000sass-3.2.12/test/.bundle/config000066400000000000000000000000071222366545200162520ustar00rootroot00000000000000--- {} sass-3.2.12/test/Gemfile000066400000000000000000000000361222366545200150300ustar00rootroot00000000000000source :gemcutter gem 'rake' sass-3.2.12/test/Gemfile.lock000066400000000000000000000001441222366545200157570ustar00rootroot00000000000000GEM remote: http://rubygems.org/ specs: rake (0.9.2) PLATFORMS ruby DEPENDENCIES rake sass-3.2.12/test/sass/000077500000000000000000000000001222366545200145075ustar00rootroot00000000000000sass-3.2.12/test/sass/cache_test.rb000077500000000000000000000056141222366545200171470ustar00rootroot00000000000000#!/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 sass-3.2.12/test/sass/callbacks_test.rb000077500000000000000000000021431222366545200200150ustar00rootroot00000000000000#!/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 sass-3.2.12/test/sass/conversion_test.rb000077500000000000000000000557661222366545200203060ustar00rootroot00000000000000#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' class ConversionTest < Test::Unit::TestCase def test_basic assert_renders < 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 < true foo bar :baz 12 $bang "bip" SASS foo bar { baz: 12 $bang "bip"; } SCSS end def test_multiline_properties assert_scss_to_sass < 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 < 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 < 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(< " " foo bar baz bang baz: bang bip: bop blat: boo SASS foo bar { baz bang { baz: bang; bip: bop; } blat: boo; } SCSS assert_renders < "\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 < " " 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 < "\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 < " " 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 < "\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 < ' ') foo // bar /* baz a: b SASS foo { // bar /* baz */ a: b; } SCSS end def test_ambiguous_negation assert_renders(< ' ') 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 sass-3.2.12/test/sass/css2sass_test.rb000077500000000000000000000170751222366545200176540ustar00rootroot00000000000000#!/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 = < true)) h1 :color red SASS end def test_nesting assert_equal(< .bar a: b .baz c: d SASS .foo>.bar {a: b} .foo>.baz {c: d} CSS assert_equal(< .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 sass-3.2.12/test/sass/data/000077500000000000000000000000001222366545200154205ustar00rootroot00000000000000sass-3.2.12/test/sass/data/hsl-rgb.txt000066400000000000000000000121621222366545200175210ustar00rootroot00000000000000hsl(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) sass-3.2.12/test/sass/engine_test.rb000077500000000000000000002251111222366545200173450ustar00rootroot00000000000000#!/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" => < '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 '.", "@if false\n@else if " => "Invalid else directive '@else if': expected 'if '.", "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 < [File.dirname(__FILE__) + '/templates/']} munge_filename options result = Sass::Engine.new("@import 'same_name_different_ext'", options).render assert_equal(< [File.dirname(__FILE__) + '/templates/']} munge_filename options result = Sass::Engine.new("@import 'same_name_different_partiality'", options).render assert_equal(< 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 = < err assert_equal(5, err.sass_line) else assert(false, "Exception not raised for '#{to_render}'!") end end def test_exception_location to_render = < 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(< 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(< 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(< 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 < 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 < err assert_equal(< "bar", :line => 4) assert_hash_has(err.sass_backtrace[1], :mixin => "foo", :line => 2) end def test_deep_mixin_loop_exception render < err assert_equal(< "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 < 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, < filename_for_test, :load_paths => [importer], :importer => importer) assert_raise_message(Sass::SyntaxError, < filename_for_test, :load_paths => [importer], :importer => importer) assert_raise_message(Sass::SyntaxError, < 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(< true} render(< e assert_equal(< true} render(< e assert_equal(< :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, < :compact, :cache => false, :load_paths => [File.dirname(__FILE__) + "/templates"], }) assert !File.exists?(sassc_path("importee")) end def test_import_in_rule assert_equal(< [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(< :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(< :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 = < :compact)) assert_equal("@a{b:c;#d{e:f}g:h}\n", render(to_render, :style => :compressed)) end def test_property_hacks assert_equal(< 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(< 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(< 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(< 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(< 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(< 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(< :expanded } end def test_directive_style_mixins assert_equal < e assert_equal("Function plus is missing argument $var1.", e.message) end def test_function_with_extra_argument render(< e assert_equal("Function plus doesn't have an argument named $var3.", e.message) end def test_function_with_positional_and_keyword_argument render(< 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(< e assert_equal("Positional arguments must come before keyword arguments.", e.message) end def test_function_with_if assert_equal(< :compressed) foo{color:blue;/*! foo * bar */} CSS foo color: blue /*! foo * bar */ SASS end def test_loud_comment_is_evaluated assert_equal < :new)) :focus { outline: 0; } CSS :focus outline: 0 SASS end def test_pseudo_class_with_new_properties assert_equal(< :new)) p :focus { outline: 0; } CSS p :focus outline: 0 SASS end def test_nil_option assert_equal(< nil)) foo { a: b; } CSS foo a: b SASS end def test_interpolation_in_raw_functions assert_equal(< true) CSS @warn "this is a warning" SASS end end def test_warn_with_imports expected_warning = < :compact, :load_paths => [File.dirname(__FILE__) + "/templates"] end end def test_media_bubbling assert_equal < :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 < :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 < 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 < 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 < :compressed)) .box{border-style:solid} RESULT .box :border /*:color black :style solid SOURCE end def test_compressed_comment_beneath_directive assert_equal(< :compressed)) @foo{a:b} RESULT @foo a: b /*b: c SOURCE end def test_comment_with_crazy_indentation assert_equal(< :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(< 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(< :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(< 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(< 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 sass-3.2.12/test/sass/exec_test.rb000077500000000000000000000050151222366545200170230ustar00rootroot00000000000000#!/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 sass-3.2.12/test/sass/extend_test.rb000077500000000000000000001147651222366545200174030ustar00rootroot00000000000000#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' class ExtendTest < Test::Unit::TestCase def test_basic assert_equal < :sass) .foo, .bar { a: b; } CSS .foo a: b .bar @extend .foo SASS assert_equal < :sass) .foo, .bar { a: b; } CSS .foo a: b .bar @extend \#{".foo"} SASS end def test_multiple_targets assert_equal < 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 < .foo, .baz > .bar { a: b; } CSS .baz > { .foo {a: b} .bar {@extend .foo} } SCSS assert_equal < .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 < .baz { a: b; } CSS .foo { .bar {a: b} .bip > .baz {@extend .bar} } SCSS assert_equal < .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 < .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 < .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 < .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 < .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 < :scss}.merge(options) munge_filename options Sass::Engine.new(sass, options).render end end sass-3.2.12/test/sass/fixtures/000077500000000000000000000000001222366545200163605ustar00rootroot00000000000000sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.css000066400000000000000000000000301222366545200267360ustar00rootroot00000000000000.pear { color: green; } sass-3.2.12/test/sass/fixtures/test_staleness_check_across_importers.scss000066400000000000000000000000211222366545200271210ustar00rootroot00000000000000@import "apple"; sass-3.2.12/test/sass/functions_test.rb000077500000000000000000001433511222366545200201150ustar00rootroot00000000000000#!/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(< 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 sass-3.2.12/test/sass/importer_test.rb000077500000000000000000000130051222366545200177360ustar00rootroot00000000000000#!/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 = < :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 = < :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 sass-3.2.12/test/sass/logger_test.rb000077500000000000000000000027101222366545200173550ustar00rootroot00000000000000#!/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 sass-3.2.12/test/sass/mock_importer.rb000066400000000000000000000016161222366545200177120ustar00rootroot00000000000000class 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 sass-3.2.12/test/sass/more_results/000077500000000000000000000000001222366545200172325ustar00rootroot00000000000000sass-3.2.12/test/sass/more_results/more1.css000066400000000000000000000005421222366545200207700ustar00rootroot00000000000000body { 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; } sass-3.2.12/test/sass/more_results/more1_with_line_comments.css000066400000000000000000000012761222366545200247440ustar00rootroot00000000000000/* 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; } sass-3.2.12/test/sass/more_results/more_import.css000066400000000000000000000017171222366545200223060ustar00rootroot00000000000000@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; } sass-3.2.12/test/sass/more_templates/000077500000000000000000000000001222366545200175275ustar00rootroot00000000000000sass-3.2.12/test/sass/more_templates/_more_partial.sass000066400000000000000000000000361222366545200232360ustar00rootroot00000000000000#foo :background-color #baf sass-3.2.12/test/sass/more_templates/more1.sass000066400000000000000000000004641222366545200214510ustar00rootroot00000000000000 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 brownsass-3.2.12/test/sass/more_templates/more_import.sass000066400000000000000000000003001222366545200227470ustar00rootroot00000000000000$preconst: hello =premixin pre-mixin: here @import importee, basic, basic.css, ../results/complex.css, more_partial nonimported :myconst $preconst :otherconst $postconst +postmixin sass-3.2.12/test/sass/plugin_test.rb000077500000000000000000000412011222366545200173720ustar00rootroot00000000000000#!/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(< { 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 < [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 sass-3.2.12/test/sass/results/000077500000000000000000000000001222366545200162105ustar00rootroot00000000000000sass-3.2.12/test/sass/results/alt.css000066400000000000000000000003551222366545200175050ustar00rootroot00000000000000h1 { 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; } sass-3.2.12/test/sass/results/basic.css000066400000000000000000000005421222366545200200040ustar00rootroot00000000000000body { 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; } sass-3.2.12/test/sass/results/cached_import_option.css000066400000000000000000000000661222366545200231150ustar00rootroot00000000000000partial { value: correct; } main { value: correct; } sass-3.2.12/test/sass/results/compact.css000066400000000000000000000003311222366545200203450ustar00rootroot00000000000000#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; } sass-3.2.12/test/sass/results/complex.css000066400000000000000000000153431222366545200203770ustar00rootroot00000000000000body { 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; } sass-3.2.12/test/sass/results/compressed.css000066400000000000000000000002221222366545200210620ustar00rootroot00000000000000#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} sass-3.2.12/test/sass/results/expanded.css000066400000000000000000000003601222366545200205110ustar00rootroot00000000000000#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; } sass-3.2.12/test/sass/results/filename_fn.css000066400000000000000000000005561222366545200211730ustar00rootroot00000000000000filename { 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; } sass-3.2.12/test/sass/results/if.css000066400000000000000000000000471222366545200173210ustar00rootroot00000000000000a { branch: if; } b { branch: else; } sass-3.2.12/test/sass/results/import.css000066400000000000000000000017501222366545200202370ustar00rootroot00000000000000@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; } sass-3.2.12/test/sass/results/import_charset.css000066400000000000000000000001101222366545200217350ustar00rootroot00000000000000@charset "UTF-8"; @import url(foo.css); .foo { a: b; } .bar { a: щ; } sass-3.2.12/test/sass/results/import_charset_1_8.css000066400000000000000000000001101222366545200224040ustar00rootroot00000000000000@charset "IBM866"; @import url(foo.css); .foo { a: b; } .bar { a: ; } sass-3.2.12/test/sass/results/import_charset_ibm866.css000066400000000000000000000001101222366545200230300ustar00rootroot00000000000000@charset "IBM866"; @import url(foo.css); .foo { a: b; } .bar { a: ; } sass-3.2.12/test/sass/results/import_content.css000066400000000000000000000000131222366545200217600ustar00rootroot00000000000000a { b: c; }sass-3.2.12/test/sass/results/line_numbers.css000066400000000000000000000021471222366545200214100ustar00rootroot00000000000000/* 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; } sass-3.2.12/test/sass/results/mixins.css000066400000000000000000000031371222366545200202350ustar00rootroot00000000000000#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; } sass-3.2.12/test/sass/results/multiline.css000066400000000000000000000006441222366545200207300ustar00rootroot00000000000000#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; } sass-3.2.12/test/sass/results/nested.css000066400000000000000000000005751222366545200202130ustar00rootroot00000000000000#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; } sass-3.2.12/test/sass/results/options.css000066400000000000000000000000271222366545200204140ustar00rootroot00000000000000foo { style: compact; }sass-3.2.12/test/sass/results/parent_ref.css000066400000000000000000000006731222366545200210550ustar00rootroot00000000000000a { 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; } sass-3.2.12/test/sass/results/script.css000066400000000000000000000021351222366545200202270ustar00rootroot00000000000000#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; } sass-3.2.12/test/sass/results/scss_import.css000066400000000000000000000017501222366545200212720ustar00rootroot00000000000000@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; } sass-3.2.12/test/sass/results/scss_importee.css000066400000000000000000000000321222366545200215740ustar00rootroot00000000000000scss { imported: yes; } sass-3.2.12/test/sass/results/subdir/000077500000000000000000000000001222366545200175005ustar00rootroot00000000000000sass-3.2.12/test/sass/results/subdir/nested_subdir/000077500000000000000000000000001222366545200223325ustar00rootroot00000000000000sass-3.2.12/test/sass/results/subdir/nested_subdir/nested_subdir.css000066400000000000000000000000251222366545200256730ustar00rootroot00000000000000#pi { width: 314px; }sass-3.2.12/test/sass/results/subdir/subdir.css000066400000000000000000000001141222366545200214760ustar00rootroot00000000000000#nested { relative: true; } #subdir { font-size: 20px; font-weight: bold; }sass-3.2.12/test/sass/results/units.css000066400000000000000000000002421222366545200200620ustar00rootroot00000000000000b { 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; } sass-3.2.12/test/sass/results/warn.css000066400000000000000000000000001222366545200176570ustar00rootroot00000000000000sass-3.2.12/test/sass/results/warn_imported.css000066400000000000000000000000001222366545200215620ustar00rootroot00000000000000sass-3.2.12/test/sass/script_conversion_test.rb000077500000000000000000000213761222366545200216600ustar00rootroot00000000000000#!/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 < :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(< 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 sass-3.2.12/test/sass/scss/000077500000000000000000000000001222366545200154625ustar00rootroot00000000000000sass-3.2.12/test/sass/scss/css_test.rb000077500000000000000000000566451222366545200176610ustar00rootroot00000000000000#!/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 < baz {bar: baz} SCSS end if Sass::Util.ruby1_8? def test_unicode assert_parses < 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(<)') assert_selector_can_contain_selectors(':current()') assert_selector_can_contain_selectors(':nth-match(n of )') assert_selector_can_contain_selectors(':nth-last-match(n of )') assert_selector_can_contain_selectors(':column()') assert_selector_can_contain_selectors(':-moz-any()') end def assert_selector_can_contain_selectors(sel) try = lambda {|subsel| assert_selector_parses(sel.gsub('', 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 < > 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", '@ import "foo";') assert_not_parses("identifier", '@12 "foo";') end def test_invalid_classes assert_not_parses("class name", 'p. foo {a: b}') assert_not_parses("class name", 'p.1foo {a: b}') end def test_invalid_ids assert_not_parses("id name", 'p# foo {a: b}') end def test_no_properties_at_toplevel assert_not_parses('pseudoclass or pseudoelement', 'a: b;') end def test_no_scss_directives assert_parses('@import "foo.sass";') assert_parses <$var = 12;") assert_not_parses('"}"', "foo { !var = 12; }") end def test_no_parent_selectors assert_not_parses('"{"', "foo &.bar {a: b}") end def test_no_selector_interpolation assert_not_parses('"{"', 'foo #{"bar"}.baz {a: b}') end def test_no_prop_name_interpolation assert_not_parses('":"', 'foo {a#{"bar"}baz: b}') end def test_no_prop_val_interpolation assert_not_parses('"}"', 'foo {a: b #{"bar"} c}') end def test_no_string_interpolation assert_parses <* c}') end def test_no_nested_rules assert_not_parses('":"', 'foo {bar {a: b}}') assert_not_parses('"}"', 'foo {[bar=baz] {a: b}}') end def test_no_nested_properties assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: {a: b}}') assert_not_parses('expression (e.g. 1px, bold)', 'foo {bar: bang {a: b}}') end def test_no_nested_directives assert_not_parses('"}"', 'foo {@bar {a: b}}') end def test_error_with_windows_newlines render < 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(<{a: b}") end def test_closing_line_comment_end_with_compact_output assert_equal(< :compact)) /* foo */ bar { baz: bang; } CSS /* * foo */ bar {baz: bang} SCSS end def test_single_line_comment_within_multiline_comment assert_equal(< 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 < e assert_equal < e assert_equal "Undefined mixin 'bar'.", e.message assert_equal 3, e.sass_line end def test_rules_beneath_properties render < 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 < 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 < 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 assert(false, "Expected syntax error") rescue Sass::SyntaxError => e assert_equal 'Invalid CSS after " .bar:baz ": expected "{", was "; }"', e.message assert_equal 2, e.sass_line end def test_uses_property_exception_with_space_after_name render < 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 < 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 < 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 < 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 < 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 < e assert_equal "Undefined variable: \"$bang\".", e.message assert_equal 4, e.sass_line end def test_post_multiline_script_runtime_error render < e assert_equal "Undefined variable: \"$bop\".", e.message assert_equal 5, e.sass_line end def test_multiline_property_runtime_error render < 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, < :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(< :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 < where an error is expected" unless scss.include?("") after, was = scss.split("") 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("", "") 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 sass-3.2.12/test/sass/templates/000077500000000000000000000000001222366545200165055ustar00rootroot00000000000000sass-3.2.12/test/sass/templates/_cached_import_option_partial.scss000066400000000000000000000000341222366545200254430ustar00rootroot00000000000000partial {value: whatever()} sass-3.2.12/test/sass/templates/_double_import_loop2.sass000066400000000000000000000000351222366545200235140ustar00rootroot00000000000000@import "double_import_loop1"sass-3.2.12/test/sass/templates/_filename_fn_import.scss000066400000000000000000000002271222366545200233770ustar00rootroot00000000000000@mixin imported-mixin { imported-mixin: filename(); } @function imported-function() { @return filename(); } filename { imported: filename(); } sass-3.2.12/test/sass/templates/_imported_charset_ibm866.sass000066400000000000000000000000371222366545200241660ustar00rootroot00000000000000@charset "IBM866" .bar a: sass-3.2.12/test/sass/templates/_imported_charset_utf8.sass000066400000000000000000000000371222366545200240410ustar00rootroot00000000000000@charset "UTF-8" .bar a: щ sass-3.2.12/test/sass/templates/_imported_content.sass000066400000000000000000000000341222366545200231110ustar00rootroot00000000000000@mixin foo a @content sass-3.2.12/test/sass/templates/_partial.sass000066400000000000000000000000361222366545200211720ustar00rootroot00000000000000#foo :background-color #baf sass-3.2.12/test/sass/templates/_same_name_different_partiality.scss000066400000000000000000000000231222366545200257510ustar00rootroot00000000000000.foo {partial: yes}sass-3.2.12/test/sass/templates/alt.sass000066400000000000000000000003631222366545200201620ustar00rootroot00000000000000h1 :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 sass-3.2.12/test/sass/templates/basic.sass000066400000000000000000000004621222366545200204630ustar00rootroot00000000000000 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 brownsass-3.2.12/test/sass/templates/bork1.sass000066400000000000000000000000231222366545200204110ustar00rootroot00000000000000bork :bork $bork sass-3.2.12/test/sass/templates/bork2.sass000066400000000000000000000000241222366545200204130ustar00rootroot00000000000000bork :bork: bork; sass-3.2.12/test/sass/templates/bork3.sass000066400000000000000000000000151222366545200204140ustar00rootroot00000000000000bork bork: sass-3.2.12/test/sass/templates/bork4.sass000066400000000000000000000000141222366545200204140ustar00rootroot00000000000000 bork: blah sass-3.2.12/test/sass/templates/bork5.sass000066400000000000000000000000371222366545200204220ustar00rootroot00000000000000bork /* foo */ :bork $bork sass-3.2.12/test/sass/templates/cached_import_option.scss000066400000000000000000000001021222366545200235640ustar00rootroot00000000000000@import "cached_import_option_partial"; main {value: whatever()} sass-3.2.12/test/sass/templates/compact.sass000066400000000000000000000003411222366545200210240ustar00rootroot00000000000000#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 sass-3.2.12/test/sass/templates/complex.sass000066400000000000000000000142251222366545200210530ustar00rootroot00000000000000body :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 sass-3.2.12/test/sass/templates/compressed.sass000066400000000000000000000002571222366545200215500ustar00rootroot00000000000000#main :width 15em :color #0000ff p :border :style dotted :width 2px .cool :width 100px #left :font :size 2em :weight bold :float left sass-3.2.12/test/sass/templates/double_import_loop1.sass000066400000000000000000000000351222366545200233540ustar00rootroot00000000000000@import "double_import_loop2"sass-3.2.12/test/sass/templates/expanded.sass000066400000000000000000000003411222366545200211660ustar00rootroot00000000000000#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 sass-3.2.12/test/sass/templates/filename_fn.scss000066400000000000000000000004541222366545200216500ustar00rootroot00000000000000@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(); } sass-3.2.12/test/sass/templates/if.sass000066400000000000000000000001541222366545200177760ustar00rootroot00000000000000a @if true branch: if @else branch: else b @if false branch: if @else branch: else sass-3.2.12/test/sass/templates/import.sass000066400000000000000000000003421222366545200207110ustar00rootroot00000000000000$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 sass-3.2.12/test/sass/templates/import_charset.sass000066400000000000000000000003011222366545200224150ustar00rootroot00000000000000.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" sass-3.2.12/test/sass/templates/import_charset_1_8.sass000066400000000000000000000001021222366545200230630ustar00rootroot00000000000000.foo a: b @import "foo.css" @import "imported_charset_ibm866" sass-3.2.12/test/sass/templates/import_charset_ibm866.sass000066400000000000000000000003151222366545200235150ustar00rootroot00000000000000@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" sass-3.2.12/test/sass/templates/import_content.sass000066400000000000000000000000561222366545200224450ustar00rootroot00000000000000@import imported_content @include foo b: c sass-3.2.12/test/sass/templates/importee.less000066400000000000000000000000361222366545200212200ustar00rootroot00000000000000.foo {a: b} .bar () {c: d} sass-3.2.12/test/sass/templates/importee.sass000066400000000000000000000003141222366545200212220ustar00rootroot00000000000000$postconst: goodbye =postmixin post-mixin: here imported :otherconst $preconst :myconst $postconst +premixin @import basic midrule :inthe middle =crazymixin foo: bar baz blat: bang sass-3.2.12/test/sass/templates/line_numbers.sass000066400000000000000000000001471222366545200220640ustar00rootroot00000000000000foo bar: baz =premixin squggle blat: bang $preconst: 12 @import importee umph +crazymixinsass-3.2.12/test/sass/templates/mixin_bork.sass000066400000000000000000000000601222366545200215350ustar00rootroot00000000000000=outer-mixin +error-mixin foo +outer-mixin sass-3.2.12/test/sass/templates/mixins.sass000066400000000000000000000016161222366545200207130ustar00rootroot00000000000000$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 sass-3.2.12/test/sass/templates/multiline.sass000066400000000000000000000003431222366545200214020ustar00rootroot00000000000000#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 blocksass-3.2.12/test/sass/templates/nested.sass000066400000000000000000000005171222366545200206650ustar00rootroot00000000000000#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 sass-3.2.12/test/sass/templates/nested_bork1.sass000066400000000000000000000000171222366545200217560ustar00rootroot00000000000000 @import bork1 sass-3.2.12/test/sass/templates/nested_bork2.sass000066400000000000000000000000171222366545200217570ustar00rootroot00000000000000 @import bork2 sass-3.2.12/test/sass/templates/nested_bork3.sass000066400000000000000000000000171222366545200217600ustar00rootroot00000000000000 @import bork3 sass-3.2.12/test/sass/templates/nested_bork4.sass000066400000000000000000000000171222366545200217610ustar00rootroot00000000000000 @import bork4 sass-3.2.12/test/sass/templates/nested_import.sass000066400000000000000000000000251222366545200222510ustar00rootroot00000000000000.foo @import basic sass-3.2.12/test/sass/templates/nested_mixin_bork.sass000066400000000000000000000000661222366545200231050ustar00rootroot00000000000000 =error-mixin width: 1px * 1em @import mixin_bork sass-3.2.12/test/sass/templates/options.sass000066400000000000000000000000341222366545200210700ustar00rootroot00000000000000foo style: option("style")sass-3.2.12/test/sass/templates/parent_ref.sass000066400000000000000000000005451222366545200215310ustar00rootroot00000000000000a :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 sass-3.2.12/test/sass/templates/same_name_different_ext.sass000066400000000000000000000000211222366545200242240ustar00rootroot00000000000000.foo ext: sass sass-3.2.12/test/sass/templates/same_name_different_ext.scss000066400000000000000000000000211222366545200242260ustar00rootroot00000000000000.foo {ext: scss} sass-3.2.12/test/sass/templates/same_name_different_partiality.scss000066400000000000000000000000221222366545200256110ustar00rootroot00000000000000.foo {partial: no}sass-3.2.12/test/sass/templates/script.sass000066400000000000000000000031261222366545200207060ustar00rootroot00000000000000$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 sass-3.2.12/test/sass/templates/scss_import.scss000066400000000000000000000004041222366545200217450ustar00rootroot00000000000000$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; } sass-3.2.12/test/sass/templates/scss_importee.scss000066400000000000000000000000251222366545200222560ustar00rootroot00000000000000scss {imported: yes} sass-3.2.12/test/sass/templates/single_import_loop.sass000066400000000000000000000000341222366545200233010ustar00rootroot00000000000000@import "single_import_loop"sass-3.2.12/test/sass/templates/subdir/000077500000000000000000000000001222366545200177755ustar00rootroot00000000000000sass-3.2.12/test/sass/templates/subdir/nested_subdir/000077500000000000000000000000001222366545200226275ustar00rootroot00000000000000sass-3.2.12/test/sass/templates/subdir/nested_subdir/_nested_partial.sass000066400000000000000000000000311222366545200266510ustar00rootroot00000000000000#nested :relative true sass-3.2.12/test/sass/templates/subdir/nested_subdir/nested_subdir.sass000066400000000000000000000000271222366545200263530ustar00rootroot00000000000000#pi :width 314px sass-3.2.12/test/sass/templates/subdir/subdir.sass000066400000000000000000000001331222366545200221550ustar00rootroot00000000000000@import nested_subdir/nested_partial.sass #subdir :font :size 20px :weight bold sass-3.2.12/test/sass/templates/units.sass000066400000000000000000000005001222366545200205350ustar00rootroot00000000000000b :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 sass-3.2.12/test/sass/templates/warn.sass000066400000000000000000000001051222366545200203430ustar00rootroot00000000000000@warn "In the main file" @import warn_imported.sass +emits-a-warning sass-3.2.12/test/sass/templates/warn_imported.sass000066400000000000000000000001021222366545200222430ustar00rootroot00000000000000@warn "Imported" =emits-a-warning @warn "In an imported mixin" sass-3.2.12/test/sass/test_helper.rb000066400000000000000000000003121222366545200173460ustar00rootroot00000000000000test_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 sass-3.2.12/test/sass/util/000077500000000000000000000000001222366545200154645ustar00rootroot00000000000000sass-3.2.12/test/sass/util/multibyte_string_scanner_test.rb000077500000000000000000000073671222366545200242050ustar00rootroot00000000000000#!/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 sass-3.2.12/test/sass/util/subset_map_test.rb000077500000000000000000000052171222366545200212220ustar00rootroot00000000000000#!/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 sass-3.2.12/test/sass/util_test.rb000077500000000000000000000250631222366545200170610ustar00rootroot00000000000000#!/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 sass-3.2.12/test/test_helper.rb000066400000000000000000000035771222366545200164150ustar00rootroot00000000000000lib_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 sass-3.2.12/vendor/000077500000000000000000000000001222366545200140545ustar00rootroot00000000000000sass-3.2.12/vendor/listen/000077500000000000000000000000001222366545200153525ustar00rootroot00000000000000sass-3.2.12/yard/000077500000000000000000000000001222366545200135165ustar00rootroot00000000000000sass-3.2.12/yard/callbacks.rb000066400000000000000000000014601222366545200157630ustar00rootroot00000000000000class CallbacksHandler < YARD::Handlers::Ruby::Legacy::Base handles /\Adefine_callback(\s|\()/ def process callback_name = tokval(statement.tokens[2]) attr_index = statement.comments.each_with_index {|c, i| break i if c[0] == ?@} if attr_index.is_a?(Fixnum) docstring = statement.comments[0...attr_index] attrs = statement.comments[attr_index..-1] else docstring = statement.comments attrs = [] end yieldparams = "" attrs.reject! do |a| next unless a =~ /^@yield *(\[.*?\])/ yieldparams = $1 true end o = register(MethodObject.new(namespace, "on_#{callback_name}", scope)) o.docstring = docstring + [ "@return [void]", "@yield #{yieldparams} When the callback is run" ] + attrs o.signature = true end end sass-3.2.12/yard/default/000077500000000000000000000000001222366545200151425ustar00rootroot00000000000000sass-3.2.12/yard/default/.gitignore000066400000000000000000000000061222366545200171260ustar00rootroot00000000000000*.css sass-3.2.12/yard/default/fulldoc/000077500000000000000000000000001222366545200165725ustar00rootroot00000000000000sass-3.2.12/yard/default/fulldoc/html/000077500000000000000000000000001222366545200175365ustar00rootroot00000000000000sass-3.2.12/yard/default/fulldoc/html/css/000077500000000000000000000000001222366545200203265ustar00rootroot00000000000000sass-3.2.12/yard/default/fulldoc/html/css/common.sass000066400000000000000000000007141222366545200225130ustar00rootroot00000000000000.maruku_toc background: #ddd border: 1px solid #ccc margin-right: 2em float: left ul padding: 0 1em #frequently_asked_questions + & float: none margin: 0 2em #filecontents *:target, dt:target + dd background-color: #ccf border: 1px solid #88b dt font-weight: bold dd margin: left: 0 bottom: 0.7em padding-left: 3em dt:target border-bottom-style: none & + dd border-top-style: none sass-3.2.12/yard/default/layout/000077500000000000000000000000001222366545200164575ustar00rootroot00000000000000sass-3.2.12/yard/default/layout/html/000077500000000000000000000000001222366545200174235ustar00rootroot00000000000000sass-3.2.12/yard/default/layout/html/footer.erb000066400000000000000000000007551222366545200214220ustar00rootroot00000000000000<%= superb :footer %> <% if ENV["ANALYTICS"] %> <% end %> sass-3.2.12/yard/inherited_hash.rb000066400000000000000000000027231222366545200170250ustar00rootroot00000000000000class InheritedHashHandler < YARD::Handlers::Ruby::Legacy::Base handles /\Ainherited_hash(\s|\()/ def process hash_name = tokval(statement.tokens[2]) name = statement.comments.first.strip type = statement.comments[1].strip o = register(MethodObject.new(namespace, hash_name, scope)) o.docstring = [ "Gets a #{name} from this {Environment} or one of its \\{#parent}s.", "@param name [String] The name of the #{name}", "@return [#{type}] The #{name} value", ] o.signature = true o.parameters = ["name"] o = register(MethodObject.new(namespace, "set_#{hash_name}", scope)) o.docstring = [ "Sets a #{name} in this {Environment} or one of its \\{#parent}s.", "If the #{name} is already defined in some environment,", "that one is set; otherwise, a new one is created in this environment.", "@param name [String] The name of the #{name}", "@param value [#{type}] The value of the #{name}", "@return [#{type}] `value`", ] o.signature = true o.parameters = ["name", "value"] o = register(MethodObject.new(namespace, "set_local_#{hash_name}", scope)) o.docstring = [ "Sets a #{name} in this {Environment}.", "Ignores any parent environments.", "@param name [String] The name of the #{name}", "@param value [#{type}] The value of the #{name}", "@return [#{type}] `value`", ] o.signature = true o.parameters = ["name", "value"] end end