listen 3.3.0 → 3.9.0

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,25 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require 'listen/error'
4
5
 
5
6
  module Listen
6
7
  # @private api
7
8
  class Record
8
9
  class SymlinkDetector
9
- WIKI = 'https://github.com/guard/listen/wiki/Duplicate-directory-errors'.freeze
10
+ README_URL = 'https://github.com/guard/listen/blob/master/README.md'
10
11
 
11
- SYMLINK_LOOP_ERROR = <<-EOS.freeze
12
+ SYMLINK_LOOP_ERROR = <<-EOS
12
13
  ** ERROR: directory is already being watched! **
13
14
 
14
15
  Directory: %s
15
16
 
16
17
  is already being watched through: %s
17
18
 
18
- MORE INFO: #{WIKI}
19
+ MORE INFO: #{README_URL}
19
20
  EOS
20
21
 
21
- class Error < RuntimeError
22
- end
22
+ Error = ::Listen::Error # for backward compatibility
23
23
 
24
24
  def initialize
25
25
  @real_dirs = Set.new
@@ -27,14 +27,20 @@ module Listen
27
27
 
28
28
  def verify_unwatched!(entry)
29
29
  real_path = entry.real_path
30
- @real_dirs.add?(real_path) || _fail(entry.sys_path, real_path)
30
+ @real_dirs.add?(real_path) or _fail(entry.sys_path, real_path)
31
+ end
32
+
33
+ # Leaving this stub here since some warning work-arounds were referring to it.
34
+ # Deprecated. Will be removed in Listen v4.0.
35
+ def warn(message)
36
+ Listen.adapter_warn(message)
31
37
  end
32
38
 
33
39
  private
34
40
 
35
41
  def _fail(symlinked, real_path)
36
- STDERR.puts format(SYMLINK_LOOP_ERROR, symlinked, real_path)
37
- fail Error, 'Failed due to looped symlinks'
42
+ warn(format(SYMLINK_LOOP_ERROR, symlinked, real_path))
43
+ raise ::Listen::Error::SymlinkLoop, 'Failed due to looped symlinks'
38
44
  end
39
45
  end
40
46
  end
data/lib/listen/record.rb CHANGED
@@ -10,14 +10,17 @@ module Listen
10
10
  # TODO: deprecate
11
11
 
12
12
  attr_reader :root
13
- def initialize(directory)
14
- @tree = _auto_hash
13
+
14
+ def initialize(directory, silencer)
15
+ reset_tree
15
16
  @root = directory.to_s
17
+ @silencer = silencer
16
18
  end
17
19
 
18
20
  def add_dir(rel_path)
19
- return if [nil, '', '.'].include? rel_path
20
- @tree[rel_path] ||= {}
21
+ if !empty_dirname?(rel_path.to_s)
22
+ @tree[rel_path.to_s]
23
+ end
21
24
  end
22
25
 
23
26
  def update_file(rel_path, data)
@@ -32,47 +35,32 @@ module Listen
32
35
 
33
36
  def file_data(rel_path)
34
37
  dirname, basename = Pathname(rel_path).split.map(&:to_s)
35
- if [nil, '', '.'].include? dirname
36
- tree[basename] ||= {}
37
- tree[basename].dup
38
+ if empty_dirname?(dirname)
39
+ @tree[basename].dup
38
40
  else
39
- tree[dirname] ||= {}
40
- tree[dirname][basename] ||= {}
41
- tree[dirname][basename].dup
41
+ @tree[dirname][basename] ||= {}
42
+ @tree[dirname][basename].dup
42
43
  end
43
44
  end
44
45
 
45
46
  def dir_entries(rel_path)
46
- subtree =
47
- if [nil, '', '.'].include? rel_path.to_s
48
- tree
49
- else
50
- _sub_tree(rel_path)
51
- end
52
-
53
- result = {}
54
- subtree.each do |key, values|
55
- # only get data for file entries
56
- result[key] = values.key?(:mtime) ? values : {}
47
+ rel_path_s = rel_path.to_s
48
+ subtree = if empty_dirname?(rel_path_s)
49
+ @tree
50
+ else
51
+ @tree[rel_path_s]
57
52
  end
