listen 3.2.1 → 3.5.1

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'English'
2
4
 
3
5
  require 'listen/version'
@@ -19,7 +21,6 @@ require 'listen/listener/config'
19
21
 
20
22
  module Listen
21
23
  class Listener
22
- # TODO: move the state machine's methods private
23
24
  include Listen::FSM
24
25
 
25
26
  # Initializes the directories listener.
@@ -32,13 +33,14 @@ module Listen
32
33
  # @yieldparam [Array<String>] added the list of added files
33
34
  # @yieldparam [Array<String>] removed the list of removed files
34
35
  #
36
+ # rubocop:disable Metrics/MethodLength
35
37
  def initialize(*dirs, &block)
36
38
  options = dirs.last.is_a?(Hash) ? dirs.pop : {}
37
39
 
38
40
  @config = Config.new(options)
39
41
 
40
42
  eq_config = Event::Queue::Config.new(@config.relative?)
41
- queue = Event::Queue.new(eq_config) { @processor.wakeup_on_event }
43
+ queue = Event::Queue.new(eq_config)
42
44
 
43
45
  silencer = Silencer.new
44
46
  rules = @config.silencer_rules
@@ -57,41 +59,43 @@ module Listen
57
59
 
58
60
  @processor = Event::Loop.new(pconfig)
59
61
 
60
- super() # FSM
62
+ initialize_fsm
61
63
  end
64
+ # rubocop:enable Metrics/MethodLength
62
65
 
63
- default_state :initializing
66
+ start_state :initializing
64
67
 
65
68
  state :initializing, to: [:backend_started, :stopped]
66
69
 
67
- state :backend_started, to: [:frontend_ready, :stopped] do
68
- backend.start
69
- end
70
-
71
- state :frontend_ready, to: [:processing_events, :stopped] do
72
- processor.setup
70
+ state :backend_started, to: [:processing_events, :stopped] do
71
+ @backend.start
73
72
  end
74
73
 
75
74
  state :processing_events, to: [:paused, :stopped] do
76
- processor.resume
75
+ @processor.start
77
76
  end
78
77
 
79
78
  state :paused, to: [:processing_events, :stopped] do
80
- processor.pause
79
+ @processor.pause
81
80
  end
82
81
 
83
82
  state :stopped, to: [:backend_started] do
84
- backend.stop # should be before processor.teardown to halt events ASAP
85
- processor.teardown
83
+ @backend.stop # halt events ASAP
84
+ @processor.stop
86
85
  end
87
86
 
88
87
  # Starts processing events and starts adapters
89
88
  # or resumes invoking callbacks if paused
90
89
  def start
91
- transition :backend_started if state == :initializing
92
- transition :frontend_ready if state == :backend_started
93
- transition :processing_events if state == :frontend_ready
94
- transition :processing_events if state == :paused
90
+ case state
91
+ when :initializing
92
+ transition :backend_started
93
+ transition :processing_events
94
+ when :paused
95
+ transition :processing_events
96
+ else
97
+ raise ArgumentError, "cannot start from state #{state.inspect}"
98
+ end
95
99
  end
96
100
 
97
101
  # Stops both listening for events and processing them
@@ -113,6 +117,10 @@ module Listen
113
117
  state == :paused
114
118
  end
115
119
 
120
+ def stopped?
121
+ state == :stopped
122
+ end
123
+
116
124
  def ignore(regexps)
117
125
  @silencer_controller.append_ignores(regexps)
118
126
  end
@@ -124,10 +132,5 @@ module Listen
124
132
  def only(regexps)
125
133
  @silencer_controller.replace_with_only(regexps)
126
134
  end
127
-
128
- private
129
-
130
- attr_reader :processor
131
- attr_reader :backend
132
135
  end
133
136
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  class Listener
3
5
  class Config
@@ -23,9 +25,7 @@ module Listen
23
25
  @relative
24
26
  end
25
27
 
26
- attr_reader :min_delay_between_events
27
-
28
- attr_reader :silencer_rules
28
+ attr_reader :min_delay_between_events, :silencer_rules
29
29
 
30
30
  def adapter_instance_options(klass)
31
31
  valid_keys = klass.const_get('DEFAULTS').keys
