watchcat 0.2.1-arm64-darwin → 0.5.0-arm64-darwin
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 +13 -0
- data/Gemfile.lock +41 -23
- data/README.md +83 -4
- data/cli_example.yml +14 -0
- data/exe/watchcat +14 -0
- data/lib/watchcat/3.1/watchcat.bundle +0 -0
- data/lib/watchcat/3.2/watchcat.bundle +0 -0
- data/lib/watchcat/3.3/watchcat.bundle +0 -0
- data/lib/watchcat/3.4/watchcat.bundle +0 -0
- data/lib/watchcat/cli/action_executor.rb +43 -0
- data/lib/watchcat/cli/config.rb +72 -0
- data/lib/watchcat/cli/watcher.rb +85 -0
- data/lib/watchcat/cli.rb +52 -0
- data/lib/watchcat/debouncer.rb +41 -0
- data/lib/watchcat/executor.rb +35 -36
- data/lib/watchcat/version.rb +1 -1
- data/lib/watchcat.rb +3 -4
- metadata +30 -10
- data/lib/watchcat/3.0/watchcat.bundle +0 -0
- data/lib/watchcat/client.rb +0 -26
- data/lib/watchcat/server.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85e38ea68cf289319fd7692a8878f002b12053abe1123f3086b42cdb8a99c9e2
|
|
4
|
+
data.tar.gz: 7bf351ab7eca1ea5597b21584c8fb74314e20827c127b504919b18f3dab61105
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d78990ddc6006f6e6690af7f6aa8421a1898335f9a9d4efe25f4daeca7012111a59dd8730911249e18562cf3f26a35f0ef768b47d8b33816afd1263afac73ff7
|
|
7
|
+
data.tar.gz: 2118803afbbd69369a31b8b672d51ec98288e4e069c9fbb06ea50b7367357f25a7c5087bc53dbdd2cf439048fdfea6673ef4eb888fc0360bec82c1c2eeeb3b00
|
data/CHANGELOG.md
ADDED
data/Gemfile.lock
CHANGED
|
@@ -1,57 +1,74 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
watchcat (0.
|
|
5
|
-
|
|
4
|
+
watchcat (0.5.0)
|
|
5
|
+
psych
|
|
6
6
|
rb_sys
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: https://rubygems.org/
|
|
10
10
|
specs:
|
|
11
|
-
|
|
11
|
+
cgi (0.5.0)
|
|
12
|
+
date (3.4.1)
|
|
13
|
+
debug (1.11.0)
|
|
12
14
|
irb (~> 1.10)
|
|
13
15
|
reline (>= 0.3.8)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
ffi (1.17.
|
|
17
|
-
ffi (1.17.
|
|
18
|
-
|
|
19
|
-
|
|
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)
|
|
20
25
|
rdoc (>= 4.0.0)
|
|
21
26
|
reline (>= 0.4.2)
|
|
22
27
|
listen (3.9.0)
|
|
23
28
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
24
29
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
25
|
-
minitest (5.25.
|
|
26
|
-
minitest-
|
|
30
|
+
minitest (5.25.5)
|
|
31
|
+
minitest-fail-fast (0.1.0)
|
|
32
|
+
minitest (~> 5)
|
|
33
|
+
minitest-retry (0.2.5)
|
|
27
34
|
minitest (>= 5.0)
|
|
28
|
-
nokogiri (1.
|
|
35
|
+
nokogiri (1.18.1-arm64-darwin)
|
|
29
36
|
racc (~> 1.4)
|
|
30
|
-
nokogiri (1.
|
|
37
|
+
nokogiri (1.18.1-x64-mingw-ucrt)
|
|
31
38
|
racc (~> 1.4)
|
|
32
|
-
nokogiri (1.
|
|
39
|
+
nokogiri (1.18.1-x86_64-darwin)
|
|
33
40
|
racc (~> 1.4)
|
|
34
|
-
|
|
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
|
|
35
48
|
stringio
|
|
36
49
|
racc (1.8.1)
|
|
37
|
-
rake (13.
|
|
38
|
-
rake-compiler (1.
|
|
50
|
+
rake (13.3.0)
|
|
51
|
+
rake-compiler (1.3.0)
|
|
39
52
|
rake
|
|
53
|
+
rake-compiler-dock (1.9.1)
|
|
40
54
|
rb-fsevent (0.11.2)
|
|
41
55
|
rb-inotify (0.11.1)
|
|
42
56
|
ffi (~> 1.0)
|
|
43
|
-
rb_sys (0.9.
|
|
44
|
-
|
|
57
|
+
rb_sys (0.9.117)
|
|
58
|
+
rake-compiler-dock (= 1.9.1)
|
|
59
|
+
rdoc (6.14.1)
|
|
60
|
+
erb
|
|
45
61
|
psych (>= 4.0.0)
|
|
46
|
-
reline (0.
|
|
62
|
+
reline (0.6.1)
|
|
47
63
|
io-console (~> 0.5)
|
|
48
|
-
ruby_memcheck (3.0.
|
|
64
|
+
ruby_memcheck (3.0.1)
|
|
49
65
|
nokogiri
|
|
50
|
-
stringio (3.1.
|
|
66
|
+
stringio (3.1.7)
|
|
51
67
|
|
|
52
68
|
PLATFORMS
|
|
53
69
|
arm64-darwin-22
|
|
54
70
|
arm64-darwin-23
|
|
71
|
+
x64-mingw-ucrt
|
|
55
72
|
x86_64-darwin-19
|
|
56
73
|
x86_64-darwin-20
|
|
57
74
|
x86_64-linux
|
|
@@ -60,6 +77,7 @@ DEPENDENCIES
|
|
|
60
77
|
debug
|
|
61
78
|
listen
|
|
62
79
|
minitest
|
|
80
|
+
minitest-fail-fast
|
|
63
81
|
minitest-retry
|
|
64
82
|
rake
|
|
65
83
|
rake-compiler
|
|
@@ -67,4 +85,4 @@ DEPENDENCIES
|
|
|
67
85
|
watchcat!
|
|
68
86
|
|
|
69
87
|
BUNDLED WITH
|
|
70
|
-
2.
|
|
88
|
+
2.6.2
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ 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
|
|
11
|
+
This gem supports Linux, macOS and Windows.
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -27,7 +27,7 @@ Please specify a filename or directory and callback block to `Watchcat.watch`. T
|
|
|
27
27
|
```ruby
|
|
28
28
|
require "watchcat"
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Watchcat.watch("/tmp/test") do |e|
|
|
31
31
|
pp e.paths, e.kind
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -58,7 +58,7 @@ You can know what event is happened with `Watchcat::EventKind`. For example, wha
|
|
|
58
58
|
```ruby
|
|
59
59
|
require "watchcat"
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Watchcat.watch("/tmp/target") do |e|
|
|
62
62
|
if e.kind.create?
|
|
63
63
|
if e.kind.create.file?
|
|
64
64
|
puts "'#{e.paths[0]}'(File) is added."
|
|
@@ -84,7 +84,6 @@ sleep
|
|
|
84
84
|
|
|
85
85
|
**CAUTION** The `watchcat` doesn't normalize the events. So the result might change per the platform.
|
|
86
86
|
|
|
87
|
-
|
|
88
87
|
### Options
|
|
89
88
|
|
|
90
89
|
| Name | Description | Default |
|
|
@@ -94,6 +93,86 @@ sleep
|
|
|
94
93
|
| **debounce** | Debounce events for the same file. | `-1` |
|
|
95
94
|
|
|
96
95
|
|
|
96
|
+
### Filters Option
|
|
97
|
+
|
|
98
|
+
You can use the `filters` option to ignore specific event types:
|
|
99
|
+
|
|
100
|
+
| Key | Description |
|
|
101
|
+
|-----------------|-----------------------------------|
|
|
102
|
+
| ignore_remove | Ignore remove (delete) events |
|
|
103
|
+
| ignore_access | Ignore access events |
|
|
104
|
+
| ignore_create | Ignore create events |
|
|
105
|
+
| ignore_modify | Ignore modify events |
|
|
106
|
+
|
|
107
|
+
Example usage:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
Watchcat.watch("/tmp/test", filters: { ignore_remove: true, ignore_access: true }) do |e|
|
|
111
|
+
pp e.paths, e.kind
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## CLI
|
|
117
|
+
|
|
118
|
+
`watchcat` comes with a command-line interface that allows you to watch files and execute commands when changes occur.
|
|
119
|
+
|
|
120
|
+
### Usage
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
# Run watchcat with a config file
|
|
124
|
+
$ watchcat -C config.yml
|
|
125
|
+
|
|
126
|
+
# Generate a template config file
|
|
127
|
+
$ watchcat --init config.yml
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Configuration File
|
|
131
|
+
|
|
132
|
+
The configuration file should be in YAML format. Here's an example:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
watches:
|
|
136
|
+
- path: "./lib"
|
|
137
|
+
recursive: true
|
|
138
|
+
debounce: 300
|
|
139
|
+
filters:
|
|
140
|
+
ignore_access: true
|
|
141
|
+
patterns:
|
|
142
|
+
- "*.rb"
|
|
143
|
+
- "*.yml"
|
|
144
|
+
actions:
|
|
145
|
+
- command: "echo 'Ruby/YAML file changed: {{file_name}}'"
|
|
146
|
+
- command: "rubocop {{file_path}}"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Configuration Options
|
|
150
|
+
|
|
151
|
+
Each watch entry supports the following options:
|
|
152
|
+
|
|
153
|
+
| Option | Description | Default |
|
|
154
|
+
|-------------|--------------------------------------------------------|---------|
|
|
155
|
+
| path | Directory or file path to watch (required) | - |
|
|
156
|
+
| recursive | Watch a directory recursively or not | `true` |
|
|
157
|
+
| debounce | Debounce events for the same file (in milliseconds) | `-1` |
|
|
158
|
+
| filters | Event filters (same as library filters option) | `{}` |
|
|
159
|
+
| patterns | File patterns to match (using File.fnmatch) | `[]` |
|
|
160
|
+
| actions | Commands to execute when files change | `[]` |
|
|
161
|
+
|
|
162
|
+
### Available Variables for Commands
|
|
163
|
+
|
|
164
|
+
When specifying commands, you can use the following variables:
|
|
165
|
+
|
|
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
|
+
|
|
175
|
+
|
|
97
176
|
## Contributing
|
|
98
177
|
|
|
99
178
|
Bug reports and pull requests are welcome on GitHub at https://github.com/y-yagi/watchcat.
|
data/cli_example.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Watchcat Configuration File
|
|
2
|
+
|
|
3
|
+
watches:
|
|
4
|
+
- path: "./lib"
|
|
5
|
+
recursive: true
|
|
6
|
+
debounce: 300
|
|
7
|
+
filters:
|
|
8
|
+
ignore_access: true
|
|
9
|
+
patterns:
|
|
10
|
+
- "*.rb"
|
|
11
|
+
- "*.yml"
|
|
12
|
+
actions:
|
|
13
|
+
- command: "echo 'Ruby/YAML file changed: {{file_name}}'"
|
|
14
|
+
- command: "rubocop {{file_path}}"
|
data/exe/watchcat
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Watchcat
|
|
2
|
+
module CLI
|
|
3
|
+
class ActionExecutor
|
|
4
|
+
def initialize(file_path, event)
|
|
5
|
+
@file_path = file_path
|
|
6
|
+
@event = event
|
|
7
|
+
@file_dir = File.dirname(file_path)
|
|
8
|
+
@file_name = File.basename(file_path)
|
|
9
|
+
@file_ext = File.extname(file_path)
|
|
10
|
+
@file_base = File.basename(file_path, @file_ext)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def execute(action)
|
|
14
|
+
execute_command(action)
|
|
15
|
+
rescue => e
|
|
16
|
+
puts "Error executing action #{action}: #{e.message}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def execute_command(action)
|
|
22
|
+
command = substitute_variables(action["command"])
|
|
23
|
+
puts "Executing: #{command}"
|
|
24
|
+
|
|
25
|
+
success = system(command)
|
|
26
|
+
unless success
|
|
27
|
+
puts "Command failed with exit code: #{$?.exitstatus}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def substitute_variables(template)
|
|
32
|
+
return template unless template.is_a?(String)
|
|
33
|
+
|
|
34
|
+
template
|
|
35
|
+
.gsub("{{file_path}}", @file_path)
|
|
36
|
+
.gsub("{{file_dir}}", @file_dir)
|
|
37
|
+
.gsub("{{file_name}}", @file_name)
|
|
38
|
+
.gsub("{{file_base}}", @file_base)
|
|
39
|
+
.gsub("{{file_ext}}", @file_ext)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "psych"
|
|
2
|
+
|
|
3
|
+
module Watchcat
|
|
4
|
+
module CLI
|
|
5
|
+
class Config
|
|
6
|
+
attr_reader :watches
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@watches = parse_watches(data["watches"] || [])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.load(file_path)
|
|
13
|
+
unless File.exist?(file_path)
|
|
14
|
+
raise Error, "Configuration file not found: #{file_path}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
data = Psych.load_file(file_path)
|
|
19
|
+
new(data)
|
|
20
|
+
rescue Psych::SyntaxError => e
|
|
21
|
+
raise Error, "Invalid YAML syntax in #{file_path}: #{e.message}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.generate_template(file_path)
|
|
26
|
+
template = <<~YAML
|
|
27
|
+
# Watchcat Configuration File
|
|
28
|
+
|
|
29
|
+
watches:
|
|
30
|
+
- path: "./src"
|
|
31
|
+
recursive: true
|
|
32
|
+
debounce: 300
|
|
33
|
+
patterns:
|
|
34
|
+
- "*.js"
|
|
35
|
+
- "*.ts"
|
|
36
|
+
- "*.css"
|
|
37
|
+
actions:
|
|
38
|
+
- command: "echo 'File changed: {{file_path}}'"
|
|
39
|
+
|
|
40
|
+
- path: "./docs"
|
|
41
|
+
recursive: true
|
|
42
|
+
patterns:
|
|
43
|
+
- "*.md"
|
|
44
|
+
actions:
|
|
45
|
+
- command: "echo 'Documentation updated: {{file_name}}'"
|
|
46
|
+
YAML
|
|
47
|
+
|
|
48
|
+
if File.exist?(file_path)
|
|
49
|
+
raise Error, "File already exists: #{file_path}. Won't overwrite."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
File.write(file_path, template)
|
|
53
|
+
puts "Config template generated at #{file_path}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def parse_watches(watches_data)
|
|
59
|
+
watches_data.map do |watch_config|
|
|
60
|
+
{
|
|
61
|
+
path: watch_config["path"],
|
|
62
|
+
recursive: watch_config.fetch("recursive", true),
|
|
63
|
+
patterns: watch_config["patterns"] || [],
|
|
64
|
+
actions: watch_config["actions"] || [],
|
|
65
|
+
debounce: watch_config.fetch("debounce", -1),
|
|
66
|
+
filters: watch_config["filters"]&.transform_keys(&:to_sym) || {},
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Watchcat
|
|
2
|
+
module CLI
|
|
3
|
+
class Watcher
|
|
4
|
+
def initialize(config)
|
|
5
|
+
@config = config
|
|
6
|
+
@watchers = []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def start
|
|
10
|
+
puts "Starting Watchcat file watcher..."
|
|
11
|
+
|
|
12
|
+
@config.watches.each do |watch_config|
|
|
13
|
+
start_watching_path(watch_config)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
puts "Watchcat is now watching for file changes. Press Ctrl+C to stop."
|
|
17
|
+
|
|
18
|
+
# Keep the main thread alive
|
|
19
|
+
begin
|
|
20
|
+
sleep
|
|
21
|
+
rescue Interrupt
|
|
22
|
+
puts "\nStopping Watchcat..."
|
|
23
|
+
stop
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stop
|
|
28
|
+
@watchers.each(&:stop)
|
|
29
|
+
@watchers.clear
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def start_watching_path(watch_config)
|
|
35
|
+
path = watch_config[:path]
|
|
36
|
+
|
|
37
|
+
unless File.exist?(path)
|
|
38
|
+
puts "Warning: Path does not exist: #{path}"
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
puts "Watching: #{path} (recursive: #{watch_config[:recursive]}, debounce: #{watch_config[:debounce]}ms)"
|
|
43
|
+
|
|
44
|
+
watcher = Watchcat.watch(
|
|
45
|
+
path,
|
|
46
|
+
recursive: watch_config[:recursive],
|
|
47
|
+
filters: watch_config[:filters],
|
|
48
|
+
debounce: watch_config[:debounce],
|
|
49
|
+
) do |event|
|
|
50
|
+
handle_file_event(event, watch_config)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@watchers << watcher
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def handle_file_event(event, watch_config)
|
|
57
|
+
return if event.kind.access?
|
|
58
|
+
|
|
59
|
+
event.paths.each do |file_path|
|
|
60
|
+
next unless should_process_file?(file_path, watch_config[:patterns])
|
|
61
|
+
|
|
62
|
+
puts "File changed: #{file_path}"
|
|
63
|
+
execute_actions(file_path, event, watch_config[:actions])
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def should_process_file?(file_path, patterns)
|
|
68
|
+
return true if patterns.empty?
|
|
69
|
+
|
|
70
|
+
patterns.any? do |pattern|
|
|
71
|
+
File.fnmatch?(pattern, File.basename(file_path)) ||
|
|
72
|
+
File.fnmatch?(pattern, file_path)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def execute_actions(file_path, event, actions)
|
|
77
|
+
executor = ActionExecutor.new(file_path, event)
|
|
78
|
+
|
|
79
|
+
actions.each do |action|
|
|
80
|
+
executor.execute(action)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/watchcat/cli.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require_relative "cli/watcher"
|
|
3
|
+
require_relative "cli/config"
|
|
4
|
+
require_relative "cli/action_executor"
|
|
5
|
+
|
|
6
|
+
module Watchcat
|
|
7
|
+
module CLI
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
class << self
|
|
10
|
+
def start(argv)
|
|
11
|
+
options = parse(argv)
|
|
12
|
+
|
|
13
|
+
if options[:init]
|
|
14
|
+
Config.generate_template(options[:init])
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
config = Config.load(options[:config])
|
|
19
|
+
watcher = Watcher.new(config)
|
|
20
|
+
watcher.start
|
|
21
|
+
rescue => e
|
|
22
|
+
raise Error, "Failed to start Watchcat: #{e.message}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parse(argv)
|
|
26
|
+
options = { config: 'watchcat.yml' }
|
|
27
|
+
OptionParser.new do |opts|
|
|
28
|
+
opts.banner = "Usage: watchcat [options]"
|
|
29
|
+
|
|
30
|
+
opts.on("-C", "--config PATH", "Path to the config file. Default is 'watchcat.yml'.") do |v|
|
|
31
|
+
options[:config] = v
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opts.on("--init PATH", "Generate a template config file at the specified path") do |v|
|
|
35
|
+
options[:init] = v
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
opts.on("-h", "--help", "Show this help message") do
|
|
39
|
+
puts opts
|
|
40
|
+
exit
|
|
41
|
+
end
|
|
42
|
+
end.parse!(argv)
|
|
43
|
+
|
|
44
|
+
if !options[:init] && options[:config].nil?
|
|
45
|
+
raise OptionParser::MissingArgument.new("-C")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
options
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Watchcat
|
|
2
|
+
class Debouncer
|
|
3
|
+
def initialize
|
|
4
|
+
@timers = {}
|
|
5
|
+
@mutex = Mutex.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def debounce(key, delay_ms, &block)
|
|
9
|
+
@mutex.synchronize do
|
|
10
|
+
# Cancel existing timer for this key
|
|
11
|
+
if @timers[key]
|
|
12
|
+
@timers[key].kill
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Create new timer
|
|
16
|
+
@timers[key] = Thread.new do
|
|
17
|
+
sleep(delay_ms / 1000.0) # Convert ms to seconds
|
|
18
|
+
|
|
19
|
+
@mutex.synchronize do
|
|
20
|
+
@timers.delete(key)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
block.call
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def clear
|
|
29
|
+
@mutex.synchronize do
|
|
30
|
+
@timers.each_value(&:kill)
|
|
31
|
+
@timers.clear
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def pending_count
|
|
36
|
+
@mutex.synchronize do
|
|
37
|
+
@timers.size
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/watchcat/executor.rb
CHANGED
|
@@ -1,67 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
require "drb/unix"
|
|
3
|
-
require_relative "server"
|
|
4
|
-
require_relative "client"
|
|
1
|
+
require_relative "event"
|
|
5
2
|
|
|
6
3
|
module Watchcat
|
|
7
4
|
class Executor
|
|
8
|
-
def initialize(paths, recursive:, force_polling:, poll_interval:,
|
|
9
|
-
@service = nil
|
|
10
|
-
@child_pid = nil
|
|
5
|
+
def initialize(paths, recursive:, force_polling:, poll_interval:, filters:, debounce:, block:)
|
|
11
6
|
@paths = paths
|
|
12
7
|
@recursive = recursive
|
|
13
8
|
@force_polling = force_polling
|
|
14
9
|
@poll_interval = poll_interval
|
|
15
|
-
@
|
|
16
|
-
@ignore_remove = ignore_remove
|
|
10
|
+
@filters = filters || {}
|
|
17
11
|
@debounce = debounce
|
|
12
|
+
@debouncer = Debouncer.new if @debounce > 0
|
|
18
13
|
@block = block
|
|
19
14
|
@watcher = Watchcat::Watcher.new
|
|
15
|
+
@watch_thread = nil
|
|
16
|
+
@stop_requested = false
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
def start
|
|
23
|
-
|
|
24
|
-
@
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@child_pid = fork do
|
|
29
|
-
client = build_client unless @wait_until_startup
|
|
30
|
-
Process.setproctitle("watchcat: watcher")
|
|
31
|
-
client.run
|
|
20
|
+
# Always start watching in a background thread to avoid blocking
|
|
21
|
+
@watch_thread = Thread.new do
|
|
22
|
+
Thread.current.name = "watchcat-watcher"
|
|
23
|
+
start_watching
|
|
32
24
|
end
|
|
33
25
|
|
|
34
|
-
main = Process.pid
|
|
35
26
|
at_exit do
|
|
36
|
-
|
|
37
|
-
stop if Process.pid == main
|
|
38
|
-
exit @exit_status if @exit_status
|
|
27
|
+
stop
|
|
39
28
|
end
|
|
40
29
|
end
|
|
41
30
|
|
|
42
31
|
def stop
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# NOTE: We can ignore this error because there process is already dead.
|
|
32
|
+
@stop_requested = true
|
|
33
|
+
@watcher.close
|
|
34
|
+
if @watch_thread && @watch_thread.alive?
|
|
35
|
+
@watch_thread.join(1) # Wait up to 1 second for thread to finish
|
|
48
36
|
end
|
|
49
|
-
@service.stop_service
|
|
50
37
|
end
|
|
51
38
|
|
|
52
39
|
private
|
|
53
40
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
@
|
|
57
|
-
watcher: @watcher,
|
|
58
|
-
paths: @paths,
|
|
41
|
+
def start_watching
|
|
42
|
+
@watcher.watch(
|
|
43
|
+
@paths,
|
|
59
44
|
recursive: @recursive,
|
|
60
45
|
force_polling: @force_polling,
|
|
61
46
|
poll_interval: @poll_interval,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
ignore_remove: @filters[:ignore_remove],
|
|
48
|
+
ignore_access: @filters[:ignore_access],
|
|
49
|
+
ignore_create: @filters[:ignore_create],
|
|
50
|
+
ignore_modify: @filters[:ignore_modify]
|
|
51
|
+
) do |kind, paths, raw_kind|
|
|
52
|
+
break if @stop_requested
|
|
53
|
+
|
|
54
|
+
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
|
|
59
|
+
else
|
|
60
|
+
event = Watchcat::Event.new(kind, paths, raw_kind)
|
|
61
|
+
@block.call(event)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
65
64
|
end
|
|
66
65
|
end
|
|
67
66
|
end
|
data/lib/watchcat/version.rb
CHANGED
data/lib/watchcat.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative "watchcat/version"
|
|
2
2
|
require_relative "watchcat/executor"
|
|
3
|
+
require_relative "watchcat/debouncer"
|
|
3
4
|
|
|
4
5
|
begin
|
|
5
6
|
require "watchcat/#{RUBY_VERSION.to_f}/watchcat"
|
|
@@ -14,8 +15,7 @@ module Watchcat
|
|
|
14
15
|
recursive: true,
|
|
15
16
|
force_polling: false,
|
|
16
17
|
poll_interval: nil,
|
|
17
|
-
|
|
18
|
-
ignore_remove: false,
|
|
18
|
+
filters: {},
|
|
19
19
|
debounce: -1,
|
|
20
20
|
&block
|
|
21
21
|
)
|
|
@@ -25,8 +25,7 @@ module Watchcat
|
|
|
25
25
|
recursive: recursive,
|
|
26
26
|
force_polling: force_polling,
|
|
27
27
|
poll_interval: poll_interval,
|
|
28
|
-
|
|
29
|
-
ignore_remove: ignore_remove,
|
|
28
|
+
filters: filters,
|
|
30
29
|
debounce: debounce,
|
|
31
30
|
block: block
|
|
32
31
|
)
|
metadata
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: watchcat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: arm64-darwin
|
|
6
6
|
authors:
|
|
7
7
|
- Yuji Yaginuma
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-09-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: psych
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
@@ -66,6 +66,20 @@ dependencies:
|
|
|
66
66
|
- - ">="
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: minitest-fail-fast
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
84
|
name: rake
|
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -129,22 +143,28 @@ executables: []
|
|
|
129
143
|
extensions: []
|
|
130
144
|
extra_rdoc_files: []
|
|
131
145
|
files:
|
|
146
|
+
- CHANGELOG.md
|
|
132
147
|
- Gemfile
|
|
133
148
|
- Gemfile.lock
|
|
134
149
|
- LICENSE.txt
|
|
135
150
|
- README.md
|
|
136
151
|
- Rakefile
|
|
152
|
+
- cli_example.yml
|
|
153
|
+
- exe/watchcat
|
|
137
154
|
- ext/watchcat/.gitignore
|
|
138
155
|
- lib/watchcat.rb
|
|
139
|
-
- lib/watchcat/3.0/watchcat.bundle
|
|
140
156
|
- lib/watchcat/3.1/watchcat.bundle
|
|
141
157
|
- lib/watchcat/3.2/watchcat.bundle
|
|
142
158
|
- lib/watchcat/3.3/watchcat.bundle
|
|
143
|
-
- lib/watchcat/
|
|
159
|
+
- lib/watchcat/3.4/watchcat.bundle
|
|
160
|
+
- lib/watchcat/cli.rb
|
|
161
|
+
- lib/watchcat/cli/action_executor.rb
|
|
162
|
+
- lib/watchcat/cli/config.rb
|
|
163
|
+
- lib/watchcat/cli/watcher.rb
|
|
164
|
+
- lib/watchcat/debouncer.rb
|
|
144
165
|
- lib/watchcat/event.rb
|
|
145
166
|
- lib/watchcat/executor.rb
|
|
146
167
|
- lib/watchcat/kind.rb
|
|
147
|
-
- lib/watchcat/server.rb
|
|
148
168
|
- lib/watchcat/version.rb
|
|
149
169
|
homepage: https://github.com/y-yagi/watchcat
|
|
150
170
|
licenses:
|
|
@@ -160,17 +180,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
160
180
|
requirements:
|
|
161
181
|
- - ">="
|
|
162
182
|
- !ruby/object:Gem::Version
|
|
163
|
-
version: '3.
|
|
183
|
+
version: '3.1'
|
|
164
184
|
- - "<"
|
|
165
185
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: 3.
|
|
186
|
+
version: 3.5.dev
|
|
167
187
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
188
|
requirements:
|
|
169
189
|
- - ">="
|
|
170
190
|
- !ruby/object:Gem::Version
|
|
171
191
|
version: '0'
|
|
172
192
|
requirements: []
|
|
173
|
-
rubygems_version: 3.
|
|
193
|
+
rubygems_version: 3.5.23
|
|
174
194
|
signing_key:
|
|
175
195
|
specification_version: 4
|
|
176
196
|
summary: Simple filesystem notification library for Ruby.
|
|
Binary file
|
data/lib/watchcat/client.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
module Watchcat
|
|
2
|
-
class Client
|
|
3
|
-
def initialize(uri, watcher:, paths:, recursive:, force_polling:, poll_interval:, ignore_remove:, debounce:)
|
|
4
|
-
DRb.start_service
|
|
5
|
-
@watcher = watcher
|
|
6
|
-
@server = DRbObject.new_with_uri(uri)
|
|
7
|
-
@paths = paths
|
|
8
|
-
@recursive = recursive
|
|
9
|
-
@force_polling = force_polling
|
|
10
|
-
@poll_interval = poll_interval
|
|
11
|
-
@ignore_remove = ignore_remove
|
|
12
|
-
@debounce = debounce
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def run
|
|
16
|
-
@watcher.watch(
|
|
17
|
-
@paths,
|
|
18
|
-
recursive: @recursive,
|
|
19
|
-
force_polling: @force_polling,
|
|
20
|
-
poll_interval: @poll_interval,
|
|
21
|
-
ignore_remove: @ignore_remove,
|
|
22
|
-
debounce: @debounce
|
|
23
|
-
) { |notification| @server.execute(notification) }
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
data/lib/watchcat/server.rb
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
require "watchcat/event"
|
|
2
|
-
|
|
3
|
-
module Watchcat
|
|
4
|
-
class Server
|
|
5
|
-
def initialize(block)
|
|
6
|
-
@block = block
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def execute(notification)
|
|
10
|
-
event = Watchcat::Event.new(notification[0], notification[1], notification[2])
|
|
11
|
-
@block.call(event)
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|