diff options
| -rw-r--r-- | Cargo.lock | 627 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | dotup/Cargo.toml | 14 | ||||
| -rw-r--r-- | dotup/src/archive.rs | 44 | ||||
| -rw-r--r-- | dotup/src/depot.rs | 484 | ||||
| -rw-r--r-- | dotup/src/error.rs | 21 | ||||
| -rw-r--r-- | dotup/src/lib.rs | 18 | ||||
| -rw-r--r-- | dotup/src/utils.rs | 94 | ||||
| -rw-r--r-- | dotup/tests/integration_tests.rs | 126 | ||||
| -rw-r--r-- | dotup/tests/testing_depot.toml | 7 | ||||
| -rw-r--r-- | dotup_cli/Cargo.toml | 29 | ||||
| -rw-r--r-- | dotup_cli/src/commands/init.rs | 22 | ||||
| -rw-r--r-- | dotup_cli/src/commands/install.rs | 25 | ||||
| -rw-r--r-- | dotup_cli/src/commands/link.rs | 134 | ||||
| -rw-r--r-- | dotup_cli/src/commands/mod.rs | 11 | ||||
| -rw-r--r-- | dotup_cli/src/commands/mv.rs | 53 | ||||
| -rw-r--r-- | dotup_cli/src/commands/status.rs | 175 | ||||
| -rw-r--r-- | dotup_cli/src/commands/uninstall.rs | 26 | ||||
| -rw-r--r-- | dotup_cli/src/commands/unlink.rs | 43 | ||||
| -rw-r--r-- | dotup_cli/src/config.rs | 10 | ||||
| -rw-r--r-- | dotup_cli/src/main.rs | 111 | ||||
| -rw-r--r-- | dotup_cli/src/utils.rs | 182 | ||||
| -rw-r--r-- | dotup_cli/tests/cli.rs | 145 |
23 files changed, 0 insertions, 2403 deletions
diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index dd07c28..0000000 --- a/Cargo.lock +++ /dev/null | |||
| @@ -1,627 +0,0 @@ | |||
| 1 | # This file is automatically @generated by Cargo. | ||
| 2 | # It is not intended for manual editing. | ||
| 3 | version = 3 | ||
| 4 | |||
| 5 | [[package]] | ||
| 6 | name = "aho-corasick" | ||
| 7 | version = "0.7.18" | ||
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" | ||
| 10 | dependencies = [ | ||
| 11 | "memchr", | ||
| 12 | ] | ||
| 13 | |||
| 14 | [[package]] | ||
| 15 | name = "ansi_term" | ||
| 16 | version = "0.12.1" | ||
| 17 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" | ||
| 19 | dependencies = [ | ||
| 20 | "winapi", | ||
| 21 | ] | ||
| 22 | |||
| 23 | [[package]] | ||
| 24 | name = "anyhow" | ||
| 25 | version = "1.0.51" | ||
| 26 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 27 | checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" | ||
| 28 | |||
| 29 | [[package]] | ||
| 30 | name = "assert_cmd" | ||
| 31 | version = "2.0.2" | ||
| 32 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 33 | checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" | ||
| 34 | dependencies = [ | ||
| 35 | "bstr", | ||
| 36 | "doc-comment", | ||
| 37 | "predicates", | ||
| 38 | "predicates-core", | ||
| 39 | "predicates-tree", | ||
| 40 | "wait-timeout", | ||
| 41 | ] | ||
| 42 | |||
| 43 | [[package]] | ||
| 44 | name = "atty" | ||
| 45 | version = "0.2.14" | ||
| 46 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 47 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" | ||
| 48 | dependencies = [ | ||
| 49 | "hermit-abi", | ||
| 50 | "libc", | ||
| 51 | "winapi", | ||
| 52 | ] | ||
| 53 | |||
| 54 | [[package]] | ||
| 55 | name = "autocfg" | ||
| 56 | version = "1.0.1" | ||
| 57 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 58 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" | ||
| 59 | |||
| 60 | [[package]] | ||
| 61 | name = "bitflags" | ||
| 62 | version = "1.2.1" | ||
| 63 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 64 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" | ||
| 65 | |||
| 66 | [[package]] | ||
| 67 | name = "bstr" | ||
| 68 | version = "0.2.16" | ||
| 69 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 70 | checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" | ||
| 71 | dependencies = [ | ||
| 72 | "lazy_static", | ||
| 73 | "memchr", | ||
| 74 | "regex-automata", | ||
| 75 | ] | ||
| 76 | |||
| 77 | [[package]] | ||
| 78 | name = "cfg-if" | ||
| 79 | version = "1.0.0" | ||
| 80 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 81 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||
| 82 | |||
| 83 | [[package]] | ||
| 84 | name = "clap" | ||
| 85 | version = "3.0.0-rc.7" | ||
| 86 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 87 | checksum = "9468f8012246b0836c6fd11725102b0844254985f2462b6c637d50040ef49df0" | ||
| 88 | dependencies = [ | ||
| 89 | "atty", | ||
| 90 | "bitflags", | ||
| 91 | "clap_derive", | ||
| 92 | "indexmap", | ||
| 93 | "lazy_static", | ||
| 94 | "os_str_bytes", | ||
| 95 | "strsim", | ||
| 96 | "termcolor", | ||
| 97 | "textwrap", | ||
| 98 | ] | ||
| 99 | |||
| 100 | [[package]] | ||
| 101 | name = "clap_derive" | ||
| 102 | version = "3.0.0-rc.7" | ||
| 103 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 104 | checksum = "b72e1af32a4de4d21a43d26de33fe69c00e895371bd8b1523d674f011b610467" | ||
| 105 | dependencies = [ | ||
| 106 | "heck", | ||
| 107 | "proc-macro-error", | ||
| 108 | "proc-macro2", | ||
| 109 | "quote", | ||
| 110 | "syn", | ||
| 111 | ] | ||
| 112 | |||
| 113 | [[package]] | ||
| 114 | name = "difflib" | ||
| 115 | version = "0.4.0" | ||
| 116 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 117 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" | ||
| 118 | |||
| 119 | [[package]] | ||
| 120 | name = "doc-comment" | ||
| 121 | version = "0.3.3" | ||
| 122 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 123 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" | ||
| 124 | |||
| 125 | [[package]] | ||
| 126 | name = "dotup" | ||
| 127 | version = "0.1.0" | ||
| 128 | dependencies = [ | ||
| 129 | "log", | ||
| 130 | "serde", | ||
| 131 | "slotmap", | ||
| 132 | "tempfile", | ||
| 133 | "thiserror", | ||
| 134 | "toml", | ||
| 135 | ] | ||
| 136 | |||
| 137 | [[package]] | ||
| 138 | name = "dotup_cli" | ||
| 139 | version = "0.1.0" | ||
| 140 | dependencies = [ | ||
| 141 | "ansi_term", | ||
| 142 | "anyhow", | ||
| 143 | "assert_cmd", | ||
| 144 | "clap", | ||
| 145 | "dotup", | ||
| 146 | "flexi_logger", | ||
| 147 | "log", | ||
| 148 | "tempfile", | ||
| 149 | ] | ||
| 150 | |||
| 151 | [[package]] | ||
| 152 | name = "either" | ||
| 153 | version = "1.6.1" | ||
| 154 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 155 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" | ||
| 156 | |||
| 157 | [[package]] | ||
| 158 | name = "flexi_logger" | ||
| 159 | version = "0.22.0" | ||
| 160 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 161 | checksum = "11be38a063886b7be57de89636d65c07d318c1f9bd985cd8ab2c343786a910bc" | ||
| 162 | dependencies = [ | ||
| 163 | "ansi_term", | ||
| 164 | "atty", | ||
| 165 | "glob", | ||
| 166 | "lazy_static", | ||
| 167 | "log", | ||
| 168 | "regex", | ||
| 169 | "rustversion", | ||
| 170 | "thiserror", | ||
| 171 | "time", | ||
| 172 | ] | ||
| 173 | |||
| 174 | [[package]] | ||
| 175 | name = "getrandom" | ||
| 176 | version = "0.2.3" | ||
| 177 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 178 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" | ||
| 179 | dependencies = [ | ||
| 180 | "cfg-if", | ||
| 181 | "libc", | ||
| 182 | "wasi", | ||
| 183 | ] | ||
| 184 | |||
| 185 | [[package]] | ||
| 186 | name = "glob" | ||
| 187 | version = "0.3.0" | ||
| 188 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 189 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" | ||
| 190 | |||
| 191 | [[package]] | ||
| 192 | name = "hashbrown" | ||
| 193 | version = "0.11.2" | ||
| 194 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 195 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" | ||
| 196 | |||
| 197 | [[package]] | ||
| 198 | name = "heck" | ||
| 199 | version = "0.3.3" | ||
| 200 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 201 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" | ||
| 202 | dependencies = [ | ||
| 203 | "unicode-segmentation", | ||
| 204 | ] | ||
| 205 | |||
| 206 | [[package]] | ||
| 207 | name = "hermit-abi" | ||
| 208 | version = "0.1.19" | ||
| 209 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 210 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" | ||
| 211 | dependencies = [ | ||
| 212 | "libc", | ||
| 213 | ] | ||
| 214 | |||
| 215 | [[package]] | ||
| 216 | name = "indexmap" | ||
| 217 | version = "1.7.0" | ||
| 218 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 219 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" | ||
| 220 | dependencies = [ | ||
| 221 | "autocfg", | ||
| 222 | "hashbrown", | ||
| 223 | ] | ||
| 224 | |||
| 225 | [[package]] | ||
| 226 | name = "itertools" | ||
| 227 | version = "0.10.1" | ||
| 228 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 229 | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" | ||
| 230 | dependencies = [ | ||
| 231 | "either", | ||
| 232 | ] | ||
| 233 | |||
| 234 | [[package]] | ||
| 235 | name = "itoa" | ||
| 236 | version = "0.4.8" | ||
| 237 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 238 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" | ||
| 239 | |||
| 240 | [[package]] | ||
| 241 | name = "lazy_static" | ||
| 242 | version = "1.4.0" | ||
| 243 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 244 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" | ||
| 245 | |||
| 246 | [[package]] | ||
| 247 | name = "libc" | ||
| 248 | version = "0.2.106" | ||
| 249 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 250 | checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" | ||
| 251 | |||
| 252 | [[package]] | ||
| 253 | name = "log" | ||
| 254 | version = "0.4.14" | ||
| 255 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 256 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" | ||
| 257 | dependencies = [ | ||
| 258 | "cfg-if", | ||
| 259 | ] | ||
| 260 | |||
| 261 | [[package]] | ||
| 262 | name = "memchr" | ||
| 263 | version = "2.4.0" | ||
| 264 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 265 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" | ||
| 266 | |||
| 267 | [[package]] | ||
| 268 | name = "os_str_bytes" | ||
| 269 | version = "6.0.0" | ||
| 270 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 271 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" | ||
| 272 | dependencies = [ | ||
| 273 | "memchr", | ||
| 274 | ] | ||
| 275 | |||
| 276 | [[package]] | ||
| 277 | name = "ppv-lite86" | ||
| 278 | version = "0.2.10" | ||
| 279 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 280 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" | ||
| 281 | |||
| 282 | [[package]] | ||
| 283 | name = "predicates" | ||
| 284 | version = "2.0.0" | ||
| 285 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 286 | checksum = "c6e46ca79eb4e21e2ec14430340c71250ab69332abf85521c95d3a8bc336aa76" | ||
| 287 | dependencies = [ | ||
| 288 | "difflib", | ||
| 289 | "itertools", | ||
| 290 | "predicates-core", | ||
| 291 | ] | ||
| 292 | |||
| 293 | [[package]] | ||
| 294 | name = "predicates-core" | ||
| 295 | version = "1.0.2" | ||
| 296 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 297 | checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" | ||
| 298 | |||
| 299 | [[package]] | ||
| 300 | name = "predicates-tree" | ||
| 301 | version = "1.0.2" | ||
| 302 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 303 | checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" | ||
| 304 | dependencies = [ | ||
| 305 | "predicates-core", | ||
| 306 | "treeline", | ||
| 307 | ] | ||
| 308 | |||
| 309 | [[package]] | ||
| 310 | name = "proc-macro-error" | ||
| 311 | version = "1.0.4" | ||
| 312 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 313 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" | ||
| 314 | dependencies = [ | ||
| 315 | "proc-macro-error-attr", | ||
| 316 | "proc-macro2", | ||
| 317 | "quote", | ||
| 318 | "syn", | ||
| 319 | "version_check", | ||
| 320 | ] | ||
| 321 | |||
| 322 | [[package]] | ||
| 323 | name = "proc-macro-error-attr" | ||
| 324 | version = "1.0.4" | ||
| 325 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 326 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" | ||
| 327 | dependencies = [ | ||
| 328 | "proc-macro2", | ||
| 329 | "quote", | ||
| 330 | "version_check", | ||
| 331 | ] | ||
| 332 | |||
| 333 | [[package]] | ||
| 334 | name = "proc-macro2" | ||
| 335 | version = "1.0.32" | ||
| 336 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 337 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" | ||
| 338 | dependencies = [ | ||
| 339 | "unicode-xid", | ||
| 340 | ] | ||
| 341 | |||
| 342 | [[package]] | ||
| 343 | name = "quote" | ||
| 344 | version = "1.0.9" | ||
| 345 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 346 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" | ||
| 347 | dependencies = [ | ||
| 348 | "proc-macro2", | ||
| 349 | ] | ||
| 350 | |||
| 351 | [[package]] | ||
| 352 | name = "rand" | ||
| 353 | version = "0.8.4" | ||
| 354 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 355 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" | ||
| 356 | dependencies = [ | ||
| 357 | "libc", | ||
| 358 | "rand_chacha", | ||
| 359 | "rand_core", | ||
| 360 | "rand_hc", | ||
| 361 | ] | ||
| 362 | |||
| 363 | [[package]] | ||
| 364 | name = "rand_chacha" | ||
| 365 | version = "0.3.1" | ||
| 366 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 367 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" | ||
| 368 | dependencies = [ | ||
| 369 | "ppv-lite86", | ||
| 370 | "rand_core", | ||
| 371 | ] | ||
| 372 | |||
| 373 | [[package]] | ||
| 374 | name = "rand_core" | ||
| 375 | version = "0.6.3" | ||
| 376 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 377 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" | ||
| 378 | dependencies = [ | ||
| 379 | "getrandom", | ||
| 380 | ] | ||
| 381 | |||
| 382 | [[package]] | ||
| 383 | name = "rand_hc" | ||
| 384 | version = "0.3.1" | ||
| 385 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 386 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" | ||
| 387 | dependencies = [ | ||
| 388 | "rand_core", | ||
| 389 | ] | ||
| 390 | |||
| 391 | [[package]] | ||
| 392 | name = "redox_syscall" | ||
| 393 | version = "0.2.9" | ||
| 394 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 395 | checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" | ||
| 396 | dependencies = [ | ||
| 397 | "bitflags", | ||
| 398 | ] | ||
| 399 | |||
| 400 | [[package]] | ||
| 401 | name = "regex" | ||
| 402 | version = "1.5.4" | ||
| 403 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 404 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" | ||
| 405 | dependencies = [ | ||
| 406 | "aho-corasick", | ||
| 407 | "memchr", | ||
| 408 | "regex-syntax", | ||
| 409 | ] | ||
| 410 | |||
| 411 | [[package]] | ||
| 412 | name = "regex-automata" | ||
| 413 | version = "0.1.10" | ||
| 414 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 415 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" | ||
| 416 | |||
| 417 | [[package]] | ||
| 418 | name = "regex-syntax" | ||
| 419 | version = "0.6.25" | ||
| 420 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 421 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" | ||
| 422 | |||
| 423 | [[package]] | ||
| 424 | name = "remove_dir_all" | ||
| 425 | version = "0.5.3" | ||
| 426 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 427 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" | ||
| 428 | dependencies = [ | ||
| 429 | "winapi", | ||
| 430 | ] | ||
| 431 | |||
| 432 | [[package]] | ||
| 433 | name = "rustversion" | ||
| 434 | version = "1.0.5" | ||
| 435 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 436 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" | ||
| 437 | |||
| 438 | [[package]] | ||
| 439 | name = "serde" | ||
| 440 | version = "1.0.132" | ||
| 441 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 442 | checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" | ||
| 443 | dependencies = [ | ||
| 444 | "serde_derive", | ||
| 445 | ] | ||
| 446 | |||
| 447 | [[package]] | ||
| 448 | name = "serde_derive" | ||
| 449 | version = "1.0.132" | ||
| 450 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 451 | checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" | ||
| 452 | dependencies = [ | ||
| 453 | "proc-macro2", | ||
| 454 | "quote", | ||
| 455 | "syn", | ||
| 456 | ] | ||
| 457 | |||
| 458 | [[package]] | ||
| 459 | name = "slotmap" | ||
| 460 | version = "1.0.6" | ||
| 461 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 462 | checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" | ||
| 463 | dependencies = [ | ||
| 464 | "version_check", | ||
| 465 | ] | ||
| 466 | |||
| 467 | [[package]] | ||
| 468 | name = "strsim" | ||
| 469 | version = "0.10.0" | ||
| 470 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 471 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" | ||
| 472 | |||
| 473 | [[package]] | ||
| 474 | name = "syn" | ||
| 475 | version = "1.0.81" | ||
| 476 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 477 | checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" | ||
| 478 | dependencies = [ | ||
| 479 | "proc-macro2", | ||
| 480 | "quote", | ||
| 481 | "unicode-xid", | ||
| 482 | ] | ||
| 483 | |||
| 484 | [[package]] | ||
| 485 | name = "tempfile" | ||
| 486 | version = "3.2.0" | ||
| 487 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 488 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" | ||
| 489 | dependencies = [ | ||
| 490 | "cfg-if", | ||
| 491 | "libc", | ||
| 492 | "rand", | ||
| 493 | "redox_syscall", | ||
| 494 | "remove_dir_all", | ||
| 495 | "winapi", | ||
| 496 | ] | ||
| 497 | |||
| 498 | [[package]] | ||
| 499 | name = "termcolor" | ||
| 500 | version = "1.1.2" | ||
| 501 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 502 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" | ||
| 503 | dependencies = [ | ||
| 504 | "winapi-util", | ||
| 505 | ] | ||
| 506 | |||
| 507 | [[package]] | ||
| 508 | name = "textwrap" | ||
| 509 | version = "0.14.2" | ||
| 510 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 511 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" | ||
| 512 | |||
| 513 | [[package]] | ||
| 514 | name = "thiserror" | ||
| 515 | version = "1.0.30" | ||
| 516 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 517 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" | ||
| 518 | dependencies = [ | ||
| 519 | "thiserror-impl", | ||
| 520 | ] | ||
| 521 | |||
| 522 | [[package]] | ||
| 523 | name = "thiserror-impl" | ||
| 524 | version = "1.0.30" | ||
| 525 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 526 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" | ||
| 527 | dependencies = [ | ||
| 528 | "proc-macro2", | ||
| 529 | "quote", | ||
| 530 | "syn", | ||
| 531 | ] | ||
| 532 | |||
| 533 | [[package]] | ||
| 534 | name = "time" | ||
| 535 | version = "0.3.5" | ||
| 536 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 537 | checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" | ||
| 538 | dependencies = [ | ||
| 539 | "itoa", | ||
| 540 | "libc", | ||
| 541 | "time-macros", | ||
| 542 | ] | ||
| 543 | |||
| 544 | [[package]] | ||
| 545 | name = "time-macros" | ||
| 546 | version = "0.2.3" | ||
| 547 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 548 | checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" | ||
| 549 | |||
| 550 | [[package]] | ||
| 551 | name = "toml" | ||
| 552 | version = "0.5.8" | ||
| 553 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 554 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" | ||
| 555 | dependencies = [ | ||
| 556 | "serde", | ||
| 557 | ] | ||
| 558 | |||
| 559 | [[package]] | ||
| 560 | name = "treeline" | ||
| 561 | version = "0.1.0" | ||
| 562 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 563 | checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" | ||
| 564 | |||
| 565 | [[package]] | ||
| 566 | name = "unicode-segmentation" | ||
| 567 | version = "1.8.0" | ||
| 568 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 569 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" | ||
| 570 | |||
| 571 | [[package]] | ||
| 572 | name = "unicode-xid" | ||
| 573 | version = "0.2.2" | ||
| 574 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 575 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" | ||
| 576 | |||
| 577 | [[package]] | ||
| 578 | name = "version_check" | ||
| 579 | version = "0.9.3" | ||
| 580 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 581 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" | ||
| 582 | |||
| 583 | [[package]] | ||
| 584 | name = "wait-timeout" | ||
| 585 | version = "0.2.0" | ||
| 586 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 587 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" | ||
| 588 | dependencies = [ | ||
| 589 | "libc", | ||
| 590 | ] | ||
| 591 | |||
| 592 | [[package]] | ||
| 593 | name = "wasi" | ||
| 594 | version = "0.10.0+wasi-snapshot-preview1" | ||
| 595 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 596 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" | ||
| 597 | |||
| 598 | [[package]] | ||
| 599 | name = "winapi" | ||
| 600 | version = "0.3.9" | ||
| 601 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 602 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" | ||
| 603 | dependencies = [ | ||
| 604 | "winapi-i686-pc-windows-gnu", | ||
| 605 | "winapi-x86_64-pc-windows-gnu", | ||
| 606 | ] | ||
| 607 | |||
| 608 | [[package]] | ||
| 609 | name = "winapi-i686-pc-windows-gnu" | ||
| 610 | version = "0.4.0" | ||
| 611 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 612 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | ||
| 613 | |||
| 614 | [[package]] | ||
| 615 | name = "winapi-util" | ||
| 616 | version = "0.1.5" | ||
| 617 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 618 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" | ||
| 619 | dependencies = [ | ||
| 620 | "winapi", | ||
| 621 | ] | ||
| 622 | |||
| 623 | [[package]] | ||
| 624 | name = "winapi-x86_64-pc-windows-gnu" | ||
| 625 | version = "0.4.0" | ||
| 626 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 627 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||
diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 76e6a10..0000000 --- a/Cargo.toml +++ /dev/null | |||
| @@ -1,2 +0,0 @@ | |||
| 1 | [workspace] | ||
| 2 | members = ["dotup", "dotup_cli"] | ||
diff --git a/dotup/Cargo.toml b/dotup/Cargo.toml deleted file mode 100644 index fe4876c..0000000 --- a/dotup/Cargo.toml +++ /dev/null | |||
| @@ -1,14 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2018" | ||
| 3 | name = "dotup" | ||
| 4 | version = "0.1.0" | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | log = "0.4" | ||
| 8 | serde = { version = "*", features = ["derive"] } | ||
| 9 | thiserror = "1.0" | ||
| 10 | toml = "0.5" | ||
| 11 | slotmap = "1.0" | ||
| 12 | |||
| 13 | [dev-dependencies] | ||
| 14 | tempfile = "3.2" | ||
diff --git a/dotup/src/archive.rs b/dotup/src/archive.rs deleted file mode 100644 index 7328b5b..0000000 --- a/dotup/src/archive.rs +++ /dev/null | |||
| @@ -1,44 +0,0 @@ | |||
| 1 | use serde::{Deserialize, Serialize}; | ||
| 2 | use std::path::{Path, PathBuf}; | ||
| 3 | |||
| 4 | use crate::internal_prelude::*; | ||
| 5 | |||
| 6 | #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
| 7 | pub struct ArchiveLink { | ||
| 8 | pub origin: PathBuf, | ||
| 9 | pub destination: PathBuf, | ||
| 10 | } | ||
| 11 | |||
| 12 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] | ||
| 13 | pub struct Archive { | ||
| 14 | pub links: Vec<ArchiveLink>, | ||
| 15 | } | ||
| 16 | |||
| 17 | pub fn archive_exists(path: impl AsRef<Path>) -> bool { | ||
| 18 | utils::is_file(path).unwrap_or_default() | ||
| 19 | } | ||
| 20 | |||
| 21 | pub fn archive_read(path: impl AsRef<Path>) -> Result<Archive> { | ||
| 22 | let contents = std::fs::read_to_string(path)?; | ||
| 23 | archive_deserialize(contents) | ||
| 24 | } | ||
| 25 | |||
| 26 | pub fn archive_write(path: impl AsRef<Path>, archive: &Archive) -> Result<()> { | ||
| 27 | let serialized = archive_serialize(archive)?; | ||
| 28 | std::fs::write(path, &serialized)?; | ||
| 29 | Ok(()) | ||
| 30 | } | ||
| 31 | |||
| 32 | pub fn archive_serialize(archive: &Archive) -> Result<String> { | ||
| 33 | match toml::to_string_pretty(archive) { | ||
| 34 | Ok(serialized) => Ok(serialized), | ||
| 35 | Err(e) => Err(Error::SerializationError(Box::new(e))), | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn archive_deserialize(contents: impl AsRef<str>) -> Result<Archive> { | ||
| 40 | match toml::from_str(contents.as_ref()) { | ||
| 41 | Ok(archive) => Ok(archive), | ||
| 42 | Err(e) => Err(Error::SerializationError(Box::new(e))), | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/dotup/src/depot.rs b/dotup/src/depot.rs deleted file mode 100644 index 658c0f6..0000000 --- a/dotup/src/depot.rs +++ /dev/null | |||
| @@ -1,484 +0,0 @@ | |||
| 1 | use slotmap::SlotMap; | ||
| 2 | use std::{ | ||
| 3 | collections::HashMap, | ||
| 4 | fs::Metadata, | ||
| 5 | path::{Path, PathBuf}, | ||
| 6 | }; | ||
| 7 | use thiserror::Error; | ||
| 8 | |||
| 9 | use crate::{internal_prelude::*, Archive, ArchiveLink}; | ||
| 10 | |||
| 11 | #[derive(Debug, Error)] | ||
| 12 | pub enum LinkCreateError { | ||
| 13 | #[error("Link origin is outside depot base\nDepot : {}\nLink : {}", .depot_base.display(), .origin.display())] | ||
| 14 | LinkOriginOutsideDepot { | ||
| 15 | depot_base: PathBuf, | ||
| 16 | origin: PathBuf, | ||
| 17 | }, | ||
| 18 | #[error("Link path is not relative : {}", .0.display())] | ||
| 19 | LinkPathIsNotRelative(PathBuf), | ||
| 20 | #[error("Link origin doesnt exist : {}", .0.display())] | ||
| 21 | LinkOriginDoesntExist(PathBuf), | ||
| 22 | #[error("Cannot create link for directory {} beacause it has a linked child", .0.display())] | ||
| 23 | DirectoryHasLinkedChildren(PathBuf), | ||
| 24 | #[error("Cannot create link for file {} beacause it has a linked parent", .0.display())] | ||
| 25 | FileHasLinkedParent(PathBuf), | ||
| 26 | #[error(transparent)] | ||
| 27 | IOError(#[from] std::io::Error), | ||
| 28 | } | ||
| 29 | |||
| 30 | #[derive(Debug, Error)] | ||
| 31 | pub enum LinkInstallError { | ||
| 32 | #[error(transparent)] | ||
| 33 | IOError(#[from] std::io::Error), | ||
| 34 | #[error("File already exists at {}", .0.display())] | ||
| 35 | FileExists(PathBuf, Metadata), | ||
| 36 | /// .0 = LinkPath , .1 = LinkDestination | ||
| 37 | #[error("Link already exists {} -> {}", .0.display(), .1.display())] | ||
| 38 | LinkExists(PathBuf, PathBuf), | ||
| 39 | } | ||
| 40 | |||
| 41 | #[derive(Debug)] | ||
| 42 | pub struct DepotConfig { | ||
| 43 | /// The archive used to initialize the depot. | ||
| 44 | /// A default archive can be create if one didnt already exist. | ||
| 45 | pub archive: Archive, | ||
| 46 | /// Path to the archive file. This path must be valid and must exist. | ||
| 47 | pub archive_path: PathBuf, | ||
| 48 | } | ||
| 49 | |||
| 50 | slotmap::new_key_type! { pub struct LinkID; } | ||
| 51 | |||
| 52 | #[derive(Debug)] | ||
| 53 | pub struct LinkCreateParams { | ||
| 54 | pub origin: PathBuf, | ||
| 55 | /// This must be a relative path | ||
| 56 | pub destination: PathBuf, | ||
| 57 | } | ||
| 58 | |||
| 59 | impl LinkCreateParams { | ||
| 60 | pub fn new(origin: impl Into<PathBuf>, destination: impl Into<PathBuf>) -> Self { | ||
| 61 | Self { | ||
| 62 | origin: origin.into(), | ||
| 63 | destination: destination.into(), | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | impl std::fmt::Display for LinkCreateParams { | ||
| 69 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 70 | write!( | ||
| 71 | f, | ||
| 72 | "{} -> {}", | ||
| 73 | self.origin.display(), | ||
| 74 | self.destination.display() | ||
| 75 | ) | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | impl From<ArchiveLink> for LinkCreateParams { | ||
| 80 | fn from(archive_link: ArchiveLink) -> Self { | ||
| 81 | Self { | ||
| 82 | origin: archive_link.origin, | ||
| 83 | destination: archive_link.destination, | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 89 | enum LinkType { | ||
| 90 | File, | ||
| 91 | Directory, | ||
| 92 | } | ||
| 93 | |||
| 94 | #[derive(Debug)] | ||
| 95 | pub struct Link { | ||
| 96 | id: LinkID, | ||
| 97 | ty: LinkType, | ||
| 98 | /// The origin path, when joined with the depot base path, must be valid and it point to a file that exists. | ||
| 99 | origin: PathBuf, | ||
| 100 | /// Canonical version of origin | ||
| 101 | origin_canonical: PathBuf, | ||
| 102 | /// The destination path has to be a relative path. | ||
| 103 | /// To install a link the destination path is joined with the | ||
| 104 | /// install path and the file at base path + origin path is linked | ||
| 105 | /// to this resulting destination path. | ||
| 106 | destination: PathBuf, | ||
| 107 | } | ||
| 108 | |||
| 109 | impl Link { | ||
| 110 | pub fn id(&self) -> LinkID { | ||
| 111 | self.id | ||
| 112 | } | ||
| 113 | |||
| 114 | fn link_type(&self) -> LinkType { | ||
| 115 | self.ty | ||
| 116 | } | ||
| 117 | |||
| 118 | /// The relative path to the origin file. Relative from depot folder. | ||
| 119 | pub fn origin(&self) -> &Path { | ||
| 120 | &self.origin | ||
| 121 | } | ||
| 122 | |||
| 123 | pub fn origin_canonical(&self) -> &Path { | ||
| 124 | &self.origin_canonical | ||
| 125 | } | ||
| 126 | |||
| 127 | /// The relative path to the install destination. | ||
| 128 | /// This path should be concatenated with an install destination to get the actual destination | ||
| 129 | /// for this link. | ||
| 130 | pub fn destination(&self) -> &Path { | ||
| 131 | &self.destination | ||
| 132 | } | ||
| 133 | |||
| 134 | /// Where this link would be installed with the given `install_base`. | ||
| 135 | pub fn install_destination(&self, install_base: &Path) -> PathBuf { | ||
| 136 | utils::weakly_canonical(install_base.join(self.destination())) | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | impl std::fmt::Display for Link { | ||
| 141 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 142 | write!( | ||
| 143 | f, | ||
| 144 | "{} -> {}", | ||
| 145 | self.origin().display(), | ||
| 146 | self.destination().display() | ||
| 147 | ) | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | #[derive(Debug)] | ||
| 152 | pub struct Depot { | ||
| 153 | // Must be canonical path | ||
| 154 | base_path: PathBuf, | ||
| 155 | // Must be canonical path | ||
| 156 | archive_path: PathBuf, | ||
| 157 | // Maps the origin to the link | ||
| 158 | links: SlotMap<LinkID, Link>, | ||
| 159 | links_by_origin: HashMap<PathBuf, LinkID>, | ||
| 160 | } | ||
| 161 | |||
| 162 | impl Depot { | ||
| 163 | /// Creates a new [`Depot`] using the config. | ||
| 164 | /// Fails if any of the links in the provided archive fail to be created. | ||
| 165 | pub fn new(config: DepotConfig) -> Result<Self> { | ||
| 166 | depot_create(config) | ||
| 167 | } | ||
| 168 | |||
| 169 | /// Creates a new link from the description. | ||
| 170 | /// The origin path must exist. | ||
| 171 | /// If a link with the same origin already existed then it is replaced. | ||
| 172 | pub fn create_link(&mut self, link_desc: LinkCreateParams) -> Result<LinkID, LinkCreateError> { | ||
| 173 | let link = depot_create_link(self, link_desc)?; | ||
| 174 | let link_id = depot_insert_link(self, link); | ||
| 175 | Ok(link_id) | ||
| 176 | } | ||
| 177 | |||
| 178 | pub fn get_link(&self, link_id: LinkID) -> Option<&Link> { | ||
| 179 | depot_get_link(self, link_id) | ||
| 180 | } | ||
| 181 | |||
| 182 | pub fn get_link_by_path(&self, path: &Path) -> Option<&Link> { | ||
| 183 | self.get_link_id_by_path(path) | ||
| 184 | .map(|id| self.get_link(id).unwrap()) | ||
| 185 | } | ||
| 186 | |||
| 187 | pub fn get_link_id_by_path(&self, path: &Path) -> Option<LinkID> { | ||
| 188 | let weak = utils::weakly_canonical(path); | ||
| 189 | for link in self.links() { | ||
| 190 | if link.origin_canonical() == weak { | ||
| 191 | return Some(link.id()); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | None | ||
| 195 | } | ||
| 196 | |||
| 197 | pub fn rename_link(&mut self, link_id: LinkID, origin: &Path) { | ||
| 198 | // TODO: improve | ||
| 199 | if let Some(id) = self.get_link_id_by_path(origin) { | ||
| 200 | if link_id != id { | ||
| 201 | self.remove_link(id); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | if let Some(link) = self.links.get_mut(link_id) { | ||
| 205 | let origin_canonical = utils::weakly_canonical(origin); | ||
| 206 | if !origin_canonical.starts_with(&self.base_path) { | ||
| 207 | panic!("new origin outside depot"); | ||
| 208 | } | ||
| 209 | |||
| 210 | link.origin = origin_canonical; | ||
| 211 | link.origin_canonical = utils::weakly_canonical(&link.origin); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | /// checks if there are any linked files/directories under the path `path` | ||
| 216 | /// if `path` is a path to a file and that file is linked then this function returns true | ||
| 217 | pub fn subpath_has_links(&self, path: &Path) -> bool { | ||
| 218 | let canonical = utils::weakly_canonical(path); | ||
| 219 | for link in self.links() { | ||
| 220 | if link.origin_canonical().starts_with(&canonical) { | ||
| 221 | return true; | ||
| 222 | } | ||
| 223 | } | ||
| 224 | false | ||
| 225 | } | ||
| 226 | |||
| 227 | pub fn remove_link(&mut self, link_id: LinkID) { | ||
| 228 | depot_remove_link(self, link_id) | ||
| 229 | } | ||
| 230 | |||
| 231 | pub fn remove_link_by_path(&mut self, path: &Path) { | ||
| 232 | if let Some(id) = self.get_link_id_by_path(path) { | ||
| 233 | self.remove_link(id); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | /// Archives this depot so it can be serialized | ||
| 238 | pub fn archive(&self) -> Archive { | ||
| 239 | depot_archive(self) | ||
| 240 | } | ||
| 241 | |||
| 242 | pub fn links(&self) -> impl Iterator<Item = &Link> { | ||
| 243 | depot_links(self) | ||
| 244 | } | ||
| 245 | |||
| 246 | pub fn install_link( | ||
| 247 | &self, | ||
| 248 | link: &Link, | ||
| 249 | install_base: impl AsRef<Path>, | ||
| 250 | ) -> Result<(), LinkInstallError> { | ||
| 251 | depot_install_link(self, link, install_base.as_ref()) | ||
| 252 | } | ||
| 253 | |||
| 254 | pub fn is_link_installed(&self, link: &Link, install_base: impl AsRef<Path>) -> bool { | ||
| 255 | depot_is_link_installed(link, install_base.as_ref()) | ||
| 256 | } | ||
| 257 | |||
| 258 | pub fn uninstall_link(&self, link: &Link, install_base: impl AsRef<Path>) -> Result<()> { | ||
| 259 | depot_uninstall_link(self, link, install_base.as_ref()) | ||
| 260 | } | ||
| 261 | |||
| 262 | pub fn base_path(&self) -> &Path { | ||
| 263 | &self.base_path | ||
| 264 | } | ||
| 265 | |||
| 266 | pub fn archive_path(&self) -> &Path { | ||
| 267 | &self.archive_path | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | fn depot_create(config: DepotConfig) -> Result<Depot> { | ||
| 272 | let archive_path = match config.archive_path.canonicalize() { | ||
| 273 | Ok(canonicalized) => canonicalized, | ||
| 274 | Err(e) => return Err(Error::ArchiveMissing(config.archive_path, e)), | ||
| 275 | }; | ||
| 276 | if !archive_path.is_file() { | ||
| 277 | return Err(Error::ArchivePathNotFile(archive_path)); | ||
| 278 | } | ||
| 279 | let base_path = archive_path | ||
| 280 | .parent() | ||
| 281 | .expect("Failed to get parent of archive path") | ||
| 282 | .to_path_buf(); | ||
| 283 | |||
| 284 | let mut depot = Depot { | ||
| 285 | base_path, | ||
| 286 | archive_path, | ||
| 287 | links: Default::default(), | ||
| 288 | links_by_origin: Default::default(), | ||
| 289 | }; | ||
| 290 | |||
| 291 | for archive_link in config.archive.links { | ||
| 292 | let link_desc = LinkCreateParams::from(archive_link); | ||
| 293 | let link = depot_create_link(&depot, link_desc)?; | ||
| 294 | depot_insert_link(&mut depot, link); | ||
| 295 | } | ||
| 296 | |||
| 297 | Ok(depot) | ||
| 298 | } | ||
| 299 | |||
| 300 | fn depot_archive(depot: &Depot) -> Archive { | ||
| 301 | let mut links = Vec::new(); | ||
| 302 | |||
| 303 | for link in depot_links(depot) { | ||
| 304 | let archive_link = link_to_archive_link(link); | ||
| 305 | links.push(archive_link); | ||
| 306 | } | ||
| 307 | |||
| 308 | Archive { links } | ||
| 309 | } | ||
| 310 | |||
| 311 | /// Create a valid link for that given Depot using the given link desc. | ||
| 312 | /// The link id is corrected when the link is inserted in the depot. | ||
| 313 | fn depot_create_link(depot: &Depot, link_desc: LinkCreateParams) -> Result<Link, LinkCreateError> { | ||
| 314 | // link_ensure_relative_path(&link_desc.origin)?; | ||
| 315 | link_ensure_relative_path(&link_desc.destination)?; | ||
| 316 | debug_assert!(utils::is_canonical(depot.base_path())); | ||
| 317 | |||
| 318 | // Check if the file/directory at origin actually exists | ||
| 319 | let origin_joined = depot.base_path().join(&link_desc.origin); | ||
| 320 | let origin_result = origin_joined.canonicalize(); | ||
| 321 | let origin_canonical = match origin_result { | ||
| 322 | Ok(canonical) => canonical, | ||
| 323 | Err(e) => match e.kind() { | ||
| 324 | std::io::ErrorKind::NotFound => { | ||
| 325 | return Err(LinkCreateError::LinkOriginDoesntExist(origin_joined)) | ||
| 326 | } | ||
| 327 | _ => return Err(e.into()), | ||
| 328 | }, | ||
| 329 | }; | ||
| 330 | |||
| 331 | if !origin_canonical.starts_with(depot.base_path()) { | ||
| 332 | return Err(LinkCreateError::LinkOriginOutsideDepot { | ||
| 333 | depot_base: depot.base_path().to_path_buf(), | ||
| 334 | origin: origin_canonical, | ||
| 335 | }); | ||
| 336 | } | ||
| 337 | |||
| 338 | // unwrap should be fine, this path starts with the prefix | ||
| 339 | let origin = origin_canonical | ||
| 340 | .strip_prefix(depot.base_path()) | ||
| 341 | .unwrap() | ||
| 342 | .to_path_buf(); | ||
| 343 | let destination = link_desc.destination; | ||
| 344 | |||
| 345 | let ty = if origin.is_dir() { | ||
| 346 | for link in depot.links() { | ||
| 347 | if link.origin().starts_with(&origin) && link.origin() != origin { | ||
| 348 | return Err(LinkCreateError::DirectoryHasLinkedChildren(origin)); | ||
| 349 | } | ||
| 350 | } | ||
| 351 | LinkType::Directory | ||
| 352 | } else { | ||
| 353 | for link in depot.links() { | ||
| 354 | if origin.starts_with(link.origin()) && origin != link.origin() { | ||
| 355 | assert_eq!(link.link_type(), LinkType::Directory); | ||
| 356 | return Err(LinkCreateError::FileHasLinkedParent(origin)); | ||
| 357 | } | ||
| 358 | } | ||
| 359 | LinkType::File | ||
| 360 | }; | ||
| 361 | |||
| 362 | Ok(Link { | ||
| 363 | id: Default::default(), | ||
| 364 | ty, | ||
| 365 | origin, | ||
| 366 | origin_canonical, | ||
| 367 | destination, | ||
| 368 | }) | ||
| 369 | } | ||
| 370 | |||
| 371 | fn depot_get_link(depot: &Depot, link_id: LinkID) -> Option<&Link> { | ||
| 372 | depot.links.get(link_id) | ||
| 373 | } | ||
| 374 | |||
| 375 | fn depot_remove_link(depot: &mut Depot, link_id: LinkID) { | ||
| 376 | depot.links.remove(link_id); | ||
| 377 | } | ||
| 378 | |||
| 379 | fn depot_install_link( | ||
| 380 | _depot: &Depot, | ||
| 381 | link: &Link, | ||
| 382 | install_base: &Path, | ||
| 383 | ) -> Result<(), LinkInstallError> { | ||
| 384 | let final_origin = link.origin_canonical(); | ||
| 385 | let final_destination = link.install_destination(install_base); | ||
| 386 | |||
| 387 | log::debug!("Final origin : {}", final_origin.display()); | ||
| 388 | log::debug!("Final destination : {}", final_destination.display()); | ||
| 389 | |||
| 390 | if let Some(dest_base) = final_destination.parent() { | ||
| 391 | std::fs::create_dir_all(dest_base)?; | ||
| 392 | } | ||
| 393 | |||
| 394 | // Exit early if there is some error or if the link already exists | ||
| 395 | match std::fs::symlink_metadata(&final_destination) { | ||
| 396 | Ok(metadata) => { | ||
| 397 | let filetype = metadata.file_type(); | ||
| 398 | if filetype.is_symlink() { | ||
| 399 | let symlink_destination = std::fs::read_link(&final_destination)?; | ||
| 400 | if symlink_destination == final_origin { | ||
| 401 | return Ok(()); | ||
| 402 | } | ||
| 403 | log::trace!( | ||
| 404 | "Symlink destinations where not equal : {} != {}", | ||
| 405 | final_origin.display(), | ||
| 406 | symlink_destination.display() | ||
| 407 | ); | ||
| 408 | return Err(LinkInstallError::LinkExists( | ||
| 409 | final_destination, | ||
| 410 | symlink_destination, | ||
| 411 | )); | ||
| 412 | } else { | ||
| 413 | return Err(LinkInstallError::FileExists(final_destination, metadata)); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} | ||
| 417 | Err(e) => return Err(e.into()), | ||
| 418 | }; | ||
| 419 | |||
| 420 | log::debug!( | ||
| 421 | "Creating symlink from {} to {}", | ||
| 422 | final_origin.display(), | ||
| 423 | final_destination.display() | ||
| 424 | ); | ||
| 425 | std::os::unix::fs::symlink(&final_origin, &final_destination)?; | ||
| 426 | |||
| 427 | Ok(()) | ||
| 428 | } | ||
| 429 | |||
| 430 | fn depot_is_link_installed(link: &Link, install_base: &Path) -> bool { | ||
| 431 | let origin_canonical = link.origin_canonical(); | ||
| 432 | let install_destination = link.install_destination(install_base); | ||
| 433 | match std::fs::read_link(&install_destination) { | ||
| 434 | Ok(target) if target == origin_canonical => true, | ||
| 435 | _ => false, | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | fn depot_uninstall_link(_depot: &Depot, link: &Link, install_base: &Path) -> Result<()> { | ||
| 440 | let origin_canonical = link.origin_canonical(); | ||
| 441 | let install_destination = link.install_destination(install_base); | ||
| 442 | let link_target = match std::fs::read_link(&install_destination) { | ||
| 443 | Ok(target) => target, | ||
| 444 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), | ||
| 445 | Err(e) => return Err(e.into()), | ||
| 446 | }; | ||
| 447 | |||
| 448 | if link_target.canonicalize()? == origin_canonical { | ||
| 449 | std::fs::remove_file(&install_destination)?; | ||
| 450 | } | ||
| 451 | |||
| 452 | Ok(()) | ||
| 453 | } | ||
| 454 | |||
| 455 | fn depot_insert_link(depot: &mut Depot, mut link: Link) -> LinkID { | ||
| 456 | let origin = link.origin().to_path_buf(); | ||
| 457 | if let Some(link_id) = depot.links_by_origin.remove(&origin) { | ||
| 458 | depot.links.remove(link_id); | ||
| 459 | } | ||
| 460 | let link_id = depot.links.insert_with_key(move |k| { | ||
| 461 | link.id = k; | ||
| 462 | link | ||
| 463 | }); | ||
| 464 | depot.links_by_origin.insert(origin, link_id); | ||
| 465 | link_id | ||
| 466 | } | ||
| 467 | |||
| 468 | fn depot_links(depot: &Depot) -> impl Iterator<Item = &Link> { | ||
| 469 | depot.links.values() | ||
| 470 | } | ||
| 471 | |||
| 472 | fn link_ensure_relative_path(path: &Path) -> Result<(), LinkCreateError> { | ||
| 473 | if !path.is_relative() { | ||
| 474 | return Err(LinkCreateError::LinkPathIsNotRelative(path.to_path_buf())); | ||
| 475 | } | ||
| 476 | Ok(()) | ||
| 477 | } | ||
| 478 | |||
| 479 | fn link_to_archive_link(depot_link: &Link) -> ArchiveLink { | ||
| 480 | ArchiveLink { | ||
| 481 | origin: depot_link.origin().to_path_buf(), | ||
| 482 | destination: depot_link.destination().to_path_buf(), | ||
| 483 | } | ||
| 484 | } | ||
diff --git a/dotup/src/error.rs b/dotup/src/error.rs deleted file mode 100644 index 8ad801d..0000000 --- a/dotup/src/error.rs +++ /dev/null | |||
| @@ -1,21 +0,0 @@ | |||
| 1 | use crate::{LinkCreateError, LinkInstallError}; | ||
| 2 | use std::path::PathBuf; | ||
| 3 | use thiserror::Error; | ||
| 4 | |||
| 5 | #[derive(Debug, Error)] | ||
| 6 | pub enum Error { | ||
| 7 | #[error("Link install error : {0}")] | ||
| 8 | LinkInstallError(#[from] LinkInstallError), | ||
| 9 | #[error("Link create error : {0}")] | ||
| 10 | LinkCreateError(#[from] LinkCreateError), | ||
| 11 | #[error("Link origin doesnt exist : {}", .0.display())] | ||
| 12 | ArchivePathNotFile(PathBuf), | ||
| 13 | #[error("The archive path did not exist : {}\n{}", .0.display(), .1)] | ||
| 14 | ArchiveMissing(PathBuf, std::io::Error), | ||
| 15 | #[error("Deserialization error : {0}")] | ||
| 16 | SerializationError(Box<dyn std::error::Error + Send + Sync + 'static>), | ||
| 17 | #[error(transparent)] | ||
| 18 | IOError(#[from] std::io::Error), | ||
| 19 | } | ||
| 20 | |||
| 21 | pub type Result<T, E = Error> = std::result::Result<T, E>; | ||
diff --git a/dotup/src/lib.rs b/dotup/src/lib.rs deleted file mode 100644 index 96c4cd7..0000000 --- a/dotup/src/lib.rs +++ /dev/null | |||
| @@ -1,18 +0,0 @@ | |||
| 1 | mod archive; | ||
| 2 | mod depot; | ||
| 3 | mod error; | ||
| 4 | |||
| 5 | pub mod utils; | ||
| 6 | |||
| 7 | pub use archive::{ | ||
| 8 | archive_deserialize, archive_exists, archive_read, archive_serialize, archive_write, Archive, | ||
| 9 | ArchiveLink, | ||
| 10 | }; | ||
| 11 | pub use depot::{ | ||
| 12 | Depot, DepotConfig, Link, LinkCreateError, LinkCreateParams, LinkID, LinkInstallError, | ||
| 13 | }; | ||
| 14 | pub use error::{Error, Result}; | ||
| 15 | |||
| 16 | pub(crate) mod internal_prelude { | ||
| 17 | pub use super::{utils, Error, Result}; | ||
| 18 | } | ||
diff --git a/dotup/src/utils.rs b/dotup/src/utils.rs deleted file mode 100644 index c9ea959..0000000 --- a/dotup/src/utils.rs +++ /dev/null | |||
| @@ -1,94 +0,0 @@ | |||
| 1 | use std::path::{Component, Path, PathBuf}; | ||
| 2 | |||
| 3 | pub fn is_file(path: impl AsRef<Path>) -> std::io::Result<bool> { | ||
| 4 | let metadata = match std::fs::metadata(path) { | ||
| 5 | Ok(metadata) => metadata, | ||
| 6 | Err(e) => match e.kind() { | ||
| 7 | std::io::ErrorKind::NotFound => return Ok(false), | ||
| 8 | _ => return Err(e), | ||
| 9 | }, | ||
| 10 | }; | ||
| 11 | Ok(metadata.is_file()) | ||
| 12 | } | ||
| 13 | |||
| 14 | pub fn is_directory(path: impl AsRef<Path>) -> std::io::Result<bool> { | ||
| 15 | let metadata = match std::fs::metadata(path) { | ||
| 16 | Ok(metadata) => metadata, | ||
| 17 | Err(e) => match e.kind() { | ||
| 18 | std::io::ErrorKind::NotFound => return Ok(false), | ||
| 19 | _ => return Err(e), | ||
| 20 | }, | ||
| 21 | }; | ||
| 22 | Ok(metadata.is_dir()) | ||
| 23 | } | ||
| 24 | |||
| 25 | pub fn is_canonical(path: &Path) -> bool { | ||
| 26 | path == weakly_canonical(path).as_path() | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn weakly_canonical(path: impl AsRef<Path>) -> PathBuf { | ||
| 30 | let cwd = std::env::current_dir().expect("Failed to obtain current directory"); | ||
| 31 | weakly_canonical_cwd(path, cwd) | ||
| 32 | } | ||
| 33 | |||
| 34 | fn weakly_canonical_cwd(path: impl AsRef<Path>, cwd: PathBuf) -> PathBuf { | ||
| 35 | // Adapated from | ||
| 36 | // https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61 | ||
| 37 | let path = path.as_ref(); | ||
| 38 | |||
| 39 | let mut components = path.components().peekable(); | ||
| 40 | let mut canonical = cwd; | ||
| 41 | let prefix = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { | ||
| 42 | components.next(); | ||
| 43 | PathBuf::from(c.as_os_str()) | ||
| 44 | } else { | ||
| 45 | PathBuf::new() | ||
| 46 | }; | ||
| 47 | |||
| 48 | for component in components { | ||
| 49 | match component { | ||
| 50 | Component::Prefix(_) => unreachable!(), | ||
| 51 | Component::RootDir => { | ||
| 52 | canonical = prefix.clone(); | ||
| 53 | canonical.push(component.as_os_str()) | ||
| 54 | } | ||
| 55 | Component::CurDir => {} | ||
| 56 | Component::ParentDir => { | ||
| 57 | canonical.pop(); | ||
| 58 | } | ||
| 59 | Component::Normal(p) => canonical.push(p), | ||
| 60 | }; | ||
| 61 | } | ||
| 62 | |||
| 63 | canonical | ||
| 64 | } | ||
| 65 | |||
| 66 | #[cfg(test)] | ||
| 67 | mod tests { | ||
| 68 | use super::*; | ||
| 69 | |||
| 70 | #[test] | ||
| 71 | fn weak_canonical_test() { | ||
| 72 | let cwd = PathBuf::from("/home/user"); | ||
| 73 | assert_eq!( | ||
| 74 | PathBuf::from("/home/dest"), | ||
| 75 | weakly_canonical_cwd("../dest", cwd.clone()) | ||
| 76 | ); | ||
| 77 | assert_eq!( | ||
| 78 | PathBuf::from("/home/dest/configs/init.vim"), | ||
| 79 | weakly_canonical_cwd("../dest/configs/init.vim", cwd.clone()) | ||
| 80 | ); | ||
| 81 | assert_eq!( | ||
| 82 | PathBuf::from("/dest/configs/init.vim"), | ||
| 83 | weakly_canonical_cwd("/dest/configs/init.vim", cwd.clone()) | ||
| 84 | ); | ||
| 85 | assert_eq!( | ||
| 86 | PathBuf::from("/home/user/configs/nvim/lua/setup.lua"), | ||
| 87 | weakly_canonical_cwd("./configs/nvim/lua/setup.lua", cwd.clone()) | ||
| 88 | ); | ||
| 89 | assert_eq!( | ||
| 90 | PathBuf::from("/home/user/configs/nvim/lua/setup.lua"), | ||
| 91 | weakly_canonical_cwd("configs/nvim/lua/setup.lua", cwd.clone()) | ||
| 92 | ); | ||
| 93 | } | ||
| 94 | } | ||
diff --git a/dotup/tests/integration_tests.rs b/dotup/tests/integration_tests.rs deleted file mode 100644 index d6bed8d..0000000 --- a/dotup/tests/integration_tests.rs +++ /dev/null | |||
| @@ -1,126 +0,0 @@ | |||
| 1 | use std::path::{Path, PathBuf}; | ||
| 2 | |||
| 3 | use dotup::{ArchiveLink, Depot, DepotConfig, LinkCreateParams}; | ||
| 4 | use tempfile::TempDir; | ||
| 5 | |||
| 6 | const TESTING_DEPOT_NAME: &str = "depot.toml"; | ||
| 7 | const TESTING_DEPOT_CONTENTS: &str = include_str!("testing_depot.toml"); | ||
| 8 | |||
| 9 | fn create_empty_file(path: impl AsRef<Path>) { | ||
| 10 | let path = path.as_ref(); | ||
| 11 | if let Some(parent) = path.parent() { | ||
| 12 | std::fs::create_dir_all(parent).unwrap(); | ||
| 13 | } | ||
| 14 | std::fs::write(path, "").unwrap(); | ||
| 15 | } | ||
| 16 | |||
| 17 | fn prepare_empty_temp_dir() -> TempDir { | ||
| 18 | TempDir::new().unwrap() | ||
| 19 | } | ||
| 20 | |||
| 21 | fn prepare_temp_dir() -> TempDir { | ||
| 22 | let dir = TempDir::new().unwrap(); | ||
| 23 | std::fs::write(dir.path().join(TESTING_DEPOT_NAME), TESTING_DEPOT_CONTENTS).unwrap(); | ||
| 24 | create_empty_file(dir.path().join("o1/file1.txt")); | ||
| 25 | create_empty_file(dir.path().join("o2/file2.txt")); | ||
| 26 | dir | ||
| 27 | } | ||
| 28 | |||
| 29 | fn read_depot(dir: &TempDir) -> Depot { | ||
| 30 | let archive_path = dir.path().join(TESTING_DEPOT_NAME); | ||
| 31 | Depot::new(DepotConfig { | ||
| 32 | archive: dotup::archive_read(&archive_path).unwrap(), | ||
| 33 | archive_path, | ||
| 34 | }) | ||
| 35 | .unwrap() | ||
| 36 | } | ||
| 37 | |||
| 38 | #[test] | ||
| 39 | fn test_archive_deserialize() { | ||
| 40 | let archive = dotup::archive_deserialize(&TESTING_DEPOT_CONTENTS).unwrap(); | ||
| 41 | |||
| 42 | let link1 = ArchiveLink { | ||
| 43 | origin: PathBuf::from("o1/file1.txt"), | ||
| 44 | destination: PathBuf::from("d1/file.txt"), | ||
| 45 | }; | ||
| 46 | let link2 = ArchiveLink { | ||
| 47 | origin: PathBuf::from("o2/file2.txt"), | ||
| 48 | destination: PathBuf::from("d2/d2/file.txt"), | ||
| 49 | }; | ||
| 50 | |||
| 51 | assert_eq!(2, archive.links.len()); | ||
| 52 | assert!(archive.links.contains(&link1)); | ||
| 53 | assert!(archive.links.contains(&link2)); | ||
| 54 | } | ||
| 55 | |||
| 56 | #[test] | ||
| 57 | fn test_archive_exists() { | ||
| 58 | let empty_dir = prepare_empty_temp_dir(); | ||
| 59 | let dir = prepare_temp_dir(); | ||
| 60 | |||
| 61 | assert!(!dotup::archive_exists( | ||
| 62 | empty_dir.path().join(TESTING_DEPOT_NAME) | ||
| 63 | )); | ||
| 64 | assert!(dotup::archive_exists(dir.path().join(TESTING_DEPOT_NAME))); | ||
| 65 | } | ||
| 66 | |||
| 67 | #[test] | ||
| 68 | fn test_depot_create() { | ||
| 69 | let empty_dir = prepare_empty_temp_dir(); | ||
| 70 | let dir = prepare_temp_dir(); | ||
| 71 | |||
| 72 | let d1 = Depot::new(DepotConfig { | ||
| 73 | archive: Default::default(), | ||
| 74 | archive_path: empty_dir.path().join(TESTING_DEPOT_NAME), | ||
| 75 | }); | ||
| 76 | assert!(d1.is_err()); | ||
| 77 | |||
| 78 | let archive_path = dir.path().join(TESTING_DEPOT_NAME); | ||
| 79 | let d2 = Depot::new(DepotConfig { | ||
| 80 | archive: dotup::archive_read(&archive_path).unwrap(), | ||
| 81 | archive_path, | ||
| 82 | }); | ||
| 83 | assert!(d2.is_ok()); | ||
| 84 | } | ||
| 85 | |||
| 86 | #[test] | ||
| 87 | fn test_depot_create_link() { | ||
| 88 | let dir = prepare_temp_dir(); | ||
| 89 | let mut depot = read_depot(&dir); | ||
| 90 | |||
| 91 | create_empty_file(dir.path().join("o3/file.txt")); | ||
| 92 | |||
| 93 | let l1 = depot.create_link(LinkCreateParams { | ||
| 94 | origin: PathBuf::from("o3/file.txt"), | ||
| 95 | destination: PathBuf::from(".config/file.txt"), | ||
| 96 | }); | ||
| 97 | assert!(l1.is_ok()); | ||
| 98 | |||
| 99 | let l2 = depot.create_link(LinkCreateParams { | ||
| 100 | origin: PathBuf::from("o4/file.txt"), | ||
| 101 | destination: PathBuf::from(".config/file.txt"), | ||
| 102 | }); | ||
| 103 | assert!(l2.is_err()); | ||
| 104 | } | ||
| 105 | |||
| 106 | #[test] | ||
| 107 | fn test_depot_install_uninstall_link() { | ||
| 108 | let dir = prepare_temp_dir(); | ||
| 109 | let depot = read_depot(&dir); | ||
| 110 | let install_base = dir.path(); | ||
| 111 | |||
| 112 | for link in depot.links() { | ||
| 113 | depot.install_link(link, install_base).unwrap(); | ||
| 114 | } | ||
| 115 | |||
| 116 | for link in depot.links() { | ||
| 117 | let link_path = link.install_destination(install_base); | ||
| 118 | let link_target = std::fs::read_link(&link_path).unwrap(); | ||
| 119 | assert_eq!(link_target.canonicalize().unwrap(), link.origin_canonical()); | ||
| 120 | } | ||
| 121 | |||
| 122 | for link in depot.links() { | ||
| 123 | depot.uninstall_link(link, install_base).unwrap(); | ||
| 124 | assert!(!link.install_destination(install_base).exists()); | ||
| 125 | } | ||
| 126 | } | ||
diff --git a/dotup/tests/testing_depot.toml b/dotup/tests/testing_depot.toml deleted file mode 100644 index ee5224a..0000000 --- a/dotup/tests/testing_depot.toml +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | [[links]] | ||
| 2 | origin = "o1/file1.txt" | ||
| 3 | destination = "d1/file.txt" | ||
| 4 | |||
| 5 | [[links]] | ||
| 6 | origin = "o2/file2.txt" | ||
| 7 | destination = "d2/d2/file.txt" | ||
diff --git a/dotup_cli/Cargo.toml b/dotup_cli/Cargo.toml deleted file mode 100644 index 89b8c27..0000000 --- a/dotup_cli/Cargo.toml +++ /dev/null | |||
| @@ -1,29 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2018" | ||
| 3 | name = "dotup_cli" | ||
| 4 | version = "0.1.0" | ||
| 5 | |||
| 6 | [[bin]] | ||
| 7 | name = "dotup" | ||
| 8 | path = "src/main.rs" | ||
| 9 | doc = false | ||
| 10 | |||
| 11 | [dependencies] | ||
| 12 | anyhow = "1.0" | ||
| 13 | log = "0.4" | ||
| 14 | ansi_term = "0.12.1" | ||
| 15 | |||
| 16 | [dependencies.clap] | ||
| 17 | features = ["derive"] | ||
| 18 | version = "3.0.0-rc.7" | ||
| 19 | |||
| 20 | [dependencies.dotup] | ||
| 21 | path = "../dotup" | ||
| 22 | |||
| 23 | [dependencies.flexi_logger] | ||
| 24 | features = ["colors"] | ||
| 25 | version = "0.22" | ||
| 26 | |||
| 27 | [dev-dependencies] | ||
| 28 | tempfile = "3.2" | ||
| 29 | assert_cmd = "2.0" | ||
diff --git a/dotup_cli/src/commands/init.rs b/dotup_cli/src/commands/init.rs deleted file mode 100644 index 45129bf..0000000 --- a/dotup_cli/src/commands/init.rs +++ /dev/null | |||
| @@ -1,22 +0,0 @@ | |||
| 1 | use super::prelude::*; | ||
| 2 | |||
| 3 | /// Creates an empty depot file if one doesnt already exist. | ||
| 4 | /// | ||
| 5 | /// By default this will create the file in the current directory | ||
| 6 | /// but the --depot flag can be used to change this path. | ||
| 7 | #[derive(Parser)] | ||
| 8 | pub struct Opts {} | ||
| 9 | |||
| 10 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 11 | if !dotup::utils::is_file(&config.archive_path)? { | ||
| 12 | let archive = Archive::default(); | ||
| 13 | log::info!("Creating archive at {}", &config.archive_path.display()); | ||
| 14 | utils::write_archive(&config.archive_path, &archive)?; | ||
| 15 | } else { | ||
| 16 | log::warn!( | ||
| 17 | "Archive file already exists : '{}'", | ||
| 18 | config.archive_path.display() | ||
| 19 | ); | ||
| 20 | } | ||
| 21 | Ok(()) | ||
| 22 | } | ||
diff --git a/dotup_cli/src/commands/install.rs b/dotup_cli/src/commands/install.rs deleted file mode 100644 index 6d9fbf7..0000000 --- a/dotup_cli/src/commands/install.rs +++ /dev/null | |||
| @@ -1,25 +0,0 @@ | |||
| 1 | use std::path::PathBuf; | ||
| 2 | |||
| 3 | use super::prelude::*; | ||
| 4 | |||
| 5 | /// Install links. (Creates symlinks). | ||
| 6 | /// | ||
| 7 | /// Installing a link will create the necessary directories. | ||
| 8 | /// If a file or directory already exists at the location a link would be installed this command will fail. | ||
| 9 | #[derive(Parser)] | ||
| 10 | pub struct Opts { | ||
| 11 | /// The files/directories to install. | ||
| 12 | #[clap(min_values = 1, default_value = ".")] | ||
| 13 | paths: Vec<PathBuf>, | ||
| 14 | } | ||
| 15 | |||
| 16 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 17 | let depot = utils::read_depot(&config.archive_path)?; | ||
| 18 | |||
| 19 | for link in utils::collect_links_by_base_paths(&depot, &opts.paths) { | ||
| 20 | log::info!("Installing link {}", link); | ||
| 21 | depot.install_link(link, &config.install_path)?; | ||
| 22 | } | ||
| 23 | |||
| 24 | Ok(()) | ||
| 25 | } | ||
diff --git a/dotup_cli/src/commands/link.rs b/dotup_cli/src/commands/link.rs deleted file mode 100644 index d1f61ea..0000000 --- a/dotup_cli/src/commands/link.rs +++ /dev/null | |||
| @@ -1,134 +0,0 @@ | |||
| 1 | use std::{ | ||
| 2 | fs::{DirEntry, Metadata}, | ||
| 3 | path::{Path, PathBuf}, | ||
| 4 | }; | ||
| 5 | |||
| 6 | use super::prelude::*; | ||
| 7 | |||
| 8 | /// Creates links | ||
| 9 | /// | ||
| 10 | /// If a link is created for a file that already had a link then the old link will be overwritten. | ||
| 11 | /// By default creating a link to a directory will recursively link all files under that | ||
| 12 | /// directory, to actually link a directory use the --directory flag. | ||
| 13 | #[derive(Parser)] | ||
| 14 | pub struct Opts { | ||
| 15 | /// Treats the paths as directories. This will create links to the actual directories instead | ||
| 16 | /// of recursively linking all files under them. | ||
| 17 | #[clap(long)] | ||
| 18 | directory: bool, | ||
| 19 | |||
| 20 | /// The paths to link. The last path is the destination. | ||
| 21 | paths: Vec<PathBuf>, | ||
| 22 | } | ||
| 23 | |||
| 24 | // TODO: require destination | ||
| 25 | // remove else branch | ||
| 26 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 27 | let mut depot = utils::read_depot(&config.archive_path)?; | ||
| 28 | |||
| 29 | let (origins, destination) = match opts.paths.as_slice() { | ||
| 30 | p @ [] | p @ [_] => (p, None), | ||
| 31 | [o @ .., dest] => (o, Some(dest)), | ||
| 32 | _ => unreachable!(), | ||
| 33 | }; | ||
| 34 | |||
| 35 | if let Some(destination) = destination { | ||
| 36 | let params = if opts.directory { | ||
| 37 | origins | ||
| 38 | .iter() | ||
| 39 | .map(|p| LinkCreateParams { | ||
| 40 | origin: p.to_path_buf(), | ||
| 41 | destination: destination.clone(), | ||
| 42 | }) | ||
| 43 | .collect() | ||
| 44 | } else { | ||
| 45 | let mut params = Vec::new(); | ||
| 46 | for origin in origins { | ||
| 47 | generate_link_params(&depot, origin, destination, origin, &mut params)?; | ||
| 48 | } | ||
| 49 | params | ||
| 50 | }; | ||
| 51 | |||
| 52 | for link_params in params { | ||
| 53 | log::info!("Creating link : {}", link_params); | ||
| 54 | depot.create_link(link_params)?; | ||
| 55 | } | ||
| 56 | } else { | ||
| 57 | let base_path = match origins { | ||
| 58 | [] => std::env::current_dir()?, | ||
| 59 | [path] => path.clone(), | ||
| 60 | _ => unreachable!(), | ||
| 61 | }; | ||
| 62 | |||
| 63 | for link in utils::collect_links_by_base_paths(&depot, std::iter::once(base_path)) { | ||
| 64 | log::info!("{}", link); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | utils::write_depot(&depot)?; | ||
| 69 | |||
| 70 | Ok(()) | ||
| 71 | } | ||
| 72 | |||
| 73 | fn generate_link_params( | ||
| 74 | depot: &Depot, | ||
| 75 | origin: &Path, | ||
| 76 | destination: &Path, | ||
| 77 | base: &Path, | ||
| 78 | params: &mut Vec<LinkCreateParams>, | ||
| 79 | ) -> anyhow::Result<()> { | ||
| 80 | let metadata = std::fs::metadata(origin)?; | ||
| 81 | if metadata.is_file() { | ||
| 82 | generate_file_link_params(depot, origin, destination, base, params)?; | ||
| 83 | } else if metadata.is_dir() { | ||
| 84 | generate_directory_link_params_recursive(depot, origin, destination, base, params)?; | ||
| 85 | } | ||
| 86 | Ok(()) | ||
| 87 | } | ||
| 88 | |||
| 89 | fn generate_file_link_params( | ||
| 90 | depot: &Depot, | ||
| 91 | origin: &Path, | ||
| 92 | destination: &Path, | ||
| 93 | base: &Path, | ||
| 94 | params: &mut Vec<LinkCreateParams>, | ||
| 95 | ) -> anyhow::Result<()> { | ||
| 96 | let origin_canonical = origin | ||
| 97 | .canonicalize() | ||
| 98 | .expect("Failed to canonicalize origin path"); | ||
| 99 | let base_canonical = base | ||
| 100 | .canonicalize() | ||
| 101 | .expect("Failed to canonicalize base path"); | ||
| 102 | |||
| 103 | log::debug!("Origin canonical : {}", origin_canonical.display()); | ||
| 104 | log::debug!("Base : {}", base.display()); | ||
| 105 | |||
| 106 | let partial = origin_canonical | ||
| 107 | .strip_prefix(base_canonical) | ||
| 108 | .expect("Failed to remove prefix from origin path"); | ||
| 109 | let destination = destination.join(partial); | ||
| 110 | let origin = origin_canonical | ||
| 111 | .strip_prefix(depot.base_path()) | ||
| 112 | .unwrap_or(&origin_canonical); | ||
| 113 | |||
| 114 | let link_params = LinkCreateParams { | ||
| 115 | origin: origin.to_path_buf(), | ||
| 116 | destination, | ||
| 117 | }; | ||
| 118 | params.push(link_params); | ||
| 119 | Ok(()) | ||
| 120 | } | ||
| 121 | |||
| 122 | fn generate_directory_link_params_recursive( | ||
| 123 | depot: &Depot, | ||
| 124 | dir_path: &Path, | ||
| 125 | destination: &Path, | ||
| 126 | base: &Path, | ||
| 127 | params: &mut Vec<LinkCreateParams>, | ||
| 128 | ) -> anyhow::Result<()> { | ||
| 129 | for origin in dir_path.read_dir()? { | ||
| 130 | let origin = origin?.path(); | ||
| 131 | generate_link_params(depot, &origin, destination, base, params)?; | ||
| 132 | } | ||
| 133 | Ok(()) | ||
| 134 | } | ||
diff --git a/dotup_cli/src/commands/mod.rs b/dotup_cli/src/commands/mod.rs deleted file mode 100644 index bd92599..0000000 --- a/dotup_cli/src/commands/mod.rs +++ /dev/null | |||
| @@ -1,11 +0,0 @@ | |||
| 1 | pub mod init; | ||
| 2 | pub mod install; | ||
| 3 | pub mod link; | ||
| 4 | pub mod mv; | ||
| 5 | pub mod status; | ||
| 6 | pub mod uninstall; | ||
| 7 | pub mod unlink; | ||
| 8 | |||
| 9 | mod prelude { | ||
| 10 | pub use crate::prelude::*; | ||
| 11 | } | ||
diff --git a/dotup_cli/src/commands/mv.rs b/dotup_cli/src/commands/mv.rs deleted file mode 100644 index aae2715..0000000 --- a/dotup_cli/src/commands/mv.rs +++ /dev/null | |||
| @@ -1,53 +0,0 @@ | |||
| 1 | use std::path::{Path, PathBuf}; | ||
| 2 | |||
| 3 | use super::prelude::*; | ||
| 4 | |||
| 5 | /// Install links. (Creates symlinks). | ||
| 6 | /// | ||
| 7 | /// Installing a link will create the necessary directories. | ||
| 8 | /// If a file or directory already exists at the location a link would be installed this command will fail. | ||
| 9 | #[derive(Parser)] | ||
| 10 | pub struct Opts { | ||
| 11 | /// The files/directories to move | ||
| 12 | #[clap(min_values = 2)] | ||
| 13 | paths: Vec<PathBuf>, | ||
| 14 | } | ||
| 15 | |||
| 16 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 17 | let mut depot = utils::read_depot(&config.archive_path)?; | ||
| 18 | |||
| 19 | let (sources, destination) = match opts.paths.as_slice() { | ||
| 20 | [source, destination] => {} | ||
| 21 | [sources @ .., destination] => { | ||
| 22 | let mut curr_destination = destination.to_owned(); | ||
| 23 | for source in sources { | ||
| 24 | let filename = match source.file_name() { | ||
| 25 | Some(filename) => filename, | ||
| 26 | None => { | ||
| 27 | log::warn!("Ignoring '{}', unknown file name", source.display()); | ||
| 28 | continue; | ||
| 29 | } | ||
| 30 | }; | ||
| 31 | curr_destination.push(filename); | ||
| 32 | std::fs::rename(source, &curr_destination)?; | ||
| 33 | if let Some(id) = depot.get_link_id_by_path(&source) { | ||
| 34 | depot.rename_link(id, &curr_destination); | ||
| 35 | } | ||
| 36 | curr_destination.pop(); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | _ => unreachable!(), | ||
| 40 | }; | ||
| 41 | |||
| 42 | utils::write_depot(&depot)?; | ||
| 43 | |||
| 44 | Ok(()) | ||
| 45 | } | ||
| 46 | |||
| 47 | fn rename(depot: &mut Depot, source: &Path, destination: &Path) -> anyhow::Result<()> { | ||
| 48 | std::fs::rename(source, &destination)?; | ||
| 49 | if let Some(id) = depot.get_link_id_by_path(&source) { | ||
| 50 | depot.rename_link(id, &destination); | ||
| 51 | } | ||
| 52 | Ok(()) | ||
| 53 | } | ||
diff --git a/dotup_cli/src/commands/status.rs b/dotup_cli/src/commands/status.rs deleted file mode 100644 index b7221db..0000000 --- a/dotup_cli/src/commands/status.rs +++ /dev/null | |||
| @@ -1,175 +0,0 @@ | |||
| 1 | use std::path::{Path, PathBuf}; | ||
| 2 | |||
| 3 | use ansi_term::Colour; | ||
| 4 | use clap::Parser; | ||
| 5 | use dotup::{Depot, Link}; | ||
| 6 | |||
| 7 | use crate::{utils, Config}; | ||
| 8 | |||
| 9 | /// Shows information about links | ||
| 10 | /// | ||
| 11 | /// If a link is created for a file that already had a link then the old link will be overwritten. | ||
| 12 | /// By default creating a link to a directory will recursively link all files under that | ||
| 13 | /// directory, to actually link a directory use the --directory flag. | ||
| 14 | #[derive(Parser)] | ||
| 15 | pub struct Opts { | ||
| 16 | /// The location where links will be installed to. | ||
| 17 | /// Defaults to the home directory. | ||
| 18 | #[clap(long)] | ||
| 19 | install_base: Option<PathBuf>, | ||
| 20 | |||
| 21 | /// The paths to show the status of | ||
| 22 | paths: Vec<PathBuf>, | ||
| 23 | } | ||
| 24 | |||
| 25 | #[derive(Debug)] | ||
| 26 | struct State { | ||
| 27 | max_depth: u32, | ||
| 28 | install_path: PathBuf, | ||
| 29 | } | ||
| 30 | |||
| 31 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 32 | let mut depot = utils::read_depot(&config.archive_path)?; | ||
| 33 | |||
| 34 | // walk dir | ||
| 35 | // if node is file: | ||
| 36 | // if linked | ||
| 37 | // print name in green and destination blue | ||
| 38 | // if invalid | ||
| 39 | // print name and destination red | ||
| 40 | // if not linked | ||
| 41 | // print name in gray | ||
| 42 | // if node is directory: | ||
| 43 | // if linked | ||
| 44 | // print name in green and destination blue | ||
| 45 | // if invalid | ||
| 46 | // print name and destination red | ||
| 47 | // if not linked: | ||
| 48 | // print name in gray | ||
| 49 | // if contains files that are linked/invalid: | ||
| 50 | // recurse into directory | ||
| 51 | // | ||
| 52 | |||
| 53 | let depot_base = depot.base_path(); | ||
| 54 | let mut paths = Vec::new(); | ||
| 55 | for path in opts.paths { | ||
| 56 | let canonical = dotup::utils::weakly_canonical(&path); | ||
| 57 | if canonical.starts_with(depot_base) { | ||
| 58 | paths.push(canonical); | ||
| 59 | } else { | ||
| 60 | log::warn!("Path '{}' is outside the depot", path.display()); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | if paths.is_empty() { | ||
| 65 | paths.push(PathBuf::from(".")); | ||
| 66 | } | ||
| 67 | |||
| 68 | let state = State { | ||
| 69 | max_depth: u32::MAX, | ||
| 70 | install_path: config.install_path, | ||
| 71 | }; | ||
| 72 | |||
| 73 | let (directories, files) = utils::collect_read_dir_split(".")?; | ||
| 74 | for path in directories.into_iter().chain(files.into_iter()) { | ||
| 75 | display_status_path(&depot, &state, &path, 0); | ||
| 76 | } | ||
| 77 | |||
| 78 | utils::write_depot(&depot)?; | ||
| 79 | |||
| 80 | Ok(()) | ||
| 81 | } | ||
| 82 | |||
| 83 | fn display_status_path(depot: &Depot, state: &State, path: &Path, depth: u32) { | ||
| 84 | if depth == state.max_depth { | ||
| 85 | return; | ||
| 86 | } | ||
| 87 | |||
| 88 | if path.is_dir() { | ||
| 89 | display_status_directory(depot, state, path, depth); | ||
| 90 | } else { | ||
| 91 | display_status_file(depot, state, path, depth); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | fn display_status_directory(depot: &Depot, state: &State, path: &Path, depth: u32) { | ||
| 96 | assert!(path.is_dir()); | ||
| 97 | if depth == state.max_depth || !depot.subpath_has_links(path) { | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | if let Some(link) = depot.get_link_by_path(path) { | ||
| 102 | print_link(depot, state, link, depth); | ||
| 103 | } else { | ||
| 104 | for entry in std::fs::read_dir(path).unwrap() { | ||
| 105 | let entry = match entry { | ||
| 106 | Ok(entry) => entry, | ||
| 107 | Err(_) => continue, | ||
| 108 | }; | ||
| 109 | let entry_path = entry.path().canonicalize().unwrap(); | ||
| 110 | let entry_path_stripped = entry_path | ||
| 111 | .strip_prefix(std::env::current_dir().unwrap()) | ||
| 112 | .unwrap(); | ||
| 113 | |||
| 114 | print_identation(depth); | ||
| 115 | println!( | ||
| 116 | "{}", | ||
| 117 | Colour::Yellow.paint(&format!("{}", path.file_name().unwrap().to_string_lossy())) | ||
| 118 | ); | ||
| 119 | |||
| 120 | display_status_path(depot, state, &entry_path_stripped, depth + 1); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | fn display_status_file(depot: &Depot, state: &State, path: &Path, depth: u32) { | ||
| 126 | print_identation(depth); | ||
| 127 | if let Some(link) = depot.get_link_by_path(path) { | ||
| 128 | print_link(depot, state, link, depth); | ||
| 129 | } else { | ||
| 130 | print_unlinked(path, depth); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | fn print_link(depot: &Depot, state: &State, link: &Link, depth: u32) { | ||
| 135 | let origin = link.origin(); | ||
| 136 | let dest = link.destination(); | ||
| 137 | let filename = match origin.file_name() { | ||
| 138 | Some(filename) => filename, | ||
| 139 | None => return, | ||
| 140 | }; | ||
| 141 | |||
| 142 | print_identation(depth); | ||
| 143 | if depot.is_link_installed(link, &state.install_path) { | ||
| 144 | println!( | ||
| 145 | "{} -> {}", | ||
| 146 | Colour::Green.paint(&format!("{}", filename.to_string_lossy())), | ||
| 147 | Colour::Blue.paint(&format!("{}", dest.display())), | ||
| 148 | ); | ||
| 149 | } else { | ||
| 150 | println!( | ||
| 151 | "{}", | ||
| 152 | Colour::Red.paint(&format!( | ||
| 153 | "{} -> {}", | ||
| 154 | filename.to_string_lossy(), | ||
| 155 | dest.display() | ||
| 156 | )) | ||
| 157 | ); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | fn print_unlinked(path: &Path, depth: u32) { | ||
| 162 | let filename = match path.file_name() { | ||
| 163 | Some(filename) => filename, | ||
| 164 | None => return, | ||
| 165 | }; | ||
| 166 | |||
| 167 | print_identation(depth); | ||
| 168 | println!("{}", filename.to_string_lossy()); | ||
| 169 | } | ||
| 170 | |||
| 171 | fn print_identation(depth: u32) { | ||
| 172 | for i in 0..depth { | ||
| 173 | print!(" "); | ||
| 174 | } | ||
| 175 | } | ||
diff --git a/dotup_cli/src/commands/uninstall.rs b/dotup_cli/src/commands/uninstall.rs deleted file mode 100644 index fe44bf0..0000000 --- a/dotup_cli/src/commands/uninstall.rs +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | use std::path::PathBuf; | ||
| 2 | |||
| 3 | use super::prelude::*; | ||
| 4 | |||
| 5 | /// Uninstalls links. (Removes symlinks). | ||
| 6 | /// | ||
| 7 | /// Uninstalling a link for a file that didnt have a link will do nothing. | ||
| 8 | /// Uninstalling a directory will recursively uninstall all files under it. | ||
| 9 | /// Symlinks are only deleted if they were pointing to the correct file. | ||
| 10 | #[derive(Parser)] | ||
| 11 | pub struct Opts { | ||
| 12 | /// The files/directories to uninstall. | ||
| 13 | #[clap(min_values = 1, default_value = ".")] | ||
| 14 | paths: Vec<PathBuf>, | ||
| 15 | } | ||
| 16 | |||
| 17 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 18 | let depot = utils::read_depot(&config.archive_path)?; | ||
| 19 | |||
| 20 | for link in utils::collect_links_by_base_paths(&depot, &opts.paths) { | ||
| 21 | log::info!("Uninstalling link : {}", link); | ||
| 22 | depot.uninstall_link(link, &config.install_path)?; | ||
| 23 | } | ||
| 24 | |||
| 25 | Ok(()) | ||
| 26 | } | ||
diff --git a/dotup_cli/src/commands/unlink.rs b/dotup_cli/src/commands/unlink.rs deleted file mode 100644 index abe23e3..0000000 --- a/dotup_cli/src/commands/unlink.rs +++ /dev/null | |||
| @@ -1,43 +0,0 @@ | |||
| 1 | use std::{ | ||
| 2 | fs::{DirEntry, Metadata}, | ||
| 3 | path::{Path, PathBuf}, | ||
| 4 | }; | ||
| 5 | |||
| 6 | use super::prelude::*; | ||
| 7 | |||
| 8 | /// Unlinks files/directories. | ||
| 9 | /// | ||
| 10 | /// This will recursively remove links. If a path is a directory then it will remove all links | ||
| 11 | /// recursively. | ||
| 12 | /// The links are not uninstall by default, see the --uninstall parameter. | ||
| 13 | #[derive(Parser)] | ||
| 14 | pub struct Opts { | ||
| 15 | /// Specify the install base if the links are also to be uninstalled. | ||
| 16 | #[clap(long)] | ||
| 17 | uninstall: Option<PathBuf>, | ||
| 18 | |||
| 19 | /// The paths to unlink. | ||
| 20 | #[clap(required = true, min_values = 1)] | ||
| 21 | paths: Vec<PathBuf>, | ||
| 22 | } | ||
| 23 | |||
| 24 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 25 | let mut depot = utils::read_depot(&config.archive_path)?; | ||
| 26 | |||
| 27 | for link_id in utils::collect_link_ids_by_base_paths(&depot, &opts.paths) { | ||
| 28 | let link = depot.get_link(link_id).unwrap(); | ||
| 29 | log::info!( | ||
| 30 | "Unlinking(uninstall = {}) : {}", | ||
| 31 | opts.uninstall.is_some(), | ||
| 32 | link | ||
| 33 | ); | ||
| 34 | if let Some(ref install_base) = opts.uninstall { | ||
| 35 | depot.uninstall_link(link, &install_base)?; | ||
| 36 | } | ||
| 37 | depot.remove_link(link_id); | ||
| 38 | } | ||
| 39 | |||
| 40 | utils::write_depot(&depot)?; | ||
| 41 | |||
| 42 | Ok(()) | ||
| 43 | } | ||
diff --git a/dotup_cli/src/config.rs b/dotup_cli/src/config.rs deleted file mode 100644 index dabaf74..0000000 --- a/dotup_cli/src/config.rs +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | use std::path::PathBuf; | ||
| 2 | |||
| 3 | #[derive(Debug)] | ||
| 4 | pub struct Config { | ||
| 5 | pub archive_path: PathBuf, | ||
| 6 | pub install_path: PathBuf, | ||
| 7 | pub working_path: PathBuf, | ||
| 8 | } | ||
| 9 | |||
| 10 | impl Config {} | ||
diff --git a/dotup_cli/src/main.rs b/dotup_cli/src/main.rs deleted file mode 100644 index 0d730da..0000000 --- a/dotup_cli/src/main.rs +++ /dev/null | |||
| @@ -1,111 +0,0 @@ | |||
| 1 | #![allow(unused)] | ||
| 2 | |||
| 3 | pub mod commands; | ||
| 4 | pub mod config; | ||
| 5 | pub mod utils; | ||
| 6 | |||
| 7 | pub use config::Config; | ||
| 8 | |||
| 9 | pub mod prelude { | ||
| 10 | pub use super::{utils, Config}; | ||
| 11 | pub use clap::{AppSettings, Parser}; | ||
| 12 | pub use dotup::{Archive, Depot, DepotConfig, Link, LinkCreateParams, LinkID}; | ||
| 13 | } | ||
| 14 | |||
| 15 | use clap::{AppSettings, Parser}; | ||
| 16 | use flexi_logger::Logger; | ||
| 17 | use std::{ | ||
| 18 | collections::HashMap, | ||
| 19 | iter::FromIterator, | ||
| 20 | path::{Path, PathBuf}, | ||
| 21 | }; | ||
| 22 | |||
| 23 | use prelude::*; | ||
| 24 | |||
| 25 | #[derive(Parser)] | ||
| 26 | struct Opts { | ||
| 27 | /// Path to the depot file. | ||
| 28 | /// | ||
| 29 | /// By default it will try to find a file named "depot.toml" in the current directory or any of | ||
| 30 | /// the parent directories. | ||
| 31 | #[clap(long)] | ||
| 32 | depot: Option<PathBuf>, | ||
| 33 | |||
| 34 | /// Disable output to the console | ||
| 35 | #[clap(short, long)] | ||
| 36 | quiet: bool, | ||
| 37 | |||
| 38 | /// A level of verbosity, and can be used multiple times | ||
| 39 | /// | ||
| 40 | /// Level 1 - Info | ||
| 41 | /// | ||
| 42 | /// Level 2 - Debug | ||
| 43 | /// | ||
| 44 | /// Level 3 - Trace | ||
| 45 | #[clap(short, long, parse(from_occurrences))] | ||
| 46 | verbose: i32, | ||
| 47 | |||
| 48 | /// The location where links will be installed to. | ||
| 49 | /// Defaults to the home directory. | ||
| 50 | #[clap(short, long)] | ||
| 51 | install_path: Option<PathBuf>, | ||
| 52 | |||
| 53 | #[clap(subcommand)] | ||
| 54 | subcmd: SubCommand, | ||
| 55 | } | ||
| 56 | |||
| 57 | #[derive(Parser)] | ||
| 58 | enum SubCommand { | ||
| 59 | Init(commands::init::Opts), | ||
| 60 | Link(commands::link::Opts), | ||
| 61 | Mv(commands::mv::Opts), | ||
| 62 | Status(commands::status::Opts), | ||
| 63 | Unlink(commands::unlink::Opts), | ||
| 64 | Install(commands::install::Opts), | ||
| 65 | Uninstall(commands::uninstall::Opts), | ||
| 66 | } | ||
| 67 | |||
| 68 | fn main() -> anyhow::Result<()> { | ||
| 69 | let opts = Opts::parse(); | ||
| 70 | |||
| 71 | if !opts.quiet { | ||
| 72 | let log_level = match opts.verbose { | ||
| 73 | 0 => "warn", | ||
| 74 | 1 => "info", | ||
| 75 | 2 => "debug", | ||
| 76 | _ => "trace", | ||
| 77 | }; | ||
| 78 | |||
| 79 | Logger::try_with_env_or_str(log_level)? | ||
| 80 | .format(flexi_logger::colored_default_format) | ||
| 81 | .set_palette("196;208;32;198;15".to_string()) | ||
| 82 | .start()?; | ||
| 83 | } | ||
| 84 | |||
| 85 | let archive_path = match opts.depot { | ||
| 86 | Some(path) => path, | ||
| 87 | None => utils::find_archive_path()?, | ||
| 88 | }; | ||
| 89 | let install_path = match opts.install_path { | ||
| 90 | Some(path) => path, | ||
| 91 | None => utils::home_directory()?, | ||
| 92 | }; | ||
| 93 | let working_path = std::env::current_dir().expect("Failed to obtain current working directory"); | ||
| 94 | log::debug!("Archive path : {}", archive_path.display()); | ||
| 95 | |||
| 96 | let config = Config { | ||
| 97 | archive_path, | ||
| 98 | install_path, | ||
| 99 | working_path, | ||
| 100 | }; | ||
| 101 | |||
| 102 | match opts.subcmd { | ||
| 103 | SubCommand::Init(opts) => commands::init::main(config, opts), | ||
| 104 | SubCommand::Link(opts) => commands::link::main(config, opts), | ||
| 105 | SubCommand::Mv(opts) => commands::mv::main(config, opts), | ||
| 106 | SubCommand::Status(opts) => commands::status::main(config, opts), | ||
| 107 | SubCommand::Unlink(opts) => commands::unlink::main(config, opts), | ||
| 108 | SubCommand::Install(opts) => commands::install::main(config, opts), | ||
| 109 | SubCommand::Uninstall(opts) => commands::uninstall::main(config, opts), | ||
| 110 | } | ||
| 111 | } | ||
diff --git a/dotup_cli/src/utils.rs b/dotup_cli/src/utils.rs deleted file mode 100644 index b9a76a7..0000000 --- a/dotup_cli/src/utils.rs +++ /dev/null | |||
| @@ -1,182 +0,0 @@ | |||
| 1 | use std::{ | ||
| 2 | collections::VecDeque, | ||
| 3 | path::{Path, PathBuf}, | ||
| 4 | }; | ||
| 5 | |||
| 6 | use crate::prelude::*; | ||
| 7 | |||
| 8 | const DEFAULT_DEPOT_NAME: &str = "depot.toml"; | ||
| 9 | |||
| 10 | pub fn home_directory() -> anyhow::Result<PathBuf> { | ||
| 11 | match std::env::var("HOME") { | ||
| 12 | Ok(val) => Ok(PathBuf::from(val)), | ||
| 13 | Err(e) => { | ||
| 14 | log::error!("Failed to get home directory from enviornment variable"); | ||
| 15 | Err(e.into()) | ||
| 16 | } | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | pub fn find_archive_path() -> anyhow::Result<PathBuf> { | ||
| 21 | let cwd = std::env::current_dir()?; | ||
| 22 | let compn = cwd.components().count(); | ||
| 23 | let mut start = PathBuf::new(); | ||
| 24 | for _ in 0..=compn { | ||
| 25 | start.push(DEFAULT_DEPOT_NAME); | ||
| 26 | if dotup::archive_exists(&start) { | ||
| 27 | return Ok(start); | ||
| 28 | } | ||
| 29 | start.pop(); | ||
| 30 | start.push(".."); | ||
| 31 | } | ||
| 32 | Ok(PathBuf::from(DEFAULT_DEPOT_NAME)) | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn write_archive(path: impl AsRef<Path>, archive: &Archive) -> anyhow::Result<()> { | ||
| 36 | let path = path.as_ref(); | ||
| 37 | log::debug!("Writing archive to {}", path.display()); | ||
| 38 | match dotup::archive_write(path, archive) { | ||
| 39 | Ok(_) => Ok(()), | ||
| 40 | Err(e) => { | ||
| 41 | log::error!( | ||
| 42 | "Failed to write archive to : {}\nError : {}", | ||
| 43 | path.display(), | ||
| 44 | e | ||
| 45 | ); | ||
| 46 | Err(e.into()) | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | pub fn write_depot(depot: &Depot) -> anyhow::Result<()> { | ||
| 52 | let write_path = depot.archive_path(); | ||
| 53 | let archive = depot.archive(); | ||
| 54 | match dotup::archive_write(write_path, &archive) { | ||
| 55 | Ok(_) => Ok(()), | ||
| 56 | Err(e) => { | ||
| 57 | log::error!( | ||
| 58 | "Failed to write depot archive to : {}\nError : {}", | ||
| 59 | write_path.display(), | ||
| 60 | e | ||
| 61 | ); | ||
| 62 | Err(e.into()) | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | pub fn read_archive(path: impl AsRef<Path>) -> anyhow::Result<Archive> { | ||
| 68 | let path = path.as_ref(); | ||
| 69 | match dotup::archive_read(path) { | ||
| 70 | Ok(archive) => Ok(archive), | ||
| 71 | Err(e) => { | ||
| 72 | log::error!( | ||
| 73 | "Failed to read archive from : {}\nError : {}", | ||
| 74 | path.display(), | ||
| 75 | e | ||
| 76 | ); | ||
| 77 | Err(e.into()) | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | pub fn read_depot(archive_path: impl AsRef<Path>) -> anyhow::Result<Depot> { | ||
| 83 | let archive_path = archive_path.as_ref().to_path_buf(); | ||
| 84 | let archive = read_archive(&archive_path)?; | ||
| 85 | let depot_config = DepotConfig { | ||
| 86 | archive: Default::default(), | ||
| 87 | archive_path, | ||
| 88 | }; | ||
| 89 | let mut depot = Depot::new(depot_config)?; | ||
| 90 | |||
| 91 | for archive_link in archive.links { | ||
| 92 | let link_params = LinkCreateParams::from(archive_link); | ||
| 93 | if let Err(e) = depot.create_link(link_params) { | ||
| 94 | log::warn!("Error while adding link : {}", e); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | Ok(depot) | ||
| 99 | } | ||
| 100 | |||
| 101 | pub fn collect_links_by_base_paths( | ||
| 102 | depot: &Depot, | ||
| 103 | paths: impl IntoIterator<Item = impl AsRef<Path>>, | ||
| 104 | ) -> Vec<&Link> { | ||
| 105 | let canonical_paths: Vec<_> = paths | ||
| 106 | .into_iter() | ||
| 107 | .map(|p| p.as_ref().canonicalize().unwrap()) | ||
| 108 | .collect(); | ||
| 109 | |||
| 110 | depot | ||
| 111 | .links() | ||
| 112 | .filter(|&l| { | ||
| 113 | canonical_paths | ||
| 114 | .iter() | ||
| 115 | .any(|p| l.origin_canonical().starts_with(p)) | ||
| 116 | }) | ||
| 117 | .collect() | ||
| 118 | } | ||
| 119 | |||
| 120 | pub fn collect_link_ids_by_base_paths( | ||
| 121 | depot: &Depot, | ||
| 122 | paths: impl IntoIterator<Item = impl AsRef<Path>>, | ||
| 123 | ) -> Vec<LinkID> { | ||
| 124 | collect_links_by_base_paths(depot, paths) | ||
| 125 | .into_iter() | ||
| 126 | .map(|l| l.id()) | ||
| 127 | .collect() | ||
| 128 | } | ||
| 129 | |||
| 130 | /// Returns a list of canonical paths to all the files in `dir`. This includes files in | ||
| 131 | /// subdirectories. | ||
| 132 | /// Fails if dir isnt a directory or if there is some other io error. | ||
| 133 | pub fn collect_files_in_dir(dir: impl Into<PathBuf>) -> anyhow::Result<Vec<PathBuf>> { | ||
| 134 | let mut paths = Vec::new(); | ||
| 135 | let mut dirs = VecDeque::new(); | ||
| 136 | dirs.push_back(dir.into()); | ||
| 137 | |||
| 138 | while let Some(dir) = dirs.pop_front() { | ||
| 139 | for entry in std::fs::read_dir(dir)? { | ||
| 140 | let entry = entry?; | ||
| 141 | let filetype = entry.file_type()?; | ||
| 142 | if filetype.is_dir() { | ||
| 143 | dirs.push_back(entry.path()); | ||
| 144 | } else { | ||
| 145 | paths.push(entry.path()); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | Ok(paths) | ||
| 151 | } | ||
| 152 | |||
| 153 | /// Collects the result of std::fs::read_dir into two vecs | ||
| 154 | /// The first one contains all the directories and the second one all the files | ||
| 155 | pub fn collect_read_dir_split( | ||
| 156 | dir: impl AsRef<Path>, | ||
| 157 | ) -> anyhow::Result<(Vec<PathBuf>, Vec<PathBuf>)> { | ||
| 158 | Ok(std::fs::read_dir(dir)? | ||
| 159 | .filter_map(|e| e.ok()) | ||
| 160 | .map(|e| e.path()) | ||
| 161 | .partition(|p| p.is_dir())) | ||
| 162 | } | ||
| 163 | |||
| 164 | /// Checks if `path` is inside a git repository | ||
| 165 | pub fn path_is_in_git_repo(path: &Path) -> bool { | ||
| 166 | let mut path = if !path.is_absolute() { | ||
| 167 | dbg!(dotup::utils::weakly_canonical(path)) | ||
| 168 | } else { | ||
| 169 | path.to_owned() | ||
| 170 | }; | ||
| 171 | let recurse = path.pop(); | ||
| 172 | path.push(".git"); | ||
| 173 | if path.is_dir() { | ||
| 174 | return true; | ||
| 175 | } | ||
| 176 | if recurse { | ||
| 177 | path.pop(); | ||
| 178 | return path_is_in_git_repo(&path); | ||
| 179 | } else { | ||
| 180 | return false; | ||
| 181 | } | ||
| 182 | } | ||
diff --git a/dotup_cli/tests/cli.rs b/dotup_cli/tests/cli.rs deleted file mode 100644 index c836f63..0000000 --- a/dotup_cli/tests/cli.rs +++ /dev/null | |||
| @@ -1,145 +0,0 @@ | |||
| 1 | use assert_cmd::{assert::Assert, prelude::*}; | ||
| 2 | use dotup::ArchiveLink; | ||
| 3 | use std::{ | ||
| 4 | path::{Path, PathBuf}, | ||
| 5 | process::Command, | ||
| 6 | }; | ||
| 7 | use tempfile::TempDir; | ||
| 8 | |||
| 9 | const DEPOT_FILE_NAME: &str = "depot.toml"; | ||
| 10 | const BIN_NAME: &str = "dotup"; | ||
| 11 | |||
| 12 | fn create_empty_file(path: impl AsRef<Path>) { | ||
| 13 | let path = path.as_ref(); | ||
| 14 | if let Some(parent) = path.parent() { | ||
| 15 | std::fs::create_dir_all(parent).unwrap(); | ||
| 16 | } | ||
| 17 | std::fs::write(path, "").unwrap(); | ||
| 18 | } | ||
| 19 | |||
| 20 | fn prepare_command(dir: &TempDir) -> Command { | ||
| 21 | let mut cmd = Command::cargo_bin(BIN_NAME).unwrap(); | ||
| 22 | cmd.current_dir(dir.path()); | ||
| 23 | cmd | ||
| 24 | } | ||
| 25 | |||
| 26 | fn run_command(dir: &TempDir, cmd: &str) -> Assert { | ||
| 27 | let mut c = prepare_command(dir); | ||
| 28 | c.current_dir(dir.path()); | ||
| 29 | c.args(cmd.split_whitespace()); | ||
| 30 | c.assert() | ||
| 31 | } | ||
| 32 | |||
| 33 | fn prepare_dir() -> TempDir { | ||
| 34 | let dir = TempDir::new().unwrap(); | ||
| 35 | create_empty_file(dir.path().join("o1/file.txt")); | ||
| 36 | create_empty_file(dir.path().join("o1/dir/file.txt")); | ||
| 37 | create_empty_file(dir.path().join("o2/file1.txt")); | ||
| 38 | create_empty_file(dir.path().join("o2/file2.txt")); | ||
| 39 | dir | ||
| 40 | } | ||
| 41 | |||
| 42 | #[test] | ||
| 43 | fn test_cli_init() { | ||
| 44 | let dir = prepare_dir(); | ||
| 45 | let assert = run_command(&dir, "init"); | ||
| 46 | |||
| 47 | assert.success().code(0); | ||
| 48 | assert!(dir.path().join(DEPOT_FILE_NAME).is_file()); | ||
| 49 | } | ||
| 50 | |||
| 51 | #[test] | ||
| 52 | fn test_cli_link() { | ||
| 53 | let dir = prepare_dir(); | ||
| 54 | run_command(&dir, "init").success(); | ||
| 55 | |||
| 56 | let assert = run_command(&dir, "link o1 .config"); | ||
| 57 | assert.success().code(0); | ||
| 58 | |||
| 59 | let assert = run_command(&dir, "link --directory o2 .scripts"); | ||
| 60 | assert.success().code(0); | ||
| 61 | |||
| 62 | let archive = dotup::archive_read(dir.path().join(DEPOT_FILE_NAME)).unwrap(); | ||
| 63 | let link1 = ArchiveLink { | ||
| 64 | origin: PathBuf::from("o1/file.txt"), | ||
| 65 | destination: PathBuf::from(".config/file.txt"), | ||
| 66 | }; | ||
| 67 | let link2 = ArchiveLink { | ||
| 68 | origin: PathBuf::from("o1/dir/file.txt"), | ||
| 69 | destination: PathBuf::from(".config/dir/file.txt"), | ||
| 70 | }; | ||
| 71 | let link3 = ArchiveLink { | ||
| 72 | origin: PathBuf::from("o2"), | ||
| 73 | destination: PathBuf::from(".scripts"), | ||
| 74 | }; | ||
| 75 | |||
| 76 | assert!(archive.links.contains(&link1)); | ||
| 77 | assert!(archive.links.contains(&link2)); | ||
| 78 | assert!(archive.links.contains(&link3)); | ||
| 79 | } | ||
| 80 | |||
| 81 | #[test] | ||
| 82 | fn test_cli_install_uninstall_unlink() { | ||
| 83 | let dir = prepare_dir(); | ||
| 84 | run_command(&dir, "init").success(); | ||
| 85 | run_command(&dir, "link o1 .config").success(); | ||
| 86 | run_command(&dir, "link --directory o2 .scripts").success(); | ||
| 87 | |||
| 88 | let install_dir = TempDir::new().unwrap(); | ||
| 89 | let install_base = format!("{}", install_dir.path().display()); | ||
| 90 | run_command( | ||
| 91 | &dir, | ||
| 92 | &format!("--install-path {} install o1 o2", install_base), | ||
| 93 | ) | ||
| 94 | .success(); | ||
| 95 | |||
| 96 | assert_eq!( | ||
| 97 | std::fs::read_link(install_dir.path().join(".config/file.txt")).unwrap(), | ||
| 98 | dir.path().join("o1/file.txt") | ||
| 99 | ); | ||
| 100 | assert_eq!( | ||
| 101 | std::fs::read_link(install_dir.path().join(".config/dir/file.txt")).unwrap(), | ||
| 102 | dir.path().join("o1/dir/file.txt") | ||
| 103 | ); | ||
| 104 | assert_eq!( | ||
| 105 | std::fs::read_link(install_dir.path().join(".scripts")).unwrap(), | ||
| 106 | dir.path().join("o2") | ||
| 107 | ); | ||
| 108 | |||
| 109 | run_command( | ||
| 110 | &dir, | ||
| 111 | &format!("--install-path {} uninstall o1/file.txt", install_base), | ||
| 112 | ) | ||
| 113 | .success(); | ||
| 114 | assert!(!install_dir.path().join(".config/file.txt").exists()); | ||
| 115 | assert!(install_dir.path().join(".config/dir/file.txt").exists()); | ||
| 116 | assert!(install_dir.path().join(".scripts").exists()); | ||
| 117 | |||
| 118 | run_command( | ||
| 119 | &dir, | ||
| 120 | &format!("--install-path {} uninstall o1", install_base), | ||
| 121 | ) | ||
| 122 | .success(); | ||
| 123 | assert!(!install_dir.path().join(".config/file.txt").exists()); | ||
| 124 | assert!(!install_dir.path().join(".config/dir/file.txt").exists()); | ||
| 125 | assert!(install_dir.path().join(".scripts").exists()); | ||
| 126 | |||
| 127 | assert_eq!( | ||
| 128 | 3, | ||
| 129 | dotup::archive_read(dir.path().join(DEPOT_FILE_NAME)) | ||
| 130 | .unwrap() | ||
| 131 | .links | ||
| 132 | .len() | ||
| 133 | ); | ||
| 134 | |||
| 135 | run_command(&dir, &format!("unlink --uninstall {} o2", install_base)).success(); | ||
| 136 | assert!(!install_dir.path().join(".scripts").exists()); | ||
| 137 | |||
| 138 | assert_eq!( | ||
| 139 | 2, | ||
| 140 | dotup::archive_read(dir.path().join(DEPOT_FILE_NAME)) | ||
| 141 | .unwrap() | ||
| 142 | .links | ||
| 143 | .len() | ||
| 144 | ); | ||
| 145 | } | ||
