2009-11-09 18:03:02 -08:00
|
|
|
require 'sass'
|
2007-01-07 04:08:48 +00:00
|
|
|
|
2006-12-04 02:47:37 +00:00
|
|
|
module Sass
|
2009-04-25 15:58:42 -07:00
|
|
|
# This module handles the compilation of Sass files.
|
|
|
|
# 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.
|
2009-11-09 18:35:29 -08:00
|
|
|
# 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}.
|
2006-12-04 02:47:37 +00:00
|
|
|
module Plugin
|
2009-04-22 13:45:26 -07:00
|
|
|
extend self
|
|
|
|
|
|
|
|
@options = {
|
|
|
|
:css_location => './public/stylesheets',
|
|
|
|
:always_update => false,
|
|
|
|
:always_check => true,
|
|
|
|
:full_exception => true
|
|
|
|
}
|
|
|
|
@checked_for_updates = false
|
|
|
|
|
2009-04-25 15:58:42 -07:00
|
|
|
# Whether or not Sass has **ever** checked if the stylesheets need to be updated
|
2009-04-22 13:45:26 -07:00
|
|
|
# (in this Ruby instance).
|
2009-04-25 15:58:42 -07:00
|
|
|
#
|
|
|
|
# @return [Boolean]
|
|
|
|
attr_reader :checked_for_updates
|
2008-02-11 19:49:16 -08:00
|
|
|
|
2009-04-25 15:58:42 -07:00
|
|
|
# An options hash.
|
2009-06-18 13:40:57 -07:00
|
|
|
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
2009-04-25 15:58:42 -07:00
|
|
|
#
|
|
|
|
# @return [Hash<Symbol, Object>]
|
|
|
|
attr_reader :options
|
2006-12-04 02:47:37 +00:00
|
|
|
|
2009-04-25 15:58:42 -07:00
|
|
|
# Sets the options hash.
|
2009-06-18 13:40:57 -07:00
|
|
|
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
2009-04-25 15:58:42 -07:00
|
|
|
#
|
|
|
|
# @param value [Hash<Symbol, Object>] The options hash
|
2009-04-22 13:45:26 -07:00
|
|
|
def options=(value)
|
|
|
|
@options.merge!(value)
|
|
|
|
end
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-25 15:58:42 -07:00
|
|
|
# Non-destructively modifies \{#options} so that default values are properly set.
|
|
|
|
#
|
|
|
|
# @param additional_options [Hash<Symbol, Object>] An options hash with which to merge \{#options}
|
|
|
|
# @return [Hash<Symbol, Object>] The modified options hash
|
2009-04-22 13:45:26 -07:00
|
|
|
def engine_options(additional_options = {})
|
|
|
|
opts = options.dup.merge(additional_options)
|
|
|
|
opts[:load_paths] = load_paths(opts)
|
|
|
|
opts
|
|
|
|
end
|
2008-09-03 10:36:14 -07:00
|
|
|
|
2009-11-05 22:55:46 -08:00
|
|
|
# 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
|
|
|
|
|
2009-04-25 15:58:42 -07:00
|
|
|
# Updates out-of-date stylesheets.
|
|
|
|
#
|
2009-06-18 13:40:57 -07:00
|
|
|
# Checks each Sass file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
|
2009-04-25 15:58:42 -07:00
|
|
|
# to see if it's been modified more recently than the corresponding CSS file
|
2009-06-18 13:40:57 -07:00
|
|
|
# in {file:SASS_REFERENCE.md#css_location-option} `:css_location`}.
|
2009-04-25 15:58:42 -07:00
|
|
|
# If it has, it updates the CSS file.
|
2009-04-22 13:45:26 -07:00
|
|
|
def update_stylesheets
|
|
|
|
return if options[:never_update]
|
2007-09-02 01:48:25 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
@checked_for_updates = true
|
|
|
|
template_locations.zip(css_locations).each do |template_location, css_location|
|
2008-06-19 16:10:50 -07:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
Dir.glob(File.join(template_location, "**", "*.sass")).each do |file|
|
|
|
|
# Get the relative path to the file with no extension
|
|
|
|
name = file.sub(template_location + "/", "")[0...-5]
|
2008-06-19 16:10:50 -07:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name, template_location, css_location))
|
|
|
|
update_stylesheet(name, template_location, css_location)
|
2006-12-04 02:47:37 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
end
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
private
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def update_stylesheet(name, template_location, css_location)
|
|
|
|
css = css_filename(name, css_location)
|
|
|
|
File.delete(css) if File.exists?(css)
|
2008-06-19 16:10:50 -07:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
filename = template_filename(name, template_location)
|
|
|
|
result = begin
|
2009-04-23 16:42:37 -07:00
|
|
|
Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
|
2009-04-22 13:45:26 -07:00
|
|
|
rescue Exception => e
|
|
|
|
exception_string(e)
|
|
|
|
end
|
2008-06-19 16:10:50 -07:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
# Create any directories that might be necessary
|
|
|
|
mkpath(css_location, name)
|
2008-06-19 16:10:50 -07:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
# Finally, write the file
|
|
|
|
File.open(css, 'w') do |file|
|
|
|
|
file.print(result)
|
2008-06-19 16:10:50 -07:00
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
# Create any successive directories required to be able to write a file to: File.join(base,name)
|
|
|
|
def mkpath(base, name)
|
|
|
|
dirs = [base]
|
|
|
|
name.split(File::SEPARATOR)[0...-1].each { |dir| dirs << File.join(dirs[-1],dir) }
|
|
|
|
dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
|
|
|
|
end
|
2008-06-19 16:10:50 -07:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def load_paths(opts = options)
|
|
|
|
(opts[:load_paths] || []) + template_locations
|
|
|
|
end
|
|
|
|
|
|
|
|
def template_locations
|
|
|
|
location = (options[:template_location] || File.join(options[:css_location],'sass'))
|
|
|
|
if location.is_a?(String)
|
|
|
|
[location]
|
|
|
|
else
|
|
|
|
location.to_a.map { |l| l.first }
|
2008-06-19 16:10:50 -07:00
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def css_locations
|
|
|
|
if options[:template_location] && !options[:template_location].is_a?(String)
|
|
|
|
options[:template_location].to_a.map { |l| l.last }
|
|
|
|
else
|
|
|
|
[options[:css_location]]
|
2007-09-02 01:48:25 +00:00
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
end
|
2007-09-02 01:48:25 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def exception_string(e)
|
|
|
|
if options[:full_exception]
|
|
|
|
e_string = "#{e.class}: #{e.message}"
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
if e.is_a? Sass::SyntaxError
|
|
|
|
e_string << "\non line #{e.sass_line}"
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
if e.sass_filename
|
|
|
|
e_string << " of #{e.sass_filename}"
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
if File.exists?(e.sass_filename)
|
|
|
|
e_string << "\n\n"
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
min = [e.sass_line - 5, 0].max
|
|
|
|
begin
|
|
|
|
File.read(e.sass_filename).rstrip.split("\n")[
|
|
|
|
min .. e.sass_line + 5
|
|
|
|
].each_with_index do |line, i|
|
|
|
|
e_string << "#{min + i + 1}: #{line}\n"
|
2007-08-28 05:10:48 +00:00
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
rescue
|
|
|
|
e_string << "Couldn't read sass file: #{e.sass_filename}"
|
2007-08-28 05:10:48 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
end
|
|
|
|
<<END
|
2008-04-18 13:59:47 -07:00
|
|
|
/*
|
|
|
|
#{e_string}
|
|
|
|
|
|
|
|
Backtrace:\n#{e.backtrace.join("\n")}
|
|
|
|
*/
|
|
|
|
body:before {
|
|
|
|
white-space: pre;
|
|
|
|
font-family: monospace;
|
|
|
|
content: "#{e_string.gsub('"', '\"').gsub("\n", '\\A ')}"; }
|
|
|
|
END
|
2009-04-22 13:45:26 -07:00
|
|
|
# Fix an emacs syntax-highlighting hiccup: '
|
|
|
|
else
|
|
|
|
"/* Internal stylesheet error */"
|
2007-08-28 05:10:48 +00:00
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
end
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def template_filename(name, path)
|
|
|
|
"#{path}/#{name}.sass"
|
|
|
|
end
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def css_filename(name, path)
|
|
|
|
"#{path}/#{name}.css"
|
|
|
|
end
|
2007-08-28 05:10:48 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def forbid_update?(name)
|
|
|
|
name.sub(/^.*\//, '')[0] == ?_
|
|
|
|
end
|
2007-08-28 05:33:57 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def stylesheet_needs_update?(name, template_path, css_path)
|
|
|
|
css_file = css_filename(name, css_path)
|
|
|
|
template_file = template_filename(name, template_path)
|
|
|
|
exact_stylesheet_needs_update?(css_file, template_file)
|
|
|
|
end
|
2009-04-22 12:41:08 -07:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def exact_stylesheet_needs_update?(css_file, template_file)
|
|
|
|
return true unless File.exists?(css_file)
|
2007-09-02 01:48:25 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
css_mtime = File.mtime(css_file)
|
|
|
|
File.mtime(template_file) > css_mtime ||
|
|
|
|
dependencies(template_file).any?(&dependency_updated?(css_mtime))
|
|
|
|
end
|
2007-09-02 01:48:25 +00:00
|
|
|
|
2009-04-22 13:45:26 -07:00
|
|
|
def dependency_updated?(css_mtime)
|
|
|
|
lambda do |dep|
|
|
|
|
File.mtime(dep) > css_mtime ||
|
|
|
|
dependencies(dep).any?(&dependency_updated?(css_mtime))
|
2006-12-04 02:47:37 +00:00
|
|
|
end
|
|
|
|
end
|
2009-04-22 13:45:26 -07:00
|
|
|
|
|
|
|
def dependencies(filename)
|
|
|
|
File.readlines(filename).grep(/^@import /).map do |line|
|
|
|
|
line[8..-1].split(',').map do |inc|
|
2009-04-22 13:50:21 -07:00
|
|
|
Sass::Files.find_file_to_import(inc.strip, [File.dirname(filename)] + load_paths)
|
2009-04-22 13:45:26 -07:00
|
|
|
end
|
|
|
|
end.flatten.grep(/\.sass$/)
|
|
|
|
end
|
2006-12-04 02:47:37 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2007-11-17 19:55:26 +00:00
|
|
|
require 'sass/plugin/rails' if defined?(ActionController)
|
|
|
|
require 'sass/plugin/merb' if defined?(Merb::Plugins)
|