sass 3.3.0 → 3.4.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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +58 -50
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/bin/sass +1 -1
  9. data/bin/scss +1 -1
  10. data/lib/sass/cache_stores/filesystem.rb +6 -2
  11. data/lib/sass/css.rb +1 -3
  12. data/lib/sass/engine.rb +37 -46
  13. data/lib/sass/environment.rb +13 -17
  14. data/lib/sass/error.rb +6 -9
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +424 -0
  18. data/lib/sass/exec.rb +5 -771
  19. data/lib/sass/features.rb +7 -0
  20. data/lib/sass/importers/base.rb +7 -2
  21. data/lib/sass/importers/filesystem.rb +9 -25
  22. data/lib/sass/importers.rb +0 -1
  23. data/lib/sass/media.rb +1 -4
  24. data/lib/sass/plugin/compiler.rb +200 -83
  25. data/lib/sass/plugin/staleness_checker.rb +1 -1
  26. data/lib/sass/plugin.rb +3 -3
  27. data/lib/sass/script/css_lexer.rb +1 -1
  28. data/lib/sass/script/functions.rb +622 -268
  29. data/lib/sass/script/lexer.rb +99 -34
  30. data/lib/sass/script/parser.rb +24 -23
  31. data/lib/sass/script/tree/funcall.rb +1 -1
  32. data/lib/sass/script/tree/interpolation.rb +20 -2
  33. data/lib/sass/script/tree/selector.rb +26 -0
  34. data/lib/sass/script/tree/string_interpolation.rb +1 -1
  35. data/lib/sass/script/tree.rb +1 -0
  36. data/lib/sass/script/value/base.rb +7 -5
  37. data/lib/sass/script/value/bool.rb +0 -5
  38. data/lib/sass/script/value/color.rb +39 -21
  39. data/lib/sass/script/value/helpers.rb +107 -0
  40. data/lib/sass/script/value/list.rb +0 -15
  41. data/lib/sass/script/value/null.rb +0 -5
  42. data/lib/sass/script/value/number.rb +62 -14
  43. data/lib/sass/script/value/string.rb +59 -11
  44. data/lib/sass/script/value.rb +0 -1
  45. data/lib/sass/scss/css_parser.rb +8 -2
  46. data/lib/sass/scss/parser.rb +190 -328
  47. data/lib/sass/scss/rx.rb +15 -6
  48. data/lib/sass/scss/static_parser.rb +298 -1
  49. data/lib/sass/selector/abstract_sequence.rb +28 -13
  50. data/lib/sass/selector/comma_sequence.rb +92 -13
  51. data/lib/sass/selector/pseudo.rb +256 -0
  52. data/lib/sass/selector/sequence.rb +94 -24
  53. data/lib/sass/selector/simple.rb +14 -25
  54. data/lib/sass/selector/simple_sequence.rb +97 -33
  55. data/lib/sass/selector.rb +57 -194
  56. data/lib/sass/shared.rb +1 -1
  57. data/lib/sass/source/map.rb +26 -12
  58. data/lib/sass/stack.rb +0 -6
  59. data/lib/sass/supports.rb +2 -3
  60. data/lib/sass/tree/at_root_node.rb +1 -0
  61. data/lib/sass/tree/charset_node.rb +1 -1
  62. data/lib/sass/tree/directive_node.rb +8 -2
  63. data/lib/sass/tree/error_node.rb +18 -0
  64. data/lib/sass/tree/extend_node.rb +1 -1
  65. data/lib/sass/tree/function_node.rb +4 -0
  66. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  67. data/lib/sass/tree/prop_node.rb +1 -1
  68. data/lib/sass/tree/rule_node.rb +12 -7
  69. data/lib/sass/tree/visitors/check_nesting.rb +38 -10
  70. data/lib/sass/tree/visitors/convert.rb +16 -18
  71. data/lib/sass/tree/visitors/cssize.rb +29 -29
  72. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  73. data/lib/sass/tree/visitors/perform.rb +45 -33
  74. data/lib/sass/tree/visitors/set_options.rb +14 -0
  75. data/lib/sass/tree/visitors/to_css.rb +15 -14
  76. data/lib/sass/util/subset_map.rb +1 -1
  77. data/lib/sass/util.rb +222 -99
  78. data/lib/sass/version.rb +5 -5
  79. data/lib/sass.rb +0 -5
  80. data/test/sass/cache_test.rb +62 -20
  81. data/test/sass/callbacks_test.rb +1 -1
  82. data/test/sass/compiler_test.rb +19 -10
  83. data/test/sass/conversion_test.rb +58 -1
  84. data/test/sass/css2sass_test.rb +23 -4
  85. data/test/sass/encoding_test.rb +219 -0
  86. data/test/sass/engine_test.rb +136 -199
  87. data/test/sass/exec_test.rb +2 -2
  88. data/test/sass/extend_test.rb +236 -19
  89. data/test/sass/functions_test.rb +295 -253
  90. data/test/sass/importer_test.rb +31 -21
  91. data/test/sass/logger_test.rb +1 -1
  92. data/test/sass/more_results/more_import.css +1 -1
  93. data/test/sass/plugin_test.rb +14 -13
  94. data/test/sass/results/compact.css +1 -1
  95. data/test/sass/results/complex.css +4 -4
  96. data/test/sass/results/expanded.css +1 -1
  97. data/test/sass/results/import.css +1 -1
  98. data/test/sass/results/import_charset_ibm866.css +2 -2
  99. data/test/sass/results/mixins.css +17 -17
  100. data/test/sass/results/nested.css +1 -1
  101. data/test/sass/results/parent_ref.css +2 -2
  102. data/test/sass/results/script.css +3 -3
  103. data/test/sass/results/scss_import.css +1 -1
  104. data/test/sass/script_conversion_test.rb +10 -7
  105. data/test/sass/script_test.rb +288 -74
  106. data/test/sass/scss/css_test.rb +141 -24
  107. data/test/sass/scss/rx_test.rb +4 -4
  108. data/test/sass/scss/scss_test.rb +457 -18
  109. data/test/sass/source_map_test.rb +115 -25
  110. data/test/sass/superselector_test.rb +191 -0
  111. data/test/sass/templates/scss_import.scss +2 -1
  112. data/test/sass/test_helper.rb +1 -1
  113. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  114. data/test/sass/util/normalized_map_test.rb +1 -1
  115. data/test/sass/util/subset_map_test.rb +2 -2
  116. data/test/sass/util_test.rb +31 -1
  117. data/test/sass/value_helpers_test.rb +5 -7
  118. data/test/test_helper.rb +2 -2
  119. data/vendor/listen/CHANGELOG.md +1 -228
  120. data/vendor/listen/Gemfile +5 -15
  121. data/vendor/listen/README.md +111 -77
  122. data/vendor/listen/Rakefile +0 -42
  123. data/vendor/listen/lib/listen/adapter.rb +195 -82
  124. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  125. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  126. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  127. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  128. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  129. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  130. data/vendor/listen/lib/listen/listener.rb +135 -37
  131. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  132. data/vendor/listen/lib/listen/version.rb +1 -1
  133. data/vendor/listen/lib/listen.rb +33 -19
  134. data/vendor/listen/listen.gemspec +6 -0
  135. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  136. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  137. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  138. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  139. data/vendor/listen/spec/listen_spec.rb +15 -21
  140. data/vendor/listen/spec/spec_helper.rb +4 -0
  141. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  142. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  143. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  144. metadata +25 -22
  145. data/ext/mkrf_conf.rb +0 -27
  146. data/lib/sass/importers/deprecated_path.rb +0 -51
  147. data/lib/sass/script/value/deprecated_false.rb +0 -55
  148. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  149. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  150. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  151. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