58
- result
59
- end
60
53
 
61
- def _sub_tree(rel_path)
62
- tree.each_with_object({}) do |(path, meta), result|
63
- next unless path.start_with?(rel_path)
64
-
65
- if path == rel_path
66
- result.merge!(meta)
67
- else
68
- sub_path = path.sub(%r{\A#{rel_path}/?}, '')
69
- result[sub_path] = meta
54
+ subtree.each_with_object({}) do |(key, values), result|
55
+ # only return data for file entries inside the dir (which will each be sub-hashes)
56
+ if values.is_a?(Hash)
57
+ result[key] = values.has_key?(:mtime) ? values : {}
70
58
  end
71
59
  end
72
60
  end
73
61
 
74
62
  def build
75
- @tree = _auto_hash
63
+ reset_tree
76
64
  # TODO: test with a file name given
77
65
  # TODO: test other permissions
78
66
  # TODO: test with mixed encoding
@@ -84,35 +72,38 @@ module Listen
84
72
 
85
73
  private
86
74
 
87
- def _auto_hash
88
- Hash.new { |h, k| h[k] = Hash.new }
75
+ def empty_dirname?(dirname)
76
+ dirname == '.' || dirname == ''
89
77
  end
90
78
 
91
- attr_reader :tree
79
+ def reset_tree
80
+ @tree = Hash.new { |h, k| h[k] = {} }
81
+ end
92
82
 
93
83
  def _fast_update_file(dirname, basename, data)
94
- if [nil, '', '.'].include? dirname
95
- tree[basename] = (tree[basename] || {}).merge(data)
84
+ if empty_dirname?(dirname.to_s)
85
+ @tree[basename] = @tree[basename].merge(data)
96
86
  else
97
- tree[dirname] ||= {}
98
- tree[dirname][basename] = (tree[dirname][basename] || {}).merge(data)
87
+ @tree[dirname][basename] = (@tree[dirname][basename] || {}).merge(data)
99
88
  end
100
89
  end
101
90
 
102
91
  def _fast_unset_path(dirname, basename)
103
92
  # this may need to be reworked to properly remove
104
93
  # entries from a tree, without adding non-existing dirs to the record
105
- if [nil, '', '.'].include? dirname
106
- return unless tree.key?(basename)
107
- tree.delete(basename)
108
- else
109
- return unless tree.key?(dirname)
110
- tree[dirname].delete(basename)
94
+ if empty_dirname?(dirname.to_s)
95
+ if @tree.key?(basename)
96
+ @tree.delete(basename)
97
+ end
98
+ elsif @tree.key?(dirname)
99
+ @tree[dirname].delete(basename)
111
100
  end
112
101
  end
113
102
 
114
103
  def _fast_build_dir(remaining, symlink_detector)
115
104
  entry = remaining.pop
105
+ return if @silencer.silenced?(entry.record_dir_key, :dir)
106
+
116
107
  children = entry.children # NOTE: children() implicitly tests if dir
117
108
  symlink_detector.verify_unwatched!(entry)
118
109
  children.each { |child| remaining << child }
@@ -3,8 +3,8 @@
3
3
  module Listen
4
4
  class Silencer
5
5
  # The default list of directories that get ignored.
6
- DEFAULT_IGNORED_DIRECTORIES = %r{^(?:
7
- \.git
6
+ DEFAULT_IGNORED_FILES = %r{\A(?:
7
+ \.git
8
8
  | \.svn
9
9
  | \.hg
10
10
  | \.rbx
@@ -13,8 +13,12 @@ module Listen
13
13
  | vendor/bundle
14
14
  | log
15
15
  | tmp
16
- |vendor/ruby
17
- )(/|$)}x
16
+ | vendor/ruby
17
+
18
+ # emacs temp files
19
+ | \#.+\#
20
+ | \.\#.+
21
+ )(/|\z)}x.freeze
18
22
 
19
23
  # The default list of files that get ignored.
20
24
  DEFAULT_IGNORED_EXTENSIONS = %r{(?:
@@ -35,7 +39,7 @@ module Listen
35
39
  | ^4913
36
40
 
37
41
  # Sed temporary files - but without actual words, like 'sedatives'
38
- | (?:^
42
+ | (?:\A
39
43
  sed
40
44
 
41
45
  (?:
@@ -55,41 +59,41 @@ module Listen
55
59
  | \.DS_Store
56
60
  | \.tmp
57
61
  | ~
58
- )$}x
62
+ )\z}x.freeze
59
63
 
64
+ # TODO: deprecate these mutators; use attr_reader instead
60
65
  attr_accessor :only_patterns, :ignore_patterns
61
66
 
62
- def initialize
63
- configure({})
67
+ def initialize(**options)
68
+ configure(options)
64
69
  end
65
70
 
71
+ # TODO: deprecate this mutator
66
72
  def configure(options)
67
73
  @only_patterns = options[:only] ? Array(options[:only]) : nil
68
74
  @ignore_patterns = _init_ignores(options[:ignore], options[:ignore!])
69
75
  end
70
76
 
71
- # Note: relative_path is temporarily expected to be a relative Pathname to
72
- # make refactoring easier (ideally, it would take a string)
73
-
74
- # TODO: switch type and path places - and verify
75
77
  def silenced?(relative_path, type)
76
- path = relative_path.to_s
78
+ path = relative_path.to_s # in case it is a Pathname
77
79
 
78
- if only_patterns && type == :file
79
- return true unless only_patterns.any? { |pattern| path =~ pattern }
80
- end
81
-
82
- ignore_patterns.any? { |pattern| path =~ pattern }
80
+ _ignore?(path) || (only_patterns && type == :file && !_only?(path))
83
81
  end
84
82
 
85
83
  private
86
84
 
87
- attr_reader :options
85
+ def _ignore?(path)
86
+ ignore_patterns.any? { |pattern| path =~ pattern }
87
+ end
88
+
89
+ def _only?(path)
90
+ only_patterns.any? { |pattern| path =~ pattern }
91
+ end
88
92
 
89
93
  def _init_ignores(ignores, overrides)
90
94
  patterns = []
91
95
  unless overrides
92
- patterns << DEFAULT_IGNORED_DIRECTORIES
96
+ patterns << DEFAULT_IGNORED_FILES
93
97
  patterns << DEFAULT_IGNORED_EXTENSIONS
94
98
  end
95
99
 
data/lib/listen/thread.rb CHANGED
@@ -9,6 +9,7 @@ module Listen
9
9
  class << self
10
10
  # Creates a new thread with the given name.
11
11
  # Any exceptions raised by the thread will be logged with the thread name and complete backtrace.
12
+ # rubocop:disable Style/MultilineBlockChain
12
13
  def new(name, &block)
13
14
  thread_name = "listen-#{name}"
14
15
  caller_stack = caller
@@ -19,31 +20,32 @@ module Listen
19
20
  thread.name = thread_name
20
21
  end
21
22
  end
23
+ # rubocop:enable Style/MultilineBlockChain
22
24
 
23
25
  def rescue_and_log(method_name, *args, caller_stack: nil)
24
26
  yield(*args)
25
- rescue Exception => ex
26
- _log_exception(ex, method_name, caller_stack: caller_stack)
27
+ rescue => exception
28
+ _log_exception(exception, method_name, caller_stack: caller_stack)
27
29
  end
28
30
 
29
31
  private
30
32
 
31
- def _log_exception(ex, thread_name, caller_stack: nil)
33
+ def _log_exception(exception, thread_name, caller_stack: nil)
32
34
  complete_backtrace = if caller_stack
33
- [*ex.backtrace, "--- Thread.new ---", *caller_stack]
34
- else
35
- ex.backtrace
36
- end
37
- message = "Exception rescued in #{thread_name}:\n#{_exception_with_causes(ex)}\n#{complete_backtrace * "\n"}"
35
+ [*exception.backtrace, "--- Thread.new ---", *caller_stack]
36
+ else
37
+ exception.backtrace
38
+ end
39
+ message = "Exception rescued in #{thread_name}:\n#{_exception_with_causes(exception)}\n#{complete_backtrace * "\n"}"
38
40
  Listen.logger.error(message)
39
41
  end
40
42
 
41
- def _exception_with_causes(ex)
42
- result = +"#{ex.class}: #{ex}"
43
- if ex.cause
43
+ def _exception_with_causes(exception)
44
+ result = +"#{exception.class}: #{exception}"
45
+ if exception.cause
44
46
  result << "\n"
45
47
  result << "--- Caused by: ---\n"
46
- result << _exception_with_causes(ex.cause)
48
+ result << _exception_with_causes(exception.cause)
47
49
  end
48
50
  result
49
51
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Listen
4
- VERSION = '3.3.0'
4
+ VERSION = '3.9.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: listen
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thibaud Guillaume-Gentil
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-10 00:00:00.000000000 Z
11
+ date: 2024-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb-fsevent
@@ -51,7 +51,7 @@ dependencies:
51
51
  - !ruby/object:Gem::Version
52
52
  version: 0.9.10
53
53
  description: The Listen gem listens to file modifications and notifies you about the
54
- changegem. Works everywhere!
54
+ changes. Works everywhere!
55
55
  email: thibaud@thibaud.gg
56
56
  executables:
57
57
  - listen
@@ -76,6 +76,7 @@ files:
76
76
  - lib/listen/change.rb
77
77
  - lib/listen/cli.rb
78
78
  - lib/listen/directory.rb
79
+ - lib/listen/error.rb
79
80
  - lib/listen/event/config.rb
80
81
  - lib/listen/event/loop.rb
81
82
  - lib/listen/event/processor.rb
@@ -85,6 +86,7 @@ files:
85
86
  - lib/listen/listener.rb
86
87
  - lib/listen/listener/config.rb
87
88
  - lib/listen/logger.rb
89
+ - lib/listen/monotonic_time.rb
88
90
  - lib/listen/options.rb
89
91
  - lib/listen/queue_optimizer.rb
90
92
  - lib/listen/record.rb
@@ -99,7 +101,12 @@ licenses:
99
101
  - MIT
100
102
  metadata:
101
103
  allowed_push_host: https://rubygems.org
102
- post_install_message:
104
+ bug_tracker_uri: https://github.com/guard/listen/issues
105
+ changelog_uri: https://github.com/guard/listen/releases
106
+ documentation_uri: https://www.rubydoc.info/gems/listen/3.9.0
107
+ homepage_uri: https://github.com/guard/listen
108
+ source_code_uri: https://github.com/guard/listen/tree/v3.9.0
109
+ post_install_message:
103
110
  rdoc_options: []
104
111
  require_paths:
105
112
  - lib
@@ -107,15 +114,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
114
  requirements:
108
115
  - - ">="
109
116
  - !ruby/object:Gem::Version
110
- version: 2.2.7
117
+ version: 2.4.0
111
118
  required_rubygems_version: !ruby/object:Gem::Requirement
112
119
  requirements:
113
120
  - - ">="
114
121
  - !ruby/object:Gem::Version
115
122
  version: '0'
116
123
  requirements: []
117
- rubygems_version: 3.0.1
118
- signing_key:
124
+ rubygems_version: 3.4.6
125
+ signing_key:
119
126
  specification_version: 4
120
127
  summary: Listen to file modifications
121
128
  test_files: []