/BundleTransformer.SassAndScss/Resources/sass_in_one.rb
Ruby | 12296 lines | 6778 code | 1263 blank | 4255 comment | 960 complexity | 0f37ab1933d39476db91811877f06a4d MD5 | raw file
Possible License(s): Apache-2.0, CC-BY-SA-3.0, BSD-2-Clause, BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- # 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)
- # This is necessary for loading Sass when Haml is required in Rails 3.
- # Once the split is complete, we can remove it.
- #require 'erb'
- require 'set'
- require 'enumerator'
- require 'stringio'
- require 'strscan'
- require 'rbconfig'
- module Sass
- # The root directory of the Sass source tree.
- # This may be overridden by the package manager
- # if the lib directory is separated from the main source tree.
- # @api public
- ROOT_DIR = File.expand_path(File.join(__FILE__, "../../.."))
- end
- require 'set'
- module Sass
- module Util
- # A map from sets to values.
- # A value is \{#\[]= set} by providing a set (the "set-set") and a value,
- # which is then recorded as corresponding to that set.
- # Values are \{#\[] accessed} by providing a set (the "get-set")
- # and returning all values that correspond to set-sets
- # that are subsets of the get-set.
- #
- # SubsetMap preserves the order of values as they're inserted.
- #
- # @example
- # ssm = SubsetMap.new
- # ssm[Set[1, 2]] = "Foo"
- # ssm[Set[2, 3]] = "Bar"
- # ssm[Set[1, 2, 3]] = "Baz"
- #
- # ssm[Set[1, 2, 3]] #=> ["Foo", "Bar", "Baz"]
- class SubsetMap
- # Creates a new, empty SubsetMap.
- def initialize
- @hash = {}
- @vals = []
- end
- # Whether or not this SubsetMap has any key-value pairs.
- #
- # @return [Boolean]
- def empty?
- @hash.empty?
- end
- # Associates a value with a set.
- # When `set` or any of its supersets is accessed,
- # `value` will be among the values returned.
- #
- # Note that if the same `set` is passed to this method multiple times,
- # all given `value`s will be associated with that `set`.
- #
- # This runs in `O(n)` time, where `n` is the size of `set`.
- #
- # @param set [#to_set] The set to use as the map key. May not be empty.
- # @param value [Object] The value to associate with `set`.
- # @raise [ArgumentError] If `set` is empty.
- def []=(set, value)
- raise ArgumentError.new("SubsetMap keys may not be empty.") if set.empty?
- index = @vals.size
- @vals << value
- set.each do |k|
- @hash[k] ||= []
- @hash[k] << [set, set.to_set, index]
- end
- end
- # Returns all values associated with subsets of `set`.
- #
- # In the worst case, this runs in `O(m*max(n, log m))` time,
- # where `n` is the size of `set`
- # and `m` is the number of assocations in the map.
- # However, unless many keys in the map overlap with `set`,
- # `m` will typically be much smaller.
- #
- # @param set [Set] The set to use as the map key.
- # @return [Array<(Object, #to_set)>] An array of pairs,
- # where the first value is the value associated with a subset of `set`,
- # and the second value is that subset of `set`
- # (or whatever `#to_set` object was used to set the value)
- # This array is in insertion order.
- # @see #[]
- def get(set)
- res = set.map do |k|
- next unless subsets = @hash[k]
- subsets.map do |subenum, subset, index|
- next unless subset.subset?(set)
- [index, subenum]
- end
- end
- res = Sass::Util.flatten(res, 1)
- res.compact!
- res.uniq!
- res.sort!
- res.map! {|i, s| [@vals[i], s]}
- return res
- end
- # Same as \{#get}, but doesn't return the subsets of the argument
- # for which values were found.
- #
- # @param set [Set] The set to use as the map key.
- # @return [Array] The array of all values
- # associated with subsets of `set`, in insertion order.
- # @see #get
- def [](set)
- get(set).map {|v, _| v}
- end
- end
- end
- end
- 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, &block)
- to_hash(hash.map(&block))
- end
- # Computes the powerset of the given array.
- # This is the set of all subsets of the array.
- #
- # @example
- # powerset([1, 2, 3]) #=>
- # Set[Set[], Set[1], Set[2], Set[3], Set[1, 2], Set[2, 3], Set[1, 3], Set[1, 2, 3]]
- # @param arr [Enumerable]
- # @return [Set<Set>] The subsets of `arr`
- def powerset(arr)
- arr.inject([Set.new].to_set) do |powerset, el|
- new_powerset = Set.new
- powerset.each do |subset|
- new_powerset << subset
- new_powerset << subset + [el]
- end
- new_powerset
- end
- end
- # Restricts a number to falling within a given range.
- # Returns the number if it falls within the range,
- # or the closest value in the range if it doesn't.
- #
- # @param value [Numeric]
- # @param range [Range<Numeric>]
- # @return [Numeric]
- def restrict(value, range)
- [[value, range.first].max, range.last].min
- end
- # Concatenates all strings that are adjacent in an array,
- # while leaving other elements as they are.
- #
- # @example
- # merge_adjacent_strings([1, "foo", "bar", 2, "baz"])
- # #=> [1, "foobar", 2, "baz"]
- # @param arr [Array]
- # @return [Array] The enumerable with strings merged
- def merge_adjacent_strings(arr)
- # Optimize for the common case of one element
- return arr if arr.size < 2
- arr.inject([]) do |a, e|
- if e.is_a?(String)
- if a.last.is_a?(String)
- a.last << e
- else
- a << e.dup
- end
- else
- a << e
- end
- a
- end
- end
- # Intersperses a value in an enumerable, as would be done with `Array#join`
- # but without concatenating the array together afterwards.
- #
- # @param enum [Enumerable]
- # @param val
- # @return [Array]
- def intersperse(enum, val)
- enum.inject([]) {|a, e| a << e << val}[0...-1]
- end
- # Substitutes a sub-array of one array with another sub-array.
- #
- # @param ary [Array] The array in which to make the substitution
- # @param from [Array] The sequence of elements to replace with `to`
- # @param to [Array] The sequence of elements to replace `from` with
- def substitute(ary, from, to)
- res = ary.dup
- i = 0
- while i < res.size
- if res[i...i+from.size] == from
- res[i...i+from.size] = to
- end
- i += 1
- end
- res
- end
- # Destructively strips whitespace from the beginning and end
- # of the first and last elements, respectively,
- # in the array (if those elements are strings).
- #
- # @param arr [Array]
- # @return [Array] `arr`
- def strip_string_array(arr)
- arr.first.lstrip! if arr.first.is_a?(String)
- arr.last.rstrip! if arr.last.is_a?(String)
- arr
- end
- # Return an array of all possible paths through the given arrays.
- #
- # @param arrs [Array<Array>]
- # @return [Array<Arrays>]
- #
- # @example
- # paths([[1, 2], [3, 4], [5]]) #=>
- # # [[1, 3, 5],
- # # [2, 3, 5],
- # # [1, 4, 5],
- # # [2, 4, 5]]
- def paths(arrs)
- arrs.inject([[]]) do |paths, arr|
- flatten(arr.map {|e| paths.map {|path| path + [e]}}, 1)
- end
- end
- # Computes a single longest common subsequence for `x` and `y`.
- # If there are more than one longest common subsequences,
- # the one returned is that which starts first in `x`.
- #
- # @param x [Array]
- # @param y [Array]
- # @yield [a, b] An optional block to use in place of a check for equality
- # between elements of `x` and `y`.
- # @yieldreturn [Object, nil] If the two values register as equal,
- # this will return the value to use in the LCS array.
- # @return [Array] The LCS
- def lcs(x, y, &block)
- x = [nil, *x]
- y = [nil, *y]
- block ||= proc {|a, b| a == b && a}
- lcs_backtrace(lcs_table(x, y, &block), x, y, x.size-1, y.size-1, &block)
- end
- # 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 = 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_silence_warnings = @@silence_warnings
- @@silence_warnings = true
- yield
- ensure
- @@silence_warnings = old_silence_warnings
- end
- # The same as `Kernel#warn`, but is silenced by \{#silence\_sass\_warnings}.
- #
- # @param msg [String]
- def sass_warn(msg)
- return if @@silence_warnings
- 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
- ## Cross-Ruby-Version Compatibility
- # 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
- # Checks that the encoding of a string is valid in Ruby 1.9
- # and cleans up potential encoding gotchas like the UTF-8 BOM.
- # If it's not, yields an error string describing the invalid character
- # and the line on which it occurrs.
- #
- # @param str [String] The string of which to check the encoding
- # @yield [msg] A block in which an encoding error can be raised.
- # Only yields if there is an encoding error
- # @yieldparam msg [String] The error message to be raised
- # @return [String] `str`, potentially with encoding gotchas like BOMs removed
- def check_encoding(str)
- if ruby1_8?
- return str.gsub(/\A\xEF\xBB\xBF/, '') # Get rid of the UTF-8 BOM
- elsif str.valid_encoding?
- # Get rid of the Unicode BOM if possible
- if str.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?$/
- return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
- else
- return str
- end
- end
- encoding = str.encoding
- newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
- str.force_encoding("binary").split(newlines).each_with_index do |line, i|
- begin
- line.encode(encoding)
- rescue Encoding::UndefinedConversionError => e
- yield <<MSG.rstrip, i + 1
- Invalid #{encoding.name} character #{e.error_char.dump}
- MSG
- end
- end
- return str
- end
- # Like {\#check\_encoding}, but also checks for a `@charset` declaration
- # at the beginning of the file and uses that encoding if it exists.
- #
- # The Sass encoding rules are simple.
- # If a `@charset` declaration exists,
- # we assume that that's the original encoding of the document.
- # Otherwise, we use whatever encoding Ruby has.
- # Then we convert that to UTF-8 to process internally.
- # The UTF-8 end result is what's returned by this method.
- #
- # @param str [String] The string of which to check the encoding
- # @yield [msg] A block in which an encoding error can be raised.
- # Only yields if there is an encoding error
- # @yieldparam msg [String] The error message to be raised
- # @return [(String, Encoding)] The original string encoded as UTF-8,
- # and the source encoding of the string (or `nil` under Ruby 1.8)
- # @raise [Encoding::UndefinedConversionError] if the source encoding
- # cannot be converted to UTF-8
- # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
- def check_sass_encoding(str, &block)
- return check_encoding(str, &block), nil if ruby1_8?
- # We allow any printable ASCII characters but double quotes in the charset decl
- bin = str.dup.force_encoding("BINARY")
- encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
- bin =~ Sass::Util::CHARSET_REGEXPS[enc]
- end
- charset, bom = $1, $2
- if charset
- charset = charset.force_encoding(encoding).encode("UTF-8")
- if endianness = encoding[/[BL]E$/]
- begin
- Encoding.find(charset + endianness)
- charset << endianness
- rescue ArgumentError # Encoding charset + endianness doesn't exist
- end
- end
- str.force_encoding(charset)
- elsif bom
- str.force_encoding(encoding)
- end
- str = check_encoding(str, &block)
- return str.encode("UTF-8"), str.encoding
- end
- unless ruby1_8?
- # @private
- def _enc(string, encoding)
- string.encode(encoding).force_encoding("BINARY")
- end
- # We could automatically add in any non-ASCII-compatible encodings here,
- # but there's not really a good way to do that
- # without manually checking that each encoding
- # encodes all ASCII characters properly,
- # which takes long enough to affect the startup time of the CLI.
- ENCODINGS_TO_CHECK = %w[UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE]
- CHARSET_REGEXPS = Hash.new do |h, e|
- h[e] =
- begin
- # /\A(?:\uFEFF)?@charset "(.*?)"|\A(\uFEFF)/
- Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
- _enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
- _enc("\uFEFF", e)})/)
- rescue
- # /\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
- # 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
- ## 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
- 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
- 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.
- # 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",
- # :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",
- # :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('.').
- numbers = "3.2.0.alpha.0".strip.split('.').
- map {|n| n =~ /^[0-9]+$/ ? n.to_i : n}
- name = "Bleeding Edge"
- @@version = {
- :major => numbers[0],
- :minor => numbers[1],
- :teeny => numbers[2],
- :name => name
- }
- 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
- 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
- # 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
- # 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 'strscan'
- require 'set'
- require 'digest/sha1'
- require 'stringio'
- module Sass
- # Sass cache stores are in charge of storing cached information,
- # especially parse trees for Sass documents.
- #
- # User-created importers must inherit from {CacheStores::Base}.
- module CacheStores
- end
- end
- module Sass
- module CacheStores
- # An abstract base class for backends for the Sass cache.
- # Any key-value store can act as such a backend;
- # it just needs to implement the
- # \{#_store} and \{#_retrieve} methods.
- #
- # To use a cache store with Sass,
- # use the {file:SASS_REFERENCE.md#cache_store-option `:cache_store` option}.
- #
- # @abstract
- class Base
- # Store cached contents for later retrieval
- # Must be implemented by all CacheStore subclasses
- #
- # Note: cache contents contain binary data.
- #
- # @param key [String] The key to store the contents under
- # @param version [String] The current sass version.
- # Cached contents must not be retrieved across different versions of sass.
- # @param sha [String] The sha of the sass source.
- # Cached contents must not be retrieved if the sha has changed.
- # @param contents [String] The contents to store.
- def _store(key, version, sha, contents)
- raise "#{self.class} must implement #_store."
- end
- # Retrieved cached contents.
- # Must be implemented by all subclasses.
- #
- # Note: if the key exists but the sha or version have changed,
- # then the key may be deleted by the cache store, if it wants to do so.
- #
- # @param key [String] The key to retrieve
- # @param version [String] The current sass version.
- # Cached contents must not be retrieved across different versions of sass.
- # @param sha [String] The sha of the sass source.
- # Cached contents must not be retrieved if the sha has changed.
- # @return [String] The contents that were previously stored.
- # @return [NilClass] when the cache key is not found or the version or sha have changed.
- def _retrieve(key, version, sha)
- raise "#{self.class} must implement #_retrieve."
- end
- # Store a {Sass::Tree::RootNode}.
- #
- # @param key [String] The key to store it under.
- # @param sha [String] The checksum for the contents that are being stored.
- # @param obj [Object] The object to cache.
- def store(key, sha, root)
- _store(key, Sass::VERSION, sha, Marshal.dump(root))
- rescue TypeError, LoadError => e
- Sass::Util.sass_warn "Warning. Error encountered while saving cache #{path_to(key)}: #{e}"
- 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}"
- 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
- 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))
- contents = nil
- 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))
- File.open(compiled_filename, "wb") 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)
- File.join(cache_location, key)
- end
- end
- end
- end
- module Sass
- module CacheStores
- # A backend for the Sass cache using in-process memory.
- class Memory < Base
- # Since the {Memory} store is stored in the Sass tree's options hash,
- # when the options get serialized as part of serializing the tree,
- # you get crazy exponential growth in the size of the cached objects
- # unless you don't dump the cache.
- #
- # @private
- def _dump(depth)
- ""
- end
- # If we deserialize this class, just make a new empty one.
- #
- # @private
- def self._load(repr)
- Memory.new
- end
- # Create a new, empty cache store.
- def initialize
- @contents = {}
- end
- # @see Base#retrieve
- def retrieve(key, sha)
- if @contents.has_key?(key)
- return unless @contents[key][:sha] == sha
- obj = @contents[key][:obj]
- obj.respond_to?(:deep_copy) ? obj.deep_copy : obj.dup
- end
- end
- # @see Base#store
- def store(key, sha, obj)
- @contents[key] = {:sha => sha, :obj => obj}
- end
-
- # Destructively clear the cache.
- def reset!
- @contents = {}
- end
- end
- end
- end
- module Sass
- module CacheStores
- # A meta-cache that chains multiple caches together.
- # Specifically:
- #
- # * All `#store`s are passed to all caches.
- # * `#retrieve`s are passed to each cache until one has a hit.
- # * When one cache has a hit, the value is `#store`d in all earlier caches.
- class Chain < Base
- # Create a new cache chaining the given caches.
- #
- # @param caches [Array<Sass::CacheStores::Base>] The caches to chain.
- def initialize(*caches)
- @caches = caches
- end
- # @see Base#store
- def store(key, sha, obj)
- @caches.each {|c| c.store(key, sha, obj)}
- end
- # @see Base#retrieve
- def retrieve(key, sha)
- @caches.each_with_index do |c, i|
- next unless obj = c.retrieve(key, sha)
- @caches[0...i].each {|c| c.store(key, sha, obj)}
- return obj
- end
- nil
- end
- end
- end
- end
- module Sass
- # A namespace for nodes in the Sass parse tree.
- #
- # The Sass parse tree has three states: dynamic, static Sass, and static CSS.
- #
- # When it's first parsed, a Sass document is in the dynamic state.
- # It has nodes for mixin definitions and `@for` loops and so forth,
- # in addition to nodes for CSS rules and properties.
- # Nodes that only appear in this state are called **dynamic nodes**.
- #
- # {Tree::Visitors::Perform} creates a static Sass tree, which is different.
- # It still has nodes for CSS rules and properties
- # but it doesn't have any dynamic-generation-related nodes.
- # The nodes in this state are in the same structure as the Sass document:
- # rules and properties are nested beneath one another.
- # Nodes that can be in this state or in the dynamic state
- # are called **static nodes**.
- #
- # {Tree::Visitors::Cssize} is then used to create a static CSS tree.
- # This is like a static Sass tree,
- # but the structure exactly mirrors that of the generated CSS.
- # Rules and properties can't be nested beneath one another in this state.
- #
- # Finally, {Tree::Visitors::ToCss} can be called on a static CSS tree
- # to get the actual CSS code as a string.
- module Tree
- # The abstract superclass of all parse-tree nodes.
- class Node
- include Enumerable
- # The child nodes of this node.
- #
- # @return [Array<Tree::Node>]
- attr_accessor :children
- # Whether or not this node has child nodes.
- # This may be true even when \{#children} is empty,
- # in which case this node has an empty block (e.g. `{}`).
- #
- # @return [Boolean]
- attr_accessor :has_children
- # The line of the document on which this node appeared.
- #
- # @return [Fixnum]
- attr_accessor :line
- # The name of the document on which this node appeared.
- #
- # @return [String]
- attr_writer :filename
- # The options hash for the node.
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
- #
- # @return [{Symbol => Object}]
- attr_reader :options
- def initialize
- @children = []
- end
- # Sets the options hash for the node and all its children.
- #
- # @param options [{Symbol => Object}] The options
- # @see #options
- def options=(options)
- children.each {|c| c.options = options}
- @options = options
- end
- # @private
- def children=(children)
- self.has_children ||= !children.empty?
- @children = children
- end
- # The name of the document on which this node appeared.
- #
- # @return [String]
- def filename
- @filename || (@options && @options[:filename])
- end
- # Appends a child to the node.
- #
- # @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
- # @raise [Sass::SyntaxError] if `child` is invalid
- def <<(child)
- return if child.nil?
- if child.is_a?(Array)
- child.each {|c| self << c}
- else
- self.has_children = true
- @children << child
- end
- end
- # Compares this node and another object (only other {Tree::Node}s will be equal).
- # This does a structural comparison;
- # if the contents of the nodes and all the child nodes are equivalent,
- # then the nodes are as well.
- #
- # Only static nodes need to override this.
- #
- # @param other [Object] The object to compare with
- # @return [Boolean] Whether or not this node and the other object
- # are the same
- # @see Sass::Tree
- def ==(other)
- self.class == other.class && other.children == children
- end
- # True if \{#to\_s} will return `nil`;
- # that is, if the node shouldn't be rendered.
- # Should only be called in a static tree.
- #
- # @return [Boolean]
- def invisible?; false; end
- # The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
- #
- # @return [Symbol]
- def style
- @options[:style]
- end
- # Computes the CSS corresponding to this static CSS tree.
- #
- # @return [String, nil] The resulting CSS
- # @see Sass::Tree
- def to_s
- Sass::Tree::Visitors::ToCss.visit(self)
- end
- # Converts a static CSS tree (e.g. the output of \{Tree::Visitors::Cssize})
- # into another static CSS tree,
- # with the given extensions applied to all relevant {RuleNode}s.
- #
- # @todo Link this to the reference documentation on `@extend`
- # when such a thing exists.
- #
- # @param extends [Sass::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
- # The extensions to perform on this tree
- # @return [Tree::Node] The resulting tree of static CSS nodes.
- # @raise [Sass::SyntaxError] Only if there's a programmer error
- # and this is not a static CSS tree
- def do_extend(extends)
- node = dup
- node.children = children.map {|c| c.do_extend(extends)}
- node
- rescue Sass::SyntaxError => e
- e.modify_backtrace(:filename => filename, :line => line)
- raise e
- 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(&block)
- yield self
- children.each {|c| c.each(&block)}
- 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
- node = dup
- node.children = children.map {|c| c.deep_copy}
- node
- 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
- module Sass
- module Tree
- # A static node that is the root node of the Sass document.
- class RootNode < Node
- # The Sass template from which this node was created
- #
- # @param template [String]
- attr_reader :template
- # @param template [String] The Sass template from which this node was created
- def initialize(template)
- super()
- @template = template
- end
- # Runs the dynamic Sass code *and* computes the CSS for the tree.
- # @see #to_s
- def render
- Visitors::CheckNesting.visit(self)
- result = Visitors::Perform.visit(self)
- Visitors::CheckNesting.visit(result) # Check again to validate mixins
- result, extends = Visitors::Cssize.visit(result)
- result = result.do_extend(extends) unless extends.empty?
- result.to_s
- end
- end
- end
- end
- require 'pathname'
- require 'uri'
- module Sass::Tree
- # A static node reprenting a CSS rule.
- #
- # @see Sass::Tree
- class RuleNode < Node
- # The character used to include the parent selector
- PARENT = '&'
- # The CSS selector for this rule,
- # interspersed with {Sass::Script::Node}s
- # representing `#{}`-interpolation.
- # Any adjacent strings will be merged together.
- #
- # @return [Array<String, Sass::Script::Node>]
- attr_accessor :rule
- # The CSS selector for this rule,
- # without any unresolved interpolation
- # but with parent references still intact.
- # It's only set once {Tree::Node#perform} has been called.
- #
- # @return [Selector::CommaSequence]
- attr_accessor :parsed_rules
- # The CSS selector for this rule,
- # without any unresolved interpolation or parent references.
- # It's only set once {Tree::Visitors::Cssize} has been run.
- #
- # @return [Selector::CommaSequence]
- attr_accessor :resolved_rules
- # How deep this rule is indented
- # relative to a base-level rule.
- # This is only greater than 0 in the case that:
- #
- # * This node is in a CSS tree
- # * The style is :nested
- # * This is a child rule of another rule
- # * The parent rule has properties, and thus will be rendered
- #
- # @return [Fixnum]
- attr_accessor :tabs
- # Whether or not this rule is the last rule in a nested group.
- # This is only set in a CSS tree.
- #
- # @return [Boolean]
- attr_accessor :group_end
- # @param rule [Array<String, Sass::Script::Node>]
- # The CSS rule. See \{#rule}
- def initialize(rule)
- merged = Sass::Util.merge_adjacent_strings(rule)
- @rule = Sass::Util.strip_string_array(merged)
- @tabs = 0
- try_to_parse_non_interpolated_rules
- super()
- end
- …
Large files files are truncated, but you can click here to view the full file