@@ -33,7 +33,7 @@ module Listen
33
33
  end
34
34
 
35
35
  def adapter_select_options
36
- valid_keys = %w(force_polling polling_fallback_message).map(&:to_sym)
36
+ valid_keys = %w[force_polling polling_fallback_message].map(&:to_sym)
37
37
  Hash[@options.select { |key, _| valid_keys.include?(key) }]
38
38
  end
39
39
  end
data/lib/listen/logger.rb CHANGED
@@ -1,32 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
- def self.logger
3
- @logger ||= nil
4
- end
4
+ @logger = nil
5
5
 
6
- def self.logger=(logger)
7
- @logger = logger
8
- end
6
+ # Listen.logger will always be present.
7
+ # If you don't want logging, set Listen.logger = ::Logger.new('/dev/null', level: ::Logger::UNKNOWN)
9
8
 
10
- def self.setup_default_logger_if_unset
11
- self.logger ||= ::Logger.new(STDERR).tap do |logger|
12
- debugging = ENV['LISTEN_GEM_DEBUGGING']
13
- logger.level =
14
- case debugging.to_s
15
- when /2/
9
+ class << self
10
+ attr_writer :logger
11
+
12
+ def logger
13
+ @logger ||= default_logger
14
+ end
15
+
16
+ private
17
+
18
+ def default_logger
19
+ level =
20
+ case ENV['LISTEN_GEM_DEBUGGING'].to_s
21
+ when /debug|2/i
16
22
  ::Logger::DEBUG
17
- when /true|yes|1/i
23
+ when /info|true|yes|1/i
18
24
  ::Logger::INFO
25
+ when /warn/i
26
+ ::Logger::WARN
27
+ when /fatal/i
28
+ ::Logger::FATAL
19
29
  else
20
30
  ::Logger::ERROR
21
31
  end
22
- end
23
- end
24
32
 
25
- class Logger
26
- [:fatal, :error, :warn, :info, :debug].each do |meth|
27
- define_singleton_method(meth) do |*args, &block|
28
- Listen.logger.public_send(meth, *args, &block) if Listen.logger
29
- end
33
+ ::Logger.new(STDERR, level: level)
30
34
  end
31
35
  end
32
36
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Listen
4
+ module MonotonicTime
5
+ class << self
6
+ if defined?(Process::CLOCK_MONOTONIC)
7
+
8
+ def now
9
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
10
+ end
11
+
12
+ elsif defined?(Process::CLOCK_MONOTONIC_RAW)
13
+
14
+ def now
15
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW)
16
+ end
17
+
18
+ else
19
+
20
+ def now
21
+ Time.now.to_f
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,23 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  class Options
3
5
  def initialize(opts, defaults)
4
6
  @options = {}
5
7
  given_options = opts.dup
6
- defaults.keys.each do |key|
8
+ defaults.each_key do |key|
7
9
  @options[key] = given_options.delete(key) || defaults[key]
8
10
  end
9
11
 
10
- return if given_options.empty?
12
+ given_options.empty? or raise ArgumentError, "Unknown options: #{given_options.inspect}"
13
+ end
11
14
 
12
- msg = "Unknown options: #{given_options.inspect}"
13
- Listen::Logger.warn msg
14
- fail msg
15
+ # rubocop:disable Lint/MissingSuper
16
+ def respond_to_missing?(name, *_)
17
+ @options.has_key?(name)
15
18
  end
16
19
 
17
20
  def method_missing(name, *_)
18
- return @options[name] if @options.key?(name)
19
- msg = "Bad option: #{name.inspect} (valid:#{@options.keys.inspect})"
20
- fail NameError, msg
21
+ respond_to_missing?(name) or raise NameError, "Bad option: #{name.inspect} (valid:#{@options.keys.inspect})"
22
+ @options[name]
21
23
  end
24
+ # rubocop:enable Lint/MissingSuper
22
25
  end
23
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  class QueueOptimizer
3
5
  class Config
@@ -60,7 +62,7 @@ module Listen
60
62
  actions << :added if actions.delete(:moved_to)
61
63
  actions << :removed if actions.delete(:moved_from)
62
64
 
63
- modified = actions.detect { |x| x == :modified }
65
+ modified = actions.find { |x| x == :modified }
64
66
  _calculate_add_remove_difference(actions, path, modified)
