sass-listen 3.0.7

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.
@@ -0,0 +1,137 @@
1
+ require 'listen/options'
2
+ require 'listen/record'
3
+ require 'listen/change'
4
+
5
+ module Listen
6
+ module Adapter
7
+ class Base
8
+ attr_reader :options
9
+
10
+ # TODO: only used by tests
11
+ DEFAULTS = {}
12
+
13
+ attr_reader :config
14
+
15
+ def initialize(config)
16
+ @started = false
17
+ @config = config
18
+
19
+ @configured = nil
20
+
21
+ fail 'No directories to watch!' if config.directories.empty?
22
+
23
+ defaults = self.class.const_get('DEFAULTS')
24
+ @options = Listen::Options.new(config.adapter_options, defaults)
25
+ rescue
26
+ _log_exception 'adapter config failed: %s:%s called from: %s', caller
27
+ raise
28
+ end
29
+
30
+ # TODO: it's a separate method as a temporary workaround for tests
31
+ def configure
32
+ if @configured
33
+ _log(:warn, 'Adapter already configured!')
34
+ return
35
+ end
36
+
37
+ @configured = true
38
+
39
+ @callbacks ||= {}
40
+ config.directories.each do |dir|
41
+ callback = @callbacks[dir] || lambda do |event|
42
+ _process_event(dir, event)
43
+ end
44
+ @callbacks[dir] = callback
45
+ _configure(dir, &callback)
46
+ end
47
+
48
+ @snapshots ||= {}
49
+ # TODO: separate config per directory (some day maybe)
50
+ change_config = Change::Config.new(config.queue, config.silencer)
51
+ config.directories.each do |dir|
52
+ record = Record.new(dir)
53
+ snapshot = Change.new(change_config, record)
54
+ @snapshots[dir] = snapshot
55
+ end
56
+ end
57
+
58
+ def started?
59
+ @started
60
+ end
61
+
62
+ def start
63
+ configure
64
+
65
+ if started?
66
+ _log(:warn, 'Adapter already started!')
67
+ return
68
+ end
69
+
70
+ @started = true
71
+
72
+ calling_stack = caller.dup
73
+ Listen::Internals::ThreadPool.add do
74
+ begin
75
+ @snapshots.values.each do |snapshot|
76
+ _timed('Record.build()') { snapshot.record.build }
77
+ end
78
+ _run
79
+ rescue
80
+ msg = 'run() in thread failed: %s:\n'\
81
+ ' %s\n\ncalled from:\n %s'
82
+ _log_exception(msg, calling_stack)
83
+ raise # for unit tests mostly
84
+ end
85
+ end
86
+ end
87
+
88
+ def stop
89
+ _stop
90
+ end
91
+
92
+ def self.usable?
93
+ const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os']
94
+ end
95
+
96
+ private
97
+
98
+ def _stop
99
+ end
100
+
101
+ def _timed(title)
102
+ start = Time.now.to_f
103
+ yield
104
+ diff = Time.now.to_f - start
105
+ Listen::Logger.info format('%s: %.05f seconds', title, diff)
106
+ rescue
107
+ Listen::Logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}"
108
+ raise
109
+ end
110
+
111
+ # TODO: allow backend adapters to pass specific invalidation objects
112
+ # e.g. Darwin -> DirRescan, INotify -> MoveScan, etc.
113
+ def _queue_change(type, dir, rel_path, options)
114
+ @snapshots[dir].invalidate(type, rel_path, options)
115
+ end
116
+
117
+ def _log(*args, &block)
118
+ self.class.send(:_log, *args, &block)
119
+ end
120
+
121
+ def _log_exception(msg, caller_stack)
122
+ formatted = format(
123
+ msg,
124
+ $ERROR_INFO,
125
+ $ERROR_POSITION * "\n",
126
+ caller_stack * "\n"
127
+ )
128
+
129
+ _log(:error, formatted)
130
+ end
131
+
132
+ def self._log(*args, &block)
133
+ Listen::Logger.send(*args, &block)
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,106 @@
1
+ # Listener implementation for BSD's `kqueue`.
2
+ # @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
3
+ # @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
4
+ #
5
+ module Listen
6
+ module Adapter
7
+ class BSD < Base
8
+ OS_REGEXP = /bsd|dragonfly/i
9
+
10
+ DEFAULTS = {
11
+ events: [
12
+ :delete,
13
+ :write,
14
+ :extend,
15
+ :attrib,
16
+ :rename
17
+ # :link, :revoke
18
+ ]
19
+ }
20
+
21
+ BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
22
+ Please add the following to your Gemfile to avoid polling for changes:
23
+ require 'rbconfig'
24
+ if RbConfig::CONFIG['target_os'] =~ /#{OS_REGEXP}/
25
+ gem 'rb-kqueue', '>= 0.2'
26
+ end
27
+ EOS
28
+
29
+ def self.usable?
30
+ return false unless super
31
+ require 'rb-kqueue'
32
+ require 'find'
33
+ true
34
+ rescue LoadError
35
+ Kernel.warn BUNDLER_DECLARE_GEM
36
+ false
37
+ end
38
+
39
+ private
40
+
41
+ def _configure(directory, &_callback)
42
+ @worker ||= KQueue::Queue.new
43
+ @callback = _callback
44
+ # use Record to make a snapshot of dir, so we
45
+ # can detect new files
46
+ _find(directory.to_s) { |path| _watch_file(path, @worker) }
47
+ end
48
+
49
+ def _run
50
+ @worker.run
51
+ end
52
+
53
+ def _process_event(dir, event)
54
+ full_path = _event_path(event)
55
+ if full_path.directory?
56
+ # Force dir content tracking to kick in, or we won't have
57
+ # names of added files
58
+ _queue_change(:dir, dir, '.', recursive: true)
59
+ elsif full_path.exist?
60
+ path = full_path.relative_path_from(dir)
61
+ _queue_change(:file, dir, path.to_s, change: _change(event.flags))
62
+ end
63
+
64
+ # If it is a directory, and it has a write flag, it means a
65
+ # file has been added so find out which and deal with it.
66
+ # No need to check for removed files, kqueue will forget them
67
+ # when the vfs does.
68
+ _watch_for_new_file(event) if full_path.directory?
69
+ end
70
+
71
+ def _change(event_flags)
72
+ { modified: [:attrib, :extend],
73
+ added: [:write],
74
+ removed: [:rename, :delete]
75
+ }.each do |change, flags|
76
+ return change unless (flags & event_flags).empty?
77
+ end
78
+ nil
79
+ end
80
+
81
+ def _event_path(event)
82
+ Pathname.new(event.watcher.path)
83
+ end
84
+
85
+ def _watch_for_new_file(event)
86
+ queue = event.watcher.queue
87
+ _find(_event_path(event).to_s) do |file_path|
88
+ unless queue.watchers.detect { |_, v| v.path == file_path.to_s }
89
+ _watch_file(file_path, queue)
90
+ end
91
+ end
92
+ end
93
+
94
+ def _watch_file(path, queue)
95
+ queue.watch_file(path, *options.events, &@callback)
96
+ rescue Errno::ENOENT => e
97
+ _log :warn, "kqueue: watch file failed: #{e.message}"
98
+ end
99
+
100
+ # Quick rubocop workaround
101
+ def _find(*paths, &block)
102
+ Find.send(:find, *paths, &block)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,26 @@
1
+ require 'pathname'
2
+
3
+ module Listen
4
+ module Adapter
5
+ class Config
6
+ attr_reader :directories
7
+ attr_reader :silencer
8
+ attr_reader :queue
9
+ attr_reader :adapter_options
10
+
11
+ def initialize(directories, queue, silencer, adapter_options)
12
+ # Default to current directory if no directories are supplied
13
+ directories = [Dir.pwd] if directories.to_a.empty?
14
+
15
+ # TODO: fix (flatten, array, compact?)
16
+ @directories = directories.map do |directory|
17
+ Pathname.new(directory.to_s).realpath
18
+ end
19
+
20
+ @silencer = silencer
21
+ @queue = queue
22
+ @adapter_options = adapter_options
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ require 'thread'
2
+ require 'listen/internals/thread_pool'
3
+
4
+ module Listen
5
+ module Adapter
6
+ # Adapter implementation for Mac OS X `FSEvents`.
7
+ #
8
+ class Darwin < Base
9
+ OS_REGEXP = /darwin(1.+)?$/i
10
+
11
+ # The default delay between checking for changes.
12
+ DEFAULTS = { latency: 0.1 }
13
+
14
+ private
15
+
16
+ # NOTE: each directory gets a DIFFERENT callback!
17
+ def _configure(dir, &callback)
18
+ require 'rb-fsevent'
19
+
20
+ opts = { latency: options.latency }
21
+
22
+ @workers ||= ::Queue.new
23
+ @workers << FSEvent.new.tap do |worker|
24
+ _log :debug, "fsevent: watching: #{dir.to_s.inspect}"
25
+ worker.watch(dir.to_s, opts, &callback)
26
+ end
27
+ end
28
+
29
+ def _run
30
+ first = @workers.pop
31
+
32
+ # NOTE: _run is called within a thread, so run every other
33
+ # worker in it's own thread
34
+ _run_workers_in_background(_to_array(@workers))
35
+ _run_worker(first)
36
+ end
37
+
38
+ def _process_event(dir, event)
39
+ _log :debug, "fsevent: processing event: #{event.inspect}"
40
+ event.each do |path|
41
+ new_path = Pathname.new(path.sub(/\/$/, ''))
42
+ _log :debug, "fsevent: #{new_path}"
43
+ # TODO: does this preserve symlinks?
44
+ rel_path = new_path.relative_path_from(dir).to_s
45
+ _queue_change(:dir, dir, rel_path, recursive: true)
46
+ end
47
+ end
48
+
49
+ def _run_worker(worker)
50
+ _log :debug, "fsevent: running worker: #{worker.inspect}"
51
+ worker.run
52
+ rescue
53
+ _log_exception 'fsevent: running worker failed: %s:%s called from: %s', caller
54
+ end
55
+
56
+ def _run_workers_in_background(workers)
57
+ workers.each do |worker|
58
+ # NOTE: while passing local variables to the block below is not
59
+ # thread safe, using 'worker' from the enumerator above is ok
60
+ Listen::Internals::ThreadPool.add { _run_worker(worker) }
61
+ end
62
+ end
63
+
64
+ def _to_array(queue)
65
+ workers = []
66
+ workers << queue.pop until queue.empty?
67
+ workers
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,106 @@
1
+ module Listen
2
+ module Adapter
3
+ # @see https://github.com/nex3/rb-inotify
4
+ class Linux < Base
5
+ OS_REGEXP = /linux/i
6
+
7
+ DEFAULTS = {
8
+ events: [
9
+ :recursive,
10
+ :attrib,
11
+ :create,
12
+ :delete,
13
+ :move,
14
+ :close_write
15
+ ],
16
+ wait_for_delay: 0.1
17
+ }
18
+
19
+ private
20
+
21
+ WIKI_URL = 'https://github.com/guard/listen'\
22
+ '/wiki/Increasing-the-amount-of-inotify-watchers'
23
+
24
+ INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
25
+ FATAL: Listen error: unable to monitor directories for changes.
26
+ Visit #{WIKI_URL} for info on how to fix this.
27
+ EOS
28
+
29
+ def _configure(directory, &callback)
30
+ require 'rb-inotify'
31
+ @worker ||= ::INotify::Notifier.new
32
+ @worker.watch(directory.to_s, *options.events, &callback)
33
+ rescue Errno::ENOSPC
34
+ abort(INOTIFY_LIMIT_MESSAGE)
35
+ end
36
+
37
+ def _run
38
+ @worker.run
39
+ end
40
+
41
+ def _process_event(dir, event)
42
+ # NOTE: avoid using event.absolute_name since new API
43
+ # will need to have a custom recursion implemented
44
+ # to properly match events to configured directories
45
+ path = Pathname.new(event.watcher.path) + event.name
46
+ rel_path = path.relative_path_from(dir).to_s
47
+
48
+ _log(:debug) { "inotify: #{rel_path} (#{event.flags.inspect})" }
49
+
50
+ if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
51
+ if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
52
+ rel_path = path.dirname.relative_path_from(dir).to_s
53
+ _queue_change(:dir, dir, rel_path, {})
54
+ else
55
+ _queue_change(:dir, dir, rel_path, {})
56
+ end
57
+ return
58
+ end
59
+
60
+ return if _skip_event?(event)
61
+
62
+ cookie_params = event.cookie.zero? ? {} : { cookie: event.cookie }
63
+
64
+ # Note: don't pass options to force rescanning the directory, so we can
65
+ # detect moving/deleting a whole tree
66
+ if _dir_event?(event)
67
+ _queue_change(:dir, dir, rel_path, cookie_params)
68
+ return
69
+ end
70
+
71
+ params = cookie_params.merge(change: _change(event.flags))
72
+
73
+ _queue_change(:file, dir, rel_path, params)
74
+ end
75
+
76
+ def _skip_event?(event)
77
+ # Event on root directory
78
+ return true if event.name == ''
79
+ # INotify reports changes to files inside directories as events
80
+ # on the directories themselves too.
81
+ #
82
+ # @see http://linux.die.net/man/7/inotify
83
+ _dir_event?(event) && (event.flags & [:close, :modify]).any?
84
+ end
85
+
86
+ def _change(event_flags)
87
+ { modified: [:attrib, :close_write],
88
+ moved_to: [:moved_to],
89
+ moved_from: [:moved_from],
90
+ added: [:create],
91
+ removed: [:delete] }.each do |change, flags|
92
+ return change unless (flags & event_flags).empty?
93
+ end
94
+ nil
95
+ end
96
+
97
+ def _dir_event?(event)
98
+ event.flags.include?(:isdir)
99
+ end
100
+
101
+ def _stop
102
+ @worker.close
103
+ end
104
+ end
105
+ end
106
+ end