watchcat 0.5.1-aarch64-linux → 0.6.0-aarch64-linux

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cae51fb9f8c80b3e8d550da8200b24ba78f0a71ddce814b7aa51c81e4d1ef29
4
- data.tar.gz: 51a5907459c3dea9ef3b851991ce4fab82c77a6918edee8879b2314156f38848
3
+ metadata.gz: b9d81fa2550b74f1fc51df8391f8b83f7bce7e93b180b6753da9fc247d1a67da
4
+ data.tar.gz: e7705470952ff1c3dd9f22191233ea9573bbc980a4fe583849e0f04366927637
5
5
  SHA512:
6
- metadata.gz: aa8c40827819379a0846b8fa0241d8f638f43b588ec2cfe26bf35838d0cc012d7d07143b86e65911fa776110317013b87bc6f80d12400390a5befae9d2944f4f
7
- data.tar.gz: c2d5a02016c8d2d1bc74952db52372a79f70af4b2fe812cf9561b4acff42e6e81c73cee742ad6863838c23c952237b6553720e73947021d529a260609ac38b76
6
+ metadata.gz: 15686a635eee451410f2400c0039f76f924614db1c23a4fd780437659e98a9dc638add34549d2683c269c3bef64dda27e0b872327fbce5c2b9db6626621c224b
7
+ data.tar.gz: 27d15cdfb98d5f8eaece63725e34f3da1d39794eeb6ad77a4efb6d75f6026c715180933fff44cb78dfe7019d57184b53159e3ad448b025bf56c8fddb5b6a3476
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## Unreleased
2
+
3
+ ## 0.6.0
4
+
5
+ * Add `#watch`, `#unwatch`, and `#watched` to the watcher returned by `Watchcat.watch` for dynamically managing watched paths.
6
+ * Add `Watchcat::EventHandler` for handling events with callback methods.
7
+ * Add `Watchcat::Event#src_path` and `Watchcat::Event#dest_path` for move/rename events.
8
+ * Add `patterns`, `ignore_patterns`, and `ignore_directories` options to `Watchcat.watch`.
9
+ * Fix `Watchcat::Executor#stop` so that it is a no-op in a forked child process, and make it idempotent.
10
+
11
+ ## 0.5.2
12
+
13
+ * Support Ruby 4.0
14
+
1
15
  ## 0.5.1
2
16
 
3
17
  * Fix missing executable files
data/Gemfile CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in watchcat.gemspec
5
+ gem "fiddle", platforms: %i[ mri windows ]
6
6
  gemspec