data/lib/sass/features.rb CHANGED
@@ -4,7 +4,14 @@ module Sass
4
4
  # by providing a feature name.
5
5
  module Features
6
6
  # This is the set of features that can be detected.
7
+ #
8
+ # When this is updated, the documentation of `feature-exists()` should be
9
+ # updated as well.
7
10
  KNOWN_FEATURES = Set[*%w{
11
+ global-variable-shadowing
12
+ extend-selector-pseudoclass
13
+ units-level-3
14
+ at-error
8
15
  }]
9
16
 
10
17
  # Check if a feature exists by name. This is used to implement
@@ -124,6 +124,10 @@ module Sass
124
124
  # sourcemap generation to fail if any CSS is generated from files imported
125
125
  # from this importer.
126
126
  #
127
+ # If an absolute "file:" URI can be produced for an imported file, that
128
+ # should be preferred to returning `nil`. However, a URL relative to
129
+ # `sourcemap_directory` should be preferred over an absolute "file:" URI.
130
+ #
127
131
  # @param uri [String] A URI known to be valid for this importer.
128
132
  # @param sourcemap_directory [String, NilClass] The absolute path to a
129
133
  # directory on disk where the sourcemap will be saved. If uri refers to
@@ -131,8 +135,9 @@ module Sass
131
135
  # may return a relative URL. This may be `nil` if the sourcemap's
