diff options
| -rw-r--r-- | Cargo.lock | 530 | ||||
| -rw-r--r-- | Cargo.toml | 12 | ||||
| -rw-r--r-- | src/dotup/action_tree.rs | 341 | ||||
| -rw-r--r-- | src/dotup/mod.rs | 697 | ||||
| -rw-r--r-- | src/main.rs | 57 |
5 files changed, 979 insertions, 658 deletions
| @@ -12,6 +12,46 @@ dependencies = [ | |||
| 12 | ] | 12 | ] |
| 13 | 13 | ||
| 14 | [[package]] | 14 | [[package]] |
| 15 | name = "anstream" | ||
| 16 | version = "0.2.6" | ||
| 17 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 18 | checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" | ||
| 19 | dependencies = [ | ||
| 20 | "anstyle", | ||
| 21 | "anstyle-parse", | ||
| 22 | "anstyle-wincon", | ||
| 23 | "concolor-override", | ||
| 24 | "concolor-query", | ||
| 25 | "is-terminal", | ||
| 26 | "utf8parse", | ||
| 27 | ] | ||
| 28 | |||
| 29 | [[package]] | ||
| 30 | name = "anstyle" | ||
| 31 | version = "0.3.5" | ||
| 32 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 33 | checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" | ||
| 34 | |||
| 35 | [[package]] | ||
| 36 | name = "anstyle-parse" | ||
| 37 | version = "0.1.1" | ||
| 38 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 39 | checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" | ||
| 40 | dependencies = [ | ||
| 41 | "utf8parse", | ||
| 42 | ] | ||
| 43 | |||
| 44 | [[package]] | ||
| 45 | name = "anstyle-wincon" | ||
| 46 | version = "0.2.0" | ||
| 47 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 48 | checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" | ||
| 49 | dependencies = [ | ||
| 50 | "anstyle", | ||
| 51 | "windows-sys 0.45.0", | ||
| 52 | ] | ||
| 53 | |||
| 54 | [[package]] | ||
| 15 | name = "anyhow" | 55 | name = "anyhow" |
| 16 | version = "1.0.70" | 56 | version = "1.0.70" |
| 17 | source = "registry+https://github.com/rust-lang/crates.io-index" | 57 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -23,7 +63,7 @@ version = "0.2.14" | |||
| 23 | source = "registry+https://github.com/rust-lang/crates.io-index" | 63 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" | 64 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" |
| 25 | dependencies = [ | 65 | dependencies = [ |
| 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" | |||
| 57 | checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" | 97 | checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" |
| 58 | 98 | ||
| 59 | [[package]] | 99 | [[package]] |
| 100 | name = "cc" | ||
| 101 | version = "1.0.79" | ||
| 102 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 103 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" | ||
| 104 | |||
| 105 | [[package]] | ||
| 60 | name = "cfg-if" | 106 | name = "cfg-if" |
| 61 | version = "1.0.0" | 107 | version = "1.0.0" |
| 62 | source = "registry+https://github.com/rust-lang/crates.io-index" | 108 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -64,51 +110,107 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | |||
| 64 | 110 | ||
| 65 | [[package]] | 111 | [[package]] |
| 66 | name = "clap" | 112 | name = "clap" |
| 67 | version = "3.2.23" | 113 | version = "4.2.1" |
| 68 | source = "registry+https://github.com/rust-lang/crates.io-index" | 114 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 69 | checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" | 115 | checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" |
| 70 | dependencies = [ | 116 | dependencies = [ |
| 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]] | ||
| 123 | name = "clap_builder" | ||
| 124 | version = "4.2.1" | ||
| 125 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 126 | checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" | ||
| 127 | dependencies = [ | ||
| 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]] |
| 83 | name = "clap_derive" | 136 | name = "clap_derive" |
| 84 | version = "3.2.18" | 137 | version = "4.2.0" |
| 85 | source = "registry+https://github.com/rust-lang/crates.io-index" | 138 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 86 | checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" | 139 | checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" |
| 87 | dependencies = [ | 140 | dependencies = [ |
| 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]] |
| 96 | name = "clap_lex" | 148 | name = "clap_lex" |
| 97 | version = "0.2.4" | 149 | version = "0.4.1" |
| 98 | source = "registry+https://github.com/rust-lang/crates.io-index" | 150 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 99 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" | 151 | checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" |
| 152 | |||
| 153 | [[package]] | ||
| 154 | name = "colored" | ||
| 155 | version = "2.0.0" | ||
| 156 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 157 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" | ||
| 158 | dependencies = [ | ||
| 159 | "atty", | ||
| 160 | "lazy_static", | ||
| 161 | "winapi", | ||
| 162 | ] | ||
| 163 | |||
| 164 | [[package]] | ||
| 165 | name = "concolor-override" | ||
| 166 | version = "1.0.0" | ||
| 167 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 168 | checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" | ||
| 169 | |||
| 170 | [[package]] | ||
| 171 | name = "concolor-query" | ||
| 172 | version = "0.3.3" | ||
| 173 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 174 | checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" | ||
| 175 | dependencies = [ | ||
| 176 | "windows-sys 0.45.0", | ||
| 177 | ] | ||
| 178 | |||
| 179 | [[package]] | ||
| 180 | name = "crossterm" | ||
| 181 | version = "0.25.0" | ||
| 182 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 183 | checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" | ||
| 184 | dependencies = [ | ||
| 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]] | ||
| 196 | name = "crossterm_winapi" | ||
| 197 | version = "0.9.0" | ||
| 198 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 199 | checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" | ||
| 100 | dependencies = [ | 200 | dependencies = [ |
| 101 | "os_str_bytes", | 201 | "winapi", |
| 102 | ] | 202 | ] |
| 103 | 203 | ||
| 104 | [[package]] | 204 | [[package]] |
| 105 | name = "dotup" | 205 | name = "dotup" |
| 106 | version = "0.1.0" | 206 | version = "0.2.0" |
| 107 | dependencies = [ | 207 | dependencies = [ |
| 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]] |
| 222 | name = "dyn-clone" | ||
| 223 | version = "1.0.11" | ||
| 224 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 225 | checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" | ||
| 226 | |||
| 227 | [[package]] | ||
| 120 | name = "env_logger" | 228 | name = "env_logger" |
| 121 | version = "0.9.3" | 229 | version = "0.9.3" |
| 122 | source = "registry+https://github.com/rust-lang/crates.io-index" | 230 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -130,6 +238,27 @@ dependencies = [ | |||
| 130 | ] | 238 | ] |
| 131 | 239 | ||
| 132 | [[package]] | 240 | [[package]] |
| 241 | name = "errno" | ||
| 242 | version = "0.3.0" | ||
| 243 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 244 | checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" | ||
| 245 | dependencies = [ | ||
| 246 | "errno-dragonfly", | ||
| 247 | "libc", | ||
| 248 | "windows-sys 0.45.0", | ||
| 249 | ] | ||
| 250 | |||
| 251 | [[package]] | ||
| 252 | name = "errno-dragonfly" | ||
| 253 | version = "0.1.2" | ||
| 254 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 255 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" | ||
| 256 | dependencies = [ | ||
| 257 | "cc", | ||
| 258 | "libc", | ||
| 259 | ] | ||
| 260 | |||
| 261 | [[package]] | ||
| 133 | name = "fnv" | 262 | name = "fnv" |
| 134 | version = "1.0.7" | 263 | version = "1.0.7" |
| 135 | source = "registry+https://github.com/rust-lang/crates.io-index" | 264 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -149,12 +278,6 @@ dependencies = [ | |||
| 149 | ] | 278 | ] |
| 150 | 279 | ||
| 151 | [[package]] | 280 | [[package]] |
| 152 | name = "hashbrown" | ||
| 153 | version = "0.12.3" | ||
| 154 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 155 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" | ||
| 156 | |||
| 157 | [[package]] | ||
| 158 | name = "heck" | 281 | name = "heck" |
| 159 | version = "0.4.1" | 282 | version = "0.4.1" |
| 160 | source = "registry+https://github.com/rust-lang/crates.io-index" | 283 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -170,28 +293,85 @@ dependencies = [ | |||
| 170 | ] | 293 | ] |
| 171 | 294 | ||
| 172 | [[package]] | 295 | [[package]] |
| 296 | name = "hermit-abi" | ||
| 297 | version = "0.3.1" | ||
| 298 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 299 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" | ||
| 300 | |||
| 301 | [[package]] | ||
| 173 | name = "humantime" | 302 | name = "humantime" |
| 174 | version = "2.1.0" | 303 | version = "2.1.0" |
| 175 | source = "registry+https://github.com/rust-lang/crates.io-index" | 304 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 176 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" | 305 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" |
| 177 | 306 | ||
| 178 | [[package]] | 307 | [[package]] |
| 179 | name = "indexmap" | 308 | name = "inquire" |
| 180 | version = "1.9.3" | 309 | version = "0.6.0" |
| 181 | source = "registry+https://github.com/rust-lang/crates.io-index" | 310 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 182 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" | 311 | checksum = "fd079157ad94a32f7511b2e13037f3ae417ad80a6a9b0de29154d48b86f5d6c8" |
| 183 | dependencies = [ | 312 | dependencies = [ |
| 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]] | ||
| 324 | name = "io-lifetimes" | ||
| 325 | version = "1.0.10" | ||
| 326 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 327 | checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" | ||
| 328 | dependencies = [ | ||
| 329 | "hermit-abi 0.3.1", | ||
| 330 | "libc", | ||
| 331 | "windows-sys 0.48.0", | ||
| 332 | ] | ||
| 333 | |||
| 334 | [[package]] | ||
| 335 | name = "is-terminal" | ||
| 336 | version = "0.4.7" | ||
| 337 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 338 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" | ||
| 339 | dependencies = [ | ||
| 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]] |
| 347 | name = "lazy_static" | ||
| 348 | version = "1.4.0" | ||
| 349 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 350 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" | ||
| 351 | |||
| 352 | [[package]] | ||
| 189 | name = "libc" | 353 | name = "libc" |
| 190 | version = "0.2.141" | 354 | version = "0.2.141" |
| 191 | source = "registry+https://github.com/rust-lang/crates.io-index" | 355 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 192 | checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" | 356 | checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" |
| 193 | 357 | ||
| 194 | [[package]] | 358 | [[package]] |
| 359 | name = "linux-raw-sys" | ||
| 360 | version = "0.3.1" | ||
| 361 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 362 | checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" | ||
| 363 | |||
| 364 | [[package]] | ||
| 365 | name = "lock_api" | ||
| 366 | version = "0.4.9" | ||
| 367 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 368 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" | ||
| 369 | dependencies = [ | ||
| 370 | "autocfg", | ||
| 371 | "scopeguard", | ||
| 372 | ] | ||
| 373 | |||
| 374 | [[package]] | ||
| 195 | name = "log" | 375 | name = "log" |
| 196 | version = "0.4.17" | 376 | version = "0.4.17" |
| 197 | source = "registry+https://github.com/rust-lang/crates.io-index" | 377 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -213,6 +393,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 213 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" | 393 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" |
| 214 | 394 | ||
| 215 | [[package]] | 395 | [[package]] |
| 396 | name = "mio" | ||
| 397 | version = "0.8.6" | ||
| 398 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 399 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" | ||
| 400 | dependencies = [ | ||
| 401 | "libc", | ||
| 402 | "log", | ||
| 403 | "wasi", | ||
| 404 | "windows-sys 0.45.0", | ||
| 405 | ] | ||
| 406 | |||
| 407 | [[package]] | ||
| 408 | name = "newline-converter" | ||
| 409 | version = "0.2.2" | ||
| 410 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 411 | checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" | ||
| 412 | dependencies = [ | ||
| 413 | "unicode-segmentation", | ||
| 414 | ] | ||
| 415 | |||
| 416 | [[package]] | ||
| 216 | name = "nom" | 417 | name = "nom" |
| 217 | version = "7.1.3" | 418 | version = "7.1.3" |
| 218 | source = "registry+https://github.com/rust-lang/crates.io-index" | 419 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -240,33 +441,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 240 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" | 441 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" |
| 241 | 442 | ||
| 242 | [[package]] | 443 | [[package]] |
| 243 | name = "os_str_bytes" | 444 | name = "parking_lot" |
| 244 | version = "6.5.0" | 445 | version = "0.12.1" |
| 245 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 246 | checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" | ||
| 247 | |||
| 248 | [[package]] | ||
| 249 | name = "proc-macro-error" | ||
| 250 | version = "1.0.4" | ||
| 251 | source = "registry+https://github.com/rust-lang/crates.io-index" | 446 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 252 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" | 447 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" |
| 253 | dependencies = [ | 448 | dependencies = [ |
| 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]] |
| 262 | name = "proc-macro-error-attr" | 454 | name = "parking_lot_core" |
| 263 | version = "1.0.4" | 455 | version = "0.9.7" |
| 264 | source = "registry+https://github.com/rust-lang/crates.io-index" | 456 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 265 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" | 457 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" |
| 266 | dependencies = [ | 458 | dependencies = [ |
| 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]] |
| 485 | name = "redox_syscall" | ||
| 486 | version = "0.2.16" | ||
| 487 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 488 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" | ||
| 489 | dependencies = [ | ||
| 490 | "bitflags", | ||
| 491 | ] | ||
| 492 | |||
| 493 | [[package]] | ||
| 291 | name = "regex" | 494 | name = "regex" |
| 292 | version = "1.7.3" | 495 | version = "1.7.3" |
| 293 | source = "registry+https://github.com/rust-lang/crates.io-index" | 496 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -305,12 +508,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 305 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" | 508 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" |
| 306 | 509 | ||
| 307 | [[package]] | 510 | [[package]] |
| 511 | name = "rustix" | ||
| 512 | version = "0.37.8" | ||
| 513 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 514 | checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635" | ||
| 515 | dependencies = [ | ||
| 516 | "bitflags", | ||
| 517 | "errno", | ||
| 518 | "io-lifetimes", | ||
| 519 | "libc", | ||
| 520 | "linux-raw-sys", | ||
| 521 | "windows-sys 0.48.0", | ||
| 522 | ] | ||
| 523 | |||
| 524 | [[package]] | ||
| 525 | name = "scopeguard" | ||
| 526 | version = "1.1.0" | ||
| 527 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 528 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" | ||
| 529 | |||
| 530 | [[package]] | ||
| 308 | name = "serde" | 531 | name = "serde" |
| 309 | version = "1.0.159" | 532 | version = "1.0.159" |
| 310 | source = "registry+https://github.com/rust-lang/crates.io-index" | 533 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 311 | checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" | 534 | checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" |
| 312 | 535 | ||
| 313 | [[package]] | 536 | [[package]] |
| 537 | name = "signal-hook" | ||
| 538 | version = "0.3.15" | ||
| 539 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 540 | checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" | ||
| 541 | dependencies = [ | ||
| 542 | "libc", | ||
| 543 | "signal-hook-registry", | ||
| 544 | ] | ||
| 545 | |||
| 546 | [[package]] | ||
| 547 | name = "signal-hook-mio" | ||
| 548 | version = "0.2.3" | ||
| 549 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 550 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" | ||
| 551 | dependencies = [ | ||
| 552 | "libc", | ||
| 553 | "mio", | ||
| 554 | "signal-hook", | ||
| 555 | ] | ||
| 556 | |||
| 557 | [[package]] | ||
| 558 | name = "signal-hook-registry" | ||
| 559 | version = "1.4.1" | ||
| 560 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 561 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" | ||
| 562 | dependencies = [ | ||
| 563 | "libc", | ||
| 564 | ] | ||
| 565 | |||
| 566 | [[package]] | ||
| 314 | name = "slotmap" | 567 | name = "slotmap" |
| 315 | version = "1.0.6" | 568 | version = "1.0.6" |
| 316 | source = "registry+https://github.com/rust-lang/crates.io-index" | 569 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -320,21 +573,16 @@ dependencies = [ | |||
| 320 | ] | 573 | ] |
| 321 | 574 | ||
| 322 | [[package]] | 575 | [[package]] |
| 323 | name = "strsim" | 576 | name = "smallvec" |
| 324 | version = "0.10.0" | 577 | version = "1.10.0" |
| 325 | source = "registry+https://github.com/rust-lang/crates.io-index" | 578 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 326 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" | 579 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" |
| 327 | 580 | ||
| 328 | [[package]] | 581 | [[package]] |
| 329 | name = "syn" | 582 | name = "strsim" |
| 330 | version = "1.0.109" | 583 | version = "0.10.0" |
| 331 | source = "registry+https://github.com/rust-lang/crates.io-index" | 584 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 332 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" | 585 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" |
| 333 | dependencies = [ | ||
| 334 | "proc-macro2", | ||
| 335 | "quote", | ||
| 336 | "unicode-ident", | ||
| 337 | ] | ||
| 338 | 586 | ||
| 339 | [[package]] | 587 | [[package]] |
| 340 | name = "syn" | 588 | name = "syn" |
| @@ -357,12 +605,6 @@ dependencies = [ | |||
| 357 | ] | 605 | ] |
| 358 | 606 | ||
| 359 | [[package]] | 607 | [[package]] |
| 360 | name = "textwrap" | ||
| 361 | version = "0.16.0" | ||
| 362 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 363 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" | ||
| 364 | |||
| 365 | [[package]] | ||
| 366 | name = "thiserror" | 608 | name = "thiserror" |
| 367 | version = "1.0.40" | 609 | version = "1.0.40" |
| 368 | source = "registry+https://github.com/rust-lang/crates.io-index" | 610 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -379,7 +621,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" | |||
| 379 | dependencies = [ | 621 | dependencies = [ |
| 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" | |||
| 389 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" | 631 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" |
| 390 | 632 | ||
| 391 | [[package]] | 633 | [[package]] |
| 634 | name = "unicode-segmentation" | ||
| 635 | version = "1.10.1" | ||
| 636 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 637 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" | ||
| 638 | |||
| 639 | [[package]] | ||
| 640 | name = "unicode-width" | ||
| 641 | version = "0.1.10" | ||
| 642 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 643 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" | ||
| 644 | |||
| 645 | [[package]] | ||
| 646 | name = "utf8parse" | ||
| 647 | version = "0.2.1" | ||
| 648 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 649 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | ||
| 650 | |||
| 651 | [[package]] | ||
| 392 | name = "version_check" | 652 | name = "version_check" |
| 393 | version = "0.9.4" | 653 | version = "0.9.4" |
| 394 | source = "registry+https://github.com/rust-lang/crates.io-index" | 654 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 395 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" | 655 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" |
| 396 | 656 | ||
| 397 | [[package]] | 657 | [[package]] |
| 658 | name = "wasi" | ||
| 659 | version = "0.11.0+wasi-snapshot-preview1" | ||
| 660 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 661 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" | ||
| 662 | |||
| 663 | [[package]] | ||
| 398 | name = "winapi" | 664 | name = "winapi" |
| 399 | version = "0.3.9" | 665 | version = "0.3.9" |
| 400 | source = "registry+https://github.com/rust-lang/crates.io-index" | 666 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -424,3 +690,135 @@ name = "winapi-x86_64-pc-windows-gnu" | |||
| 424 | version = "0.4.0" | 690 | version = "0.4.0" |
| 425 | source = "registry+https://github.com/rust-lang/crates.io-index" | 691 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 426 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | 692 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" |
| 693 | |||
| 694 | [[package]] | ||
| 695 | name = "windows-sys" | ||
| 696 | version = "0.45.0" | ||
| 697 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 698 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" | ||
| 699 | dependencies = [ | ||
| 700 | "windows-targets 0.42.2", | ||
| 701 | ] | ||
| 702 | |||
| 703 | [[package]] | ||
| 704 | name = "windows-sys" | ||
| 705 | version = "0.48.0" | ||
| 706 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 707 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" | ||
| 708 | dependencies = [ | ||
| 709 | "windows-targets 0.48.0", | ||
| 710 | ] | ||
| 711 | |||
| 712 | [[package]] | ||
| 713 | name = "windows-targets" | ||
| 714 | version = "0.42.2" | ||
| 715 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 716 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" | ||
| 717 | dependencies = [ | ||
| 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]] | ||
| 728 | name = "windows-targets" | ||
| 729 | version = "0.48.0" | ||
| 730 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 731 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" | ||
| 732 | dependencies = [ | ||
| 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]] | ||
| 743 | name = "windows_aarch64_gnullvm" | ||
| 744 | version = "0.42.2" | ||
| 745 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 746 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" | ||
| 747 | |||
| 748 | [[package]] | ||
| 749 | name = "windows_aarch64_gnullvm" | ||
| 750 | version = "0.48.0" | ||
| 751 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 752 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" | ||
| 753 | |||
| 754 | [[package]] | ||
| 755 | name = "windows_aarch64_msvc" | ||
| 756 | version = "0.42.2" | ||
| 757 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 758 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" | ||
| 759 | |||
| 760 | [[package]] | ||
| 761 | name = "windows_aarch64_msvc" | ||
| 762 | version = "0.48.0" | ||
| 763 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 764 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" | ||
| 765 | |||
| 766 | [[package]] | ||
| 767 | name = "windows_i686_gnu" | ||
| 768 | version = "0.42.2" | ||
| 769 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 770 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" | ||
| 771 | |||
| 772 | [[package]] | ||
| 773 | name = "windows_i686_gnu" | ||
| 774 | version = "0.48.0" | ||
| 775 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 776 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" | ||
| 777 | |||
| 778 | [[package]] | ||
| 779 | name = "windows_i686_msvc" | ||
| 780 | version = "0.42.2" | ||
| 781 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 782 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" | ||
| 783 | |||
| 784 | [[package]] | ||
| 785 | name = "windows_i686_msvc" | ||
| 786 | version = "0.48.0" | ||
| 787 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 788 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" | ||
| 789 | |||
| 790 | [[package]] | ||
| 791 | name = "windows_x86_64_gnu" | ||
| 792 | version = "0.42.2" | ||
| 793 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 794 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" | ||
| 795 | |||
| 796 | [[package]] | ||
| 797 | name = "windows_x86_64_gnu" | ||
| 798 | version = "0.48.0" | ||
| 799 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 800 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" | ||
| 801 | |||
| 802 | [[package]] | ||
| 803 | name = "windows_x86_64_gnullvm" | ||
| 804 | version = "0.42.2" | ||
| 805 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 806 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" | ||
| 807 | |||
| 808 | [[package]] | ||
| 809 | name = "windows_x86_64_gnullvm" | ||
| 810 | version = "0.48.0" | ||
| 811 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 812 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" | ||
| 813 | |||
| 814 | [[package]] | ||
| 815 | name = "windows_x86_64_msvc" | ||
| 816 | version = "0.42.2" | ||
| 817 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 818 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" | ||
| 819 | |||
| 820 | [[package]] | ||
| 821 | name = "windows_x86_64_msvc" | ||
| 822 | version = "0.48.0" | ||
| 823 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 824 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" | ||
| @@ -1,17 +1,19 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "dotup" | 2 | name = "dotup" |
| 3 | version = "0.1.0" | 3 | version = "0.2.0" |
| 4 | edition = "2021" | 4 | edition = "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] |
| 9 | anyhow = "1" | 9 | anyhow = "1" |
| 10 | clap = { version = "3.2.21", features = ["derive"] } | 10 | clap = { version = "4", features = ["derive"] } |
| 11 | colored = "2" | ||
| 11 | env_logger = "0.9" | 12 | env_logger = "0.9" |
| 12 | globset = "0.4.9" | 13 | globset = "0.4" |
| 13 | log = "0.4.17" | 14 | inquire = "0.6" |
| 14 | nom = "7.1.1" | 15 | log = "0.4" |
| 16 | nom = "7" | ||
| 15 | nom_locate = "4" | 17 | nom_locate = "4" |
| 16 | slotmap = "1" | 18 | slotmap = "1" |
| 17 | thiserror = "1" | 19 | thiserror = "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 @@ | |||
| 1 | use std::{collections::HashSet, ffi::OsString, ops::Index, path::PathBuf}; | ||
| 2 | |||
| 3 | use slotmap::SlotMap; | ||
| 4 | |||
| 5 | use super::{AbsPath, AbsPathBuf}; | ||
| 6 | |||
| 7 | slotmap::new_key_type! { | ||
| 8 | pub struct NodeID; | ||
| 9 | pub struct ActionID; | ||
| 10 | } | ||
| 11 | |||
| 12 | #[derive(Debug)] | ||
| 13 | pub enum Action { | ||
| 14 | Link { source: PathBuf }, | ||
| 15 | Copy { source: PathBuf }, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[derive(Debug)] | ||
| 19 | pub struct TreeAction { | ||
| 20 | path: AbsPathBuf, | ||
| 21 | action: Action, | ||
| 22 | } | ||
| 23 | |||
| 24 | #[derive(Debug)] | ||
| 25 | enum TreeNodeKind { | ||
| 26 | Action(ActionID), | ||
| 27 | SubTree(HashSet<NodeID>), | ||
| 28 | } | ||
| 29 | |||
| 30 | #[derive(Debug)] | ||
| 31 | struct TreeNode { | ||
| 32 | path: AbsPathBuf, | ||
| 33 | component: OsString, | ||
| 34 | kind: TreeNodeKind, | ||
| 35 | } | ||
| 36 | |||
| 37 | #[derive(Debug)] | ||
| 38 | pub struct ActionTree { | ||
| 39 | root_id: NodeID, | ||
| 40 | nodes: SlotMap<NodeID, TreeNode>, | ||
| 41 | actions: SlotMap<ActionID, TreeAction>, | ||
| 42 | } | ||
| 43 | |||
| 44 | // -------------------- TreeAction -------------------- // | ||
| 45 | |||
| 46 | impl 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)] | ||
| 59 | impl 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 | |||
| 91 | impl 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 | |||
| 99 | impl 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)] | ||
| 279 | mod 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 @@ | |||
| 1 | mod action_tree; | ||
| 2 | mod cfg; | 1 | mod cfg; |
| 3 | mod paths; | 2 | mod paths; |
| 4 | 3 | ||
| 5 | use std::collections::HashSet; | ||
| 6 | use std::{ | 4 | use std::{ |
| 7 | collections::HashMap, | 5 | collections::{HashMap, HashSet, VecDeque}, |
| 8 | path::{Path, PathBuf}, | 6 | path::{Path, PathBuf}, |
| 9 | }; | 7 | }; |
| 10 | 8 | ||
| 9 | use colored::Colorize; | ||
| 11 | use slotmap::SlotMap; | 10 | use slotmap::SlotMap; |
| 12 | use thiserror::Error; | 11 | use thiserror::Error; |
| 13 | 12 | ||
| @@ -20,37 +19,59 @@ slotmap::new_key_type! { pub struct GroupID; } | |||
| 20 | #[derive(Debug, Error)] | 19 | #[derive(Debug, Error)] |
| 21 | pub enum Error { | 20 | pub 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)] | 29 | impl Error { |
| 31 | pub 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)] | ||
| 36 | pub struct Context { | ||
| 37 | working_directory: AbsPathBuf, | ||
| 38 | destination_directory: AbsPathBuf, | ||
| 39 | } | ||
| 40 | |||
| 41 | impl 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)] |
| 39 | pub struct Dotup { | 60 | pub struct InstallParams { |
| 40 | root_id: GroupID, | 61 | pub force: bool, |
| 41 | groups: SlotMap<GroupID, Group>, | ||
| 42 | } | 62 | } |
| 43 | 63 | ||
| 44 | #[derive(Debug, Clone, Copy)] | 64 | impl Default for InstallParams { |
| 45 | pub 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)] |
| 51 | pub struct UninstallParams<'p> { | 71 | pub 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)] | ||
| 84 | struct 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)] |
| 63 | struct IncludeAction { | 92 | struct IncludeAction { |
| 64 | group: String, | 93 | group: String, |
| 94 | group_id: GroupID, | ||
| 65 | } | 95 | } |
| 66 | 96 | ||
| 67 | #[derive(Debug, Clone)] | 97 | #[derive(Debug, Clone)] |
| 68 | struct LinkAction { | 98 | struct 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)] |
| 74 | struct CopyAction { | 105 | struct 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 | ||
| 86 | pub fn load(content: &str) -> Result<Dotup> { | 117 | #[derive(Debug, Clone)] |
| 118 | enum ExecutableAction { | ||
| 119 | Link(LinkAction), | ||
| 120 | Copy(CopyAction), | ||
| 121 | } | ||
| 122 | |||
| 123 | pub 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 | ||
| 91 | pub fn load_file(path: impl AsRef<Path>) -> Result<Dotup> { | 128 | pub 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 | ||
| 96 | pub fn format(content: &str) -> Result<String> { | 133 | pub 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 -------------------- // | 149 | pub 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 | ||
| 114 | impl 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(()) | 204 | pub 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) { | 226 | pub 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 | |||
| 270 | fn 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 | ||
| 151 | impl Dotup { | 288 | fn 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) | 331 | fn 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(), | 373 | fn 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); | 409 | fn 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 { | 414 | fn 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 | |||
| 418 | fn 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( | 441 | fn 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 | |||
| 469 | fn 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( | 477 | fn 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 | ||
| 333 | fn cfg_action_to_action(cfg_action: cfg::Action) -> Result<Action> { | 519 | fn 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`. | 530 | fn fs_metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata> { |
| 368 | fn 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 | |||
| 541 | fn 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 | |||
| 552 | fn 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 | |||
| 563 | fn 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 | |||
| 574 | fn 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 | |||
| 583 | fn 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 | |||
| 611 | fn 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 | |||
| 629 | fn 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; | ||
| 4 | pub mod dotup; | 4 | pub mod dotup; |
| 5 | 5 | ||
| 6 | use std::path::PathBuf; | 6 | use std::path::PathBuf; |
| 7 | 7 | ||
| 8 | use anyhow::Context; | 8 | use anyhow::Context; |
| 9 | use clap::{Parser, Subcommand}; | 9 | use clap::{Parser, Subcommand}; |
| 10 | use dotup::InstallParams; | ||
| 10 | 11 | ||
| 11 | #[derive(Parser, Debug)] | 12 | #[derive(Parser, Debug)] |
| 12 | struct GlobalFlags { | 13 | struct GlobalFlags { |
| @@ -27,6 +28,9 @@ enum SubCommand { | |||
| 27 | 28 | ||
| 28 | #[derive(Parser, Debug)] | 29 | #[derive(Parser, Debug)] |
| 29 | struct InstallArgs { | 30 | struct 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 {} | |||
| 47 | struct Args { | 51 | struct 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 | ||
| 66 | impl GlobalFlags { | 71 | impl 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 | ||
| 74 | fn command_install(globals: GlobalFlags, args: InstallArgs) -> anyhow::Result<()> { | 83 | fn 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, ¶ms, &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 | ||
| 90 | fn command_uninstall(globals: GlobalFlags, args: UninstallArgs) -> anyhow::Result<()> { | 93 | fn 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 | ||
| 106 | fn command_status(globals: GlobalFlags, args: StatusArgs) -> anyhow::Result<()> { | 102 | fn 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 | |||
| 116 | fn 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 | } | ||
