watchcat 0.5.2-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 +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +1 -1
- data/README.md +109 -1
- data/lib/watchcat/3.1/watchcat.so +0 -0
- data/lib/watchcat/3.2/watchcat.so +0 -0
- data/lib/watchcat/3.3/watchcat.so +0 -0
- data/lib/watchcat/3.4/watchcat.so +0 -0
- data/lib/watchcat/4.0/watchcat.so +0 -0
- data/lib/watchcat/event.rb +37 -0
- data/lib/watchcat/event_handler.rb +29 -0
- data/lib/watchcat/executor.rb +55 -7
- data/lib/watchcat/version.rb +1 -1
- data/lib/watchcat.rb +12 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b9d81fa2550b74f1fc51df8391f8b83f7bce7e93b180b6753da9fc247d1a67da
|
|
4
|
+
data.tar.gz: e7705470952ff1c3dd9f22191233ea9573bbc980a4fe583849e0f04366927637
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15686a635eee451410f2400c0039f76f924614db1c23a4fd780437659e98a9dc638add34549d2683c269c3bef64dda27e0b872327fbce5c2b9db6626621c224b
|
|
7
|
+
data.tar.gz: 27d15cdfb98d5f8eaece63725e34f3da1d39794eeb6ad77a4efb6d75f6026c715180933fff44cb78dfe7019d57184b53159e3ad448b025bf56c8fddb5b6a3476
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
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
|
+
|
|
1
11
|
## 0.5.2
|
|
2
12
|
|
|
3
13
|
* Support Ruby 4.0
|
data/Gemfile
CHANGED
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
|
-
|
|
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
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/watchcat/event.rb
CHANGED
|
@@ -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
|
data/lib/watchcat/executor.rb
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
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/version.rb
CHANGED
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
|
-
|
|
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.
|
|
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: 2026-
|
|
11
|
+
date: 2026-08-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: psych
|
|
@@ -164,6 +164,7 @@ files:
|
|
|
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
|