dotsync 0.1.25 → 0.1.26
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/.github/workflows/{gem-push.yml → ci.yml} +1 -1
- data/.github/workflows/release.yml +50 -0
- data/.gitignore +2 -0
- data/.pre-commit-config.yaml +9 -0
- data/CHANGELOG.md +33 -1
- data/Gemfile.lock +1 -1
- data/README.md +213 -88
- data/Rakefile +123 -9
- data/lib/dotsync/config/concerns/sync_mappings.rb +191 -0
- data/lib/dotsync/config/pull_action_config.rb +23 -5
- data/lib/dotsync/config/push_action_config.rb +24 -5
- data/lib/dotsync/loaders/pull_loader.rb +4 -0
- data/lib/dotsync/loaders/push_loader.rb +3 -0
- data/lib/dotsync/runner.rb +1 -0
- data/lib/dotsync/version.rb +1 -1
- data/lib/dotsync.rb +1 -0
- metadata +7 -8
- data/.github/workflows/gem-push.yml.bak +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89c25a6529e3557dc1383f3facf677f0775053d7db53863b7cc61a5b44d13b11
|
|
4
|
+
data.tar.gz: 1cfadbe1c539923fb6bf9c27eafde24beeab527feeaadf2b88d997d762f972b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b7012240c48cd7b9d84cf8e20d54efa913fd08324720b08d270db0c39e46f87a1f63f124e6d562d06ab2bef7f0fd017bb686e666b40866fc89eceae831de1bf
|
|
7
|
+
data.tar.gz: 87f03124b8dc8cad8c3588d3391858414ac0a577416c7a227465702a081c8de9fa36648e5eb5c642ba9b6beae81c19043259c25b536a634cd3bfcd3b83a7cfc9
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v6
|
|
18
|
+
|
|
19
|
+
- name: Get version from tag
|
|
20
|
+
id: version
|
|
21
|
+
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
|
22
|
+
|
|
23
|
+
- name: Extract changelog for version
|
|
24
|
+
id: changelog
|
|
25
|
+
run: |
|
|
26
|
+
# Extract the section for this version from CHANGELOG.md
|
|
27
|
+
# Matches from "## [X.Y.Z]" until the next "## [" or end of file
|
|
28
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
29
|
+
CHANGELOG=$(awk -v ver="$VERSION" '
|
|
30
|
+
/^## \[/ {
|
|
31
|
+
if (found) exit
|
|
32
|
+
if ($0 ~ "\\[" ver "\\]") found=1
|
|
33
|
+
}
|
|
34
|
+
found { print }
|
|
35
|
+
' CHANGELOG.md | tail -n +2)
|
|
36
|
+
|
|
37
|
+
# Use heredoc for multiline output
|
|
38
|
+
echo "content<<EOF" >> $GITHUB_OUTPUT
|
|
39
|
+
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
|
40
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
41
|
+
|
|
42
|
+
- name: Create GitHub Release
|
|
43
|
+
uses: softprops/action-gh-release@v2
|
|
44
|
+
with:
|
|
45
|
+
name: v${{ steps.version.outputs.version }}
|
|
46
|
+
body: ${{ steps.changelog.outputs.content }}
|
|
47
|
+
draft: false
|
|
48
|
+
prerelease: false
|
|
49
|
+
env:
|
|
50
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
## [0.1.26] - 2025-01-11
|
|
2
|
+
|
|
3
|
+
**Breaking Changes:**
|
|
4
|
+
- The explicit sync mapping syntax has changed from `[[sync]]` to `[[sync.mappings]]`
|
|
5
|
+
- Old: `[[sync]]` with `local`/`remote` keys
|
|
6
|
+
- New: `[[sync.mappings]]` with `local`/`remote` keys
|
|
7
|
+
- This allows combining explicit mappings with XDG shorthands in the same config
|
|
8
|
+
- See README for migration examples
|
|
9
|
+
|
|
10
|
+
**New Features:**
|
|
11
|
+
- Add bidirectional `[[sync.mappings]]` DSL for two-way synchronization
|
|
12
|
+
- Simplified syntax replaces separate push/pull mappings
|
|
13
|
+
- Automatic expansion to bidirectional mappings (local ↔ remote)
|
|
14
|
+
- Supports all existing options: `force`, `only`, `ignore`
|
|
15
|
+
- Add XDG shorthand DSL for sync mappings
|
|
16
|
+
- `[[sync.home]]` - syncs $HOME ↔ $HOME_MIRROR
|
|
17
|
+
- `[[sync.xdg_config]]` - syncs $XDG_CONFIG_HOME ↔ $XDG_CONFIG_HOME_MIRROR
|
|
18
|
+
- `[[sync.xdg_data]]` - syncs $XDG_DATA_HOME ↔ $XDG_DATA_HOME_MIRROR
|
|
19
|
+
- `[[sync.xdg_cache]]` - syncs $XDG_CACHE_HOME ↔ $XDG_CACHE_HOME_MIRROR
|
|
20
|
+
- `[[sync.xdg_bin]]` - syncs $XDG_BIN_HOME ↔ $XDG_BIN_HOME_MIRROR (new)
|
|
21
|
+
- Use `path` for specific subdirectories or `only` for multiple paths
|
|
22
|
+
- Fix custom config path (`-c` flag) not being applied to Runner
|
|
23
|
+
|
|
24
|
+
**Documentation:**
|
|
25
|
+
- Document bidirectional sync mappings with examples
|
|
26
|
+
- Document XDG shorthand DSL with usage examples
|
|
27
|
+
- Update README with new configuration options and supported shorthands table
|
|
28
|
+
|
|
29
|
+
**Infrastructure:**
|
|
30
|
+
- Rename GitHub workflow to ci.yml
|
|
31
|
+
- Add sync_mappings concern to push and pull loaders
|
|
32
|
+
|
|
33
|
+
## [0.1.25]
|
|
2
34
|
|
|
3
35
|
**Features:**
|
|
4
36
|
- Add support for file-specific paths in 'only' configuration option
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles across machines. Whether you're setting up a new development environment or keeping configurations in sync, Dotsync makes it effortless.
|
|
14
14
|
|
|
15
15
|
**Key Features:**
|
|
16
|
-
- **Bidirectional Sync**:
|
|
16
|
+
- **Bidirectional Sync Mappings**: Define once, sync both ways — eliminates config duplication with `[[sync.mappings]]` syntax
|
|
17
|
+
- **XDG Shorthand DSL**: Concise `[[sync.home]]`, `[[sync.xdg_config]]`, `[[sync.xdg_data]]`, `[[sync.xdg_bin]]` syntax for common directory patterns
|
|
17
18
|
- **Preview Mode**: See what changes would be made before applying them (dry-run by default)
|
|
18
19
|
- **Smart Filtering**: Use `force`, `only`, and `ignore` options to precisely control what gets synced
|
|
19
20
|
- **Automatic Backups**: Pull operations create timestamped backups for easy recovery
|
|
@@ -29,6 +30,9 @@ Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles acro
|
|
|
29
30
|
- [Usage](#usage)
|
|
30
31
|
- [Executable Commands](#executable-commands)
|
|
31
32
|
- [Configuration](#configuration)
|
|
33
|
+
- [Bidirectional Sync Mappings (Recommended)](#bidirectional-sync-mappings-recommended)
|
|
34
|
+
- [Alternative: Unidirectional Mappings](#alternative-unidirectional-mappings)
|
|
35
|
+
- [Mapping Options (force, only, ignore)](#force-only-and-ignore-options-in-mappings)
|
|
32
36
|
- [Safety Features](#safety-features)
|
|
33
37
|
- [Customizing Icons](#customizing-icons)
|
|
34
38
|
- [Automatic Update Checks](#automatic-update-checks)
|
|
@@ -76,22 +80,29 @@ Get started with Dotsync in just a few steps:
|
|
|
76
80
|
```
|
|
77
81
|
This creates `~/.config/dotsync.toml` with example mappings.
|
|
78
82
|
|
|
79
|
-
3. **Edit the configuration** (`~/.config/dotsync.toml`) to define your dotfile mappings:
|
|
83
|
+
3. **Edit the configuration** (`~/.config/dotsync.toml`) to define your dotfile mappings using bidirectional sync:
|
|
80
84
|
```toml
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
# Sync your shell config
|
|
86
|
+
[[sync.home]]
|
|
87
|
+
path = ".zshenv"
|
|
88
|
+
|
|
89
|
+
# Sync XDG config directories
|
|
90
|
+
[[sync.xdg_config]]
|
|
91
|
+
only = ["nvim", "alacritty", "zsh", "git"]
|
|
92
|
+
force = true
|
|
84
93
|
```
|
|
85
94
|
|
|
86
95
|
4. **Preview your changes** (dry-run mode):
|
|
87
96
|
```shell
|
|
88
|
-
dotsync pull
|
|
97
|
+
dotsync pull # Preview pulling from repo to local
|
|
98
|
+
dotsync push # Preview pushing from local to repo
|
|
89
99
|
```
|
|
90
100
|
This shows what would be changed without modifying any files.
|
|
91
101
|
|
|
92
102
|
5. **Apply changes** when you're ready:
|
|
93
103
|
```shell
|
|
94
|
-
dotsync pull --apply
|
|
104
|
+
dotsync pull --apply # Apply repo → local
|
|
105
|
+
dotsync push --apply # Apply local → repo
|
|
95
106
|
```
|
|
96
107
|
|
|
97
108
|
## Usage
|
|
@@ -127,7 +138,7 @@ Dotsync provides the following commands to manage your dotfiles:
|
|
|
127
138
|
```shell
|
|
128
139
|
dotsync watch [OPTIONS]
|
|
129
140
|
```
|
|
130
|
-
|
|
141
|
+
|
|
131
142
|
The watch command supports the same output control options as push and pull (e.g., `--quiet`, `--no-legend`, `--no-mappings`).
|
|
132
143
|
|
|
133
144
|
- **Setup** (alias: **init**): Generate a default configuration file at `~/.config/dotsync.toml` with example mappings for `pull`, `push`, and `watch`.
|
|
@@ -212,53 +223,162 @@ dotsync watch --quiet # Watch with minimal output
|
|
|
212
223
|
|
|
213
224
|
### Configuration
|
|
214
225
|
|
|
215
|
-
|
|
226
|
+
Dotsync uses TOML configuration files to define mappings between your local machine and your dotfiles repository. The recommended approach is **bidirectional sync mappings**, which eliminate duplication and keep your config clean.
|
|
227
|
+
|
|
228
|
+
> [!TIP]
|
|
229
|
+
> Set up mirror environment variables for cleaner configuration:
|
|
230
|
+
> ```bash
|
|
231
|
+
> # Add to your ~/.zshrc or ~/.bashrc
|
|
232
|
+
> export DOTFILES_DIR="$HOME/Code/dotfiles"
|
|
233
|
+
> export XDG_CONFIG_HOME_MIRROR="$DOTFILES_DIR/xdg_config_home"
|
|
234
|
+
> export XDG_DATA_HOME_MIRROR="$DOTFILES_DIR/xdg_data_home"
|
|
235
|
+
> export HOME_MIRROR="$DOTFILES_DIR/home"
|
|
236
|
+
> ```
|
|
237
|
+
|
|
238
|
+
#### Bidirectional Sync Mappings (Recommended)
|
|
239
|
+
|
|
240
|
+
Use `[[sync]]` mappings to define paths that sync in both directions. This is the **preferred approach** as it eliminates duplication between push and pull configurations.
|
|
241
|
+
|
|
242
|
+
##### XDG Shorthand Syntax
|
|
243
|
+
|
|
244
|
+
The most concise way to define mappings for standard XDG directories:
|
|
216
245
|
|
|
217
246
|
```toml
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
ignore = ["nvim"]
|
|
247
|
+
# Sync home directory files
|
|
248
|
+
[[sync.home]]
|
|
249
|
+
path = ".zshenv"
|
|
222
250
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
251
|
+
# Sync multiple configs from XDG_CONFIG_HOME
|
|
252
|
+
[[sync.xdg_config]]
|
|
253
|
+
only = ["alacritty", "git", "zsh", "starship.toml"]
|
|
254
|
+
force = true
|
|
255
|
+
|
|
256
|
+
# Sync specific config with custom options
|
|
257
|
+
[[sync.xdg_config]]
|
|
258
|
+
path = "nvim"
|
|
227
259
|
force = true
|
|
228
|
-
# FEATURE: use relative paths to "dest" to ignore files and folders
|
|
229
260
|
ignore = ["lazy-lock.json"]
|
|
230
261
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
262
|
+
# Sync XDG data directories
|
|
263
|
+
[[sync.xdg_data]]
|
|
264
|
+
path = "git"
|
|
265
|
+
force = true
|
|
266
|
+
```
|
|
234
267
|
|
|
268
|
+
**Supported shorthands:**
|
|
235
269
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
270
|
+
| Shorthand | Local | Remote |
|
|
271
|
+
|-----------|-------|--------|
|
|
272
|
+
| `sync.home` | `$HOME` | `$HOME_MIRROR` |
|
|
273
|
+
| `sync.xdg_config` | `$XDG_CONFIG_HOME` | `$XDG_CONFIG_HOME_MIRROR` |
|
|
274
|
+
| `sync.xdg_data` | `$XDG_DATA_HOME` | `$XDG_DATA_HOME_MIRROR` |
|
|
275
|
+
| `sync.xdg_cache` | `$XDG_CACHE_HOME` | `$XDG_CACHE_HOME_MIRROR` |
|
|
276
|
+
| `sync.xdg_bin` | `$XDG_BIN_HOME` | `$XDG_BIN_HOME_MIRROR` |
|
|
239
277
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
# FEATURE: transfer only relative paths of files and folders passed here
|
|
244
|
-
only = ["alacritty.toml", "rose-pine.toml"]
|
|
278
|
+
**Options:**
|
|
279
|
+
- `path` (optional): Relative path within the directory. If omitted, syncs the entire directory.
|
|
280
|
+
- `force`, `ignore`, `only`: All standard mapping options are supported.
|
|
245
281
|
|
|
282
|
+
##### Explicit Sync Syntax
|
|
246
283
|
|
|
247
|
-
[[
|
|
248
|
-
|
|
249
|
-
|
|
284
|
+
For custom paths that don't follow XDG conventions, use explicit `[[sync.mappings]]` entries:
|
|
285
|
+
|
|
286
|
+
```toml
|
|
287
|
+
[[sync.mappings]]
|
|
288
|
+
local = "$XDG_CONFIG_HOME/nvim"
|
|
289
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/nvim"
|
|
290
|
+
force = true
|
|
291
|
+
ignore = ["lazy-lock.json"]
|
|
292
|
+
|
|
293
|
+
[[sync.mappings]]
|
|
294
|
+
local = "$HOME/.zshenv"
|
|
295
|
+
remote = "$HOME_MIRROR/.zshenv"
|
|
296
|
+
|
|
297
|
+
# Sync config file to a different location in repo
|
|
298
|
+
[[sync.mappings]]
|
|
299
|
+
local = "$XDG_CONFIG_HOME/dotsync.toml"
|
|
300
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.macbook.toml"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**How it works:**
|
|
304
|
+
- `local` is your local machine path (e.g., `~/.config/nvim`)
|
|
305
|
+
- `remote` is your dotfiles repository path (e.g., `~/dotfiles/config/nvim`)
|
|
306
|
+
- For **push** operations: `local` → `remote`
|
|
307
|
+
- For **pull** operations: `remote` → `local`
|
|
308
|
+
- All standard options (`force`, `ignore`, `only`) are supported
|
|
309
|
+
|
|
310
|
+
##### Complete Example
|
|
311
|
+
|
|
312
|
+
Here's a real-world configuration using bidirectional sync:
|
|
313
|
+
|
|
314
|
+
```toml
|
|
315
|
+
## BIDIRECTIONAL SYNC CONFIGURATION
|
|
316
|
+
|
|
317
|
+
# Home directory files
|
|
318
|
+
[[sync.home]]
|
|
319
|
+
path = ".zshenv"
|
|
320
|
+
|
|
321
|
+
# Bulk sync multiple configs
|
|
322
|
+
[[sync.xdg_config]]
|
|
323
|
+
only = [
|
|
324
|
+
"alacritty",
|
|
325
|
+
"brewfile",
|
|
326
|
+
"git",
|
|
327
|
+
"lazygit",
|
|
328
|
+
"tmux",
|
|
329
|
+
"zellij",
|
|
330
|
+
"starship.toml"
|
|
331
|
+
]
|
|
332
|
+
force = true
|
|
333
|
+
|
|
334
|
+
# Zsh with ignored files
|
|
335
|
+
[[sync.xdg_config]]
|
|
336
|
+
path = "zsh"
|
|
337
|
+
ignore = [".zsh_sessions", ".zsh_history", ".zcompdump"]
|
|
338
|
+
|
|
339
|
+
# Neovim with force sync
|
|
340
|
+
[[sync.xdg_config]]
|
|
341
|
+
path = "nvim"
|
|
342
|
+
force = true
|
|
343
|
+
ignore = ["lazy-lock.json"]
|
|
344
|
+
|
|
345
|
+
# Git templates in data directory
|
|
346
|
+
[[sync.xdg_data]]
|
|
347
|
+
path = "git"
|
|
348
|
+
force = true
|
|
349
|
+
|
|
350
|
+
# This config file itself
|
|
351
|
+
[[sync.mappings]]
|
|
352
|
+
local = "$XDG_CONFIG_HOME/dotsync.toml"
|
|
353
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.macbook.toml"
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### Alternative: Unidirectional Mappings
|
|
357
|
+
|
|
358
|
+
For asymmetric sync scenarios where push and pull need different configurations, you can use separate `[[push.mappings]]` and `[[pull.mappings]]` sections:
|
|
250
359
|
|
|
360
|
+
```toml
|
|
361
|
+
# Pull from repo to local (repo → local)
|
|
362
|
+
[[pull.mappings]]
|
|
363
|
+
src = "$XDG_CONFIG_HOME_MIRROR/nvim"
|
|
364
|
+
dest = "$XDG_CONFIG_HOME/nvim"
|
|
365
|
+
force = true
|
|
366
|
+
ignore = ["lazy-lock.json"]
|
|
367
|
+
|
|
368
|
+
# Push from local to repo (local → repo)
|
|
369
|
+
[[push.mappings]]
|
|
370
|
+
src = "$XDG_CONFIG_HOME/alacritty"
|
|
371
|
+
dest = "$XDG_CONFIG_HOME_MIRROR/alacritty"
|
|
372
|
+
only = ["alacritty.toml", "themes"]
|
|
373
|
+
|
|
374
|
+
# Watch for live syncing
|
|
251
375
|
[[watch.mappings]]
|
|
252
|
-
src = "$XDG_CONFIG_HOME/
|
|
253
|
-
dest = "$
|
|
376
|
+
src = "$XDG_CONFIG_HOME/nvim"
|
|
377
|
+
dest = "$XDG_CONFIG_HOME_MIRROR/nvim"
|
|
254
378
|
```
|
|
255
379
|
|
|
256
|
-
> [!
|
|
257
|
-
>
|
|
258
|
-
>
|
|
259
|
-
> ```bash
|
|
260
|
-
> export XDG_CONFIG_HOME_MIRROR="$HOME/Code/dotfiles/xdg_config_home"
|
|
261
|
-
> ```
|
|
380
|
+
> [!NOTE]
|
|
381
|
+
> You can mix `[[sync.mappings]]`, XDG shorthands (`[[sync.home]]`, `[[sync.xdg_config]]`, etc.), and `[[push.mappings]]`/`[[pull.mappings]]` in the same config file. Use bidirectional sync for symmetric mappings and unidirectional for special cases.
|
|
262
382
|
|
|
263
383
|
#### `force`, `only`, and `ignore` Options in Mappings
|
|
264
384
|
|
|
@@ -270,9 +390,8 @@ A boolean (true/false) value. When set to `true`, it forces deletion of files in
|
|
|
270
390
|
|
|
271
391
|
**Example:**
|
|
272
392
|
```toml
|
|
273
|
-
[[
|
|
274
|
-
|
|
275
|
-
dest = "$XDG_CONFIG_HOME/nvim"
|
|
393
|
+
[[sync.xdg_config]]
|
|
394
|
+
path = "nvim"
|
|
276
395
|
force = true
|
|
277
396
|
ignore = ["lazy-lock.json"]
|
|
278
397
|
```
|
|
@@ -293,41 +412,34 @@ An array of relative paths (files or directories) to selectively transfer from t
|
|
|
293
412
|
|
|
294
413
|
**Example 1: Selecting specific directories**
|
|
295
414
|
```toml
|
|
296
|
-
[[
|
|
297
|
-
src = "$XDG_CONFIG_HOME"
|
|
298
|
-
dest = "$DOTFILES_DIR/config"
|
|
415
|
+
[[sync.xdg_config]]
|
|
299
416
|
only = ["nvim", "alacritty", "zsh"]
|
|
300
417
|
```
|
|
301
418
|
This transfers only the `nvim/`, `alacritty/`, and `zsh/` directories.
|
|
302
419
|
|
|
303
420
|
**Example 2: Selecting specific files**
|
|
304
421
|
```toml
|
|
305
|
-
[[
|
|
306
|
-
|
|
307
|
-
dest = "$DOTFILES_DIR/config/alacritty"
|
|
422
|
+
[[sync.xdg_config]]
|
|
423
|
+
path = "alacritty"
|
|
308
424
|
only = ["alacritty.toml", "rose-pine.toml"]
|
|
309
425
|
```
|
|
310
426
|
This transfers only two specific TOML files from the alacritty config directory.
|
|
311
427
|
|
|
312
428
|
**Example 3: Selecting files inside nested directories**
|
|
313
429
|
```toml
|
|
314
|
-
[[
|
|
315
|
-
src = "$HOME/.config"
|
|
316
|
-
dest = "$DOTFILES_DIR/config"
|
|
430
|
+
[[sync.xdg_config]]
|
|
317
431
|
only = ["bundle/config", "ghc/ghci.conf", "cabal/config"]
|
|
318
432
|
```
|
|
319
433
|
This transfers only specific configuration files from different subdirectories:
|
|
320
434
|
- `bundle/config` file from the `bundle/` directory
|
|
321
|
-
- `ghc/ghci.conf` file from the `ghc/` directory
|
|
435
|
+
- `ghc/ghci.conf` file from the `ghc/` directory
|
|
322
436
|
- `cabal/config` file from the `cabal/` directory
|
|
323
437
|
|
|
324
438
|
The parent directories (`bundle/`, `ghc/`, `cabal/`) are created automatically in the destination, but other files in those directories are not transferred.
|
|
325
439
|
|
|
326
440
|
**Example 4: Deeply nested paths**
|
|
327
441
|
```toml
|
|
328
|
-
[[
|
|
329
|
-
src = "$XDG_CONFIG_HOME"
|
|
330
|
-
dest = "$DOTFILES_DIR/config"
|
|
442
|
+
[[sync.xdg_config]]
|
|
331
443
|
only = ["nvim/lua/plugins/init.lua", "nvim/lua/config/settings.lua"]
|
|
332
444
|
```
|
|
333
445
|
This transfers only specific Lua files from deeply nested paths within the nvim configuration.
|
|
@@ -343,17 +455,15 @@ An array of relative paths or patterns to exclude during transfer. This allows y
|
|
|
343
455
|
|
|
344
456
|
**Example:**
|
|
345
457
|
```toml
|
|
346
|
-
[[
|
|
347
|
-
|
|
348
|
-
dest = "$XDG_CONFIG_HOME/nvim"
|
|
458
|
+
[[sync.xdg_config]]
|
|
459
|
+
path = "nvim"
|
|
349
460
|
ignore = ["lazy-lock.json", "plugin/packer_compiled.lua"]
|
|
350
461
|
```
|
|
351
462
|
|
|
352
463
|
**Combining options:**
|
|
353
464
|
```toml
|
|
354
|
-
[[
|
|
355
|
-
|
|
356
|
-
dest = "$DOTFILES_DIR/config/nvim"
|
|
465
|
+
[[sync.xdg_config]]
|
|
466
|
+
path = "nvim"
|
|
357
467
|
only = ["lua", "init.lua"]
|
|
358
468
|
ignore = ["lua/plugin/packer_compiled.lua"]
|
|
359
469
|
force = true
|
|
@@ -426,7 +536,7 @@ By default, all `push` and `pull` commands run in preview mode:
|
|
|
426
536
|
|
|
427
537
|
Dotsync provides clear, actionable error messages for common issues:
|
|
428
538
|
|
|
429
|
-
- **Permission Errors**:
|
|
539
|
+
- **Permission Errors**:
|
|
430
540
|
```
|
|
431
541
|
Permission denied: /path/to/file
|
|
432
542
|
Try: chmod +w <path> or check file permissions
|
|
@@ -496,10 +606,8 @@ diff_created = "✨" # New files created
|
|
|
496
606
|
diff_updated = "📝" # Files modified
|
|
497
607
|
diff_removed = "🗑️ " # Files deleted
|
|
498
608
|
|
|
499
|
-
# Example
|
|
500
|
-
[[
|
|
501
|
-
src = "$XDG_CONFIG_HOME_MIRROR"
|
|
502
|
-
dest = "$XDG_CONFIG_HOME"
|
|
609
|
+
# Example sync mapping
|
|
610
|
+
[[sync.xdg_config]]
|
|
503
611
|
ignore = ["cache"]
|
|
504
612
|
```
|
|
505
613
|
|
|
@@ -550,10 +658,10 @@ The check runs after your command completes and uses a cached timestamp to avoid
|
|
|
550
658
|
```bash
|
|
551
659
|
# Work dotfiles
|
|
552
660
|
dotsync -c ~/work-dotfiles.toml push --apply
|
|
553
|
-
|
|
661
|
+
|
|
554
662
|
# Personal dotfiles
|
|
555
663
|
dotsync -c ~/.config/personal.toml pull --apply
|
|
556
|
-
|
|
664
|
+
|
|
557
665
|
# Server configs
|
|
558
666
|
dotsync --config ~/server.toml push --apply
|
|
559
667
|
```
|
|
@@ -562,7 +670,7 @@ The check runs after your command completes and uses a cached timestamp to avoid
|
|
|
562
670
|
```shell
|
|
563
671
|
# In a script or CI/CD pipeline
|
|
564
672
|
dotsync pull --apply --yes --quiet
|
|
565
|
-
|
|
673
|
+
|
|
566
674
|
# Shorthand
|
|
567
675
|
dotsync push -ayq
|
|
568
676
|
```
|
|
@@ -604,14 +712,13 @@ The check runs after your command completes and uses a cached timestamp to avoid
|
|
|
604
712
|
|
|
605
713
|
## Common Use Cases
|
|
606
714
|
|
|
607
|
-
Here are
|
|
715
|
+
Here are practical examples using bidirectional sync for popular configuration files:
|
|
608
716
|
|
|
609
717
|
### Syncing Neovim Configuration
|
|
610
718
|
|
|
611
719
|
```toml
|
|
612
|
-
[[
|
|
613
|
-
|
|
614
|
-
dest = "$HOME/.config/nvim"
|
|
720
|
+
[[sync.xdg_config]]
|
|
721
|
+
path = "nvim"
|
|
615
722
|
force = true
|
|
616
723
|
ignore = ["lazy-lock.json", ".luarc.json"]
|
|
617
724
|
```
|
|
@@ -619,31 +726,49 @@ ignore = ["lazy-lock.json", ".luarc.json"]
|
|
|
619
726
|
### Syncing Terminal Emulator (Alacritty)
|
|
620
727
|
|
|
621
728
|
```toml
|
|
622
|
-
[[
|
|
623
|
-
|
|
624
|
-
dest = "$HOME/dotfiles/config/alacritty"
|
|
729
|
+
[[sync.xdg_config]]
|
|
730
|
+
path = "alacritty"
|
|
625
731
|
only = ["alacritty.toml", "themes"]
|
|
626
732
|
```
|
|
627
733
|
|
|
628
734
|
### Syncing Shell Configuration
|
|
629
735
|
|
|
630
736
|
```toml
|
|
631
|
-
[[
|
|
632
|
-
|
|
633
|
-
dest = "$HOME"
|
|
737
|
+
[[sync.home]]
|
|
738
|
+
path = ".zshenv"
|
|
634
739
|
|
|
635
|
-
[[
|
|
636
|
-
|
|
637
|
-
|
|
740
|
+
[[sync.xdg_config]]
|
|
741
|
+
path = "zsh"
|
|
742
|
+
ignore = [".zsh_sessions", ".zsh_history", ".zcompdump"]
|
|
638
743
|
```
|
|
639
744
|
|
|
640
745
|
### Syncing Multiple Config Directories
|
|
641
746
|
|
|
642
747
|
```toml
|
|
643
|
-
[[
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
748
|
+
[[sync.xdg_config]]
|
|
749
|
+
only = ["nvim", "alacritty", "git", "zsh", "tmux", "starship.toml"]
|
|
750
|
+
force = true
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Per-Machine Configuration Files
|
|
754
|
+
|
|
755
|
+
Use different config files for different machines:
|
|
756
|
+
|
|
757
|
+
```toml
|
|
758
|
+
# In dotsync.macbook.toml
|
|
759
|
+
[[sync.mappings]]
|
|
760
|
+
local = "$XDG_CONFIG_HOME/dotsync.toml"
|
|
761
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.macbook.toml"
|
|
762
|
+
|
|
763
|
+
# In dotsync.work.toml
|
|
764
|
+
[[sync.mappings]]
|
|
765
|
+
local = "$XDG_CONFIG_HOME/dotsync.toml"
|
|
766
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.work.toml"
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
Then use `-c` flag to select the appropriate config:
|
|
770
|
+
```shell
|
|
771
|
+
dotsync -c ~/.config/dotsync/dotsync.macbook.toml push --apply
|
|
647
772
|
```
|
|
648
773
|
|
|
649
774
|
## Troubleshooting
|
|
@@ -652,7 +777,7 @@ ignore = ["nvim", "cache", "*.log"]
|
|
|
652
777
|
|
|
653
778
|
**Problem**: Icons appear as boxes, question marks, or strange characters.
|
|
654
779
|
|
|
655
|
-
**Solution**:
|
|
780
|
+
**Solution**:
|
|
656
781
|
- Install a [Nerd Font](https://www.nerdfonts.com/) and configure your terminal to use it
|
|
657
782
|
- Or customize icons in `~/.config/dotsync.toml` using UTF-8 emojis or regular characters:
|
|
658
783
|
```toml
|
data/Rakefile
CHANGED
|
@@ -15,23 +15,137 @@ end
|
|
|
15
15
|
task default: :spec
|
|
16
16
|
|
|
17
17
|
namespace :release do
|
|
18
|
-
desc "
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
version =
|
|
18
|
+
desc "Generate CHANGELOG entry for a new version"
|
|
19
|
+
# Usage: rake release:changelog[0.2.1]
|
|
20
|
+
task :changelog, [:version] do |_t, args|
|
|
21
|
+
version = args[:version]
|
|
22
|
+
unless version
|
|
23
|
+
require_relative "./lib/dotsync/version"
|
|
24
|
+
version = Dotsync::VERSION
|
|
25
|
+
end
|
|
26
|
+
version = version.sub(/^v/, "")
|
|
27
|
+
today = Date.today.strftime("%Y-%m-%d")
|
|
28
|
+
|
|
29
|
+
latest_tag = `git describe --tags --abbrev=0 2>/dev/null`.strip
|
|
30
|
+
commits = if latest_tag.empty?
|
|
31
|
+
`git log --oneline --no-decorate`.strip.split("\n")
|
|
32
|
+
else
|
|
33
|
+
`git log #{latest_tag}..HEAD --oneline --no-decorate`.strip.split("\n")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if commits.empty?
|
|
37
|
+
abort "No commits since #{latest_tag}. Nothing to release."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
categories = { "Added" => [], "Changed" => [], "Fixed" => [], "Removed" => [], "Security" => [], "Dependencies" => [] }
|
|
41
|
+
|
|
42
|
+
commits.each do |commit|
|
|
43
|
+
message = commit.sub(/^[a-f0-9]+\s+/, "")
|
|
44
|
+
case message.downcase
|
|
45
|
+
when /^add|^feat|^implement|^create|^new|^support/i then categories["Added"] << message
|
|
46
|
+
when /^fix|^bugfix|^hotfix|^resolve|^correct/i then categories["Fixed"] << message
|
|
47
|
+
when /^remove|^delete|^drop/i then categories["Removed"] << message
|
|
48
|
+
when /^security|^vuln|^cve/i then categories["Security"] << message
|
|
49
|
+
when /^bump|^upgrade|^update.*dependency|^dep/i then categories["Dependencies"] << message
|
|
50
|
+
else categories["Changed"] << message
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
changelog_entry = "## [#{version}] - #{today}\n"
|
|
55
|
+
categories.each do |category, items|
|
|
56
|
+
next if items.empty?
|
|
57
|
+
changelog_entry += "\n### #{category}\n\n"
|
|
58
|
+
items.each { |item| changelog_entry += "- #{item}\n" }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
changelog_path = "CHANGELOG.md"
|
|
62
|
+
abort "CHANGELOG.md not found." unless File.exist?(changelog_path)
|
|
63
|
+
|
|
64
|
+
changelog = File.read(changelog_path)
|
|
65
|
+
abort "Version #{version} already exists in CHANGELOG.md" if changelog.include?("[#{version}]")
|
|
66
|
+
|
|
67
|
+
match = changelog.match(/^## \[/m)
|
|
68
|
+
new_changelog = match ? changelog.sub(/^## \[/m, "#{changelog_entry}\n## [") : changelog.rstrip + "\n\n#{changelog_entry}"
|
|
69
|
+
|
|
70
|
+
repo_url = `git remote get-url origin`.strip.sub(/\.git$/, "").sub(/^git@github\.com:/, "https://github.com/")
|
|
71
|
+
previous_version = latest_tag.empty? ? "v0.0.0" : latest_tag
|
|
72
|
+
link_entry = "[#{version}]: #{repo_url}/compare/#{previous_version}...v#{version}"
|
|
73
|
+
|
|
74
|
+
new_changelog = if new_changelog.match?(/^\[[\d.]+\]:.*compare/m)
|
|
75
|
+
new_changelog.sub(/^(\[[\d.]+\]:.*compare)/m, "#{link_entry}\n\\1")
|
|
76
|
+
else
|
|
77
|
+
new_changelog.rstrip + "\n\n#{link_entry}\n"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
File.write(changelog_path, new_changelog)
|
|
81
|
+
puts "Updated CHANGELOG.md with version #{version}"
|
|
82
|
+
puts "\n#{changelog_entry}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc "Create annotated git tag from CHANGELOG"
|
|
86
|
+
# Usage: rake release:tag[0.2.1] or just rake release:tag (uses Dotsync::VERSION)
|
|
87
|
+
task :tag, [:version] do |_t, args|
|
|
88
|
+
version = args[:version]
|
|
89
|
+
unless version
|
|
90
|
+
require_relative "./lib/dotsync/version"
|
|
91
|
+
version = Dotsync::VERSION
|
|
92
|
+
end
|
|
93
|
+
version = version.sub(/^v/, "")
|
|
22
94
|
tag_name = "v#{version}"
|
|
23
95
|
|
|
24
|
-
# Check if tag already exists
|
|
25
96
|
if `git tag --list`.split.include?(tag_name)
|
|
26
97
|
puts "Tag #{tag_name} already exists."
|
|
27
98
|
exit(1)
|
|
28
99
|
end
|
|
29
100
|
|
|
101
|
+
changelog_path = "CHANGELOG.md"
|
|
102
|
+
abort "CHANGELOG.md not found." unless File.exist?(changelog_path)
|
|
103
|
+
|
|
104
|
+
changelog = File.read(changelog_path)
|
|
105
|
+
version_regex = /^## \[#{Regexp.escape(version)}\] - (\d{4}-\d{2}-\d{2})\n(.*?)(?=^## \[|\z)/m
|
|
106
|
+
match = changelog.match(version_regex)
|
|
107
|
+
|
|
108
|
+
unless match
|
|
109
|
+
abort "Version #{version} not found in CHANGELOG.md\nRun 'rake release:changelog[#{version}]' first."
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
date = match[1]
|
|
113
|
+
content = match[2].strip
|
|
114
|
+
tag_message = "#{version} - #{date}\n\n#{content}"
|
|
115
|
+
|
|
30
116
|
puts "Tagging commit as #{tag_name}..."
|
|
31
|
-
sh "git tag -a
|
|
32
|
-
puts "
|
|
33
|
-
|
|
34
|
-
|
|
117
|
+
sh "git", "tag", "-a", tag_name, "-m", tag_message
|
|
118
|
+
puts "Tag created. Push with: git push origin #{tag_name}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
desc "Full release: update changelog, commit, tag, and push"
|
|
122
|
+
# Usage: rake release:publish[0.2.1] or rake release:publish (uses Dotsync::VERSION)
|
|
123
|
+
task :publish, [:version] do |_t, args|
|
|
124
|
+
version = args[:version]
|
|
125
|
+
unless version
|
|
126
|
+
require_relative "./lib/dotsync/version"
|
|
127
|
+
version = Dotsync::VERSION
|
|
128
|
+
end
|
|
129
|
+
version = version.sub(/^v/, "")
|
|
130
|
+
|
|
131
|
+
status = `git status --porcelain`.strip
|
|
132
|
+
uncommitted = status.split("\n").reject { |line| line.end_with?("CHANGELOG.md") }
|
|
133
|
+
abort "Uncommitted changes:\n#{uncommitted.join("\n")}" unless uncommitted.empty?
|
|
134
|
+
|
|
135
|
+
Rake::Task["release:changelog"].invoke(version)
|
|
136
|
+
puts "\nReview CHANGELOG.md, then press Enter to continue..."
|
|
137
|
+
$stdin.gets
|
|
138
|
+
|
|
139
|
+
sh "git", "add", "CHANGELOG.md"
|
|
140
|
+
sh "git", "commit", "-m", "Update CHANGELOG for v#{version}\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
|
|
141
|
+
|
|
142
|
+
Rake::Task["release:tag"].reenable
|
|
143
|
+
Rake::Task["release:tag"].invoke(version)
|
|
144
|
+
|
|
145
|
+
branch = `git rev-parse --abbrev-ref HEAD`.strip
|
|
146
|
+
sh "git", "push", "origin", branch
|
|
147
|
+
sh "git", "push", "origin", "v#{version}"
|
|
148
|
+
puts "\n✅ Released v#{version}"
|
|
35
149
|
end
|
|
36
150
|
end
|
|
37
151
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dotsync
|
|
4
|
+
# SyncMappings provides bidirectional mapping support.
|
|
5
|
+
# It reads [sync] section mappings and converts them to push or pull format.
|
|
6
|
+
#
|
|
7
|
+
# The [sync] section supports multiple sub-types:
|
|
8
|
+
#
|
|
9
|
+
# 1. Explicit mappings with [[sync.mappings]]:
|
|
10
|
+
# [[sync.mappings]]
|
|
11
|
+
# local = "$XDG_CONFIG_HOME/nvim"
|
|
12
|
+
# remote = "$XDG_CONFIG_HOME_MIRROR/nvim"
|
|
13
|
+
# force = true
|
|
14
|
+
# ignore = ["lazy-lock.json"]
|
|
15
|
+
#
|
|
16
|
+
# 2. XDG shorthand mappings that auto-expand environment variables:
|
|
17
|
+
# [[sync.xdg_config]]
|
|
18
|
+
# path = "nvim"
|
|
19
|
+
# force = true
|
|
20
|
+
# # Expands to: local=$XDG_CONFIG_HOME/nvim, remote=$XDG_CONFIG_HOME_MIRROR/nvim
|
|
21
|
+
#
|
|
22
|
+
# Supported shorthands:
|
|
23
|
+
# - sync.home: $HOME <-> $HOME_MIRROR
|
|
24
|
+
# - sync.xdg_config: $XDG_CONFIG_HOME <-> $XDG_CONFIG_HOME_MIRROR
|
|
25
|
+
# - sync.xdg_data: $XDG_DATA_HOME <-> $XDG_DATA_HOME_MIRROR
|
|
26
|
+
# - sync.xdg_cache: $XDG_CACHE_HOME <-> $XDG_CACHE_HOME_MIRROR
|
|
27
|
+
# - sync.xdg_bin: $XDG_BIN_HOME <-> $XDG_BIN_HOME_MIRROR
|
|
28
|
+
# - sync.mappings: explicit local/remote mappings
|
|
29
|
+
#
|
|
30
|
+
# For push: local → remote (src=local, dest=remote)
|
|
31
|
+
# For pull: remote → local (src=remote, dest=local)
|
|
32
|
+
module SyncMappings
|
|
33
|
+
SYNC_SECTION = "sync"
|
|
34
|
+
MAPPINGS_KEY = "mappings"
|
|
35
|
+
|
|
36
|
+
# Shorthand type definitions mapping to local/remote base paths
|
|
37
|
+
SHORTHANDS = {
|
|
38
|
+
"home" => { local: "$HOME", remote: "$HOME_MIRROR" },
|
|
39
|
+
"xdg_config" => { local: "$XDG_CONFIG_HOME", remote: "$XDG_CONFIG_HOME_MIRROR" },
|
|
40
|
+
"xdg_data" => { local: "$XDG_DATA_HOME", remote: "$XDG_DATA_HOME_MIRROR" },
|
|
41
|
+
"xdg_cache" => { local: "$XDG_CACHE_HOME", remote: "$XDG_CACHE_HOME_MIRROR" },
|
|
42
|
+
"xdg_bin" => { local: "$XDG_BIN_HOME", remote: "$XDG_BIN_HOME_MIRROR" }
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
def sync_mappings_for_push
|
|
46
|
+
all_sync_mappings(:push)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def sync_mappings_for_pull
|
|
50
|
+
all_sync_mappings(:pull)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def has_sync_mappings?
|
|
54
|
+
return false unless sync_section.is_a?(Hash)
|
|
55
|
+
|
|
56
|
+
explicit_mappings_raw.any? || shorthand_mappings_raw.any?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def all_sync_mappings(direction)
|
|
61
|
+
convert_explicit_mappings(direction: direction) + convert_shorthand_mappings(direction: direction)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def sync_section
|
|
65
|
+
@config[SYNC_SECTION]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Explicit [[sync.mappings]] with local/remote keys
|
|
69
|
+
def explicit_mappings_raw
|
|
70
|
+
return [] unless sync_section.is_a?(Hash)
|
|
71
|
+
return [] unless sync_section.key?(MAPPINGS_KEY)
|
|
72
|
+
|
|
73
|
+
Array(sync_section[MAPPINGS_KEY])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def convert_explicit_mappings(direction:)
|
|
77
|
+
explicit_mappings_raw.map do |mapping|
|
|
78
|
+
converted = convert_explicit_mapping(mapping, direction)
|
|
79
|
+
Dotsync::Mapping.new(converted)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def convert_explicit_mapping(mapping, direction)
|
|
84
|
+
local = mapping["local"]
|
|
85
|
+
remote = mapping["remote"]
|
|
86
|
+
|
|
87
|
+
base = case direction
|
|
88
|
+
when :push
|
|
89
|
+
{ "src" => local, "dest" => remote }
|
|
90
|
+
when :pull
|
|
91
|
+
{ "src" => remote, "dest" => local }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Preserve other options
|
|
95
|
+
base["force"] = mapping["force"] if mapping.key?("force")
|
|
96
|
+
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
97
|
+
base["only"] = mapping["only"] if mapping.key?("only")
|
|
98
|
+
|
|
99
|
+
base
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Shorthand mappings: [[sync.home]], [[sync.xdg_config]], etc.
|
|
103
|
+
def shorthand_mappings_raw
|
|
104
|
+
return [] unless sync_section.is_a?(Hash)
|
|
105
|
+
|
|
106
|
+
mappings = []
|
|
107
|
+
SHORTHANDS.each_key do |shorthand_type|
|
|
108
|
+
next unless sync_section.key?(shorthand_type)
|
|
109
|
+
Array(sync_section[shorthand_type]).each do |mapping|
|
|
110
|
+
mappings << { type: shorthand_type, mapping: mapping }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
mappings
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def convert_shorthand_mappings(direction:)
|
|
117
|
+
shorthand_mappings_raw.map do |entry|
|
|
118
|
+
converted = convert_shorthand_mapping(entry[:type], entry[:mapping], direction)
|
|
119
|
+
Dotsync::Mapping.new(converted)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def convert_shorthand_mapping(shorthand_type, mapping, direction)
|
|
124
|
+
shorthand_def = SHORTHANDS[shorthand_type]
|
|
125
|
+
|
|
126
|
+
# Support both 'path' for single paths and 'only' for multiple paths
|
|
127
|
+
path = mapping["path"]
|
|
128
|
+
only = mapping["only"]
|
|
129
|
+
|
|
130
|
+
local = build_path(shorthand_def[:local], path)
|
|
131
|
+
remote = build_path(shorthand_def[:remote], path)
|
|
132
|
+
|
|
133
|
+
base = case direction
|
|
134
|
+
when :push
|
|
135
|
+
{ "src" => local, "dest" => remote }
|
|
136
|
+
when :pull
|
|
137
|
+
{ "src" => remote, "dest" => local }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Preserve other options
|
|
141
|
+
base["force"] = mapping["force"] if mapping.key?("force")
|
|
142
|
+
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
143
|
+
base["only"] = only if only
|
|
144
|
+
|
|
145
|
+
base
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def build_path(base, path)
|
|
149
|
+
path ? File.join(base, path) : base
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def validate_sync_mappings!
|
|
153
|
+
return unless sync_section
|
|
154
|
+
|
|
155
|
+
unless sync_section.is_a?(Hash)
|
|
156
|
+
raise Dotsync::ConfigError,
|
|
157
|
+
"Configuration error: [sync] must be a table, not an array. " \
|
|
158
|
+
"Use [[sync.mappings]] for explicit mappings."
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
validate_explicit_mappings!
|
|
162
|
+
validate_shorthand_mappings!
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def validate_explicit_mappings!
|
|
166
|
+
return unless sync_section.key?(MAPPINGS_KEY)
|
|
167
|
+
|
|
168
|
+
explicit_mappings_raw.each_with_index do |mapping, index|
|
|
169
|
+
unless mapping.is_a?(Hash) && mapping.key?("local") && mapping.key?("remote")
|
|
170
|
+
raise Dotsync::ConfigError,
|
|
171
|
+
"Configuration error in sync.mappings ##{index + 1}: " \
|
|
172
|
+
"Each mapping must have 'local' and 'remote' keys."
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def validate_shorthand_mappings!
|
|
178
|
+
SHORTHANDS.each_key do |shorthand_type|
|
|
179
|
+
next unless sync_section.key?(shorthand_type)
|
|
180
|
+
|
|
181
|
+
Array(sync_section[shorthand_type]).each_with_index do |mapping, index|
|
|
182
|
+
unless mapping.is_a?(Hash)
|
|
183
|
+
raise Dotsync::ConfigError,
|
|
184
|
+
"Configuration error in sync.#{shorthand_type} ##{index + 1}: " \
|
|
185
|
+
"Each mapping must be a table."
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
module Dotsync
|
|
4
4
|
class PullActionConfig < BaseConfig
|
|
5
5
|
include XDGBaseDirectory
|
|
6
|
+
include SyncMappings
|
|
6
7
|
|
|
7
8
|
def mappings
|
|
8
|
-
|
|
9
|
-
Array(mappings_list).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
9
|
+
section_mappings + sync_mappings_for_pull
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def backups_root
|
|
@@ -20,13 +20,31 @@ module Dotsync
|
|
|
20
20
|
SECTION_NAME
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def section_mappings
|
|
24
|
+
return [] unless section && section["mappings"]
|
|
25
|
+
Array(section["mappings"]).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
26
|
+
end
|
|
27
|
+
|
|
23
28
|
def validate!
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
validate_pull_or_sync_present!
|
|
30
|
+
validate_pull_mappings!
|
|
31
|
+
validate_sync_mappings!
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_pull_or_sync_present!
|
|
35
|
+
has_pull = @config.key?(section_name) && section["mappings"]&.any?
|
|
36
|
+
|
|
37
|
+
unless has_pull || has_sync_mappings?
|
|
38
|
+
raise_error "No [#{section_name}] mappings or [sync] mappings found in config file"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def validate_pull_mappings!
|
|
43
|
+
return unless section && section["mappings"]
|
|
26
44
|
|
|
27
45
|
Array(section["mappings"]).each_with_index do |mapping, index|
|
|
28
46
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
29
|
-
raise "Configuration error in mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
47
|
+
raise "Configuration error in pull mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
30
48
|
end
|
|
31
49
|
end
|
|
32
50
|
end
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Dotsync
|
|
4
4
|
class PushActionConfig < BaseConfig
|
|
5
|
+
include SyncMappings
|
|
6
|
+
|
|
5
7
|
def mappings
|
|
6
|
-
|
|
7
|
-
Array(mappings_list).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
8
|
+
section_mappings + sync_mappings_for_push
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
private
|
|
@@ -14,13 +15,31 @@ module Dotsync
|
|
|
14
15
|
SECTION_NAME
|
|
15
16
|
end
|
|
16
17
|
|
|
18
|
+
def section_mappings
|
|
19
|
+
return [] unless section && section["mappings"]
|
|
20
|
+
Array(section["mappings"]).map { |mapping| Dotsync::Mapping.new(mapping) }
|
|
21
|
+
end
|
|
22
|
+
|
|
17
23
|
def validate!
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
validate_push_or_sync_present!
|
|
25
|
+
validate_push_mappings!
|
|
26
|
+
validate_sync_mappings!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate_push_or_sync_present!
|
|
30
|
+
has_push = @config.key?(section_name) && section["mappings"]&.any?
|
|
31
|
+
|
|
32
|
+
unless has_push || has_sync_mappings?
|
|
33
|
+
raise_error "No [#{section_name}] mappings or [sync] mappings found in config file"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_push_mappings!
|
|
38
|
+
return unless section && section["mappings"]
|
|
20
39
|
|
|
21
40
|
Array(section["mappings"]).each_with_index do |mapping, index|
|
|
22
41
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
23
|
-
raise "Configuration error in mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
42
|
+
raise "Configuration error in push mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
24
43
|
end
|
|
25
44
|
end
|
|
26
45
|
end
|
|
@@ -16,6 +16,10 @@ require_relative "../utils/config_cache"
|
|
|
16
16
|
require_relative "../models/mapping"
|
|
17
17
|
require_relative "../models/diff"
|
|
18
18
|
|
|
19
|
+
# Config Concerns
|
|
20
|
+
require_relative "../config/concerns/xdg_base_directory"
|
|
21
|
+
require_relative "../config/concerns/sync_mappings"
|
|
22
|
+
|
|
19
23
|
# Config
|
|
20
24
|
require_relative "../config/base_config"
|
|
21
25
|
require_relative "../config/pull_action_config"
|
|
@@ -16,6 +16,9 @@ require_relative "../utils/config_cache"
|
|
|
16
16
|
require_relative "../models/mapping"
|
|
17
17
|
require_relative "../models/diff"
|
|
18
18
|
|
|
19
|
+
# Config Concerns
|
|
20
|
+
require_relative "../config/concerns/sync_mappings"
|
|
21
|
+
|
|
19
22
|
# Config
|
|
20
23
|
require_relative "../config/base_config"
|
|
21
24
|
require_relative "../config/push_action_config"
|
data/lib/dotsync/runner.rb
CHANGED
data/lib/dotsync/version.rb
CHANGED
data/lib/dotsync.rb
CHANGED
|
@@ -22,6 +22,7 @@ require_relative "dotsync/version"
|
|
|
22
22
|
|
|
23
23
|
# Config Concerns (loaded early as they're used by other modules)
|
|
24
24
|
require_relative "dotsync/config/concerns/xdg_base_directory"
|
|
25
|
+
require_relative "dotsync/config/concerns/sync_mappings"
|
|
25
26
|
|
|
26
27
|
# Utils
|
|
27
28
|
require_relative "dotsync/utils/path_utils"
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dotsync
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.26
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Sáenz
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: toml-rb
|
|
@@ -285,9 +284,10 @@ extensions: []
|
|
|
285
284
|
extra_rdoc_files: []
|
|
286
285
|
files:
|
|
287
286
|
- ".editorconfig"
|
|
288
|
-
- ".github/workflows/
|
|
289
|
-
- ".github/workflows/
|
|
287
|
+
- ".github/workflows/ci.yml"
|
|
288
|
+
- ".github/workflows/release.yml"
|
|
290
289
|
- ".gitignore"
|
|
290
|
+
- ".pre-commit-config.yaml"
|
|
291
291
|
- ".rspec"
|
|
292
292
|
- ".rubocop.yml"
|
|
293
293
|
- ".ruby-version"
|
|
@@ -317,6 +317,7 @@ files:
|
|
|
317
317
|
- lib/dotsync/actions/watch_action.rb
|
|
318
318
|
- lib/dotsync/colors.rb
|
|
319
319
|
- lib/dotsync/config/base_config.rb
|
|
320
|
+
- lib/dotsync/config/concerns/sync_mappings.rb
|
|
320
321
|
- lib/dotsync/config/concerns/xdg_base_directory.rb
|
|
321
322
|
- lib/dotsync/config/pull_action_config.rb
|
|
322
323
|
- lib/dotsync/config/push_action_config.rb
|
|
@@ -346,7 +347,6 @@ metadata:
|
|
|
346
347
|
homepage_uri: https://github.com/dsaenztagarro/dotsync
|
|
347
348
|
source_code_uri: https://github.com/dsaenztagarro/dotsync
|
|
348
349
|
changelog_uri: https://github.com/dsaenztagarro/dotsync/blob/master/CHANGELOG.md
|
|
349
|
-
post_install_message:
|
|
350
350
|
rdoc_options: []
|
|
351
351
|
require_paths:
|
|
352
352
|
- lib
|
|
@@ -361,8 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
361
361
|
- !ruby/object:Gem::Version
|
|
362
362
|
version: '0'
|
|
363
363
|
requirements: []
|
|
364
|
-
rubygems_version: 3.
|
|
365
|
-
signing_key:
|
|
364
|
+
rubygems_version: 3.7.2
|
|
366
365
|
specification_version: 4
|
|
367
366
|
summary: Manage dotfiles like a boss
|
|
368
367
|
test_files: []
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
jobs:
|
|
2
|
-
build:
|
|
3
|
-
name: Build + Publish
|
|
4
|
-
runs-on: ubuntu-latest
|
|
5
|
-
permissions:
|
|
6
|
-
contents: read
|
|
7
|
-
packages: write
|
|
8
|
-
|
|
9
|
-
steps:
|
|
10
|
-
- uses: actions/checkout@v4
|
|
11
|
-
|
|
12
|
-
- name: Publish to GPR
|
|
13
|
-
run: |
|
|
14
|
-
mkdir -p $HOME/.gem
|
|
15
|
-
touch $HOME/.gem/credentials
|
|
16
|
-
chmod 0600 $HOME/.gem/credentials
|
|
17
|
-
printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
|
18
|
-
gem build *.gemspec
|
|
19
|
-
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
|
20
|
-
env:
|
|
21
|
-
GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
|
|
22
|
-
OWNER: ${{ github.repository_owner }}
|
|
23
|
-
|
|
24
|
-
publish:
|
|
25
|
-
needs: test
|
|
26
|
-
runs-on: ubuntu-latest
|
|
27
|
-
if: github.ref == 'refs/heads/master' && matrix.ruby == '3.2'
|
|
28
|
-
|
|
29
|
-
steps:
|
|
30
|
-
- uses: actions/checkout@v4
|
|
31
|
-
|
|
32
|
-
- name: Set up Ruby
|
|
33
|
-
uses: ruby/setup-ruby@v1
|
|
34
|
-
with:
|
|
35
|
-
ruby-version: 3.2
|
|
36
|
-
bundler-cache: true
|
|
37
|
-
|
|
38
|
-
- name: Check if gem version has changed
|
|
39
|
-
id: gem_version_check
|
|
40
|
-
run: |
|
|
41
|
-
gem build *.gemspec
|
|
42
|
-
gem_name=$(ls *.gem | head -n 1)
|
|
43
|
-
gem info $gem_name --remote || echo "new_version" > version_changed
|
|
44
|
-
env:
|
|
45
|
-
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|