diff options
| author | diogo464 <[email protected]> | 2021-07-08 17:11:46 -0400 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2021-07-08 17:11:46 -0400 |
| commit | ed0baec0a3f953c99445f6842dadc5566e89cb75 (patch) | |
| tree | 9f988c41db34907283dd126dc57d29b3d0792bd9 | |
Initial commit
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 458 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | configs/depot.toml | 7 | ||||
| -rw-r--r-- | configs/neovim/init.vim | 0 | ||||
| -rw-r--r-- | configs/neovim/lua/setup.lua | 0 | ||||
| -rw-r--r-- | dotup/Cargo.toml | 11 | ||||
| -rw-r--r-- | dotup/src/archive.rs | 44 | ||||
| -rw-r--r-- | dotup/src/depot.rs | 371 | ||||
| -rw-r--r-- | dotup/src/error.rs | 30 | ||||
| -rw-r--r-- | dotup/src/lib.rs | 16 | ||||
| -rw-r--r-- | dotup/src/utils.rs | 96 | ||||
| -rw-r--r-- | dotup_cli/Cargo.toml | 20 | ||||
| -rw-r--r-- | dotup_cli/src/.main.rs.rustfmt | 87 | ||||
| -rw-r--r-- | dotup_cli/src/commands/init.rs | 20 | ||||
| -rw-r--r-- | dotup_cli/src/commands/install.rs | 31 | ||||
| -rw-r--r-- | dotup_cli/src/commands/link.rs | 181 | ||||
| -rw-r--r-- | dotup_cli/src/commands/mod.rs | 11 | ||||
| -rw-r--r-- | dotup_cli/src/commands/uninstall.rs | 31 | ||||
| -rw-r--r-- | dotup_cli/src/commands/unlink.rs | 38 | ||||
| -rw-r--r-- | dotup_cli/src/commands/utils.rs | 100 | ||||
| -rw-r--r-- | dotup_cli/src/config.rs | 6 | ||||
| -rw-r--r-- | dotup_cli/src/main.rs | 87 |
23 files changed, 1648 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore | |||
| @@ -0,0 +1 @@ | |||
| /target | |||
diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a981e03 --- /dev/null +++ b/Cargo.lock | |||
| @@ -0,0 +1,458 @@ | |||
| 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 = "anyhow" | ||
| 16 | version = "1.0.41" | ||
| 17 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 18 | checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" | ||
| 19 | |||
| 20 | [[package]] | ||
| 21 | name = "atty" | ||
| 22 | version = "0.2.14" | ||
| 23 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" | ||
| 25 | dependencies = [ | ||
| 26 | "hermit-abi", | ||
| 27 | "libc", | ||
| 28 | "winapi", | ||
| 29 | ] | ||
| 30 | |||
| 31 | [[package]] | ||
| 32 | name = "autocfg" | ||
| 33 | version = "1.0.1" | ||
| 34 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 35 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" | ||
| 36 | |||
| 37 | [[package]] | ||
| 38 | name = "bitflags" | ||
| 39 | version = "1.2.1" | ||
| 40 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 41 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" | ||
| 42 | |||
| 43 | [[package]] | ||
| 44 | name = "cfg-if" | ||
| 45 | version = "1.0.0" | ||
| 46 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 47 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||
| 48 | |||
| 49 | [[package]] | ||
| 50 | name = "chrono" | ||
| 51 | version = "0.4.19" | ||
| 52 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 53 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" | ||
| 54 | dependencies = [ | ||
| 55 | "libc", | ||
| 56 | "num-integer", | ||
| 57 | "num-traits", | ||
| 58 | "time", | ||
| 59 | "winapi", | ||
| 60 | ] | ||
| 61 | |||
| 62 | [[package]] | ||
| 63 | name = "clap" | ||
| 64 | version = "3.0.0-beta.2" | ||
| 65 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 66 | checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" | ||
| 67 | dependencies = [ | ||
| 68 | "atty", | ||
| 69 | "bitflags", | ||
| 70 | "clap_derive", | ||
| 71 | "indexmap", | ||
| 72 | "lazy_static", | ||
| 73 | "os_str_bytes", | ||
| 74 | "strsim", | ||
| 75 | "termcolor", | ||
| 76 | "textwrap", | ||
| 77 | "unicode-width", | ||
| 78 | "vec_map", | ||
| 79 | ] | ||
| 80 | |||
| 81 | [[package]] | ||
| 82 | name = "clap_derive" | ||
| 83 | version = "3.0.0-beta.2" | ||
| 84 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 85 | checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" | ||
| 86 | dependencies = [ | ||
| 87 | "heck", | ||
| 88 | "proc-macro-error", | ||
| 89 | "proc-macro2", | ||
| 90 | "quote", | ||
| 91 | "syn", | ||
| 92 | ] | ||
| 93 | |||
| 94 | [[package]] | ||
| 95 | name = "dotup" | ||
| 96 | version = "0.1.0" | ||
| 97 | dependencies = [ | ||
| 98 | "log", | ||
| 99 | "serde", | ||
| 100 | "slotmap", | ||
| 101 | "thiserror", | ||
| 102 | "toml", | ||
| 103 | ] | ||
| 104 | |||
| 105 | [[package]] | ||
| 106 | name = "dotup_cli" | ||
| 107 | version = "0.1.0" | ||
| 108 | dependencies = [ | ||
| 109 | "anyhow", | ||
| 110 | "clap", | ||
| 111 | "dotup", | ||
| 112 | "flexi_logger", | ||
| 113 | "log", | ||
| 114 | ] | ||
| 115 | |||
| 116 | [[package]] | ||
| 117 | name = "flexi_logger" | ||
| 118 | version = "0.18.0" | ||
| 119 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 120 | checksum = "8ba2265890613939b533fa11c3728651531419ac549ccf527896201581f23991" | ||
| 121 | dependencies = [ | ||
| 122 | "atty", | ||
| 123 | "chrono", | ||
| 124 | "glob", | ||
| 125 | "lazy_static", | ||
| 126 | "log", | ||
| 127 | "regex", | ||
| 128 | "thiserror", | ||
| 129 | "yansi", | ||
| 130 | ] | ||
| 131 | |||
| 132 | [[package]] | ||
| 133 | name = "glob" | ||
| 134 | version = "0.3.0" | ||
| 135 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 136 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" | ||
| 137 | |||
| 138 | [[package]] | ||
| 139 | name = "hashbrown" | ||
| 140 | version = "0.11.2" | ||
| 141 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 142 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" | ||
| 143 | |||
| 144 | [[package]] | ||
| 145 | name = "heck" | ||
| 146 | version = "0.3.3" | ||
| 147 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 148 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" | ||
| 149 | dependencies = [ | ||
| 150 | "unicode-segmentation", | ||
| 151 | ] | ||
| 152 | |||
| 153 | [[package]] | ||
| 154 | name = "hermit-abi" | ||
| 155 | version = "0.1.19" | ||
| 156 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 157 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" | ||
| 158 | dependencies = [ | ||
| 159 | "libc", | ||
| 160 | ] | ||
| 161 | |||
| 162 | [[package]] | ||
| 163 | name = "indexmap" | ||
| 164 | version = "1.7.0" | ||
| 165 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 166 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" | ||
| 167 | dependencies = [ | ||
| 168 | "autocfg", | ||
| 169 | "hashbrown", | ||
| 170 | ] | ||
| 171 | |||
| 172 | [[package]] | ||
| 173 | name = "lazy_static" | ||
| 174 | version = "1.4.0" | ||
| 175 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 176 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" | ||
| 177 | |||
| 178 | [[package]] | ||
| 179 | name = "libc" | ||
| 180 | version = "0.2.97" | ||
| 181 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 182 | checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" | ||
| 183 | |||
| 184 | [[package]] | ||
| 185 | name = "log" | ||
| 186 | version = "0.4.14" | ||
| 187 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 188 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" | ||
| 189 | dependencies = [ | ||
| 190 | "cfg-if", | ||
| 191 | ] | ||
| 192 | |||
| 193 | [[package]] | ||
| 194 | name = "memchr" | ||
| 195 | version = "2.4.0" | ||
| 196 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 197 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" | ||
| 198 | |||
| 199 | [[package]] | ||
| 200 | name = "num-integer" | ||
| 201 | version = "0.1.44" | ||
| 202 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 203 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" | ||
| 204 | dependencies = [ | ||
| 205 | "autocfg", | ||
| 206 | "num-traits", | ||
| 207 | ] | ||
| 208 | |||
| 209 | [[package]] | ||
| 210 | name = "num-traits" | ||
| 211 | version = "0.2.14" | ||
| 212 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 213 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" | ||
| 214 | dependencies = [ | ||
| 215 | "autocfg", | ||
| 216 | ] | ||
| 217 | |||
| 218 | [[package]] | ||
| 219 | name = "os_str_bytes" | ||
| 220 | version = "2.4.0" | ||
| 221 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 222 | checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" | ||
| 223 | |||
| 224 | [[package]] | ||
| 225 | name = "proc-macro-error" | ||
| 226 | version = "1.0.4" | ||
| 227 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 228 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" | ||
| 229 | dependencies = [ | ||
| 230 | "proc-macro-error-attr", | ||
| 231 | "proc-macro2", | ||
| 232 | "quote", | ||
| 233 | "syn", | ||
| 234 | "version_check", | ||
| 235 | ] | ||
| 236 | |||
| 237 | [[package]] | ||
| 238 | name = "proc-macro-error-attr" | ||
| 239 | version = "1.0.4" | ||
| 240 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 241 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" | ||
| 242 | dependencies = [ | ||
| 243 | "proc-macro2", | ||
| 244 | "quote", | ||
| 245 | "version_check", | ||
| 246 | ] | ||
| 247 | |||
| 248 | [[package]] | ||
| 249 | name = "proc-macro2" | ||
| 250 | version = "1.0.27" | ||
| 251 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 252 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" | ||
| 253 | dependencies = [ | ||
| 254 | "unicode-xid", | ||
| 255 | ] | ||
| 256 | |||
| 257 | [[package]] | ||
| 258 | name = "quote" | ||
| 259 | version = "1.0.9" | ||
| 260 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 261 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" | ||
| 262 | dependencies = [ | ||
| 263 | "proc-macro2", | ||
| 264 | ] | ||
| 265 | |||
| 266 | [[package]] | ||
| 267 | name = "regex" | ||
| 268 | version = "1.5.4" | ||
| 269 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 270 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" | ||
| 271 | dependencies = [ | ||
| 272 | "aho-corasick", | ||
| 273 | "memchr", | ||
| 274 | "regex-syntax", | ||
| 275 | ] | ||
| 276 | |||
| 277 | [[package]] | ||
| 278 | name = "regex-syntax" | ||
| 279 | version = "0.6.25" | ||
| 280 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 281 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" | ||
| 282 | |||
| 283 | [[package]] | ||
| 284 | name = "serde" | ||
| 285 | version = "1.0.126" | ||
| 286 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 287 | checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" | ||
| 288 | dependencies = [ | ||
| 289 | "serde_derive", | ||
| 290 | ] | ||
| 291 | |||
| 292 | [[package]] | ||
| 293 | name = "serde_derive" | ||
| 294 | version = "1.0.126" | ||
| 295 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 296 | checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" | ||
| 297 | dependencies = [ | ||
| 298 | "proc-macro2", | ||
| 299 | "quote", | ||
| 300 | "syn", | ||
| 301 | ] | ||
| 302 | |||
| 303 | [[package]] | ||
| 304 | name = "slotmap" | ||
| 305 | version = "1.0.5" | ||
| 306 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 307 | checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4" | ||
| 308 | dependencies = [ | ||
| 309 | "version_check", | ||
| 310 | ] | ||
| 311 | |||
| 312 | [[package]] | ||
| 313 | name = "strsim" | ||
| 314 | version = "0.10.0" | ||
| 315 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 316 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" | ||
| 317 | |||
| 318 | [[package]] | ||
| 319 | name = "syn" | ||
| 320 | version = "1.0.73" | ||
| 321 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 322 | checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" | ||
| 323 | dependencies = [ | ||
| 324 | "proc-macro2", | ||
| 325 | "quote", | ||
| 326 | "unicode-xid", | ||
| 327 | ] | ||
| 328 | |||
| 329 | [[package]] | ||
| 330 | name = "termcolor" | ||
| 331 | version = "1.1.2" | ||
| 332 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 333 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" | ||
| 334 | dependencies = [ | ||
| 335 | "winapi-util", | ||
| 336 | ] | ||
| 337 | |||
| 338 | [[package]] | ||
| 339 | name = "textwrap" | ||
| 340 | version = "0.12.1" | ||
| 341 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 342 | checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" | ||
| 343 | dependencies = [ | ||
| 344 | "unicode-width", | ||
| 345 | ] | ||
| 346 | |||
| 347 | [[package]] | ||
| 348 | name = "thiserror" | ||
| 349 | version = "1.0.26" | ||
| 350 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 351 | checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" | ||
| 352 | dependencies = [ | ||
| 353 | "thiserror-impl", | ||
| 354 | ] | ||
| 355 | |||
| 356 | [[package]] | ||
| 357 | name = "thiserror-impl" | ||
| 358 | version = "1.0.26" | ||
| 359 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 360 | checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" | ||
| 361 | dependencies = [ | ||
| 362 | "proc-macro2", | ||
| 363 | "quote", | ||
| 364 | "syn", | ||
| 365 | ] | ||
| 366 | |||
| 367 | [[package]] | ||
| 368 | name = "time" | ||
| 369 | version = "0.1.44" | ||
| 370 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 371 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" | ||
| 372 | dependencies = [ | ||
| 373 | "libc", | ||
| 374 | "wasi", | ||
| 375 | "winapi", | ||
| 376 | ] | ||
| 377 | |||
| 378 | [[package]] | ||
| 379 | name = "toml" | ||
| 380 | version = "0.5.8" | ||
| 381 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 382 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" | ||
| 383 | dependencies = [ | ||
| 384 | "serde", | ||
| 385 | ] | ||
| 386 | |||
| 387 | [[package]] | ||
| 388 | name = "unicode-segmentation" | ||
| 389 | version = "1.8.0" | ||
| 390 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 391 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" | ||
| 392 | |||
| 393 | [[package]] | ||
| 394 | name = "unicode-width" | ||
| 395 | version = "0.1.8" | ||
| 396 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 397 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" | ||
| 398 | |||
| 399 | [[package]] | ||
| 400 | name = "unicode-xid" | ||
| 401 | version = "0.2.2" | ||
| 402 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 403 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" | ||
| 404 | |||
| 405 | [[package]] | ||
| 406 | name = "vec_map" | ||
| 407 | version = "0.8.2" | ||
| 408 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 409 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" | ||
| 410 | |||
| 411 | [[package]] | ||
| 412 | name = "version_check" | ||
| 413 | version = "0.9.3" | ||
| 414 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 415 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" | ||
| 416 | |||
| 417 | [[package]] | ||
| 418 | name = "wasi" | ||
| 419 | version = "0.10.0+wasi-snapshot-preview1" | ||
| 420 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 421 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" | ||
| 422 | |||
| 423 | [[package]] | ||
| 424 | name = "winapi" | ||
| 425 | version = "0.3.9" | ||
| 426 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 427 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" | ||
| 428 | dependencies = [ | ||
| 429 | "winapi-i686-pc-windows-gnu", | ||
| 430 | "winapi-x86_64-pc-windows-gnu", | ||
| 431 | ] | ||
| 432 | |||
| 433 | [[package]] | ||
| 434 | name = "winapi-i686-pc-windows-gnu" | ||
| 435 | version = "0.4.0" | ||
| 436 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 437 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | ||
| 438 | |||
| 439 | [[package]] | ||
| 440 | name = "winapi-util" | ||
| 441 | version = "0.1.5" | ||
| 442 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 443 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" | ||
| 444 | dependencies = [ | ||
| 445 | "winapi", | ||
| 446 | ] | ||
| 447 | |||
| 448 | [[package]] | ||
| 449 | name = "winapi-x86_64-pc-windows-gnu" | ||
| 450 | version = "0.4.0" | ||
| 451 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 452 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||
| 453 | |||
| 454 | [[package]] | ||
| 455 | name = "yansi" | ||
| 456 | version = "0.5.0" | ||
| 457 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 458 | checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" | ||
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..76e6a10 --- /dev/null +++ b/Cargo.toml | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | [workspace] | ||
| 2 | members = ["dotup", "dotup_cli"] | ||
diff --git a/configs/depot.toml b/configs/depot.toml new file mode 100644 index 0000000..a0bf689 --- /dev/null +++ b/configs/depot.toml | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | [[links]] | ||
| 2 | origin = 'neovim/init.vim' | ||
| 3 | destination = '.config/nvim/init.vim' | ||
| 4 | |||
| 5 | [[links]] | ||
| 6 | origin = 'neovim/lua/setup.lua' | ||
| 7 | destination = '.config/nvim/lua/setup.lua' | ||
diff --git a/configs/neovim/init.vim b/configs/neovim/init.vim new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/configs/neovim/init.vim | |||
diff --git a/configs/neovim/lua/setup.lua b/configs/neovim/lua/setup.lua new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/configs/neovim/lua/setup.lua | |||
diff --git a/dotup/Cargo.toml b/dotup/Cargo.toml new file mode 100644 index 0000000..2545e94 --- /dev/null +++ b/dotup/Cargo.toml | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2018" | ||
| 3 | name = "dotup" | ||
| 4 | version = "0.1.0" | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | log = "*" | ||
| 8 | serde = { version = "*", features = ["derive"] } | ||
| 9 | thiserror = "*" | ||
| 10 | toml = "*" | ||
| 11 | slotmap = "*" | ||
diff --git a/dotup/src/archive.rs b/dotup/src/archive.rs new file mode 100644 index 0000000..ad6088d --- /dev/null +++ b/dotup/src/archive.rs | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | use serde::{Deserialize, Serialize}; | ||
| 2 | use std::path::{Path, PathBuf}; | ||
| 3 | |||
| 4 | use crate::internal_prelude::*; | ||
| 5 | |||
| 6 | #[derive(Debug, Default, Serialize, Deserialize)] | ||
| 7 | pub struct ArchiveLink { | ||
| 8 | pub origin: PathBuf, | ||
| 9 | pub destination: PathBuf, | ||
| 10 | } | ||
| 11 | |||
| 12 | #[derive(Debug, Default, 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 new file mode 100644 index 0000000..6f18738 --- /dev/null +++ b/dotup/src/depot.rs | |||
| @@ -0,0 +1,371 @@ | |||
| 1 | use slotmap::SlotMap; | ||
| 2 | use std::{ | ||
| 3 | fs::Metadata, | ||
| 4 | path::{Path, PathBuf}, | ||
| 5 | sync::Arc, | ||
| 6 | }; | ||
| 7 | use thiserror::Error; | ||
| 8 | |||
| 9 | use crate::{internal_prelude::*, Archive, ArchiveLink}; | ||
| 10 | |||
| 11 | #[derive(Debug)] | ||
| 12 | pub struct DepotConfig { | ||
| 13 | /// The archive used to initialize the depot. | ||
| 14 | /// A default archive can be create if one didnt already exist. | ||
| 15 | pub archive: Archive, | ||
| 16 | /// Path to the archive file. This path must be valid and must exist. | ||
| 17 | pub archive_path: PathBuf, | ||
| 18 | } | ||
| 19 | |||
| 20 | slotmap::new_key_type! { pub struct LinkID; } | ||
| 21 | |||
| 22 | #[derive(Debug)] | ||
| 23 | pub struct LinkDesc { | ||
| 24 | pub origin: PathBuf, | ||
| 25 | /// This must be a relative path | ||
| 26 | pub destination: PathBuf, | ||
| 27 | } | ||
| 28 | |||
| 29 | impl LinkDesc { | ||
| 30 | pub fn new(origin: impl Into<PathBuf>, destination: impl Into<PathBuf>) -> Self { | ||
| 31 | Self { | ||
| 32 | origin: origin.into(), | ||
| 33 | destination: destination.into(), | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | impl std::fmt::Display for LinkDesc { | ||
| 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 40 | write!( | ||
| 41 | f, | ||
| 42 | "LinkDesc[{} -> {}]", | ||
| 43 | self.origin.display(), | ||
| 44 | self.destination.display() | ||
| 45 | ) | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | impl From<ArchiveLink> for LinkDesc { | ||
| 50 | fn from(archive_link: ArchiveLink) -> Self { | ||
| 51 | Self { | ||
| 52 | origin: archive_link.origin, | ||
| 53 | destination: archive_link.destination, | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | #[derive(Debug)] | ||
| 59 | pub struct Link { | ||
| 60 | id: LinkID, | ||
| 61 | /// The origin path, when joined with the depot base path, must be valid and it point to a file that exists. | ||
| 62 | origin: PathBuf, | ||
| 63 | /// Canonical version of origin | ||
| 64 | origin_canonical: PathBuf, | ||
| 65 | /// The destination path has to be a relative path. | ||
| 66 | /// To install a link the destination path is joined with the | ||
| 67 | /// install path and the file at base path + origin path is linked | ||
| 68 | /// to this resulting destination path. | ||
| 69 | destination: PathBuf, | ||
| 70 | } | ||
| 71 | |||
| 72 | impl Link { | ||
| 73 | pub fn id(&self) -> LinkID { | ||
| 74 | self.id | ||
| 75 | } | ||
| 76 | |||
| 77 | /// The relative path to the origin file. Relative from depot folder. | ||
| 78 | pub fn origin(&self) -> &Path { | ||
| 79 | &self.origin | ||
| 80 | } | ||
| 81 | |||
| 82 | pub fn origin_canonical(&self) -> &Path { | ||
| 83 | &self.origin_canonical | ||
| 84 | } | ||
| 85 | |||
| 86 | /// The relative path to the install destination. | ||
| 87 | /// This path should be concatenated with an install destination to get the actual destination | ||
| 88 | /// for this link. | ||
| 89 | pub fn destination(&self) -> &Path { | ||
| 90 | &self.destination | ||
| 91 | } | ||
| 92 | |||
| 93 | fn install_destination(&self, install_base: &Path) -> std::io::Result<PathBuf> { | ||
| 94 | Ok(utils::weakly_canonical( | ||
| 95 | install_base.join(self.destination()), | ||
| 96 | )?) | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | impl std::fmt::Display for Link { | ||
| 101 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 102 | write!( | ||
| 103 | f, | ||
| 104 | "Link[{} -> {}]", | ||
| 105 | self.origin().display(), | ||
| 106 | self.destination().display() | ||
| 107 | ) | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | #[derive(Debug)] | ||
| 112 | struct DepotShared { | ||
| 113 | /// Must be canonical path | ||
| 114 | base_path: PathBuf, | ||
| 115 | /// Must be canonical path | ||
| 116 | archive_path: PathBuf, | ||
| 117 | } | ||
| 118 | |||
| 119 | #[derive(Debug)] | ||
| 120 | pub struct Depot { | ||
| 121 | shared: Arc<DepotShared>, | ||
| 122 | // Maps the origin to the link | ||
| 123 | links: SlotMap<LinkID, Link>, | ||
| 124 | } | ||
| 125 | |||
| 126 | impl Depot { | ||
| 127 | pub fn new(config: DepotConfig) -> Result<Self> { | ||
| 128 | depot_create(config) | ||
| 129 | } | ||
| 130 | |||
| 131 | /// Creates a new link from the description. | ||
| 132 | /// The origin path must exist. | ||
| 133 | pub fn create_link(&mut self, link_desc: LinkDesc) -> Result<LinkID> { | ||
| 134 | let link = depot_create_link(self, link_desc)?; | ||
| 135 | let link_id = depot_insert_link(self, link); | ||
| 136 | Ok(link_id) | ||
| 137 | } | ||
| 138 | |||
| 139 | pub fn get_link(&self, link_id: LinkID) -> Option<&Link> { | ||
| 140 | depot_get_link(self, link_id) | ||
| 141 | } | ||
| 142 | |||
| 143 | pub fn remove_link(&mut self, link_id: LinkID) { | ||
| 144 | depot_remove_link(self, link_id) | ||
| 145 | } | ||
| 146 | |||
| 147 | /// Archives this depot so it can be serialized | ||
| 148 | pub fn archive(&self) -> Archive { | ||
| 149 | depot_archive(self) | ||
| 150 | } | ||
| 151 | |||
| 152 | pub fn links(&self) -> impl Iterator<Item = &Link> { | ||
| 153 | depot_links(self) | ||
| 154 | } | ||
| 155 | |||
| 156 | pub fn install_link( | ||
| 157 | &self, | ||
| 158 | link: &Link, | ||
| 159 | install_base: impl AsRef<Path>, | ||
| 160 | ) -> Result<(), LinkInstallError> { | ||
| 161 | depot_install_link(self, link, install_base.as_ref()) | ||
| 162 | } | ||
| 163 | |||
| 164 | pub fn uninstall_link(&self, link: &Link, install_base: impl AsRef<Path>) -> Result<()> { | ||
| 165 | depot_uninstall_link(self, link, install_base.as_ref()) | ||
| 166 | } | ||
| 167 | |||
| 168 | pub fn base_path(&self) -> &Path { | ||
| 169 | &self.shared.base_path | ||
| 170 | } | ||
| 171 | |||
| 172 | pub fn archive_path(&self) -> &Path { | ||
| 173 | &self.shared.archive_path | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | fn depot_create(config: DepotConfig) -> Result<Depot> { | ||
| 178 | let archive_path = match config.archive_path.canonicalize() { | ||
| 179 | Ok(canonicalized) => canonicalized, | ||
| 180 | Err(e) => return Err(Error::ArchiveMissing(config.archive_path, e)), | ||
| 181 | }; | ||
| 182 | if !archive_path.is_file() { | ||
| 183 | return Err(Error::ArchivePathNotFile(archive_path)); | ||
| 184 | } | ||
| 185 | let base_path = archive_path | ||
| 186 | .parent() | ||
| 187 | .expect("Failed to get parent of archive path") | ||
| 188 | .to_path_buf(); | ||
| 189 | |||
| 190 | let depot_shared = DepotShared { | ||
| 191 | base_path, | ||
| 192 | archive_path, | ||
| 193 | }; | ||
| 194 | |||
| 195 | let mut depot = Depot { | ||
| 196 | shared: Arc::new(depot_shared), | ||
| 197 | links: Default::default(), | ||
| 198 | }; | ||
| 199 | |||
| 200 | for archive_link in config.archive.links { | ||
| 201 | let link_desc = LinkDesc::from(archive_link); | ||
| 202 | let link = depot_create_link(&depot, link_desc)?; | ||
| 203 | depot_insert_link(&mut depot, link); | ||
| 204 | } | ||
| 205 | |||
| 206 | Ok(depot) | ||
| 207 | } | ||
| 208 | |||
| 209 | fn depot_archive(depot: &Depot) -> Archive { | ||
| 210 | let mut links = Vec::new(); | ||
| 211 | |||
| 212 | for link in depot_links(depot) { | ||
| 213 | let archive_link = link_to_archive_link(&link); | ||
| 214 | links.push(archive_link); | ||
| 215 | } | ||
| 216 | |||
| 217 | Archive { links } | ||
| 218 | } | ||
| 219 | |||
| 220 | /// Create a valid link for that given Depot using the given link desc. | ||
| 221 | /// The link id is corrected when the link is inserted in the depot. | ||
| 222 | fn depot_create_link(depot: &Depot, link_desc: LinkDesc) -> Result<Link> { | ||
| 223 | // link_ensure_relative_path(&link_desc.origin)?; | ||
| 224 | link_ensure_relative_path(&link_desc.destination)?; | ||
| 225 | debug_assert!(utils::is_canonical(&depot.base_path())?); | ||
| 226 | |||
| 227 | let origin_joined = depot.base_path().join(&link_desc.origin); | ||
| 228 | let origin_result = origin_joined.canonicalize(); | ||
| 229 | let origin_canonical = match origin_result { | ||
| 230 | Ok(canonical) => canonical, | ||
| 231 | Err(e) => match e.kind() { | ||
| 232 | std::io::ErrorKind::NotFound => { | ||
| 233 | return Err(Error::LinkOriginDoesntExist(origin_joined)) | ||
| 234 | } | ||
| 235 | _ => return Err(e.into()), | ||
| 236 | }, | ||
| 237 | }; | ||
| 238 | |||
| 239 | if !origin_canonical.starts_with(depot.base_path()) { | ||
| 240 | return Err(Error::LinkOriginOutsideDepot { | ||
| 241 | depot_base: depot.base_path().to_path_buf(), | ||
| 242 | origin: origin_canonical, | ||
| 243 | }); | ||
| 244 | } | ||
| 245 | |||
| 246 | // unwrap should be fine, this path starts with the prefix | ||
| 247 | let origin = origin_canonical | ||
| 248 | .strip_prefix(depot.base_path()) | ||
| 249 | .unwrap() | ||
| 250 | .to_path_buf(); | ||
| 251 | // let origin = origin_canonical; | ||
| 252 | let destination = link_desc.destination; | ||
| 253 | |||
| 254 | Ok(Link { | ||
| 255 | id: Default::default(), | ||
| 256 | origin, | ||
| 257 | origin_canonical, | ||
| 258 | destination, | ||
| 259 | }) | ||
| 260 | } | ||
| 261 | |||
| 262 | fn depot_get_link(depot: &Depot, link_id: LinkID) -> Option<&Link> { | ||
| 263 | depot.links.get(link_id) | ||
| 264 | } | ||
| 265 | |||
| 266 | fn depot_remove_link(depot: &mut Depot, link_id: LinkID) { | ||
| 267 | depot.links.remove(link_id); | ||
| 268 | } | ||
| 269 | |||
| 270 | #[derive(Debug, Error)] | ||
| 271 | pub enum LinkInstallError { | ||
| 272 | #[error(transparent)] | ||
| 273 | IOError(#[from] std::io::Error), | ||
| 274 | #[error("File already exists at {}", .0.display())] | ||
| 275 | FileExists(PathBuf, Metadata), | ||
| 276 | /// .0 = LinkPath , .1 = LinkDestination | ||
| 277 | #[error("Link already exists {} -> {}", .0.display(), .1.display())] | ||
| 278 | LinkExists(PathBuf, PathBuf), | ||
| 279 | } | ||
| 280 | |||
| 281 | fn depot_install_link( | ||
| 282 | _depot: &Depot, | ||
| 283 | link: &Link, | ||
| 284 | install_base: &Path, | ||
| 285 | ) -> Result<(), LinkInstallError> { | ||
| 286 | let final_origin = link.origin_canonical(); | ||
| 287 | let final_destination = link.install_destination(install_base)?; | ||
| 288 | |||
| 289 | log::debug!("Final origin : {}", final_origin.display()); | ||
| 290 | log::debug!("Final destination : {}", final_destination.display()); | ||
| 291 | |||
| 292 | if let Some(dest_base) = final_destination.parent() { | ||
| 293 | std::fs::create_dir_all(dest_base)?; | ||
| 294 | } | ||
| 295 | |||
| 296 | // Exit early if there is some error or if the link already exists | ||
| 297 | match std::fs::symlink_metadata(&final_destination) { | ||
| 298 | Ok(metadata) => { | ||
| 299 | let filetype = metadata.file_type(); | ||
| 300 | if filetype.is_symlink() { | ||
| 301 | let symlink_destination = std::fs::read_link(&final_destination)?; | ||
| 302 | if symlink_destination == final_origin { | ||
| 303 | return Ok(()); | ||
| 304 | } | ||
| 305 | log::trace!( | ||
| 306 | "Symlink destinations where not equal : {} != {}", | ||
| 307 | final_origin.display(), | ||
| 308 | symlink_destination.display() | ||
| 309 | ); | ||
| 310 | return Err(LinkInstallError::LinkExists( | ||
| 311 | final_destination, | ||
| 312 | symlink_destination, | ||
| 313 | )); | ||
| 314 | } else { | ||
| 315 | return Err(LinkInstallError::FileExists(final_destination, metadata)); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} | ||
| 319 | Err(e) => return Err(e.into()), | ||
| 320 | }; | ||
| 321 | |||
| 322 | log::debug!( | ||
| 323 | "Creating symlink from {} to {}", | ||
| 324 | final_origin.display(), | ||
| 325 | final_destination.display() | ||
| 326 | ); | ||
| 327 | std::os::unix::fs::symlink(&final_origin, &final_destination)?; | ||
| 328 | |||
| 329 | Ok(()) | ||
| 330 | } | ||
| 331 | |||
| 332 | fn depot_uninstall_link(_depot: &Depot, link: &Link, install_base: &Path) -> Result<()> { | ||
| 333 | let origin_canonical = link.origin_canonical(); | ||
| 334 | let install_destination = link.install_destination(install_base)?; | ||
| 335 | let link_target = match std::fs::read_link(&install_destination) { | ||
| 336 | Ok(target) => target, | ||
| 337 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), | ||
| 338 | Err(e) => return Err(e.into()), | ||
| 339 | }; | ||
| 340 | |||
| 341 | if link_target.canonicalize()? == origin_canonical { | ||
| 342 | std::fs::remove_file(&install_destination)?; | ||
| 343 | } | ||
| 344 | |||
| 345 | Ok(()) | ||
| 346 | } | ||
| 347 | |||
| 348 | fn depot_insert_link(depot: &mut Depot, mut link: Link) -> LinkID { | ||
| 349 | depot.links.insert_with_key(move |k| { | ||
| 350 | link.id = k; | ||
| 351 | link | ||
| 352 | }) | ||
| 353 | } | ||
| 354 | |||
| 355 | fn depot_links(depot: &Depot) -> impl Iterator<Item = &Link> { | ||
| 356 | depot.links.values() | ||
| 357 | } | ||
| 358 | |||
| 359 | fn link_ensure_relative_path(path: &Path) -> Result<()> { | ||
| 360 | if !path.is_relative() { | ||
| 361 | return Err(Error::LinkPathIsNotRelative(path.to_path_buf())); | ||
| 362 | } | ||
| 363 | Ok(()) | ||
| 364 | } | ||
| 365 | |||
| 366 | fn link_to_archive_link(depot_link: &Link) -> ArchiveLink { | ||
| 367 | ArchiveLink { | ||
| 368 | origin: depot_link.origin().to_path_buf(), | ||
| 369 | destination: depot_link.destination().to_path_buf(), | ||
| 370 | } | ||
| 371 | } | ||
diff --git a/dotup/src/error.rs b/dotup/src/error.rs new file mode 100644 index 0000000..2a6f241 --- /dev/null +++ b/dotup/src/error.rs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | use crate::LinkInstallError; | ||
| 2 | use std::path::PathBuf; | ||
| 3 | use thiserror::Error; | ||
| 4 | |||
| 5 | #[derive(Debug, Error)] | ||
| 6 | pub enum Error { | ||
| 7 | #[error("Link origin is outside depot base\nDepot : {}\nLink : {}", .depot_base.display(), .origin.display())] | ||
| 8 | LinkOriginOutsideDepot { | ||
| 9 | depot_base: PathBuf, | ||
| 10 | origin: PathBuf, | ||
| 11 | }, | ||
| 12 | #[error("Link install error : {0}")] | ||
| 13 | LinkInstallError(#[from] LinkInstallError), | ||
| 14 | #[error("Link path is not relative : {}", .0.display())] | ||
| 15 | LinkPathIsNotRelative(PathBuf), | ||
| 16 | #[error("Link origin is not a file exist : {}", .0.display())] | ||
| 17 | LinkOriginIsNotFile(PathBuf), | ||
| 18 | #[error("Link origin doesnt exist : {}", .0.display())] | ||
| 19 | LinkOriginDoesntExist(PathBuf), | ||
| 20 | #[error("The archive path is not a file. It many not exist or there could be a permission's problem.")] | ||
| 21 | ArchivePathNotFile(PathBuf), | ||
| 22 | #[error("The archive path did not exist : {}\n{}", .0.display(), .1)] | ||
| 23 | ArchiveMissing(PathBuf, std::io::Error), | ||
| 24 | #[error("Deserialization error : {0}")] | ||
| 25 | SerializationError(Box<dyn std::error::Error + Send + Sync + 'static>), | ||
| 26 | #[error(transparent)] | ||
| 27 | IOError(#[from] std::io::Error), | ||
| 28 | } | ||
| 29 | |||
| 30 | pub type Result<T, E = Error> = std::result::Result<T, E>; | ||
diff --git a/dotup/src/lib.rs b/dotup/src/lib.rs new file mode 100644 index 0000000..6ef59e6 --- /dev/null +++ b/dotup/src/lib.rs | |||
| @@ -0,0 +1,16 @@ | |||
| 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::{Depot, DepotConfig, Link, LinkDesc, LinkID, LinkInstallError}; | ||
| 12 | pub use error::{Error, Result}; | ||
| 13 | |||
| 14 | pub(crate) mod internal_prelude { | ||
| 15 | pub use super::{utils, Error, Result}; | ||
| 16 | } | ||
diff --git a/dotup/src/utils.rs b/dotup/src/utils.rs new file mode 100644 index 0000000..cfcab0f --- /dev/null +++ b/dotup/src/utils.rs | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | use std::path::{Component, Path, PathBuf}; | ||
| 2 | |||
| 3 | use crate::internal_prelude::*; | ||
| 4 | |||
| 5 | pub fn is_file(path: impl AsRef<Path>) -> Result<bool> { | ||
| 6 | let metadata = match std::fs::metadata(path) { | ||
| 7 | Ok(metadata) => metadata, | ||
| 8 | Err(e) => match e.kind() { | ||
| 9 | std::io::ErrorKind::NotFound => return Ok(false), | ||
| 10 | _ => return Err(e.into()), | ||
| 11 | }, | ||
| 12 | }; | ||
| 13 | Ok(metadata.is_file()) | ||
| 14 | } | ||
| 15 | |||
| 16 | pub fn is_directory(path: impl AsRef<Path>) -> Result<bool> { | ||
| 17 | let metadata = match std::fs::metadata(path) { | ||
| 18 | Ok(metadata) => metadata, | ||
| 19 | Err(e) => match e.kind() { | ||
| 20 | std::io::ErrorKind::NotFound => return Ok(false), | ||
| 21 | _ => return Err(e.into()), | ||
| 22 | }, | ||
| 23 | }; | ||
| 24 | Ok(metadata.is_dir()) | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn is_canonical(path: &Path) -> Result<bool> { | ||
| 28 | Ok(path == path.canonicalize()?.as_path()) | ||
| 29 | } | ||
| 30 | |||
| 31 | pub fn weakly_canonical(path: impl AsRef<Path>) -> std::io::Result<PathBuf> { | ||
| 32 | let cwd = std::env::current_dir()?; | ||
| 33 | Ok(weakly_canonical_cwd(path, cwd)) | ||
| 34 | } | ||
| 35 | |||
| 36 | fn weakly_canonical_cwd(path: impl AsRef<Path>, cwd: PathBuf) -> PathBuf { | ||
| 37 | // Adapated from | ||
| 38 | // https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61 | ||
| 39 | let path = path.as_ref(); | ||
| 40 | |||
| 41 | let mut components = path.components().peekable(); | ||
| 42 | let mut canonical = cwd; | ||
| 43 | let prefix = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { | ||
| 44 | components.next(); | ||
| 45 | PathBuf::from(c.as_os_str()) | ||
| 46 | } else { | ||
| 47 | PathBuf::new() | ||
| 48 | }; | ||
| 49 | |||
| 50 | for component in components { | ||
| 51 | match component { | ||
| 52 | Component::Prefix(_) => unreachable!(), | ||
| 53 | Component::RootDir => { | ||
| 54 | canonical = prefix.clone(); | ||
| 55 | canonical.push(component.as_os_str()) | ||
| 56 | } | ||
| 57 | Component::CurDir => {} | ||
| 58 | Component::ParentDir => { | ||
| 59 | canonical.pop(); | ||
| 60 | } | ||
| 61 | Component::Normal(p) => canonical.push(p), | ||
| 62 | }; | ||
| 63 | } | ||
| 64 | |||
| 65 | canonical | ||
| 66 | } | ||
| 67 | |||
| 68 | #[cfg(test)] | ||
| 69 | mod tests { | ||
| 70 | use super::*; | ||
| 71 | |||
| 72 | #[test] | ||
| 73 | fn weak_canonical_test() { | ||
| 74 | let cwd = PathBuf::from("/home/user"); | ||
| 75 | assert_eq!( | ||
| 76 | PathBuf::from("/home/dest"), | ||
| 77 | weakly_canonical_cwd("../dest", cwd.clone()) | ||
| 78 | ); | ||
| 79 | assert_eq!( | ||
| 80 | PathBuf::from("/home/dest/configs/init.vim"), | ||
| 81 | weakly_canonical_cwd("../dest/configs/init.vim", cwd.clone()) | ||
| 82 | ); | ||
| 83 | assert_eq!( | ||
| 84 | PathBuf::from("/dest/configs/init.vim"), | ||
| 85 | weakly_canonical_cwd("/dest/configs/init.vim", cwd.clone()) | ||
| 86 | ); | ||
| 87 | assert_eq!( | ||
| 88 | PathBuf::from("/home/user/configs/nvim/lua/setup.lua"), | ||
| 89 | weakly_canonical_cwd("./configs/nvim/lua/setup.lua", cwd.clone()) | ||
| 90 | ); | ||
| 91 | assert_eq!( | ||
| 92 | PathBuf::from("/home/user/configs/nvim/lua/setup.lua"), | ||
| 93 | weakly_canonical_cwd("configs/nvim/lua/setup.lua", cwd.clone()) | ||
| 94 | ); | ||
| 95 | } | ||
| 96 | } | ||
diff --git a/dotup_cli/Cargo.toml b/dotup_cli/Cargo.toml new file mode 100644 index 0000000..891d32a --- /dev/null +++ b/dotup_cli/Cargo.toml | |||
| @@ -0,0 +1,20 @@ | |||
| 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 | |||
| 10 | [dependencies] | ||
| 11 | anyhow = "*" | ||
| 12 | clap = "3.0.0-beta.2" | ||
| 13 | log = "*" | ||
| 14 | |||
| 15 | [dependencies.dotup] | ||
| 16 | path = "../dotup" | ||
| 17 | |||
| 18 | [dependencies.flexi_logger] | ||
| 19 | features = ["colors"] | ||
| 20 | version = "*" | ||
diff --git a/dotup_cli/src/.main.rs.rustfmt b/dotup_cli/src/.main.rs.rustfmt new file mode 100644 index 0000000..6d020e1 --- /dev/null +++ b/dotup_cli/src/.main.rs.rustfmt | |||
| @@ -0,0 +1,87 @@ | |||
| 1 | #![allow(unused)] | ||
| 2 | |||
| 3 | pub mod commands; | ||
| 4 | pub mod config; | ||
| 5 | |||
| 6 | pub use config::Config; | ||
| 7 | |||
| 8 | pub mod prelude { | ||
| 9 | pub use super::Config; | ||
| 10 | pub use dotup::{Archive, Depot, DepotConfig, Link, LinkDesc, LinkID}; | ||
| 11 | } | ||
| 12 | |||
| 13 | use clap::{AppSettings, Clap}; | ||
| 14 | use flexi_logger::Logger; | ||
| 15 | use std::{ | ||
| 16 | collections::HashMap, | ||
| 17 | iter::FromIterator, | ||
| 18 | path::{Path, PathBuf}, | ||
| 19 | }; | ||
| 20 | |||
| 21 | use prelude::*; | ||
| 22 | |||
| 23 | const DEFAULT_DEPOT_NAME: &str = "depot.toml"; | ||
| 24 | |||
| 25 | #[derive(Clap)] | ||
| 26 | #[clap(version = "0.1", author = "d464")] | ||
| 27 | #[clap(setting = AppSettings::ColoredHelp)] | ||
| 28 | struct Opts { | ||
| 29 | /// Path to the depot file. | ||
| 30 | #[clap(long, default_value = DEFAULT_DEPOT_NAME)] | ||
| 31 | depot: PathBuf, | ||
| 32 | |||
| 33 | /// Disable output to the console | ||
| 34 | #[clap(short, long)] | ||
| 35 | quiet: bool, | ||
| 36 | |||
| 37 | /// A level of verbosity, and can be used multiple times | ||
| 38 | /// | ||
| 39 | /// Level 0 - Warnings (Default) | ||
| 40 | /// Level 1 - Info | ||
| 41 | /// Level 2 - Debug | ||
| 42 | /// Level 3 - Trace | ||
| 43 | #[clap(short, long, parse(from_occurrences))] | ||
| 44 | verbose: i32, | ||
| 45 | |||
| 46 | #[clap(subcommand)] | ||
| 47 | subcmd: SubCommand, | ||
| 48 | } | ||
| 49 | |||
| 50 | #[derive(Clap)] | ||
| 51 | enum SubCommand { | ||
| 52 | Init(commands::init::Opts), | ||
| 53 | Link(commands::link::Opts), | ||
| 54 | Unlink(commands::unlink::Opts), | ||
| 55 | Install(commands::install::Opts), | ||
| 56 | Uninstall(commands::uninstall::Opts), | ||
| 57 | } | ||
| 58 | |||
| 59 | fn main() -> anyhow::Result<()> { | ||
| 60 | let opts = Opts::parse(); | ||
| 61 | |||
| 62 | if !opts.quiet { | ||
| 63 | let log_level = match opts.verbose { | ||
| 64 | 0 => "warn", | ||
| 65 | 1 => "info", | ||
| 66 | 2 => "debug", | ||
| 67 | 3 | _ => "trace", | ||
| 68 | }; | ||
| 69 | |||
| 70 | Logger::try_with_env_or_str(log_level)? | ||
| 71 | .format(flexi_logger::colored_default_format) | ||
| 72 | //.set_palette("196;208;32;198;15".to_string()) | ||
| 73 | .start()?; | ||
| 74 | } | ||
| 75 | |||
| 76 | let config = Config { | ||
| 77 | archive_path: opts.depot, | ||
| 78 | }; | ||
| 79 | |||
| 80 | match opts.subcmd { | ||
| 81 | SubCommand::Init(opts) => commands::init::main(config, opts), | ||
| 82 | SubCommand::Link(opts) => commands::link::main(config, opts), | ||
| 83 | SubCommand::Unlink(opts) => commands::unlink::main(config, opts), | ||
| 84 | SubCommand::Install(opts) => commands::install::main(config, opts), | ||
| 85 | SubCommand::Uninstall(opts) => commands::uninstall::main(config, opts), | ||
| 86 | } | ||
| 87 | } | ||
diff --git a/dotup_cli/src/commands/init.rs b/dotup_cli/src/commands/init.rs new file mode 100644 index 0000000..bfec6ca --- /dev/null +++ b/dotup_cli/src/commands/init.rs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | use clap::Clap; | ||
| 2 | |||
| 3 | use super::prelude::*; | ||
| 4 | |||
| 5 | #[derive(Clap)] | ||
| 6 | pub struct Opts {} | ||
| 7 | |||
| 8 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 9 | if !dotup::utils::is_file(&config.archive_path)? { | ||
| 10 | let archive = Archive::default(); | ||
| 11 | log::info!("Creating archive"); | ||
| 12 | utils::write_archive(&config.archive_path, &archive)?; | ||
| 13 | } else { | ||
| 14 | log::info!( | ||
| 15 | "Archive file already exists : {}", | ||
| 16 | config.archive_path.display() | ||
| 17 | ); | ||
| 18 | } | ||
| 19 | Ok(()) | ||
| 20 | } | ||
diff --git a/dotup_cli/src/commands/install.rs b/dotup_cli/src/commands/install.rs new file mode 100644 index 0000000..72cabf3 --- /dev/null +++ b/dotup_cli/src/commands/install.rs | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | use clap::Clap; | ||
| 2 | use std::path::PathBuf; | ||
| 3 | |||
| 4 | use super::prelude::*; | ||
| 5 | |||
| 6 | #[derive(Clap)] | ||
| 7 | pub struct Opts { | ||
| 8 | /// The location where links will be installed to. | ||
| 9 | /// Defaults to home directory. | ||
| 10 | #[clap(long)] | ||
| 11 | install_base: Option<PathBuf>, | ||
| 12 | |||
| 13 | /// The files/directories to install | ||
| 14 | #[clap(required = true, min_values = 1)] | ||
| 15 | paths: Vec<PathBuf>, | ||
| 16 | } | ||
| 17 | |||
| 18 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 19 | let install_base = match opts.install_base { | ||
| 20 | Some(path) => path, | ||
| 21 | None => utils::home_directory()?, | ||
| 22 | }; | ||
| 23 | let depot = utils::read_depot(&config.archive_path)?; | ||
| 24 | |||
| 25 | for link in utils::collect_links_by_base_paths(&depot, &opts.paths) { | ||
| 26 | log::info!("Installing link {}", link); | ||
| 27 | depot.install_link(link, &install_base)?; | ||
| 28 | } | ||
| 29 | |||
| 30 | Ok(()) | ||
| 31 | } | ||
diff --git a/dotup_cli/src/commands/link.rs b/dotup_cli/src/commands/link.rs new file mode 100644 index 0000000..fd99253 --- /dev/null +++ b/dotup_cli/src/commands/link.rs | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | use clap::Clap; | ||
| 2 | use std::{ | ||
| 3 | fs::{DirEntry, Metadata}, | ||
| 4 | path::{Path, PathBuf}, | ||
| 5 | }; | ||
| 6 | |||
| 7 | use super::prelude::*; | ||
| 8 | |||
| 9 | #[derive(Clap)] | ||
| 10 | pub struct Opts { | ||
| 11 | #[clap(long)] | ||
| 12 | directory: bool, | ||
| 13 | |||
| 14 | paths: Vec<PathBuf>, | ||
| 15 | } | ||
| 16 | |||
| 17 | /* | ||
| 18 | config/ | ||
| 19 | nvim/ | ||
| 20 | init.vim | ||
| 21 | lua/ | ||
| 22 | setup.lua | ||
| 23 | bash/ | ||
| 24 | .bashrc | ||
| 25 | |||
| 26 | link nvim .config/nvim | ||
| 27 | nvim/init.vim -> .config/nvim/init.vim | ||
| 28 | nvim/lua/setup.lua -> config/nvim/lua/setup.lua | ||
| 29 | |||
| 30 | link bash . | ||
| 31 | bash/.bashrc -> ./.bashrc | ||
| 32 | |||
| 33 | link --directory scripts .scripts | ||
| 34 | scripts/ -> ./.scripts | ||
| 35 | */ | ||
| 36 | |||
| 37 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 38 | let mut depot = utils::read_depot(&config.archive_path)?; | ||
| 39 | |||
| 40 | let (origins, destination) = match opts.paths.as_slice() { | ||
| 41 | p @ [] | p @ [_] => (p, None), | ||
| 42 | [o @ .., dest] => (o, Some(dest)), | ||
| 43 | _ => unreachable!(), | ||
| 44 | }; | ||
| 45 | |||
| 46 | if let Some(destination) = destination { | ||
| 47 | for path in collect_file_type(origins, FileType::File)? { | ||
| 48 | let link_desc = LinkDesc { | ||
| 49 | origin: path, | ||
| 50 | destination: destination.clone(), | ||
| 51 | }; | ||
| 52 | log::info!("Creating link : {}", link_desc); | ||
| 53 | depot.create_link(link_desc)?; | ||
| 54 | } | ||
| 55 | } else { | ||
| 56 | let base_path = match origins { | ||
| 57 | [] => std::env::current_dir()?, | ||
| 58 | [path] => path.clone(), | ||
| 59 | _ => unreachable!(), | ||
| 60 | }; | ||
| 61 | |||
| 62 | for link in utils::collect_links_by_base_paths(&depot, std::iter::once(base_path)) { | ||
| 63 | log::info!("{}", link); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | //if let Some(destination) = destination { | ||
| 68 | // for origin in origins { | ||
| 69 | // let origin_canonical = origin.canonicalize()?; | ||
| 70 | // let base = if origin_canonical.is_file() { | ||
| 71 | // origin_canonical.parent().unwrap().to_path_buf() | ||
| 72 | // } else { | ||
| 73 | // origin_canonical.to_path_buf() | ||
| 74 | // }; | ||
| 75 | |||
| 76 | // link(&mut depot, origin.as_path(), destination.as_path(), &base)?; | ||
| 77 | // } | ||
| 78 | //} else { | ||
| 79 | // log::warn!("Missing destination"); | ||
| 80 | //} | ||
| 81 | |||
| 82 | utils::write_depot(&depot)?; | ||
| 83 | |||
| 84 | Ok(()) | ||
| 85 | } | ||
| 86 | |||
| 87 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 88 | enum FileType { | ||
| 89 | File, | ||
| 90 | Directory, | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Collects canonical files of the given type starting from, and including, entry_paths | ||
| 94 | fn collect_file_type( | ||
| 95 | entry_paths: impl IntoIterator<Item = impl AsRef<Path>>, | ||
| 96 | collect_type: FileType, | ||
| 97 | ) -> anyhow::Result<Vec<PathBuf>> { | ||
| 98 | let entry_paths: Vec<PathBuf> = entry_paths | ||
| 99 | .into_iter() | ||
| 100 | .map(|p| p.as_ref().to_path_buf()) | ||
| 101 | .collect(); | ||
| 102 | let mut collected = Vec::new(); | ||
| 103 | let mut pending: Vec<_> = entry_paths.iter().cloned().filter(|p| p.is_dir()).collect(); | ||
| 104 | |||
| 105 | for path in entry_paths { | ||
| 106 | let path = path.canonicalize()?; | ||
| 107 | if (path.is_file() && collect_type == FileType::File) | ||
| 108 | || (path.is_dir() && collect_type == FileType::Directory) | ||
| 109 | { | ||
| 110 | collected.push(path); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | while let Some(dir_path) = pending.pop() { | ||
| 115 | for entry in dir_path.read_dir()? { | ||
| 116 | let entry = entry?; | ||
| 117 | let filetype = entry.file_type()?; | ||
| 118 | |||
| 119 | if filetype.is_file() && collect_type == FileType::File { | ||
| 120 | collected.push(entry.path()); | ||
| 121 | } else if filetype.is_dir() { | ||
| 122 | if collect_type == FileType::Directory { | ||
| 123 | collected.push(entry.path()); | ||
| 124 | } | ||
| 125 | pending.push(entry.path()); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | Ok(collected) | ||
| 131 | } | ||
| 132 | |||
| 133 | fn link(depot: &mut Depot, origin: &Path, destination: &Path, base: &Path) -> anyhow::Result<()> { | ||
| 134 | let metadata = std::fs::metadata(origin)?; | ||
| 135 | if metadata.is_file() { | ||
| 136 | link_file(depot, origin, destination, base)?; | ||
| 137 | } else if metadata.is_dir() { | ||
| 138 | link_directory_recursive(depot, origin, destination, base)?; | ||
| 139 | } else { | ||
| 140 | unimplemented!() | ||
| 141 | } | ||
| 142 | Ok(()) | ||
| 143 | } | ||
| 144 | |||
| 145 | fn link_file( | ||
| 146 | depot: &mut Depot, | ||
| 147 | origin: &Path, | ||
| 148 | destination: &Path, | ||
| 149 | base: &Path, | ||
| 150 | ) -> anyhow::Result<()> { | ||
| 151 | let origin_canonical = origin | ||
| 152 | .canonicalize() | ||
| 153 | .expect("Failed to canonicalize origin path"); | ||
| 154 | let partial = origin_canonical | ||
| 155 | .strip_prefix(base) | ||
| 156 | .expect("Failed to remove prefix from origin path"); | ||
| 157 | let destination = destination.join(partial); | ||
| 158 | |||
| 159 | let link_desc = LinkDesc { | ||
| 160 | origin: origin_canonical, | ||
| 161 | destination, | ||
| 162 | }; | ||
| 163 | |||
| 164 | log::debug!("Linking file {:#?}", link_desc); | ||
| 165 | depot.create_link(link_desc)?; | ||
| 166 | |||
| 167 | Ok(()) | ||
| 168 | } | ||
| 169 | |||
| 170 | fn link_directory_recursive( | ||
| 171 | depot: &mut Depot, | ||
| 172 | dir_path: &Path, | ||
| 173 | destination: &Path, | ||
| 174 | base: &Path, | ||
| 175 | ) -> anyhow::Result<()> { | ||
| 176 | for origin in dir_path.read_dir()? { | ||
| 177 | let origin = origin?.path(); | ||
| 178 | link(depot, &origin, destination, base)?; | ||
| 179 | } | ||
| 180 | Ok(()) | ||
| 181 | } | ||
diff --git a/dotup_cli/src/commands/mod.rs b/dotup_cli/src/commands/mod.rs new file mode 100644 index 0000000..f372662 --- /dev/null +++ b/dotup_cli/src/commands/mod.rs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | pub mod init; | ||
| 2 | pub mod install; | ||
| 3 | pub mod link; | ||
| 4 | pub mod uninstall; | ||
| 5 | pub mod unlink; | ||
| 6 | pub mod utils; | ||
| 7 | |||
| 8 | mod prelude { | ||
| 9 | pub use super::utils; | ||
| 10 | pub use crate::prelude::*; | ||
| 11 | } | ||
diff --git a/dotup_cli/src/commands/uninstall.rs b/dotup_cli/src/commands/uninstall.rs new file mode 100644 index 0000000..dad55a5 --- /dev/null +++ b/dotup_cli/src/commands/uninstall.rs | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | use clap::Clap; | ||
| 2 | use std::path::PathBuf; | ||
| 3 | |||
| 4 | use super::prelude::*; | ||
| 5 | |||
| 6 | #[derive(Clap)] | ||
| 7 | pub struct Opts { | ||
| 8 | /// The location where links will be uninstalled from. | ||
| 9 | /// Defaults to home directory. | ||
| 10 | #[clap(long)] | ||
| 11 | install_base: Option<PathBuf>, | ||
| 12 | |||
| 13 | /// The files/directories to uninstall | ||
| 14 | #[clap(required = true, min_values = 1)] | ||
| 15 | paths: Vec<PathBuf>, | ||
| 16 | } | ||
| 17 | |||
| 18 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 19 | let install_base = match opts.install_base { | ||
| 20 | Some(path) => path, | ||
| 21 | None => utils::home_directory()?, | ||
| 22 | }; | ||
| 23 | let depot = utils::read_depot(&config.archive_path)?; | ||
| 24 | |||
| 25 | for link in utils::collect_links_by_base_paths(&depot, &opts.paths) { | ||
| 26 | log::info!("Uninstalling link : {}", link); | ||
| 27 | depot.uninstall_link(link, &install_base)?; | ||
| 28 | } | ||
| 29 | |||
| 30 | Ok(()) | ||
| 31 | } | ||
diff --git a/dotup_cli/src/commands/unlink.rs b/dotup_cli/src/commands/unlink.rs new file mode 100644 index 0000000..7ebb19c --- /dev/null +++ b/dotup_cli/src/commands/unlink.rs | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | use clap::Clap; | ||
| 2 | use std::{ | ||
| 3 | fs::{DirEntry, Metadata}, | ||
| 4 | path::{Path, PathBuf}, | ||
| 5 | }; | ||
| 6 | |||
| 7 | use super::prelude::*; | ||
| 8 | |||
| 9 | #[derive(Clap)] | ||
| 10 | pub struct Opts { | ||
| 11 | /// Specifies the install base if the links are also to be uninstalled. | ||
| 12 | #[clap(long)] | ||
| 13 | uninstall: Option<PathBuf>, | ||
| 14 | |||
| 15 | #[clap(required = true, min_values = 1)] | ||
| 16 | paths: Vec<PathBuf>, | ||
| 17 | } | ||
| 18 | |||
| 19 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 20 | let mut depot = utils::read_depot(&config.archive_path)?; | ||
| 21 | |||
| 22 | for link_id in utils::collect_link_ids_by_base_paths(&depot, &opts.paths) { | ||
| 23 | let link = depot.get_link(link_id).unwrap(); | ||
| 24 | log::info!( | ||
| 25 | "Unlinking(uninstall = {}) : {}", | ||
| 26 | opts.uninstall.is_some(), | ||
| 27 | link | ||
| 28 | ); | ||
| 29 | if let Some(ref install_base) = opts.uninstall { | ||
| 30 | depot.uninstall_link(link, &install_base)?; | ||
| 31 | } | ||
| 32 | depot.remove_link(link_id); | ||
| 33 | } | ||
| 34 | |||
| 35 | utils::write_depot(&depot)?; | ||
| 36 | |||
| 37 | Ok(()) | ||
| 38 | } | ||
diff --git a/dotup_cli/src/commands/utils.rs b/dotup_cli/src/commands/utils.rs new file mode 100644 index 0000000..4160078 --- /dev/null +++ b/dotup_cli/src/commands/utils.rs | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | use std::path::{Path, PathBuf}; | ||
| 2 | |||
| 3 | use crate::prelude::*; | ||
| 4 | |||
| 5 | pub fn home_directory() -> anyhow::Result<PathBuf> { | ||
| 6 | match std::env::var("HOME") { | ||
| 7 | Ok(val) => Ok(PathBuf::from(val)), | ||
| 8 | Err(e) => { | ||
| 9 | log::error!("Failed to get home directory from enviornment variable"); | ||
| 10 | Err(e.into()) | ||
| 11 | } | ||
| 12 | } | ||
| 13 | } | ||
| 14 | |||
| 15 | pub fn write_archive(path: impl AsRef<Path>, archive: &Archive) -> anyhow::Result<()> { | ||
| 16 | let path = path.as_ref(); | ||
| 17 | log::debug!("Writing archive to {}", path.display()); | ||
| 18 | match dotup::archive_write(path, archive) { | ||
| 19 | Ok(_) => Ok(()), | ||
| 20 | Err(e) => { | ||
| 21 | log::error!( | ||
| 22 | "Failed to write archive to : {}\nError : {}", | ||
| 23 | path.display(), | ||
| 24 | e | ||
| 25 | ); | ||
| 26 | Err(e.into()) | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | pub fn write_depot(depot: &Depot) -> anyhow::Result<()> { | ||
| 32 | let write_path = depot.archive_path(); | ||
| 33 | let archive = depot.archive(); | ||
| 34 | match dotup::archive_write(write_path, &archive) { | ||
| 35 | Ok(_) => Ok(()), | ||
| 36 | Err(e) => { | ||
| 37 | log::error!( | ||
| 38 | "Failed to write depot archive to : {}\nError : {}", | ||
| 39 | write_path.display(), | ||
| 40 | e | ||
| 41 | ); | ||
| 42 | Err(e.into()) | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | pub fn read_archive(path: impl AsRef<Path>) -> anyhow::Result<Archive> { | ||
| 48 | let path = path.as_ref(); | ||
| 49 | match dotup::archive_read(path) { | ||
| 50 | Ok(archive) => Ok(archive), | ||
| 51 | Err(e) => { | ||
| 52 | log::error!( | ||
| 53 | "Failed to read archive from : {}\nError : {}", | ||
| 54 | path.display(), | ||
| 55 | e | ||
| 56 | ); | ||
| 57 | Err(e.into()) | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn read_depot(archive_path: impl AsRef<Path>) -> anyhow::Result<Depot> { | ||
| 63 | let archive_path = archive_path.as_ref().to_path_buf(); | ||
| 64 | let archive = read_archive(&archive_path)?; | ||
| 65 | let depot_config = DepotConfig { | ||
| 66 | archive_path, | ||
| 67 | archive, | ||
| 68 | }; | ||
| 69 | let depot = Depot::new(depot_config)?; | ||
| 70 | Ok(depot) | ||
| 71 | } | ||
| 72 | |||
| 73 | pub fn collect_links_by_base_paths( | ||
| 74 | depot: &Depot, | ||
| 75 | paths: impl IntoIterator<Item = impl AsRef<Path>>, | ||
| 76 | ) -> Vec<&Link> { | ||
| 77 | let canonical_paths: Vec<_> = paths | ||
| 78 | .into_iter() | ||
| 79 | .map(|p| p.as_ref().canonicalize().unwrap()) | ||
| 80 | .collect(); | ||
| 81 | |||
| 82 | depot | ||
| 83 | .links() | ||
| 84 | .filter(|&l| { | ||
| 85 | canonical_paths | ||
| 86 | .iter() | ||
| 87 | .any(|p| l.origin_canonical().starts_with(p)) | ||
| 88 | }) | ||
| 89 | .collect() | ||
| 90 | } | ||
| 91 | |||
| 92 | pub fn collect_link_ids_by_base_paths( | ||
| 93 | depot: &Depot, | ||
| 94 | paths: impl IntoIterator<Item = impl AsRef<Path>>, | ||
| 95 | ) -> Vec<LinkID> { | ||
| 96 | collect_links_by_base_paths(depot, paths) | ||
| 97 | .into_iter() | ||
| 98 | .map(|l| l.id()) | ||
| 99 | .collect() | ||
| 100 | } | ||
diff --git a/dotup_cli/src/config.rs b/dotup_cli/src/config.rs new file mode 100644 index 0000000..ac4fc66 --- /dev/null +++ b/dotup_cli/src/config.rs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | use std::path::PathBuf; | ||
| 2 | |||
| 3 | #[derive(Debug)] | ||
| 4 | pub struct Config { | ||
| 5 | pub archive_path: PathBuf, | ||
| 6 | } | ||
diff --git a/dotup_cli/src/main.rs b/dotup_cli/src/main.rs new file mode 100644 index 0000000..6d020e1 --- /dev/null +++ b/dotup_cli/src/main.rs | |||
| @@ -0,0 +1,87 @@ | |||
| 1 | #![allow(unused)] | ||
| 2 | |||
| 3 | pub mod commands; | ||
| 4 | pub mod config; | ||
| 5 | |||
| 6 | pub use config::Config; | ||
| 7 | |||
| 8 | pub mod prelude { | ||
| 9 | pub use super::Config; | ||
| 10 | pub use dotup::{Archive, Depot, DepotConfig, Link, LinkDesc, LinkID}; | ||
| 11 | } | ||
| 12 | |||
| 13 | use clap::{AppSettings, Clap}; | ||
| 14 | use flexi_logger::Logger; | ||
| 15 | use std::{ | ||
| 16 | collections::HashMap, | ||
| 17 | iter::FromIterator, | ||
| 18 | path::{Path, PathBuf}, | ||
| 19 | }; | ||
| 20 | |||
| 21 | use prelude::*; | ||
| 22 | |||
| 23 | const DEFAULT_DEPOT_NAME: &str = "depot.toml"; | ||
| 24 | |||
| 25 | #[derive(Clap)] | ||
| 26 | #[clap(version = "0.1", author = "d464")] | ||
| 27 | #[clap(setting = AppSettings::ColoredHelp)] | ||
| 28 | struct Opts { | ||
| 29 | /// Path to the depot file. | ||
| 30 | #[clap(long, default_value = DEFAULT_DEPOT_NAME)] | ||
| 31 | depot: PathBuf, | ||
| 32 | |||
| 33 | /// Disable output to the console | ||
| 34 | #[clap(short, long)] | ||
| 35 | quiet: bool, | ||
| 36 | |||
| 37 | /// A level of verbosity, and can be used multiple times | ||
| 38 | /// | ||
| 39 | /// Level 0 - Warnings (Default) | ||
| 40 | /// Level 1 - Info | ||
| 41 | /// Level 2 - Debug | ||
| 42 | /// Level 3 - Trace | ||
| 43 | #[clap(short, long, parse(from_occurrences))] | ||
| 44 | verbose: i32, | ||
| 45 | |||
| 46 | #[clap(subcommand)] | ||
| 47 | subcmd: SubCommand, | ||
| 48 | } | ||
| 49 | |||
| 50 | #[derive(Clap)] | ||
| 51 | enum SubCommand { | ||
| 52 | Init(commands::init::Opts), | ||
| 53 | Link(commands::link::Opts), | ||
| 54 | Unlink(commands::unlink::Opts), | ||
| 55 | Install(commands::install::Opts), | ||
| 56 | Uninstall(commands::uninstall::Opts), | ||
| 57 | } | ||
| 58 | |||
| 59 | fn main() -> anyhow::Result<()> { | ||
| 60 | let opts = Opts::parse(); | ||
| 61 | |||
| 62 | if !opts.quiet { | ||
| 63 | let log_level = match opts.verbose { | ||
| 64 | 0 => "warn", | ||
| 65 | 1 => "info", | ||
| 66 | 2 => "debug", | ||
| 67 | 3 | _ => "trace", | ||
| 68 | }; | ||
| 69 | |||
| 70 | Logger::try_with_env_or_str(log_level)? | ||
| 71 | .format(flexi_logger::colored_default_format) | ||
| 72 | //.set_palette("196;208;32;198;15".to_string()) | ||
| 73 | .start()?; | ||
| 74 | } | ||
| 75 | |||
| 76 | let config = Config { | ||
| 77 | archive_path: opts.depot, | ||
| 78 | }; | ||
| 79 | |||
| 80 | match opts.subcmd { | ||
| 81 | SubCommand::Init(opts) => commands::init::main(config, opts), | ||
| 82 | SubCommand::Link(opts) => commands::link::main(config, opts), | ||
| 83 | SubCommand::Unlink(opts) => commands::unlink::main(config, opts), | ||
| 84 | SubCommand::Install(opts) => commands::install::main(config, opts), | ||
| 85 | SubCommand::Uninstall(opts) => commands::uninstall::main(config, opts), | ||
| 86 | } | ||
| 87 | } | ||
