listen 3.0.8 → 3.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,13 @@
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'
10
+ README_URL = 'https://github.com/guard/listen/blob/master/README.md'
8
11
 
9
12
  SYMLINK_LOOP_ERROR = <<-EOS
10
13
  ** ERROR: directory is already being watched! **
@@ -13,11 +16,10 @@ module Listen
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
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,17 @@ module Listen
8
10
  # TODO: deprecate
9
11
 
10
12
  attr_reader :root
11
- def initialize(directory)
12
- @tree = _auto_hash
13
+
14
+ def initialize(directory, silencer)
15
+ reset_tree
13
16
  @root = directory.to_s
17
+ @silencer = silencer
14
18
  end
15
19
 
16
20
  def add_dir(rel_path)
17
- return if [nil, '', '.'].include? rel_path
18
- @tree[rel_path] ||= {}
21
+ if !empty_dirname?(rel_path.to_s)
22
+ @tree[rel_path.to_s]
23
+ end
19
24
  end
20
25
 
21
26
  def update_file(rel_path, data)
@@ -30,35 +35,32 @@ module Listen
30
35
 
31
36
  def file_data(rel_path)
32
37
  dirname, basename = Pathname(rel_path).split.map(&:to_s)
33
- if [nil, '', '.'].include? dirname
34
- tree[basename] ||= {}
35
- tree[basename].dup
38
+ if empty_dirname?(dirname)
39
+ @tree[basename].dup
36
40
  else
37
- tree[dirname] ||= {}
38
- tree[dirname][basename] ||= {}
39
- tree[dirname][basename].dup
41
+ @tree[dirname][basename] ||= {}
42
+ @tree[dirname][basename].dup
40
43
  end
41
44
  end
42
45
 
43
46
  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
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]
52
+ end
51
53
 
52
- result = {}
53
- subtree.each do |key, values|
54
- # only get data for file entries
55
- result[key] = values.key?(:mtime) ? values : {}
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 : {}
58
+ end
56
59
  end
57
- result
58
60
  end
59
61
 
60
62
  def build
61
- @tree = _auto_hash
63
+ reset_tree
62
64
  # TODO: test with a file name given
63
65
  # TODO: test other permissions
64
66
  # TODO: test with mixed encoding
@@ -70,37 +72,38 @@ module Listen
70
72
 
71
73
  private
72
74
 
73
- def _auto_hash
74
- Hash.new { |h, k| h[k] = Hash.new }
75
+ def empty_dirname?(dirname)
76
+ dirname == '.' || dirname == ''
75
77
  end
76
78
 
77
- def tree
78
- @tree
79
+ def reset_tree
80
+ @tree = Hash.new { |h, k| h[k] = {} }
79
81
  end
80
82
 
81
83
  def _fast_update_file(dirname, basename, data)
82
- if [nil, '', '.'].include? dirname
83
- tree[basename] = (tree[basename] || {}).merge(data)
84
+ if empty_dirname?(dirname.to_s)
85
+ @tree[basename] = @tree[basename].merge(data)
84
86
  else
85
- tree[dirname] ||= {}
86
- tree[dirname][basename] = (tree[dirname][basename] || {}).merge(data)
87
+ @tree[dirname][basename] = (@tree[dirname][basename] || {}).merge(data)
87
88
  end
88
89
  end
89
90
 
90
91
  def _fast_unset_path(dirname, basename)
91
92
  # this may need to be reworked to properly remove
92
93
  # entries from a tree, without adding non-existing dirs to the record
93
- if [nil, '', '.'].include? dirname
94
- return unless tree.key?(basename)
95
- tree.delete(basename)
96
- else
97
- return unless tree.key?(dirname)
98
- 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)
99
100
  end
100
101
  end
101
102
 
102
103
  def _fast_build_dir(remaining, symlink_detector)
103
104
  entry = remaining.pop
105
+ return if @silencer.silenced?(entry.record_dir_key, :dir)
106
+
104
107
  children = entry.children # NOTE: children() implicitly tests if dir
