sass 3.2.0.alpha.104 → 3.2.0.alpha.236

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/REVISION +1 -1
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/lib/sass/engine.rb +8 -1
  5. data/lib/sass/exec.rb +2 -2
  6. data/lib/sass/plugin/compiler.rb +51 -43
  7. data/lib/sass/script/color.rb +4 -9
  8. data/lib/sass/script/funcall.rb +11 -2
  9. data/lib/sass/script/functions.rb +16 -30
  10. data/lib/sass/script/lexer.rb +7 -1
  11. data/lib/sass/script/list.rb +2 -2
  12. data/lib/sass/script/literal.rb +8 -0
  13. data/lib/sass/script/null.rb +34 -0
  14. data/lib/sass/script/operation.rb +4 -0
  15. data/lib/sass/script/parser.rb +4 -2
  16. data/lib/sass/scss/parser.rb +1 -5
  17. data/lib/sass/scss/rx.rb +1 -1
  18. data/lib/sass/tree/directive_node.rb +5 -0
  19. data/lib/sass/tree/media_node.rb +3 -0
  20. data/lib/sass/tree/prop_node.rb +7 -3
  21. data/lib/sass/tree/visitors/base.rb +1 -1
  22. data/lib/sass/tree/visitors/check_nesting.rb +21 -18
  23. data/lib/sass/tree/visitors/convert.rb +1 -0
  24. data/lib/sass/tree/visitors/perform.rb +3 -2
  25. data/lib/sass/tree/visitors/to_css.rb +1 -0
  26. data/lib/sass/util.rb +28 -0
  27. data/test/sass/conversion_test.rb +33 -0
  28. data/test/sass/engine_test.rb +118 -3
  29. data/test/sass/functions_test.rb +56 -24
  30. data/test/sass/script_test.rb +60 -4
  31. data/test/sass/scss/scss_test.rb +10 -0
  32. data/vendor/listen/CHANGELOG.md +19 -1
  33. data/vendor/listen/README.md +27 -12
  34. data/vendor/listen/lib/listen/adapter.rb +9 -1
  35. data/vendor/listen/lib/listen/adapters/linux.rb +18 -7
  36. data/vendor/listen/lib/listen/adapters/windows.rb +7 -8
  37. data/vendor/listen/lib/listen/directory_record.rb +108 -48
  38. data/vendor/listen/lib/listen/listener.rb +28 -11
  39. data/vendor/listen/lib/listen/multi_listener.rb +6 -6
  40. data/vendor/listen/lib/listen/version.rb +1 -1
  41. data/vendor/listen/spec/listen/adapters/linux_spec.rb +11 -0
  42. data/vendor/listen/spec/listen/directory_record_spec.rb +268 -41
  43. data/vendor/listen/spec/listen/listener_spec.rb +8 -4
  44. data/vendor/listen/spec/listen/multi_listener_spec.rb +9 -4
  45. data/vendor/listen/spec/support/adapter_helper.rb +178 -0
  46. data/vendor/listen/spec/support/directory_record_helper.rb +24 -4
  47. data/vendor/listen/spec/support/listeners_helper.rb +11 -0
  48. metadata +11 -11
  49. data/lib/sass/plugin/listener.rb +0 -61
@@ -63,17 +63,16 @@ module Listen
63
63
  # @return [FChange::Notifier] initialized worker
64
64
  #
65
65
  def init_worker
66
- worker = FChange::Notifier.new
67
- @directories.each do |directory|
68
- watcher = worker.watch(directory, :all_events, :recursive) do |event|
69
- next if @paused
70
- @mutex.synchronize do
71
- @changed_dirs << File.expand_path(event.watcher.path)
66
+ FChange::Notifier.new.tap do |worker|
67
+ @directories.each do |directory|
68
+ worker.watch(directory, :all_events, :recursive) do |event|
69
+ next if @paused
70
+ @mutex.synchronize do
71
+ @changed_dirs << File.expand_path(event.watcher.path)
72
+ end
72
73
  end
73
74
  end
74
- worker.add_watcher(watcher)
75
75
  end
76
- worker
77
76
  end
78
77
 
79
78
  end
@@ -1,6 +1,5 @@
1
1
  require 'set'
2
2
  require 'find'
3
- require 'pathname'
4
3
  require 'digest/sha1'
5
4
 
6
5
  module Listen
@@ -12,61 +11,92 @@ module Listen
12
11
  class DirectoryRecord
