aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2021-07-08 17:11:46 -0400
committerdiogo464 <[email protected]>2021-07-08 17:11:46 -0400
commited0baec0a3f953c99445f6842dadc5566e89cb75 (patch)
tree9f988c41db34907283dd126dc57d29b3d0792bd9
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock458
-rw-r--r--Cargo.toml2
-rw-r--r--configs/depot.toml7
-rw-r--r--configs/neovim/init.vim0
-rw-r--r--configs/neovim/lua/setup.lua0
-rw-r--r--dotup/Cargo.toml11
-rw-r--r--dotup/src/archive.rs44
-rw-r--r--dotup/src/depot.rs371
-rw-r--r--dotup/src/error.rs30
-rw-r--r--dotup/src/lib.rs16
-rw-r--r--dotup/src/utils.rs96
-rw-r--r--dotup_cli/Cargo.toml20
-rw-r--r--dotup_cli/src/.main.rs.rustfmt87
-rw-r--r--dotup_cli/src/commands/init.rs20
-rw-r--r--dotup_cli/src/commands/install.rs31
-rw-r--r--dotup_cli/src/commands/link.rs181
-rw-r--r--dotup_cli/src/commands/mod.rs11
-rw-r--r--dotup_cli/src/commands/uninstall.rs31
-rw-r--r--dotup_cli/src/commands/unlink.rs38
-rw-r--r--dotup_cli/src/commands/utils.rs100
-rw-r--r--dotup_cli/src/config.rs6
-rw-r--r--dotup_cli/src/main.rs87
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.
3version = 3
4
5[[package]]
6name = "aho-corasick"
7version = "0.7.18"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
10dependencies = [
11 "memchr",
12]
13
14[[package]]
15name = "anyhow"
16version = "1.0.41"
17source = "registry+https://github.com/rust-lang/crates.io-index"
18checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
19
20[[package]]
21name = "atty"
22version = "0.2.14"
23source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
25dependencies = [
26 "hermit-abi",
27 "libc",
28 "winapi",
29]
30
31[[package]]
32name = "autocfg"
33version = "1.0.1"
34source = "registry+https://github.com/rust-lang/crates.io-index"
35checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
36
37[[package]]
38name = "bitflags"
39version = "1.2.1"
40source = "registry+https://github.com/rust-lang/crates.io-index"
41checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
42
43[[package]]
44name = "cfg-if"
45version = "1.0.0"
46source = "registry+https://github.com/rust-lang/crates.io-index"
47checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
48
49[[package]]
50name = "chrono"
51version = "0.4.19"
52source = "registry+https://github.com/rust-lang/crates.io-index"
53checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
54dependencies = [
55 "libc",
56 "num-integer",
57 "num-traits",
58 "time",
59 "winapi",
60]
61
62[[package]]
63name = "clap"
64version = "3.0.0-beta.2"
65source = "registry+https://github.com/rust-lang/crates.io-index"
66checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
67dependencies = [
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]]
82name = "clap_derive"
83version = "3.0.0-beta.2"
84source = "registry+https://github.com/rust-lang/crates.io-index"
85checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
86dependencies = [
87 "heck",
88 "proc-macro-error",
89 "proc-macro2",
90 "quote",
91 "syn",
92]
93
94[[package]]
95name = "dotup"
96version = "0.1.0"
97dependencies = [
98 "log",
99 "serde",
100 "slotmap",
101 "thiserror",
102 "toml",
103]
104
105[[package]]
106name = "dotup_cli"
107version = "0.1.0"
108dependencies = [
109 "anyhow",
110 "clap",
111 "dotup",
112 "flexi_logger",
113 "log",
114]
115
116[[package]]
117name = "flexi_logger"
118version = "0.18.0"
119source = "registry+https://github.com/rust-lang/crates.io-index"
120checksum = "8ba2265890613939b533fa11c3728651531419ac549ccf527896201581f23991"
121dependencies = [
122 "atty",
123 "chrono",
124 "glob",
125 "lazy_static",
126 "log",
127 "regex",
128 "thiserror",
129 "yansi",
130]
131
132[[package]]
133name = "glob"
134version = "0.3.0"
135source = "registry+https://github.com/rust-lang/crates.io-index"
136checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
137
138[[package]]
139name = "hashbrown"
140version = "0.11.2"
141source = "registry+https://github.com/rust-lang/crates.io-index"
142checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
143
144[[package]]
145name = "heck"
146version = "0.3.3"
147source = "registry+https://github.com/rust-lang/crates.io-index"
148checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
149dependencies = [
150 "unicode-segmentation",
151]
152
153[[package]]
154name = "hermit-abi"
155version = "0.1.19"
156source = "registry+https://github.com/rust-lang/crates.io-index"
157checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
158dependencies = [
159 "libc",
160]
161
162[[package]]
163name = "indexmap"
164version = "1.7.0"
165source = "registry+https://github.com/rust-lang/crates.io-index"
166checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
167dependencies = [
168 "autocfg",
169 "hashbrown",
170]
171
172[[package]]
173name = "lazy_static"
174version = "1.4.0"
175source = "registry+https://github.com/rust-lang/crates.io-index"
176checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
177
178[[package]]
179name = "libc"
180version = "0.2.97"
181source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
183
184[[package]]
185name = "log"
186version = "0.4.14"
187source = "registry+https://github.com/rust-lang/crates.io-index"
188checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
189dependencies = [
190 "cfg-if",
191]
192
193[[package]]
194name = "memchr"
195version = "2.4.0"
196source = "registry+https://github.com/rust-lang/crates.io-index"
197checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
198
199[[package]]
200name = "num-integer"
201version = "0.1.44"
202source = "registry+https://github.com/rust-lang/crates.io-index"
203checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
204dependencies = [
205 "autocfg",
206 "num-traits",
207]
208
209[[package]]
210name = "num-traits"
211version = "0.2.14"
212source = "registry+https://github.com/rust-lang/crates.io-index"
213checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
214dependencies = [
215 "autocfg",
216]
217
218[[package]]
219name = "os_str_bytes"
220version = "2.4.0"
221source = "registry+https://github.com/rust-lang/crates.io-index"
222checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
223
224[[package]]
225name = "proc-macro-error"
226version = "1.0.4"
227source = "registry+https://github.com/rust-lang/crates.io-index"
228checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
229dependencies = [
230 "proc-macro-error-attr",
231 "proc-macro2",
232 "quote",
233 "syn",
234 "version_check",
235]
236
237[[package]]
238name = "proc-macro-error-attr"
239version = "1.0.4"
240source = "registry+https://github.com/rust-lang/crates.io-index"
241checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
242dependencies = [
243 "proc-macro2",
244 "quote",
245 "version_check",
246]
247
248[[package]]
249name = "proc-macro2"
250version = "1.0.27"
251source = "registry+https://github.com/rust-lang/crates.io-index"
252checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
253dependencies = [
254 "unicode-xid",
255]
256
257[[package]]
258name = "quote"
259version = "1.0.9"
260source = "registry+https://github.com/rust-lang/crates.io-index"
261checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
262dependencies = [
263 "proc-macro2",
264]
265
266[[package]]
267name = "regex"
268version = "1.5.4"
269source = "registry+https://github.com/rust-lang/crates.io-index"
270checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
271dependencies = [
272 "aho-corasick",
273 "memchr",
274 "regex-syntax",
275]
276
277[[package]]
278name = "regex-syntax"
279version = "0.6.25"
280source = "registry+https://github.com/rust-lang/crates.io-index"
281checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
282
283[[package]]
284name = "serde"
285version = "1.0.126"
286source = "registry+https://github.com/rust-lang/crates.io-index"
287checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
288dependencies = [
289 "serde_derive",
290]
291
292[[package]]
293name = "serde_derive"
294version = "1.0.126"
295source = "registry+https://github.com/rust-lang/crates.io-index"
296checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
297dependencies = [
298 "proc-macro2",
299 "quote",
300 "syn",
301]
302
303[[package]]
304name = "slotmap"
305version = "1.0.5"
306source = "registry+https://github.com/rust-lang/crates.io-index"
307checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4"
308dependencies = [
309 "version_check",
310]
311
312[[package]]
313name = "strsim"
314version = "0.10.0"
315source = "registry+https://github.com/rust-lang/crates.io-index"
316checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
317
318[[package]]
319name = "syn"
320version = "1.0.73"
321source = "registry+https://github.com/rust-lang/crates.io-index"
322checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
323dependencies = [
324 "proc-macro2",
325 "quote",
326 "unicode-xid",
327]
328
329[[package]]
330name = "termcolor"
331version = "1.1.2"
332source = "registry+https://github.com/rust-lang/crates.io-index"
333checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
334dependencies = [
335 "winapi-util",
336]
337
338[[package]]
339name = "textwrap"
340version = "0.12.1"
341source = "registry+https://github.com/rust-lang/crates.io-index"
342checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
343dependencies = [
344 "unicode-width",
345]
346
347[[package]]
348name = "thiserror"
349version = "1.0.26"
350source = "registry+https://github.com/rust-lang/crates.io-index"
351checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
352dependencies = [
353 "thiserror-impl",
354]
355
356[[package]]
357name = "thiserror-impl"
358version = "1.0.26"
359source = "registry+https://github.com/rust-lang/crates.io-index"
360checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
361dependencies = [
362 "proc-macro2",
363 "quote",
364 "syn",
365]
366
367[[package]]
368name = "time"
369version = "0.1.44"
370source = "registry+https://github.com/rust-lang/crates.io-index"
371checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
372dependencies = [
373 "libc",
374 "wasi",
375 "winapi",
376]
377
378[[package]]
379name = "toml"
380version = "0.5.8"
381source = "registry+https://github.com/rust-lang/crates.io-index"
382checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
383dependencies = [
384 "serde",
385]
386
387[[package]]
388name = "unicode-segmentation"
389version = "1.8.0"
390source = "registry+https://github.com/rust-lang/crates.io-index"
391checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
392
393[[package]]
394name = "unicode-width"
395version = "0.1.8"
396source = "registry+https://github.com/rust-lang/crates.io-index"
397checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
398
399[[package]]
400name = "unicode-xid"
401version = "0.2.2"
402source = "registry+https://github.com/rust-lang/crates.io-index"
403checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
404
405[[package]]
406name = "vec_map"
407version = "0.8.2"
408source = "registry+https://github.com/rust-lang/crates.io-index"
409checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
410
411[[package]]
412name = "version_check"
413version = "0.9.3"
414source = "registry+https://github.com/rust-lang/crates.io-index"
415checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
416
417[[package]]
418name = "wasi"
419version = "0.10.0+wasi-snapshot-preview1"
420source = "registry+https://github.com/rust-lang/crates.io-index"
421checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
422
423[[package]]
424name = "winapi"
425version = "0.3.9"
426source = "registry+https://github.com/rust-lang/crates.io-index"
427checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
428dependencies = [
429 "winapi-i686-pc-windows-gnu",
430 "winapi-x86_64-pc-windows-gnu",
431]
432
433[[package]]
434name = "winapi-i686-pc-windows-gnu"
435version = "0.4.0"
436source = "registry+https://github.com/rust-lang/crates.io-index"
437checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
438
439[[package]]
440name = "winapi-util"
441version = "0.1.5"
442source = "registry+https://github.com/rust-lang/crates.io-index"
443checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
444dependencies = [
445 "winapi",
446]
447
448[[package]]
449name = "winapi-x86_64-pc-windows-gnu"
450version = "0.4.0"
451source = "registry+https://github.com/rust-lang/crates.io-index"
452checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
453
454[[package]]
455name = "yansi"
456version = "0.5.0"
457source = "registry+https://github.com/rust-lang/crates.io-index"
458checksum = "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]
2members = ["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]]
2origin = 'neovim/init.vim'
3destination = '.config/nvim/init.vim'
4
5[[links]]
6origin = 'neovim/lua/setup.lua'
7destination = '.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]
2edition = "2018"
3name = "dotup"
4version = "0.1.0"
5
6[dependencies]
7log = "*"
8serde = { version = "*", features = ["derive"] }
9thiserror = "*"
10toml = "*"
11slotmap = "*"
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 @@
1use serde::{Deserialize, Serialize};
2use std::path::{Path, PathBuf};
3
4use crate::internal_prelude::*;
5
6#[derive(Debug, Default, Serialize, Deserialize)]
7pub struct ArchiveLink {
8 pub origin: PathBuf,
9 pub destination: PathBuf,
10}
11
12#[derive(Debug, Default, Serialize, Deserialize)]
13pub struct Archive {
14 pub links: Vec<ArchiveLink>,
15}
16
17pub fn archive_exists(path: impl AsRef<Path>) -> bool {
18 utils::is_file(path).unwrap_or_default()
19}
20
21pub fn archive_read(path: impl AsRef<Path>) -> Result<Archive> {
22 let contents = std::fs::read_to_string(path)?;
23 archive_deserialize(contents)
24}
25
26pub 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
32pub 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
39pub 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 @@
1use slotmap::SlotMap;
2use std::{
3 fs::Metadata,
4 path::{Path, PathBuf},
5 sync::Arc,
6};
7use thiserror::Error;
8
9use crate::{internal_prelude::*, Archive, ArchiveLink};
10
11#[derive(Debug)]
12pub 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
20slotmap::new_key_type! { pub struct LinkID; }
21
22#[derive(Debug)]
23pub struct LinkDesc {
24 pub origin: PathBuf,
25 /// This must be a relative path
26 pub destination: PathBuf,
27}
28
29impl 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
38impl 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
49impl 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)]
59pub 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
72impl 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
100impl 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)]
112struct DepotShared {
113 /// Must be canonical path
114 base_path: PathBuf,
115 /// Must be canonical path
116 archive_path: PathBuf,
117}
118
119#[derive(Debug)]
120pub struct Depot {
121 shared: Arc<DepotShared>,
122 // Maps the origin to the link
123 links: SlotMap<LinkID, Link>,
124}
125
126impl 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
177fn 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
209fn 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.
222fn 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
262fn depot_get_link(depot: &Depot, link_id: LinkID) -> Option<&Link> {
263 depot.links.get(link_id)
264}
265
266fn depot_remove_link(depot: &mut Depot, link_id: LinkID) {
267 depot.links.remove(link_id);
268}
269
270#[derive(Debug, Error)]
271pub 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
281fn 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
332fn 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
348fn 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
355fn depot_links(depot: &Depot) -> impl Iterator<Item = &Link> {
356 depot.links.values()
357}
358
359fn 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
366fn 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 @@
1use crate::LinkInstallError;
2use std::path::PathBuf;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub 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
30pub 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 @@
1mod archive;
2mod depot;
3mod error;
4
5pub mod utils;
6
7pub use archive::{
8 archive_deserialize, archive_exists, archive_read, archive_serialize, archive_write, Archive,
9 ArchiveLink,
10};
11pub use depot::{Depot, DepotConfig, Link, LinkDesc, LinkID, LinkInstallError};
12pub use error::{Error, Result};
13
14pub(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 @@
1use std::path::{Component, Path, PathBuf};
2
3use crate::internal_prelude::*;
4
5pub 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
16pub 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
27pub fn is_canonical(path: &Path) -> Result<bool> {
28 Ok(path == path.canonicalize()?.as_path())
29}
30
31pub 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
36fn 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)]
69mod 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]
2edition = "2018"
3name = "dotup_cli"
4version = "0.1.0"
5
6[[bin]]
7name = "dotup"
8path = "src/main.rs"
9
10[dependencies]
11anyhow = "*"
12clap = "3.0.0-beta.2"
13log = "*"
14
15[dependencies.dotup]
16path = "../dotup"
17
18[dependencies.flexi_logger]
19features = ["colors"]
20version = "*"
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
3pub mod commands;
4pub mod config;
5
6pub use config::Config;
7
8pub mod prelude {
9 pub use super::Config;
10 pub use dotup::{Archive, Depot, DepotConfig, Link, LinkDesc, LinkID};
11}
12
13use clap::{AppSettings, Clap};
14use flexi_logger::Logger;
15use std::{
16 collections::HashMap,
17 iter::FromIterator,
18 path::{Path, PathBuf},
19};
20
21use prelude::*;
22
23const DEFAULT_DEPOT_NAME: &str = "depot.toml";
24
25#[derive(Clap)]
26#[clap(version = "0.1", author = "d464")]
27#[clap(setting = AppSettings::ColoredHelp)]
28struct 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)]
51enum 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
59fn 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 @@
1use clap::Clap;
2
3use super::prelude::*;
4
5#[derive(Clap)]
6pub struct Opts {}
7
8pub 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 @@
1use clap::Clap;
2use std::path::PathBuf;
3
4use super::prelude::*;
5
6#[derive(Clap)]
7pub 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
18pub 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 @@
1use clap::Clap;
2use std::{
3 fs::{DirEntry, Metadata},
4 path::{Path, PathBuf},
5};
6
7use super::prelude::*;
8
9#[derive(Clap)]
10pub 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
37pub 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)]
88enum FileType {
89 File,
90 Directory,
91}
92
93/// Collects canonical files of the given type starting from, and including, entry_paths
94fn 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
133fn 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
145fn 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
170fn 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 @@
1pub mod init;
2pub mod install;
3pub mod link;
4pub mod uninstall;
5pub mod unlink;
6pub mod utils;
7
8mod 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 @@
1use clap::Clap;
2use std::path::PathBuf;
3
4use super::prelude::*;
5
6#[derive(Clap)]
7pub 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
18pub 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 @@
1use clap::Clap;
2use std::{
3 fs::{DirEntry, Metadata},
4 path::{Path, PathBuf},
5};
6
7use super::prelude::*;
8
9#[derive(Clap)]
10pub 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
19pub 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 @@
1use std::path::{Path, PathBuf};
2
3use crate::prelude::*;
4
5pub 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
15pub 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
31pub 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
47pub 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
62pub 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
73pub 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
92pub 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 @@
1use std::path::PathBuf;
2
3#[derive(Debug)]
4pub 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
3pub mod commands;
4pub mod config;
5
6pub use config::Config;
7
8pub mod prelude {
9 pub use super::Config;
10 pub use dotup::{Archive, Depot, DepotConfig, Link, LinkDesc, LinkID};
11}
12
13use clap::{AppSettings, Clap};
14use flexi_logger::Logger;
15use std::{
16 collections::HashMap,
17 iter::FromIterator,
18 path::{Path, PathBuf},
19};
20
21use prelude::*;
22
23const DEFAULT_DEPOT_NAME: &str = "depot.toml";
24
25#[derive(Clap)]
26#[clap(version = "0.1", author = "d464")]
27#[clap(setting = AppSettings::ColoredHelp)]
28struct 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)]
51enum 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
59fn 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}