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

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