132
136
  # eventual location is unknown.
133
137
  # @return [String?] The publicly-visible URL for this file, or `nil`
134
- # indicating that no publicly-visible URL exists.
135
- def public_url(uri, sourcemap_directory = nil)
138
+ # indicating that no publicly-visible URL exists. This should be
139
+ # appropriately URL-escaped.
140
+ def public_url(uri, sourcemap_directory)
136
141
  return if @public_url_warning_issued
137
142
  @public_url_warning_issued = true
138
143
  Sass::Util.sass_warn <<WARNING
@@ -64,17 +64,16 @@ module Sass
64
64
  filename.start_with?(root + File::SEPARATOR)
65
65
  end
66
66
 
67
- def public_url(name, sourcemap_directory = nil)
67
+ def public_url(name, sourcemap_directory)
68
+ file_pathname = Sass::Util.cleanpath(Sass::Util.absolute_path(name, @root))
68
69
  if sourcemap_directory.nil?
69
- warn_about_public_url(name)
70
+ Sass::Util.file_uri_from_path(file_pathname)
70
71
  else
71
- file_pathname = Sass::Util.pathname(Sass::Util.absolute_path(name, @root)).cleanpath
72
- sourcemap_pathname = Sass::Util.pathname(sourcemap_directory).cleanpath
72
+ sourcemap_pathname = Sass::Util.cleanpath(sourcemap_directory)
73
73
  begin
74
- file_pathname.relative_path_from(sourcemap_pathname).to_s
74
+ Sass::Util.file_uri_from_path(file_pathname.relative_path_from(sourcemap_pathname))
75
75
  rescue ArgumentError # when a relative path cannot be constructed
76
- warn_about_public_url(name)
77
- nil
76
+ Sass::Util.file_uri_from_path(file_pathname)
78
77
  end
79
78
  end
80
79
  end
@@ -139,15 +138,16 @@ module Sass
139
138
  # @param name [String] The filename to search for.
140
139
  # @return [(String, Symbol)] A filename-syntax pair.
141
140
  def find_real_file(dir, name, options)
142
- # on windows 'dir' can be in native File::ALT_SEPARATOR form
141
+ # On windows 'dir' or 'name' can be in native File::ALT_SEPARATOR form.
143
142
  dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
143
+ name = name.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
144
144
 
145
145
  found = possible_files(remove_root(name)).map do |f, s|
146
146
  path = (dir == "." || Sass::Util.pathname(f).absolute?) ? f :
147
147
  "#{escape_glob_characters(dir)}/#{f}"
148
148
  Dir[path].map do |full_path|
149
149
  full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR)
150
- [Sass::Util.pathname(full_path).cleanpath.to_s, s]
150
+ [Sass::Util.cleanpath(full_path).to_s, s]
151
151
  end
152
152
  end
153
153
  found = Sass::Util.flatten(found, 1)
