aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs914
1 files changed, 900 insertions, 14 deletions
diff --git a/src/main.rs b/src/main.rs
index f623450..9b17787 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,847 @@
2 2
3// TODO: rewrite all errors so they start with lower case 3// TODO: rewrite all errors so they start with lower case
4 4
5pub mod depot { 5mod depot {
6 use std::{
7 collections::HashSet,
8 ffi::{OsStr, OsString},
9 ops::Index,
10 path::{Path, PathBuf},
11 };
12 use thiserror::Error;
13
14 use slotmap::{Key, SlotMap};
15
16 //pub type Result<T, E = DepotError> = std::result::Result<T, E>;
17 pub use anyhow::Result;
18 pub use disk::{read, write};
19
20 slotmap::new_key_type! {pub struct LinkID;}
21 slotmap::new_key_type! {struct NodeID;}
22
23 #[derive(Debug, Error)]
24 enum DepotError {
25 #[error("path must be relative")]
26 InvalidPath,
27 #[error("path must be relative and not empty")]
28 InvalidLinkPath,
29 #[error("{0}")]
30 LinkCreateError(#[from] LinkCreateError),
31 }
32
33 #[derive(Debug, Error)]
34 enum LinkCreateError {
35 #[error("an ancestor of this path is already linked")]
36 AncestorAlreadyLinked,
37 }
38
39 #[derive(Debug, Clone)]
40 struct Node {
41 comp: OsString,
42 parent: NodeID,
43 kind: NodeKind,
44 }
45
46 #[derive(Debug, Clone)]
47 enum NodeKind {
48 Link(LinkID),
49 Directory(HashSet<NodeID>),
50 }
51
52 #[derive(Debug, Clone, PartialEq, Eq)]
53 pub enum NodeSearchResult {
54 Found(NodeID),
55 /// the closest NodeID up the the search point.
56 NotFound(NodeID),
57 }
58
59 #[derive(Debug, Clone, PartialEq, Eq)]
60 pub enum DirNode {
61 Link(LinkID),
62 Directory(PathBuf),
63 }
64
65 #[derive(Debug, Clone, PartialEq, Eq)]
66 pub enum SearchResult {
67 Found(LinkID),
68 Ancestor(LinkID),
69 NotFound,
70 }
71
72 #[derive(Debug, Clone)]
73 struct Link {
74 origin: PathBuf,
75 destination: PathBuf,
76 origin_id: NodeID,
77 }
78
79 #[derive(Debug)]
80 pub struct LinkView<'a> {
81 link_id: LinkID,
82 depot: &'a Depot,
83 }
84
85 impl<'a> LinkView<'a> {
86 pub fn origin(&self) -> &Path {
87 &self.depot.links[self.link_id].origin
88 }
89
90 pub fn destination(&self) -> &Path {
91 &self.depot.links[self.link_id].destination
92 }
93 }
94
95 #[derive(Debug, Clone)]
96 struct DepotTree {
97 root: NodeID,
98 nodes: SlotMap<NodeID, Node>,
99 }
100
101 impl Default for DepotTree {
102 fn default() -> Self {
103 let mut nodes = SlotMap::<NodeID, Node>::default();
104 let root = nodes.insert(Node {
105 comp: Default::default(),
106 parent: Default::default(),
107 kind: NodeKind::Directory(Default::default()),
108 });
109 Self { root, nodes }
110 }
111 }
112
113 impl Index<NodeID> for DepotTree {
114 type Output = Node;
115
116 fn index(&self, index: NodeID) -> &Self::Output {
117 self.nodes.index(index)
118 }
119 }
120
121 impl DepotTree {
122 /// create a node of kind [`NodeKind::Link`].
123 pub fn link_create(&mut self, path: &Path, link_id: LinkID) -> Result<NodeID> {
124 debug_assert!(path_verify_link(path).is_ok());
125
126 let path_search_result = self.search(path);
127
128 // handle the error cases
129 match path_search_result {
130 NodeSearchResult::Found(node_id) => {
131 let node = &self.nodes[node_id];
132 match &node.kind {
133 NodeKind::Link(_) => Err(anyhow::anyhow!("link already exists")),
134 NodeKind::Directory(_) => {
135 Err(anyhow::anyhow!("path already has links under it"))
136 }
137 }
138 }
139 NodeSearchResult::NotFound(ancestor_node_id) => {
140 let ancestor_node = &self.nodes[ancestor_node_id];
141 match &ancestor_node.kind {
142 NodeKind::Link(_) => Err(anyhow::anyhow!(
143 "an ancestor of this path is already linked"
144 )),
145 NodeKind::Directory(_) => Ok(()),
146 }
147 }
148 }?;
149
150 // create the node
151 // unwrap: this is a verfied link path, it must have atleast one component
152 let filename = path.file_name().unwrap();
153 let parent_path = path_parent_or_empty(path);
154 let node_id = self.nodes.insert(Node {
155 comp: filename.to_owned(),
156 parent: Default::default(),
157 kind: NodeKind::Link(link_id),
158 });
159 let parent_id = self.directory_get_or_create(parent_path, node_id);
160 self.nodes[node_id].parent = parent_id;
161 Ok(node_id)
162 }
163
164 pub fn link_update_id(&mut self, node_id: NodeID, link_id: LinkID) {
165 let node = &mut self.nodes[node_id];
166 match &mut node.kind {
167 NodeKind::Link(lid) => *lid = link_id,
168 NodeKind::Directory(_) => unreachable!(),
169 }
170 }
171
172 /// attempts to moves a node of kind [`NodeKind::Link`] to `destination`.
173 pub fn link_move(&mut self, node_id: NodeID, destination: &Path) -> Result<()> {
174 let parent_id = self.nodes[node_id].parent;
175 let parent = &mut self.nodes[parent_id];
176
177 // remove the node from its parent temporarily so that the search never returns this
178 // link and that way any link will find means an error.
179 // if an error does happen then we re-add this node to its parent to keep the data
180 // consistent.
181 match &mut parent.kind {
182 NodeKind::Link(_) => unreachable!(),
183 NodeKind::Directory(children) => children.remove(&node_id),
184 };
185
186 let search_result = self.search(destination);
187 // handle the error cases
188 match search_result {
189 NodeSearchResult::Found(found_id) => {
190 assert!(found_id != node_id);
191 self.directory_add_child(parent_id, node_id);
192 return Err(anyhow::anyhow!("link already exists at that path"));
193 }
194 NodeSearchResult::NotFound(ancestor_id) => {
195 let ancestor = &self.nodes[ancestor_id];
196 match &ancestor.kind {
197 NodeKind::Link(_) => {
198 self.directory_add_child(parent_id, node_id);
199 return Err(anyhow::anyhow!("ancestor path is already linked"));
200 }
201 NodeKind::Directory(_) => {}
202 }
203 }
204 };
205
206 let destination_parent = path_parent_or_empty(destination);
207 let new_parent_id = self.directory_get_or_create(destination_parent, node_id);
208 if new_parent_id != parent_id {
209 self.nodes[node_id].parent = new_parent_id;
210
211 // we have to re-add and call the remove function because it could lead to the removal
212 // of several directories if they become empty after this remove.
213 self.directory_add_child(parent_id, node_id);
214 self.directory_remove_child(parent_id, node_id);
215 }
216
217 // unwrap: destination is a verified link path so it has atleast 1 component
218 let comp = destination.file_name().unwrap();
219 let node = &mut self.nodes[node_id];
220 if node.comp != comp {
221 node.comp = comp.to_owned();
222 }
223
224 Ok(())
225 }
226
227 pub fn link_search(&self, path: &Path) -> SearchResult {
228 match self.search(path) {
229 NodeSearchResult::Found(node_id) => match &self.nodes[node_id].kind {
230 NodeKind::Link(link_id) => SearchResult::Found(*link_id),
231 NodeKind::Directory(_) => SearchResult::NotFound,
232 },
233 NodeSearchResult::NotFound(node_id) => match &self.nodes[node_id].kind {
234 NodeKind::Link(link_id) => SearchResult::Ancestor(*link_id),
235 NodeKind::Directory(_) => SearchResult::NotFound,
236 },
237 }
238 }
239
240 /// remove a node of kind [`NodeKind::Link`].
241 pub fn link_remove(&mut self, node_id: NodeID) {
242 let node = &self.nodes[node_id];
243 assert!(std::matches!(node.kind, NodeKind::Link(_)));
244 let parent_id = node.parent;
245 self.nodes.remove(node_id);
246 self.directory_remove_child(parent_id, node_id);
247 }
248
249 pub fn links_under(&self, path: &Path) -> impl Iterator<Item = LinkID> + '_ {
250 let links = match self.search(path) {
251 NodeSearchResult::Found(node_id) => {
252 let node = &self.nodes[node_id];
253 match &node.kind {
254 NodeKind::Link(link_id) => vec![*link_id],
255 NodeKind::Directory(children) => {
256 let mut links = Vec::new();
257 let mut node_ids = Vec::from_iter(children.iter().copied());
258 while let Some(child_id) = node_ids.pop() {
259 let child = &self.nodes[child_id];
260 match &child.kind {
261 NodeKind::Link(link_id) => links.push(*link_id),
262 NodeKind::Directory(extra_children) => {
263 node_ids.extend(extra_children.iter().copied())
264 }
265 }
266 }
267 links
268 }
269 }
270 }
271 NodeSearchResult::NotFound(_) => vec![],
272 };
273 links.into_iter()
274 }
275
276 pub fn has_links_under(&self, path: &Path) -> bool {
277 // it does not matter what type of node is found. if a directory exists then there
278 // must be atleast one link under it.
279 match self.search(path) {
280 NodeSearchResult::Found(_) => true,
281 NodeSearchResult::NotFound(_) => false,
282 }
283 }
284
285 pub fn read_dir(&self, path: &Path) -> Result<impl Iterator<Item = DirNode> + '_> {
286 match self.search(path) {
287 NodeSearchResult::Found(node_id) => match &self.nodes[node_id].kind {
288 NodeKind::Link(_) => Err(anyhow::anyhow!("read dir called on a link")),
289 NodeKind::Directory(children) => Ok(children.iter().map(|child_id| {
290 let child = &self.nodes[*child_id];
291 match &child.kind {
292 NodeKind::Link(link_id) => DirNode::Link(*link_id),
293 NodeKind::Directory(_) => {
294 DirNode::Directory(self.build_path(*child_id))
295 }
296 }
297 })),
298 },
299 NodeSearchResult::NotFound(_) => Err(anyhow::anyhow!("directory not found")),
300 }
301 }
302
303 pub fn build_path(&self, node_id: NodeID) -> PathBuf {
304 fn recursive_helper(nodes: &SlotMap<NodeID, Node>, nid: NodeID, pbuf: &mut PathBuf) {
305 if nid.is_null() {
306 return;
307 }
308 let parent_id = nodes[nid].parent;
309 recursive_helper(nodes, parent_id, pbuf);
310 pbuf.push(&nodes[nid].comp);
311 }
312
313 let mut node_path = PathBuf::default();
314 recursive_helper(&self.nodes, node_id, &mut node_path);
315 node_path
316 }
317
318 fn search(&self, path: &Path) -> NodeSearchResult {
319 debug_assert!(path_verify(path).is_ok());
320
321 let mut curr_node_id = self.root;
322 let mut comp_iter = path_iter_comps(path).peekable();
323 while let Some(comp) = comp_iter.next() {
324 if let Some(child_id) = self.directory_search_children(curr_node_id, comp) {
325 let child = &self.nodes[child_id];
326 match &child.kind {
327 NodeKind::Link(_) => {
328 if comp_iter.peek().is_some() {
329 return NodeSearchResult::NotFound(child_id);
330 } else {
331 return NodeSearchResult::Found(child_id);
332 }
333 }
334 NodeKind::Directory(_) => curr_node_id = child_id,
335 }
336 } else {
337 return NodeSearchResult::NotFound(curr_node_id);
338 }
339 }
340 NodeSearchResult::Found(curr_node_id)
341 }
342
343 // creates directories all the way up to and including path.
344 // there cannot be any links up to `path`.
345 fn directory_get_or_create(&mut self, path: &Path, initial_child: NodeID) -> NodeID {
346 // TODO: this could be replaced if the search function also returned the depth of the
347 // node and we skip those components and just start creating directories up to the
348 // path.
349 let mut curr_node_id = self.root;
350 for comp in path_iter_comps(path) {
351 if let Some(child_id) = self.directory_search_children(curr_node_id, comp) {
352 debug_assert!(std::matches!(
353 self.nodes[child_id].kind,
354 NodeKind::Directory(_)
355 ));
356 curr_node_id = child_id;
357 } else {
358 let new_node_id = self.nodes.insert(Node {
359 comp: comp.to_owned(),
360 parent: curr_node_id,
361 kind: NodeKind::Directory(Default::default()),
362 });
363 self.directory_add_child(curr_node_id, new_node_id);
364 curr_node_id = new_node_id;
365 }
366 }
367 self.directory_add_child(curr_node_id, initial_child);
368 curr_node_id
369 }
370
371 fn directory_search_children(&self, node_id: NodeID, comp: &OsStr) -> Option<NodeID> {
372 let node = &self.nodes[node_id];
373 match &node.kind {
374 NodeKind::Link(_) => unreachable!(),
375 NodeKind::Directory(children) => {
376 for &child_id in children {
377 let child = &self.nodes[child_id];
378 if child.comp == comp {
379 return Some(child_id);
380 }
381 }
382 }
383 }
384 None
385 }
386
387 fn directory_add_child(&mut self, node_id: NodeID, child_id: NodeID) {
388 let node = &mut self.nodes[node_id];
389 match &mut node.kind {
390 NodeKind::Link(_) => unreachable!(),
391 NodeKind::Directory(children) => children.insert(child_id),
392 };
393 }
394
395 fn directory_remove_child(&mut self, node_id: NodeID, child_id: NodeID) {
396 let node = &mut self.nodes[node_id];
397 match &mut node.kind {
398 NodeKind::Link(_) => unreachable!(),
399 NodeKind::Directory(children) => {
400 children.remove(&child_id);
401 if children.is_empty() && !node.parent.is_null() {
402 let parent_id = node.parent;
403 self.directory_remove_child(parent_id, node_id);
404 }
405 }
406 }
407 }
408 }
409
410 #[derive(Debug, Default, Clone)]
411 pub struct Depot {
412 links: SlotMap<LinkID, Link>,
413 origin: DepotTree,
414 }
415
416 impl Depot {
417 pub fn link_create(
418 &mut self,
419 origin: impl AsRef<Path>,
420 destination: impl AsRef<Path>,
421 ) -> Result<LinkID> {
422 let origin = origin.as_ref();
423 let destination = destination.as_ref();
424 path_verify_link(origin)?;
425 path_verify_link(destination)?;
426 self.link_create_unchecked(origin, destination)
427 }
428
429 pub fn link_remove(&mut self, link_id: LinkID) {
430 let node_id = self.links[link_id].origin_id;
431 self.links.remove(link_id);
432 self.origin.link_remove(node_id);
433 }
434
435 /// moves the link specified by `link_id` to the path at `destination`.
436 /// if the link is already at the destination nothing is done.
437 /// if the destination is another link that that link is removed.
438 /// if the destination is under another link then an error is returned.
439 /// `destination` will be the link's new origin.
440 pub fn link_move(&mut self, link_id: LinkID, destination: impl AsRef<Path>) -> Result<()> {
441 let destination = destination.as_ref();
442 path_verify_link(destination)?;
443 self.link_move_unchecked(link_id, destination)
444 }
445
446 pub fn link_search(&self, path: impl AsRef<Path>) -> Result<SearchResult> {
447 let path = path.as_ref();
448 path_verify(path)?;
449 Ok(self.link_search_unchecked(path))
450 }
451
452 pub fn link_find(&self, path: impl AsRef<Path>) -> Result<Option<LinkID>> {
453 let path = path.as_ref();
454 path_verify(path)?;
455 Ok(self.link_find_unchecked(path))
456 }
457
458 pub fn links_under(
459 &self,
460 path: impl AsRef<Path>,
461 ) -> Result<impl Iterator<Item = LinkID> + '_> {
462 let path = path.as_ref();
463 path_verify(path)?;
464 Ok(self.links_under_unchecked(path))
465 }
466
467 pub fn has_links_under(&self, path: impl AsRef<Path>) -> Result<bool> {
468 let path = path.as_ref();
469 path_verify(path)?;
470 Ok(self.has_links_under_unchecked(path))
471 }
472
473 pub fn link_view(&self, link_id: LinkID) -> LinkView {
474 LinkView {
475 link_id,
476 depot: self,
477 }
478 }
479
480 pub fn read_dir(
481 &self,
482 path: impl AsRef<Path>,
483 ) -> Result<impl Iterator<Item = DirNode> + '_> {
484 let path = path.as_ref();
485 path_verify(path)?;
486 self.read_dir_unchecked(path)
487 }
488
489 fn link_create_unchecked(&mut self, origin: &Path, destination: &Path) -> Result<LinkID> {
490 let node_id = self.origin.link_create(origin, Default::default())?;
491 let link_id = self.links.insert(Link {
492 origin: origin.to_owned(),
493 destination: destination.to_owned(),
494 origin_id: node_id,
495 });
496 self.origin.link_update_id(node_id, link_id);
497 Ok(link_id)
498 }
499
500 fn link_move_unchecked(&mut self, link_id: LinkID, destination: &Path) -> Result<()> {
501 let link = &self.links[link_id];
502 if link.origin == destination {
503 return Ok(());
504 }
505 let node_id = link.origin_id;
506 self.origin.link_move(node_id, destination)?;
507 self.links[link_id].origin = destination.to_owned();
508 Ok(())
509 }
510
511 fn link_search_unchecked(&self, path: &Path) -> SearchResult {
512 self.origin.link_search(path)
513 }
514
515 fn link_find_unchecked(&self, path: &Path) -> Option<LinkID> {
516 match self.link_search_unchecked(path) {
517 SearchResult::Found(link_id) => Some(link_id),
518 _ => None,
519 }
520 }
521
522 fn links_under_unchecked(&self, path: &Path) -> impl Iterator<Item = LinkID> + '_ {
523 self.origin.links_under(path)
524 }
525
526 fn has_links_under_unchecked(&self, path: &Path) -> bool {
527 self.origin.has_links_under(path)
528 }
529
530 fn read_dir_unchecked(&self, path: &Path) -> Result<impl Iterator<Item = DirNode> + '_> {
531 self.origin.read_dir(path)
532 }
533 }
534
535 /// a verified link path is a path that:
536 /// + is not empty
537 /// + is relative
538 /// + does not contain Prefix/RootDir/ParentDir
539 fn path_verify_link(path: &Path) -> Result<()> {
540 // make sure the path is not empty
541 if path.components().next().is_none() {
542 return Err(DepotError::InvalidLinkPath.into());
543 }
544 path_verify(path).map_err(|_| DepotError::InvalidLinkPath.into())
545 }
546
547 /// a verified path is a path that:
548 /// + is not empty
549 /// + is relative
550 /// + does not contain Prefix/RootDir/ParentDir
551 fn path_verify(path: &Path) -> Result<()> {
552 // make sure the path is relative
553 // make sure the path does not contain '.' or '..'
554 for component in path.components() {
555 match component {
556 std::path::Component::Prefix(_)
557 | std::path::Component::RootDir
558 | std::path::Component::CurDir
559 | std::path::Component::ParentDir => return Err(DepotError::InvalidPath.into()),
560 std::path::Component::Normal(_) => {}
561 }
562 }
563 Ok(())
564 }
565
566 fn path_parent_or_empty(path: &Path) -> &Path {
567 path.parent().unwrap_or(Path::new(""))
568 }
569
570 /// Iterate over the components of a path.
571 /// # Pre
572 /// The path can only have "Normal" components.
573 fn path_iter_comps(path: &Path) -> impl Iterator<Item = &OsStr> {
574 debug_assert!(path_verify(path).is_ok());
575 path.components().map(|component| match component {
576 std::path::Component::Normal(comp) => comp,
577 _ => unreachable!(),
578 })
579 }
580
581 mod disk {
582 use std::path::{Path, PathBuf};
583
584 use anyhow::Context;
585 use serde::{Deserialize, Serialize};
586
587 use super::Depot;
588
589 #[derive(Debug, Serialize, Deserialize)]
590 struct DiskLink {
591 origin: PathBuf,
592 destination: PathBuf,
593 }
594
595 #[derive(Debug, Serialize, Deserialize)]
596 struct DiskLinks {
597 links: Vec<DiskLink>,
598 }
599
600 pub fn read(path: &Path) -> anyhow::Result<Depot> {
601 let contents = std::fs::read_to_string(path).context("Failed to read depot file")?;
602 let disk_links = toml::from_str::<DiskLinks>(&contents)
603 .context("Failed to parse depot file")?
604 .links;
605 let mut depot = Depot::default();
606 for disk_link in disk_links {
607 depot
608 .link_create(disk_link.origin, disk_link.destination)
609 .context("Failed to build depot from file. File is in an invalid state")?;
610 }
611 Ok(depot)
612 }
613
614 pub fn write(path: &Path, depot: &Depot) -> anyhow::Result<()> {
615 let mut links = Vec::with_capacity(depot.links.len());
616 for (_, link) in depot.links.iter() {
617 links.push(DiskLink {
618 origin: link.origin.clone(),
619 destination: link.destination.clone(),
620 });
621 }
622 let contents = toml::to_string_pretty(&DiskLinks { links })
623 .context("Failed to serialize depot")?;
624 std::fs::write(path, contents).context("Failed to write depot to file")?;
625 Ok(())
626 }
627 }
628
629 #[cfg(test)]
630 mod tests {
631 use super::*;
632
633 #[test]
634 fn test_depot_link_create() {
635 let mut depot = Depot::default();
636 let f1 = depot.link_create("f1", "f1").unwrap();
637 let f2 = depot.link_create("f2", "f2").unwrap();
638 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
639 let f4 = depot.link_create("d1/d2/f4", "d1/d2/d4").unwrap();
640
641 assert_eq!(depot.link_find("f1").unwrap(), Some(f1));
642 assert_eq!(depot.link_find("f2").unwrap(), Some(f2));
643 assert_eq!(depot.link_find("d1/f3").unwrap(), Some(f3));
644 assert_eq!(depot.link_find("d1/d2/f4").unwrap(), Some(f4));
645
646 depot.link_create("f2", "").unwrap_err();
647 depot.link_create("", "d4").unwrap_err();
648 depot.link_create("f1/f3", "f3").unwrap_err();
649 }
650
651 #[test]
652 fn test_depot_link_remove() {
653 let mut depot = Depot::default();
654 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
655 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
656 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
657 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
658 let d3 = depot.link_create("d3", "d3").unwrap();
659
660 depot.link_remove(f2);
661 assert_eq!(depot.link_find("d1/f1").unwrap(), Some(f1));
662 assert_eq!(depot.link_find("d1/f2").unwrap(), None);
663 depot.link_remove(f4);
664 assert_eq!(depot.link_find("d1/d2/f4").unwrap(), None);
665 depot.link_remove(d3);
666 assert_eq!(depot.link_find("d3").unwrap(), None);
667 }
668
669 #[test]
670 fn test_depot_link_move() {
671 let mut depot = Depot::default();
672 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
673 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
674
675 depot.link_move(f1, "").unwrap_err();
676 depot.link_move(f1, "d1/f2/f1").unwrap_err();
677 depot.link_move(f1, "d1/f2").unwrap_err();
678
679 depot.link_move(f1, "f1").unwrap();
680 assert_eq!(depot.link_view(f1).origin(), Path::new("f1"));
681 depot.link_move(f1, "f2").unwrap();
682 assert_eq!(depot.link_view(f1).origin(), Path::new("f2"));
683 assert_eq!(depot.link_find("f2").unwrap(), Some(f1));
684 }
685
686 #[test]
687 fn test_depot_link_search() {
688 let mut depot = Depot::default();
689 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
690 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
691 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
692 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
693 let _d3 = depot.link_create("d3", "d3").unwrap();
694
695 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::Found(f1));
696 assert_eq!(
697 depot.link_search("d1/f1/f5").unwrap(),
698 SearchResult::Ancestor(f1)
699 );
700 assert_eq!(depot.link_search("d1").unwrap(), SearchResult::NotFound);
701 assert_eq!(
702 depot.link_search("d1/d2/f5").unwrap(),
703 SearchResult::NotFound
704 );
705 }
706
707 #[test]
708 fn test_depot_link_find() {
709 let mut depot = Depot::default();
710 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
711 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
712 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
713 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
714 let d3 = depot.link_create("d3", "d3").unwrap();
715
716 assert_eq!(depot.link_find("d1/f1").unwrap(), Some(f1));
717 assert_eq!(depot.link_find("d1/f3").unwrap(), Some(f3));
718 assert_eq!(depot.link_find("d1/d2/f4").unwrap(), Some(f4));
719 assert_eq!(depot.link_find("d3").unwrap(), Some(d3));
720
721 assert_eq!(depot.link_find("d5").unwrap(), None);
722 assert_eq!(depot.link_find("d3/d5").unwrap(), None);
723 assert_eq!(depot.link_find("d1/d2/f5").unwrap(), None);
724 }
725
726 #[test]
727 fn test_depot_links_under() {
728 let mut depot = Depot::default();
729 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
730 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
731 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
732 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
733 let d3 = depot.link_create("d3", "d3").unwrap();
734
735 let under_f1 = depot.links_under("d1/f1").unwrap().collect::<Vec<_>>();
736 assert_eq!(under_f1, vec![f1]);
737
738 let under_d1 = depot.links_under("d1").unwrap().collect::<Vec<_>>();
739 let expected_under_d1 = vec![f1, f2, f3, f4];
740 assert!(
741 under_d1.len() == expected_under_d1.len()
742 && expected_under_d1.iter().all(|x| under_d1.contains(x))
743 );
744
745 let under_d2 = depot.links_under("d2").unwrap().collect::<Vec<_>>();
746 assert_eq!(under_d2, vec![]);
747
748 let under_d3 = depot.links_under("d3").unwrap().collect::<Vec<_>>();
749 assert_eq!(under_d3, vec![d3]);
750
751 let under_root = depot.links_under("").unwrap().collect::<Vec<_>>();
752 let expected_under_root = vec![f1, f2, f3, f4, d3];
753 assert!(
754 under_root.len() == expected_under_root.len()
755 && expected_under_root.iter().all(|x| under_root.contains(x))
756 );
757 }
758
759 #[test]
760 fn test_depot_has_links_under() {
761 let mut depot = Depot::default();
762 let _f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
763 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
764 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
765 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
766 let _d3 = depot.link_create("d3", "d3").unwrap();
767
768 assert!(depot.has_links_under("").unwrap());
769 assert!(depot.has_links_under("d1").unwrap());
770 assert!(depot.has_links_under("d3").unwrap());
771 assert!(depot.has_links_under("d1/f1").unwrap());
772 assert!(depot.has_links_under("d1/d2").unwrap());
773 assert!(depot.has_links_under("d1/d2/f4").unwrap());
774
775 assert!(!depot.has_links_under("d2").unwrap());
776 assert!(!depot.has_links_under("d4").unwrap());
777 assert!(!depot.has_links_under("d1/d2/f4/f5").unwrap());
778 }
779
780 #[test]
781 fn test_depot_read_dir() {
782 let mut depot = Depot::default();
783 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
784 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
785 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
786 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
787 let _d3 = depot.link_create("d3", "d3").unwrap();
788
789 let read_dir = depot.read_dir("d1").unwrap().collect::<Vec<_>>();
790 let expected_read_dir = vec![
791 DirNode::Link(f1),
792 DirNode::Link(f2),
793 DirNode::Link(f3),
794 DirNode::Directory(PathBuf::from("d1/d2")),
795 ];
796 assert!(
797 read_dir.len() == expected_read_dir.len()
798 && expected_read_dir.iter().all(|x| read_dir.contains(x))
799 );
800 }
801
802 #[test]
803 fn test_path_verify() {
804 path_verify(Path::new("")).unwrap();
805 path_verify(Path::new("f1")).unwrap();
806 path_verify(Path::new("d1/f1")).unwrap();
807 path_verify(Path::new("d1/f1.txt")).unwrap();
808 path_verify(Path::new("d1/./f1.txt")).unwrap();
809
810 path_verify(Path::new("/")).unwrap_err();
811 path_verify(Path::new("./f1")).unwrap_err();
812 path_verify(Path::new("/d1/f1")).unwrap_err();
813 path_verify(Path::new("d1/../f1.txt")).unwrap_err();
814 path_verify(Path::new("/d1/../f1.txt")).unwrap_err();
815 }
816
817 #[test]
818 fn test_path_verify_link() {
819 path_verify_link(Path::new("f1")).unwrap();
820 path_verify_link(Path::new("d1/f1")).unwrap();
821 path_verify_link(Path::new("d1/f1.txt")).unwrap();
822 path_verify_link(Path::new("d1/./f1.txt")).unwrap();
823
824 path_verify_link(Path::new("")).unwrap_err();
825 path_verify_link(Path::new("/")).unwrap_err();
826 path_verify_link(Path::new("./f1")).unwrap_err();
827 path_verify_link(Path::new("/d1/f1")).unwrap_err();
828 path_verify_link(Path::new("d1/../f1.txt")).unwrap_err();
829 path_verify_link(Path::new("/d1/../f1.txt")).unwrap_err();
830 }
831
832 #[test]
833 fn test_path_iter_comps() {
834 let path = Path::new("comp1/comp2/./comp3/file.txt");
835 let mut iter = path_iter_comps(path);
836 assert_eq!(iter.next(), Some(OsStr::new("comp1")));
837 assert_eq!(iter.next(), Some(OsStr::new("comp2")));
838 assert_eq!(iter.next(), Some(OsStr::new("comp3")));
839 assert_eq!(iter.next(), Some(OsStr::new("file.txt")));
840 assert_eq!(iter.next(), None);
841 }
842 }
843}
844
845pub mod depot2 {
6 use std::{ 846 use std::{
7 collections::HashSet, 847 collections::HashSet,
8 ffi::{OsStr, OsString}, 848 ffi::{OsStr, OsString},
@@ -933,19 +1773,16 @@ pub mod dotup {
933 } 1773 }
934 } 1774 }
935 1775
936 pub fn unlink(&mut self, paths: impl Iterator<Item = impl AsRef<Path>>) { 1776 pub fn unlink(&mut self, paths: impl Iterator<Item = impl AsRef<Path>>, uninstall: bool) {
937 for origin in paths { 1777 for origin in paths {
938 let unlink_result: anyhow::Result<()> = try { 1778 let unlink_result: anyhow::Result<()> = try {
939 let origin = self.prepare_relative_path(origin.as_ref())?; 1779 let origin = self.prepare_relative_path(origin.as_ref())?;
940 let search_results = self.depot.link_search(&origin)?; 1780 let links_under: Vec<_> = self.depot.links_under(&origin)?.collect();
941 match search_results { 1781 for link_id in links_under {
942 depot::SearchResult::Found(link_id) => { 1782 if uninstall && self.symlink_is_installed_by_link_id(link_id)? {
943 self.depot.link_remove(link_id); 1783 self.symlink_uninstall_by_link_id(link_id)?;
944 println!("removed link {}", origin.display());
945 }
946 depot::SearchResult::Ancestor(_) | depot::SearchResult::NotFound => {
947 println!("{} is not linked", origin.display())
948 } 1784 }
1785 self.depot.link_remove(link_id);
949 } 1786 }
950 }; 1787 };
951 match unlink_result { 1788 match unlink_result {
@@ -1149,7 +1986,7 @@ pub mod dotup {
1149 } else if is_installed { 1986 } else if is_installed {
1150 Color::Green 1987 Color::Green
1151 } else { 1988 } else {
1152 Color::Cyan 1989 Color::RGB(255, 127, 0)
1153 } 1990 }
1154 } 1991 }
1155 1992
@@ -1301,6 +2138,11 @@ pub mod dotup {
1301 Ok(()) 2138 Ok(())
1302 } 2139 }
1303 2140
2141 fn symlink_uninstall_by_link_id(&self, link_id: LinkID) -> anyhow::Result<()> {
2142 let canonical_pair = self.canonical_pair_from_link_id(link_id);
2143 self.symlink_uninstall(&canonical_pair.origin, &canonical_pair.destination)
2144 }
2145
1304 fn canonical_pair_from_link_id(&self, link_id: LinkID) -> CanonicalPair { 2146 fn canonical_pair_from_link_id(&self, link_id: LinkID) -> CanonicalPair {
1305 let link_view = self.depot.link_view(link_id); 2147 let link_view = self.depot.link_view(link_id);
1306 let relative_origin = link_view.origin(); 2148 let relative_origin = link_view.origin();
@@ -1336,7 +2178,10 @@ pub mod dotup {
1336} 2178}
1337 2179
1338mod utils { 2180mod utils {
1339 use std::path::{Component, Path, PathBuf}; 2181 use std::{
2182 collections::VecDeque,
2183 path::{Component, Path, PathBuf},
2184 };
1340 2185
1341 use crate::{ 2186 use crate::{
1342 dotup::{self, Dotup}, 2187 dotup::{self, Dotup},
@@ -1345,6 +2190,29 @@ mod utils {
1345 2190
1346 pub const DEFAULT_DEPOT_FILE_NAME: &str = ".depot"; 2191 pub const DEFAULT_DEPOT_FILE_NAME: &str = ".depot";
1347 2192
2193 /// Returns a list of canonical paths to all the files in `dir`. This includes files in
2194 /// subdirectories.
2195 /// Fails if dir isnt a directory or if there is some other io error.
2196 pub fn collect_files_in_dir_recursive(dir: impl Into<PathBuf>) -> anyhow::Result<Vec<PathBuf>> {
2197 let mut paths = Vec::new();
2198 let mut dirs = VecDeque::new();
2199 dirs.push_back(dir.into());
2200
2201 while let Some(dir) = dirs.pop_front() {
2202 for entry in std::fs::read_dir(dir)? {
2203 let entry = entry?;
2204 let filetype = entry.file_type()?;
2205 if filetype.is_dir() {
2206 dirs.push_back(entry.path());
2207 } else {
2208 paths.push(entry.path());
2209 }
2210 }
2211 }
2212
2213 Ok(paths)
2214 }
2215
1348 pub fn collect_paths_in_dir(dir: impl AsRef<Path>) -> anyhow::Result<Vec<PathBuf>> { 2216 pub fn collect_paths_in_dir(dir: impl AsRef<Path>) -> anyhow::Result<Vec<PathBuf>> {
1349 Ok(std::fs::read_dir(dir)? 2217 Ok(std::fs::read_dir(dir)?
1350 .filter_map(|e| e.ok()) 2218 .filter_map(|e| e.ok())
@@ -1522,6 +2390,7 @@ fn main() -> anyhow::Result<()> {
1522 2 => "debug", 2390 2 => "debug",
1523 _ => "trace", 2391 _ => "trace",
1524 }; 2392 };
2393 let log_level = "trace";
1525 2394
1526 Logger::try_with_env_or_str(log_level)? 2395 Logger::try_with_env_or_str(log_level)?
1527 .format(flexi_logger::colored_default_format) 2396 .format(flexi_logger::colored_default_format)
@@ -1565,31 +2434,48 @@ fn command_init(_global_flags: Flags, args: InitArgs) -> anyhow::Result<()> {
1565 2434
1566#[derive(Parser, Debug)] 2435#[derive(Parser, Debug)]
1567struct LinkArgs { 2436struct LinkArgs {
2437 #[clap(long)]
2438 directory: bool,
2439
1568 origin: PathBuf, 2440 origin: PathBuf,
2441
1569 destination: PathBuf, 2442 destination: PathBuf,
1570} 2443}
1571 2444
1572fn command_link(global_flags: Flags, args: LinkArgs) -> anyhow::Result<()> { 2445fn command_link(global_flags: Flags, args: LinkArgs) -> anyhow::Result<()> {
1573 let mut dotup = utils::read_dotup(&global_flags)?; 2446 let mut dotup = utils::read_dotup(&global_flags)?;
1574 dotup.link(args.origin, args.destination); 2447 let origins = if args.directory {
2448 vec![args.origin]
2449 } else {
2450 utils::collect_files_in_dir_recursive(args.origin)?
2451 };
2452 for origin in origins {
2453 dotup.link(origin, &args.destination);
2454 }
1575 utils::write_dotup(&dotup)?; 2455 utils::write_dotup(&dotup)?;
1576 Ok(()) 2456 Ok(())
1577} 2457}
1578 2458
1579#[derive(Parser, Debug)] 2459#[derive(Parser, Debug)]
1580struct UnlinkArgs { 2460struct UnlinkArgs {
2461 #[clap(long)]
2462 uninstall: bool,
2463
1581 paths: Vec<PathBuf>, 2464 paths: Vec<PathBuf>,
1582} 2465}
1583 2466
1584fn command_unlink(global_flags: Flags, args: UnlinkArgs) -> anyhow::Result<()> { 2467fn command_unlink(global_flags: Flags, args: UnlinkArgs) -> anyhow::Result<()> {
1585 let mut dotup = utils::read_dotup(&global_flags)?; 2468 let mut dotup = utils::read_dotup(&global_flags)?;
1586 dotup.unlink(args.paths.into_iter()); 2469 dotup.unlink(args.paths.into_iter(), args.uninstall);
1587 utils::write_dotup(&dotup)?; 2470 utils::write_dotup(&dotup)?;
1588 Ok(()) 2471 Ok(())
1589} 2472}
1590 2473
1591#[derive(Parser, Debug)] 2474#[derive(Parser, Debug)]
1592struct InstallArgs { 2475struct InstallArgs {
2476 #[clap(long)]
2477 directory: bool,
2478
1593 paths: Vec<PathBuf>, 2479 paths: Vec<PathBuf>,
1594} 2480}
1595 2481