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