sass 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING +1 -1
  3. data/MIT-LICENSE +2 -2
  4. data/README.md +29 -17
  5. data/Rakefile +43 -9
  6. data/VERSION +1 -1
  7. data/VERSION_DATE +1 -0
  8. data/VERSION_NAME +1 -1
  9. data/bin/sass +6 -1
  10. data/bin/sass-convert +6 -1
  11. data/bin/scss +6 -1
  12. data/ext/mkrf_conf.rb +27 -0
  13. data/lib/sass/cache_stores/base.rb +7 -3
  14. data/lib/sass/cache_stores/chain.rb +3 -2
  15. data/lib/sass/cache_stores/filesystem.rb +5 -7
  16. data/lib/sass/cache_stores/memory.rb +1 -1
  17. data/lib/sass/cache_stores/null.rb +2 -2
  18. data/lib/sass/callbacks.rb +2 -1
  19. data/lib/sass/css.rb +168 -53
  20. data/lib/sass/engine.rb +502 -174
  21. data/lib/sass/environment.rb +151 -111
  22. data/lib/sass/error.rb +7 -7
  23. data/lib/sass/exec.rb +176 -60
  24. data/lib/sass/features.rb +40 -0
  25. data/lib/sass/importers/base.rb +46 -7
  26. data/lib/sass/importers/deprecated_path.rb +51 -0
  27. data/lib/sass/importers/filesystem.rb +113 -30
  28. data/lib/sass/importers.rb +1 -0
  29. data/lib/sass/logger/base.rb +30 -0
  30. data/lib/sass/logger/log_level.rb +45 -0
  31. data/lib/sass/logger.rb +12 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin/compiler.rb +194 -104
  34. data/lib/sass/plugin/configuration.rb +18 -25
  35. data/lib/sass/plugin/merb.rb +1 -1
  36. data/lib/sass/plugin/staleness_checker.rb +37 -11
  37. data/lib/sass/plugin.rb +10 -13
  38. data/lib/sass/railtie.rb +2 -1
  39. data/lib/sass/repl.rb +5 -6
  40. data/lib/sass/script/css_lexer.rb +8 -4
  41. data/lib/sass/script/css_parser.rb +5 -2
  42. data/lib/sass/script/functions.rb +1547 -618
  43. data/lib/sass/script/lexer.rb +122 -72
  44. data/lib/sass/script/parser.rb +304 -135
  45. data/lib/sass/script/tree/funcall.rb +306 -0
  46. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +43 -13
  47. data/lib/sass/script/tree/list_literal.rb +77 -0
  48. data/lib/sass/script/tree/literal.rb +45 -0
  49. data/lib/sass/script/tree/map_literal.rb +64 -0
  50. data/lib/sass/script/{node.rb → tree/node.rb} +30 -12
  51. data/lib/sass/script/{operation.rb → tree/operation.rb} +33 -21
  52. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +14 -4
  53. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +21 -9
  54. data/lib/sass/script/tree/variable.rb +57 -0
  55. data/lib/sass/script/tree.rb +15 -0
  56. data/lib/sass/script/value/arg_list.rb +36 -0
  57. data/lib/sass/script/value/base.rb +238 -0
  58. data/lib/sass/script/value/bool.rb +40 -0
  59. data/lib/sass/script/{color.rb → value/color.rb} +256 -74
  60. data/lib/sass/script/value/deprecated_false.rb +55 -0
  61. data/lib/sass/script/value/helpers.rb +155 -0
  62. data/lib/sass/script/value/list.rb +128 -0
  63. data/lib/sass/script/value/map.rb +70 -0
  64. data/lib/sass/script/value/null.rb +49 -0
  65. data/lib/sass/script/{number.rb → value/number.rb} +115 -62
  66. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  67. data/lib/sass/script/value.rb +12 -0
  68. data/lib/sass/script.rb +35 -9
  69. data/lib/sass/scss/css_parser.rb +2 -12
  70. data/lib/sass/scss/parser.rb +657 -230
  71. data/lib/sass/scss/rx.rb +17 -12
  72. data/lib/sass/scss/static_parser.rb +37 -6
  73. data/lib/sass/scss.rb +0 -1
  74. data/lib/sass/selector/abstract_sequence.rb +35 -3
  75. data/lib/sass/selector/comma_sequence.rb +29 -14
  76. data/lib/sass/selector/sequence.rb +371 -74
  77. data/lib/sass/selector/simple.rb +28 -13
  78. data/lib/sass/selector/simple_sequence.rb +163 -36
  79. data/lib/sass/selector.rb +138 -36
  80. data/lib/sass/shared.rb +3 -5
  81. data/lib/sass/source/map.rb +196 -0
  82. data/lib/sass/source/position.rb +39 -0
  83. data/lib/sass/source/range.rb +41 -0
  84. data/lib/sass/stack.rb +126 -0
  85. data/lib/sass/supports.rb +228 -0
  86. data/lib/sass/tree/at_root_node.rb +82 -0
  87. data/lib/sass/tree/comment_node.rb +34 -29
  88. data/lib/sass/tree/content_node.rb +9 -0
  89. data/lib/sass/tree/css_import_node.rb +60 -0
  90. data/lib/sass/tree/debug_node.rb +3 -3
  91. data/lib/sass/tree/directive_node.rb +33 -3
  92. data/lib/sass/tree/each_node.rb +9 -9
  93. data/lib/sass/tree/extend_node.rb +20 -6
  94. data/lib/sass/tree/for_node.rb +6 -6
  95. data/lib/sass/tree/function_node.rb +12 -4
  96. data/lib/sass/tree/if_node.rb +2 -15
  97. data/lib/sass/tree/import_node.rb +11 -5
  98. data/lib/sass/tree/media_node.rb +27 -11
  99. data/lib/sass/tree/mixin_def_node.rb +15 -4
  100. data/lib/sass/tree/mixin_node.rb +27 -7
  101. data/lib/sass/tree/node.rb +69 -35
  102. data/lib/sass/tree/prop_node.rb +47 -31
  103. data/lib/sass/tree/return_node.rb +4 -3
  104. data/lib/sass/tree/root_node.rb +20 -4
  105. data/lib/sass/tree/rule_node.rb +37 -26
  106. data/lib/sass/tree/supports_node.rb +38 -0
  107. data/lib/sass/tree/trace_node.rb +33 -0
  108. data/lib/sass/tree/variable_node.rb +10 -4
  109. data/lib/sass/tree/visitors/base.rb +5 -8
  110. data/lib/sass/tree/visitors/check_nesting.rb +67 -52
  111. data/lib/sass/tree/visitors/convert.rb +134 -53
  112. data/lib/sass/tree/visitors/cssize.rb +245 -51
  113. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  114. data/lib/sass/tree/visitors/extend.rb +68 -0
  115. data/lib/sass/tree/visitors/perform.rb +331 -105
  116. data/lib/sass/tree/visitors/set_options.rb +125 -0
  117. data/lib/sass/tree/visitors/to_css.rb +259 -95
  118. data/lib/sass/tree/warn_node.rb +3 -3
  119. data/lib/sass/tree/while_node.rb +3 -3
  120. data/lib/sass/util/cross_platform_random.rb +19 -0
  121. data/lib/sass/util/multibyte_string_scanner.rb +157 -0
  122. data/lib/sass/util/normalized_map.rb +130 -0
  123. data/lib/sass/util/ordered_hash.rb +192 -0
  124. data/lib/sass/util/subset_map.rb +11 -2
  125. data/lib/sass/util/test.rb +9 -0
  126. data/lib/sass/util.rb +565 -39
  127. data/lib/sass/version.rb +27 -15
  128. data/lib/sass.rb +39 -4
  129. data/test/sass/cache_test.rb +15 -0
  130. data/test/sass/compiler_test.rb +223 -0
  131. data/test/sass/conversion_test.rb +901 -107
  132. data/test/sass/css2sass_test.rb +94 -0
  133. data/test/sass/engine_test.rb +1059 -164
  134. data/test/sass/exec_test.rb +86 -0
  135. data/test/sass/extend_test.rb +933 -837
  136. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  137. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  138. data/test/sass/functions_test.rb +995 -136
  139. data/test/sass/importer_test.rb +338 -18
  140. data/test/sass/logger_test.rb +58 -0
  141. data/test/sass/more_results/more_import.css +2 -2
  142. data/test/sass/plugin_test.rb +114 -30
  143. data/test/sass/results/cached_import_option.css +3 -0
  144. data/test/sass/results/filename_fn.css +3 -0
  145. data/test/sass/results/import.css +2 -2
  146. data/test/sass/results/import_charset.css +1 -0
  147. data/test/sass/results/import_charset_1_8.css +1 -0
  148. data/test/sass/results/import_charset_ibm866.css +1 -0
  149. data/test/sass/results/import_content.css +1 -0
  150. data/test/sass/results/script.css +1 -1
  151. data/test/sass/results/scss_import.css +2 -2
  152. data/test/sass/results/units.css +2 -2
  153. data/test/sass/script_conversion_test.rb +43 -1
  154. data/test/sass/script_test.rb +380 -36
  155. data/test/sass/scss/css_test.rb +257 -75
  156. data/test/sass/scss/scss_test.rb +2322 -110
  157. data/test/sass/source_map_test.rb +887 -0
  158. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  159. data/test/sass/templates/_double_import_loop2.sass +1 -0
  160. data/test/sass/templates/_filename_fn_import.scss +11 -0
  161. data/test/sass/templates/_imported_content.sass +3 -0
  162. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  163. data/test/sass/templates/bork5.sass +3 -0
  164. data/test/sass/templates/cached_import_option.scss +3 -0
  165. data/test/sass/templates/double_import_loop1.sass +1 -0
  166. data/test/sass/templates/filename_fn.scss +18 -0
  167. data/test/sass/templates/import_charset.sass +2 -0
  168. data/test/sass/templates/import_charset_1_8.sass +2 -0
  169. data/test/sass/templates/import_charset_ibm866.sass +2 -0
  170. data/test/sass/templates/import_content.sass +4 -0
  171. data/test/sass/templates/same_name_different_ext.sass +2 -0
  172. data/test/sass/templates/same_name_different_ext.scss +1 -0
  173. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  174. data/test/sass/templates/single_import_loop.sass +1 -0
  175. data/test/sass/templates/subdir/import_up1.scss +1 -0
  176. data/test/sass/templates/subdir/import_up2.scss +1 -0
  177. data/test/sass/test_helper.rb +1 -1
  178. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  179. data/test/sass/util/normalized_map_test.rb +51 -0
  180. data/test/sass/util_test.rb +183 -0
  181. data/test/sass/value_helpers_test.rb +181 -0
  182. data/test/test_helper.rb +45 -5
  183. data/vendor/listen/CHANGELOG.md +228 -0
  184. data/vendor/listen/CONTRIBUTING.md +38 -0
  185. data/vendor/listen/Gemfile +30 -0
  186. data/vendor/listen/Guardfile +8 -0
  187. data/vendor/{fssm → listen}/LICENSE +1 -1
  188. data/vendor/listen/README.md +315 -0
  189. data/vendor/listen/Rakefile +47 -0
  190. data/vendor/listen/Vagrantfile +96 -0
  191. data/vendor/listen/lib/listen/adapter.rb +214 -0
  192. data/vendor/listen/lib/listen/adapters/bsd.rb +112 -0
  193. data/vendor/listen/lib/listen/adapters/darwin.rb +85 -0
  194. data/vendor/listen/lib/listen/adapters/linux.rb +113 -0
  195. data/vendor/listen/lib/listen/adapters/polling.rb +67 -0
  196. data/vendor/listen/lib/listen/adapters/windows.rb +87 -0
  197. data/vendor/listen/lib/listen/dependency_manager.rb +126 -0
  198. data/vendor/listen/lib/listen/directory_record.rb +371 -0
  199. data/vendor/listen/lib/listen/listener.rb +225 -0
  200. data/vendor/listen/lib/listen/multi_listener.rb +143 -0
  201. data/vendor/listen/lib/listen/turnstile.rb +28 -0
  202. data/vendor/listen/lib/listen/version.rb +3 -0
  203. data/vendor/listen/lib/listen.rb +40 -0
  204. data/vendor/listen/listen.gemspec +22 -0
  205. data/vendor/listen/spec/listen/adapter_spec.rb +183 -0
  206. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  207. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  208. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  209. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  210. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  211. data/vendor/listen/spec/listen/dependency_manager_spec.rb +107 -0
  212. data/vendor/listen/spec/listen/directory_record_spec.rb +1225 -0
  213. data/vendor/listen/spec/listen/listener_spec.rb +169 -0
  214. data/vendor/listen/spec/listen/multi_listener_spec.rb +174 -0
  215. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  216. data/vendor/listen/spec/listen_spec.rb +73 -0
  217. data/vendor/listen/spec/spec_helper.rb +21 -0
  218. data/vendor/listen/spec/support/adapter_helper.rb +629 -0
  219. data/vendor/listen/spec/support/directory_record_helper.rb +55 -0
  220. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  221. data/vendor/listen/spec/support/listeners_helper.rb +156 -0
  222. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  223. metadata +344 -271
  224. data/lib/sass/less.rb +0 -382
  225. data/lib/sass/script/bool.rb +0 -18
  226. data/lib/sass/script/funcall.rb +0 -162
  227. data/lib/sass/script/list.rb +0 -76
  228. data/lib/sass/script/literal.rb +0 -245
  229. data/lib/sass/script/variable.rb +0 -54
  230. data/lib/sass/scss/sass_parser.rb +0 -11
  231. data/test/sass/less_conversion_test.rb +0 -653
  232. data/vendor/fssm/README.markdown +0 -55
  233. data/vendor/fssm/Rakefile +0 -59
  234. data/vendor/fssm/VERSION.yml +0 -5
  235. data/vendor/fssm/example.rb +0 -9
  236. data/vendor/fssm/fssm.gemspec +0 -77
  237. data/vendor/fssm/lib/fssm/backends/fsevents.rb +0 -36
  238. data/vendor/fssm/lib/fssm/backends/inotify.rb +0 -26
  239. data/vendor/fssm/lib/fssm/backends/polling.rb +0 -25
  240. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +0 -131
  241. data/vendor/fssm/lib/fssm/monitor.rb +0 -26
  242. data/vendor/fssm/lib/fssm/path.rb +0 -91
  243. data/vendor/fssm/lib/fssm/pathname.rb +0 -502
  244. data/vendor/fssm/lib/fssm/state/directory.rb +0 -57
  245. data/vendor/fssm/lib/fssm/state/file.rb +0 -24
  246. data/vendor/fssm/lib/fssm/support.rb +0 -63
  247. data/vendor/fssm/lib/fssm/tree.rb +0 -176
  248. data/vendor/fssm/lib/fssm.rb +0 -33
  249. data/vendor/fssm/profile/prof-cache.rb +0 -40
  250. data/vendor/fssm/profile/prof-fssm-pathname.html +0 -1231
  251. data/vendor/fssm/profile/prof-pathname.rb +0 -68
  252. data/vendor/fssm/profile/prof-plain-pathname.html +0 -988
  253. data/vendor/fssm/profile/prof.html +0 -2379
  254. data/vendor/fssm/spec/path_spec.rb +0 -75
  255. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  256. data/vendor/fssm/spec/root/file.css +0 -0
  257. data/vendor/fssm/spec/root/file.rb +0 -0
  258. data/vendor/fssm/spec/root/file.yml +0 -0
  259. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  260. data/vendor/fssm/spec/spec_helper.rb +0 -14
