sass 3.3.0.alpha.201 → 3.3.0.alpha.211

Sign up to get free protection for your applications and to get access to all the features.
data/REVISION CHANGED
@@ -1 +1 @@
1
- 3a64325d2550f0bc57f6490854c0a7d0bc5c9db5
1
+ 33078d3298227e86d5cc9628b17270b823bf9003
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.3.0.alpha.201
1
+ 3.3.0.alpha.211
@@ -1 +1 @@
1
- 17 July 2013 20:29:34 GMT
1
+ 18 July 2013 18:31:42 GMT
@@ -191,6 +191,14 @@ module Sass
191
191
  options[:filesystem_importer].new(p.to_s)
192
192
  end
193
193
 
194
+ # Remove any deprecated importers if the location is imported explicitly
195
+ options[:load_paths].reject! do |importer|
196
+ importer.is_a?(Sass::Importers::DeprecatedPath) &&
197
+ options[:load_paths].find {|other_importer| other_importer.is_a?(Sass::Importers::Filesystem) &&
198
+ other_importer != importer &&
199
+ other_importer.root == importer.root}
200
+ end
201
+
194
202
  # Backwards compatibility
195
203
  options[:property_syntax] ||= options[:attribute_syntax]
196
204
  case options[:property_syntax]
@@ -197,7 +197,7 @@ MESSAGE
197
197
  def initialize(args)
198
198
  super
199
199
  @options[:for_engine] = {
200
- :load_paths => ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
200
+ :load_paths => default_sass_path
201
201
  }
202
202
  @default_syntax = :sass
203
203
  end
@@ -433,17 +433,14 @@ MSG
433
433
  ::Sass::Plugin.on_updated_stylesheet do |_, css, sourcemap|
434
434
  [css, sourcemap].each do |file|
435
435
  next unless file
436
- if File.exists? file
437
- puts_action :overwrite, :yellow, file
438
- else
439
- puts_action :create, :green, file
440
- end
436
+ puts_action :write, :green, file
441
437
  end
442
438
  end
443
439
 
444
440
  had_error = false
445
441
  ::Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
446
442
  ::Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
443
+ ::Sass::Plugin.on_deleting_sourcemap {|filename| puts_action :delete, :yellow, filename}
447
444
  ::Sass::Plugin.on_compilation_error do |error, _, _|
448
445
  if error.is_a?(SystemCallError) && !@options[:stop_on_error]
449
446
  had_error = true
@@ -505,6 +502,16 @@ MSG
505
502
  return false if colon_path?(path)
506
503
  return ::Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
507
504
  end
