PageRenderTime 50ms CodeModel.GetById 22ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/plugin.rb

http://github.com/scalate/scalate
Ruby | 279 lines | 159 code | 34 blank | 86 comment | 15 complexity | 7f806360c4f3a0857c32144f10100dc8 MD5 | raw file
  1require 'fileutils'
  2
  3require 'sass'
  4require 'sass/plugin/configuration'
  5require 'sass/plugin/staleness_checker'
  6
  7module Sass
  8  # This module handles the compilation of Sass/SCSS files.
  9  # It provides global options and checks whether CSS files
 10  # need to be updated.
 11  #
 12  # This module is used as the primary interface with Sass
 13  # when it's used as a plugin for various frameworks.
 14  # All Rack-enabled frameworks are supported out of the box.
 15  # The plugin is {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}.
 16  # Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}.
 17  #
 18  # This module has a large set of callbacks available
 19  # to allow users to run code (such as logging) when certain things happen.
 20  # All callback methods are of the form `on_#{name}`,
 21  # and they all take a block that's called when the given action occurs.
 22  #
 23  # @example Using a callback
 24  # Sass::Plugin.on_updating_stylesheet do |template, css|
 25  #   puts "Compiling #{template} to #{css}"
 26  # end
 27  # Sass::Plugin.update_stylesheets
 28  #   #=> Compiling app/sass/screen.scss to public/stylesheets/screen.css
 29  #   #=> Compiling app/sass/print.scss to public/stylesheets/print.css
 30  #   #=> Compiling app/sass/ie.scss to public/stylesheets/ie.css
 31  module Plugin
 32    include Haml::Util
 33
 34    @checked_for_updates = false
 35
 36    # Whether or not Sass has **ever** checked if the stylesheets need to be updated
 37    # (in this Ruby instance).
 38    #
 39    # @return [Boolean]
 40    attr_reader :checked_for_updates
 41
 42    # Same as \{#update\_stylesheets}, but respects \{#checked\_for\_updates}
 43    # and the {file:SASS_REFERENCE.md#always_update-option `:always_update`}
 44    # and {file:SASS_REFERENCE.md#always_check-option `:always_check`} options.
 45    #
 46    # @see #update_stylesheets
 47    def check_for_updates
 48      return unless !Sass::Plugin.checked_for_updates ||
 49          Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
 50      update_stylesheets
 51    end
 52
 53    # Updates out-of-date stylesheets.
 54    #
 55    # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
 56    # to see if it's been modified more recently than the corresponding CSS file
 57    # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
 58    # If it has, it updates the CSS file.
 59    #
 60    # @param individual_files [Array<(String, String)>]
 61    #   A list of files to check for updates
 62    #   **in addition to those specified by the
 63    #   {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
 64    #   The first string in each pair is the location of the Sass/SCSS file,
 65    #   the second is the location of the CSS file that it should be compiled to.
 66    def update_stylesheets(individual_files = [])
 67      return if options[:never_update]
 68
 69      run_updating_stylesheets individual_files
 70      @checked_for_updates = true
 71      staleness_checker = StalenessChecker.new
 72
 73      individual_files.each do |t, c|
 74        if options[:always_update] || staleness_checker.stylesheet_needs_update?(c, t)
 75          update_stylesheet(t, c)
 76        end
 77      end
 78
 79      template_location_array.each do |template_location, css_location|
 80
 81        Dir.glob(File.join(template_location, "**", "*.s[ca]ss")).sort.each do |file|
 82          # Get the relative path to the file
 83          name = file.sub(template_location.sub(/\/*$/, '/'), "")
 84          css = css_filename(name, css_location)
 85
 86          next if forbid_update?(name)
 87          if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
 88            update_stylesheet file, css
 89          else
 90            run_not_updating_stylesheet file, css
 91          end
 92        end
 93      end
 94    end
 95
 96    # Updates all stylesheets, even those that aren't out-of-date.
 97    # Ignores the cache.
 98    #
 99    # @param individual_files [Array<(String, String)>]
100    #   A list of files to check for updates
101    #   **in addition to those specified by the
102    #   {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
103    #   The first string in each pair is the location of the Sass/SCSS file,
104    #   the second is the location of the CSS file that it should be compiled to.
105    # @see #update_stylesheets
106    def force_update_stylesheets(individual_files = [])
107      old_options = options
108      self.options = options.dup
109      options[:never_update] = false
110      options[:always_update] = true
111      options[:cache] = false
112      update_stylesheets(individual_files)
113    ensure
114      self.options = old_options
115    end
116
117    # Watches the template directory (or directories)
118    # and updates the CSS files whenever the related Sass/SCSS files change.
119    # `watch` never returns.
120    #
121    # Whenever a change is detected to a Sass/SCSS file in
122    # {file:SASS_REFERENCE.md#template_location-option `:template_location`},
123    # the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
124    # will be recompiled.
125    # The CSS files of any Sass/SCSS files that import the changed file will also be recompiled.
126    #
127    # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
128    #
129    # Note that `watch` uses the [FSSM](http://github.com/ttilley/fssm) library
130    # to monitor the filesystem for changes.
131    # FSSM isn't loaded until `watch` is run.
132    # The version of FSSM distributed with Sass is loaded by default,
133    # but if another version has already been loaded that will be used instead.
134    #
135    # @param individual_files [Array<(String, String)>]
136    #   A list of files to watch for updates
137    #   **in addition to those specified by the
138    #   {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
139    #   The first string in each pair is the location of the Sass/SCSS file,
140    #   the second is the location of the CSS file that it should be compiled to.
141    def watch(individual_files = [])
142      update_stylesheets(individual_files)
143
144      begin
145        require 'fssm'
146      rescue LoadError => e
147        e.message << "\n" <<
148          if File.exists?(scope(".git"))
149            'Run "git submodule update --init" to get the recommended version.'
150          else
151            'Run "gem install fssm" to get it.'
152          end
153        raise e
154      end
155
156      unless individual_files.empty? && FSSM::Backends::Default.name == "FSSM::Backends::FSEvents"
157        # As of FSSM 0.1.4, it doesn't support FSevents on individual files,
158        # but it also isn't smart enough to switch to polling itself.
159        require 'fssm/backends/polling'
160        Haml::Util.silence_warnings do
161          FSSM::Backends.const_set(:Default, FSSM::Backends::Polling)
162        end
163      end
164
165      # TODO: Keep better track of what depends on what
166      # so we don't have to run a global update every time anything changes.
167      FSSM.monitor do |mon|
168        template_location_array.each do |template_location, css_location|
169          mon.path template_location do |path|
170            path.glob '**/*.s[ac]ss'
171
172            path.update do |base, relative|
173              run_template_modified File.join(base, relative)
174              update_stylesheets(individual_files)
175            end
176
177            path.create do |base, relative|
178              run_template_created File.join(base, relative)
179              update_stylesheets(individual_files)
180            end
181
182            path.delete do |base, relative|
183              run_template_deleted File.join(base, relative)
184              css = File.join(css_location, relative.gsub(/\.s[ac]ss$/, '.css'))
185              try_delete_css css
186              update_stylesheets(individual_files)
187            end
188          end
189        end
190
191        individual_files.each do |template, css|
192          mon.file template do |path|
193            path.update do
194              run_template_modified template
195              update_stylesheets(individual_files)
196            end
197
198            path.create do
199              run_template_created template
200              update_stylesheets(individual_files)
201            end
202
203            path.delete do
204              run_template_deleted template
205              try_delete_css css
206              update_stylesheets(individual_files)
207            end
208          end
209        end
210      end
211    end
212
213    private
214
215    def update_stylesheet(filename, css)
216      dir = File.dirname(css)
217      unless File.exists?(dir)
218        run_creating_directory dir
219        FileUtils.mkdir_p dir
220      end
221
222      begin
223        result = Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
224      rescue Exception => e
225        run_compilation_error e, filename, css
226        result = Sass::SyntaxError.exception_to_css(e, options)
227      else
228        run_updating_stylesheet filename, css
229      end
230
231      # Finally, write the file
232      flag = 'w'
233      flag = 'wb' if Haml::Util.windows? && options[:unix_newlines]
234      File.open(css, flag) do |file|
235        file.set_encoding(result.encoding) unless Haml::Util.ruby1_8?
236        file.print(result)
237      end
238    end
239
240    def try_delete_css(css)
241      return unless File.exists?(css)
242      run_deleting_css css
243      File.delete css
244    end
245
246    def load_paths(opts = options)
247      (opts[:load_paths] || []) + template_locations
248    end
249
250    def template_locations
251      template_location_array.to_a.map {|l| l.first}
252    end
253
254    def css_locations
255      template_location_array.to_a.map {|l| l.last}
256    end
257
258    def css_filename(name, path)
259      "#{path}/#{name}".gsub(/\.s[ac]ss$/, '.css')
260    end
261
262    def forbid_update?(name)
263      name.sub(/^.*\//, '')[0] == ?_
264    end
265
266    # Compass expects this to exist
267    def stylesheet_needs_update?(css_file, template_file)
268      StalenessChecker.stylesheet_needs_update?(css_file, template_file)
269    end
270  end
271end
272
273if defined?(ActionController)
274  require 'sass/plugin/rails'
275elsif defined?(Merb::Plugins)
276  require 'sass/plugin/merb'
277else
278  require 'sass/plugin/generic'
279end