sass 3.2.0.alpha.101 → 3.2.0.alpha.102

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/REVISION +1 -1
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/lib/sass/plugin/compiler.rb +25 -32
  5. data/lib/sass/plugin/listener.rb +59 -0
  6. data/lib/sass/script/lexer.rb +1 -1
  7. data/lib/sass/script/list.rb +1 -0
  8. data/test/sass/conversion_test.rb +10 -0
  9. data/test/sass/scss/css_test.rb +9 -0
  10. data/vendor/listen/CHANGELOG.md +72 -0
  11. data/vendor/listen/Gemfile +35 -0
  12. data/vendor/listen/Guardfile +8 -0
  13. data/vendor/listen/LICENSE +20 -0
  14. data/vendor/listen/README.md +297 -0
  15. data/vendor/listen/Rakefile +47 -0
  16. data/vendor/listen/Vagrantfile +96 -0
  17. data/vendor/listen/lib/listen.rb +38 -0
  18. data/vendor/listen/lib/listen/adapter.rb +159 -0
  19. data/vendor/listen/lib/listen/adapters/darwin.rb +84 -0
  20. data/vendor/listen/lib/listen/adapters/linux.rb +99 -0
  21. data/vendor/listen/lib/listen/adapters/polling.rb +66 -0
  22. data/vendor/listen/lib/listen/adapters/windows.rb +82 -0
  23. data/vendor/listen/lib/listen/directory_record.rb +257 -0
  24. data/vendor/listen/lib/listen/listener.rb +186 -0
  25. data/vendor/listen/lib/listen/multi_listener.rb +121 -0
  26. data/vendor/listen/lib/listen/turnstile.rb +28 -0
  27. data/vendor/listen/lib/listen/version.rb +3 -0
  28. data/vendor/listen/listen.gemspec +26 -0
  29. data/vendor/listen/spec/listen/adapter_spec.rb +142 -0
  30. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +31 -0
  31. data/vendor/listen/spec/listen/adapters/linux_spec.rb +30 -0
  32. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  33. data/vendor/listen/spec/listen/adapters/windows_spec.rb +24 -0
  34. data/vendor/listen/spec/listen/directory_record_spec.rb +807 -0
  35. data/vendor/listen/spec/listen/listener_spec.rb +151 -0
  36. data/vendor/listen/spec/listen/multi_listener_spec.rb +151 -0
  37. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  38. data/vendor/listen/spec/listen_spec.rb +73 -0
  39. data/vendor/listen/spec/spec_helper.rb +16 -0
  40. data/vendor/listen/spec/support/adapter_helper.rb +538 -0
  41. data/vendor/listen/spec/support/directory_record_helper.rb +35 -0
  42. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  43. data/vendor/listen/spec/support/listeners_helper.rb +133 -0
  44. data/vendor/listen/spec/support/platform_helper.rb +11 -0
  45. metadata +41 -5
