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

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 (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