105
108
  symlink_detector.verify_unwatched!(entry)
106
109
  children.each { |child| remaining << child }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  class Silencer
3
5
  class Controller
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  class Silencer
3
5
  # The default list of directories that get ignored.
4
- DEFAULT_IGNORED_DIRECTORIES = %r{^(?:
5
- \.git
6
+ DEFAULT_IGNORED_FILES = %r{\A(?:
7
+ \.git
6
8
  | \.svn
7
9
  | \.hg
8
10
  | \.rbx
@@ -11,11 +13,15 @@ module Listen
11
13
  | vendor/bundle
12
14
  | log
13
15
  | tmp
14
- |vendor/ruby
15
- )(/|$)}x
16
+ | vendor/ruby
17
+
18
+ # emacs temp files
19
+ | \#.+\#
20
+ | \.\#.+
21
+ )(/|\z)}x.freeze
16
22
 
17
23
  # The default list of files that get ignored.
18
- DEFAULT_IGNORED_EXTENSIONS = /(?:
24
+ DEFAULT_IGNORED_EXTENSIONS = %r{(?:
19
25
  # Kate's tmp\/swp files
20
26
  \..*\d+\.new
21
27
  | \.kate-swp
@@ -33,7 +39,7 @@ module Listen
33
39
  | ^4913
34
40
 
35
41
  # Sed temporary files - but without actual words, like 'sedatives'
36
- | (?:^
42
+ | (?:\A
37
43
  sed
38
44
 
39
45
  (?:
@@ -46,45 +52,48 @@ module Listen
46
52
  )
47
53
  )
48
54
 
55
+ # Mutagen sync temporary files
56
+ | \.mutagen-temporary.*
57
+
49
58
  # other files
50
59
  | \.DS_Store
51
60
  | \.tmp
52
61
  | ~
53
- )$/x
62
+ )\z}x.freeze
54
63
 
64
+ # TODO: deprecate these mutators; use attr_reader instead
55
65
  attr_accessor :only_patterns, :ignore_patterns
56
66
 
57
- def initialize
58
- configure({})
67
+ def initialize(**options)
68
+ configure(options)
59
69
  end
60
70
 
71
+ # TODO: deprecate this mutator
61
72
  def configure(options)
62
73
  @only_patterns = options[:only] ? Array(options[:only]) : nil
63
74
  @ignore_patterns = _init_ignores(options[:ignore], options[:ignore!])
64
75
  end
65
76
 
66
- # Note: relative_path is temporarily expected to be a relative Pathname to
67
- # make refactoring easier (ideally, it would take a string)
68
-
69
- # TODO: switch type and path places - and verify
70
77
  def silenced?(relative_path, type)
71
- path = relative_path.to_s
72
-
73
- if only_patterns && type == :file
74
- return true unless only_patterns.any? { |pattern| path =~ pattern }
75
- end
78
+ path = relative_path.to_s # in case it is a Pathname
76
79
 
77
- ignore_patterns.any? { |pattern| path =~ pattern }
80
+ _ignore?(path) || (only_patterns && type == :file && !_only?(path))
78
81
  end
79
82
 
80
83
  private
81
84
 
82
- 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
83
92
 
84
93
  def _init_ignores(ignores, overrides)
85
94
  patterns = []
86
95
  unless overrides
87
- patterns << DEFAULT_IGNORED_DIRECTORIES
96
+ patterns << DEFAULT_IGNORED_FILES
88
97
  patterns << DEFAULT_IGNORED_EXTENSIONS
89
98
  end
90
99
 
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ require_relative 'logger'
6
+
7
+ module Listen
8
+ module Thread
9
+ class << self
10
+ # Creates a new thread with the given name.
11
+ # Any exceptions raised by the thread will be logged with the thread name and complete backtrace.
12
+ # rubocop:disable Style/MultilineBlockChain
13
+ def new(name, &block)
14
+ thread_name = "listen-#{name}"
15
+ caller_stack = caller
16
+
17
+ ::Thread.new do
18
+ rescue_and_log(thread_name, caller_stack: caller_stack, &block)
19
+ end.tap do |thread|
20
+ thread.name = thread_name
21
+ end
22
+ end
23
+ # rubocop:enable Style/MultilineBlockChain
24
+
25
+ def rescue_and_log(method_name, *args, caller_stack: nil)
26
+ yield(*args)
27
+ rescue => exception
28
+ _log_exception(exception, method_name, caller_stack: caller_stack)
29
+ end
30
+
31
+ private
32
+
33
+ def _log_exception(exception, thread_name, caller_stack: nil)
34
+ complete_backtrace = if caller_stack
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"}"
40
+ Listen.logger.error(message)
41
+ end
42
+
43
+ def _exception_with_causes(exception)
44
+ result = +"#{exception.class}: #{exception}"
45
+ if exception.cause
46
+ result << "\n"
47
+ result << "--- Caused by: ---\n"
48
+ result << _exception_with_causes(exception.cause)
49
+ end
50
+ result
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
- VERSION = '3.0.8'
4
+ VERSION = '3.7.1'
3
5
  end
data/lib/listen.rb CHANGED
@@ -1,23 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
4
+ require 'weakref'
2
5
  require 'listen/logger'
3
6
  require 'listen/listener'
4
7
 
5
- require 'listen/internals/thread_pool'
6
-
7
- # Always set up logging by default first time file is required
8
- #
9
- # NOTE: If you need to clear the logger completely, do so *after*
10
- # requiring this file. If you need to set a custom logger,
11
- # require the listen/logger file and set the logger before requiring
12
- # this file.
13
- Listen.setup_default_logger_if_unset
14
-
15
8
  # Won't print anything by default because of level - unless you've set
16
9
  # LISTEN_GEM_DEBUGGING or provided your own logger with a high enough level
17
- Listen::Logger.info "Listen loglevel set to: #{Listen.logger.level}"
18
- Listen::Logger.info "Listen version: #{Listen::VERSION}"
10
+ Listen.logger.info "Listen loglevel set to: #{Listen.logger.level}"
11
+ Listen.logger.info "Listen version: #{Listen::VERSION}"
19
12
 
20
13
  module Listen
14
+ @listeners = Queue.new
15
+
21
16
  class << self
22
17
  # Listens to file system modifications on a either single directory or
23
18
  # multiple directories.
@@ -32,24 +27,21 @@ module Listen
32
27
  # @return [Listen::Listener] the listener
33
28
  #
34
29
  def to(*args, &block)
35
- @listeners ||= []
36
30
  Listener.new(*args, &block).tap do |listener|
37
- @listeners << listener
31
+ @listeners.enq(WeakRef.new(listener))
38
32
  end
39
33
  end
40
34
 
41
35
  # This is used by the `listen` binary to handle Ctrl-C
42
36
  #
43
37
  def stop
44
- Internals::ThreadPool.stop
45
- @listeners ||= []
46
-
47
- # TODO: should use a mutex for this
48
- @listeners.each do |listener|
49
- # call stop to halt the main loop
50
- listener.stop
38
+ while (listener = @listeners.deq(true))
39
+ begin
40
+ listener.stop
41
+ rescue WeakRef::RefError
42
+ end
51
43
  end
52
- @listeners = nil
44
+ rescue ThreadError
53
45
  end
54
46
  end
55
47
  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.0.8
4
+ version: 3.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thibaud Guillaume-Gentil
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-18 00:00:00.000000000 Z
11
+ date: 2022-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb-fsevent
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.9'
19
+ version: '0.10'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 0.9.4
22
+ version: 0.10.3
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '0.9'
29
+ version: '0.10'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.9.4
32
+ version: 0.10.3
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: rb-inotify
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,7 @@ dependencies:
39
39
  version: '0.9'
40
40
  - - ">="
41
41
  - !ruby/object:Gem::Version
42
- version: 0.9.7
42
+ version: 0.9.10
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,21 +49,7 @@ dependencies:
49
49
  version: '0.9'
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
- version: 0.9.7
53
- - !ruby/object:Gem::Dependency
54
- name: bundler
55
- requirement: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: 1.3.5
60
- type: :development
61
- prerelease: false
62
- version_requirements: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: 1.3.5
52
+ version: 0.9.10
67
53
  description: The Listen gem listens to file modifications and notifies you about the
68
54
  changes. Works everywhere!
69
55
  email: thibaud@thibaud.gg
@@ -90,16 +76,17 @@ files:
90
76
  - lib/listen/change.rb
91
77
  - lib/listen/cli.rb
92
78
  - lib/listen/directory.rb
79
+ - lib/listen/error.rb
93
80
  - lib/listen/event/config.rb
94
81
  - lib/listen/event/loop.rb
95
82
  - lib/listen/event/processor.rb
96
83
  - lib/listen/event/queue.rb
97
84
  - lib/listen/file.rb
98
85
  - lib/listen/fsm.rb
99
- - lib/listen/internals/thread_pool.rb
100
86
  - lib/listen/listener.rb
101
87
  - lib/listen/listener/config.rb
102
88
  - lib/listen/logger.rb
89
+ - lib/listen/monotonic_time.rb
103
90
  - lib/listen/options.rb
104
91
  - lib/listen/queue_optimizer.rb
105
92
  - lib/listen/record.rb
@@ -107,11 +94,18 @@ files:
107
94
  - lib/listen/record/symlink_detector.rb
108
95
  - lib/listen/silencer.rb
109
96
  - lib/listen/silencer/controller.rb
97
+ - lib/listen/thread.rb
110
98
  - lib/listen/version.rb
111
99
  homepage: https://github.com/guard/listen
112
100
  licenses:
113
101
  - MIT
114
- metadata: {}
102
+ metadata:
103
+ allowed_push_host: https://rubygems.org
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.7.1
107
+ homepage_uri: https://github.com/guard/listen
108
+ source_code_uri: https://github.com/guard/listen/tree/v3.7.1
115
109
  post_install_message:
116
110
  rdoc_options: []
117
111
  require_paths:
@@ -120,17 +114,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
114
  requirements:
121
115
  - - ">="
122
116
  - !ruby/object:Gem::Version
123
- version: 1.9.3
117
+ version: 2.4.0
124
118
  required_rubygems_version: !ruby/object:Gem::Requirement
125
119
  requirements:
126
120
  - - ">="
127
121
  - !ruby/object:Gem::Version
128
122
  version: '0'
129
123
  requirements: []
130
- rubyforge_project:
131
- rubygems_version: 2.5.1
124
+ rubygems_version: 3.0.1
132
125
  signing_key:
133
126
  specification_version: 4
134
127
  summary: Listen to file modifications
135
128
  test_files: []
136
- has_rdoc:
@@ -1,29 +0,0 @@
1
- module Listen
2
- # @private api
3
- module Internals
4
- module ThreadPool
5
- def self.add(&block)
6
- Thread.new { block.call }.tap do |th|
7
- (@threads ||= Queue.new) << th
8
- end
9
- end
10
-
11
- def self.stop
12
- return unless @threads ||= nil
13
- return if @threads.empty? # return to avoid using possibly stubbed Queue
14
-
15
- killed = Queue.new
16
- # You can't kill a read on a descriptor in JRuby, so let's just
17
- # ignore running threads (listen rb-inotify waiting for disk activity
18
- # before closing) pray threads die faster than they are created...
19
- limit = RUBY_ENGINE == 'jruby' ? [1] : []
20
-
21
- killed << @threads.pop.kill until @threads.empty?
22
- until killed.empty?
23
- th = killed.pop
24
- th.join(*limit) unless th[:listen_blocking_read_thread]
25
- end
26
- end
27
- end
28
- end
29
- end