@@ -0,0 +1,66 @@
1
+ module Listen
2
+ module Adapters
3
+
4
+ # The default delay between checking for changes.
5
+ DEFAULT_POLLING_LATENCY = 1.0
6
+
7
+ # Polling Adapter that works cross-platform and
8
+ # has no dependencies. This is the adapter that
9
+ # uses the most CPU processing power and has higher
10
+ # file IO that the other implementations.
11
+ #
12
+ class Polling < Adapter
13
+
14
+ # Initialize the Adapter. See {Listen::Adapter#initialize} for more info.
15
+ #
16
+ def initialize(directories, options = {}, &callback)
17
+ @latency ||= DEFAULT_POLLING_LATENCY
18
+ super
19
+ end
20
+
21
+ # Start the adapter.
22
+ #
23
+ # @param [Boolean] blocking whether or not to block the current thread after starting
24
+ #
25
+ def start(blocking = true)
26
+ @mutex.synchronize do
27
+ return if @stop == false
28
+ super
29
+ end
30
+
31
+ @poll_thread = Thread.new { poll }
32
+ @poll_thread.join if blocking
33
+ end
34
+
35
+ # Stop the adapter.
36
+ #
37
+ def stop
38
+ @mutex.synchronize do
39
+ return if @stop == true
40
+ super
41
+ end
42
+
43
+ @poll_thread.join
44
+ end
45
+
46
+ private
47
+
48
+ # Poll listener directory for file system changes.
49
+ #
50
+ def poll
51
+ until @stop
52
+ sleep(0.1) && next if @paused
53
+
54
+ start = Time.now.to_f
55
+ @callback.call(@directories.dup, :recursive => true)
56
+ @turnstile.signal
57
+ nap_time = @latency - (Time.now.to_f - start)
58
+ sleep(nap_time) if nap_time > 0
59
+ end
60
+ rescue Interrupt
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,82 @@
1
+ require 'set'
2
+
3
+ module Listen
4
+ module Adapters
5
+
6
+ # Adapter implementation for Windows `fchange`.
7
+ #
8
+ class Windows < Adapter
9
+
10
+ # Initializes the Adapter. See {Listen::Adapter#initialize} for more info.
11
+ #
12
+ def initialize(directories, options = {}, &callback)
13
+ super
14
+ @worker = init_worker
15
+ end
16
+
17
+ # Starts the adapter.
18
+ #
19
+ # @param [Boolean] blocking whether or not to block the current thread after starting
20
+ #
21
+ def start(blocking = true)
22
+ @mutex.synchronize do
23
+ return if @stop == false
24
+ super
25
+ end
26
+
27
+ @worker_thread = Thread.new { @worker.run }
28
+ @poll_thread = Thread.new { poll_changed_dirs(true) }
29
+ @poll_thread.join if blocking
30
+ end
31
+
32
+ # Stops the adapter.
33
+ #
34
+ def stop
35
+ @mutex.synchronize do
36
+ return if @stop == true
37
+ super
38
+ end
39
+
40
+ @worker.stop
41
+ Thread.kill(@worker_thread) if @worker_thread
42
+ @poll_thread.join
43
+ end
44
+
45
+ # Checks if the adapter is usable on the current OS.
46
+ #
47
+ # @return [Boolean] whether usable or not
48
+ #
49
+ def self.usable?
50
+ return false unless RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
51
+
52
+ require 'rb-fchange'
53
+ true
54
+ rescue LoadError
55
+ false
56
+ end
57
+
58
+ private
59
+
60
+ # Initializes a FChange worker and adds a watcher for
61
+ # each directory passed to the adapter.
62
+ #
63
+ # @return [FChange::Notifier] initialized worker
64
+ #
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)
72
+ end
73
+ end
74
+ worker.add_watcher(watcher)
75
+ end
76
+ worker
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,257 @@
1
+ require 'set'
2
+ require 'find'
3
+ require 'pathname'
4
+ require 'digest/sha1'
5
+
6
+ module Listen
7
+
8
+ # The directory record stores information about
9
+ # a directory and keeps track of changes to
10
+ # the structure of its childs.
11
+ #
12
+ class DirectoryRecord
13
+ attr_reader :directory, :paths, :sha1_checksums
14
+
15
+ # Default paths' beginnings that doesn't get stored in the record
16
+ DEFAULT_IGNORED_PATHS = %w[.bundle .git .DS_Store log tmp vendor]
17
+
18
+ # Initializes a directory record.
19
+ #
20
+ # @option [String] directory the directory to keep track of
21
+ #
22
+ def initialize(directory)
23
+ @directory = directory
24
+ raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(@directory)
25
+
26
+ @ignored_paths = Set.new(DEFAULT_IGNORED_PATHS)
27
+ @filters = Set.new
28
+ @sha1_checksums = Hash.new
29
+ end
30
+
31
+ # Returns the ignored paths in the record
32
+ #
33
+ # @return [Array<String>] the ignored paths
34
+ #
35
+ def ignored_paths
36
+ @ignored_paths.to_a
37
+ end
38
+
39
+ # Returns the filters used in the record to know
40
+ # which paths should be stored.
41
+ #
42
+ # @return [Array<String>] the used filters
43
+ #
44
+ def filters
45
+ @filters.to_a
46
+ end
47
+
48
+ # Adds ignored path to the record.
49
+ #
50
+ # @example Ignore some paths
51
+ # ignore ".git", ".svn"
52
+ #
53
+ # @param [String, Array<String>] paths a path or a list of paths to ignore
54
+ #
55
+ def ignore(*paths)
56
+ @ignored_paths.merge(paths)
57
+ end
58
+
59
+ # Adds file filters to the listener.
60
+ #
61
+ # @example Filter some files
62
+ # ignore /\.txt$/, /.*\.zip/
63
+ #
64
+ # @param [Array<Regexp>] regexps a list of regexps file filters
65
+ #
66
+ # @return [Listen::Listener] the listener itself
67
+ #
68
+ def filter(*regexps)
69
+ @filters.merge(regexps)
70
+ end
71
+
72
+ # Returns whether a path should be ignored or not.
73
+ #
74
+ # @param [String] path the path to test.
75
+ #
76
+ # @return [Boolean]
77
+ #
78
+ def ignored?(path)
79
+ @ignored_paths.any? { |ignored_path| path =~ /#{ignored_path}$/ }
80
+ end
81
+
82
+ # Returns whether a path should be filtered or not.
83
+ #
84
+ # @param [String] path the path to test.
85
+ #
86
+ # @return [Boolean]
87
+ #
88
+ def filtered?(path)
89
+ @filters.empty? || @filters.any? { |filter| path =~ filter }
90
+ end
91
+
92
+ # Finds the paths that should be stored and adds them
93
+ # to the paths' hash.
94
+ #
95
+ def build
96
+ @paths = Hash.new { |h, k| h[k] = Hash.new }
97
+ important_paths { |path| insert_path(path) }
98
+ @updated_at = Time.now.to_i
99
+ end
100
+
101
+ # Detects changes in the passed directories, updates
102
+ # the record with the new changes and returns the changes
103
+ #
104
+ # @param [Array] directories the list of directories scan for changes
105
+ # @param [Hash] options
106
+ # @option options [Boolean] recursive scan all sub-directories recursively
107
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
108
+ #
109
+ # @return [Hash<Array>] the changes
110
+ #
111
+ def fetch_changes(directories, options = {})
112
+ @changes = { :modified => [], :added => [], :removed => [] }
113
+ directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
114
+ directories.each do |directory|
115
+ next unless directory[@directory] # Path is or inside directory
116
+ detect_modifications_and_removals(directory, options)
117
+ detect_additions(directory, options)
118
+ end
119
+ @updated_at = Time.now.to_i
120
+ @changes
121
+ end
122
+
123
+ private
124
+
125
+ # Detects modifications and removals recursively in a directory.
126
+ #
127
+ # @note Modifications detection begins by checking the modification time (mtime)
128
+ # of files and then by checking content changes (using SHA1-checksum)
129
+ # when the mtime of files is not changed.
130
+ #
131
+ # @param [String] directory the path to analyze
132
+ # @param [Hash] options
133
+ # @option options [Boolean] recursive scan all sub-directories recursively
134
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
135
+ #
136
+ def detect_modifications_and_removals(directory, options = {})
137
+ @paths[directory].each do |basename, type|
138
+ path = File.join(directory, basename)
139
+
140
+ case type
141
+ when 'Dir'
142
+ if File.directory?(path)
143
+ detect_modifications_and_removals(path, options) if options[:recursive]
144
+ else
145
+ detect_modifications_and_removals(path, { :recursive => true }.merge(options))
146
+ @paths[directory].delete(basename)
147
+ @paths.delete("#{directory}/#{basename}")
148
+ end
149
+ when 'File'
150
+ 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))
153
+ @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
154
+ end
155
+ else
156
+ @paths[directory].delete(basename)
157
+ @sha1_checksums.delete(path)
158
+ @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ # Detects additions in a directory.
165
+ #
166
+ # @param [String] directory the path to analyze
167
+ # @param [Hash] options
168
+ # @option options [Boolean] recursive scan all sub-directories recursively
169
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
170
+ #
171
+ def detect_additions(directory, options = {})
172
+ # Don't process removed directories
173
+ return unless File.exist?(directory)
174
+
175
+ Find.find(directory) do |path|
176
+ next if path == @directory
177
+
178
+ if File.directory?(path)
179
+ if ignored?(path) || (directory != path && (!options[:recursive] && existing_path?(path)))
180
+ Find.prune # Don't look any further into this directory.
181
+ else
182
+ insert_path(path)
183
+ end
184
+ elsif !ignored?(path) && filtered?(path) && !existing_path?(path)
185
+ if File.file?(path)
186
+ @changes[:added] << (options[:relative_paths] ? relative_to_base(path) : path)
187
+ end
188
+ insert_path(path)
189
+ end
190
+ end
191
+ end
192
+
193
+ # Returns whether or not a file's content has been modified by
194
+ # comparing the SHA1-checksum to a stored one.
195
+ #
196
+ # @param [String] path the file path
197
+ #
198
+ def content_modified?(path)
199
+ 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
206
+ end
207
+
208
+ # Traverses the base directory looking for paths that should
209
+ # be stored; thus paths that are filters or not ignored.
210
+ #
211
+ # @yield [path] an important path
212
+ #
213
+ def important_paths
214
+ Find.find(@directory) do |path|
215
+ next if path == @directory
216
+
217
+ if File.directory?(path)
218
+ if ignored?(path)
219
+ Find.prune # Don't look any further into this directory.
220
+ else
221
+ yield(path)
222
+ end
223
+ elsif !ignored?(path) && filtered?(path)
224
+ yield(path)
225
+ end
226
+ end
227
+ end
228
+
229
+ # Inserts a path with its type (Dir or File) in paths hash.
230
+ #
231
+ # @param [String] path the path to insert in @paths.
232
+ #
233
+ def insert_path(path)
234
+ @paths[File.dirname(path)][File.basename(path)] = File.directory?(path) ? 'Dir' : 'File'
235
+ end
236
+
237
+ # Returns whether or not a path exists in the paths hash.
238
+ #
239
+ # @param [String] path the path to check
240
+ #
241
+ # @return [Boolean]
242
+ #
243
+ def existing_path?(path)
244
+ @paths[File.dirname(path)][File.basename(path)] != nil
245
+ end
246
+
247
+ # Converts an absolute path to a path that's relative to the base directory.
248
+ #
249
+ # @param [String] path the path to convert
250
+ #
251
+ # @return [String] the relative path
252
+ #
253
+ def relative_to_base(path)
254
+ path.sub(%r(^#{@directory}/?/), '')
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,186 @@
1
+ module Listen
2
+ class Listener
3
+ attr_reader :directory, :directory_record, :adapter
4
+
5
+ # The default value for using relative paths in the callback.
6
+ DEFAULT_TO_RELATIVE_PATHS = false
7
+
8
+ # Initializes the directory listener.
9
+ #
10
+ # @param [String] directory the directory to listen to
11
+ # @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 [Float] latency the delay between checking for changes in seconds
15
+ # @option options [Boolean] relative_paths whether or not to use relative-paths in the callback
16
+ # @option options [Boolean] force_polling whether to force the polling adapter or not
17
+ # @option options [String, Boolean] polling_fallback_message to change polling fallback message or remove it
18
+ #
19
+ # @yield [modified, added, removed] the changed files
20
+ # @yieldparam [Array<String>] modified the list of modified files
21
+ # @yieldparam [Array<String>] added the list of added files
22
+ # @yieldparam [Array<String>] removed the list of removed files
23
+ #
24
+ def initialize(directory, options = {}, &block)
25
+ @block = block
26
+ @directory = directory
27
+ @directory_record = DirectoryRecord.new(directory)
28
+ @use_relative_paths = DEFAULT_TO_RELATIVE_PATHS
29
+
30
+ @use_relative_paths = options.delete(:relative_paths) if options[:relative_paths]
31
+ @directory_record.ignore(*options.delete(:ignore)) if options[:ignore]
32
+ @directory_record.filter(*options.delete(:filter)) if options[:filter]
33
+
34
+ @adapter_options = options
35
+ end
36
+
37
+ # Starts the listener by initializing the adapter and building
38
+ # the directory record concurrently, then it starts the adapter to watch
39
+ # for changes.
40
+ #
41
+ # @param [Boolean] blocking whether or not to block the current thread after starting
42
+ #
43
+ def start(blocking = true)
44
+ t = Thread.new { @adapter = initialize_adapter }
45
+ @directory_record.build
46
+ t.join
47
+ @adapter.start(blocking)
48
+ end
49
+
50
+ # Stops the listener.
51
+ #
52
+ def stop
53
+ @adapter.stop
54
+ end
55
+
56
+ # Pauses the listener.
57
+ #
58
+ # @return [Listen::Listener] the listener
59
+ #
60
+ def pause
61
+ @adapter.paused = true
62
+ self
63
+ end
64
+
65
+ # Unpauses the listener.
66
+ #
67
+ # @return [Listen::Listener] the listener
68
+ #
69
+ def unpause
70
+ @directory_record.build
71
+ @adapter.paused = false
72
+ self
73
+ end
74
+
75
+ # Returns whether the listener is paused or not.
76
+ #
77
+ # @return [Boolean] adapter paused status
78
+ #
79
+ def paused?
80
+ !!@adapter && @adapter.paused == true
81
+ end
82
+
83
+ # Adds ignored paths to the listener.
84
+ #
85
+ # @param (see Listen::DirectoryRecord#ignore)
86
+ #
87
+ # @return [Listen::Listener] the listener
88
+ #
89
+ def ignore(*paths)
90
+ @directory_record.ignore(*paths)
91
+ self
92
+ end
93
+
94
+ # Adds file filters to the listener.
95
+ #
96
+ # @param (see Listen::DirectoryRecord#filter)
97
+ #
98
+ # @return [Listen::Listener] the listener
99
+ #
100
+ def filter(*regexps)
101
+ @directory_record.filter(*regexps)
102
+ self
103
+ end
104
+
105
+ # Sets the latency for the adapter. This is a helper method
106
+ # to simplify changing the latency directly from the listener.
107
+ #
108
+ # @example Wait 0.5 seconds each time before checking changes
109
+ # latency 0.5
110
+ #
111
+ # @param [Float] seconds the amount of delay, in seconds
112
+ #
113
+ # @return [Listen::Listener] the listener
114
+ #
115
+ def latency(seconds)
116
+ @adapter_options[:latency] = seconds
117
+ self
118
+ end
119
+
120
+ # Defines whether the use of the polling adapter
121
+ # should be forced or not.
122
+ #
123
+ # @example Forcing the use of the polling adapter
124
+ # force_polling true
125
+ #
126
+ # @param [Boolean] value whether to force the polling adapter or not
127
+ #
128
+ # @return [Listen::Listener] the listener
129
+ #
130
+ def force_polling(value)
131
+ @adapter_options[:force_polling] = value
132
+ self
133
+ end
134
+
135
+ # Defines a custom polling fallback message of disable it.
136
+ #
137
+ # @example Disabling the polling fallback message
138
+ # polling_fallback_message false
139
+ #
140
+ # @param [String, Boolean] value to change polling fallback message or remove it
141
+ #
142
+ # @return [Listen::Listener] the listener
143
+ #
144
+ def polling_fallback_message(value)
145
+ @adapter_options[:polling_fallback_message] = value
146
+ self
147
+ end
148
+
149
+ # Sets the callback that gets called on changes.
150
+ #
151
+ # @example Assign a callback to be called on changes
152
+ # callback = lambda { |modified, added, removed| ... }
153
+ # change &callback
154
+ #
155
+ # @param [Proc] block the callback proc
156
+ #
157
+ # @return [Listen::Listener] the listener
158
+ #
159
+ def change(&block) # modified, added, removed
160
+ @block = block
161
+ self
162
+ end
163
+
164
+ # Runs the callback passing it the changes if there are any.
165
+ #
166
+ # @param (see Listen::DirectoryRecord#fetch_changes)
167
+ #
168
+ def on_change(directories, options = {})
169
+ changes = @directory_record.fetch_changes(directories, options.merge(
170
+ :relative_paths => @use_relative_paths
171
+ ))
172
+ unless changes.values.all? { |paths| paths.empty? }
173
+ @block.call(changes[:modified],changes[:added],changes[:removed])
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ # Initializes an adapter passing it the callback and adapters' options.
180
+ #
181
+ def initialize_adapter
182
+ callback = lambda { |changed_dirs, options| self.on_change(changed_dirs, options) }
183
+ Adapter.select_and_initialize(@directory, @adapter_options, &callback)
184
+ end
185
+ end
186
+ end