@@ -195,22 +195,6 @@ WARNING
195
195
  [dirname, basename, extension]
196
196
  end
197
197
 
198
- # Issues a warning about being unable to determine a public url.
199
- #
200
- # @param uri [String] A URI known to be valid for this importer.
201
- # @return [NilClass] nil
202
- def warn_about_public_url(uri)
203
- @warnings_issued ||= Set.new
204
- unless @warnings_issued.include?(uri)
205
- Sass::Util.sass_warn <<WARNING
206
- WARNING: Couldn't determine public URL for "#{uri}" while generating sourcemap.
207
- Without a public URL, there's nothing for the source map to link to.
208
- WARNING
209
- @warnings_issued << uri
210
- end
211
- nil
212
- end
213
-
214
198
  private
215
199
 
216
200
  def _find(dir, name, options)
@@ -20,4 +20,3 @@ end
20
20
 
21
21
  require 'sass/importers/base'
22
22
  require 'sass/importers/filesystem'
23
- require 'sass/importers/deprecated_path'
data/lib/sass/media.rb CHANGED
@@ -205,9 +205,6 @@ module Sass::Media
205
205
  # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
206
206
  # @return [String]
207
207
  def self._interp_to_src(interp, options)
208
- interp.map do |r|
209
- next r if r.is_a?(String)
210
- "\#{#{r.to_sass(options)}}"
211
- end.join
208
+ interp.map {|r| r.is_a?(String) ? r : r.to_sass(options)}.join
212
209
  end
213
210
  end
@@ -36,19 +36,30 @@ module Sass::Plugin
36
36
  options.merge!(opts)
37
37
  end
38
38
 
39
- # Register a callback to be run after stylesheets are mass-updated.
39
+ # Register a callback to be run before stylesheets are mass-updated.
40
40
  # This is run whenever \{#update\_stylesheets} is called,
41
41
  # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
42
42
  # is enabled.
43
43
  #
44
- # @yield [individual_files]
45
- # @yieldparam individual_files [<(String, String)>]
46
- # Individual files to be updated, in addition to the directories
47
- # specified in the options.
44
+ # @yield [files]
45
+ # @yieldparam files [<(String, String, String)>]
46
+ # Individual files to be updated. Files in directories specified are included in this list.
48
47
  # The first element of each pair is the source file,
49
- # the second is the target CSS file.
48
+ # the second is the target CSS file,
49
+ # the third is the target sourcemap file.
50
50
  define_callback :updating_stylesheets
51
51
 
52
+ # Register a callback to be run after stylesheets are mass-updated.
53
+ # This is run whenever \{#update\_stylesheets} is called,
54
+ # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
55
+ # is enabled.
56
+ #
57
+ # @yield [updated_files]
58
+ # @yieldparam updated_files [<(String, String)>]
59
+ # Individual files that were updated.
60
+ # The first element of each pair is the source file, the second is the target CSS file.
61
+ define_callback :updated_stylesheets
62
+
52
63
  # Register a callback to be run after a single stylesheet is updated.
53
64
  # The callback is only run if the stylesheet is really updated;
54
65
  # if the CSS file is fresh, this won't be run.
@@ -67,6 +78,21 @@ module Sass::Plugin
67
78
  # The location of the sourcemap being generated, if any.
68
79
  define_callback :updated_stylesheet
69
80
 
81
+ # Register a callback to be run when compilation starts.
82
+ #
83
+ # In combination with on_updated_stylesheet, this could be used
84
+ # to collect compilation statistics like timing or to take a
85
+ # diff of the changes to the output file.
86
+ #
87
+ # @yield [template, css, sourcemap]
88
+ # @yieldparam template [String]
89
+ # The location of the Sass/SCSS file being updated.
90
+ # @yieldparam css [String]
91
+ # The location of the CSS file being generated.
92
+ # @yieldparam sourcemap [String]
93
+ # The location of the sourcemap being generated, if any.
94
+ define_callback :compilation_starting
95
+
70
96
  # Register a callback to be run when Sass decides not to update a stylesheet.
