/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/plugin.rb
Ruby | 279 lines | 159 code | 34 blank | 86 comment | 15 complexity | 7f806360c4f3a0857c32144f10100dc8 MD5 | raw file
- require 'fileutils'
- require 'sass'
- require 'sass/plugin/configuration'
- require 'sass/plugin/staleness_checker'
- module Sass
- # This module handles the compilation of Sass/SCSS 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.
- # 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.
- #
- # @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
- module Plugin
- include Haml::Util
- @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_reader :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
- # 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]
- run_updating_stylesheets individual_files
- @checked_for_updates = true
- staleness_checker = StalenessChecker.new
- individual_files.each do |t, c|
- if options[:always_update] || staleness_checker.stylesheet_needs_update?(c, t)
- update_stylesheet(t, c)
- end
- end
- template_location_array.each do |template_location, css_location|
- Dir.glob(File.join(template_location, "**", "*.s[ca]ss")).sort.each do |file|
- # Get the relative path to the file
- name = file.sub(template_location.sub(/\/*$/, '/'), "")
- css = css_filename(name, css_location)
- next if forbid_update?(name)
- 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
- 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 = [])
- old_options = options
- self.options = options.dup
- options[:never_update] = false
- options[:always_update] = true
- options[:cache] = false
- update_stylesheets(individual_files)
- ensure
- self.options = old_options
- 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 [FSSM](http://github.com/ttilley/fssm) library
- # to monitor the filesystem for changes.
- # FSSM isn't loaded until `watch` is run.
- # The version of FSSM 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)
- begin
- require 'fssm'
- 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 fssm" to get it.'
- end
- raise e
- end
- unless individual_files.empty? && FSSM::Backends::Default.name == "FSSM::Backends::FSEvents"
- # As of FSSM 0.1.4, it doesn't support FSevents on individual files,
- # but it also isn't smart enough to switch to polling itself.
- require 'fssm/backends/polling'
- Haml::Util.silence_warnings do
- FSSM::Backends.const_set(:Default, FSSM::Backends::Polling)
- end
- end
- # TODO: Keep better track of what depends on what
- # so we don't have to run a global update every time anything changes.
- FSSM.monitor do |mon|
- template_location_array.each do |template_location, css_location|
- mon.path template_location do |path|
- path.glob '**/*.s[ac]ss'
- path.update do |base, relative|
- run_template_modified File.join(base, relative)
- update_stylesheets(individual_files)
- end
- path.create do |base, relative|
- run_template_created File.join(base, relative)
- update_stylesheets(individual_files)
- end
- path.delete do |base, relative|
- run_template_deleted File.join(base, relative)
- css = File.join(css_location, relative.gsub(/\.s[ac]ss$/, '.css'))
- try_delete_css css
- update_stylesheets(individual_files)
- end
- end
- end
- individual_files.each do |template, css|
- mon.file template do |path|
- path.update do
- run_template_modified template
- update_stylesheets(individual_files)
- end
- path.create do
- run_template_created template
- update_stylesheets(individual_files)
- end
- path.delete do
- run_template_deleted template
- try_delete_css css
- update_stylesheets(individual_files)
- end
- end
- end
- end
- end
- private
- def update_stylesheet(filename, css)
- dir = File.dirname(css)
- unless File.exists?(dir)
- run_creating_directory dir
- FileUtils.mkdir_p dir
- end
- begin
- result = Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
- rescue Exception => e
- run_compilation_error e, filename, css
- result = Sass::SyntaxError.exception_to_css(e, options)
- else
- run_updating_stylesheet filename, css
- end
- # Finally, write the file
- flag = 'w'
- flag = 'wb' if Haml::Util.windows? && options[:unix_newlines]
- File.open(css, flag) do |file|
- file.set_encoding(result.encoding) unless Haml::Util.ruby1_8?
- file.print(result)
- 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
- def forbid_update?(name)
- name.sub(/^.*\//, '')[0] == ?_
- end
- # Compass expects this to exist
- def stylesheet_needs_update?(css_file, template_file)
- StalenessChecker.stylesheet_needs_update?(css_file, template_file)
- end
- end
- end
- if defined?(ActionController)
- require 'sass/plugin/rails'
- elsif defined?(Merb::Plugins)
- require 'sass/plugin/merb'
- else
- require 'sass/plugin/generic'
- end