zwerg 0.1.0 → 0.1.2
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 +11 -0
- data/README.md +28 -1
- data/example_zwerg.yml +12 -2
- data/exe/zwerg +1 -1
- data/lib/zwerg/action_executor.rb +0 -9
- data/lib/zwerg/config.rb +2 -1
- data/lib/zwerg/debouncer.rb +43 -0
- data/lib/zwerg/version.rb +1 -1
- data/lib/zwerg/watcher.rb +11 -3
- data/lib/zwerg.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f5101ab9cfd3931ba280fd1f99ea072f390a0b729d746f733a53de3b984c81c
|
4
|
+
data.tar.gz: ae2a722ad3f32d7942d89e22e4afc5b6de3094dcf5d411e68a51827dbb50dbe1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7d330a786f9d42e790df64840ee513195ce15505d899d2cb4f8e6228415eca0fcfa8374b1c4625aaa7fd2c83b0c83b8adfcc210341d4937f7ddf7f7191819b6
|
7
|
+
data.tar.gz: 77c468b8cb4ff0e8d8da43ebaccee257a0b629855ba6ff92c8be62e5f845ff2872f5440d1c0b95c345afb9785583a24344a55b3a7f3ec847b8f3f517a7815400
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -40,6 +40,7 @@ gem install zwerg
|
|
40
40
|
watches:
|
41
41
|
- path: "./src"
|
42
42
|
recursive: true
|
43
|
+
debounce: 500
|
43
44
|
patterns:
|
44
45
|
- "*.rb"
|
45
46
|
- "*.yml"
|
@@ -63,6 +64,7 @@ Each watch entry can have the following properties:
|
|
63
64
|
|
64
65
|
- `path`: The file or directory to watch (required)
|
65
66
|
- `recursive`: Whether to watch subdirectories (default: true)
|
67
|
+
- `debounce`: Wait time in milliseconds before firing actions for the same file (default: 500)
|
66
68
|
- `patterns`: Array of glob patterns to match files (optional, matches all if empty)
|
67
69
|
- `actions`: Array of actions to execute when files change (required)
|
68
70
|
|
@@ -78,6 +80,32 @@ You can use any shell command, including complex operations with pipes, redirect
|
|
78
80
|
- command: "if [ -f {{file_path}} ]; then echo 'File exists: {{file_name}}' >> changes.log; fi"
|
79
81
|
```
|
80
82
|
|
83
|
+
### Debounce Feature
|
84
|
+
|
85
|
+
The debounce feature prevents actions from firing too frequently for the same file. When a file changes multiple times within the debounce period, only the last change will trigger the actions. This is implemented using Zwerg's built-in debouncer.
|
86
|
+
|
87
|
+
```yaml
|
88
|
+
- path: "./src"
|
89
|
+
debounce: 1000 # Wait 1000ms (1 second) before firing actions
|
90
|
+
actions:
|
91
|
+
- command: "echo 'File changed: {{file_path}}'"
|
92
|
+
```
|
93
|
+
|
94
|
+
**How it works:**
|
95
|
+
- Each file path is tracked separately with its own debounce timer
|
96
|
+
- When a file changes, any existing timer for that file is cancelled
|
97
|
+
- A new timer is started for the specified debounce period
|
98
|
+
- If the file changes again before the timer expires, the timer is reset
|
99
|
+
- Actions only execute when the timer completes without interruption
|
100
|
+
|
101
|
+
This is particularly useful for:
|
102
|
+
- Files that are saved multiple times in quick succession by editors
|
103
|
+
- Build processes that modify multiple files rapidly
|
104
|
+
- Log files that are updated frequently
|
105
|
+
- Preventing duplicate actions when files are modified in batches
|
106
|
+
|
107
|
+
The debounce value is specified in milliseconds. If not specified, the default is 500ms.
|
108
|
+
|
81
109
|
### Variable Substitution
|
82
110
|
|
83
111
|
The following variables can be used in action configurations:
|
@@ -87,7 +115,6 @@ The following variables can be used in action configurations:
|
|
87
115
|
- `{{file_name}}`: Name of the file (including extension)
|
88
116
|
- `{{file_base}}`: Name of the file without extension
|
89
117
|
- `{{file_ext}}`: File extension (including the dot)
|
90
|
-
- `{{event_type}}`: Type of event (create, modify, remove, access)
|
91
118
|
|
92
119
|
## Example Configuration
|
93
120
|
|
data/example_zwerg.yml
CHANGED
@@ -5,6 +5,7 @@ watches:
|
|
5
5
|
# Watch a source directory for Ruby files
|
6
6
|
- path: "./src"
|
7
7
|
recursive: true
|
8
|
+
debounce: 300 # Wait 300ms before firing actions for the same file
|
8
9
|
patterns:
|
9
10
|
- "*.rb"
|
10
11
|
- "*.yml"
|
@@ -15,6 +16,7 @@ watches:
|
|
15
16
|
# Watch a documentation directory
|
16
17
|
- path: "./docs"
|
17
18
|
recursive: true
|
19
|
+
debounce: 1000 # Wait 1 second for documentation files
|
18
20
|
patterns:
|
19
21
|
- "*.md"
|
20
22
|
- "*.txt"
|
@@ -25,23 +27,32 @@ watches:
|
|
25
27
|
# Watch configuration files in the root directory
|
26
28
|
- path: "."
|
27
29
|
recursive: false
|
30
|
+
debounce: 2000 # Wait 2 seconds for config files (they might be edited multiple times)
|
28
31
|
patterns:
|
29
32
|
- "*.yml"
|
30
33
|
- "*.yaml"
|
31
34
|
- "*.json"
|
32
35
|
actions:
|
33
|
-
- command: "echo '
|
36
|
+
- command: "echo 'Config file changed: {{file_name}}'"
|
34
37
|
- command: "echo 'Config changed: {{file_path}}' | mail -s 'Config Alert' admin@example.com"
|
35
38
|
|
36
39
|
# Watch for log files and compress them when they get large
|
37
40
|
- path: "./logs"
|
38
41
|
recursive: false
|
42
|
+
debounce: 5000 # Wait 5 seconds for log files (they change frequently)
|
39
43
|
patterns:
|
40
44
|
- "*.log"
|
41
45
|
actions:
|
42
46
|
- command: "if [ $(stat -c%s {{file_path}}) -gt 1048576 ]; then gzip {{file_path}}; fi"
|
43
47
|
|
44
48
|
# Each action is a shell command that will be executed when files change
|
49
|
+
#
|
50
|
+
# Configuration options:
|
51
|
+
# - path: Directory or file to watch (required)
|
52
|
+
# - recursive: Watch subdirectories (default: true)
|
53
|
+
# - debounce: Wait time in milliseconds before firing actions (default: 500ms)
|
54
|
+
# - patterns: Glob patterns to match files (optional, matches all if empty)
|
55
|
+
# - actions: Commands to execute when files change (required)
|
45
56
|
|
46
57
|
# Available variables for substitution:
|
47
58
|
# - {{file_path}}: Full path to the changed file
|
@@ -49,4 +60,3 @@ watches:
|
|
49
60
|
# - {{file_name}}: Name of the file (including extension)
|
50
61
|
# - {{file_base}}: Name of the file without extension
|
51
62
|
# - {{file_ext}}: File extension (including the dot)
|
52
|
-
# - {{event_type}}: Type of event (create, modify, remove, access)
|
data/exe/zwerg
CHANGED
@@ -26,6 +26,7 @@ def show_help
|
|
26
26
|
watches:
|
27
27
|
- path: "./src"
|
28
28
|
recursive: true
|
29
|
+
debounce: 500
|
29
30
|
patterns:
|
30
31
|
- "*.rb"
|
31
32
|
- "*.yml"
|
@@ -39,7 +40,6 @@ def show_help
|
|
39
40
|
{{file_name}} Name of the file (including extension)
|
40
41
|
{{file_base}} Name of the file without extension
|
41
42
|
{{file_ext}} File extension (including the dot)
|
42
|
-
{{event_type}} Type of event (create, modify, remove, access)
|
43
43
|
|
44
44
|
For more information, visit: https://github.com/y-yagi/zwerg
|
45
45
|
HELP
|
@@ -38,15 +38,6 @@ module Zwerg
|
|
38
38
|
.gsub("{{file_name}}", @file_name)
|
39
39
|
.gsub("{{file_base}}", @file_base)
|
40
40
|
.gsub("{{file_ext}}", @file_ext)
|
41
|
-
.gsub("{{event_type}}", determine_event_type)
|
42
|
-
end
|
43
|
-
|
44
|
-
def determine_event_type
|
45
|
-
return "create" if @event.kind.create?
|
46
|
-
return "modify" if @event.kind.modify?
|
47
|
-
return "remove" if @event.kind.remove?
|
48
|
-
return "access" if @event.kind.access?
|
49
|
-
"unknown"
|
50
41
|
end
|
51
42
|
end
|
52
43
|
end
|
data/lib/zwerg/config.rb
CHANGED
@@ -31,7 +31,8 @@ module Zwerg
|
|
31
31
|
path: watch_config["path"],
|
32
32
|
recursive: watch_config.fetch("recursive", true),
|
33
33
|
patterns: watch_config["patterns"] || [],
|
34
|
-
actions: watch_config["actions"] || []
|
34
|
+
actions: watch_config["actions"] || [],
|
35
|
+
debounce: watch_config.fetch("debounce", 500)
|
35
36
|
}
|
36
37
|
end
|
37
38
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zwerg
|
4
|
+
class Debouncer
|
5
|
+
def initialize
|
6
|
+
@timers = {}
|
7
|
+
@mutex = Mutex.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def debounce(key, delay_ms, &block)
|
11
|
+
@mutex.synchronize do
|
12
|
+
# Cancel existing timer for this key
|
13
|
+
if @timers[key]
|
14
|
+
@timers[key].kill
|
15
|
+
end
|
16
|
+
|
17
|
+
# Create new timer
|
18
|
+
@timers[key] = Thread.new do
|
19
|
+
sleep(delay_ms / 1000.0) # Convert ms to seconds
|
20
|
+
|
21
|
+
@mutex.synchronize do
|
22
|
+
@timers.delete(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
block.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
@mutex.synchronize do
|
32
|
+
@timers.each_value(&:kill)
|
33
|
+
@timers.clear
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def pending_count
|
38
|
+
@mutex.synchronize do
|
39
|
+
@timers.size
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/zwerg/version.rb
CHANGED
data/lib/zwerg/watcher.rb
CHANGED
@@ -7,6 +7,7 @@ module Zwerg
|
|
7
7
|
def initialize(config)
|
8
8
|
@config = config
|
9
9
|
@watchers = []
|
10
|
+
@debouncer = Debouncer.new
|
10
11
|
end
|
11
12
|
|
12
13
|
def start
|
@@ -28,6 +29,7 @@ module Zwerg
|
|
28
29
|
end
|
29
30
|
|
30
31
|
def stop
|
32
|
+
@debouncer.clear
|
31
33
|
@watchers.each(&:stop)
|
32
34
|
@watchers.clear
|
33
35
|
end
|
@@ -42,7 +44,7 @@ module Zwerg
|
|
42
44
|
return
|
43
45
|
end
|
44
46
|
|
45
|
-
puts "Watching: #{path} (recursive: #{watch_config[:recursive]})"
|
47
|
+
puts "Watching: #{path} (recursive: #{watch_config[:recursive]}, debounce: #{watch_config[:debounce]}ms)"
|
46
48
|
|
47
49
|
watcher = Watchcat.watch(
|
48
50
|
path,
|
@@ -56,11 +58,17 @@ module Zwerg
|
|
56
58
|
end
|
57
59
|
|
58
60
|
def handle_file_event(event, watch_config)
|
61
|
+
return if event.kind.access?
|
62
|
+
|
59
63
|
event.paths.each do |file_path|
|
60
64
|
next unless should_process_file?(file_path, watch_config[:patterns])
|
61
65
|
|
62
|
-
|
63
|
-
|
66
|
+
# Use debouncer to delay action execution
|
67
|
+
debounce_key = "#{watch_config[:path]}:#{file_path}"
|
68
|
+
@debouncer.debounce(debounce_key, watch_config[:debounce]) do
|
69
|
+
puts "File changed: #{file_path}"
|
70
|
+
execute_actions(file_path, event, watch_config[:actions])
|
71
|
+
end
|
64
72
|
end
|
65
73
|
end
|
66
74
|
|
data/lib/zwerg.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zwerg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuji Yaginuma
|
@@ -44,6 +44,7 @@ executables:
|
|
44
44
|
extensions: []
|
45
45
|
extra_rdoc_files: []
|
46
46
|
files:
|
47
|
+
- CHANGELOG.md
|
47
48
|
- CODE_OF_CONDUCT.md
|
48
49
|
- LICENSE.txt
|
49
50
|
- README.md
|
@@ -53,6 +54,7 @@ files:
|
|
53
54
|
- lib/zwerg.rb
|
54
55
|
- lib/zwerg/action_executor.rb
|
55
56
|
- lib/zwerg/config.rb
|
57
|
+
- lib/zwerg/debouncer.rb
|
56
58
|
- lib/zwerg/version.rb
|
57
59
|
- lib/zwerg/watcher.rb
|
58
60
|
homepage: https://github.com/y-yagi/zwerg
|