71
97
  # In particular, the callback is run when Sass finds that
72
98
  # the template file and none of its dependencies
@@ -139,7 +165,8 @@ module Sass::Plugin
139
165
  define_callback :template_deleted
140
166
 
141
167
  # Register a callback to be run when Sass deletes a CSS file.
142
- # This happens when the corresponding Sass/SCSS file has been deleted.
168
+ # This happens when the corresponding Sass/SCSS file has been deleted
169
+ # and when the compiler cleans the output files.
143
170
  #
144
171
  # @yield [filename]
145
172
  # @yieldparam filename [String]
@@ -147,7 +174,8 @@ module Sass::Plugin
147
174
  define_callback :deleting_css
148
175
 
149
176
  # Register a callback to be run when Sass deletes a sourcemap file.
150
- # This happens when the corresponding Sass/SCSS file has been deleted.
177
+ # This happens when the corresponding Sass/SCSS file has been deleted
178
+ # and when the compiler cleans the output files.
151
179
  #
152
180
  # @yield [filename]
153
181
  # @yieldparam filename [String]
@@ -162,35 +190,72 @@ module Sass::Plugin
162
190
  # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
163
191
  # If it has, it updates the CSS file.
164
192
  #
165
- # @param individual_files [Array<(String, String)>]
193
+ # @param individual_files [Array<(String, String[, String])>]
166
194
  # A list of files to check for updates
167
195
  # **in addition to those specified by the
168
196
  # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
169
197
  # The first string in each pair is the location of the Sass/SCSS file,
170
198
  # the second is the location of the CSS file that it should be compiled to.
199
+ # The third string, if provided, is the location of the Sourcemap file.
171
200
  def update_stylesheets(individual_files = [])
172
- individual_files = individual_files.dup
173
201
  Sass::Plugin.checked_for_updates = true
174
202
  staleness_checker = StalenessChecker.new(engine_options)
175
203
 
176
- template_location_array.each do |template_location, css_location|
177
- Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
178
- # Get the relative path to the file
179
- name = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
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
204
+ files = file_list(individual_files)
205
+ run_updating_stylesheets(files)
185
206
 
186
- individual_files.each do |file, css, sourcemap|
207
+ updated_stylesheets = []
208
+ files.each do |file, css, sourcemap|
187
209
  # TODO: Does staleness_checker need to check the sourcemap file as well?
188
210
  if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
211
+ # XXX For consistency, this should return the sourcemap too, but it would
212
+ # XXX be an API change.
213
+ updated_stylesheets << [file, css]
189
214
  update_stylesheet(file, css, sourcemap)
190
215
  else
191
216
  run_not_updating_stylesheet(file, css, sourcemap)
192
217
  end
193
218
  end
219
+ run_updated_stylesheets(updated_stylesheets)
220
+ end
221
+
222
+ # Construct a list of files that might need to be compiled
223
+ # from the provided individual_files and the template_locations.
224
+ #
225
+ # Note: this method does not cache the results as they can change
226
+ # across invocations when sass files are added or removed.
227
+ #
228
+ # @param individual_files [Array<(String, String[, String])>]
229
+ # A list of files to check for updates
230
+ # **in addition to those specified by the
231
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
232
+ # The first string in each pair is the location of the Sass/SCSS file,
233
+ # the second is the location of the CSS file that it should be compiled to.
234
+ # The third string, if provided, is the location of the Sourcemap file.
235
+ # @return [Array<(String, String, String)>]
236
+ # A list of [sass_file, css_file, sourcemap_file] tuples similar
237
+ # to what was passed in, but expanded to include the current state
238
+ # of the directories being updated.
239
+ def file_list(individual_files = [])
240
+ files = individual_files.map do |tuple|
241
+ if tuple.size < 3
242
+ [tuple[0], tuple[1], Sass::Util.sourcemap_name(tuple[1])]
243
+ else
244
+ tuple
245
+ end
246
+ end
247
+
248
+ template_location_array.each do |template_location, css_location|
249
+ Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
250
+ # Get the relative path to the file
251
+ name = Sass::Util.pathname(file).relative_path_from(
252
+ Sass::Util.pathname(template_location.to_s)).to_s
253
+ css = css_filename(name, css_location)
254
+ sourcemap = Sass::Util.sourcemap_name(css) unless engine_options[:sourcemap] == :none
255
+ files << [file, css, sourcemap]
256
+ end
257
+ end
258
+ files
194
259
  end