505
+
506
+ def default_sass_path
507
+ if ENV['SASSPATH']
508
+ # The select here prevents errors when the environment's load paths specified do not exist.
509
+ ENV['SASSPATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)}
510
+ else
511
+ [::Sass::Importers::DeprecatedPath.new(".")]
512
+ end
513
+ end
514
+
508
515
  end
509
516
 
510
517
  class Scss < Sass
@@ -20,3 +20,4 @@ end
20
20
 
21
21
  require 'sass/importers/base'
22
22
  require 'sass/importers/filesystem'
23
+ require 'sass/importers/deprecated_path'
@@ -144,8 +144,27 @@ module Sass
144
144
  def to_s
145
145
  Sass::Util.abstract(self)
146
146
  end
147
+
148
+ # If the importer is based on files on the local filesystem
149
+ # this method should return folders which should be watched
150
+ # for changes.
151
+ #
152
+ # @return [Array<String>] List of absolute paths of directories to watch
153
+ def directories_to_watch
154
+ []
155
+ end
156
+
157
+ # If this importer is based on files on the local filesystem This method
158
+ # should return true if the file, when changed, should trigger a
159
+ # recompile.
160
+ #
161
+ # It is acceptable for non-sass files to be watched and trigger a recompile.
162
+ #
163
+ # @param filename [String] The absolute filename for a file that has changed.
164
+ # @return [Boolean] When the file changed should cause a recompile.
165
+ def watched_file?(filename)
166
+ false
167
+ end
147
168
  end
148
169
  end
149
170
  end
150
-
151
-
@@ -0,0 +1,45 @@
1
+ module Sass
2
+ module Importers
3
+ # This importer emits a deprecation warning the first time it is used to
4
+ # import a file. It is used to deprecate the current working
5
+ # directory from the list of automatic sass load paths.
6
+ class DeprecatedPath < Filesystem
7
+
8
+ # @param root [String] The absolute, expanded path to the folder that is deprecated.
9
+ def initialize(root)
10
+ @specified_root = root
11
+ @warning_given = false
12
+ super
13
+ end
14
+
15
+ # @see Sass::Importers::Base#find
16
+ def find(*args)
17
+ found = super
18
+ if found && !@warning_given
19
+ @warning_given = true
20
+ Sass::Util.sass_warn deprecation_warning
21
+ end
22
+ found
23
+ end
24
+
25
+ # @see Sass::Importers::Base#to_s
26
+ def to_s
27
+ "#{@root} (DEPRECATED)"
28
+ end
29
+
30
+ protected
31
+
32
+ # @return [String] The deprecation warning that will be printed the first
33
+ # time an import occurs.
34
+ def deprecation_warning
35
+ path = (@specified_root == ".") ? "the current working directory" : @specified_root
36
+ <<WARNING
37
+ DEPRECATION WARNING: Importing from #{path} will not be
38
+ automatic in future versions of Sass. To avoid future errors, you can add it
39
+ to your environment explicitly by setting `SASSPATH=#{@specified_root}`, by using the -I command
40
+ line option, or by changing your Sass configuration options.
41
+ WARNING
42
+ end
43
+ end
44
+ end
45
+ end
@@ -55,6 +55,17 @@ module Sass
55
55
  root.eql?(other.root)
56
56
  end
57
57
 
58
+ # @see Base#directories_to_watch
59
+ def directories_to_watch
60
+ [root]
61
+ end
62
+
63
+ # @see Base#watched_file?
64
+ def watched_file?(filename)
65
+ filename =~ /\.s[ac]ss$/ &&
66
+ filename.start_with?(root + File::SEPARATOR)
67
+ end
68
+
58
69
  protected
59
70
 
60
71
  # If a full uri is passed, this removes the root from it
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'pathname'
2
3
 
3
4
  require 'sass'
4
5
  # XXX CE: is this still necessary now that we have the compiler class?
@@ -148,6 +149,14 @@ module Sass::Plugin
148
149
  # The location of the CSS file that was deleted.
149
150
  define_callback :deleting_css
150
151
 
152
+ # Register a callback to be run when Sass deletes a sourcemap file.
153
+ # This happens when the corresponding Sass/SCSS file has been deleted.
154
+ #
155
+ # @yield [filename]
156
+ # @yieldparam filename [String]
157
+ # The location of the sourcemap file that was deleted.
158
+ define_callback :deleting_sourcemap
159
+
151
160
  # Updates out-of-date stylesheets.
152
161
  #
153
162
  # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
@@ -213,53 +222,57 @@ module Sass::Plugin
213
222
  def watch(individual_files = [])
214
223
  update_stylesheets(individual_files)
215
224
 
216
- require 'listen'
217
-
218
- template_paths = template_locations # cache the locations
219
- individual_files_hash = individual_files.inject({}) do |h, files|
220
- parent = File.dirname(files.first)
221
- (h[parent] ||= []) << files unless template_paths.include?(parent)
222
- h
225
+ directories = watched_paths
226
+ individual_files.each do |(source, _, _)|
227
+ directories << File.dirname(File.expand_path(source))
223
228
  end
224
- directories = template_paths + individual_files_hash.keys +
225
- [{:relative_paths => true}]
229
+ directories = remove_redundant_directories(directories)
226
230
 
227
231
  # TODO: Keep better track of what depends on what
228
232
  # so we don't have to run a global update every time anything changes.
229
- listener = Listen::MultiListener.new(*directories) do |modified, added, removed|
230
- modified.each do |f|
231
- parent = File.dirname(f)
232
- if files = individual_files_hash[parent]
233
- next unless files.first == f
234
- else
235
- next unless f =~ /\.s[ac]ss$/
236
- end
237
- run_template_modified(f)
233
+ listener = create_listener(*(directories + [{:relative_paths => false}])) do |modified, added, removed|
234
+ recompile_required = false
235
+
236
+ modified.uniq.each do |f|
237
+ next unless watched_file?(f)
238
+ recompile_required = true
239
+ run_template_modified(relative_to_pwd(f))
238
240
  end
239
241
 
240
- added.each do |f|
241
- parent = File.dirname(f)
242
- if files = individual_files_hash[parent]
243
- next unless files.first == f
244
- else
245
- next unless f =~ /\.s[ac]ss$/
246
- end
247
- run_template_created(f)
242
+ added.uniq.each do |f|
243
+ next unless watched_file?(f)
244
+ recompile_required = true
245
+ run_template_created(relative_to_pwd(f))
248
246
  end
249
247
 
250
- removed.each do |f|
251
- parent = File.dirname(f)
252
- if files = individual_files_hash[parent]
253
- next unless files.first == f
248
+ removed.uniq.each do |f|
249
+ if files = individual_files.find {|(source,_,_)| File.expand_path(source) == f}
250
+ recompile_required = true
251
+ # This was a file we were watching explicitly and compiling to a particular location.
252
+ # Delete the corresponding file.
254
253
  try_delete_css files[1]
255
254
  else
256
- next unless f =~ /\.s[ac]ss$/
257
- try_delete_css f.gsub(/\.s[ac]ss$/, '.css')
255
+ next unless watched_file?(f)
256
+ recompile_required = true
257
+ # Look for the sass directory that contained the sass file
258
+ # And try to remove the css file that corresponds to it
259
+ template_location_array.each do |(sass_dir, css_dir)|
260
+ sass_dir = File.expand_path(sass_dir)
261
+ if child_of_directory?(sass_dir, f)
262
+ remainder = f[(sass_dir.size + 1)..-1]
263
+ try_delete_css(css_filename(remainder, css_dir))
264
+ break
265
+ end
266
+ end
258
267
  end
259
- run_template_deleted(f)
268
+ run_template_deleted(relative_to_pwd(f))
260
269
  end
261
270
 
262
- update_stylesheets(individual_files)
271
+ if recompile_required
272
+ # In case a file we're watching is removed and then recreated we prune out the non-existant files here.
273
+ watched_files_remaining = individual_files.select {|(source, _, _)| File.exists?(source)}
274
+ update_stylesheets(watched_files_remaining)
275
+ end
263
276
  end
264
277
 
265
278
  # The native windows listener is much slower than the polling
@@ -267,7 +280,7 @@ module Sass::Plugin
267
280
  listener.force_polling(true) if @options[:poll] || Sass::Util.windows?
268
281
 
269
282
  begin
270
- listener.start
283
+ listener.start!
271
284
  rescue Exception => e
272
285
  raise e unless e.is_a?(Interrupt)
273
286
  end
@@ -291,6 +304,23 @@ module Sass::Plugin
291
304
 
292
305
  private
293
306
 
307
+ def create_listener(*args, &block)
308
+ require 'listen'
309
+ Listen::Listener.new(*args, &block)
310
+ end
311
+
312
+ def remove_redundant_directories(directories)
313
+ dedupped = []
314
+ directories.each do |new_directory|
315
+ # no need to add a directory that is already watched.
316
+ next if dedupped.any? {|existing_directory| child_of_directory?(existing_directory, new_directory)}
317
+ # get rid of any sub directories of this new directory
318
+ dedupped.reject! {|existing_directory| child_of_directory?(new_directory, existing_directory)}
319
+ dedupped << new_directory
320
+ end
321
+ dedupped
322
+ end
323
+
294
324
  def update_stylesheet(filename, css, sourcemap)
295
325
  dir = File.dirname(css)
296
326
  unless File.exists?(dir)
@@ -329,9 +359,27 @@ module Sass::Plugin
329
359
  end
330
360
 
331
361
  def try_delete_css(css)
332
- return unless File.exists?(css)
333
- run_deleting_css css
334
- File.delete css
362
+ if File.exists?(css)
363
+ run_deleting_css css
364
+ File.delete css
365
+ end
366
+ map = Sass::Util.sourcemap_name(css)
367
+ if File.exists?(map)
368
+ run_deleting_sourcemap map
369
+ File.delete map
370
+ end
371
+ end
372
+
373
+ def watched_file?(file)
374
+ normalized_load_paths.find {|lp| lp.watched_file?(file)}
375
+ end
376
+
377
+ def watched_paths
378
+ @watched_paths ||= normalized_load_paths.map {|lp| lp.directories_to_watch}.compact.flatten
379
+ end
380
+
381
+ def normalized_load_paths
382
+ @normalized_load_paths ||= Sass::Engine.normalize_options(:load_paths=> load_paths)[:load_paths]
335
383
  end
336
384
 
337
385
  def load_paths(opts = options)
@@ -347,7 +395,18 @@ module Sass::Plugin
347
395
  end
348
396
 
349
397
  def css_filename(name, path)
350
- "#{path}/#{name}".gsub(/\.s[ac]ss$/, '.css')
398
+ "#{path}#{File::SEPARATOR unless path.end_with?(File::SEPARATOR)}#{name}".gsub(/\.s[ac]ss$/, '.css')
399
+ end
400
+
401
+ def relative_to_pwd(f)
402
+ Pathname.new(f).relative_path_from(Pathname.new(Dir.pwd)).to_s
403
+ rescue ArgumentError # when a relative path cannot be computed
404
+ f
405
+ end
406
+
407
+ def child_of_directory?(parent, child)
408
+ parent_dir = parent.end_with?(File::SEPARATOR) ? parent : (parent + File::SEPARATOR)
409
+ child.start_with?(parent_dir) || parent == child
351
410
  end
352
411
  end
353
412
  end
@@ -1,3 +1,3 @@
1
- source :gemcutter
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gem 'rake'
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env ruby
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../test_helper'
4
+ require 'sass/plugin'
5
+ require 'sass/plugin/compiler'
6
+
7
+ class CompilerTest < Test::Unit::TestCase
8
+ class FakeListener
9
+ attr_accessor :options
10
+ attr_accessor :directories
11
+ attr_reader :start_called
12
+
13
+ def initialize(*args, &on_filesystem_event)
14
+ self.options = args.last.is_a?(Hash) ? args.pop : {}
15
+ self.directories = args
16
+ @on_filesystem_event = on_filesystem_event
17
+ @start_called = false
18
+ reset_events!
19
+ end
20
+
21
+ def fire_events!(*args)
22
+ @on_filesystem_event.call(@modified, @added, @removed)
23
+ reset_events!
24
+ end
25
+
26
+ def changed(filename)
27
+ @modified << File.expand_path(filename)
28
+ end
29
+
30
+ def added(filename)
31
+ @added << File.expand_path(filename)
32
+ end
33
+
34
+ def removed(filename)
35
+ @removed << File.expand_path(filename)
36
+ end
37
+
38
+ def on_start!(&run_during_start)
39
+ @run_during_start = run_during_start
40
+ end
41
+
42
+ def start!
43
+ @run_during_start.call(self) if @run_during_start
44
+ end
45
+
46
+ def reset_events!
47
+ @modified = []
48
+ @added = []
49
+ @removed = []
50
+ end
51
+ end
52
+
53
+ module MockWatcher
54
+ attr_accessor :run_during_start
55
+ attr_accessor :update_stylesheets_times
56
+ attr_accessor :update_stylesheets_called_with
57
+ attr_accessor :deleted_css_files
58
+
59
+ def fake_listener
60
+ @fake_listener
61
+ end
62
+
63
+ def update_stylesheets(individual_files)
64
+ @update_stylesheets_times ||= 0
65
+ @update_stylesheets_times += 1
66
+ (@update_stylesheets_called_with ||= []) << individual_files
67
+ end
68
+
69
+ def try_delete_css(css_filename)
70
+ (@deleted_css_files ||= []) << css_filename
71
+ end
72
+
73
+ private
74
+ def create_listener(*args, &on_filesystem_event)
75
+ @fake_listener = FakeListener.new(*args, &on_filesystem_event)
76
+ @fake_listener.on_start!(&run_during_start)
77
+ @fake_listener
78
+ end
79
+ end
80
+
81
+ def test_initialize
82
+ watcher
83
+ end
84
+
85
+ def test_watch_starts_the_listener
86
+ start_called = false
87
+ c = watcher do |listener|
88
+ start_called = true
89
+ end
90
+ c.watch
91
+ assert start_called, "start! was not called"
92
+ end
93
+
94
+ def test_sass_callbacks_fire_from_listener_events
95
+ c = watcher do |listener|
96
+ listener.changed "changed.scss"
97
+ listener.added "added.scss"
98
+ listener.removed "removed.scss"
99
+ listener.fire_events!
100
+ end
101
+
102
+ modified_fired = false
103
+ c.on_template_modified do |sass_file|
104
+ modified_fired = true
105
+ assert_equal "changed.scss", sass_file
106
+ end
107
+
108
+ added_fired = false
109
+ c.on_template_created do |sass_file|
110
+ added_fired = true
111
+ assert_equal "added.scss", sass_file
112
+ end
113
+
114
+ removed_fired = false
115
+ c.on_template_deleted do |sass_file|
116
+ removed_fired = true
117
+ assert_equal "removed.scss", sass_file
118
+ end
119
+
120
+ c.watch
121
+
122
+ assert_equal 2, c.update_stylesheets_times
123
+ assert modified_fired
124
+ assert added_fired
125
+ assert removed_fired
126
+ end
127
+
128
+ def test_removing_a_sass_file_removes_corresponding_css_file
129
+ c = watcher do |listener|
130
+ listener.removed "remove_me.scss"
131
+ listener.fire_events!
132
+ end
133
+
134
+ c.watch
135
+
136
+ assert_equal "./remove_me.css", c.deleted_css_files.first
137
+ end
138
+
139
+ def test_an_importer_can_watch_an_image
140
+ image_importer = Sass::Importers::Filesystem.new(".")
141
+ class << image_importer
142
+ def watched_file?(filename)
143
+ filename =~ /\.png$/
144
+ end
145
+ end
146
+ c = watcher(:load_paths => [image_importer]) do |listener|
147
+ listener.changed "image.png"
148
+ listener.fire_events!
149
+ end
150
+
151
+ modified_fired = false
152
+ c.on_template_modified do |f|
153
+ modified_fired = true
154
+ assert_equal "image.png", f
155
+ end
156
+
157
+ c.watch
158
+
159
+ assert_equal 2, c.update_stylesheets_times
160
+ assert modified_fired
161
+ end
162
+
163
+ def test_watching_specific_files_and_one_is_deleted
164
+ directories = nil
165
+ c = watcher do |listener|
166
+ directories = listener.directories
167
+ listener.removed "/asdf/foobar/sass/foo.scss"
168
+ listener.fire_events!
169
+ end
170
+ c.watch([["/asdf/foobar/sass/foo.scss", "/asdf/foobar/css/foo.css", nil]])
171
+ assert directories.include?("/asdf/foobar/sass"), directories.inspect
172
+ assert_equal "/asdf/foobar/css/foo.css", c.deleted_css_files.first, "the corresponding css file was not deleted"
173
+ assert_equal [], c.update_stylesheets_called_with[1], "the sass file should not have been compiled"
174
+ end
175
+
176
+ def test_watched_directories_are_dedupped
177
+ directories = nil
178
+ c = watcher(:load_paths => [".", "./foo", "."]) do |listener|
179
+ directories = listener.directories
180
+ end
181
+ c.watch
182
+ assert_equal [File.expand_path(".")], directories
183
+ end
184
+
185
+ def test_a_changed_css_in_a_watched_directory_does_not_force_a_compile
186
+ c = watcher do |listener|
187
+ listener.changed "foo.css"
188
+ listener.fire_events!
189
+ end
190
+
191
+ c.on_template_modified do |f|
192
+ assert false, "Should not have been called"
193
+ end
194
+
195
+ c.watch
196
+
197
+ assert_equal 1, c.update_stylesheets_times
198
+ end
199
+
200
+ private
201
+
202
+ def default_options
203
+ {:template_location => [[".","."]]}
204
+ end
205
+
206
+ def watcher(options = {}, &run_during_start)
207
+ options = default_options.merge(options)
208
+ watcher = Sass::Plugin::Compiler.new(options)
209
+ watcher.extend(MockWatcher)
210
+ watcher.run_during_start = run_during_start
211
+ watcher
212
+ end
213
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass
3
3
  version: !ruby/object:Gem::Version
4
- hash: 592302751
4
+ hash: 592302763
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 3
8
8
  - 3
9
9
  - 0
10
10
  - alpha
11
- - 201
12
- version: 3.3.0.alpha.201
11
+ - 211
12
+ version: 3.3.0.alpha.211
13
13
  platform: ruby
14
14
  authors:
15
15
  - Nathan Weizenbaum
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2013-07-17 00:00:00 -04:00
22
+ date: 2013-07-18 00:00:00 -04:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
@@ -30,11 +30,12 @@ dependencies:
30
30
  requirements:
31
31
  - - ~>
32
32
  - !ruby/object:Gem::Version
33
- hash: 5
33
+ hash: 19
34
34
  segments:
35
+ - 1
36
+ - 1
35
37
  - 0
36
- - 7
37
- version: "0.7"
38
+ version: 1.1.0
38
39
  type: :runtime
39
40
  version_requirements: *id001
40
41
  - !ruby/object:Gem::Dependency
@@ -98,6 +99,7 @@ files:
98
99
  - lib/sass/importers.rb
99
100
  - lib/sass/importers/base.rb
100
101
  - lib/sass/importers/filesystem.rb
102
+ - lib/sass/importers/deprecated_path.rb
101
103
  - lib/sass/logger.rb
102
104
  - lib/sass/logger/base.rb
103
105
  - lib/sass/logger/log_level.rb
@@ -314,6 +316,7 @@ files:
314
316
  - test/sass/util/multibyte_string_scanner_test.rb
315
317
  - test/sass/util/subset_map_test.rb
316
318
  - test/sass/source_map_test.rb
319
+ - test/sass/compiler_test.rb
317
320
  - test/test_helper.rb
318
321
  - extra/update_watch.rb
319
322
  - Rakefile
@@ -384,3 +387,4 @@ test_files:
384
387
  - test/sass/util/multibyte_string_scanner_test.rb
385
388
  - test/sass/util/subset_map_test.rb
386
389
  - test/sass/source_map_test.rb
390
+ - test/sass/compiler_test.rb