watchcat 0.3.0-x86_64-darwin → 0.4.0-x86_64-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 +5 -0
- data/Gemfile.lock +2 -1
- data/README.md +77 -2
- 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 +41 -0
- data/lib/watchcat/cli/watcher.rb +84 -0
- data/lib/watchcat/cli.rb +39 -0
- data/lib/watchcat/debouncer.rb +41 -0
- data/lib/watchcat/executor.rb +6 -10
- data/lib/watchcat/version.rb +1 -1
- data/lib/watchcat.rb +3 -4
- metadata +24 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74e8c9a81100683be52dfe56391d315b55c8e1647cd6ad247267c4d09cd20bdd
|
4
|
+
data.tar.gz: 3f3c3b9af89b83859eac3645f4e04602a71c44e8cec60fcfde2df7ebf9105673
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bedaa68e37095371355d65881eacce92d4250b25b979a77550d519f4d181bf29e6c7a94df97fd90477cf8f61591d0ef9d336919b805f0a4ed85e33934ab7a1e
|
7
|
+
data.tar.gz: 56af7d14ddf6f0833540a4639a1955ac63e90895fc89b41feea217e550c0868d84ccf34981ded83b24aa6b461ee8af39eb1cb086f27aafff8057d73cbefea64b
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
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
|
|
@@ -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,82 @@ 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
|
+
$ watchcat -C config.yml
|
124
|
+
```
|
125
|
+
|
126
|
+
### Configuration File
|
127
|
+
|
128
|
+
The configuration file should be in YAML format. Here's an example:
|
129
|
+
|
130
|
+
```yaml
|
131
|
+
watches:
|
132
|
+
- path: "./lib"
|
133
|
+
recursive: true
|
134
|
+
debounce: 300
|
135
|
+
filters:
|
136
|
+
ignore_access: true
|
137
|
+
patterns:
|
138
|
+
- "*.rb"
|
139
|
+
- "*.yml"
|
140
|
+
actions:
|
141
|
+
- command: "echo 'Ruby/YAML file changed: {{file_name}}'"
|
142
|
+
- command: "rubocop {{file_path}}"
|
143
|
+
```
|
144
|
+
|
145
|
+
### Configuration Options
|
146
|
+
|
147
|
+
Each watch entry supports the following options:
|
148
|
+
|
149
|
+
| Option | Description | Default |
|
150
|
+
|-------------|--------------------------------------------------------|---------|
|
151
|
+
| path | Directory or file path to watch (required) | - |
|
152
|
+
| recursive | Watch a directory recursively or not | `true` |
|
153
|
+
| debounce | Debounce events for the same file (in milliseconds) | `500` |
|
154
|
+
| filters | Event filters (same as library filters option) | `{}` |
|
155
|
+
| patterns | File patterns to match (using File.fnmatch) | `[]` |
|
156
|
+
| actions | Commands to execute when files change | `[]` |
|
157
|
+
|
158
|
+
### Available Variables for Commands
|
159
|
+
|
160
|
+
When specifying commands, you can use the following variables:
|
161
|
+
|
162
|
+
| Variable | Description | Example |
|
163
|
+
|---------------|------------------------------------------|------------------------|
|
164
|
+
| {{file_path}} | Full path of the changed file | `/home/user/app/file.rb` |
|
165
|
+
| {{file_dir}} | Directory containing the file | `/home/user/app` |
|
166
|
+
| {{file_name}} | File name with extension | `file.rb` |
|
167
|
+
| {{file_base}} | File name without extension | `file` |
|
168
|
+
| {{file_ext}} | File extension | `.rb` |
|
169
|
+
|
170
|
+
|
171
|
+
|
97
172
|
## Contributing
|
98
173
|
|
99
174
|
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,41 @@
|
|
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
|
+
private
|
26
|
+
|
27
|
+
def parse_watches(watches_data)
|
28
|
+
watches_data.map do |watch_config|
|
29
|
+
{
|
30
|
+
path: watch_config["path"],
|
31
|
+
recursive: watch_config.fetch("recursive", true),
|
32
|
+
patterns: watch_config["patterns"] || [],
|
33
|
+
actions: watch_config["actions"] || [],
|
34
|
+
debounce: watch_config.fetch("debounce", 500),
|
35
|
+
filters: watch_config["filters"]&.transform_keys(&:to_sym) || {},
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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
|
+
) do |event|
|
49
|
+
handle_file_event(event, watch_config)
|
50
|
+
end
|
51
|
+
|
52
|
+
@watchers << watcher
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_file_event(event, watch_config)
|
56
|
+
return if event.kind.access?
|
57
|
+
|
58
|
+
event.paths.each do |file_path|
|
59
|
+
next unless should_process_file?(file_path, watch_config[:patterns])
|
60
|
+
|
61
|
+
puts "File changed: #{file_path} #{event.kind}"
|
62
|
+
execute_actions(file_path, event, watch_config[:actions])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def should_process_file?(file_path, patterns)
|
67
|
+
return true if patterns.empty?
|
68
|
+
|
69
|
+
patterns.any? do |pattern|
|
70
|
+
File.fnmatch?(pattern, File.basename(file_path)) ||
|
71
|
+
File.fnmatch?(pattern, file_path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute_actions(file_path, event, actions)
|
76
|
+
executor = ActionExecutor.new(file_path, event)
|
77
|
+
|
78
|
+
actions.each do |action|
|
79
|
+
executor.execute(action)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/watchcat/cli.rb
ADDED
@@ -0,0 +1,39 @@
|
|
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
|
+
config = Config.load(options[:config])
|
13
|
+
watcher = Watcher.new(config)
|
14
|
+
watcher.start
|
15
|
+
rescue => e
|
16
|
+
raise Error, "Failed to start Watchcat: #{e.message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(argv)
|
20
|
+
options = {}
|
21
|
+
OptionParser.new do |opts|
|
22
|
+
opts.banner = "Usage: watchcat [options]"
|
23
|
+
|
24
|
+
opts.on("-C", "--config PATH", "Path to the config file") do |v|
|
25
|
+
options[:config] = v
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-h", "--help", "Show this help message") do
|
29
|
+
puts opts
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end.parse!(argv)
|
33
|
+
|
34
|
+
raise OptionParser::MissingArgument.new("-C") if options[:config].nil?
|
35
|
+
options
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
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
@@ -2,13 +2,12 @@ require_relative "event"
|
|
2
2
|
|
3
3
|
module Watchcat
|
4
4
|
class Executor
|
5
|
-
def initialize(paths, recursive:, force_polling:, poll_interval:,
|
5
|
+
def initialize(paths, recursive:, force_polling:, poll_interval:, filters:, debounce:, block:)
|
6
6
|
@paths = paths
|
7
7
|
@recursive = recursive
|
8
8
|
@force_polling = force_polling
|
9
9
|
@poll_interval = poll_interval
|
10
|
-
@
|
11
|
-
@ignore_remove = ignore_remove
|
10
|
+
@filters = filters || {}
|
12
11
|
@debounce = debounce
|
13
12
|
@block = block
|
14
13
|
@watcher = Watchcat::Watcher.new
|
@@ -23,11 +22,6 @@ module Watchcat
|
|
23
22
|
start_watching
|
24
23
|
end
|
25
24
|
|
26
|
-
# If wait_until_startup is true, give the thread a moment to start
|
27
|
-
if @wait_until_startup
|
28
|
-
sleep 0.1
|
29
|
-
end
|
30
|
-
|
31
25
|
at_exit do
|
32
26
|
stop
|
33
27
|
end
|
@@ -49,12 +43,14 @@ module Watchcat
|
|
49
43
|
recursive: @recursive,
|
50
44
|
force_polling: @force_polling,
|
51
45
|
poll_interval: @poll_interval,
|
52
|
-
ignore_remove: @ignore_remove,
|
46
|
+
ignore_remove: @filters[:ignore_remove],
|
47
|
+
ignore_access: @filters[:ignore_access],
|
48
|
+
ignore_create: @filters[:ignore_create],
|
49
|
+
ignore_modify: @filters[:ignore_modify],
|
53
50
|
debounce: @debounce
|
54
51
|
) do |kind, paths, raw_kind|
|
55
52
|
break if @stop_requested
|
56
53
|
|
57
|
-
# Create an event object and call the block
|
58
54
|
event = Watchcat::Event.new(kind, paths, raw_kind)
|
59
55
|
@block.call(event)
|
60
56
|
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,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: watchcat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: x86_64-darwin
|
6
6
|
authors:
|
7
7
|
- Yuji Yaginuma
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: psych
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: debug
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,12 +135,19 @@ files:
|
|
121
135
|
- LICENSE.txt
|
122
136
|
- README.md
|
123
137
|
- Rakefile
|
138
|
+
- cli_example.yml
|
139
|
+
- exe/watchcat
|
124
140
|
- ext/watchcat/.gitignore
|
125
141
|
- lib/watchcat.rb
|
126
142
|
- lib/watchcat/3.1/watchcat.bundle
|
127
143
|
- lib/watchcat/3.2/watchcat.bundle
|
128
144
|
- lib/watchcat/3.3/watchcat.bundle
|
129
145
|
- lib/watchcat/3.4/watchcat.bundle
|
146
|
+
- lib/watchcat/cli.rb
|
147
|
+
- lib/watchcat/cli/action_executor.rb
|
148
|
+
- lib/watchcat/cli/config.rb
|
149
|
+
- lib/watchcat/cli/watcher.rb
|
150
|
+
- lib/watchcat/debouncer.rb
|
130
151
|
- lib/watchcat/event.rb
|
131
152
|
- lib/watchcat/executor.rb
|
132
153
|
- lib/watchcat/kind.rb
|