195
260
 
196
261
  # Watches the template directory (or directories)
@@ -211,14 +276,19 @@ module Sass::Plugin
211
276
  # The version of Listen distributed with Sass is loaded by default,
212
277
  # but if another version has already been loaded that will be used instead.
213
278
  #
214
- # @param individual_files [Array<(String, String)>]
215
- # A list of files to watch for updates
279
+ # @param individual_files [Array<(String, String[, String])>]
280
+ # A list of files to check for updates
216
281
  # **in addition to those specified by the
217
282
  # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
218
283
  # The first string in each pair is the location of the Sass/SCSS file,
219
284
  # the second is the location of the CSS file that it should be compiled to.
220
- def watch(individual_files = [])
221
- update_stylesheets(individual_files)
285
+ # The third string, if provided, is the location of the Sourcemap file.
286
+ # @param options [Hash] The options that control how watching works.
287
+ # @option options [Boolean] :skip_initial_update
288
+ # Don't do an initial update when starting the watcher when true
289
+ def watch(individual_files = [], options = {})
290
+ options, individual_files = individual_files, [] if individual_files.is_a?(Hash)
291
+ update_stylesheets(individual_files) unless options[:skip_initial_update]
222
292
 
223
293
  directories = watched_paths
224
294
  individual_files.each do |(source, _, _)|
@@ -226,9 +296,20 @@ module Sass::Plugin
226
296
  end
227
297
  directories = remove_redundant_directories(directories)
228
298
 
299
+ # A Listen version prior to 2.0 will write a test file to a directory to
300
+ # see if a watcher supports watching that directory. That breaks horribly
301
+ # on read-only directories, so we filter those out.
302
+ unless Sass::Util.listen_geq_2?
303
+ directories = directories.select {|d| File.directory?(d) && File.writable?(d)}
304
+ end
305
+
229
306
  # TODO: Keep better track of what depends on what
230
307
  # so we don't have to run a global update every time anything changes.
231
- listener_args = directories + [{:relative_paths => false}]
308
+ # XXX The :additional_watch_paths option exists for Compass to use until
309
+ # a deprecated feature is removed. It may be removed without warning.
310
+ listener_args = directories +
311
+ Array(options[:additional_watch_paths]) +
312
+ [{:relative_paths => false}]
232
313
 
233
314
  # The native windows listener is much slower than the polling option, according to
234
315
  # https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e
@@ -240,49 +321,8 @@ module Sass::Plugin
240
321
  end
241
322
 
242
323
  listener = create_listener(*listener_args) do |modified, added, removed|
243
- recompile_required = false
244
-
245
- modified.uniq.each do |f|
246
- next unless watched_file?(f)
247
- recompile_required = true
248
- run_template_modified(relative_to_pwd(f))
249
- end
250
-
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
256
-
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
275
- end
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)
285
- end
324
+ on_file_changed(individual_files, modified, added, removed)
325
+ yield(modified, added, removed) if block_given?
286
326
  end
287
327
 
288
328
  if poll && !Sass::Util.listen_geq_2?
@@ -302,6 +342,8 @@ module Sass::Plugin
302
342
  def engine_options(additional_options = {})
303
343
  opts = options.merge(additional_options)
304
344
  opts[:load_paths] = load_paths(opts)
345
+ options[:sourcemap] = :auto if options[:sourcemap] == true
346
+ options[:sourcemap] = :none if options[:sourcemap] == false
305
347
  opts
306
348
  end
307
349
 
@@ -310,12 +352,42 @@ module Sass::Plugin
310
352
  StalenessChecker.stylesheet_needs_update?(css_file, template_file)