@@ -7,7 +7,6 @@ require 'sass/plugin/configuration'
7
7
  require 'sass/plugin/staleness_checker'
8
8
 
9
9
  module Sass::Plugin
10
-
11
10
  # The Compiler class handles compilation of multiple files and/or directories,
12
11
  # including checking which CSS files are out-of-date and need to be updated
13
12
  # and calling Sass to perform the compilation on those files.
@@ -26,19 +25,18 @@ module Sass::Plugin
26
25
  # * `:never_update`
27
26
  # * `:always_check`
28
27
  class Compiler
29
- include Sass::Util
30
28
  include Configuration
31
29
  extend Sass::Callbacks
32
30
 
33
31
  # Creates a new compiler.
34
32
  #
35
- # @param options [{Symbol => Object}]
33
+ # @param opts [{Symbol => Object}]
36
34
  # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
37
- def initialize(options = {})
38
- self.options.merge!(options)
35
+ def initialize(opts = {})
36
+ options.merge!(opts)
39
37
  end
40
38
 
41
- # Register a callback to be run before stylesheets are mass-updated.
39
+ # Register a callback to be run after stylesheets are mass-updated.
42
40
  # This is run whenever \{#update\_stylesheets} is called,
43
41
  # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
44
42
  # is enabled.
@@ -51,8 +49,8 @@ module Sass::Plugin
51
49
  # the second is the target CSS file.
