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