dotsync 0.2.2 → 0.2.3
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 +31 -1
- data/Gemfile.lock +1 -1
- data/README.md +80 -0
- data/lib/dotsync/actions/concerns/mappings_transfer.rb +39 -2
- data/lib/dotsync/actions/pull_action.rb +2 -0
- data/lib/dotsync/actions/push_action.rb +2 -0
- data/lib/dotsync/config/concerns/sync_mappings.rb +42 -0
- data/lib/dotsync/config/pull_action_config.rb +19 -1
- data/lib/dotsync/config/push_action_config.rb +19 -1
- data/lib/dotsync/errors.rb +1 -0
- data/lib/dotsync/icons.rb +7 -0
- data/lib/dotsync/loaders/pull_loader.rb +1 -0
- data/lib/dotsync/loaders/push_loader.rb +1 -0
- data/lib/dotsync/models/mapping.rb +8 -0
- data/lib/dotsync/utils/hook_runner.rb +48 -0
- data/lib/dotsync/version.rb +1 -1
- data/lib/dotsync.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c060e4f84252048c1b038e350efcf900bc5b62b6bab5285dc9b1a2a9d0d0da0f
|
|
4
|
+
data.tar.gz: 5bef34fde923690c3ffaf68b3d5ed77d2195006d91d8b4f2c59853533c285339
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7049966bedd67e25b008ad9a7bae1697f54d189dc7f666bd42495a242af68b8c9494ead4c3e5310e500ce516db47270d5ae0f0382035b50df5d09543a631192f
|
|
7
|
+
data.tar.gz: 7abedab0ed8d49946e5b832ad38390968f17c6a7e32da3a3d774a47117767f1eec9dc3cbefd20a2f815eed5b091dcab73c4e9612b21d67fb1b7f85a0b8499ed1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
## [0.2.3] - 2026-02-08
|
|
2
|
+
|
|
3
|
+
**New Features:**
|
|
4
|
+
- Add per-mapping post-sync hooks that run commands after files are transferred
|
|
5
|
+
- `post_sync` — runs after sync in both directions (`[[sync]]` mappings)
|
|
6
|
+
- `post_push` — runs only after push (`[[sync]]` and `[[push]]` mappings)
|
|
7
|
+
- `post_pull` — runs only after pull (`[[sync]]` and `[[pull]]` mappings)
|
|
8
|
+
- Template variables: `{files}` (shell-quoted changed dest paths), `{src}`, `{dest}`
|
|
9
|
+
- Hooks only execute when files actually changed and only with `--apply`
|
|
10
|
+
- Hook failures log errors but do not abort remaining hooks or mappings
|
|
11
|
+
- Preview mode shows what commands would run without executing
|
|
12
|
+
- Add `HookError` error class for hook-related errors
|
|
13
|
+
- Add hook icon () to mappings legend with custom icon support
|
|
14
|
+
|
|
15
|
+
**New Files:**
|
|
16
|
+
- `lib/dotsync/utils/hook_runner.rb` — HookRunner utility with execute, preview, and template expansion
|
|
17
|
+
- `spec/dotsync/utils/hook_runner_spec.rb` — Comprehensive tests for HookRunner
|
|
18
|
+
|
|
19
|
+
**Documentation:**
|
|
20
|
+
- Add "Post-Sync Hooks" section to README with hook types, examples, template variables, and real-world use cases
|
|
21
|
+
- Add post-sync hooks to Key Features and Table of Contents
|
|
22
|
+
|
|
23
|
+
**Testing:**
|
|
24
|
+
- Add 35 new test examples covering hooks across all layers
|
|
25
|
+
- HookRunner: template expansion, multiple commands, failure handling, shell-escaped paths, preview mode
|
|
26
|
+
- Mapping: hooks attribute, has_hooks?, hook icon display
|
|
27
|
+
- SyncMappings: direction resolution, array concatenation, shorthand hooks, validation of invalid keys
|
|
28
|
+
- PushActionConfig/PullActionConfig: hook extraction, validation of unidirectional constraints
|
|
29
|
+
- PushAction/PullAction: hook execution with changes, skipped without changes, skipped in dry-run
|
|
30
|
+
- Total: 467 examples, 0 failures | Line: 96.45% | Branch: 82.86%
|
|
31
|
+
|
|
1
32
|
## [0.2.2] - 2025-02-07
|
|
2
33
|
|
|
3
34
|
**New Features:**
|
|
@@ -17,7 +48,6 @@
|
|
|
17
48
|
- Add integration tests for glob patterns in FileTransfer (including force mode)
|
|
18
49
|
- Add integration tests for glob patterns in DirectoryDiffer
|
|
19
50
|
- All 432 tests pass with 96.29% line coverage
|
|
20
|
-
|
|
21
51
|
## [0.2.1] - 2025-02-06
|
|
22
52
|
|
|
23
53
|
**Performance Optimizations:**
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles acro
|
|
|
19
19
|
- **Smart Filtering**: Use `force`, `only`, and `ignore` options to precisely control what gets synced
|
|
20
20
|
- **Automatic Backups**: Pull operations create timestamped backups for easy recovery
|
|
21
21
|
- **Live Watching**: Continuously monitor and sync changes in real-time with `watch` command
|
|
22
|
+
- **Post-Sync Hooks**: Run commands automatically after files change (e.g., codesigning, chmod, service reload)
|
|
22
23
|
- **Customizable Output**: Control verbosity and customize icons to match your preferences
|
|
23
24
|
- **Auto-Updates**: Get notified when new versions are available
|
|
24
25
|
|
|
@@ -33,6 +34,7 @@ Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles acro
|
|
|
33
34
|
- [Bidirectional Sync Mappings (Recommended)](#bidirectional-sync-mappings-recommended)
|
|
34
35
|
- [Alternative: Unidirectional Mappings](#alternative-unidirectional-mappings)
|
|
35
36
|
- [Mapping Options (force, only, ignore)](#force-only-and-ignore-options-in-mappings)
|
|
37
|
+
- [Post-Sync Hooks](#post-sync-hooks)
|
|
36
38
|
- [Safety Features](#safety-features)
|
|
37
39
|
- [Customizing Icons](#customizing-icons)
|
|
38
40
|
- [Automatic Update Checks](#automatic-update-checks)
|
|
@@ -499,6 +501,84 @@ This configuration:
|
|
|
499
501
|
|
|
500
502
|
These options apply when the source is a directory and are relevant for both `push` and `pull` operations.
|
|
501
503
|
|
|
504
|
+
#### Post-Sync Hooks
|
|
505
|
+
|
|
506
|
+
Hooks let you run commands automatically after a mapping's files are transferred. Hooks only execute when files actually changed, and only when using `--apply`.
|
|
507
|
+
|
|
508
|
+
##### Hook Types
|
|
509
|
+
|
|
510
|
+
| Hook | Description | Valid in |
|
|
511
|
+
|------|-------------|----------|
|
|
512
|
+
| `post_sync` | Runs after sync in both directions | `[[sync]]` mappings |
|
|
513
|
+
| `post_push` | Runs only after push | `[[sync]]` and `[[push]]` mappings |
|
|
514
|
+
| `post_pull` | Runs only after pull | `[[sync]]` and `[[pull]]` mappings |
|
|
515
|
+
|
|
516
|
+
For sync mappings, hooks are resolved by direction:
|
|
517
|
+
- **Push**: `post_sync` + `post_push` commands
|
|
518
|
+
- **Pull**: `post_sync` + `post_pull` commands
|
|
519
|
+
|
|
520
|
+
##### Examples
|
|
521
|
+
|
|
522
|
+
**Single command (shorthand mapping):**
|
|
523
|
+
```toml
|
|
524
|
+
[[sync.xdg_bin]]
|
|
525
|
+
force = true
|
|
526
|
+
|
|
527
|
+
[sync.xdg_bin.hooks]
|
|
528
|
+
post_sync = "codesign -s - {files}"
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**Multiple commands (explicit sync):**
|
|
532
|
+
```toml
|
|
533
|
+
[[sync.mappings]]
|
|
534
|
+
local = "$XDG_CONFIG_HOME/scripts"
|
|
535
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/scripts"
|
|
536
|
+
|
|
537
|
+
[sync.mappings.hooks]
|
|
538
|
+
post_sync = ["codesign -s - {files}", "chmod 700 {files}"]
|
|
539
|
+
post_pull = "launchctl kickstart -k gui/$(id -u)/com.example.service"
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Unidirectional mapping:**
|
|
543
|
+
```toml
|
|
544
|
+
[[pull.mappings]]
|
|
545
|
+
src = "$DOTFILES_DIR/scripts"
|
|
546
|
+
dest = "$HOME/Scripts"
|
|
547
|
+
|
|
548
|
+
[pull.mappings.hooks]
|
|
549
|
+
post_pull = ["codesign -s - {files}", "chmod 700 {files}"]
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
##### Template Variables
|
|
553
|
+
|
|
554
|
+
| Variable | Description |
|
|
555
|
+
|----------|-------------|
|
|
556
|
+
| `{files}` | Shell-quoted paths of changed destination files |
|
|
557
|
+
| `{src}` | The mapping's source path |
|
|
558
|
+
| `{dest}` | The mapping's destination path |
|
|
559
|
+
|
|
560
|
+
##### Real-World Examples
|
|
561
|
+
|
|
562
|
+
```toml
|
|
563
|
+
# Codesign scripts after pulling (macOS Ventura+ requirement for LaunchAgents)
|
|
564
|
+
[[sync.xdg_bin]]
|
|
565
|
+
force = true
|
|
566
|
+
|
|
567
|
+
[sync.xdg_bin.hooks]
|
|
568
|
+
post_pull = "codesign -s - {files}"
|
|
569
|
+
|
|
570
|
+
# Reload a LaunchAgent after pulling config changes
|
|
571
|
+
[[pull.mappings]]
|
|
572
|
+
src = "$DOTFILES_DIR/LaunchAgents/com.example.plist"
|
|
573
|
+
dest = "$HOME/Library/LaunchAgents/com.example.plist"
|
|
574
|
+
|
|
575
|
+
[pull.mappings.hooks]
|
|
576
|
+
post_pull = "launchctl kickstart -k gui/$(id -u)/com.example"
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
> [!NOTE]
|
|
580
|
+
> In preview mode (without `--apply`), hooks are displayed as a preview showing what commands would run. Hook failures log errors but do not abort remaining hooks or mappings.
|
|
581
|
+
|
|
502
582
|
### Safety Features
|
|
503
583
|
|
|
504
584
|
Dotsync includes several safety mechanisms to prevent accidental data loss:
|
|
@@ -23,8 +23,9 @@ module Dotsync
|
|
|
23
23
|
|
|
24
24
|
MAPPINGS_LEGEND = [
|
|
25
25
|
[Icons.force, "The source will overwrite the destination"],
|
|
26
|
-
[Icons.only, "
|
|
27
|
-
[Icons.ignore, "
|
|
26
|
+
[Icons.only, "Filtered by 'only' whitelist"],
|
|
27
|
+
[Icons.ignore, "Filtered by 'ignore' blacklist"],
|
|
28
|
+
[Icons.hook, "Post-sync hooks configured"],
|
|
28
29
|
[Icons.invalid, "Invalid paths detected in the source or destination"]
|
|
29
30
|
]
|
|
30
31
|
|
|
@@ -135,6 +136,42 @@ module Dotsync
|
|
|
135
136
|
end
|
|
136
137
|
end
|
|
137
138
|
|
|
139
|
+
def execute_hooks
|
|
140
|
+
valid_mappings.each_with_index do |mapping, idx|
|
|
141
|
+
next unless mapping.has_hooks?
|
|
142
|
+
|
|
143
|
+
differ = differs[idx]
|
|
144
|
+
changed_files = differ.additions + differ.modifications
|
|
145
|
+
next if changed_files.empty?
|
|
146
|
+
|
|
147
|
+
runner = Dotsync::HookRunner.new(mapping: mapping, changed_files: changed_files, logger: logger)
|
|
148
|
+
runner.execute
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def show_hooks_preview
|
|
153
|
+
hooks_to_run = []
|
|
154
|
+
|
|
155
|
+
valid_mappings.each_with_index do |mapping, idx|
|
|
156
|
+
next unless mapping.has_hooks?
|
|
157
|
+
|
|
158
|
+
differ = differs[idx]
|
|
159
|
+
changed_files = differ.additions + differ.modifications
|
|
160
|
+
next if changed_files.empty?
|
|
161
|
+
|
|
162
|
+
runner = Dotsync::HookRunner.new(mapping: mapping, changed_files: changed_files, logger: logger)
|
|
163
|
+
hooks_to_run.concat(runner.preview)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
return if hooks_to_run.empty?
|
|
167
|
+
|
|
168
|
+
info("Hooks to run:", icon: :hook)
|
|
169
|
+
hooks_to_run.each do |command|
|
|
170
|
+
logger.log(" #{command}")
|
|
171
|
+
end
|
|
172
|
+
logger.log("")
|
|
173
|
+
end
|
|
174
|
+
|
|
138
175
|
private
|
|
139
176
|
# Computes diffs for all valid mappings.
|
|
140
177
|
#
|
|
@@ -16,6 +16,7 @@ module Dotsync
|
|
|
16
16
|
show_mappings if output_sections[:mappings]
|
|
17
17
|
show_differences_legend if has_differences? && output_sections[:differences_legend]
|
|
18
18
|
show_differences(diff_content: output_sections[:diff_content]) if output_sections[:differences]
|
|
19
|
+
show_hooks_preview if output_sections[:differences]
|
|
19
20
|
|
|
20
21
|
return unless options[:apply]
|
|
21
22
|
|
|
@@ -32,6 +33,7 @@ module Dotsync
|
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
transfer_mappings
|
|
36
|
+
execute_hooks
|
|
35
37
|
action("Mappings pulled", icon: :done)
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -14,6 +14,7 @@ module Dotsync
|
|
|
14
14
|
show_mappings if output_sections[:mappings]
|
|
15
15
|
show_differences_legend if has_differences? && output_sections[:differences_legend]
|
|
16
16
|
show_differences(diff_content: output_sections[:diff_content]) if output_sections[:differences]
|
|
17
|
+
show_hooks_preview if output_sections[:differences]
|
|
17
18
|
|
|
18
19
|
return unless options[:apply]
|
|
19
20
|
|
|
@@ -23,6 +24,7 @@ module Dotsync
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
transfer_mappings
|
|
27
|
+
execute_hooks
|
|
26
28
|
action("Mappings pushed", icon: :done)
|
|
27
29
|
end
|
|
28
30
|
end
|
|
@@ -96,6 +96,12 @@ module Dotsync
|
|
|
96
96
|
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
97
97
|
base["only"] = mapping["only"] if mapping.key?("only")
|
|
98
98
|
|
|
99
|
+
# Resolve hooks for direction
|
|
100
|
+
if mapping.key?("hooks")
|
|
101
|
+
resolved = resolve_hooks_for_direction(mapping["hooks"], direction)
|
|
102
|
+
base["hooks"] = resolved if resolved.any?
|
|
103
|
+
end
|
|
104
|
+
|
|
99
105
|
base
|
|
100
106
|
end
|
|
101
107
|
|
|
@@ -142,9 +148,41 @@ module Dotsync
|
|
|
142
148
|
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
143
149
|
base["only"] = only if only
|
|
144
150
|
|
|
151
|
+
# Resolve hooks for direction
|
|
152
|
+
if mapping.key?("hooks")
|
|
153
|
+
resolved = resolve_hooks_for_direction(mapping["hooks"], direction)
|
|
154
|
+
base["hooks"] = resolved if resolved.any?
|
|
155
|
+
end
|
|
156
|
+
|
|
145
157
|
base
|
|
146
158
|
end
|
|
147
159
|
|
|
160
|
+
def resolve_hooks_for_direction(raw_hooks, direction)
|
|
161
|
+
return [] unless raw_hooks.is_a?(Hash)
|
|
162
|
+
|
|
163
|
+
hooks = Array(raw_hooks["post_sync"])
|
|
164
|
+
case direction
|
|
165
|
+
when :push
|
|
166
|
+
hooks += Array(raw_hooks["post_push"])
|
|
167
|
+
when :pull
|
|
168
|
+
hooks += Array(raw_hooks["post_pull"])
|
|
169
|
+
end
|
|
170
|
+
hooks
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def validate_hooks!(hooks, context)
|
|
174
|
+
return unless hooks.is_a?(Hash)
|
|
175
|
+
|
|
176
|
+
valid_keys = %w[post_sync post_push post_pull]
|
|
177
|
+
invalid_keys = hooks.keys - valid_keys
|
|
178
|
+
return if invalid_keys.empty?
|
|
179
|
+
|
|
180
|
+
raise Dotsync::ConfigError,
|
|
181
|
+
"Configuration error in #{context}: " \
|
|
182
|
+
"Invalid hook key(s): #{invalid_keys.join(", ")}. " \
|
|
183
|
+
"Valid keys are: #{valid_keys.join(", ")}"
|
|
184
|
+
end
|
|
185
|
+
|
|
148
186
|
def build_path(base, path)
|
|
149
187
|
path ? File.join(base, path) : base
|
|
150
188
|
end
|
|
@@ -171,6 +209,8 @@ module Dotsync
|
|
|
171
209
|
"Configuration error in sync.mappings ##{index + 1}: " \
|
|
172
210
|
"Each mapping must have 'local' and 'remote' keys."
|
|
173
211
|
end
|
|
212
|
+
|
|
213
|
+
validate_hooks!(mapping["hooks"], "sync.mappings ##{index + 1}") if mapping.key?("hooks")
|
|
174
214
|
end
|
|
175
215
|
end
|
|
176
216
|
|
|
@@ -184,6 +224,8 @@ module Dotsync
|
|
|
184
224
|
"Configuration error in sync.#{shorthand_type} ##{index + 1}: " \
|
|
185
225
|
"Each mapping must be a table."
|
|
186
226
|
end
|
|
227
|
+
|
|
228
|
+
validate_hooks!(mapping["hooks"], "sync.#{shorthand_type} ##{index + 1}") if mapping.is_a?(Hash) && mapping.key?("hooks")
|
|
187
229
|
end
|
|
188
230
|
end
|
|
189
231
|
end
|
|
@@ -22,7 +22,15 @@ module Dotsync
|
|
|
22
22
|
|
|
23
23
|
def section_mappings
|
|
24
24
|
return [] unless section && section["mappings"]
|
|
25
|
-
Array(section["mappings"]).map
|
|
25
|
+
Array(section["mappings"]).map do |mapping|
|
|
26
|
+
attrs = mapping.dup
|
|
27
|
+
if attrs.key?("hooks") && attrs["hooks"].is_a?(Hash)
|
|
28
|
+
resolved = Array(attrs["hooks"]["post_pull"])
|
|
29
|
+
attrs["hooks"] = resolved.any? ? resolved : nil
|
|
30
|
+
attrs.delete("hooks") unless attrs["hooks"]
|
|
31
|
+
end
|
|
32
|
+
Dotsync::Mapping.new(attrs)
|
|
33
|
+
end
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def validate!
|
|
@@ -46,6 +54,16 @@ module Dotsync
|
|
|
46
54
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
47
55
|
raise "Configuration error in pull mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
48
56
|
end
|
|
57
|
+
|
|
58
|
+
if mapping.is_a?(Hash) && mapping.key?("hooks") && mapping["hooks"].is_a?(Hash)
|
|
59
|
+
invalid_keys = mapping["hooks"].keys - ["post_pull"]
|
|
60
|
+
if invalid_keys.any?
|
|
61
|
+
raise Dotsync::ConfigError,
|
|
62
|
+
"Configuration error in pull mapping ##{index + 1}: " \
|
|
63
|
+
"Only 'post_pull' hooks are allowed in [pull] mappings. " \
|
|
64
|
+
"Invalid key(s): #{invalid_keys.join(", ")}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
49
67
|
end
|
|
50
68
|
end
|
|
51
69
|
end
|
|
@@ -17,7 +17,15 @@ module Dotsync
|
|
|
17
17
|
|
|
18
18
|
def section_mappings
|
|
19
19
|
return [] unless section && section["mappings"]
|
|
20
|
-
Array(section["mappings"]).map
|
|
20
|
+
Array(section["mappings"]).map do |mapping|
|
|
21
|
+
attrs = mapping.dup
|
|
22
|
+
if attrs.key?("hooks") && attrs["hooks"].is_a?(Hash)
|
|
23
|
+
resolved = Array(attrs["hooks"]["post_push"])
|
|
24
|
+
attrs["hooks"] = resolved.any? ? resolved : nil
|
|
25
|
+
attrs.delete("hooks") unless attrs["hooks"]
|
|
26
|
+
end
|
|
27
|
+
Dotsync::Mapping.new(attrs)
|
|
28
|
+
end
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
def validate!
|
|
@@ -41,6 +49,16 @@ module Dotsync
|
|
|
41
49
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
42
50
|
raise "Configuration error in push mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
43
51
|
end
|
|
52
|
+
|
|
53
|
+
if mapping.is_a?(Hash) && mapping.key?("hooks") && mapping["hooks"].is_a?(Hash)
|
|
54
|
+
invalid_keys = mapping["hooks"].keys - ["post_push"]
|
|
55
|
+
if invalid_keys.any?
|
|
56
|
+
raise Dotsync::ConfigError,
|
|
57
|
+
"Configuration error in push mapping ##{index + 1}: " \
|
|
58
|
+
"Only 'post_push' hooks are allowed in [push] mappings. " \
|
|
59
|
+
"Invalid key(s): #{invalid_keys.join(", ")}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
44
62
|
end
|
|
45
63
|
end
|
|
46
64
|
end
|
data/lib/dotsync/errors.rb
CHANGED
data/lib/dotsync/icons.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Dotsync
|
|
|
18
18
|
DEFAULT_ONLY = " "
|
|
19
19
|
DEFAULT_IGNORE = " "
|
|
20
20
|
DEFAULT_INVALID = " "
|
|
21
|
+
DEFAULT_HOOK = " "
|
|
21
22
|
|
|
22
23
|
# Default Mappings Differences icons
|
|
23
24
|
DEFAULT_DIFF_CREATED = " "
|
|
@@ -50,6 +51,7 @@ module Dotsync
|
|
|
50
51
|
only: config.dig("icons", "only") || DEFAULT_ONLY,
|
|
51
52
|
ignore: config.dig("icons", "ignore") || DEFAULT_IGNORE,
|
|
52
53
|
invalid: config.dig("icons", "invalid") || DEFAULT_INVALID,
|
|
54
|
+
hook: config.dig("icons", "hook") || DEFAULT_HOOK,
|
|
53
55
|
# Differences Legend
|
|
54
56
|
diff_created: config.dig("icons", "diff_created") || DEFAULT_DIFF_CREATED,
|
|
55
57
|
diff_updated: config.dig("icons", "diff_updated") || DEFAULT_DIFF_UPDATED,
|
|
@@ -75,6 +77,10 @@ module Dotsync
|
|
|
75
77
|
@custom_icons[:invalid] || DEFAULT_INVALID
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
def self.hook
|
|
81
|
+
@custom_icons[:hook] || DEFAULT_HOOK
|
|
82
|
+
end
|
|
83
|
+
|
|
78
84
|
# Differences Legend methods
|
|
79
85
|
|
|
80
86
|
def self.diff_created
|
|
@@ -100,6 +106,7 @@ module Dotsync
|
|
|
100
106
|
diff: DIFF,
|
|
101
107
|
force: -> { force },
|
|
102
108
|
ignore: -> { ignore },
|
|
109
|
+
hook: DEFAULT_HOOK,
|
|
103
110
|
pull: PULL,
|
|
104
111
|
push: PUSH,
|
|
105
112
|
watch: WATCH,
|
|
@@ -13,6 +13,7 @@ require_relative "../utils/directory_differ"
|
|
|
13
13
|
require_relative "../utils/config_cache"
|
|
14
14
|
require_relative "../utils/content_diff"
|
|
15
15
|
require_relative "../utils/parallel"
|
|
16
|
+
require_relative "../utils/hook_runner"
|
|
16
17
|
|
|
17
18
|
# Models
|
|
18
19
|
require_relative "../models/mapping"
|
|
@@ -13,6 +13,7 @@ require_relative "../utils/directory_differ"
|
|
|
13
13
|
require_relative "../utils/config_cache"
|
|
14
14
|
require_relative "../utils/content_diff"
|
|
15
15
|
require_relative "../utils/parallel"
|
|
16
|
+
require_relative "../utils/hook_runner"
|
|
16
17
|
|
|
17
18
|
# Models
|
|
18
19
|
require_relative "../models/mapping"
|
|
@@ -29,6 +29,7 @@ module Dotsync
|
|
|
29
29
|
@original_ignores = Array(attributes["ignore"])
|
|
30
30
|
@original_only = Array(attributes["only"])
|
|
31
31
|
@force = attributes["force"] || false
|
|
32
|
+
@hooks = Array(attributes["hooks"])
|
|
32
33
|
|
|
33
34
|
@sanitized_src, @sanitized_dest, @sanitized_ignores, @sanitized_only = process_paths(
|
|
34
35
|
@original_src,
|
|
@@ -58,6 +59,12 @@ module Dotsync
|
|
|
58
59
|
@force
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
attr_reader :hooks
|
|
63
|
+
|
|
64
|
+
def has_hooks?
|
|
65
|
+
@hooks.any?
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
def directories?
|
|
62
69
|
File.directory?(src) && File.directory?(dest)
|
|
63
70
|
end
|
|
@@ -103,6 +110,7 @@ module Dotsync
|
|
|
103
110
|
msg << Icons.force if force?
|
|
104
111
|
msg << Icons.only if has_inclusions?
|
|
105
112
|
msg << Icons.ignore if has_ignores?
|
|
113
|
+
msg << Icons.hook if has_hooks?
|
|
106
114
|
msg << Icons.invalid unless valid?
|
|
107
115
|
msg.join
|
|
108
116
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
|
|
6
|
+
module Dotsync
|
|
7
|
+
class HookRunner
|
|
8
|
+
def initialize(mapping:, changed_files:, logger:)
|
|
9
|
+
@mapping = mapping
|
|
10
|
+
@changed_files = changed_files
|
|
11
|
+
@logger = logger
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
@mapping.hooks.map do |command|
|
|
16
|
+
expanded = expand_template(command)
|
|
17
|
+
run_command(expanded)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def preview
|
|
22
|
+
@mapping.hooks.map { |command| expand_template(command) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def expand_template(command)
|
|
27
|
+
files_str = @changed_files.map { |f| Shellwords.escape(f) }.join(" ")
|
|
28
|
+
|
|
29
|
+
command
|
|
30
|
+
.gsub("{files}", files_str)
|
|
31
|
+
.gsub("{src}", @mapping.src)
|
|
32
|
+
.gsub("{dest}", @mapping.dest)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run_command(command)
|
|
36
|
+
stdout, stderr, status = Open3.capture3(command)
|
|
37
|
+
|
|
38
|
+
if status.success?
|
|
39
|
+
@logger.info("Hook succeeded: #{command}", icon: :hook)
|
|
40
|
+
else
|
|
41
|
+
@logger.error("Hook failed: #{command}")
|
|
42
|
+
@logger.error(" #{stderr.strip}") unless stderr.strip.empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
{ command: command, stdout: stdout, stderr: stderr, success: status.success? }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/dotsync/version.rb
CHANGED
data/lib/dotsync.rb
CHANGED
|
@@ -32,6 +32,7 @@ require_relative "dotsync/utils/directory_differ"
|
|
|
32
32
|
require_relative "dotsync/utils/version_checker"
|
|
33
33
|
require_relative "dotsync/utils/config_cache"
|
|
34
34
|
require_relative "dotsync/utils/parallel"
|
|
35
|
+
require_relative "dotsync/utils/hook_runner"
|
|
35
36
|
|
|
36
37
|
# Models
|
|
37
38
|
require_relative "dotsync/models/mapping"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dotsync
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Sáenz
|
|
@@ -337,6 +337,7 @@ files:
|
|
|
337
337
|
- lib/dotsync/utils/content_diff.rb
|
|
338
338
|
- lib/dotsync/utils/directory_differ.rb
|
|
339
339
|
- lib/dotsync/utils/file_transfer.rb
|
|
340
|
+
- lib/dotsync/utils/hook_runner.rb
|
|
340
341
|
- lib/dotsync/utils/logger.rb
|
|
341
342
|
- lib/dotsync/utils/parallel.rb
|
|
342
343
|
- lib/dotsync/utils/path_utils.rb
|