52
50
  define_callback :updating_stylesheets
53
51
 
54
- # Register a callback to be run before a single stylesheet is updated.
55
- # The callback is only run if the stylesheet is guaranteed to be updated;
52
+ # Register a callback to be run after a single stylesheet is updated.
53
+ # The callback is only run if the stylesheet is really updated;
56
54
  # if the CSS file is fresh, this won't be run.
57
55
  #
58
56
  # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
@@ -60,12 +58,14 @@ module Sass::Plugin
60
58
  # when an exception CSS file is being written.
61
59
  # To run an action for those files, use \{#on\_compilation\_error}.
62
60
  #
63
- # @yield [template, css]
61
+ # @yield [template, css, sourcemap]
64
62
  # @yieldparam template [String]
65
63
  # The location of the Sass/SCSS file being updated.
66
64
  # @yieldparam css [String]
67
65
  # The location of the CSS file being generated.
68
- define_callback :updating_stylesheet
66
+ # @yieldparam sourcemap [String]
67
+ # The location of the sourcemap being generated, if any.
68
+ define_callback :updated_stylesheet
69
69
 
70
70
  # Register a callback to be run when Sass decides not to update a stylesheet.
71
71
  # In particular, the callback is run when Sass finds that
@@ -146,9 +146,18 @@ module Sass::Plugin
146
146
  # The location of the CSS file that was deleted.
