aboutsummaryrefslogtreecommitdiff
path: root/dotup_cli/src/commands
diff options
context:
space:
mode:
Diffstat (limited to 'dotup_cli/src/commands')
-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
7 files changed, 412 insertions, 0 deletions
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}