sass-listen 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ module Listen
2
+ module Adapter
3
+ # Polling Adapter that works cross-platform and
4
+ # has no dependencies. This is the adapter that
5
+ # uses the most CPU processing power and has higher
6
+ # file IO than the other implementations.
7
+ #
8
+ class Polling < Base
9
+ OS_REGEXP = // # match every OS
10
+
11
+ DEFAULTS = { latency: 1.0, wait_for_delay: 0.05 }
12
+
13
+ private
14
+
15
+ def _configure(_, &callback)
16
+ @polling_callbacks ||= []
17
+ @polling_callbacks << callback
18
+ end
19
+
20
+ def _run
21
+ loop do
22
+ start = Time.now.to_f
23
+ @polling_callbacks.each do |callback|
24
+ callback.call(nil)
25
+ nap_time = options.latency - (Time.now.to_f - start)
26
+ # TODO: warn if nap_time is negative (polling too slow)
27
+ sleep(nap_time) if nap_time > 0
28
+ end
29
+ end
30
+ end
31
+
32
+ def _process_event(dir, _)
33
+ _queue_change(:dir, dir, '.', recursive: true)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,99 @@
1
+ module Listen
2
+ module Adapter
3
+ # Adapter implementation for Windows `wdm`.
4
+ #
5
+ class Windows < Base
6
+ OS_REGEXP = /mswin|mingw|cygwin/i
7
+
8
+ BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
9
+ Please add the following to your Gemfile to avoid polling for changes:
10
+ gem 'wdm', '>= 0.1.0' if Gem.win_platform?
11
+ EOS
12
+
13
+ def self.usable?
14
+ return false unless super
15
+ require 'wdm'
16
+ true
17
+ rescue LoadError
18
+ _log :debug, format('wdm - load failed: %s:%s', $ERROR_INFO,
19
+ $ERROR_POSITION * "\n")
20
+
21
+ Kernel.warn BUNDLER_DECLARE_GEM
22
+ false
23
+ end
24
+
25
+ private
26
+
27
+ def _configure(dir, &callback)
28
+ require 'wdm'
29
+ _log :debug, 'wdm - starting...'
30
+ @worker ||= WDM::Monitor.new
31
+ @worker.watch_recursively(dir.to_s, :files) do |change|
32
+ callback.call([:file, change])
33
+ end
34
+
35
+ @worker.watch_recursively(dir.to_s, :directories) do |change|
36
+ callback.call([:dir, change])
37
+ end
38
+
39
+ events = [:attributes, :last_write]
40
+ @worker.watch_recursively(dir.to_s, *events) do |change|
41
+ callback.call([:attr, change])
42
+ end
43
+ end
44
+
45
+ def _run
46
+ @worker.run!
47
+ end
48
+
49
+ def _process_event(dir, event)
50
+ _log :debug, "wdm - callback: #{event.inspect}"
51
+
52
+ type, change = event
53
+
54
+ full_path = Pathname(change.path)
55
+
56
+ rel_path = full_path.relative_path_from(dir).to_s
57
+
58
+ options = { change: _change(change.type) }
59
+
60
+ case type
61
+ when :file
62
+ _queue_change(:file, dir, rel_path, options)
63
+ when :attr
64
+ unless full_path.directory?
65
+ _queue_change(:file, dir, rel_path, options)
66
+ end
67
+ when :dir
68
+ if change.type == :removed
69
+ # TODO: check if watched dir?
70
+ _queue_change(:dir, dir, Pathname(rel_path).dirname.to_s, {})
71
+ elsif change.type == :added
72
+ _queue_change(:dir, dir, rel_path, {})
73
+ else
74
+ # do nothing - changed directory means either:
75
+ # - removed subdirs (handled above)
76
+ # - added subdirs (handled above)
77
+ # - removed files (handled by _file_callback)
78
+ # - added files (handled by _file_callback)
79
+ # so what's left?
80
+ end
81
+ end
82
+ rescue
83
+ details = event.inspect
84
+ _log :error, format('wdm - callback (%): %s:%s', details, $ERROR_INFO,
85
+ $ERROR_POSITION * "\n")
86
+ raise
87
+ end
88
+
89
+ def _change(type)
90
+ { modified: [:modified, :attrib], # TODO: is attrib really passed?
91
+ added: [:added, :renamed_new_file],
92
+ removed: [:removed, :renamed_old_file] }.each do |change, types|
93
+ return change if types.include?(type)
94
+ end
95
+ nil
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,41 @@
1
+ require 'listen/adapter'
2
+ require 'listen/adapter/base'
3
+ require 'listen/adapter/config'
4
+
5
+ # This class just aggregates configuration object to avoid Listener specs
6
+ # from exploding with huge test setup blocks
7
+ module Listen
8
+ class Backend
9
+ def initialize(directories, queue, silencer, config)
10
+ adapter_select_opts = config.adapter_select_options
11
+
12
+ adapter_class = Adapter.select(adapter_select_opts)
13
+
14
+ # Use default from adapter if possible
15
+ @min_delay_between_events = config.min_delay_between_events
16
+ @min_delay_between_events ||= adapter_class::DEFAULTS[:wait_for_delay]
17
+ @min_delay_between_events ||= 0.1
18
+
19
+ adapter_opts = config.adapter_instance_options(adapter_class)
20
+
21
+ aconfig = Adapter::Config.new(directories, queue, silencer, adapter_opts)
22
+ @adapter = adapter_class.new(aconfig)
23
+ end
24
+
25
+ def start
26
+ adapter.start
27
+ end
28
+
29
+ def stop
30
+ adapter.stop
31
+ end
32
+
33
+ def min_delay_between_events
34
+ @min_delay_between_events
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :adapter
40
+ end
41
+ end
@@ -0,0 +1,78 @@
1
+ require 'listen/file'
2
+ require 'listen/directory'
3
+
4
+ module Listen
5
+ # TODO: rename to Snapshot
6
+ class Change
7
+ # TODO: test this class for coverage
8
+ class Config
9
+ def initialize(queue, silencer)
10
+ @queue = queue
11
+ @silencer = silencer
12
+ end
13
+
14
+ def silenced?(path, type)
15
+ @silencer.silenced?(Pathname(path), type)
16
+ end
17
+
18
+ def queue(*args)
19
+ @queue << args
20
+ end
21
+ end
22
+
23
+ attr_reader :record
24
+
25
+ def initialize(config, record)
26
+ @config = config
27
+ @record = record
28
+ end
29
+
30
+ # Invalidate some part of the snapshot/record (dir, file, subtree, etc.)
31
+ def invalidate(type, rel_path, options)
32
+ watched_dir = Pathname.new(record.root)
33
+
34
+ change = options[:change]
35
+ cookie = options[:cookie]
36
+
37
+ if !cookie && config.silenced?(rel_path, type)
38
+ Listen::Logger.debug { "(silenced): #{rel_path.inspect}" }
39
+ return
40
+ end
41
+
42
+ path = watched_dir + rel_path
43
+
44
+ Listen::Logger.debug do
45
+ log_details = options[:silence] && 'recording' || change || 'unknown'
46
+ "#{log_details}: #{type}:#{path} (#{options.inspect})"
47
+ end
48
+
49
+ if change
50
+ options = cookie ? { cookie: cookie } : {}
51
+ config.queue(type, change, watched_dir, rel_path, options)
52
+ else
53
+ if type == :dir
54
+ # NOTE: POSSIBLE RECURSION
55
+ # TODO: fix - use a queue instead
56
+ Directory.scan(self, rel_path, options)
57
+ else
58
+ change = File.change(record, rel_path)
59
+ return if !change || options[:silence]
60
+ config.queue(:file, change, watched_dir, rel_path)
61
+ end
62
+ end
63
+ rescue RuntimeError => ex
64
+ msg = format(
65
+ '%s#%s crashed %s:%s',
66
+ self.class,
67
+ __method__,
68
+ exinspect,
69
+ ex.backtrace * "\n")
70
+ Listen::Logger.error(msg)
71
+ raise
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :config
77
+ end
78
+ end
@@ -0,0 +1,65 @@
1
+ require 'thor'
2
+ require 'listen'
3
+ require 'logger'
4
+
5
+ module Listen
6
+ class CLI < Thor
7
+ default_task :start
8
+
9
+ desc 'start', 'Starts Listen'
10
+
11
+ class_option :verbose,
12
+ type: :boolean,
13
+ default: false,
14
+ aliases: '-v',
15
+ banner: 'Verbose'
16
+
17
+ class_option :directory,
18
+ type: :array,
19
+ default: '.',
20
+ aliases: '-d',
21
+ banner: 'The directory to listen to'
22
+
23
+ class_option :relative,
24
+ type: :boolean,
25
+ default: false,
26
+ aliases: '-r',
27
+ banner: 'Convert paths relative to current directory'
28
+
29
+ def start
30
+ Listen::Forwarder.new(options).start
31
+ end
32
+ end
33
+
34
+ class Forwarder
35
+ attr_reader :logger
36
+ def initialize(options)
37
+ @options = options
38
+ @logger = ::Logger.new(STDOUT)
39
+ @logger.level = ::Logger::INFO
40
+ @logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
41
+ end
42
+
43
+ def start
44
+ logger.info 'Starting listen...'
45
+ directory = @options[:directory]
46
+ relative = @options[:relative]
47
+ callback = proc do |modified, added, removed|
48
+ if @options[:verbose]
49
+ logger.info "+ #{added}" unless added.empty?
50
+ logger.info "- #{removed}" unless removed.empty?
51
+ logger.info "> #{modified}" unless modified.empty?
52
+ end
53
+ end
54
+
55
+ listener = Listen.to(
56
+ directory,
57
+ relative: relative,
58
+ &callback)
59
+
60
+ listener.start
61
+
62
+ sleep 0.5 while listener.processing?
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,76 @@
1
+ require 'set'
2
+
3
+ module Listen
4
+ # TODO: refactor (turn it into a normal object, cache the stat, etc)
5
+ class Directory
6
+ def self.scan(snapshot, rel_path, options)
7
+ record = snapshot.record
8
+ dir = Pathname.new(record.root)
9
+ previous = record.dir_entries(rel_path)
10
+
11
+ record.add_dir(rel_path)
12
+
13
+ # TODO: use children(with_directory: false)
14
+ path = dir + rel_path
15
+ current = Set.new(path.children)
16
+
17
+ Listen::Logger.debug do
18
+ format('%s: %s(%s): %s -> %s',
19
+ (options[:silence] ? 'Recording' : 'Scanning'),
20
+ rel_path, options.inspect, previous.inspect, current.inspect)
21
+ end
22
+
23
+ begin
24
+ current.each do |full_path|
25
+ type = ::File.lstat(full_path.to_s).directory? ? :dir : :file
26
+ item_rel_path = full_path.relative_path_from(dir).to_s
27
+ _change(snapshot, type, item_rel_path, options)
28
+ end
29
+ rescue Errno::ENOENT
30
+ # The directory changed meanwhile, so rescan it
31
+ current = Set.new(path.children)
32
+ retry
33
+ end
34
+
35
+ # TODO: this is not tested properly
36
+ previous = previous.reject { |entry, _| current.include? path + entry }
37
+
38
+ _async_changes(snapshot, Pathname.new(rel_path), previous, options)
39
+
40
+ rescue Errno::ENOENT, Errno::EHOSTDOWN
41
+ record.unset_path(rel_path)
42
+ _async_changes(snapshot, Pathname.new(rel_path), previous, options)
43
+
44
+ rescue Errno::ENOTDIR
45
+ # TODO: path not tested
46
+ record.unset_path(rel_path)
47
+ _async_changes(snapshot, path, previous, options)
48
+ _change(snapshot, :file, rel_path, options)
49
+ rescue
50
+ Listen::Logger.warn do
51
+ format('scan DIED: %s:%s', $ERROR_INFO, $ERROR_POSITION * "\n")
52
+ end
53
+ raise
54
+ end
55
+
56
+ def self._async_changes(snapshot, path, previous, options)
57
+ fail "Not a Pathname: #{path.inspect}" unless path.respond_to?(:children)
58
+ previous.each do |entry, data|
59
+ # TODO: this is a hack with insufficient testing
60
+ type = data.key?(:mtime) ? :file : :dir
61
+ rel_path_s = (path + entry).to_s
62
+ _change(snapshot, type, rel_path_s, options)
63
+ end
64
+ end
65
+
66
+ def self._change(snapshot, type, path, options)
67
+ return snapshot.invalidate(type, path, options) if type == :dir
68
+
69
+ # Minor param cleanup for tests
70
+ # TODO: use a dedicated Event class
71
+ opts = options.dup
72
+ opts.delete(:recursive)
73
+ snapshot.invalidate(type, path, opts)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,59 @@
1
+ module Listen
2
+ module Event
3
+ class Config
4
+ def initialize(
5
+ listener,
6
+ event_queue,
7
+ queue_optimizer,
8
+ wait_for_delay,
9
+ &block)
10
+
11
+ @listener = listener
12
+ @event_queue = event_queue
13
+ @queue_optimizer = queue_optimizer
14
+ @min_delay_between_events = wait_for_delay
15
+ @block = block
16
+ end
17
+
18
+ def sleep(*args)
19
+ Kernel.sleep(*args)
20
+ end
21
+
22
+ def call(*args)
23
+ @block.call(*args) if @block
24
+ end
25
+
26
+ def timestamp
27
+ Time.now.to_f
28
+ end
29
+
30
+ def event_queue
31
+ @event_queue
32
+ end
33
+
34
+ def callable?
35
+ @block
36
+ end
37
+
38
+ def optimize_changes(changes)
39
+ @queue_optimizer.smoosh_changes(changes)
40
+ end
41
+
42
+ def min_delay_between_events
43
+ @min_delay_between_events
44
+ end
45
+
46
+ def stopped?
47
+ listener.state == :stopped
48
+ end
49
+
50
+ def paused?
51
+ listener.state == :paused
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :listener
57
+ end
58
+ end
59
+ end