65
67
  end
66
68
 
@@ -89,10 +91,8 @@ module Listen
89
91
  def _reinterpret_related_changes(cookies)
90
92
  table = { moved_to: :added, moved_from: :removed }
91
93
  cookies.flat_map do |_, changes|
92
- data = _detect_possible_editor_save(changes)
93
- if data
94
- to_dir, to_file = data
95
- [[:modified, to_dir, to_file]]
94
+ if (editor_modified = editor_modified?(changes))
95
+ [[:modified, *editor_modified]]
96
96
  else
97
97
  not_silenced = changes.reject do |type, _, _, path, _|
98
98
  config.silenced?(Pathname(path), type)
@@ -104,7 +104,7 @@ module Listen
104
104
  end
105
105
  end
106
106
 
107
- def _detect_possible_editor_save(changes)
107
+ def editor_modified?(changes)
108
108
  return unless changes.size == 2
109
109
 
110
110
  from_type = from = nil
@@ -116,17 +116,14 @@ module Listen
116
116
  from_type, _from_change, _, from, = data
117
117
  when :moved_to
118
118
  to_type, _to_change, to_dir, to, = data
119
- else
120
- return nil
121
119
  end
122
120
  end
123
121
 
124
- return unless from && to
125
-
126
122
  # Expect an ignored moved_from and non-ignored moved_to
127
123
  # to qualify as an "editor modify"
128
- return unless config.silenced?(Pathname(from), from_type)
129
- config.silenced?(Pathname(to), to_type) ? nil : [to_dir, to]
124
+ if from && to && config.silenced?(Pathname(from), from_type) && !config.silenced?(Pathname(to), to_type)
125
+ [to_dir, to]
126
+ end
130
127
  end
131
128
  end
132
129
  end
data/lib/listen/record.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
  require 'listen/record/entry'
3
5
  require 'listen/record/symlink_detector'
@@ -8,14 +10,16 @@ module Listen
8
10
  # TODO: deprecate
9
11
 
10
12
  attr_reader :root
13
+
11
14
  def initialize(directory)
12
15
  @tree = _auto_hash
13
16
  @root = directory.to_s
14
17
  end
15
18
 
16
19
  def add_dir(rel_path)
17
- return if [nil, '', '.'].include? rel_path
18
- @tree[rel_path] ||= {}
20
+ if ![nil, '', '.'].include?(rel_path)
21
+ @tree[rel_path] ||= {}
22
+ end
19
23
  end
20
24
 
21
25
  def update_file(rel_path, data)
@@ -31,30 +35,27 @@ module Listen
31
35
  def file_data(rel_path)
32
36
  dirname, basename = Pathname(rel_path).split.map(&:to_s)
33
37
  if [nil, '', '.'].include? dirname
34
- tree[basename] ||= {}
35
- tree[basename].dup
38
+ @tree[basename] ||= {}
39
+ @tree[basename].dup
36
40
  else
37
- tree[dirname] ||= {}
38
- tree[dirname][basename] ||= {}
39
- tree[dirname][basename].dup
41
+ @tree[dirname] ||= {}
42
+ @tree[dirname][basename] ||= {}
43
+ @tree[dirname][basename].dup
40
44
  end
41
45
  end
42
46
 
43
47
  def dir_entries(rel_path)
44
- subtree =
45
- if [nil, '', '.'].include? rel_path.to_s
46
- tree
47
- else
48
- tree[rel_path.to_s] ||= _auto_hash
49
- tree[rel_path.to_s]
50
- end
48
+ subtree = if ['', '.'].include? rel_path.to_s
49
+ @tree
50
+ else
51
+ @tree[rel_path.to_s] ||= _auto_hash
52
+ @tree[rel_path.to_s]
53
+ end
51
54
 
52
- result = {}
53
- subtree.each do |key, values|
55
+ subtree.transform_values do |values|
54
56
  # only get data for file entries
55
- result[key] = values.key?(:mtime) ? values : {}
57
+ values.key?(:mtime) ? values : {}
56
58
  end
57
- result
58
59
  end
59
60
 
60
61
  def build
@@ -71,29 +72,27 @@ module Listen
71
72
  private
