aboutsummaryrefslogtreecommitdiff
path: root/dotup_cli/src/commands/link.rs
diff options
context:
space:
mode:
Diffstat (limited to 'dotup_cli/src/commands/link.rs')
-rw-r--r--dotup_cli/src/commands/link.rs181
1 files changed, 181 insertions, 0 deletions
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}