147
147
  define_callback :deleting_css
148
148
 
149
+ # Register a callback to be run when Sass deletes a sourcemap file.
150
+ # This happens when the corresponding Sass/SCSS file has been deleted.
151
+ #
152
+ # @yield [filename]
153
+ # @yieldparam filename [String]
154
+ # The location of the sourcemap file that was deleted.
155
+ define_callback :deleting_sourcemap
156
+
149
157
  # Updates out-of-date stylesheets.
150
158
  #
151
- # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
159
+ # Checks each Sass/SCSS file in
160
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`}
152
161
  # to see if it's been modified more recently than the corresponding CSS file
153
162
  # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
154
163
  # If it has, it updates the CSS file.
@@ -160,28 +169,26 @@ module Sass::Plugin
160
169
  # The first string in each pair is the location of the Sass/SCSS file,
161
170
  # the second is the location of the CSS file that it should be compiled to.
162
171
  def update_stylesheets(individual_files = [])
163
- run_updating_stylesheets individual_files
172
+ individual_files = individual_files.dup
164
173
  Sass::Plugin.checked_for_updates = true
165
174
  staleness_checker = StalenessChecker.new(engine_options)
166
175
 
167
- individual_files.each do |t, c|
168
- if options[:always_update] || staleness_checker.stylesheet_needs_update?(c, t)
169
- update_stylesheet(t, c)
170
- end
171
- end
172
-
173
176
  template_location_array.each do |template_location, css_location|
174
-
175
- Dir.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
177
+ Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
176
178
  # Get the relative path to the file
177
179
  name = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
178
180
  css = css_filename(name, css_location)
181
+ sourcemap = Sass::Util.sourcemap_name(css) if engine_options[:sourcemap]
182
+ individual_files << [file, css, sourcemap]
183
+ end
184
+ end
179
185
 
180
- if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
181
- update_stylesheet file, css
182
- else
183
- run_not_updating_stylesheet file, css
184
- end
186
+ individual_files.each do |file, css, sourcemap|
187
+ # TODO: Does staleness_checker need to check the sourcemap file as well?
188
+ if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
189
+ update_stylesheet(file, css, sourcemap)
190
+ else
191
+ run_not_updating_stylesheet(file, css, sourcemap)
185
192
  end
186
193
  end
187
194
  end
@@ -198,10 +205,10 @@ module Sass::Plugin
198
205
  #
199
206
  # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
200
207
  #
201
- # Note that `watch` uses the [FSSM](http://github.com/ttilley/fssm) library
208
+ # Note that `watch` uses the [Listen](http://github.com/guard/listen) library
202
209
  # to monitor the filesystem for changes.
203
- # FSSM isn't loaded until `watch` is run.
204
- # The version of FSSM distributed with Sass is loaded by default,
210
+ # Listen isn't loaded until `watch` is run.
211
+ # The version of Listen distributed with Sass is loaded by default,
205
212
  # but if another version has already been loaded that will be used instead.
206
213
  #
207
214
  # @param individual_files [Array<(String, String)>]
@@ -213,79 +220,78 @@ module Sass::Plugin
213
220
  def watch(individual_files = [])
214
221
  update_stylesheets(individual_files)
215
222
 
216
- begin
217
- require 'fssm'
218
- rescue LoadError => e
219
- dir = Sass::Util.scope("vendor/fssm/lib")
220
- if $LOAD_PATH.include?(dir)
221
- e.message << "\n" <<
222
- if File.exists?(scope(".git"))
223
- 'Run "git submodule update --init" to get the recommended version.'
224
- else
225
- 'Run "gem install fssm" to get it.'
226
- end
227
- raise e
228
- else
229
- $LOAD_PATH.unshift dir
230
- retry
231
- end
232
- end
233
-
234
- unless individual_files.empty? && FSSM::Backends::Default.name == "FSSM::Backends::FSEvents"
235
- # As of FSSM 0.1.4, it doesn't support FSevents on individual files,
236
- # but it also isn't smart enough to switch to polling itself.
237
- require 'fssm/backends/polling'
238
- Sass::Util.silence_warnings do
239
- FSSM::Backends.const_set(:Default, FSSM::Backends::Polling)
240
- end
223
+ directories = watched_paths
224
+ individual_files.each do |(source, _, _)|
225
+ directories << File.dirname(File.expand_path(source))
241
226
  end
227
+ directories = remove_redundant_directories(directories)
242
228
 
243
229
  # TODO: Keep better track of what depends on what
244
230
  # so we don't have to run a global update every time anything changes.
245
- FSSM.monitor do |mon|
246
- template_location_array.each do |template_location, css_location|
247
- mon.path template_location do |path|
248
- path.glob '**/*.s[ac]ss'
249
-
250
- path.update do |base, relative|
251
- run_template_modified File.join(base, relative)
252
- update_stylesheets(individual_files)
253
- end
231
+ listener_args = directories + [{:relative_paths => false}]
232
+
233
+ # The native windows listener is much slower than the polling option, according to
234
+ # https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e
235
+ poll = @options[:poll] || Sass::Util.windows?
236
+ if poll && Sass::Util.listen_geq_2?
237
+ # In Listen 2.0.0 and on, :force_polling is an option. In earlier
238
+ # versions, it's a method on the listener (called below).
239
+ listener_args.last[:force_polling] = true
240
+ end
254
241
 
255
- path.create do |base, relative|
256
- run_template_created File.join(base, relative)
257
- update_stylesheets(individual_files)
258
- end
242
+ listener = create_listener(*listener_args) do |modified, added, removed|
243
+ recompile_required = false
259
244
 
260
- path.delete do |base, relative|
261
- run_template_deleted File.join(base, relative)
262
- css = File.join(css_location, relative.gsub(/\.s[ac]ss$/, '.css'))
263
- try_delete_css css
264
- update_stylesheets(individual_files)
265
- end
266
- end
245
+ modified.uniq.each do |f|
246
+ next unless watched_file?(f)
247
+ recompile_required = true
248
+ run_template_modified(relative_to_pwd(f))
267
249
  end
268
250
 
269
- individual_files.each do |template, css|
270
- mon.file template do |path|
271
- path.update do
272
- run_template_modified template
273
- update_stylesheets(individual_files)
274
- end
275
-
276
- path.create do
277
- run_template_created template
278
- update_stylesheets(individual_files)
279
- end
251
+ added.uniq.each do |f|
252
+ next unless watched_file?(f)
253
+ recompile_required = true
254
+ run_template_created(relative_to_pwd(f))
255
+ end
280
256
 
281
- path.delete do
282
- run_template_deleted template
283
- try_delete_css css
284
- update_stylesheets(individual_files)
257
+ removed.uniq.each do |f|
258
+ if (files = individual_files.find {|(source, _, _)| File.expand_path(source) == f})
259
+ recompile_required = true
260
+ # This was a file we were watching explicitly and compiling to a particular location.
261
+ # Delete the corresponding file.
262
+ try_delete_css files[1]
263
+ else
264
+ next unless watched_file?(f)
265
+ recompile_required = true
266
+ # Look for the sass directory that contained the sass file
267
+ # And try to remove the css file that corresponds to it
268
+ template_location_array.each do |(sass_dir, css_dir)|
269
+ sass_dir = File.expand_path(sass_dir)
270
+ if child_of_directory?(sass_dir, f)
271
+ remainder = f[(sass_dir.size + 1)..-1]
272
+ try_delete_css(css_filename(remainder, css_dir))
273
+ break
274
+ end
285
275
  end
286
276
  end
277
+ run_template_deleted(relative_to_pwd(f))
278
+ end
279
+
280
+ if recompile_required
281
+ # In case a file we're watching is removed and then recreated we
282
+ # prune out the non-existant files here.
283
+ watched_files_remaining = individual_files.select {|(source, _, _)| File.exists?(source)}
284
+ update_stylesheets(watched_files_remaining)
287
285
  end
288
286
  end
287
+
288
+ if poll && !Sass::Util.listen_geq_2?
289
+ # In Listen 2.0.0 and on, :force_polling is an option (set above). In
290
+ # earlier versions, it's a method on the listener.
291
+ listener.force_polling(true)
292
+ end
293
+
294
+ listen_to(listener)
289
295
  end
290
296
 
291
297
  # Non-destructively modifies \{#options} so that default values are properly set,
@@ -306,7 +312,46 @@ module Sass::Plugin
306
312
 
307
313
  private
308
314
 
309
- def update_stylesheet(filename, css)
315
+ def create_listener(*args, &block)
316
+ require 'listen'
317
+ if Sass::Util.listen_geq_2?
318
+ Listen.to(*args, &block)
319
+ else
320
+ Listen::Listener.new(*args, &block)
321
+ end
322
+ end
323
+
324
+ def listen_to(listener)
325
+ if Sass::Util.listen_geq_2?
326
+ listener.start
327
+ listener.thread.join
328
+ listener.stop # Partially work around guard/listen#146
329
+ else
330
+ begin
331
+ listener.start!
332
+ rescue Interrupt
333
+ # Squelch Interrupt for clean exit from Listen::Listener
334
+ end
335
+ end
336
+ end
337
+
338
+ def remove_redundant_directories(directories)
339
+ dedupped = []
340
+ directories.each do |new_directory|
341
+ # no need to add a directory that is already watched.
342
+ next if dedupped.any? do |existing_directory|
343
+ child_of_directory?(existing_directory, new_directory)
344
+ end
345
+ # get rid of any sub directories of this new directory
346
+ dedupped.reject! do |existing_directory|
347
+ child_of_directory?(new_directory, existing_directory)
348
+ end
349
+ dedupped << new_directory
350
+ end
351
+ dedupped
352
+ end
353
+
354
+ def update_stylesheet(filename, css, sourcemap)
310
355
  dir = File.dirname(css)
311
356
  unless File.exists?(dir)
312
357
  run_creating_directory dir
@@ -315,28 +360,61 @@ module Sass::Plugin
315
360
 
316
361
  begin
317
362
  File.read(filename) unless File.readable?(filename) # triggers an error for handling
318
- engine_opts = engine_options(:css_filename => css, :filename => filename)
319
- result = Sass::Engine.for_file(filename, engine_opts).render
320
- rescue Exception => e
321
- run_compilation_error e, filename, css
322
- result = Sass::SyntaxError.exception_to_css(e, options)
323
- else
324
- run_updating_stylesheet filename, css
363
+ engine_opts = engine_options(:css_filename => css,
364
+ :filename => filename,
365
+ :sourcemap_filename => sourcemap)
366
+ mapping = nil
367
+ engine = Sass::Engine.for_file(filename, engine_opts)
368
+ if sourcemap
369
+ rendered, mapping = engine.render_with_sourcemap(File.basename(sourcemap))
370
+ else
371
+ rendered = engine.render
372
+ end
373
+ rescue StandardError => e
374
+ compilation_error_occured = true
375
+ run_compilation_error e, filename, css, sourcemap
376
+ rendered = Sass::SyntaxError.exception_to_css(e, options)
325
377
  end
326
378
 
327
- # Finally, write the file
379
+ write_file(css, rendered)
380
+ if mapping
381
+ write_file(sourcemap, mapping.to_json(:css_path => css, :sourcemap_path => sourcemap))
382
+ end
383
+ run_updated_stylesheet(filename, css, sourcemap) unless compilation_error_occured
384
+ end
385
+
386
+ def write_file(fileName, content)
328
387
  flag = 'w'
329
388
  flag = 'wb' if Sass::Util.windows? && options[:unix_newlines]
330
- File.open(css, flag) do |file|
331
- file.set_encoding(result.encoding) unless Sass::Util.ruby1_8?
332
- file.print(result)
389
+ File.open(fileName, flag) do |file|
390
+ file.set_encoding(content.encoding) unless Sass::Util.ruby1_8?
391
+ file.print(content)
333
392
  end
334
393
  end
335
394
 
336
395
  def try_delete_css(css)
337
- return unless File.exists?(css)
338
- run_deleting_css css
339
- File.delete css
396
+ if File.exists?(css)
397
+ run_deleting_css css
398
+ File.delete css
399
+ end
400
+ map = Sass::Util.sourcemap_name(css)
401
+ if File.exists?(map)
402
+ run_deleting_sourcemap map
403
+ File.delete map
404
+ end
405
+ end
406
+
407
+ def watched_file?(file)
408
+ normalized_load_paths.find {|lp| lp.watched_file?(file)}
409
+ end
410
+
411
+ def watched_paths
412
+ @watched_paths ||= normalized_load_paths.map {|lp| lp.directories_to_watch}.compact.flatten
413
+ end
414
+
415
+ def normalized_load_paths
416
+ @normalized_load_paths ||=
417
+ Sass::Engine.normalize_options(:load_paths => load_paths)[:load_paths]
340
418
  end
341
419
 
342
420
  def load_paths(opts = options)
@@ -352,7 +430,19 @@ module Sass::Plugin
352
430
  end
353
431
 
354
432
  def css_filename(name, path)
355
- "#{path}/#{name}".gsub(/\.s[ac]ss$/, '.css')
433
+ "#{path}#{File::SEPARATOR unless path.end_with?(File::SEPARATOR)}#{name}".
434
+ gsub(/\.s[ac]ss$/, '.css')
435
+ end
436
+
437
+ def relative_to_pwd(f)
438
+ Sass::Util.pathname(f).relative_path_from(Sass::Util.pathname(Dir.pwd)).to_s
439
+ rescue ArgumentError # when a relative path cannot be computed
440
+ f
441
+ end
442
+
443
+ def child_of_directory?(parent, child)
444
+ parent_dir = parent.end_with?(File::SEPARATOR) ? parent : (parent + File::SEPARATOR)
445
+ child.start_with?(parent_dir) || parent == child
356
446
  end
357
447
  end
358
448
  end
@@ -1,11 +1,9 @@
1
- # We keep configuration in its own self-contained file
2
- # so that we can load it independently in Rails 3,
3
- # where the full plugin stuff is lazy-loaded.
4
-
5
1
  module Sass
6
2
  module Plugin
3
+ # We keep configuration in its own self-contained file
4
+ # so that we can load it independently in Rails 3,
5
+ # where the full plugin stuff is lazy-loaded.
7
6
  module Configuration
8
-
9
7
  # Returns the default options for a {Sass::Plugin::Compiler}.
10
8
  #
11
9
  # @return [{Symbol => Object}]
@@ -19,7 +17,8 @@ module Sass
19
17
  }.freeze
20
18
  end
21
19
 
22
- # Resets the options and {Sass::Callbacks::InstanceMethods#clear_callbacks! clears all callbacks}.
20
+ # Resets the options and
21
+ # {Sass::Callbacks::InstanceMethods#clear_callbacks! clears all callbacks}.
23
22
  def reset!
24
23
  @options = nil
25
24
  clear_callbacks!
@@ -31,26 +30,14 @@ module Sass
31
30
  # @return [{Symbol => Object}]
32
31
  def options
33
32
  @options ||= default_options.dup
34
- @options[:cache_store] ||= Sass::CacheStores::Filesystem.new(@options[:cache_location])
35
- @options
36
- end
37
-
38
- # Sets the options hash.
39
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
40
- # See {Sass::Plugin::Configuration#reset!}
41
- # @deprecated Instead, modify the options hash in-place.
42
- # @param value [{Symbol => Object}] The options hash
43
- def options=(value)
44
- Sass::Util.sass_warn("Setting Sass::Plugin.options is deprecated " +
45
- "and will be removed in a future release.")
46
- options.merge!(value)
47
33
  end
48
34
 
49
35
  # Adds a new template-location/css-location mapping.
50
36
  # This means that Sass/SCSS files in `template_location`
51
37
  # will be compiled to CSS files in `css_location`.
52
38
  #
53
- # This is preferred over manually manipulating the {file:SASS_REFERENCE.md#template_location-option `:template_location` option}
39
+ # This is preferred over manually manipulating the
40
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}
54
41
  # since the option can be in multiple formats.
55
42
  #
56
43
  # Note that this method will change `options[:template_location]`
@@ -70,7 +57,8 @@ module Sass
70
57
  # This means that Sass/SCSS files in `template_location`
71
58
  # will no longer be compiled to CSS files in `css_location`.
72
59
  #
73
- # This is preferred over manually manipulating the {file:SASS_REFERENCE.md#template_location-option `:template_location` option}
60
+ # This is preferred over manually manipulating the
61
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}
74
62
  # since the option can be in multiple formats.
75
63
  #
76
64
  # Note that this method will change `options[:template_location]`
@@ -114,10 +102,15 @@ module Sass
114
102
  options[:template_location] =
115
103
  case options[:template_location]
116
104
  when nil
117
- options[:css_location] ?
118
- [[File.join(options[:css_location], 'sass'), options[:css_location]]] : []
119
- when String; [[options[:template_location], options[:css_location]]]
120
- else; options[:template_location].to_a
105
+ if options[:css_location]
106
+ [[File.join(options[:css_location], 'sass'), options[:css_location]]]
107
+ else
108
+ []
109
+ end
110
+ when String
111
+ [[options[:template_location], options[:css_location]]]
112
+ else
113
+ options[:template_location].to_a
121
114
  end
122
115
  end
123
116
  end
@@ -5,7 +5,7 @@ unless defined?(Sass::MERB_LOADED)
5
5
  # Different default options in a m envirionment.
6
6
  def default_options
7
7
  @default_options ||= begin
8
- version = Merb::VERSION.split('.').map { |n| n.to_i }
8
+ version = Merb::VERSION.split('.').map {|n| n.to_i}
9
9
  if version[0] <= 0 && version[1] < 5
10
10
  root = MERB_ROOT
11
11
  env = MERB_ENV
@@ -1,3 +1,5 @@
1
+ require 'thread'
2
+
1
3
  module Sass
2
4
  module Plugin
3
5
  # The class handles `.s[ca]ss` file staleness checks via their mtime timestamps.
@@ -24,11 +26,13 @@ module Sass
24
26
  # as its instance-level caches are never explicitly expired.
25
27
  class StalenessChecker
26
28
  @dependencies_cache = {}
29
+ @dependency_cache_mutex = Mutex.new
27
30
 
28
31
  class << self
29
32
  # TODO: attach this to a compiler instance.
30
33
  # @private
31
34
  attr_accessor :dependencies_cache
35
+ attr_reader :dependency_cache_mutex
32
36
  end
33
37
 
34
38
  # Creates a new StalenessChecker
@@ -37,11 +41,13 @@ module Sass
37
41
  # @param options [{Symbol => Object}]
38
42
  # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
39
43
  def initialize(options)
40
- @dependencies = self.class.dependencies_cache
44
+ # URIs that are being actively checked for staleness. Protects against
45
+ # import loops.
46
+ @actively_checking = Set.new
41
47
 
42
48
  # Entries in the following instance-level caches are never explicitly expired.
43
- # Instead they are supposed to automaticaly go out of scope when a series of staleness checks
44
- # (this instance of StalenessChecker was created for) is finished.
49
+ # Instead they are supposed to automaticaly go out of scope when a series of staleness
50
+ # checks (this instance of StalenessChecker was created for) is finished.
45
51
  @mtimes, @dependencies_stale, @parse_trees = {}, {}, {}
46
52
  @options = Sass::Engine.normalize_options(options)
47
53
  end
@@ -127,7 +133,7 @@ module Sass
127
133
  begin
128
134
  mtime = importer.mtime(uri, @options)
129
135
  if mtime.nil?
130
- @dependencies.delete([uri, importer])
136
+ with_dependency_cache {|cache| cache.delete([uri, importer])}
131
137
  nil
132
138
  else
133
139
  mtime
@@ -136,22 +142,31 @@ module Sass
136
142
  end
137
143
 
138
144
  def dependencies(uri, importer)
139
- stored_mtime, dependencies = @dependencies[[uri, importer]]
145
+ stored_mtime, dependencies =
146
+ with_dependency_cache {|cache| Sass::Util.destructure(cache[[uri, importer]])}
140
147
 
141
148
  if !stored_mtime || stored_mtime < mtime(uri, importer)
142
149
  dependencies = compute_dependencies(uri, importer)
143
- @dependencies[[uri, importer]] = [mtime(uri, importer), dependencies]
150
+ with_dependency_cache do |cache|
151
+ cache[[uri, importer]] = [mtime(uri, importer), dependencies]
152
+ end
144
153
  end
145
154
 
146
155
  dependencies
147
156
  end
148
157
 
149
158
  def dependency_updated?(css_mtime)
150
- Proc.new do |uri, importer|
151
- sass_mtime = mtime(uri, importer)
152
- !sass_mtime ||
153
- sass_mtime > css_mtime ||
154
- dependencies_stale?(uri, importer, css_mtime)
159
+ proc do |uri, importer|
160
+ next true if @actively_checking.include?(uri)
161
+ begin
162
+ @actively_checking << uri
163
+ sass_mtime = mtime(uri, importer)
164
+ !sass_mtime ||
165
+ sass_mtime > css_mtime ||
166
+ dependencies_stale?(uri, importer, css_mtime)
167
+ ensure
168
+ @actively_checking.delete uri
169
+ end
155
170
  end
156
171
  end
157
172
 
@@ -168,6 +183,17 @@ module Sass
168
183
  def tree(uri, importer)
169
184
  @parse_trees[[uri, importer]] ||= importer.find(uri, @options).to_tree
170
185
  end
186
+
187
+ # Get access to the global dependency cache in a threadsafe manner.
188
+ # Inside the block, no other thread can access the dependency cache.
189
+ #
190
+ # @yieldparam cache [Hash] The hash that is the global dependency cache
191
+ # @return The value returned by the block to which this method yields
192
+ def with_dependency_cache
193
+ StalenessChecker.dependency_cache_mutex.synchronize do
194
+ yield StalenessChecker.dependencies_cache
195
+ end
196
+ end
171
197
  end
172
198
  end
173
199
  end
data/lib/sass/plugin.rb CHANGED
@@ -11,7 +11,8 @@ module Sass
11
11
  # This module is used as the primary interface with Sass
12
12
  # when it's used as a plugin for various frameworks.
13
13
  # All Rack-enabled frameworks are supported out of the box.
14
- # The plugin is {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}.
14
+ # The plugin is
15
+ # {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}.
15
16
  # Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}.
16
17
  #
17
18
  # This module has a large set of callbacks available
@@ -32,7 +33,6 @@ module Sass
32
33
  # #=> Compiling app/sass/ie.scss to public/stylesheets/ie.css
33
34
  # @see Sass::Plugin::Compiler
34
35
  module Plugin
35
- include Sass::Util
36
36
  extend self
37
37
 
38
38
  @checked_for_updates = false
@@ -65,7 +65,8 @@ module Sass
65
65
 
66
66
  # Updates out-of-date stylesheets.
67
67
  #
68
- # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
68
+ # Checks each Sass/SCSS file in
69
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`}
69
70
  # to see if it's been modified more recently than the corresponding CSS file
70
71
  # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
71
72
  # If it has, it updates the CSS file.
@@ -92,14 +93,10 @@ module Sass
92
93
  # the second is the location of the CSS file that it should be compiled to.
93
94
  # @see #update_stylesheets
94
95
  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
96
+ Compiler.new(options.dup.merge(
97
+ :never_update => false,
98
+ :always_update => true,
99
+ :cache => false)).update_stylesheets(individual_files)
103
100
  end
104
101
 
105
102
  # All other method invocations are proxied to the \{#compiler}.
@@ -123,11 +120,11 @@ module Sass
123
120
  def options
124
121
  compiler.options
125
122
  end
126
-
127
123
  end
128
124
  end
129
125
 
130
- if defined?(ActionController)
126
+ # On Rails 3+ the rails plugin is loaded at the right time in railtie.rb
127
+ if defined?(ActionController) && !Sass::Util.ap_geq_3?
131
128
  require 'sass/plugin/rails'
132
129
  elsif defined?(Merb::Plugins)
133
130
  require 'sass/plugin/merb'