72
73
 
73
74
  def _auto_hash
74
- Hash.new { |h, k| h[k] = Hash.new }
75
+ Hash.new { |h, k| h[k] = {} }
75
76
  end
76
77
 
77
- attr_reader :tree
78
-
79
78
  def _fast_update_file(dirname, basename, data)
80
- if [nil, '', '.'].include? dirname
81
- tree[basename] = (tree[basename] || {}).merge(data)
79
+ if [nil, '', '.'].include?(dirname)
80
+ @tree[basename] = (@tree[basename] || {}).merge(data)
82
81
  else
83
- tree[dirname] ||= {}
84
- tree[dirname][basename] = (tree[dirname][basename] || {}).merge(data)
82
+ @tree[dirname] ||= {}
83
+ @tree[dirname][basename] = (@tree[dirname][basename] || {}).merge(data)
85
84
  end
86
85
  end
87
86
 
88
87
  def _fast_unset_path(dirname, basename)
89
88
  # this may need to be reworked to properly remove
90
89
  # entries from a tree, without adding non-existing dirs to the record
91
- if [nil, '', '.'].include? dirname
92
- return unless tree.key?(basename)
93
- tree.delete(basename)
94
- else
95
- return unless tree.key?(dirname)
96
- tree[dirname].delete(basename)
90
+ if [nil, '', '.'].include?(dirname)
91
+ if @tree.key?(basename)
92
+ @tree.delete(basename)
93
+ end
94
+ elsif @tree.key?(dirname)
95
+ @tree[dirname].delete(basename)
97
96
  end
98
97
  end
99
98
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  # @private api
3
5
  class Record
@@ -15,14 +17,14 @@ module Listen
15
17
 
16
18
  def children
17
19
  child_relative = _join
18
- (_entries(sys_path) - %w(. ..)).map do |name|
20
+ (_entries(sys_path) - %w[. ..]).map do |name|
19
21
  Entry.new(@root, child_relative, name)
20
22
  end
21
23
  end
22
24
 
23
25
  def meta
24
26
  lstat = ::File.lstat(sys_path)
25
- { mtime: lstat.mtime.to_f, mode: lstat.mode }
27
+ { mtime: lstat.mtime.to_f, mode: lstat.mode, size: lstat.size }
26
28
  end
27
29
 
28
30
  # record hash is e.g.
@@ -1,23 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
4
+ require 'listen/error'
2
5
 
3
6
  module Listen
4
7
  # @private api
5
8
  class Record
6
9
  class SymlinkDetector
7
- WIKI = 'https://github.com/guard/listen/wiki/Duplicate-directory-errors'.freeze
10
+ README_URL = 'https://github.com/guard/listen/blob/master/README.md'
8
11
 
9
- SYMLINK_LOOP_ERROR = <<-EOS.freeze
12
+ SYMLINK_LOOP_ERROR = <<-EOS
10
13
  ** ERROR: directory is already being watched! **
11
14
 
12
15
  Directory: %s
13
16
 
14
17
  is already being watched through: %s
15
18
 
16
- MORE INFO: #{WIKI}
19
+ MORE INFO: #{README_URL}
17
20
  EOS
18
21
 
19
- class Error < RuntimeError
20
- end
22
+ Error = ::Listen::Error # for backward compatibility
21
23
 
22
24
  def initialize
23
25
  @real_dirs = Set.new
@@ -25,14 +27,14 @@ module Listen
25
27
 
26
28
  def verify_unwatched!(entry)
27
29
  real_path = entry.real_path
28
- @real_dirs.add?(real_path) || _fail(entry.sys_path, real_path)
30
+ @real_dirs.add?(real_path) or _fail(entry.sys_path, real_path)
29
31
  end
30
32
 
31
33
  private
32
34
 
33
35
  def _fail(symlinked, real_path)
34
- STDERR.puts format(SYMLINK_LOOP_ERROR, symlinked, real_path)
35
- fail Error, 'Failed due to looped symlinks'
36
+ warn(format(SYMLINK_LOOP_ERROR, symlinked, real_path))
37
+ raise ::Listen::Error::SymlinkLoop, 'Failed due to looped symlinks'
36
38
  end
37
39
  end
38
40
  end