2009-04-22 00:02:07 -07:00
|
|
|
require 'digest/sha1'
|
2009-06-01 00:31:46 -07:00
|
|
|
require 'pathname'
|
2009-11-26 02:55:55 -08:00
|
|
|
require 'fileutils'
|
2009-04-22 00:02:07 -07:00
|
|
|
|
|
|
|
module Sass
|
|
|
|
# This module contains various bits of functionality
|
2009-04-22 16:42:44 -07:00
|
|
|
# related to finding and caching Sass files.
|
2009-04-22 00:02:07 -07:00
|
|
|
module Files
|
|
|
|
extend self
|
|
|
|
|
2009-05-10 02:04:40 -07:00
|
|
|
# Returns the {Sass::Tree} for the given file,
|
|
|
|
# reading it from the Sass cache if possible.
|
|
|
|
#
|
2009-12-31 20:22:15 -08:00
|
|
|
# @param filename [String] The path to the Sass or SCSS file
|
2009-12-01 18:25:49 -08:00
|
|
|
# @param options [{Symbol => Object}] The options hash.
|
2009-06-18 13:40:57 -07:00
|
|
|
# Only the {file:SASS_REFERENCE.md#cache-option `:cache_location`} option is used
|
2009-09-13 15:52:59 -07:00
|
|
|
# @raise [Sass::SyntaxError] if there's an error in the document.
|
|
|
|
# The caller has responsibility for setting backtrace information, if necessary
|
2009-04-22 00:02:07 -07:00
|
|
|
def tree_for(filename, options)
|
|
|
|
options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
|
|
|
|
text = File.read(filename)
|
|
|
|
|
2009-04-22 16:41:55 -07:00
|
|
|
if options[:cache]
|
|
|
|
compiled_filename = sassc_filename(filename, options)
|
|
|
|
sha = Digest::SHA1.hexdigest(text)
|
|
|
|
|
2009-05-19 08:30:06 -07:00
|
|
|
if root = try_to_read_sassc(filename, compiled_filename, sha)
|
2009-05-13 14:09:35 -07:00
|
|
|
root.options = options.merge(:filename => filename)
|
|
|
|
return root
|
2009-04-22 16:41:55 -07:00
|
|
|
end
|
2009-04-22 00:02:07 -07:00
|
|
|
end
|
|
|
|
|
2009-12-21 23:39:46 -08:00
|
|
|
options = options.merge(:filename => filename)
|
|
|
|
if filename =~ /\.scss$/
|
|
|
|
options = options.merge(:syntax => :scss)
|
|
|
|
elsif filename =~ /\.sass$/
|
|
|
|
options = options.merge(:syntax => :sass)
|
|
|
|
end
|
|
|
|
|
|
|
|
engine = Sass::Engine.new(text, options)
|
2009-04-22 00:02:07 -07:00
|
|
|
|
2009-09-13 15:52:59 -07:00
|
|
|
root = engine.to_tree
|
2009-04-22 16:41:55 -07:00
|
|
|
try_to_write_sassc(root, compiled_filename, sha, options) if options[:cache]
|
2009-04-22 00:02:07 -07:00
|
|
|
root
|
|
|
|
end
|
|
|
|
|
2009-12-31 20:22:15 -08:00
|
|
|
# Find the full filename of a Sass, SCSS, or CSS file to import.
|
2009-05-10 02:04:40 -07:00
|
|
|
# This follows Sass's import rules:
|
2009-12-31 20:22:15 -08:00
|
|
|
# if the filename given ends in `".sass"`, `".scss"`, or `".css"`,
|
2009-05-10 02:04:40 -07:00
|
|
|
# it will try to find that type of file;
|
2009-12-31 20:22:15 -08:00
|
|
|
# otherwise, it will try to find the corresponding Sass/SCSS file
|
2009-05-10 02:04:40 -07:00
|
|
|
# and fall back on CSS if it's not available.
|
|
|
|
#
|
2009-12-31 20:22:15 -08:00
|
|
|
# Any Sass/SCSS filename returned will correspond to
|
|
|
|
# an actual file of the corresponding type on the filesystem.
|
2009-05-10 02:04:40 -07:00
|
|
|
# CSS filenames, however, may not;
|
|
|
|
# they're expected to be put through directly to the stylesheet
|
|
|
|
# as CSS `@import` statements.
|
|
|
|
#
|
|
|
|
# @param filename [String] The filename to search for
|
|
|
|
# @param load_paths [Array<String>] The set of filesystem paths
|
2009-12-31 20:22:15 -08:00
|
|
|
# to search for Sass/SCSS files.
|
2009-05-10 02:04:40 -07:00
|
|
|
# @return [String] The filename of the imported file.
|
2009-12-24 01:47:12 -08:00
|
|
|
# This is an absolute path if the file is a `".sass"` or `".scss"` file.
|
|
|
|
# @raise [Sass::SyntaxError] if `filename` ends in `".sass"` or `".scss"`
|
2009-12-31 20:22:15 -08:00
|
|
|
# and no corresponding Sass/SCSS file could be found.
|
2009-04-22 00:02:07 -07:00
|
|
|
def find_file_to_import(filename, load_paths)
|
2009-12-21 23:39:46 -08:00
|
|
|
was_sass = was_scss = false
|
2009-04-22 00:02:07 -07:00
|
|
|
original_filename = filename
|
|
|
|
|
2009-12-21 23:39:46 -08:00
|
|
|
if [".sass", ".scss"].include?(filename[-5..-1])
|
|
|
|
was_sass = filename[-5..-1] == ".sass"
|
|
|
|
was_scss = filename[-5..-1] == ".scss"
|
2009-12-22 11:58:52 -08:00
|
|
|
filename = filename[0...-5]
|
2009-04-22 00:02:07 -07:00
|
|
|
elsif filename[-4..-1] == ".css"
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
|
2009-12-21 23:39:46 -08:00
|
|
|
new_filename = find_full_path("#{filename}.sass", load_paths) unless was_scss
|
|
|
|
new_filename ||= find_full_path("#{filename}.scss", load_paths) unless was_sass
|
2009-04-22 00:02:07 -07:00
|
|
|
|
|
|
|
return new_filename if new_filename
|
2010-01-02 20:48:49 -08:00
|
|
|
unless was_sass || was_scss
|
2010-01-02 19:37:04 -08:00
|
|
|
warn <<END
|
2010-01-02 20:48:49 -08:00
|
|
|
WARNING: Neither #{filename}.sass nor .scss found. Using #{filename}.css instead.
|
2010-01-02 19:37:04 -08:00
|
|
|
This behavior is deprecated and will be removed in a future version.
|
|
|
|
If you really need #{filename}.css, import it explicitly.
|
|
|
|
END
|
|
|
|
return filename + '.css'
|
|
|
|
end
|
2009-09-13 14:24:02 -07:00
|
|
|
raise SyntaxError.new("File to import not found or unreadable: #{original_filename}.")
|
2009-04-22 00:02:07 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def sassc_filename(filename, options)
|
2009-04-22 13:12:32 -07:00
|
|
|
File.join(options[:cache_location],
|
2009-04-22 00:02:07 -07:00
|
|
|
Digest::SHA1.hexdigest(File.dirname(File.expand_path(filename))),
|
|
|
|
File.basename(filename) + 'c')
|
|
|
|
end
|
|
|
|
|
|
|
|
def try_to_read_sassc(filename, compiled_filename, sha)
|
|
|
|
return unless File.readable?(compiled_filename)
|
|
|
|
|
2009-07-04 13:42:26 -07:00
|
|
|
File.open(compiled_filename, "rb") do |f|
|
2009-04-22 00:02:07 -07:00
|
|
|
return unless f.readline("\n").strip == Sass::VERSION
|
|
|
|
return unless f.readline("\n").strip == sha
|
2009-05-19 08:30:06 -07:00
|
|
|
return Marshal.load(f.read)
|
2009-04-22 00:02:07 -07:00
|
|
|
end
|
2010-02-13 18:16:28 -08:00
|
|
|
rescue EOFError, TypeError, ArgumentError => e
|
2009-05-19 08:30:06 -07:00
|
|
|
warn "Warning. Error encountered while reading cache #{compiled_filename}: #{e}"
|
2009-04-22 00:02:07 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def try_to_write_sassc(root, compiled_filename, sha, options)
|
2009-04-22 13:12:32 -07:00
|
|
|
return unless File.writable?(File.dirname(options[:cache_location]))
|
|
|
|
return if File.exists?(options[:cache_location]) && !File.writable?(options[:cache_location])
|
2009-04-22 00:02:07 -07:00
|
|
|
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))
|
2009-07-04 13:42:26 -07:00
|
|
|
File.open(compiled_filename, "wb") do |f|
|
2009-05-19 09:27:53 -07:00
|
|
|
f.write(Sass::VERSION)
|
|
|
|
f.write("\n")
|
|
|
|
f.write(sha)
|
|
|
|
f.write("\n")
|
2009-04-22 00:02:07 -07:00
|
|
|
f.write(Marshal.dump(root))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_full_path(filename, load_paths)
|
2009-06-01 00:31:46 -07:00
|
|
|
partial_name = File.join(File.dirname(filename), "_#{File.basename(filename)}")
|
|
|
|
|
|
|
|
if Pathname.new(filename).absolute?
|
|
|
|
[partial_name, filename].each do |name|
|
|
|
|
return name if File.readable?(name)
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2009-04-22 00:02:07 -07:00
|
|
|
load_paths.each do |path|
|
|
|
|
[partial_name, filename].each do |name|
|
|
|
|
full_path = File.join(path, name)
|
|
|
|
if File.readable?(full_path)
|
|
|
|
return full_path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|