311
353
  end
312
354
 
355
+ # Remove all output files that would be created by calling update_stylesheets, if they exist.
356
+ #
357
+ # This method runs the deleting_css and deleting_sourcemap callbacks for
358
+ # the files that are deleted.
359
+ #
360
+ # @param individual_files [Array<(String, String[, String])>]
361
+ # A list of files to check for updates
362
+ # **in addition to those specified by the
363
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
364
+ # The first string in each pair is the location of the Sass/SCSS file,
365
+ # the second is the location of the CSS file that it should be compiled to.
366
+ # The third string, if provided, is the location of the Sourcemap file.
367
+ def clean(individual_files = [])
368
+ file_list(individual_files).each do |(_, css_file, sourcemap_file)|
369
+ if File.exist?(css_file)
370
+ run_deleting_css css_file
371
+ File.delete(css_file)
372
+ end
373
+ if sourcemap_file && File.exist?(sourcemap_file)
374
+ run_deleting_sourcemap sourcemap_file
375
+ File.delete(sourcemap_file)
376
+ end
377
+ end
378
+ nil
379
+ end
380
+
313
381
  private
314
382
 
315
383
  def create_listener(*args, &block)
316
- require 'listen'
384
+ Sass::Util.load_listen!
317
385
  if Sass::Util.listen_geq_2?
318
- Listen.to(*args, &block)
386
+ # Work around guard/listen#243.
387
+ options = args.pop if args.last.is_a?(Hash)
388
+ args.map do |dir|
389
+ Listen.to(dir, options, &block)
390
+ end
319
391
  else
320
392
  Listen::Listener.new(*args, &block)
321
393
  end
@@ -323,16 +395,12 @@ module Sass::Plugin
323
395
 
324
396
  def listen_to(listener)
325
397
  if Sass::Util.listen_geq_2?
326
- listener.start
327
- listener.thread.join
328
- listener.stop # Partially work around guard/listen#146
398
+ listener.map {|l| l.start}.each {|thread| thread.join}
329
399
  else
330
- begin
331
- listener.start!
332
- rescue Interrupt
333
- # Squelch Interrupt for clean exit from Listen::Listener
334
- end
400
+ listener.start!
335
401
  end
402
+ rescue Interrupt
403
+ # Squelch Interrupt for clean exit from Listen::Listener
336
404
  end
337
405
 
338
406
  def remove_redundant_directories(directories)
@@ -351,9 +419,55 @@ module Sass::Plugin
351
419
  dedupped
352
420
  end
353
421
 
422
+ def on_file_changed(individual_files, modified, added, removed)
423
+ recompile_required = false
424
+
425
+ modified.uniq.each do |f|
426
+ next unless watched_file?(f)
427
+ recompile_required = true
428
+ run_template_modified(relative_to_pwd(f))
429
+ end
430
+
431
+ added.uniq.each do |f|
432
+ next unless watched_file?(f)
433
+ recompile_required = true
434
+ run_template_created(relative_to_pwd(f))
435
+ end
436
+
437
+ removed.uniq.each do |f|
438
+ run_template_deleted(relative_to_pwd(f))
439
+ if (files = individual_files.find {|(source, _, _)| File.expand_path(source) == f})
440
+ recompile_required = true
441
+ # This was a file we were watching explicitly and compiling to a particular location.
442
+ # Delete the corresponding file.
443
+ try_delete_css files[1]
444
+ else
445
+ next unless watched_file?(f)
446
+ recompile_required = true
447
+ # Look for the sass directory that contained the sass file
448
+ # And try to remove the css file that corresponds to it
449
+ template_location_array.each do |(sass_dir, css_dir)|
450
+ sass_dir = File.expand_path(sass_dir)
451
+ if child_of_directory?(sass_dir, f)
452
+ remainder = f[(sass_dir.size + 1)..-1]
453
+ try_delete_css(css_filename(remainder, css_dir))
454
+ break
455
+ end
456
+ end
457
+ end
458
+ end
459
+
460
+ if recompile_required
461
+ # In case a file we're watching is removed and then recreated we
462
+ # prune out the non-existant files here.
463
+ watched_files_remaining = individual_files.select {|(source, _, _)| File.exist?(source)}
464
+ update_stylesheets(watched_files_remaining)
465
+ end
466
+ end
467
+
354
468
  def update_stylesheet(filename, css, sourcemap)
