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.
- data/REVISION +1 -1
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/sass/engine.rb +8 -1
- data/lib/sass/exec.rb +2 -2
- data/lib/sass/plugin/compiler.rb +51 -43
- data/lib/sass/script/color.rb +4 -9
- data/lib/sass/script/funcall.rb +11 -2
- data/lib/sass/script/functions.rb +16 -30
- data/lib/sass/script/lexer.rb +7 -1
- data/lib/sass/script/list.rb +2 -2
- data/lib/sass/script/literal.rb +8 -0
- data/lib/sass/script/null.rb +34 -0
- data/lib/sass/script/operation.rb +4 -0
- data/lib/sass/script/parser.rb +4 -2
- data/lib/sass/scss/parser.rb +1 -5
- data/lib/sass/scss/rx.rb +1 -1
- data/lib/sass/tree/directive_node.rb +5 -0
- data/lib/sass/tree/media_node.rb +3 -0
- data/lib/sass/tree/prop_node.rb +7 -3
- data/lib/sass/tree/visitors/base.rb +1 -1
- data/lib/sass/tree/visitors/check_nesting.rb +21 -18
- data/lib/sass/tree/visitors/convert.rb +1 -0
- data/lib/sass/tree/visitors/perform.rb +3 -2
- data/lib/sass/tree/visitors/to_css.rb +1 -0
- data/lib/sass/util.rb +28 -0
- data/test/sass/conversion_test.rb +33 -0
- data/test/sass/engine_test.rb +118 -3
- data/test/sass/functions_test.rb +56 -24
- data/test/sass/script_test.rb +60 -4
- data/test/sass/scss/scss_test.rb +10 -0
- data/vendor/listen/CHANGELOG.md +19 -1
- data/vendor/listen/README.md +27 -12
- data/vendor/listen/lib/listen/adapter.rb +9 -1
- data/vendor/listen/lib/listen/adapters/linux.rb +18 -7
- data/vendor/listen/lib/listen/adapters/windows.rb +7 -8
- data/vendor/listen/lib/listen/directory_record.rb +108 -48
- data/vendor/listen/lib/listen/listener.rb +28 -11
- data/vendor/listen/lib/listen/multi_listener.rb +6 -6
- data/vendor/listen/lib/listen/version.rb +1 -1
- data/vendor/listen/spec/listen/adapters/linux_spec.rb +11 -0
- data/vendor/listen/spec/listen/directory_record_spec.rb +268 -41
- data/vendor/listen/spec/listen/listener_spec.rb +8 -4
- data/vendor/listen/spec/listen/multi_listener_spec.rb +9 -4
- data/vendor/listen/spec/support/adapter_helper.rb +178 -0
- data/vendor/listen/spec/support/directory_record_helper.rb +24 -4
- data/vendor/listen/spec/support/listeners_helper.rb +11 -0
- metadata +11 -11
- 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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
16
|
-
|
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
|
-
|
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
|
-
@
|
27
|
-
@
|
28
|
-
@
|
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
|
63
|
+
# Returns the ignoring patterns in the record
|
32
64
|
#
|
33
|
-
# @return [Array<
|
65
|
+
# @return [Array<Regexp>] the ignoring patterns
|
34
66
|
#
|
35
|
-
def
|
36
|
-
@
|
67
|
+
def ignoring_patterns
|
68
|
+
@ignoring_patterns.to_a
|
37
69
|
end
|
38
70
|
|
39
|
-
# Returns the
|
71
|
+
# Returns the filtering patterns used in the record to know
|
40
72
|
# which paths should be stored.
|
41
73
|
#
|
42
|
-
# @return [Array<
|
74
|
+
# @return [Array<Regexp>] the filtering patterns
|
43
75
|
#
|
44
|
-
def
|
45
|
-
@
|
76
|
+
def filtering_patterns
|
77
|
+
@filtering_patterns.to_a
|
46
78
|
end
|
47
79
|
|
48
|
-
# Adds
|
80
|
+
# Adds ignoring patterns to the record.
|
49
81
|
#
|
50
82
|
# @example Ignore some paths
|
51
|
-
# ignore
|
83
|
+
# ignore %r{^ignored/path/}, /man/
|
52
84
|
#
|
53
|
-
# @param [
|
85
|
+
# @param [Regexp] regexp a pattern for ignoring paths
|
54
86
|
#
|
55
|
-
def ignore(*
|
56
|
-
@
|
87
|
+
def ignore(*regexps)
|
88
|
+
@ignoring_patterns.merge(regexps)
|
57
89
|
end
|
58
90
|
|
59
|
-
# Adds
|
91
|
+
# Adds filtering patterns to the listener.
|
60
92
|
#
|
61
93
|
# @example Filter some files
|
62
94
|
# ignore /\.txt$/, /.*\.zip/
|
63
95
|
#
|
64
|
-
# @param [
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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 =
|
152
|
-
|
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
|
-
|
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]
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
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
|
-
|
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
|
-
#
|
307
|
+
# Returns the modification time of a file based on the precision defined by the system
|
248
308
|
#
|
249
|
-
# @param [String]
|
309
|
+
# @param [String] file the file for which the mtime must be returned
|
250
310
|
#
|
251
|
-
# @return [
|
311
|
+
# @return [Fixnum, Float] the mtime of the file
|
252
312
|
#
|
253
|
-
def
|
254
|
-
|
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 [
|
13
|
-
# @option options [Regexp] filter a
|
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 { @
|
45
|
-
@
|
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
|
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(*
|
90
|
-
@directory_record.ignore(*
|
91
|
+
def ignore(*regexps)
|
92
|
+
@directory_record.ignore(*regexps)
|
91
93
|
self
|
92
94
|
end
|
93
95
|
|
94
|
-
# Adds
|
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
|
-
#
|
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 [
|
10
|
-
# @option options [Regexp] filter a
|
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 { @
|
42
|
-
@
|
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
|
@@ -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
|