aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2023-04-07 20:49:26 +0100
committerdiogo464 <[email protected]>2023-04-07 20:51:32 +0100
commite7588a7f175ca9b9604ce35d72086489da7a66e3 (patch)
tree03379454044e505c65e32ccf96cdfc34441bf914
parentfa3fecca00442acea029248c6cf1fa0ba81e96b9 (diff)
version bump: 0.2.00.2.0
-rw-r--r--Cargo.lock530
-rw-r--r--Cargo.toml12
-rw-r--r--src/dotup/action_tree.rs341
-rw-r--r--src/dotup/mod.rs697
-rw-r--r--src/main.rs57
5 files changed, 979 insertions, 658 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5b3fb5f..de9af33 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12,6 +12,46 @@ dependencies = [
12] 12]
13 13
14[[package]] 14[[package]]
15name = "anstream"
16version = "0.2.6"
17source = "registry+https://github.com/rust-lang/crates.io-index"
18checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f"
19dependencies = [
20 "anstyle",
21 "anstyle-parse",
22 "anstyle-wincon",
23 "concolor-override",
24 "concolor-query",
25 "is-terminal",
26 "utf8parse",
27]
28
29[[package]]
30name = "anstyle"
31version = "0.3.5"
32source = "registry+https://github.com/rust-lang/crates.io-index"
33checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
34
35[[package]]
36name = "anstyle-parse"
37version = "0.1.1"
38source = "registry+https://github.com/rust-lang/crates.io-index"
39checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116"
40dependencies = [
41 "utf8parse",
42]
43
44[[package]]
45name = "anstyle-wincon"
46version = "0.2.0"
47source = "registry+https://github.com/rust-lang/crates.io-index"
48checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa"
49dependencies = [
50 "anstyle",
51 "windows-sys 0.45.0",
52]
53
54[[package]]
15name = "anyhow" 55name = "anyhow"
16version = "1.0.70" 56version = "1.0.70"
17source = "registry+https://github.com/rust-lang/crates.io-index" 57source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -23,7 +63,7 @@ version = "0.2.14"
23source = "registry+https://github.com/rust-lang/crates.io-index" 63source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 64checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
25dependencies = [ 65dependencies = [
26 "hermit-abi", 66 "hermit-abi 0.1.19",
27 "libc", 67 "libc",
28 "winapi", 68 "winapi",
29] 69]
@@ -57,6 +97,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
57checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" 97checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
58 98
59[[package]] 99[[package]]
100name = "cc"
101version = "1.0.79"
102source = "registry+https://github.com/rust-lang/crates.io-index"
103checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
104
105[[package]]
60name = "cfg-if" 106name = "cfg-if"
61version = "1.0.0" 107version = "1.0.0"
62source = "registry+https://github.com/rust-lang/crates.io-index" 108source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -64,51 +110,107 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
64 110
65[[package]] 111[[package]]
66name = "clap" 112name = "clap"
67version = "3.2.23" 113version = "4.2.1"
68source = "registry+https://github.com/rust-lang/crates.io-index" 114source = "registry+https://github.com/rust-lang/crates.io-index"
69checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" 115checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3"
70dependencies = [ 116dependencies = [
71 "atty", 117 "clap_builder",
72 "bitflags",
73 "clap_derive", 118 "clap_derive",
74 "clap_lex",
75 "indexmap",
76 "once_cell", 119 "once_cell",
120]
121
122[[package]]
123name = "clap_builder"
124version = "4.2.1"
125source = "registry+https://github.com/rust-lang/crates.io-index"
126checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f"
127dependencies = [
128 "anstream",
129 "anstyle",
130 "bitflags",
131 "clap_lex",
77 "strsim", 132 "strsim",
78 "termcolor",
79 "textwrap",
80] 133]
81 134
82[[package]] 135[[package]]
83name = "clap_derive" 136name = "clap_derive"
84version = "3.2.18" 137version = "4.2.0"
85source = "registry+https://github.com/rust-lang/crates.io-index" 138source = "registry+https://github.com/rust-lang/crates.io-index"
86checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" 139checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
87dependencies = [ 140dependencies = [
88 "heck", 141 "heck",
89 "proc-macro-error",
90 "proc-macro2", 142 "proc-macro2",
91 "quote", 143 "quote",
92 "syn 1.0.109", 144 "syn",
93] 145]
94 146
95[[package]] 147[[package]]
96name = "clap_lex" 148name = "clap_lex"
97version = "0.2.4" 149version = "0.4.1"
98source = "registry+https://github.com/rust-lang/crates.io-index" 150source = "registry+https://github.com/rust-lang/crates.io-index"
99checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 151checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
152
153[[package]]
154name = "colored"
155version = "2.0.0"
156source = "registry+https://github.com/rust-lang/crates.io-index"
157checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
158dependencies = [
159 "atty",
160 "lazy_static",
161 "winapi",
162]
163
164[[package]]
165name = "concolor-override"
166version = "1.0.0"
167source = "registry+https://github.com/rust-lang/crates.io-index"
168checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f"
169
170[[package]]
171name = "concolor-query"
172version = "0.3.3"
173source = "registry+https://github.com/rust-lang/crates.io-index"
174checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
175dependencies = [
176 "windows-sys 0.45.0",
177]
178
179[[package]]
180name = "crossterm"
181version = "0.25.0"
182source = "registry+https://github.com/rust-lang/crates.io-index"
183checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
184dependencies = [
185 "bitflags",
186 "crossterm_winapi",
187 "libc",
188 "mio",
189 "parking_lot",
190 "signal-hook",
191 "signal-hook-mio",
192 "winapi",
193]
194
195[[package]]
196name = "crossterm_winapi"
197version = "0.9.0"
198source = "registry+https://github.com/rust-lang/crates.io-index"
199checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
100dependencies = [ 200dependencies = [
101 "os_str_bytes", 201 "winapi",
102] 202]
103 203
104[[package]] 204[[package]]
105name = "dotup" 205name = "dotup"
106version = "0.1.0" 206version = "0.2.0"
107dependencies = [ 207dependencies = [
108 "anyhow", 208 "anyhow",
109 "clap", 209 "clap",
210 "colored",
110 "env_logger", 211 "env_logger",
111 "globset", 212 "globset",
213 "inquire",
112 "log", 214 "log",
113 "nom", 215 "nom",
114 "nom_locate", 216 "nom_locate",
@@ -117,6 +219,12 @@ dependencies = [
117] 219]
118 220
119[[package]] 221[[package]]
222name = "dyn-clone"
223version = "1.0.11"
224source = "registry+https://github.com/rust-lang/crates.io-index"
225checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
226
227[[package]]
120name = "env_logger" 228name = "env_logger"
121version = "0.9.3" 229version = "0.9.3"
122source = "registry+https://github.com/rust-lang/crates.io-index" 230source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -130,6 +238,27 @@ dependencies = [
130] 238]
131 239
132[[package]] 240[[package]]
241name = "errno"
242version = "0.3.0"
243source = "registry+https://github.com/rust-lang/crates.io-index"
244checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
245dependencies = [
246 "errno-dragonfly",
247 "libc",
248 "windows-sys 0.45.0",
249]
250
251[[package]]
252name = "errno-dragonfly"
253version = "0.1.2"
254source = "registry+https://github.com/rust-lang/crates.io-index"
255checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
256dependencies = [
257 "cc",
258 "libc",
259]
260
261[[package]]
133name = "fnv" 262name = "fnv"
134version = "1.0.7" 263version = "1.0.7"
135source = "registry+https://github.com/rust-lang/crates.io-index" 264source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -149,12 +278,6 @@ dependencies = [
149] 278]
150 279
151[[package]] 280[[package]]
152name = "hashbrown"
153version = "0.12.3"
154source = "registry+https://github.com/rust-lang/crates.io-index"
155checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
156
157[[package]]
158name = "heck" 281name = "heck"
159version = "0.4.1" 282version = "0.4.1"
160source = "registry+https://github.com/rust-lang/crates.io-index" 283source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -170,28 +293,85 @@ dependencies = [
170] 293]
171 294
172[[package]] 295[[package]]
296name = "hermit-abi"
297version = "0.3.1"
298source = "registry+https://github.com/rust-lang/crates.io-index"
299checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
300
301[[package]]
173name = "humantime" 302name = "humantime"
174version = "2.1.0" 303version = "2.1.0"
175source = "registry+https://github.com/rust-lang/crates.io-index" 304source = "registry+https://github.com/rust-lang/crates.io-index"
176checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 305checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
177 306
178[[package]] 307[[package]]
179name = "indexmap" 308name = "inquire"
180version = "1.9.3" 309version = "0.6.0"
181source = "registry+https://github.com/rust-lang/crates.io-index" 310source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 311checksum = "fd079157ad94a32f7511b2e13037f3ae417ad80a6a9b0de29154d48b86f5d6c8"
183dependencies = [ 312dependencies = [
184 "autocfg", 313 "bitflags",
185 "hashbrown", 314 "crossterm",
315 "dyn-clone",
316 "lazy_static",
317 "newline-converter",
318 "thiserror",
319 "unicode-segmentation",
320 "unicode-width",
321]
322
323[[package]]
324name = "io-lifetimes"
325version = "1.0.10"
326source = "registry+https://github.com/rust-lang/crates.io-index"
327checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
328dependencies = [
329 "hermit-abi 0.3.1",
330 "libc",
331 "windows-sys 0.48.0",
332]
333
334[[package]]
335name = "is-terminal"
336version = "0.4.7"
337source = "registry+https://github.com/rust-lang/crates.io-index"
338checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
339dependencies = [
340 "hermit-abi 0.3.1",
341 "io-lifetimes",
342 "rustix",
343 "windows-sys 0.48.0",
186] 344]
187 345
188[[package]] 346[[package]]
347name = "lazy_static"
348version = "1.4.0"
349source = "registry+https://github.com/rust-lang/crates.io-index"
350checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
351
352[[package]]
189name = "libc" 353name = "libc"
190version = "0.2.141" 354version = "0.2.141"
191source = "registry+https://github.com/rust-lang/crates.io-index" 355source = "registry+https://github.com/rust-lang/crates.io-index"
192checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" 356checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
193 357
194[[package]] 358[[package]]
359name = "linux-raw-sys"
360version = "0.3.1"
361source = "registry+https://github.com/rust-lang/crates.io-index"
362checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
363
364[[package]]
365name = "lock_api"
366version = "0.4.9"
367source = "registry+https://github.com/rust-lang/crates.io-index"
368checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
369dependencies = [
370 "autocfg",
371 "scopeguard",
372]
373
374[[package]]
195name = "log" 375name = "log"
196version = "0.4.17" 376version = "0.4.17"
197source = "registry+https://github.com/rust-lang/crates.io-index" 377source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -213,6 +393,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
213checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 393checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
214 394
215[[package]] 395[[package]]
396name = "mio"
397version = "0.8.6"
398source = "registry+https://github.com/rust-lang/crates.io-index"
399checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
400dependencies = [
401 "libc",
402 "log",
403 "wasi",
404 "windows-sys 0.45.0",
405]
406
407[[package]]
408name = "newline-converter"
409version = "0.2.2"
410source = "registry+https://github.com/rust-lang/crates.io-index"
411checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f"
412dependencies = [
413 "unicode-segmentation",
414]
415
416[[package]]
216name = "nom" 417name = "nom"
217version = "7.1.3" 418version = "7.1.3"
218source = "registry+https://github.com/rust-lang/crates.io-index" 419source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -240,33 +441,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
240checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 441checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
241 442
242[[package]] 443[[package]]
243name = "os_str_bytes" 444name = "parking_lot"
244version = "6.5.0" 445version = "0.12.1"
245source = "registry+https://github.com/rust-lang/crates.io-index"
246checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
247
248[[package]]
249name = "proc-macro-error"
250version = "1.0.4"
251source = "registry+https://github.com/rust-lang/crates.io-index" 446source = "registry+https://github.com/rust-lang/crates.io-index"
252checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 447checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
253dependencies = [ 448dependencies = [
254 "proc-macro-error-attr", 449 "lock_api",
255 "proc-macro2", 450 "parking_lot_core",
256 "quote",
257 "syn 1.0.109",
258 "version_check",
259] 451]
260 452
261[[package]] 453[[package]]
262name = "proc-macro-error-attr" 454name = "parking_lot_core"
263version = "1.0.4" 455version = "0.9.7"
264source = "registry+https://github.com/rust-lang/crates.io-index" 456source = "registry+https://github.com/rust-lang/crates.io-index"
265checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 457checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
266dependencies = [ 458dependencies = [
267 "proc-macro2", 459 "cfg-if",
268 "quote", 460 "libc",
269 "version_check", 461 "redox_syscall",
462 "smallvec",
463 "windows-sys 0.45.0",
270] 464]
271 465
272[[package]] 466[[package]]
@@ -288,6 +482,15 @@ dependencies = [
288] 482]
289 483
290[[package]] 484[[package]]
485name = "redox_syscall"
486version = "0.2.16"
487source = "registry+https://github.com/rust-lang/crates.io-index"
488checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
489dependencies = [
490 "bitflags",
491]
492
493[[package]]
291name = "regex" 494name = "regex"
292version = "1.7.3" 495version = "1.7.3"
293source = "registry+https://github.com/rust-lang/crates.io-index" 496source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -305,12 +508,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
305checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 508checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
306 509
307[[package]] 510[[package]]
511name = "rustix"
512version = "0.37.8"
513source = "registry+https://github.com/rust-lang/crates.io-index"
514checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635"
515dependencies = [
516 "bitflags",
517 "errno",
518 "io-lifetimes",
519 "libc",
520 "linux-raw-sys",
521 "windows-sys 0.48.0",
522]
523
524[[package]]
525name = "scopeguard"
526version = "1.1.0"
527source = "registry+https://github.com/rust-lang/crates.io-index"
528checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
529
530[[package]]
308name = "serde" 531name = "serde"
309version = "1.0.159" 532version = "1.0.159"
310source = "registry+https://github.com/rust-lang/crates.io-index" 533source = "registry+https://github.com/rust-lang/crates.io-index"
311checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" 534checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
312 535
313[[package]] 536[[package]]
537name = "signal-hook"
538version = "0.3.15"
539source = "registry+https://github.com/rust-lang/crates.io-index"
540checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
541dependencies = [
542 "libc",
543 "signal-hook-registry",
544]
545
546[[package]]
547name = "signal-hook-mio"
548version = "0.2.3"
549source = "registry+https://github.com/rust-lang/crates.io-index"
550checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
551dependencies = [
552 "libc",
553 "mio",
554 "signal-hook",
555]
556
557[[package]]
558name = "signal-hook-registry"
559version = "1.4.1"
560source = "registry+https://github.com/rust-lang/crates.io-index"
561checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
562dependencies = [
563 "libc",
564]
565
566[[package]]
314name = "slotmap" 567name = "slotmap"
315version = "1.0.6" 568version = "1.0.6"
316source = "registry+https://github.com/rust-lang/crates.io-index" 569source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -320,21 +573,16 @@ dependencies = [
320] 573]
321 574
322[[package]] 575[[package]]
323name = "strsim" 576name = "smallvec"
324version = "0.10.0" 577version = "1.10.0"
325source = "registry+https://github.com/rust-lang/crates.io-index" 578source = "registry+https://github.com/rust-lang/crates.io-index"
326checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 579checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
327 580
328[[package]] 581[[package]]
329name = "syn" 582name = "strsim"
330version = "1.0.109" 583version = "0.10.0"
331source = "registry+https://github.com/rust-lang/crates.io-index" 584source = "registry+https://github.com/rust-lang/crates.io-index"
332checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 585checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
333dependencies = [
334 "proc-macro2",
335 "quote",
336 "unicode-ident",
337]
338 586
339[[package]] 587[[package]]
340name = "syn" 588name = "syn"
@@ -357,12 +605,6 @@ dependencies = [
357] 605]
358 606
359[[package]] 607[[package]]
360name = "textwrap"
361version = "0.16.0"
362source = "registry+https://github.com/rust-lang/crates.io-index"
363checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
364
365[[package]]
366name = "thiserror" 608name = "thiserror"
367version = "1.0.40" 609version = "1.0.40"
368source = "registry+https://github.com/rust-lang/crates.io-index" 610source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -379,7 +621,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
379dependencies = [ 621dependencies = [
380 "proc-macro2", 622 "proc-macro2",
381 "quote", 623 "quote",
382 "syn 2.0.13", 624 "syn",
383] 625]
384 626
385[[package]] 627[[package]]
@@ -389,12 +631,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
389checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 631checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
390 632
391[[package]] 633[[package]]
634name = "unicode-segmentation"
635version = "1.10.1"
636source = "registry+https://github.com/rust-lang/crates.io-index"
637checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
638
639[[package]]
640name = "unicode-width"
641version = "0.1.10"
642source = "registry+https://github.com/rust-lang/crates.io-index"
643checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
644
645[[package]]
646name = "utf8parse"
647version = "0.2.1"
648source = "registry+https://github.com/rust-lang/crates.io-index"
649checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
650
651[[package]]
392name = "version_check" 652name = "version_check"
393version = "0.9.4" 653version = "0.9.4"
394source = "registry+https://github.com/rust-lang/crates.io-index" 654source = "registry+https://github.com/rust-lang/crates.io-index"
395checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 655checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
396 656
397[[package]] 657[[package]]
658name = "wasi"
659version = "0.11.0+wasi-snapshot-preview1"
660source = "registry+https://github.com/rust-lang/crates.io-index"
661checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
662
663[[package]]
398name = "winapi" 664name = "winapi"
399version = "0.3.9" 665version = "0.3.9"
400source = "registry+https://github.com/rust-lang/crates.io-index" 666source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -424,3 +690,135 @@ name = "winapi-x86_64-pc-windows-gnu"
424version = "0.4.0" 690version = "0.4.0"
425source = "registry+https://github.com/rust-lang/crates.io-index" 691source = "registry+https://github.com/rust-lang/crates.io-index"
426checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 692checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
693
694[[package]]
695name = "windows-sys"
696version = "0.45.0"
697source = "registry+https://github.com/rust-lang/crates.io-index"
698checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
699dependencies = [
700 "windows-targets 0.42.2",
701]
702
703[[package]]
704name = "windows-sys"
705version = "0.48.0"
706source = "registry+https://github.com/rust-lang/crates.io-index"
707checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
708dependencies = [
709 "windows-targets 0.48.0",
710]
711
712[[package]]
713name = "windows-targets"
714version = "0.42.2"
715source = "registry+https://github.com/rust-lang/crates.io-index"
716checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
717dependencies = [
718 "windows_aarch64_gnullvm 0.42.2",
719 "windows_aarch64_msvc 0.42.2",
720 "windows_i686_gnu 0.42.2",
721 "windows_i686_msvc 0.42.2",
722 "windows_x86_64_gnu 0.42.2",
723 "windows_x86_64_gnullvm 0.42.2",
724 "windows_x86_64_msvc 0.42.2",
725]
726
727[[package]]
728name = "windows-targets"
729version = "0.48.0"
730source = "registry+https://github.com/rust-lang/crates.io-index"
731checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
732dependencies = [
733 "windows_aarch64_gnullvm 0.48.0",
734 "windows_aarch64_msvc 0.48.0",
735 "windows_i686_gnu 0.48.0",
736 "windows_i686_msvc 0.48.0",
737 "windows_x86_64_gnu 0.48.0",
738 "windows_x86_64_gnullvm 0.48.0",
739 "windows_x86_64_msvc 0.48.0",
740]
741
742[[package]]
743name = "windows_aarch64_gnullvm"
744version = "0.42.2"
745source = "registry+https://github.com/rust-lang/crates.io-index"
746checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
747
748[[package]]
749name = "windows_aarch64_gnullvm"
750version = "0.48.0"
751source = "registry+https://github.com/rust-lang/crates.io-index"
752checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
753
754[[package]]
755name = "windows_aarch64_msvc"
756version = "0.42.2"
757source = "registry+https://github.com/rust-lang/crates.io-index"
758checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
759
760[[package]]
761name = "windows_aarch64_msvc"
762version = "0.48.0"
763source = "registry+https://github.com/rust-lang/crates.io-index"
764checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
765
766[[package]]
767name = "windows_i686_gnu"
768version = "0.42.2"
769source = "registry+https://github.com/rust-lang/crates.io-index"
770checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
771
772[[package]]
773name = "windows_i686_gnu"
774version = "0.48.0"
775source = "registry+https://github.com/rust-lang/crates.io-index"
776checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
777
778[[package]]
779name = "windows_i686_msvc"
780version = "0.42.2"
781source = "registry+https://github.com/rust-lang/crates.io-index"
782checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
783
784[[package]]
785name = "windows_i686_msvc"
786version = "0.48.0"
787source = "registry+https://github.com/rust-lang/crates.io-index"
788checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
789
790[[package]]
791name = "windows_x86_64_gnu"
792version = "0.42.2"
793source = "registry+https://github.com/rust-lang/crates.io-index"
794checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
795
796[[package]]
797name = "windows_x86_64_gnu"
798version = "0.48.0"
799source = "registry+https://github.com/rust-lang/crates.io-index"
800checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
801
802[[package]]
803name = "windows_x86_64_gnullvm"
804version = "0.42.2"
805source = "registry+https://github.com/rust-lang/crates.io-index"
806checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
807
808[[package]]
809name = "windows_x86_64_gnullvm"
810version = "0.48.0"
811source = "registry+https://github.com/rust-lang/crates.io-index"
812checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
813
814[[package]]
815name = "windows_x86_64_msvc"
816version = "0.42.2"
817source = "registry+https://github.com/rust-lang/crates.io-index"
818checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
819
820[[package]]
821name = "windows_x86_64_msvc"
822version = "0.48.0"
823source = "registry+https://github.com/rust-lang/crates.io-index"
824checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
diff --git a/Cargo.toml b/Cargo.toml
index 2045a92..2c2a332 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,17 +1,19 @@
1[package] 1[package]
2name = "dotup" 2name = "dotup"
3version = "0.1.0" 3version = "0.2.0"
4edition = "2021" 4edition = "2021"
5 5
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 7
8[dependencies] 8[dependencies]
9anyhow = "1" 9anyhow = "1"
10clap = { version = "3.2.21", features = ["derive"] } 10clap = { version = "4", features = ["derive"] }
11colored = "2"
11env_logger = "0.9" 12env_logger = "0.9"
12globset = "0.4.9" 13globset = "0.4"
13log = "0.4.17" 14inquire = "0.6"
14nom = "7.1.1" 15log = "0.4"
16nom = "7"
15nom_locate = "4" 17nom_locate = "4"
16slotmap = "1" 18slotmap = "1"
17thiserror = "1" 19thiserror = "1"
diff --git a/src/dotup/action_tree.rs b/src/dotup/action_tree.rs
deleted file mode 100644
index 44d787e..0000000
--- a/src/dotup/action_tree.rs
+++ /dev/null
@@ -1,341 +0,0 @@
1use std::{collections::HashSet, ffi::OsString, ops::Index, path::PathBuf};
2
3use slotmap::SlotMap;
4
5use super::{AbsPath, AbsPathBuf};
6
7slotmap::new_key_type! {
8 pub struct NodeID;
9 pub struct ActionID;
10}
11
12#[derive(Debug)]
13pub enum Action {
14 Link { source: PathBuf },
15 Copy { source: PathBuf },
16}
17
18#[derive(Debug)]
19pub struct TreeAction {
20 path: AbsPathBuf,
21 action: Action,
22}
23
24#[derive(Debug)]
25enum TreeNodeKind {
26 Action(ActionID),
27 SubTree(HashSet<NodeID>),
28}
29
30#[derive(Debug)]
31struct TreeNode {
32 path: AbsPathBuf,
33 component: OsString,
34 kind: TreeNodeKind,
35}
36
37#[derive(Debug)]
38pub struct ActionTree {
39 root_id: NodeID,
40 nodes: SlotMap<NodeID, TreeNode>,
41 actions: SlotMap<ActionID, TreeAction>,
42}
43
44// -------------------- TreeAction -------------------- //
45
46impl TreeAction {
47 pub fn target(&self) -> &AbsPath {
48 &self.path
49 }
50
51 pub fn action(&self) -> &Action {
52 &self.action
53 }
54}
55
56// -------------------- TreeNodeKind -------------------- //
57
58#[allow(unused)]
59impl TreeNodeKind {
60 fn as_action(&self) -> ActionID {
61 match self {
62 Self::Action(id) => *id,
63 _ => unreachable!(),
64 }
65 }
66
67 fn as_action_mut(&mut self) -> &mut ActionID {
68 match self {
69 Self::Action(id) => id,
70 _ => unreachable!(),
71 }
72 }
73
74 fn as_subtree(&self) -> &HashSet<NodeID> {
75 match self {
76 Self::SubTree(ids) => ids,
77 _ => unreachable!(),
78 }
79 }
80
81 fn as_subtree_mut(&mut self) -> &mut HashSet<NodeID> {
82 match self {
83 Self::SubTree(ids) => ids,
84 _ => unreachable!(),
85 }
86 }
87}
88
89// -------------------- ActionTree -------------------- //
90
91impl Index<ActionID> for ActionTree {
92 type Output = TreeAction;
93
94 fn index(&self, index: ActionID) -> &Self::Output {
95 self.action(index).unwrap()
96 }
97}
98
99impl ActionTree {
100 pub fn new() -> Self {
101 let mut nodes = SlotMap::with_key();
102 let root_id = nodes.insert(TreeNode {
103 path: AbsPathBuf::default(),
104 component: OsString::new(),
105 kind: TreeNodeKind::SubTree(Default::default()),
106 });
107
108 Self {
109 root_id,
110 nodes,
111 actions: Default::default(),
112 }
113 }
114
115 pub fn insert(&mut self, target: &AbsPath, action: Action) -> ActionID {
116 let action_id = self.actions.insert(TreeAction {
117 path: target.to_owned(),
118 action,
119 });
120 self.force_insert_at(target, TreeNodeKind::Action(action_id));
121 action_id
122 }
123
124 pub fn install(&self) -> std::io::Result<()> {
125 for action_id in self.action_ids() {
126 self.install_action(action_id)?;
127 }
128 Ok(())
129 }
130
131 pub fn is_installed(&self, action_id: ActionID) -> bool {
132 let action = &self.actions[action_id];
133 let target = action.target();
134 match action.action() {
135 Action::Link { source } => {
136 let link = match std::fs::read_link(target) {
137 Ok(link) => link,
138 Err(_) => return false,
139 };
140 link.canonicalize().unwrap() == source.canonicalize().unwrap()
141 }
142 Action::Copy { .. } => target.as_ref().exists(),
143 }
144 }
145
146 pub fn uninstall(&self) -> std::io::Result<()> {
147 for action_id in self.action_ids() {
148 self.uninstall_action(action_id)?;
149 }
150 Ok(())
151 }
152
153 pub fn install_action(&self, action_id: ActionID) -> std::io::Result<()> {
154 let action = &self[action_id];
155 match &action.action {
156 Action::Link { source } => {
157 let target = action.target();
158 log::info!("Linking {:?} -> {:?}", source, target);
159 if target.as_ref().is_symlink() {
160 log::trace!("{:?} is a symlink, removing it", target);
161 std::fs::remove_file(target)?;
162 }
163 if let Some(parent) = target.parent() {
164 log::trace!("creating all directories up to {:?}", parent);
165 std::fs::create_dir_all(parent.as_ref())?;
166 }
167 log::trace!("creating symlink {:?} -> {:?}", source, target);
168 std::os::unix::fs::symlink(source, target)?;
169 }
170 Action::Copy { source: _ } => todo!(),
171 }
172 Ok(())
173 }
174
175 pub fn uninstall_action(&self, action_id: ActionID) -> std::io::Result<()> {
176 let action = &self[action_id];
177 if let Action::Link { ref source } = action.action {
178 let target = action.target();
179 if target.as_ref().is_symlink() {
180 log::trace!("{:?} is a symlink", target);
181 let symlink_target = std::fs::read_link(target.as_ref())?;
182 if symlink_target == *source {
183 log::info!("symlink target is {:?}, removing it", source);
184 std::fs::remove_file(target)?;
185 } else {
186 log::trace!(
187 "symlink target is {:?}, not {:?}, not removing it",
188 symlink_target,
189 source
190 );
191 }
192 }
193 }
194 Ok(())
195 }
196
197 pub fn action_ids(&self) -> impl Iterator<Item = ActionID> + '_ {
198 self.actions.keys()
199 }
200
201 pub fn action(&self, action_id: ActionID) -> Option<&TreeAction> {
202 self.actions.get(action_id)
203 }
204
205 /// Creates all nodes up to the given path.
206 /// If one of the nodes is an action node, it will be replaced with a subtree node.
207 fn force_insert_at(&mut self, target: &AbsPath, kind: TreeNodeKind) -> NodeID {
208 let mut curr = self.root_id;
209 for comp in target.components() {
210 {
211 // Try to find node if it exists
212 let curr_node = &mut self.nodes[curr];
213 match curr_node.kind {
214 TreeNodeKind::Action(action) => {
215 self.actions.remove(action);
216 curr_node.kind = TreeNodeKind::SubTree(Default::default());
217 match curr_node.kind {
218 TreeNodeKind::SubTree(ref mut children) => children,
219 _ => unreachable!(),
220 }
221 }
222 TreeNodeKind::SubTree(ref mut children) => children,
223 };
224
225 let children = self.nodes[curr].kind.as_subtree();
226 for &child_id in children.iter() {
227 let child_node = &self.nodes[child_id];
228 if child_node.component == comp {
229 curr = child_id;
230 break;
231 }
232 }
233 }
234 {
235 // Create new node
236 let new_node = TreeNode {
237 path: self.nodes[curr].path.join(comp),
238 component: comp.to_owned(),
239 kind: TreeNodeKind::SubTree(Default::default()),
240 };
241 let new_id = self.nodes.insert(new_node);
242 match &mut self.nodes[curr].kind {
243 TreeNodeKind::SubTree(children) => children.insert(new_id),
244 _ => unreachable!(),
245 };
246 curr = new_id;
247 }
248 }
249 let prev_kind = std::mem::replace(&mut self.nodes[curr].kind, kind);
250 if let TreeNodeKind::SubTree(children) = prev_kind {
251 for &child in children.iter() {
252 self.remove_node(child);
253 }
254 }
255 curr
256 }
257
258 /// Removes the given node.
259 /// Does not remove it from the parent's children node.
260 fn remove_node(&mut self, node_id: NodeID) {
261 let node = self
262 .nodes
263 .remove(node_id)
264 .expect("Node being removed does not exist");
265 match node.kind {
266 TreeNodeKind::Action(action) => {
267 self.actions.remove(action);
268 }
269 TreeNodeKind::SubTree(children) => {
270 for child in children {
271 self.remove_node(child);
272 }
273 }
274 };
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use std::{convert::TryFrom, path::Path};
281
282 use super::*;
283
284 #[test]
285 fn empty_tree() {
286 let _ = ActionTree::new();
287 }
288
289 #[test]
290 fn single_action() {
291 let mut tree = ActionTree::new();
292
293 let action_id = tree.insert(
294 TryFrom::try_from("/home/user/.config/nvim").unwrap(),
295 Action::Link {
296 source: PathBuf::from("nvim"),
297 },
298 );
299
300 let action = &tree[action_id];
301 assert_eq!(
302 action.path.as_path(),
303 AbsPath::new(Path::new("/home/user/.config/nvim"))
304 );
305 }
306
307 #[test]
308 fn subtree_replacement() {
309 let mut tree = ActionTree::new();
310
311 let action_id = tree.insert(
312 TryFrom::try_from("/home/user/.config/nvim").unwrap(),
313 Action::Link {
314 source: PathBuf::from("nvim"),
315 },
316 );
317 let action_id_original = action_id;
318
319 let action = &tree[action_id];
320 assert_eq!(
321 action.path.as_path(),
322 AbsPath::new(Path::new("/home/user/.config/nvim"))
323 );
324
325 let action_id = tree.insert(
326 TryFrom::try_from("/home/user/.config/nvim/init.vim").unwrap(),
327 Action::Link {
328 source: PathBuf::from("nvim/init.vim"),
329 },
330 );
331
332 let action = &tree[action_id];
333 assert_eq!(
334 action.path.as_path(),
335 AbsPath::new(Path::new("/home/user/.config/nvim/init.vim"))
336 );
337
338 eprintln!("{:#?}", tree);
339 assert!(tree.action(action_id_original).is_none());
340 }
341}
diff --git a/src/dotup/mod.rs b/src/dotup/mod.rs
index a70cde5..cb163db 100644
--- a/src/dotup/mod.rs
+++ b/src/dotup/mod.rs
@@ -1,13 +1,12 @@
1mod action_tree;
2mod cfg; 1mod cfg;
3mod paths; 2mod paths;
4 3
5use std::collections::HashSet;
6use std::{ 4use std::{
7 collections::HashMap, 5 collections::{HashMap, HashSet, VecDeque},
8 path::{Path, PathBuf}, 6 path::{Path, PathBuf},
9}; 7};
10 8
9use colored::Colorize;
11use slotmap::SlotMap; 10use slotmap::SlotMap;
12use thiserror::Error; 11use thiserror::Error;
13 12
@@ -20,37 +19,59 @@ slotmap::new_key_type! { pub struct GroupID; }
20#[derive(Debug, Error)] 19#[derive(Debug, Error)]
21pub enum Error { 20pub enum Error {
22 #[error(transparent)] 21 #[error(transparent)]
23 ParseError(#[from] cfg::ParseError), 22 InvalidConfig(#[from] cfg::ParseError),
24 #[error("error: {0}")] 23 #[error("error: {0}")]
25 Custom(String), 24 Custom(String),
26 #[error(transparent)] 25 #[error(transparent)]
27 IOError(#[from] std::io::Error), 26 IOError(#[from] std::io::Error),
28} 27}
29 28
30#[derive(Debug, Default)] 29impl Error {
31pub struct Group { 30 fn custom(e: impl std::fmt::Display) -> Self {
32 name: String, 31 Self::Custom(e.to_string())
33 parent: GroupID, 32 }
34 children: HashMap<String, GroupID>, 33}
35 actions: Vec<Action>, 34
35#[derive(Debug, Clone)]
36pub struct Context {
37 working_directory: AbsPathBuf,
38 destination_directory: AbsPathBuf,
39}
40
41impl Context {
42 pub fn new(
43 working_directory: impl Into<PathBuf>,
44 destination_directory: impl Into<PathBuf>,
45 ) -> std::io::Result<Self> {
46 let working_directory = working_directory.into().canonicalize()?;
47 let destination_directory = destination_directory.into().canonicalize()?;
48 let working_directory =
49 AbsPathBuf::try_from(working_directory).map_err(std::io::Error::other)?;
50 let destination_directory =
51 AbsPathBuf::try_from(destination_directory).map_err(std::io::Error::other)?;
52 Ok(Self {
53 working_directory,
54 destination_directory,
55 })
56 }
36} 57}
37 58
38#[derive(Debug)] 59#[derive(Debug)]
39pub struct Dotup { 60pub struct InstallParams {
40 root_id: GroupID, 61 pub force: bool,
41 groups: SlotMap<GroupID, Group>,
42} 62}
43 63
44#[derive(Debug, Clone, Copy)] 64impl Default for InstallParams {
45pub struct InstallParams<'p> { 65 fn default() -> Self {
46 pub cwd: &'p Path, 66 Self { force: false }
47 pub home: &'p Path, 67 }
48} 68}
49 69
50#[derive(Debug, Clone, Copy)] 70#[derive(Debug)]
51pub struct UninstallParams<'p> { 71pub struct Dotup {
52 pub cwd: &'p Path, 72 context: Context,
53 pub home: &'p Path, 73 root_id: GroupID,
74 groups: SlotMap<GroupID, Group>,
54} 75}
55 76
56#[derive(Debug)] 77#[derive(Debug)]
@@ -59,21 +80,31 @@ struct KeyValueParser {
59 keyvalues: Vec<cfg::KeyValue>, 80 keyvalues: Vec<cfg::KeyValue>,
60} 81}
61 82
83#[derive(Debug, Default)]
84struct Group {
85 name: String,
86 parent: GroupID,
87 children: HashMap<String, GroupID>,
88 actions: Vec<Action>,
89}
90
62#[derive(Debug, Clone)] 91#[derive(Debug, Clone)]
63struct IncludeAction { 92struct IncludeAction {
64 group: String, 93 group: String,
94 group_id: GroupID,
65} 95}
66 96
67#[derive(Debug, Clone)] 97#[derive(Debug, Clone)]
68struct LinkAction { 98struct LinkAction {
69 source: PathBuf, 99 source: AbsPathBuf,
70 target: PathBuf, 100 target: AbsPathBuf,
71} 101}
72 102
103#[allow(dead_code)]
73#[derive(Debug, Clone)] 104#[derive(Debug, Clone)]
74struct CopyAction { 105struct CopyAction {
75 source: PathBuf, 106 source: AbsPathBuf,
76 target: PathBuf, 107 target: AbsPathBuf,
77} 108}
78 109
79#[derive(Debug, Clone)] 110#[derive(Debug, Clone)]
@@ -83,14 +114,20 @@ enum Action {
83 Copy(CopyAction), 114 Copy(CopyAction),
84} 115}
85 116
86pub fn load(content: &str) -> Result<Dotup> { 117#[derive(Debug, Clone)]
118enum ExecutableAction {
119 Link(LinkAction),
120 Copy(CopyAction),
121}
122
123pub fn load(context: Context, content: &str) -> Result<Dotup> {
87 let config = cfg::parse(content)?; 124 let config = cfg::parse(content)?;
88 Dotup::from_config(config) 125 new(context, config)
89} 126}
90 127
91pub fn load_file(path: impl AsRef<Path>) -> Result<Dotup> { 128pub fn load_file(context: Context, path: impl AsRef<Path>) -> Result<Dotup> {
92 let content = std::fs::read_to_string(path)?; 129 let content = std::fs::read_to_string(path)?;
93 load(&content) 130 load(context, &content)
94} 131}
95 132
96pub fn format(content: &str) -> Result<String> { 133pub fn format(content: &str) -> Result<String> {
@@ -109,190 +146,339 @@ pub fn format_file_inplace(path: &Path) -> Result<()> {
109 Ok(()) 146 Ok(())
110} 147}
111 148
112// -------------------- Dotup -------------------- // 149pub fn install(dotup: &Dotup, params: &InstallParams, group: &str) -> Result<()> {
150 fn prompt_overwrite(params: &InstallParams, target: &AbsPath) -> Result<bool> {
151 if params.force {
152 return Ok(true);
153 }
113 154
114impl Dotup { 155 let result = inquire::Confirm::new(&format!(
115 pub fn find_group_by_name(&self, name: &str) -> Option<GroupID> { 156 "overwrite existing file/directory '{}'?",
116 self.find_group_by_name_rooted(self.root_id, name) 157 target.display()
158 ))
159 .with_default(false)
160 .with_help_message("Delete the existing file/directory")
161 .prompt();
162
163 match result {
164 Ok(overwrite) => Ok(overwrite),
165 Err(e) => match e {
166 inquire::InquireError::NotTTY => Ok(false),
167 _ => Err(Error::custom(e)),
168 },
169 }
117 } 170 }
118 171
119 pub fn install(&self, params: InstallParams, group_id: GroupID) -> Result<()> { 172 let group_id = get_group_by_name(dotup, group)?;
120 let action_tree = self.build_action_tree(params.cwd, params.home, group_id)?; 173 let executable = collect_group_executable_actions(dotup, group_id)?;
121 action_tree.install()?; 174
122 Ok(()) 175 for action in executable {
176 match action {
177 ExecutableAction::Link(LinkAction { source, target }) => {
178 log::debug!("linking '{}' to '{}'", source.display(), target.display());
179 if fs_exists(&target)? {
180 let metadata = fs_symlink_metadata(&target)?;
181
182 // Early return if the symlink already points to the correct source
183 if metadata.is_symlink() && fs_symlink_points_to(&target, &source)? {
184 return Ok(());
185 }
186
187 if !prompt_overwrite(params, &target)? {
188 return Ok(());
189 }
190
191 fs_remove(&target)?;
192 }
193
194 fs_create_dir_all_upto(&target)?;
195 fs_create_symlink(&source, &target)?;
196 }
197 ExecutableAction::Copy(_) => todo!(),
198 }
123 } 199 }
124 200
125 pub fn uninstall(&self, params: UninstallParams, group_id: GroupID) -> Result<()> { 201 Ok(())
126 let action_tree = self.build_action_tree(params.cwd, params.home, group_id)?; 202}
127 action_tree.uninstall()?; 203
128 Ok(()) 204pub fn uninstall(dotup: &Dotup, group: &str) -> Result<()> {
205 let group_id = get_group_by_name(dotup, group)?;
206 let executable = collect_group_executable_actions(dotup, group_id)?;
207
208 for action in executable {
209 match action {
210 ExecutableAction::Link(LinkAction { source, target }) => {
211 if !fs_exists(&target)? {
212 return Ok(());
213 }
214
215 if fs_symlink_points_to(&target, &source)? {
216 fs_remove(&target)?;
217 }
218 }
219 ExecutableAction::Copy(_) => todo!(),
220 }
129 } 221 }
130 222
131 pub fn status(&self, params: InstallParams, group_id: GroupID) -> Result<()> { 223 Ok(())
132 let action_tree = self.build_action_tree(params.cwd, params.home, group_id)?; 224}
133 for action_id in action_tree.action_ids() { 225
134 let prefix = if action_tree.is_installed(action_id) { 226pub fn status(dotup: &Dotup, group: &str) -> Result<()> {
135 "INSTALLED" 227 fn display_status(dotup: &Dotup, group_id: GroupID, depth: u32) -> Result<()> {
136 } else { 228 let group = &dotup.groups[group_id];
137 "NOT INSTALLED" 229
138 }; 230 println!("{}{}", " ".repeat(depth as usize), group.name.blue());
139 let action = action_tree.action(action_id).unwrap(); 231 log::trace!("displaying status for group '{}'", group.name);
140 let source = match action.action() { 232
141 action_tree::Action::Link { ref source } => source, 233 for action in group.actions.iter() {
142 action_tree::Action::Copy { ref source } => source, 234 match action {
143 }; 235 Action::Include(include) => {
144 let target = action.target(); 236 log::trace!("displaying status for included group '{}'", include.group);
145 println!("{}: {} -> {}", prefix, source.display(), target.display()); 237 display_status(dotup, include.group_id, depth + 1)?;
238 }
239 Action::Link(link) => {
240 log::trace!("displaying status for link '{}'", link.target.display());
241
242 let target = link.target.display();
243 let source = link.source.display();
244 let installed = is_link_installed(&link)?;
245 let output = format!(
246 "{}{} -> {}",
247 " ".repeat(depth as usize + 1),
248 target,
249 source
250 );
251 println!(
252 "{}",
253 if installed {
254 output.green()
255 } else {
256 output.red()
257 }
258 );
259 }
260 Action::Copy(_) => todo!(),
261 }
146 } 262 }
147 Ok(()) 263 Ok(())
148 } 264 }
265
266 let group_id = get_group_by_name(dotup, group)?;
267 display_status(dotup, group_id, 0)
268}
269
270fn new(context: Context, config: cfg::Config) -> Result<Dotup> {
271 let mut groups = SlotMap::default();
272 let root_id = groups.insert(Default::default());
273 let mut dotup = Dotup {
274 context,
275 root_id,
276 groups,
277 };
278
279 for group_cfg in config.groups {
280 insert_config_group(&mut dotup, root_id, group_cfg)?;
281 }
282
283 resolve_includes(&mut dotup)?;
284
285 Ok(dotup)
149} 286}
150 287
151impl Dotup { 288fn insert_config_group(
152 fn from_config(config: cfg::Config) -> Result<Self> { 289 dotup: &mut Dotup,
153 let mut groups = SlotMap::default(); 290 parent_id: GroupID,
154 let root_id = groups.insert(Default::default()); 291 mut group_cfg: cfg::Group,
155 let mut dotup = Self { root_id, groups }; 292) -> Result<()> {
293 let parent = &mut dotup.groups[parent_id];
294 if parent.children.contains_key(&group_cfg.name) {
295 return Err(Error::Custom(format!(
296 "group '{}' at {} already exists",
297 group_cfg.name, group_cfg.location,
298 )));
299 }
156 300
157 for group in config.groups { 301 let mut group = Group {
158 dotup.insert_group(root_id, group)?; 302 name: group_cfg.name.clone(),
303 parent: parent_id,
304 children: Default::default(),
305 actions: Default::default(),
306 };
307
308 for item in group_cfg
309 .items
310 .drain_filter(|item| std::matches!(item, cfg::GroupItem::Action(_)))
311 {
312 if let cfg::GroupItem::Action(action) = item {
313 let action = convert_config_action(&dotup.context, action)?;
314 group.actions.push(action);
159 } 315 }
316 }
317
318 let group_id = dotup.groups.insert(group);
319 let parent = &mut dotup.groups[parent_id];
320 parent.children.insert(group_cfg.name, group_id);
321
322 for item in group_cfg.items {
323 if let cfg::GroupItem::Group(group) = item {
324 insert_config_group(dotup, group_id, group)?;
325 }
326 }
327
328 Ok(())
329}
160 330
161 Ok(dotup) 331fn resolve_includes(dotup: &mut Dotup) -> Result<()> {
332 struct Patch {
333 group_id: GroupID,
334 action_idx: usize,
335 target_id: GroupID,
162 } 336 }
163 337
164 fn find_group_by_name_rooted(&self, root: GroupID, name: &str) -> Option<GroupID> { 338 let mut patches = Vec::new();
165 let trimmed = name.trim_start_matches('.'); 339 for group_id in dotup.groups.keys() {
166 let rel_levels = name.len() - trimmed.len(); 340 for idx in 0..dotup.groups[group_id].actions.len() {
167 let mut current = self.root_id; 341 let action = &dotup.groups[group_id].actions[idx];
168 342 let target = match action {
169 if rel_levels != 0 { 343 Action::Include(include) => include.group.as_str(),
170 current = root; 344 _ => continue,
171 for _ in 0..rel_levels - 1 { 345 };
172 current = self.groups[current].parent; 346
173 if current == self.root_id { 347 let target_id = match find_group_by_name_rooted(dotup, group_id, target) {
174 break; 348 Some(target_id) => target_id,
349 None => {
350 return Err(Error::Custom(format!("group '{}' not found", target)));
175 } 351 }
176 } 352 };
177 }
178 353
179 for comp in trimmed.split('.') { 354 patches.push(Patch {
180 let group = &self.groups[current]; 355 group_id,
181 let child_id = group.children.get(comp)?; 356 action_idx: idx,
182 current = *child_id; 357 target_id,
358 });
183 } 359 }
184 Some(current)
185 } 360 }
186 361
187 fn insert_group(&mut self, parent_id: GroupID, mut group_cfg: cfg::Group) -> Result<()> { 362 for patch in patches {
188 let parent = &mut self.groups[parent_id]; 363 let group = &mut dotup.groups[patch.group_id];
189 if parent.children.contains_key(&group_cfg.name) { 364 let action = &mut group.actions[patch.action_idx];
190 return Err(Error::Custom(format!( 365 if let Action::Include(include) = action {
191 "group '{}' at {} already exists", 366 include.group_id = patch.target_id;
192 group_cfg.name, group_cfg.location,
193 )));
194 } 367 }
368 }
195 369
196 let mut group = Group { 370 Ok(())
197 name: group_cfg.name.clone(), 371}
198 parent: parent_id, 372
199 children: Default::default(), 373fn convert_config_action(context: &Context, cfg_action: cfg::Action) -> Result<Action> {
200 actions: Default::default(), 374 let mut parser = KeyValueParser::new(cfg_action.location, cfg_action.keyvalues);
201 }; 375 match cfg_action.kind.as_str() {
202 376 "include" => {
203 for item in group_cfg 377 let group = parser.expect("group")?;
204 .items 378 parser.finalize()?;
205 .drain_filter(|item| std::matches!(item, cfg::GroupItem::Action(_))) 379 Ok(Action::Include(IncludeAction {
206 { 380 group,
207 if let cfg::GroupItem::Action(action) = item { 381 group_id: Default::default(),
208 let action = cfg_action_to_action(action)?; 382 }))
209 group.actions.push(action); 383 }
210 } 384 "link" => {
385 let source = PathBuf::from(parser.expect("source")?);
386 let target = PathBuf::from(parser.expect("target")?);
387 parser.finalize()?;
388 Ok(Action::Link(LinkAction {
389 source: make_path_absolute(&context.working_directory, &source),
390 target: make_path_absolute(&context.destination_directory, &target),
391 }))
211 } 392 }
393 "copy" => {
394 let source = PathBuf::from(parser.expect("source")?);
395 let target = PathBuf::from(parser.expect("target")?);
396 parser.finalize()?;
397 Ok(Action::Copy(CopyAction {
398 source: make_path_absolute(&context.working_directory, &source),
399 target: make_path_absolute(&context.destination_directory, &target),
400 }))
401 }
402 _ => Err(Error::Custom(format!(
403 "unknown action '{}' at {}",
404 cfg_action.kind, cfg_action.location
405 ))),
406 }
407}
212 408
213 let group_id = self.groups.insert(group); 409fn get_group_by_name(dotup: &Dotup, name: &str) -> Result<GroupID> {
214 let parent = &mut self.groups[parent_id]; 410 find_group_by_name(dotup, name)
215 parent.children.insert(group_cfg.name, group_id); 411 .ok_or_else(|| Error::Custom(format!("group '{}' not found", name,)))
412}
216 413
217 for item in group_cfg.items { 414fn find_group_by_name(dotup: &Dotup, name: &str) -> Option<GroupID> {
218 if let cfg::GroupItem::Group(group) = item { 415 find_group_by_name_rooted(dotup, dotup.root_id, name)
219 self.insert_group(group_id, group)?; 416}
417
418fn find_group_by_name_rooted(dotup: &Dotup, root: GroupID, name: &str) -> Option<GroupID> {
419 let trimmed = name.trim_start_matches('.');
420 let rel_levels = name.len() - trimmed.len();
421 let mut current = dotup.root_id;
422
423 if rel_levels != 0 {
424 current = root;
425 for _ in 0..rel_levels - 1 {
426 current = dotup.groups[current].parent;
427 if current == dotup.root_id {
428 break;
220 } 429 }
221 } 430 }
431 }
222 432
223 Ok(()) 433 for comp in trimmed.split('.') {
434 let group = &dotup.groups[current];
435 let child_id = group.children.get(comp)?;
436 current = *child_id;
224 } 437 }
438 Some(current)
439}
225 440
226 fn build_action_tree( 441fn collect_group_executable_actions(
227 &self, 442 dotup: &Dotup,
228 cwd: &Path, 443 group_id: GroupID,
229 home: &Path, 444) -> Result<Vec<ExecutableAction>> {
230 group_id: GroupID, 445 let mut executable = Vec::new();
231 ) -> Result<action_tree::ActionTree> { 446 let mut visited = HashSet::new();
232 fn inner_helper( 447 let mut queue = VecDeque::from_iter(std::iter::once(group_id));
233 dotup: &Dotup, 448
234 cwd: &AbsPath, 449 while let Some(group_id) = queue.pop_front() {
235 home: &AbsPath, 450 if !visited.insert(group_id) {
236 group_id: GroupID, 451 continue;
237 tree: &mut action_tree::ActionTree, 452 }
238 visited: &mut HashSet<GroupID>, 453
239 ) -> Result<()> { 454 let group = &dotup.groups[group_id];
240 if visited.contains(&group_id) { 455 for action in &group.actions {
241 return Ok(()); 456 match action {
242 } 457 Action::Include(include) => {
243 visited.insert(group_id); 458 queue.push_back(include.group_id);
244
245 let group = &dotup.groups[group_id];
246 for action in group.actions.iter() {
247 match action {
248 Action::Include(action) => {
249 let include_id = dotup
250 .find_group_by_name_rooted(group_id, &action.group)
251 .ok_or_else(|| {
252 Error::Custom(format!(
253 "group '{}' not found in include from group '{}'",
254 action.group, dotup.groups[group_id].name,
255 ))
256 })?;
257 inner_helper(dotup, cwd, home, include_id, tree, visited)?;
258 }
259 Action::Link(action) => {
260 let source = make_absolute_path(cwd, &action.source).into();
261 let target = make_absolute_path(home, &action.target);
262 tree.insert(&target, action_tree::Action::Link { source });
263 }
264 Action::Copy(action) => {
265 let source = make_absolute_path(cwd, &action.source).into();
266 let target = make_absolute_path(home, &action.target);
267 tree.insert(&target, action_tree::Action::Copy { source });
268 }
269 } 459 }
460 Action::Link(action) => executable.push(ExecutableAction::Link(action.clone())),
461 Action::Copy(action) => executable.push(ExecutableAction::Copy(action.clone())),
270 } 462 }
271
272 Ok(())
273 } 463 }
464 }
465
466 Ok(executable)
467}
468
469fn is_link_installed(link: &LinkAction) -> Result<bool> {
470 if !fs_exists(&link.target)? {
471 Ok(false)
472 } else {
473 fs_symlink_points_to(&link.target, &link.source)
474 }
475}
274 476
275 let cwd = AbsPathBuf::try_from( 477fn make_path_absolute(root: &AbsPath, path: &Path) -> AbsPathBuf {
276 cwd.canonicalize() 478 if path.is_absolute() {
277 .expect("failed to canonicalize current working directory path"), 479 AbsPathBuf::try_from(path.to_owned()).unwrap()
278 ) 480 } else {
279 .unwrap(); 481 root.join(path)
280 let home = AbsPathBuf::try_from(
281 home.canonicalize()
282 .expect("failed to canonicalize home directory path"),
283 )
284 .unwrap();
285
286 let mut tree = action_tree::ActionTree::new();
287 inner_helper(
288 self,
289 &cwd,
290 &home,
291 group_id,
292 &mut tree,
293 &mut Default::default(),
294 )?;
295 Ok(tree)
296 } 482 }
297} 483}
298 484
@@ -328,47 +514,128 @@ impl KeyValueParser {
328 } 514 }
329} 515}
330 516
331// -------------------- Misc -------------------- // 517// -------------------- Filesystem -------------------- //
332 518
333fn cfg_action_to_action(cfg_action: cfg::Action) -> Result<Action> { 519fn fs_exists(path: impl AsRef<Path>) -> Result<bool> {
334 let mut parser = KeyValueParser::new(cfg_action.location, cfg_action.keyvalues); 520 path.as_ref().try_exists().map_err(|err| {
335 match cfg_action.kind.as_str() { 521 Error::Custom(format!(
336 "include" => { 522 "failed to check existence of target '{}': {}",
337 let group = parser.expect("group")?; 523 path.as_ref().display(),
338 parser.finalize()?; 524 err
339 Ok(Action::Include(IncludeAction { group })) 525 ))
340 } 526 })
341 "link" => {
342 let source = parser.expect("source")?;
343 let target = parser.expect("target")?;
344 parser.finalize()?;
345 Ok(Action::Link(LinkAction {
346 source: PathBuf::from(source),
347 target: PathBuf::from(target),
348 }))
349 }
350 "copy" => {
351 let source = parser.expect("source")?;
352 let target = parser.expect("target")?;
353 parser.finalize()?;
354 Ok(Action::Copy(CopyAction {
355 source: PathBuf::from(source),
356 target: PathBuf::from(target),
357 }))
358 }
359 _ => Err(Error::Custom(format!(
360 "unknown action '{}' at {}",
361 cfg_action.kind, cfg_action.location
362 ))),
363 }
364} 527}
365 528
366/// Returns `path` if it is already absolute. 529#[allow(unused)]
367/// Otherwise makes it absolute by prepending `self.root`. 530fn fs_metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata> {
368fn make_absolute_path(root: &AbsPath, path: &Path) -> AbsPathBuf { 531 let path = path.as_ref();
369 if path.is_absolute() { 532 std::fs::metadata(path).map_err(|err| {
370 AbsPathBuf::try_from(path).unwrap() 533 Error::Custom(format!(
534 "failed to get metadata of target '{}': {}",
535 path.display(),
536 err
537 ))
538 })
539}
540
541fn fs_symlink_metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata> {
542 let path = path.as_ref();
543 std::fs::symlink_metadata(path).map_err(|err| {
544 Error::Custom(format!(
545 "failed to get metadata of target '{}': {}",
546 path.display(),
547 err
548 ))
549 })
550}
551
552fn fs_read_symlink(path: impl AsRef<Path>) -> Result<PathBuf> {
553 let path = path.as_ref();
554 std::fs::read_link(path).map_err(|err| {
555 Error::Custom(format!(
556 "failed to read symlink '{}': {}",
557 path.display(),
558 err
559 ))
560 })
561}
562
563fn fs_canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
564 let path = path.as_ref();
565 path.canonicalize().map_err(|err| {
566 Error::Custom(format!(
567 "failed to canonicalize path '{}': {}",
568 path.display(),
569 err
570 ))
571 })
572}
573
574fn fs_symlink_points_to(path: impl AsRef<Path>, target: impl AsRef<Path>) -> Result<bool> {
575 let path = path.as_ref();
576 let target = target.as_ref();
577 let link_target = fs_read_symlink(path)?;
578 let target_canonical = fs_canonicalize(target)?;
579 let link_target_canonical = fs_canonicalize(link_target)?;
580 Ok(target_canonical == link_target_canonical)
581}
582
583fn fs_remove(path: impl AsRef<Path>) -> Result<()> {
584 let path = path.as_ref();
585 log::debug!("removing target '{}'", path.display());
586
587 if !fs_exists(path)? {
588 return Ok(());
589 }
590
591 let metadata = fs_symlink_metadata(path)?;
592 if metadata.is_dir() {
593 std::fs::remove_dir_all(path).map_err(|err| {
594 Error::Custom(format!(
595 "failed to remove target '{}': {}",
596 path.display(),
597 err
598 ))
599 })
371 } else { 600 } else {
372 AbsPathBuf::from_rel(root, TryFrom::try_from(path).unwrap()) 601 std::fs::remove_file(path).map_err(|err| {
602 Error::Custom(format!(
603 "failed to remove target '{}': {}",
604 path.display(),
605 err
606 ))
607 })
373 } 608 }
374} 609}
610
611fn fs_create_symlink(source: impl AsRef<Path>, target: impl AsRef<Path>) -> Result<()> {
612 let source = source.as_ref();
613 let target = target.as_ref();
614 log::debug!(
615 "creating symlink '{}' -> '{}'",
616 target.display(),
617 source.display()
618 );
619 std::os::unix::fs::symlink(source, target).map_err(|err| {
620 Error::Custom(format!(
621 "failed to create symlink '{}' -> '{}': {}",
622 target.display(),
623 source.display(),
624 err
625 ))
626 })
627}
628
629fn fs_create_dir_all_upto(path: impl AsRef<Path>) -> Result<()> {
630 let path = path.as_ref();
631 let parent = path.parent().ok_or_else(|| {
632 Error::Custom(format!("failed to get parent of path '{}'", path.display()))
633 })?;
634 std::fs::create_dir_all(parent).map_err(|err| {
635 Error::Custom(format!(
636 "failed to create directory '{}': {}",
637 parent.display(),
638 err
639 ))
640 })
641}
diff --git a/src/main.rs b/src/main.rs
index 80a03b9..f035a45 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,13 @@
1#![feature(drain_filter)] 1#![feature(drain_filter)]
2#![feature(io_error_other)]
2 3
3//pub mod config;
4pub mod dotup; 4pub mod dotup;
5 5
6use std::path::PathBuf; 6use std::path::PathBuf;
7 7
8use anyhow::Context; 8use anyhow::Context;
9use clap::{Parser, Subcommand}; 9use clap::{Parser, Subcommand};
10use dotup::InstallParams;
10 11
11#[derive(Parser, Debug)] 12#[derive(Parser, Debug)]
12struct GlobalFlags { 13struct GlobalFlags {
@@ -27,6 +28,9 @@ enum SubCommand {
27 28
28#[derive(Parser, Debug)] 29#[derive(Parser, Debug)]
29struct InstallArgs { 30struct InstallArgs {
31 #[clap(short, long)]
32 force: bool,
33
30 groups: Vec<String>, 34 groups: Vec<String>,
31} 35}
32 36
@@ -47,6 +51,7 @@ struct FormatArgs {}
47struct Args { 51struct Args {
48 #[clap(flatten)] 52 #[clap(flatten)]
49 globals: GlobalFlags, 53 globals: GlobalFlags,
54
50 #[clap(subcommand)] 55 #[clap(subcommand)]
51 command: SubCommand, 56 command: SubCommand,
52} 57}
@@ -64,6 +69,10 @@ fn main() -> anyhow::Result<()> {
64} 69}
65 70
66impl GlobalFlags { 71impl GlobalFlags {
72 fn get_working_dir(&self) -> PathBuf {
73 self.config.parent().unwrap().to_path_buf()
74 }
75
67 fn base_path_or_default(&self) -> PathBuf { 76 fn base_path_or_default(&self) -> PathBuf {
68 self.base.clone().unwrap_or_else(|| { 77 self.base.clone().unwrap_or_else(|| {
69 PathBuf::from(std::env::var("HOME").expect("failed to get HOME directory")) 78 PathBuf::from(std::env::var("HOME").expect("failed to get HOME directory"))
@@ -72,49 +81,29 @@ impl GlobalFlags {
72} 81}
73 82
74fn command_install(globals: GlobalFlags, args: InstallArgs) -> anyhow::Result<()> { 83fn command_install(globals: GlobalFlags, args: InstallArgs) -> anyhow::Result<()> {
75 let dotup = dotup::load_file(&globals.config).context("failed to parse config")?; 84 let context = helper_new_context(&globals)?;
76 let cwd = std::env::current_dir().context("failed to get current directory")?; 85 let dotup = dotup::load_file(context, &globals.config).context("failed to parse config")?;
77 let install_params = dotup::InstallParams { 86 let params = InstallParams { force: args.force };
78 cwd: &cwd,
79 home: &globals.base_path_or_default(),
80 };
81 for group in args.groups { 87 for group in args.groups {
82 match dotup.find_group_by_name(&group) { 88 dotup::install(&dotup, &params, &group)?;
83 Some(group_id) => dotup.install(install_params, group_id)?,
84 None => log::error!("group not found: {}", group),
85 };
86 } 89 }
87 Ok(()) 90 Ok(())
88} 91}
89 92
90fn command_uninstall(globals: GlobalFlags, args: UninstallArgs) -> anyhow::Result<()> { 93fn command_uninstall(globals: GlobalFlags, args: UninstallArgs) -> anyhow::Result<()> {
91 let dotup = dotup::load_file(&globals.config).context("failed to parse config")?; 94 let context = helper_new_context(&globals)?;
92 let cwd = std::env::current_dir().context("failed to get current directory")?; 95 let dotup = dotup::load_file(context, &globals.config).context("failed to parse config")?;
93 let uninstall_params = dotup::UninstallParams {
94 cwd: &cwd,
95 home: &globals.base_path_or_default(),
96 };
97 for group in args.groups { 96 for group in args.groups {
98 match dotup.find_group_by_name(&group) { 97 dotup::uninstall(&dotup, &group)?;
99 Some(group_id) => dotup.uninstall(uninstall_params, group_id)?,
100 None => log::error!("group not found: {}", group),
101 };
102 } 98 }
103 Ok(()) 99 Ok(())
104} 100}
105 101
106fn command_status(globals: GlobalFlags, args: StatusArgs) -> anyhow::Result<()> { 102fn command_status(globals: GlobalFlags, args: StatusArgs) -> anyhow::Result<()> {
107 let dotup = dotup::load_file(&globals.config).context("failed to parse config")?; 103 let context = helper_new_context(&globals)?;
108 let cwd = std::env::current_dir().context("failed to get current directory")?; 104 let dotup = dotup::load_file(context, &globals.config).context("failed to parse config")?;
109 let install_params = dotup::InstallParams {
110 cwd: &cwd,
111 home: &globals.base_path_or_default(),
112 };
113 for group in args.groups { 105 for group in args.groups {
114 match dotup.find_group_by_name(&group) { 106 dotup::status(&dotup, &group)?;
115 Some(group_id) => dotup.status(install_params, group_id)?,
116 None => log::error!("group not found: {}", group),
117 };
118 } 107 }
119 Ok(()) 108 Ok(())
120} 109}
@@ -123,3 +112,9 @@ fn command_format(globals: GlobalFlags, _args: FormatArgs) -> anyhow::Result<()>
123 dotup::format_file_inplace(&globals.config).context("failed to format config")?; 112 dotup::format_file_inplace(&globals.config).context("failed to format config")?;
124 Ok(()) 113 Ok(())
125} 114}
115
116fn helper_new_context(globals: &GlobalFlags) -> anyhow::Result<dotup::Context> {
117 let cwd = globals.get_working_dir();
118 let home = globals.base_path_or_default();
119 Ok(dotup::Context::new(cwd, home)?)
120}