355
469
  dir = File.dirname(css)
356
- unless File.exists?(dir)
470
+ unless File.exist?(dir)
357
471
  run_creating_directory dir
358
472
  FileUtils.mkdir_p dir
359
473
  end
@@ -364,6 +478,7 @@ module Sass::Plugin
364
478
  :filename => filename,
365
479
  :sourcemap_filename => sourcemap)
366
480
  mapping = nil
481
+ run_compilation_starting(filename, css, sourcemap)
367
482
  engine = Sass::Engine.for_file(filename, engine_opts)
368
483
  if sourcemap
369
484
  rendered, mapping = engine.render_with_sourcemap(File.basename(sourcemap))
@@ -373,12 +488,14 @@ module Sass::Plugin
373
488
  rescue StandardError => e
374
489
  compilation_error_occured = true
375
490
  run_compilation_error e, filename, css, sourcemap
376
- rendered = Sass::SyntaxError.exception_to_css(e, options)
491
+ raise e unless options[:full_exception]
492
+ rendered = Sass::SyntaxError.exception_to_css(e, options[:line] || 1)
377
493
  end
378
494
 
379
495
  write_file(css, rendered)
380
496
  if mapping
381
- write_file(sourcemap, mapping.to_json(:css_path => css, :sourcemap_path => sourcemap))
497
+ write_file(sourcemap, mapping.to_json(
498
+ :css_path => css, :sourcemap_path => sourcemap, :type => options[:sourcemap]))
382
499
  end
383
500
  run_updated_stylesheet(filename, css, sourcemap) unless compilation_error_occured
384
501
  end
@@ -393,12 +510,12 @@ module Sass::Plugin
393
510
  end
394
511
 
395
512
  def try_delete_css(css)
396
- if File.exists?(css)
513
+ if File.exist?(css)
397
514
  run_deleting_css css
398
515
  File.delete css
399
516
  end
400
517
  map = Sass::Util.sourcemap_name(css)
401
- if File.exists?(map)
518
+ if File.exist?(map)
402
519
  run_deleting_sourcemap map
403
520
  File.delete map
404
521
  end
@@ -46,7 +46,7 @@ module Sass
46
46
  @actively_checking = Set.new
47
47
 
48
48
  # Entries in the following instance-level caches are never explicitly expired.
49
- # Instead they are supposed to automaticaly go out of scope when a series of staleness
49
+ # Instead they are supposed to automatically go out of scope when a series of staleness
50
50
  # checks (this instance of StalenessChecker was created for) is finished.
51
51
  @mtimes, @dependencies_stale, @parse_trees = {}, {}, {}
52
52
  @options = Sass::Engine.normalize_options(options)
data/lib/sass/plugin.rb CHANGED
@@ -123,9 +123,9 @@ module Sass
123
123
  end
124
124
  end
125
125
 
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?
128
- require 'sass/plugin/rails'
126
+ if defined?(ActionController)
127
+ # On Rails 3+ the rails plugin is loaded at the right time in railtie.rb
128
+ require 'sass/plugin/rails' unless Sass::Util.ap_geq_3?
129
129
  elsif defined?(Merb::Plugins)
130
130
  require 'sass/plugin/merb'
131
131
  else
@@ -18,7 +18,7 @@ module Sass
18
18
  end
19
19
 
20
20
  return unless scan(STRING)
21
- string_value = (@scanner[1] || @scanner[2]).gsub(/\\(['"])/, '\1')
21
+ string_value = Sass::Script::Value::String.value(@scanner[1] || @scanner[2])
22
22
  value = Script::Value::String.new(string_value, :string)
23
23
  [:string, value]
24
24
  end