13
12
  attr_reader :directory, :paths, :sha1_checksums
14
13
 
15
- # Default paths' beginnings that doesn't get stored in the record
16
- DEFAULT_IGNORED_PATHS = %w[.bundle .git .DS_Store log tmp vendor]
14
+ DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
15
+
16
+ DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
17
+
18
+ # Defines the used precision based on the type of mtime returned by the
19
+ # system (whether its in milliseconds or just seconds)
20
+ #
21
+ HIGH_PRECISION_SUPPORTED = File.mtime(__FILE__).to_f.to_s[-2..-1] != '.0'
22
+
23
+ # Data structure used to save meta data about a path
24
+ #
25
+ MetaData = Struct.new(:type, :mtime)
26
+
27
+ # Class methods
28
+ #
29
+ class << self
30
+
31
+ # Creates the ignoring patterns from the default ignored
32
+ # directories and extensions. It memoizes the generated patterns
33
+ # to avoid unnecessary computation.
34
+ #
35
+ def generate_default_ignoring_patterns
36
+ @@default_ignoring_patterns ||= Array.new.tap do |default_patterns|
37
+ # Add directories
38
+ ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
39
+ default_patterns << %r{^(?:#{ignored_directories.join('|')})/}
40
+
41
+ # Add extensions
42
+ ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
43
+ default_patterns << %r{(?:#{ignored_extensions.join('|')})$}
44
+ end
45
+ end
46
+ end
17
47
 
18
48
  # Initializes a directory record.
19
49
  #
20
50
  # @option [String] directory the directory to keep track of
21
51
  #
22
52
  def initialize(directory)
23
- @directory = directory
24
- raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(@directory)
53
+ raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
25
54
 
26
- @ignored_paths = Set.new(DEFAULT_IGNORED_PATHS)
27
- @filters = Set.new
28
- @sha1_checksums = Hash.new
55
+ @directory = directory
56
+ @ignoring_patterns = Set.new
57
+ @filtering_patterns = Set.new
58
+ @sha1_checksums = Hash.new
59
+
60
+ @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
29
61
  end
30
62
 
31
- # Returns the ignored paths in the record
63
+ # Returns the ignoring patterns in the record
32
64
  #
33
- # @return [Array<String>] the ignored paths
65
+ # @return [Array<Regexp>] the ignoring patterns
34
66
  #
35
- def ignored_paths
36
- @ignored_paths.to_a
67
+ def ignoring_patterns
68
+ @ignoring_patterns.to_a
37
69
  end
38
70
 
39
- # Returns the filters used in the record to know
71
+ # Returns the filtering patterns used in the record to know
40
72
  # which paths should be stored.
41
73
  #
42
- # @return [Array<String>] the used filters
74
+ # @return [Array<Regexp>] the filtering patterns
43
75
  #
44
- def filters
45
- @filters.to_a
76
+ def filtering_patterns
77
+ @filtering_patterns.to_a
46
78
  end
47
79
 
48
- # Adds ignored path to the record.
80
+ # Adds ignoring patterns to the record.
49
81
  #
50
82
  # @example Ignore some paths
51
- # ignore ".git", ".svn"
83
+ # ignore %r{^ignored/path/}, /man/
52
84
  #
53
- # @param [String, Array<String>] paths a path or a list of paths to ignore
85
+ # @param [Regexp] regexp a pattern for ignoring paths
54
86
  #
55
- def ignore(*paths)
56
- @ignored_paths.merge(paths)
87
+ def ignore(*regexps)
88
+ @ignoring_patterns.merge(regexps)
57
89
  end
58
90
 
59
- # Adds file filters to the listener.
91
+ # Adds filtering patterns to the listener.
60
92
  #
61
93
  # @example Filter some files
62
94
  # ignore /\.txt$/, /.*\.zip/
63
95
  #
64
- # @param [Array<Regexp>] regexps a list of regexps file filters
65
- #
66
- # @return [Listen::Listener] the listener itself
96
+ # @param [Regexp] regexp a pattern for filtering paths
67
97
  #
68
98
  def filter(*regexps)
69
- @filters.merge(regexps)
99
+ @filtering_patterns.merge(regexps)
70
100
  end
71
101
 
72
102
  # Returns whether a path should be ignored or not.
@@ -76,7 +106,8 @@ module Listen
76
106
  # @return [Boolean]
77
107
  #
78
108
  def ignored?(path)
79
- @ignored_paths.any? { |ignored_path| path =~ /#{ignored_path}$/ }
109
+ path = relative_to_base(path)
110
+ @ignoring_patterns.any? { |pattern| pattern =~ path }
80
111
  end
81
112
 
82
113
  # Returns whether a path should be filtered or not.
@@ -86,7 +117,11 @@ module Listen
86
117
  # @return [Boolean]
87
118
  #
88
119
  def filtered?(path)
89
- @filters.empty? || @filters.any? { |filter| path =~ filter }
120
+ # When no filtering patterns are set, ALL files are stored.
121
+ return true if @filtering_patterns.empty?
122
+
123
+ path = relative_to_base(path)
124
+ @filtering_patterns.any? { |pattern| pattern =~ path }
90
125
  end
91
126
 
92
127
  # Finds the paths that should be stored and adds them
@@ -95,7 +130,6 @@ module Listen
95
130
  def build
96
131
  @paths = Hash.new { |h, k| h[k] = Hash.new }
97
132
  important_paths { |path| insert_path(path) }
98
- @updated_at = Time.now.to_i
99
133
  end
100
134
 
101
135
  # Detects changes in the passed directories, updates
@@ -111,15 +145,27 @@ module Listen
111
145
  def fetch_changes(directories, options = {})
112
146
  @changes = { :modified => [], :added => [], :removed => [] }
113
147
  directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
148
+
114
149
  directories.each do |directory|
115
150
  next unless directory[@directory] # Path is or inside directory
116
151
  detect_modifications_and_removals(directory, options)
117
152
  detect_additions(directory, options)
118
153
  end
119
- @updated_at = Time.now.to_i
154
+
120
155
  @changes
121
156
  end
122
157
 
158
+ # Converts an absolute path to a path that's relative to the base directory.
159
+ #
160
+ # @param [String] path the path to convert
161
+ #
162
+ # @return [String] the relative path
163
+ #
164
+ def relative_to_base(path)
165
+ return nil unless path[@directory]
166
+ path.sub(%r{^#{@directory}#{File::SEPARATOR}?}, '')
167
+ end
168
+
123
169
  private
124
170
 
125
171
  # Detects modifications and removals recursively in a directory.
@@ -134,10 +180,10 @@ module Listen
134
180
  # @option options [Boolean] relative_paths whether or not to use relative paths for changes
135
181
  #
136
182
  def detect_modifications_and_removals(directory, options = {})
137
- @paths[directory].each do |basename, type|
183
+ @paths[directory].each do |basename, meta_data|
138
184
  path = File.join(directory, basename)
139
185
 
140
- case type
186
+ case meta_data.type
141
187
  when 'Dir'
142
188
  if File.directory?(path)
143
189
  detect_modifications_and_removals(path, options) if options[:recursive]
@@ -148,8 +194,15 @@ module Listen
148
194
  end
149
195
  when 'File'
150
196
  if File.exist?(path)
151
- new_mtime = File.mtime(path).to_i
152
- if @updated_at < new_mtime || (@updated_at == new_mtime && content_modified?(path))
197
+ new_mtime = mtime_of(path)
198
+
199
+ # First check if we are in the same second (to update checksums)
200
+ # before checking the time difference
201
+ if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
202
+ # Update the meta data of the files
203
+ meta_data.mtime = new_mtime
204
+ @paths[directory][basename] = meta_data
205
+
153
206
  @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
154
207
  end
155
208
  else
@@ -176,7 +229,9 @@ module Listen
176
229
  next if path == @directory
177
230
 
178
231
  if File.directory?(path)
179
- if ignored?(path) || (directory != path && (!options[:recursive] && existing_path?(path)))
232
+ # Add a trailing slash to directories when checking if a directory is
233
+ # ignored to optimize finding them as Find.find doesn't.
234
+ if ignored?(path + File::SEPARATOR) || (directory != path && (!options[:recursive] && existing_path?(path)))
180
235
  Find.prune # Don't look any further into this directory.
181
236
  else
182
237
  insert_path(path)
@@ -197,12 +252,12 @@ module Listen
197
252
  #
198
253
  def content_modified?(path)
199
254
  sha1_checksum = Digest::SHA1.file(path).to_s
200
- if @sha1_checksums[path] != sha1_checksum
201
- @sha1_checksums[path] = sha1_checksum
202
- true
203
- else
204
- false
205
- end
255
+ return false if @sha1_checksums[path] == sha1_checksum
256
+
257
+ had_no_checksum = @sha1_checksums[path].nil?
258
+ @sha1_checksums[path] = sha1_checksum
259
+
260
+ had_no_checksum ? false : true
206
261
  end
207
262
 
208
263
  # Traverses the base directory looking for paths that should
@@ -215,7 +270,9 @@ module Listen
215
270
  next if path == @directory
216
271
 
217
272
  if File.directory?(path)
218
- if ignored?(path)
273
+ # Add a trailing slash to directories when checking if a directory is
274
+ # ignored to optimize finding them as Find.find doesn't.
275
+ if ignored?(path + File::SEPARATOR)
219
276
  Find.prune # Don't look any further into this directory.
220
277
  else
221
278
  yield(path)
@@ -231,7 +288,10 @@ module Listen
231
288
  # @param [String] path the path to insert in @paths.
232
289
  #
233
290
  def insert_path(path)
234
- @paths[File.dirname(path)][File.basename(path)] = File.directory?(path) ? 'Dir' : 'File'
291
+ meta_data = MetaData.new
292
+ meta_data.type = File.directory?(path) ? 'Dir' : 'File'
293
+ meta_data.mtime = mtime_of(path) unless meta_data.type == 'Dir' # mtimes of dirs are not used yet
294
+ @paths[File.dirname(path)][File.basename(path)] = meta_data
235
295
  end
236
296
 
237
297
  # Returns whether or not a path exists in the paths hash.
@@ -244,14 +304,14 @@ module Listen
244
304
  @paths[File.dirname(path)][File.basename(path)] != nil
245
305
  end
246
306
 
247
- # Converts an absolute path to a path that's relative to the base directory.
307
+ # Returns the modification time of a file based on the precision defined by the system
248
308
  #
249
- # @param [String] path the path to convert
309
+ # @param [String] file the file for which the mtime must be returned
250
310
  #
251
- # @return [String] the relative path
311
+ # @return [Fixnum, Float] the mtime of the file
252
312
  #
253
- def relative_to_base(path)
254
- path.sub(%r(^#{@directory}/?/), '')
313
+ def mtime_of(file)
314
+ File.mtime(file).send(HIGH_PRECISION_SUPPORTED ? :to_f : :to_i)
255
315
  end
256
316
  end
257
317
  end
@@ -1,3 +1,5 @@
1
+ require 'pathname'
2
+
1
3
  module Listen
2
4
  class Listener
3
5
  attr_reader :directory, :directory_record, :adapter
@@ -9,8 +11,8 @@ module Listen
9
11
  #
10
12
  # @param [String] directory the directory to listen to
11
13
  # @param [Hash] options the listen options
12
- # @option options [String] ignore a list of paths to ignore
13
- # @option options [Regexp] filter a list of regexps file filters
14
+ # @option options [Regexp] ignore a pattern for ignoring paths
15
+ # @option options [Regexp] filter a pattern for filtering paths
14
16
  # @option options [Float] latency the delay between checking for changes in seconds
15
17
  # @option options [Boolean] relative_paths whether or not to use relative-paths in the callback
16
18
  # @option options [Boolean] force_polling whether to force the polling adapter or not
@@ -23,8 +25,8 @@ module Listen
23
25
  #
24
26
  def initialize(directory, options = {}, &block)
25
27
  @block = block
26
- @directory = directory
27
- @directory_record = DirectoryRecord.new(directory)
28
+ @directory = Pathname.new(directory).realpath.to_s
29
+ @directory_record = DirectoryRecord.new(@directory)
28
30
  @use_relative_paths = DEFAULT_TO_RELATIVE_PATHS
29
31
 
30
32
  @use_relative_paths = options.delete(:relative_paths) if options[:relative_paths]
@@ -41,8 +43,8 @@ module Listen
41
43
  # @param [Boolean] blocking whether or not to block the current thread after starting
42
44
  #
43
45
  def start(blocking = true)
44
- t = Thread.new { @adapter = initialize_adapter }
45
- @directory_record.build
46
+ t = Thread.new { @directory_record.build }
47
+ @adapter = initialize_adapter
46
48
  t.join
47
49
  @adapter.start(blocking)
48
50
  end
@@ -80,18 +82,18 @@ module Listen
80
82
  !!@adapter && @adapter.paused == true
81
83
  end
82
84
 
83
- # Adds ignored paths to the listener.
85
+ # Adds ignoring patterns to the listener.
84
86
  #
85
87
  # @param (see Listen::DirectoryRecord#ignore)
86
88
  #
87
89
  # @return [Listen::Listener] the listener
88
90
  #
89
- def ignore(*paths)
90
- @directory_record.ignore(*paths)
91
+ def ignore(*regexps)
92
+ @directory_record.ignore(*regexps)
91
93
  self
92
94
  end
93
95
 
94
- # Adds file filters to the listener.
96
+ # Adds filtering patterns to the listener.
95
97
  #
96
98
  # @param (see Listen::DirectoryRecord#filter)
97
99
  #
@@ -117,7 +119,7 @@ module Listen
117
119
  self
118
120
  end
119
121
 
120
- # Defines whether the use of the polling adapter
122
+ # Sets whether the use of the polling adapter
121
123
  # should be forced or not.
122
124
  #
123
125
  # @example Forcing the use of the polling adapter
@@ -132,6 +134,21 @@ module Listen
132
134
  self
133
135
  end
134
136
 
137
+ # Sets whether the paths in the callback should be
138
+ # relative or absolute.
139
+ #
140
+ # @example Enabling relative paths in the callback
141
+ # relative_paths true
142
+ #
143
+ # @param [Boolean] value whether to enable relative paths in the callback or not
144
+ #
145
+ # @return [Listen::Listener] the listener
146
+ #
147
+ def relative_paths(value)
148
+ @use_relative_paths = value
149
+ self
150
+ end
151
+
135
152
  # Defines a custom polling fallback message of disable it.
136
153
  #
137
154
  # @example Disabling the polling fallback message
@@ -6,8 +6,8 @@ module Listen
6
6
  #
7
7
  # @param [String] directories the directories to listen to
8
8
  # @param [Hash] options the listen options
9
- # @option options [String] ignore a list of paths to ignore
10
- # @option options [Regexp] filter a list of regexps file filters
9
+ # @option options [Regexp] ignore a pattern for ignoring paths
10
+ # @option options [Regexp] filter a pattern for filtering paths
11
11
  # @option options [Float] latency the delay between checking for changes in seconds
12
12
  # @option options [Boolean] force_polling whether to force the polling adapter or not
13
13
  # @option options [String, Boolean] polling_fallback_message to change polling fallback message or remove it
@@ -22,8 +22,8 @@ module Listen
22
22
  directories = args
23
23
 
24
24
  @block = block
25
- @directories = directories
26
- @directories_records = directories.map { |d| DirectoryRecord.new(d) }
25
+ @directories = directories.map { |d| Pathname.new(d).realpath.to_s }
26
+ @directories_records = @directories.map { |d| DirectoryRecord.new(d) }
27
27
 
28
28
  ignore(*options.delete(:ignore)) if options[:ignore]
29
29
  filter(*options.delete(:filter)) if options[:filter]
@@ -38,8 +38,8 @@ module Listen
38
38
  # @param [Boolean] blocking whether or not to block the current thread after starting
39
39
  #
40
40
  def start(blocking = true)
41
- t = Thread.new { @adapter = initialize_adapter }
42
- @directories_records.each { |r| r.build }
41
+ t = Thread.new { @directories_records.each { |r| r.build } }
42
+ @adapter = initialize_adapter
43
43
  t.join
44
44
  @adapter.start(blocking)
45
45
  end
@@ -1,3 +1,3 @@
1
1
  module Listen
2
- VERSION = '0.4.1'
2
+ VERSION = '0.4.2'
3
3
  end
@@ -9,6 +9,17 @@ describe Listen::Adapters::Linux do
9
9
 
10
10
  it_should_behave_like 'a filesystem adapter'
11
11
  it_should_behave_like 'an adapter that call properly listener#on_change'
12
+
13
+ describe '#initialize' do
14
+ context 'when the inotify limit for watched files is not enough' do
15
+ before { INotify::Notifier.any_instance.should_receive(:watch).and_raise(Errno::ENOSPC) }
16
+
17
+ it 'fails gracefully' do
18
+ described_class.any_instance.should_receive(:abort).with(described_class::INOTIFY_LIMIT_MESSAGE)
19
+ described_class.new(File.dirname(__FILE__))
20
+ end
21
+ end
22
+ end
12
23
  else
13
24
  it "isn't usable on Linux with #{RbConfig::CONFIG['RUBY_INSTALL_NAME']}" do
14
25
  described_class.should_not be_usable