deezconfigs 0.17.0

Manage deez config files.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# deezconfigs

[![license: GPL v3+](https://img.shields.io/badge/license-GPLv3+-blue)](https://www.gnu.org/licenses/gpl-3.0)
![GitHub Tag](https://img.shields.io/github/v/tag/qrichert/deezconfigs?sort=semver&filter=*.*.*&label=release)
[![crates.io](https://img.shields.io/crates/d/deezconfigs?logo=rust&logoColor=white&color=orange)](https://crates.io/crates/deezconfigs)
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/qrichert/deezconfigs/ci.yml?label=tests)](https://github.com/qrichert/deezconfigs/actions)

_Manage deez config files._

_deezconfigs_ will mirror your config files to your `$HOME` directory
(`sync`) and synchronize them back (`rsync`). Additionally, you can
choose to symlink the files instead (`link`).

Same idea as _GNU Stow_ or _chezmoi_, but simpler, requiring less neuron
activation to operate.

## Get `--help`

```
Manage deez config files.

Usage: deez [<options>] <command> [<args>]

Commands:
  sync [<root>|<git>]    Update home from configs
  rsync [<root>]         Update configs from home
  link [<root>]          Symlink configs to home

  status [<root>|<git>]  List files and their status
  diff [<root>|<git>]    Show what has changed
    -r, --reversed
  clean [<root>|<git>]   Remove all configs from home

  run                    Run command inside the root

Options:
  -p, --pull             Git-pull the root first
  -v, --verbose          Show files being copied

  -h, --help             Show this message and exit
  -V, --version          Show the version and exit
```

### What does deezconfigs do?

The core of deezconfigs is to replicate the file structure of a given
directory (i.e., the config root), inside the home. The main purpose of
this is to keep all the config files in one place, making it easy to
version them.

deezconfigs is very unopinionated by default. It tries to do its job
well (syncing config files), while avoiding doing what other tools do
better. For instance, there is no automatic versioning, no embedded text
editor, and no templating. You absolutely _can_ do all of the above, but
it's not something that's forced on you. It's _your_ processes, _your_
tools. All the extensibility power lies in hooks (read further below).

### Copying vs. Linking

deezconfigs supports two configuration models: copying and linking. Both
models come with different trade-offs. For instance, linking ensures
files are always up-to-date, but on the flip-side, you can't really have
machine-specific configuration. On the other hand, copied files need to
be kept up-to-date manually by `sync`ing or `rsync`ing all changes. But,
having separate copies makes it easier to keep configuration generic in
the root, and specific in the home.

### The Config Root

As mentioned before, the config root is any directory whose structure
you want to replicate in the home directory.

deezconfigs will use the root you provide as an argument on the CLI, or
default to the current working directory.

You _should_, but are not required to, create a `.deez` file in the
root. This lets deezconfigs know that it is safe to use. If deezconfigs
doesn't find a `.deez` file, it will ask for confirmation before
modifying your file system. This is a security feature to prevent you
from accidentally messing up your home if you run `deez` from the wrong
directory.

Another advantage of creating a proper root is that it lets you run
`deez` inside sub-directories as well. Just like you can run Git
commands from anywhere in the repo, deezconfigs is smart enough to
search for a root in parent directories before warning you that the
current directory is not a root.

If you always use the same config root (common case), you can point the
`DEEZ_ROOT` environment variable to it. In this case, deezconfigs will
default to it if no root is given on the CLI, and neither the current
directory, nor its parents are a root. This lets you run `deez` from
anywhere with much less typing.

```console
# Will status `/home/deez/root` wherever you are.
$ export DEEZ_ROOT=/home/deez/root
$ deez status
```

### Home

This is the directory where config files are copied or symlinked to. On
Unix, this is read from the `HOME` environment variable, and on Windows
from `USERPROFILE`.

Using a different home is not natively supported by an argument, but you
can override the environment variable to achieve what you want.

```console
$ HOME=/home/other deez sync
```

### Sync

Syncing in deezconfigs replicates the file structure from the config
root inside the home directory (minus ignored files).

```console
# Sync current config root.
$ deez sync

# Sync given config root, verbosely.
$ deez --verbose sync ~/configs

# Sync from remote.
$ deez sync https://github.com/qrichert/configs
```

### rSync

Reverse-syncing reverses the direction of syncing: it updates your
config files in the root with the current content from home.

```console
# 1. Sync your config file to your home.
$ deez sync

# 2. Make some changes.
$ vim ~/.gitconfig

# 3. rSync the changes back into your root.
$ deez rsync
```

### Link

Linking is the same as syncing, but it creates symbolic links in the
home instead of copying files. Linking has no `rsync` equivalent because
linked files are always up-to-date.

```console
# Symlink current config root.
$ deez link
```

### Status

Status prints the list of configuration files with their respective
state of 'syncness', and also prints your hooks.

Configuration files can be:

```
S  In Sync
M  Modified
!  Missing
```

### Diff

Diffing prints the line-diff between your config root and your home.
This shows you exactly what has changed and where. There is no merge
feature, however, as merging is best done by your VCS.

By default, `diff` uses the config root as the _before_, and the home as
the _after_. This assumes you make changes in the home directly, and
want to see what would change in your root if you `rsync`ed the updates
back.

```console
# Compare the config root (old) to the home (new).
$ deez diff
```

If you make changes inside the config root, however, it is more natural
to use the home as the _before_, and the root as the _after_. In other
words, you want to see what would change in your home if you `sync`ed
the updates to it.

To do this, use the `--reversed` flag:

```console
# Compare the home (old) to the config root (new).
$ deez diff -r
```

### Clean

Cleaning is removing all the files and symlinks from the home.

```console
# 1. Link your files to your home.
$ deez link

# 2. Now remove all the links you've just created.
$ deez clean
```

### Run

There is an additional `run` convenience-command which works with the
`DEEZ_ROOT` environment variable.

Sometimes, you just want to run a single command in the config root,
like a `git pull` to get the latest changes. It can be annoying to `cd`
into the root just for that, and that's where `run` shines:

```console
# Will run in `/home/deez/root` wherever you are.
$ export DEEZ_ROOT=/home/deez/root
$ deez run pwd
/home/deez/root
```

```console
# Run your editor inside the root.
$ deez run $EDITOR

# Start an interactive shell inside the root.
$ deez run $SHELL

# A common combination would be:
$ deez run git pull
$ deez sync
```

### Shortcuts

Each command has a shortcut:

```
sync   s     status  st
rsync  rs    diff    df
link   l     clean   c
run    r
```

### Ignore some files

By default, deezconfigs ignores all the hook files (at the root), the
`.git` directory at the root (if any), all `.ignore` and `.gitignore`
files, and all `.deez` files, wherever they are (enabling multi-root
repos).

You can extend this list by adding entries to your `.ignore` and/or
`.gitignore` files; they are both respected by deezconfigs.

If you want to both version a file in Git and have it ignored by
deezconfigs, you can either add it to a `.gitignore` and force-add it
with `git add -f`, or you can use a generic `.ignore` file instead.

### Git

Git is optional, but deezconfigs is designed to integrate nicely with
it. Beyond respecting `.gitignore` files, deezconfigs can use any Git
remote as config root with `sync`, `status`, `diff` and `clean`.

To expand on a previous example:

```console
# Sync from remote.
$ deez sync https://github.com/qrichert/configs
```

This will clone the repository to a temporary directory behind the
scenes, and update your home with its contents. This is useful in places
where you don't want to maintain a proper clone, and always just want to
get the latest version.

deezconfigs considers a Git root any root starting with either `git:`,
`ssh://`, `git@`, `https://`, or `http://`. `git:` is a special label
you can use to force a path to be considered a Git root.

In addition, `gh:` will be replaced with `git@github.com:` (e.g.,
`gh:qrichert/configs`).

Furthermore, you can specify a sub-root like this:

```console
# Sync sub-root.
$ deez sync gh:qrichert/configs[sub/directory]
```

Instead of assuming the root to be at the repository root, this allows
using a sub-directory as the root.

If you're using Git, you can also pull and run a command in one shot
with the `--pull` flag:

```console
# Run `git pull` in the config root, then sync.
$ deez sync --pull
```

This flag only works with local roots; remote roots are always freshly
cloned.

### Hooks

deezconfigs lets you run hooks before and after commands. Hooks are
scripts or executables located at the root and whose names match the
following pattern:

```
(pre|post)-<command>[.extension]
```

Available hooks:

```
pre-sync    post-sync
pre-rsync   post-rsync
pre-link    post-link
pre-status  post-status
pre-diff    post-diff
pre-clean   post-clean
```

A common example would be...

```
post-sync.sh
```

...a shell script that gets run after every `deez sync` command.

You can have multiple hooks for the same action; they will be run in
name order (`post-sync.001.sh`, then `post-sync.002.sh`, etc.).

Hooks are executed through `sh`. It is roughly equivalent to:

```console
$ cd <root>
$ export DEEZ_...  # deez environment variables.
$ sh -c "<root>/<hook>"
```

Note that you'll likely want the scripts to start with a shebang (e.g.,
`#!/usr/bin/env python3`).

As an example, here are two complementary scripts that respectively set
and unset Git's email address in the `.gitconfig` file when you `sync`
and `rsync` it:

```console
$ cat post-sync.sh
#!/usr/bin/env bash
[[ -n $DEEZ_VERBOSE ]] && echo "Set global Git email address."
git config --global user.email you@example.com

$ cat post-rsync.sh
#!/usr/bin/env bash
[[ -n $DEEZ_VERBOSE ]] && echo "Unset Git email address."
git config --file ./.gitconfig user.email '<>'
```

They both make use of the `DEEZ_VERBOSE` environment variable to enrich
the output of `deez` in verbose mode.

deezconfigs passes a few environment variables to hooks to make your
life easier:

- `DEEZ_ROOT` Absolute path to the config root. This is equal to `pwd`
  on Unix systems, since hooks are run in the root.
- `DEEZ_HOME` Absolute path to the home directory. This is equal to
  `$HOME` on Unix systems.
- `DEEZ_VERBOSE` Will be `true` if run in verbose mode, otherwise it
  will be unset (hint: use `[[ -n $DEEZ_VERBOSE ]]` to test for
  existence).
- `DEEZ_OS` Contains the name of the current operating system (e.g.,
  `linux`, `macos`, `windows`, etc.). The name is a re-export of Rust's
  [`std::consts::OS`].

[`std::consts::OS`]:
  https://doc.rust-lang.org/std/env/consts/constant.OS.html

### Templating

There is no built-in templating in deezconfigs, but hooks let you
implement anything from simple `sed` commands to more advanced
templating with Jinja2 in Python.

### Copy some files, and link others

Use multiple roots. You can have multiple roots (sub-directories) in one
repo. Use `sync` in one, and `link` in the other.

If you need anything more advanced than that, `deezconfigs` is likely
not the right tool for you.

## Roadmap

- [ ] Allow syncing a single file if root points to a file.
- [ ] Increase test coverage (features are mostly covered, what's
      missing are tests for the error cases).
- [ ] Perf refactorings for bottlenecks (or for fun).