data/README.md CHANGED
@@ -8,7 +8,10 @@ This gem uses [Notify](https://github.com/notify-rs/notify) to get notifications
8
8
 
9
9
  ## Platforms
10
10
 
11
- This gem supports Linux, macOS and Windows.
11
+ - Linux: inotify
12
+ - macOS: FSEvents
13
+ - Windows: ReadDirectoryChangesW
14
+ - All platforms: polling (via `force_polling` option)
12
15
 
13
16
  ## Installation
14
17
 
@@ -112,6 +115,111 @@ Watchcat.watch("/tmp/test", filters: { ignore_remove: true, ignore_access: true
112
115
  end
113
116
  ```
114
117
 
118
+ ### Pattern Options
119
+
120
+ You can use the `patterns`, `ignore_patterns`, and `ignore_directories` options to filter events by path or type, using `File.fnmatch` glob patterns:
121
+
122
+ | Name | Description | Default |
123
+ | --------------------- | ------------------------------------------------------------------------| ------- |
124
+ | **patterns** | Only dispatch events where at least one path matches one of the patterns | `[]` |
125
+ | **ignore_patterns** | Skip events where at least one path matches one of the patterns | `[]` |
126
+ | **ignore_directories**| Skip events for directories | `false` |
127
+
128
+ **CAUTION** For `access`/`modify`/`rename` events, notify doesn't tell whether the path is a file or a directory, so `ignore_directories` falls back to a live `File.directory?` check on the path (best-effort; e.g. it can't tell for a path that no longer exists).
129
+
130
+ Example usage:
131
+
132
+ ```ruby
133
+ Watchcat.watch(
134
+ "/tmp/test",
135
+ patterns: ["*.rb", "*.yml"],
136
+ ignore_patterns: ["*.tmp"],
137
+ ignore_directories: true
138
+ ) do |e|
139
+ pp e.paths, e.kind
140
+ end
141
+ ```
142
+
143
+ ### Move (Rename) Events
144
+
145
+ For move/rename events (`e.kind.modify?` and `e.kind.modify.rename?`),
146
+ `Watchcat::Event#src_path` and `#dest_path` give the old and new path without
147
+ having to interpret the raw `paths` array and `RenameMode` yourself:
148
+
149
+ ```ruby
150
+ Watchcat.watch("/tmp/test") do |e|
151
+ if e.kind.modify? && e.kind.modify.rename?
152
+ puts "moved: #{e.src_path} -> #{e.dest_path}"
153
+ end
154
+ end
155
+ ```
156
+
157
+ Platform differences affect what is available:
158
+
159
+ - **Linux**: a `both` event fires with both paths, so `src_path` and
160
+ `dest_path` are both set.
161
+ - **Windows**: `from` and `to` fire as separate events, each with only one
162
+ side set (`src_path` on `from`, `dest_path` on `to`).
163
+ - **macOS**: FSEvents can't distinguish old/new paths, so both `src_path` and
164
+ `dest_path` are `nil`.
165
+
166
+ For non-rename events, both accessors return `nil`.
167
+
168
+ ### Event Handler
169
+
170
+ Instead of writing a single block and branching on `event.kind` yourself, you
171
+ can subclass `Watchcat::EventHandler` and override just the callbacks you
172
+ need:
173
+
174
+ ```ruby
175
+ class MyHandler < Watchcat::EventHandler
176
+ def on_create(event)
177
+ puts "created: #{event.paths[0]}"
178
+ end
179
+
180
+ def on_rename(event)
181
+ puts "moved: #{event.src_path} -> #{event.dest_path}"
182
+ end
183
+ end
184
+
185
+ Watchcat.watch("/tmp/test", handler: MyHandler.new)
186
+ sleep
187
+ ```
188
+
189
+ Pass an instance via the `handler:` keyword instead of a block. `Watchcat::EventHandler` provides the following no-op callbacks to override:
190
+
191
+ | Callback | Description |
192
+ | --------------- | ----------------------------------------------------- |
193
+ | `on_any_event` | Called for every event, before the type-specific callback |
194
+ | `on_create` | Called for create events |
195
+ | `on_modify` | Called for modify events (excluding renames) |
196
+ | `on_remove` | Called for remove events |
197
+ | `on_rename` | Called for rename/move events (`src_path`/`dest_path` available) |
198
+ | `on_access` | Called for access events |
199
+
200
+ ### Dynamically Adding / Removing Paths
201
+
202
+ The watcher returned by `Watchcat.watch` can have paths added or removed while
203
+ it's running:
204
+
205
+ ```ruby
206
+ w = Watchcat.watch("/tmp/a") { |e| pp e.paths, e.kind }
207
+
208
+ w.watch("/tmp/b") # also watch /tmp/b
209
+ w.watch("/tmp/c", recursive: false) # non-recursive
210
+ w.unwatch("/tmp/a") # stop watching /tmp/a
211
+ w.watched # => current watched paths
212
+
213
+ sleep
214
+ ```
215
+
216
+ All watched paths share the single callback/handler passed to `Watchcat.watch`
217
+ (and the same `filters`/`patterns`/`debounce` settings). `recursive:` on `watch`
218
+ defaults to the value passed to `Watchcat.watch`. `watch` raises `ArgumentError`
219
+ immediately if a path does not exist. Applying `unwatch` is asynchronous, so
220
+ its exact timing (and behavior) can differ per platform, notably on macOS
221
+ (FSEvents). Both `watch` and `unwatch` accept a single path or an array of
222
+ paths.
115
223
 
116
224
  ## CLI
117
225
 
@@ -163,15 +271,14 @@ Each watch entry supports the following options:
163
271
 
164
272
  When specifying commands, you can use the following variables:
165
273
 
166
- | Variable | Description | Example |
167
- |---------------|------------------------------------------|------------------------|
168
- | {{file_path}} | Full path of the changed file | `/home/user/app/file.rb` |
169
- | {{file_dir}} | Directory containing the file | `/home/user/app` |
170
- | {{file_name}} | File name with extension | `file.rb` |
171
- | {{file_base}} | File name without extension | `file` |
172
- | {{file_ext}} | File extension | `.rb` |
173
-
174
-
274
+ | Variable | Description | Example |
275
+ |----------------|------------------------------------------|--------------------------|
276
+ | {{file_path}} | Full path of the changed file | `/home/user/app/file.rb` |
277
+ | {{file_dir}} | Directory containing the file | `/home/user/app` |
278
+ | {{file_name}} | File name with extension | `file.rb` |
279
+ | {{file_base}} | File name without extension | `file` |
280
+ | {{file_ext}} | File extension | `.rb` |
281
+ | {{event_type}} | Type of event | `create` |
175
282
 
176
283
  ## Contributing
177
284
 
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -37,6 +37,7 @@ module Watchcat
37
37
  .gsub("{{file_name}}", @file_name)
38
38
  .gsub("{{file_base}}", @file_base)
39
39
  .gsub("{{file_ext}}", @file_ext)
40
+ .gsub("{{event_type}}", @event.kind.event_type)
40
41
  end
41
42
  end
42
43
  end
@@ -14,8 +14,45 @@ module Watchcat
14
14
  { paths: @paths, event: @event }
15
15
  end
16
16
 
17
+ def directory?
18
+ if kind.create?
19
+ kind.create.folder?
20
+ elsif kind.remove?
21
+ kind.remove.folder?
22
+ elsif kind.any?
23
+ kind.any.folder?
24
+ else
25
+ File.directory?(@paths.first)
26
+ end
27
+ rescue
28
+ false
29
+ end
30
+
31
+ def src_path
32
+ return nil unless rename_event?
33
+
34
+ rename = kind.modify.rename
35
+ return @paths[0] if rename.both? || rename.from?
36
+
37
+ nil
38
+ end
39
+
40
+ def dest_path
41
+ return nil unless rename_event?
42
+
43
+ rename = kind.modify.rename
44
+ return @paths[1] if rename.both?
45
+ return @paths[0] if rename.to?
46
+
47
+ nil
48
+ end
49
+
17
50
  private
18
51
 
52
+ def rename_event?
53
+ kind.modify? && kind.modify.rename?
54
+ end
55
+
19
56
  def build_kind(kinds)
20
57
  @kind = Watchcat::EventKind.new
21
58
  @event = kinds.shift
@@ -0,0 +1,29 @@
1
+ module Watchcat
2
+ class EventHandler
3
+ def dispatch(event)
4
+ on_any_event(event)
5
+
6
+ kind = event.kind
7
+ if kind.create?
8
+ on_create(event)
9
+ elsif kind.remove?
10
+ on_remove(event)
11
+ elsif kind.modify?
12
+ if kind.modify.rename?
13
+ on_rename(event)
14
+ else
15
+ on_modify(event)
16
+ end
17
+ elsif kind.access?
18
+ on_access(event)
19
+ end
20
+ end
21
+
22
+ def on_any_event(event); end
23
+ def on_create(event); end
24
+ def on_modify(event); end
25
+ def on_remove(event); end
26
+ def on_rename(event); end
27
+ def on_access(event); end
28
+ end
29
+ end
@@ -2,7 +2,7 @@ require_relative "event"
2
2
 
3
3
  module Watchcat
4
4
  class Executor
5
- def initialize(paths, recursive:, force_polling:, poll_interval:, filters:, debounce:, block:)
5
+ def initialize(paths, recursive:, force_polling:, poll_interval:, filters:, debounce:, block:, patterns: [], ignore_patterns: [], ignore_directories: false)
6
6
  @paths = paths
7
7
  @recursive = recursive
8
8
  @force_polling = force_polling
@@ -10,13 +10,20 @@ module Watchcat
10
10
  @filters = filters || {}
11
11
  @debounce = debounce
12
12
  @debouncer = Debouncer.new if @debounce > 0
13
+ @patterns = Array(patterns)
14
+ @ignore_patterns = Array(ignore_patterns)
15
+ @ignore_directories = ignore_directories
13
16
  @block = block
14
17
  @watcher = Watchcat::Watcher.new
15
18
  @watch_thread = nil
16
19
  @stop_requested = false
20
+ @owner_pid = nil
21
+ @stopped = false
17
22
  end
18
23
 
19
24
  def start
25
+ @owner_pid = Process.pid
26
+
20
27
  # Always start watching in a background thread to avoid blocking
21
28
  @watch_thread = Thread.new do
22
29
  Thread.current.name = "watchcat-watcher"
@@ -29,6 +36,15 @@ module Watchcat
29
36
  end
30
37
 
31
38
  def stop
39
+ # A forked child inherits the at_exit hook without inheriting the watcher
40
+ # thread, so it would call #close -- which sends on a channel, and a send
41
+ # can allocate. If the watcher thread held the allocator lock at fork
42
+ # time, that lock is never released in the child.
43
+ return if @owner_pid != Process.pid
44
+
45
+ return if @stopped
46
+ @stopped = true
47
+
32
48
  @stop_requested = true
33
49
  @watcher.close
34
50
  if @watch_thread && @watch_thread.alive?
@@ -36,6 +52,25 @@ module Watchcat
36
52
  end
37
53
  end
38
54
 
55
+ def watch(paths, recursive: @recursive)
56
+ paths = Array(paths)
57
+ paths.each { |p| raise ArgumentError, "path does not exist: #{p}" unless File.exist?(p) }
58
+ @watcher.add(paths, recursive: recursive)
59
+ @paths |= paths
60
+ self
61
+ end
62
+
63
+ def unwatch(paths)
64
+ paths = Array(paths)
65
+ @watcher.unwatch(paths)
66
+ @paths -= paths
67
+ self
68
+ end
69
+
70
+ def watched
71
+ @paths.dup
72
+ end
73
+
39
74
  private
40
75
 
41
76
  def start_watching
@@ -49,18 +84,31 @@ module Watchcat
49
84
  ignore_create: @filters[:ignore_create],
50
85
  ignore_modify: @filters[:ignore_modify]
51
86
  ) do |kind, paths, raw_kind|
52
- break if @stop_requested
87
+ next if @stop_requested
88
+
89
+ event = Watchcat::Event.new(kind, paths, raw_kind)
90
+ next unless dispatch?(event)
53
91
 
54
92
  if @debounce > 0 && paths.size == 1
55
- @debouncer.debounce(paths[0], @debounce) do
56
- event = Watchcat::Event.new(kind, paths, raw_kind)
57
- @block.call(event)
58
- end
93
+ @debouncer.debounce(paths[0], @debounce) { @block.call(event) }
59
94
  else
60
- event = Watchcat::Event.new(kind, paths, raw_kind)
61
95
  @block.call(event)
62
96
  end
63
97
  end
64
98
  end
99
+
100
+ def dispatch?(event)
101
+ return false if @ignore_directories && event.directory?
102
+ return false if @patterns.any? && !matches_any_pattern?(event.paths, @patterns)
103
+ return false if @ignore_patterns.any? && matches_any_pattern?(event.paths, @ignore_patterns)
104
+
105
+ true
106
+ end
107
+
108
+ def matches_any_pattern?(paths, patterns)
109
+ paths.any? do |path|
110
+ patterns.any? { |pattern| File.fnmatch?(pattern, File.basename(path)) || File.fnmatch?(pattern, path) }
111
+ end
112
+ end
65
113
  end
66
114
  end
data/lib/watchcat/kind.rb CHANGED
@@ -27,6 +27,14 @@ module Watchcat
27
27
  def any?
28
28
  !@any.nil?
29
29
  end
30
+
31
+ def event_type
32
+ return "create" if create?
33
+ return "modify" if modify?
34
+ return "remove" if remove?
35
+ return "access" if access?
36
+ "unknown"
37
+ end
30
38
  end
31
39
 
32
40
  class AccessKind
@@ -1,3 +1,3 @@
1
1
  module Watchcat
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/watchcat.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require_relative "watchcat/version"
2
2
  require_relative "watchcat/executor"
3
3
  require_relative "watchcat/debouncer"
4
+ require_relative "watchcat/event_handler"
4
5
 
5
6
  begin
6
7
  require "watchcat/#{RUBY_VERSION.to_f}/watchcat"
@@ -17,8 +18,15 @@ module Watchcat
17
18
  poll_interval: nil,
18
19
  filters: {},
19
20
  debounce: -1,
21
+ patterns: [],
22
+ ignore_patterns: [],
23
+ ignore_directories: false,
24
+ handler: nil,
20
25
  &block
21
26
  )
27
+ callback = block || (handler && handler.method(:dispatch))
28
+ raise ArgumentError, "must provide a block or a handler:" unless callback
29
+
22
30
  w =
23
31
  Watchcat::Executor.new(
24
32
  Array(paths),
@@ -27,7 +35,10 @@ module Watchcat
27
35
  poll_interval: poll_interval,
28
36
  filters: filters,
29
37
  debounce: debounce,
30
- block: block
38
+ patterns: patterns,
39
+ ignore_patterns: ignore_patterns,
40
+ ignore_directories: ignore_directories,
41
+ block: callback
31
42
  )
32
43
  w.start
33
44
  w
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: watchcat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Yuji Yaginuma
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-11 00:00:00.000000000 Z
11
+ date: 2026-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: psych
@@ -146,7 +146,6 @@ extra_rdoc_files: []
146
146
  files:
147
147
  - CHANGELOG.md
148
148
  - Gemfile
149
- - Gemfile.lock
150
149
  - LICENSE.txt
151
150
  - README.md
152
151
  - Rakefile
@@ -158,12 +157,14 @@ files:
158
157
  - lib/watchcat/3.2/watchcat.so
159
158
  - lib/watchcat/3.3/watchcat.so
160
159
  - lib/watchcat/3.4/watchcat.so
160
+ - lib/watchcat/4.0/watchcat.so
161
161
  - lib/watchcat/cli.rb
162
162
  - lib/watchcat/cli/action_executor.rb
163
163
  - lib/watchcat/cli/config.rb
164
164
  - lib/watchcat/cli/watcher.rb
165
165
  - lib/watchcat/debouncer.rb
166
166
  - lib/watchcat/event.rb
167
+ - lib/watchcat/event_handler.rb
167
168
  - lib/watchcat/executor.rb
168
169
  - lib/watchcat/kind.rb
169
170
  - lib/watchcat/version.rb
@@ -184,7 +185,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
184
185
  version: '3.1'
185
186
  - - "<"
186
187
  - !ruby/object:Gem::Version
187
- version: 3.5.dev
188
+ version: 4.1.dev
188
189
  required_rubygems_version: !ruby/object:Gem::Requirement
189
190
  requirements:
190
191
  - - ">="
data/Gemfile.lock DELETED
@@ -1,88 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- watchcat (0.5.1)
5
- psych
6
- rb_sys
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- cgi (0.5.0)
12
- date (3.4.1)
13
- debug (1.11.0)
14
- irb (~> 1.10)
15
- reline (>= 0.3.8)
16
- erb (4.0.4)
17
- cgi (>= 0.3.3)
18
- ffi (1.17.1-arm64-darwin)
19
- ffi (1.17.1-x64-mingw-ucrt)
20
- ffi (1.17.1-x86_64-darwin)
21
- ffi (1.17.1-x86_64-linux-gnu)
22
- io-console (0.8.0)
23
- irb (1.15.2)
24
- pp (>= 0.6.0)
25
- rdoc (>= 4.0.0)
26
- reline (>= 0.4.2)
27
- listen (3.9.0)
28
- rb-fsevent (~> 0.10, >= 0.10.3)
29
- rb-inotify (~> 0.9, >= 0.9.10)
30
- minitest (5.25.5)
31
- minitest-fail-fast (0.1.0)
32
- minitest (~> 5)
33
- minitest-retry (0.2.5)
34
- minitest (>= 5.0)
35
- nokogiri (1.18.1-arm64-darwin)
36
- racc (~> 1.4)
37
- nokogiri (1.18.1-x64-mingw-ucrt)
38
- racc (~> 1.4)
39
- nokogiri (1.18.1-x86_64-darwin)
40
- racc (~> 1.4)
41
- nokogiri (1.18.1-x86_64-linux-gnu)
42
- racc (~> 1.4)
43
- pp (0.6.2)
44
- prettyprint
45
- prettyprint (0.2.0)
46
- psych (5.2.6)
47
- date
48
- stringio
49
- racc (1.8.1)
50
- rake (13.3.0)
51
- rake-compiler (1.3.0)
52
- rake
53
- rake-compiler-dock (1.9.1)
54
- rb-fsevent (0.11.2)
55
- rb-inotify (0.11.1)
56
- ffi (~> 1.0)
57
- rb_sys (0.9.117)
58
- rake-compiler-dock (= 1.9.1)
59
- rdoc (6.14.1)
60
- erb
61
- psych (>= 4.0.0)
62
- reline (0.6.1)
63
- io-console (~> 0.5)
64
- ruby_memcheck (3.0.1)
65
- nokogiri
66
- stringio (3.1.7)
67
-
68
- PLATFORMS
69
- arm64-darwin-22
70
- arm64-darwin-23
71
- x64-mingw-ucrt
72
- x86_64-darwin-19
73
- x86_64-darwin-20
74
- x86_64-linux
75
-
76
- DEPENDENCIES
77
- debug
78
- listen
79
- minitest
80
- minitest-fail-fast
81
- minitest-retry
82
- rake
83
- rake-compiler
84
- ruby_memcheck
85
- watchcat!
86
-
87
- BUNDLED WITH
88
- 2.6.2