aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2022-02-07 14:35:11 +0000
committerdiogo464 <[email protected]>2022-02-07 14:35:11 +0000
commitcbb2edb0b523f2494fd543857195792a8eda1b62 (patch)
tree600fd0a9eda4c01a64d5d3aa0ae9fc6d62e63b70 /src
parenta2117085b26557a27e8068c6caa4037e2f9f1a7f (diff)
snapshot
Diffstat (limited to 'src')
-rw-r--r--src/main.rs572
1 files changed, 459 insertions, 113 deletions
diff --git a/src/main.rs b/src/main.rs
index 1559ede..1cb68d4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -24,6 +24,12 @@ pub mod depot {
24 NotFound, 24 NotFound,
25 } 25 }
26 26
27 #[derive(Debug, Clone, PartialEq, Eq)]
28 pub enum DirNode {
29 Link(LinkID),
30 Directory(PathBuf),
31 }
32
27 #[derive(Debug)] 33 #[derive(Debug)]
28 pub struct LinkView<'a> { 34 pub struct LinkView<'a> {
29 link_id: LinkID, 35 link_id: LinkID,
@@ -96,15 +102,15 @@ pub mod depot {
96 } 102 }
97 103
98 impl Depot { 104 impl Depot {
99 pub fn create( 105 pub fn link_create(
100 &mut self, 106 &mut self,
101 origin: impl AsRef<Path>, 107 origin: impl AsRef<Path>,
102 destination: impl AsRef<Path>, 108 destination: impl AsRef<Path>,
103 ) -> anyhow::Result<LinkID> { 109 ) -> anyhow::Result<LinkID> {
104 let origin = origin.as_ref(); 110 let origin = origin.as_ref();
105 let destination = destination.as_ref(); 111 let destination = destination.as_ref();
106 verify_path(origin)?; 112 verify_link_path(origin)?;
107 verify_path(destination)?; 113 verify_link_path(destination)?;
108 114
109 // example 115 // example
110 // origin = fish/config.fish 116 // origin = fish/config.fish
@@ -141,13 +147,13 @@ pub mod depot {
141 /// if the link is already at the destination nothing is done. 147 /// if the link is already at the destination nothing is done.
142 /// if the destination is another link that that link is removed. 148 /// if the destination is another link that that link is removed.
143 /// if the destination is under another link then an error is returned. 149 /// if the destination is under another link then an error is returned.
144 pub fn move_link( 150 pub fn link_move(
145 &mut self, 151 &mut self,
146 link_id: LinkID, 152 link_id: LinkID,
147 destination: impl AsRef<Path>, 153 destination: impl AsRef<Path>,
148 ) -> anyhow::Result<()> { 154 ) -> anyhow::Result<()> {
149 let destination = destination.as_ref(); 155 let destination = destination.as_ref();
150 verify_path(destination)?; 156 verify_link_path(destination)?;
151 157
152 let link_node_id = self.links[link_id].node_id; 158 let link_node_id = self.links[link_id].node_id;
153 let link_parent_node_id = self.nodes[link_node_id].parent; 159 let link_parent_node_id = self.nodes[link_node_id].parent;
@@ -168,7 +174,7 @@ pub mod depot {
168 self.node_child_remove(link_parent_node_id, link_node_id); 174 self.node_child_remove(link_parent_node_id, link_node_id);
169 self.node_child_add(node_parent_id, link_node_id); 175 self.node_child_add(node_parent_id, link_node_id);
170 self.node_set_parent(link_node_id, node_parent_id); 176 self.node_set_parent(link_node_id, node_parent_id);
171 self.remove(node_link_id); 177 self.link_remove(node_link_id);
172 let new_origin = self.node_build_path(link_node_id); 178 let new_origin = self.node_build_path(link_node_id);
173 self.links[link_id].origin = new_origin; 179 self.links[link_id].origin = new_origin;
174 Ok(()) 180 Ok(())
@@ -195,7 +201,7 @@ pub mod depot {
195 } 201 }
196 } 202 }
197 203
198 pub fn remove(&mut self, link_id: LinkID) { 204 pub fn link_remove(&mut self, link_id: LinkID) {
199 let node_id = self.links[link_id].node_id; 205 let node_id = self.links[link_id].node_id;
200 self.node_remove(node_id); 206 self.node_remove(node_id);
201 self.links.remove(link_id); 207 self.links.remove(link_id);
@@ -212,15 +218,18 @@ pub mod depot {
212 /// returns SearchResult::Found(..) if there is a link at `origin`. 218 /// returns SearchResult::Found(..) if there is a link at `origin`.
213 /// returns SearchResult::Ancestor(..) if an ancestor of `origin` is linked. 219 /// returns SearchResult::Ancestor(..) if an ancestor of `origin` is linked.
214 /// returns SearchResult::NotFound otherwise. 220 /// returns SearchResult::NotFound otherwise.
215 pub fn search(&self, origin: impl AsRef<Path>) -> anyhow::Result<SearchResult> { 221 pub fn link_search(&self, origin: impl AsRef<Path>) -> anyhow::Result<SearchResult> {
216 let origin = origin.as_ref(); 222 let origin = origin.as_ref();
217 verify_path(origin)?; 223 verify_path(origin)?;
224 if origin.components().next().is_none() {
225 return Ok(SearchResult::NotFound);
226 }
218 Ok(self.search_unchecked(&origin)) 227 Ok(self.search_unchecked(&origin))
219 } 228 }
220 229
221 /// finds the link at origin. 230 /// finds the link at origin.
222 pub fn find(&self, origin: impl AsRef<Path>) -> anyhow::Result<Option<LinkID>> { 231 pub fn link_find(&self, origin: impl AsRef<Path>) -> anyhow::Result<Option<LinkID>> {
223 match self.search(origin)? { 232 match self.link_search(origin)? {
224 SearchResult::Found(link_id) => Ok(Some(link_id)), 233 SearchResult::Found(link_id) => Ok(Some(link_id)),
225 SearchResult::Ancestor(_) | SearchResult::NotFound => Ok(None), 234 SearchResult::Ancestor(_) | SearchResult::NotFound => Ok(None),
226 } 235 }
@@ -248,17 +257,55 @@ pub mod depot {
248 Ok(link_ids.into_iter()) 257 Ok(link_ids.into_iter())
249 } 258 }
250 259
260 pub fn has_links_under(&self, path: impl AsRef<Path>) -> anyhow::Result<bool> {
261 let path = path.as_ref();
262 verify_path(path)?;
263
264 match self.node_find(path) {
265 Some(node_id) => match &self.nodes[node_id].kind {
266 NodeKind::Link(_) => Ok(true),
267 NodeKind::Directory(children) => Ok(!children.is_empty()),
268 },
269 None => Ok(false),
270 }
271 }
272
251 /// returns true if the `path` is a link or contains an ancestor that is linked. 273 /// returns true if the `path` is a link or contains an ancestor that is linked.
252 /// returns false otherwise. 274 /// returns false otherwise.
253 pub fn is_linked(&self, path: impl AsRef<Path>) -> bool { 275 pub fn link_exists(&self, path: impl AsRef<Path>) -> bool {
254 match self.search(path) { 276 match self.link_search(path) {
255 Ok(SearchResult::Found(..)) | Ok(SearchResult::Ancestor(..)) => true, 277 Ok(SearchResult::Found(..)) | Ok(SearchResult::Ancestor(..)) => true,
256 _ => false, 278 _ => false,
257 } 279 }
258 } 280 }
259 281
282 pub fn read_dir(
283 &self,
284 path: impl AsRef<Path>,
285 ) -> anyhow::Result<impl Iterator<Item = DirNode> + '_> {
286 let path = path.as_ref();
287 verify_path(path)?;
288
289 let node_id = match self.node_find(path) {
290 Some(node_id) => node_id,
291 None => return Err(anyhow::anyhow!("Directory does not exist")),
292 };
293 let node = &self.nodes[node_id];
294 let children = match &node.kind {
295 NodeKind::Link(_) => return Err(anyhow::anyhow!("Path is not a directory")),
296 NodeKind::Directory(children) => children,
297 };
298 Ok(children.iter().map(|id| {
299 let node = &self.nodes[*id];
300 match &node.kind {
301 NodeKind::Link(link_id) => DirNode::Link(*link_id),
302 NodeKind::Directory(_) => DirNode::Directory(self.node_build_path(*id)),
303 }
304 }))
305 }
306
260 fn search_unchecked(&self, origin: &Path) -> SearchResult { 307 fn search_unchecked(&self, origin: &Path) -> SearchResult {
261 debug_assert!(verify_path(origin).is_ok()); 308 debug_assert!(verify_link_path(origin).is_ok());
262 309
263 let mut origin_comps = iter_path_comps(&origin); 310 let mut origin_comps = iter_path_comps(&origin);
264 let mut curr_node = self.root; 311 let mut curr_node = self.root;
@@ -300,12 +347,12 @@ pub mod depot {
300 } 347 }
301 348
302 /// all the nodes up to the node to be created have to be directory nodes. 349 /// all the nodes up to the node to be created have to be directory nodes.
303 /// `path` must be a verified path. 350 /// `path` must be a verified link path.
304 fn node_create_link(&mut self, path: &Path, link_id: LinkID) -> NodeID { 351 fn node_create_link(&mut self, path: &Path, link_id: LinkID) -> NodeID {
305 assert!(verify_path(path).is_ok()); 352 assert!(verify_link_path(path).is_ok());
306 let mut curr_node_id = self.root; 353 let mut curr_node_id = self.root;
307 let mut path_comps = iter_path_comps(path).peekable(); 354 let mut path_comps = iter_path_comps(path).peekable();
308 // unwrap: a verified path has atleast 1 component 355 // unwrap: a verified link path has atleast 1 component
309 let mut curr_path_comp = path_comps.next().unwrap(); 356 let mut curr_path_comp = path_comps.next().unwrap();
310 357
311 while path_comps.peek().is_some() { 358 while path_comps.peek().is_some() {
@@ -343,6 +390,11 @@ pub mod depot {
343 390
344 let mut origin_comps = iter_path_comps(&path).peekable(); 391 let mut origin_comps = iter_path_comps(&path).peekable();
345 let mut curr_node = self.root; 392 let mut curr_node = self.root;
393
394 if origin_comps.peek().is_none() {
395 return (self.root, true);
396 }
397
346 'outer: loop { 398 'outer: loop {
347 let node = &self.nodes[curr_node]; 399 let node = &self.nodes[curr_node];
348 match origin_comps.next() { 400 match origin_comps.next() {
@@ -475,7 +527,7 @@ pub mod depot {
475 let mut depot = Depot::default(); 527 let mut depot = Depot::default();
476 for disk_link in disk_links { 528 for disk_link in disk_links {
477 depot 529 depot
478 .create(disk_link.origin, disk_link.destination) 530 .link_create(disk_link.origin, disk_link.destination)
479 .context("Failed to build depot from file. File is in an invalid state")?; 531 .context("Failed to build depot from file. File is in an invalid state")?;
480 } 532 }
481 Ok(depot) 533 Ok(depot)
@@ -500,13 +552,20 @@ pub mod depot {
500 /// + is not empty 552 /// + is not empty
501 /// + is relative 553 /// + is relative
502 /// + does not contain Prefix/RootDir/ParentDir 554 /// + does not contain Prefix/RootDir/ParentDir
503 fn verify_path(path: &Path) -> anyhow::Result<()> { 555 fn verify_link_path(path: &Path) -> anyhow::Result<()> {
504 // make sure the path is not empty 556 // make sure the path is not empty
505 // make sure the path is relative
506 // make sure the path does not contain '.' or '..'
507 if path.components().next().is_none() { 557 if path.components().next().is_none() {
508 return Err(anyhow::anyhow!("Path cannot be empty")); 558 return Err(anyhow::anyhow!("Path cannot be empty"));
509 } 559 }
560 verify_path(path)
561 }
562 /// a verified path is a path that:
563 /// + is not empty
564 /// + is relative
565 /// + does not contain Prefix/RootDir/ParentDir
566 fn verify_path(path: &Path) -> anyhow::Result<()> {
567 // make sure the path is relative
568 // make sure the path does not contain '.' or '..'
510 for component in path.components() { 569 for component in path.components() {
511 match component { 570 match component {
512 std::path::Component::Prefix(_) => { 571 std::path::Component::Prefix(_) => {
@@ -540,63 +599,180 @@ pub mod depot {
540 use super::*; 599 use super::*;
541 600
542 #[test] 601 #[test]
543 fn test_depot_create() { 602 fn test_verify_path() {
603 verify_path(Path::new("")).unwrap();
604 verify_path(Path::new("f1")).unwrap();
605 verify_path(Path::new("d1/f1")).unwrap();
606 verify_path(Path::new("d1/f1.txt")).unwrap();
607 verify_path(Path::new("d1/./f1.txt")).unwrap();
608
609 verify_path(Path::new("/")).unwrap_err();
610 verify_path(Path::new("./f1")).unwrap_err();
611 verify_path(Path::new("/d1/f1")).unwrap_err();
612 verify_path(Path::new("d1/../f1.txt")).unwrap_err();
613 verify_path(Path::new("/d1/../f1.txt")).unwrap_err();
614 }
615
616 #[test]
617 fn test_verify_link_path() {
618 verify_link_path(Path::new("f1")).unwrap();
619 verify_link_path(Path::new("d1/f1")).unwrap();
620 verify_link_path(Path::new("d1/f1.txt")).unwrap();
621 verify_link_path(Path::new("d1/./f1.txt")).unwrap();
622
623 verify_link_path(Path::new("")).unwrap_err();
624 verify_link_path(Path::new("/")).unwrap_err();
625 verify_link_path(Path::new("./f1")).unwrap_err();
626 verify_link_path(Path::new("/d1/f1")).unwrap_err();
627 verify_link_path(Path::new("d1/../f1.txt")).unwrap_err();
628 verify_link_path(Path::new("/d1/../f1.txt")).unwrap_err();
629 }
630
631 #[test]
632 fn test_depot_link_create() {
544 let mut depot = Depot::default(); 633 let mut depot = Depot::default();
545 depot.create("", "dest1.txt").unwrap_err(); 634 depot.link_create("", "dest1.txt").unwrap_err();
546 depot.create("comp1.txt", "").unwrap_err(); 635 depot.link_create("comp1.txt", "").unwrap_err();
547 depot.create("", "").unwrap_err(); 636 depot.link_create("", "").unwrap_err();
548 637
549 depot.create("comp1.txt", "dest1.txt").unwrap(); 638 depot.link_create("comp1.txt", "dest1.txt").unwrap();
550 depot.create("comp1.txt", "dest1_updated.txt").unwrap(); 639 depot.link_create("comp1.txt", "dest1_updated.txt").unwrap();
640 depot
641 .link_create("./comp1.txt", "dest1_updated.txt")
642 .unwrap_err();
643 depot.link_create("/comp1.txt", "dest1.txt").unwrap_err();
644 depot.link_create("dir1/", "destdir1/").unwrap();
551 depot 645 depot
552 .create("./comp1.txt", "dest1_updated.txt") 646 .link_create("dir1/file1.txt", "destfile1.txt")
553 .unwrap_err(); 647 .unwrap_err();
554 depot.create("/comp1.txt", "dest1.txt").unwrap_err();
555 depot.create("dir1/", "destdir1/").unwrap();
556 depot.create("dir1/file1.txt", "destfile1.txt").unwrap_err();
557 } 648 }
558 649
559 #[test] 650 #[test]
560 fn test_depot_move_link() { 651 fn test_depot_link_move() {
561 let mut depot = Depot::default(); 652 let mut depot = Depot::default();
562 let f1 = depot.create("d1/f1", "d1/f1").unwrap(); 653 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
563 let _f2 = depot.create("d1/f2", "d1/f2").unwrap(); 654 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
564 655
565 depot.move_link(f1, "").unwrap_err(); 656 depot.link_move(f1, "").unwrap_err();
566 depot.move_link(f1, "d1/f2/f1").unwrap_err(); 657 depot.link_move(f1, "d1/f2/f1").unwrap_err();
567 658
568 depot.move_link(f1, "d1/f2").unwrap(); 659 depot.link_move(f1, "d1/f2").unwrap();
569 depot.move_link(f1, "f1").unwrap(); 660 depot.link_move(f1, "f1").unwrap();
570 assert_eq!(depot.link_view(f1).origin(), Path::new("f1")); 661 assert_eq!(depot.link_view(f1).origin(), Path::new("f1"));
571 depot.move_link(f1, "f2").unwrap(); 662 depot.link_move(f1, "f2").unwrap();
572 assert_eq!(depot.link_view(f1).origin(), Path::new("f2")); 663 assert_eq!(depot.link_view(f1).origin(), Path::new("f2"));
573 } 664 }
574 665
575 #[test] 666 #[test]
576 fn test_depot_remove() { 667 fn test_depot_links_under() {
577 let mut depot = Depot::default(); 668 let mut depot = Depot::default();
578 let f1 = depot.create("d1/f1", "d1/f1").unwrap(); 669 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
579 assert_eq!(depot.search("d1/f1").unwrap(), SearchResult::Found(f1)); 670 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
580 depot.remove(f1); 671 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
581 assert_eq!(depot.search("d1/f1").unwrap(), SearchResult::NotFound); 672 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
673 let d3 = depot.link_create("d3", "d3").unwrap();
674
675 let under_f1 = depot.links_under("d1/f1").unwrap().collect::<Vec<_>>();
676 assert_eq!(under_f1, vec![f1]);
677
678 let under_d1 = depot.links_under("d1").unwrap().collect::<Vec<_>>();
679 let expected_under_d1 = vec![f1, f2, f3, f4];
680 assert!(
681 under_d1.len() == expected_under_d1.len()
682 && expected_under_d1.iter().all(|x| under_d1.contains(x))
683 );
684
685 let under_d2 = depot.links_under("d2").unwrap().collect::<Vec<_>>();
686 assert_eq!(under_d2, vec![]);
687
688 let under_d3 = depot.links_under("d3").unwrap().collect::<Vec<_>>();
689 assert_eq!(under_d3, vec![d3]);
690
691 let under_root = depot.links_under("").unwrap().collect::<Vec<_>>();
692 let expected_under_root = vec![f1, f2, f3, f4, d3];
693 assert!(
694 under_root.len() == expected_under_root.len()
695 && expected_under_root.iter().all(|x| under_root.contains(x))
696 );
697 }
698
699 #[test]
700 fn test_depot_has_links_under() {
701 let mut depot = Depot::default();
702 let _f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
703 let _f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
704 let _f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
705 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
706 let _d3 = depot.link_create("d3", "d3").unwrap();
707
708 assert!(depot.has_links_under("").unwrap());
709 assert!(depot.has_links_under("d1").unwrap());
710 assert!(depot.has_links_under("d3").unwrap());
711 assert!(depot.has_links_under("d1/f1").unwrap());
712 assert!(depot.has_links_under("d1/d2").unwrap());
713 assert!(depot.has_links_under("d1/d2/f4").unwrap());
714
715 assert!(!depot.has_links_under("d2").unwrap());
716 assert!(!depot.has_links_under("d4").unwrap());
717 assert!(!depot.has_links_under("d1/d2/f4/f5").unwrap());
718 }
719
720 #[test]
721 fn test_depot_link_remove() {
722 let mut depot = Depot::default();
723 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
724 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::Found(f1));
725 depot.link_remove(f1);
726 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::NotFound);
582 } 727 }
583 728
584 #[test] 729 #[test]
585 fn test_depot_search() { 730 fn test_depot_link_search() {
586 let mut depot = Depot::default(); 731 let mut depot = Depot::default();
587 let f1 = depot.create("d1/f1", "d1/f1").unwrap(); 732 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
588 let f2 = depot.create("d1/f2", "d1/f2").unwrap(); 733 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
589 let f3 = depot.create("d1/f3", "d1/f3").unwrap(); 734 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
590 let f4 = depot.create("d1/d2/f4", "d2/f4").unwrap(); 735 let f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
591 let d3 = depot.create("d3", "d3").unwrap(); 736 let d3 = depot.link_create("d3", "d3").unwrap();
737
738 assert_eq!(depot.link_search("d1").unwrap(), SearchResult::NotFound,);
739 assert_eq!(depot.link_search("d1/f1").unwrap(), SearchResult::Found(f1),);
740 assert_eq!(depot.link_search("d1/f2").unwrap(), SearchResult::Found(f2),);
741 assert_eq!(depot.link_search("d1/f3").unwrap(), SearchResult::Found(f3),);
742 assert_eq!(
743 depot.link_search("d1/d2/f4").unwrap(),
744 SearchResult::Found(f4),
745 );
746 assert_eq!(
747 depot.link_search("d1/d2/f5").unwrap(),
748 SearchResult::NotFound,
749 );
750 assert_eq!(
751 depot.link_search("d3/f6").unwrap(),
752 SearchResult::Ancestor(d3),
753 );
754 }
592 755
593 assert_eq!(depot.search("d1").unwrap(), SearchResult::NotFound,); 756 #[test]
594 assert_eq!(depot.search("d1/f1").unwrap(), SearchResult::Found(f1),); 757 fn test_depot_read_dir() {
595 assert_eq!(depot.search("d1/f2").unwrap(), SearchResult::Found(f2),); 758 let mut depot = Depot::default();
596 assert_eq!(depot.search("d1/f3").unwrap(), SearchResult::Found(f3),); 759 let f1 = depot.link_create("d1/f1", "d1/f1").unwrap();
597 assert_eq!(depot.search("d1/d2/f4").unwrap(), SearchResult::Found(f4),); 760 let f2 = depot.link_create("d1/f2", "d1/f2").unwrap();
598 assert_eq!(depot.search("d1/d2/f5").unwrap(), SearchResult::NotFound,); 761 let f3 = depot.link_create("d1/f3", "d1/f3").unwrap();
599 assert_eq!(depot.search("d3/f6").unwrap(), SearchResult::Ancestor(d3),); 762 let _f4 = depot.link_create("d1/d2/f4", "d2/f4").unwrap();
763 let _d3 = depot.link_create("d3", "d3").unwrap();
764
765 let read_dir = depot.read_dir("d1").unwrap().collect::<Vec<_>>();
766 let expected_read_dir = vec![
767 DirNode::Link(f1),
768 DirNode::Link(f2),
769 DirNode::Link(f3),
770 DirNode::Directory(PathBuf::from("d1/d2")),
771 ];
772 assert!(
773 read_dir.len() == expected_read_dir.len()
774 && expected_read_dir.iter().all(|x| read_dir.contains(x))
775 );
600 } 776 }
601 777
602 #[test] 778 #[test]
@@ -612,8 +788,9 @@ pub mod depot {
612 } 788 }
613} 789}
614 790
615mod dotup { 791pub mod dotup {
616 use std::{ 792 use std::{
793 cmp::Ordering,
617 collections::HashSet, 794 collections::HashSet,
618 path::{Path, PathBuf}, 795 path::{Path, PathBuf},
619 }; 796 };
@@ -622,7 +799,7 @@ mod dotup {
622 use anyhow::Context; 799 use anyhow::Context;
623 800
624 use crate::{ 801 use crate::{
625 depot::{self, Depot, LinkID}, 802 depot::{self, Depot, DirNode, LinkID},
626 utils, 803 utils,
627 }; 804 };
628 805
@@ -633,6 +810,90 @@ mod dotup {
633 destination: PathBuf, 810 destination: PathBuf,
634 } 811 }
635 812
813 #[derive(Debug, Clone)]
814 enum StatusItem {
815 Link {
816 origin: PathBuf,
817 destination: PathBuf,
818 is_directory: bool,
819 },
820 Directory {
821 origin: PathBuf,
822 items: Vec<StatusItem>,
823 },
824 Unlinked {
825 origin: PathBuf,
826 is_directory: bool,
827 },
828 }
829
830 impl StatusItem {
831 fn display_ord_cmp(&self, other: &Self) -> Ordering {
832 match (self, other) {
833 (
834 StatusItem::Link {
835 origin: l_origin, ..
836 },
837 StatusItem::Link {
838 origin: r_origin, ..
839 },
840 ) => l_origin.cmp(r_origin),
841 (StatusItem::Link { .. }, StatusItem::Directory { .. }) => Ordering::Less,
842 (
843 StatusItem::Link {
844 is_directory: l_is_dir,
845 ..
846 },
847 StatusItem::Unlinked {
848 is_directory: u_is_dir,
849 ..
850 },
851 ) => {
852 if *u_is_dir && !*l_is_dir {
853 Ordering::Less
854 } else {
855 Ordering::Greater
856 }
857 }
858 (StatusItem::Directory { .. }, StatusItem::Link { .. }) => Ordering::Greater,
859 (
860 StatusItem::Directory {
861 origin: l_origin, ..
862 },
863 StatusItem::Directory {
864 origin: r_origin, ..
865 },
866 ) => l_origin.cmp(r_origin),
867 (StatusItem::Directory { .. }, StatusItem::Unlinked { .. }) => Ordering::Greater,
868 (
869 StatusItem::Unlinked {
870 is_directory: u_is_dir,
871 ..
872 },
873 StatusItem::Link {
874 is_directory: l_is_dir,
875 ..
876 },
877 ) => {
878 if *u_is_dir && !*l_is_dir {
879 Ordering::Greater
880 } else {
881 Ordering::Less
882 }
883 }
884 (StatusItem::Unlinked { .. }, StatusItem::Directory { .. }) => Ordering::Less,
885 (
886 StatusItem::Unlinked {
887 origin: l_origin, ..
888 },
889 StatusItem::Unlinked {
890 origin: r_origin, ..
891 },
892 ) => l_origin.cmp(r_origin),
893 }
894 }
895 }
896
636 #[derive(Debug)] 897 #[derive(Debug)]
637 pub struct Dotup { 898 pub struct Dotup {
638 depot: Depot, 899 depot: Depot,
@@ -664,7 +925,7 @@ mod dotup {
664 let link_result: anyhow::Result<()> = try { 925 let link_result: anyhow::Result<()> = try {
665 let origin = self.prepare_origin_path(origin.as_ref())?; 926 let origin = self.prepare_origin_path(origin.as_ref())?;
666 let destination = destination.as_ref(); 927 let destination = destination.as_ref();
667 self.depot.create(origin, destination)?; 928 self.depot.link_create(origin, destination)?;
668 }; 929 };
669 match link_result { 930 match link_result {
670 Ok(_) => {} 931 Ok(_) => {}
@@ -676,10 +937,10 @@ mod dotup {
676 for origin in paths { 937 for origin in paths {
677 let unlink_result: anyhow::Result<()> = try { 938 let unlink_result: anyhow::Result<()> = try {
678 let origin = self.prepare_origin_path(origin.as_ref())?; 939 let origin = self.prepare_origin_path(origin.as_ref())?;
679 let search_results = self.depot.search(&origin)?; 940 let search_results = self.depot.link_search(&origin)?;
680 match search_results { 941 match search_results {
681 depot::SearchResult::Found(link_id) => { 942 depot::SearchResult::Found(link_id) => {
682 self.depot.remove(link_id); 943 self.depot.link_remove(link_id);
683 println!("removed link {}", origin.display()); 944 println!("removed link {}", origin.display());
684 } 945 }
685 depot::SearchResult::Ancestor(_) | depot::SearchResult::NotFound => { 946 depot::SearchResult::Ancestor(_) | depot::SearchResult::NotFound => {
@@ -766,7 +1027,7 @@ mod dotup {
766 } 1027 }
767 1028
768 fn mv_one(&mut self, origin: &Path, destination: &Path) -> anyhow::Result<()> { 1029 fn mv_one(&mut self, origin: &Path, destination: &Path) -> anyhow::Result<()> {
769 let link_id = match self.depot.find(origin)? { 1030 let link_id = match self.depot.link_find(origin)? {
770 Some(link_id) => link_id, 1031 Some(link_id) => link_id,
771 None => { 1032 None => {
772 return Err(anyhow::anyhow!(format!( 1033 return Err(anyhow::anyhow!(format!(
@@ -777,11 +1038,11 @@ mod dotup {
777 }; 1038 };
778 let is_installed = self.symlink_is_installed_by_link_id(link_id)?; 1039 let is_installed = self.symlink_is_installed_by_link_id(link_id)?;
779 let original_origin = self.depot.link_view(link_id).origin().to_owned(); 1040 let original_origin = self.depot.link_view(link_id).origin().to_owned();
780 self.depot.move_link(link_id, destination)?; 1041 self.depot.link_move(link_id, destination)?;
781 // move the actual file on disk 1042 // move the actual file on disk
782 if let Err(e) = std::fs::rename(origin, destination).context("Failed to move file") { 1043 if let Err(e) = std::fs::rename(origin, destination).context("Failed to move file") {
783 // unwrap: moving the link back to its origin place has to work 1044 // unwrap: moving the link back to its origin place has to work
784 self.depot.move_link(link_id, original_origin).unwrap(); 1045 self.depot.link_move(link_id, original_origin).unwrap();
785 return Err(e); 1046 return Err(e);
786 } 1047 }
787 // reinstall because we just moved the origin 1048 // reinstall because we just moved the origin
@@ -794,75 +1055,154 @@ mod dotup {
794 1055
795 pub fn status(&self) { 1056 pub fn status(&self) {
796 let status_result: anyhow::Result<()> = try { 1057 let status_result: anyhow::Result<()> = try {
797 let curr_dir = utils::current_working_directory(); 1058 let canonical_dir = utils::current_working_directory();
798 let (dirs, files) = utils::collect_read_dir_split(curr_dir)?; 1059 let item = self.status_path_to_item(&canonical_dir)?;
1060 self.status_print_item(item, 0)?;
799 }; 1061 };
800 if let Err(e) = status_result { 1062 if let Err(e) = status_result {
801 println!("error while displaying status : {e}"); 1063 println!("error while displaying status : {e}");
802 } 1064 }
803 } 1065 }
1066 fn status_path_to_item(&self, canonical_path: &Path) -> anyhow::Result<StatusItem> {
1067 debug_assert!(canonical_path.is_absolute());
1068 debug_assert!(canonical_path.exists());
1069 let relative_path = self.prepare_origin_path(&canonical_path)?;
1070
1071 let item = if canonical_path.is_dir() {
1072 if let Some(link_id) = self.depot.link_find(&relative_path)? {
1073 let destination = self.depot.link_view(link_id).destination().to_owned();
1074 StatusItem::Link {
1075 origin: relative_path,
1076 destination,
1077 is_directory: true,
1078 }
1079 } else if self.depot.has_links_under(&relative_path)? {
1080 let mut items = Vec::new();
1081 let mut collected_rel_paths = HashSet::<PathBuf>::new();
1082 let directory_paths = utils::collect_paths_in_dir(&canonical_path)?;
1083 for canonical_item_path in directory_paths {
1084 let item = self.status_path_to_item(&canonical_item_path)?;
1085 match &item {
1086 StatusItem::Link { origin, .. }
1087 | StatusItem::Directory { origin, .. } => {
1088 collected_rel_paths.insert(origin.to_owned());
1089 }
1090 _ => {}
1091 }
1092 items.push(item);
1093 }
804 1094
805 pub fn status2(&self) { 1095 for dir_node in self.depot.read_dir(&relative_path)? {
806 let status_result: anyhow::Result<()> = try { 1096 match dir_node {
807 let curr_dir = &std::env::current_dir()?; 1097 DirNode::Link(link_id) => {
808 let (dirs, files) = utils::collect_read_dir_split(curr_dir)?; 1098 let link_view = self.depot.link_view(link_id);
809 for path in dirs.iter().chain(files.iter()) { 1099 let link_rel_path = link_view.origin();
810 self.print_status_for(&path, 0)?; 1100 let link_rel_dest = link_view.destination();
1101 if !collected_rel_paths.contains(link_rel_path) {
1102 items.push(StatusItem::Link {
1103 origin: link_rel_path.to_owned(),
1104 destination: link_rel_dest.to_owned(),
1105 is_directory: false,
1106 });
1107 }
1108 }
1109 DirNode::Directory(_) => {}
1110 }
1111 }
1112
1113 StatusItem::Directory {
1114 origin: relative_path,
1115 items,
1116 }
1117 } else {
1118 StatusItem::Unlinked {
1119 origin: relative_path,
1120 is_directory: true,
1121 }
1122 }
1123 } else {
1124 if let Some(link_id) = self.depot.link_find(&relative_path)? {
1125 let destination = self.depot.link_view(link_id).destination().to_owned();
1126 StatusItem::Link {
1127 origin: relative_path,
1128 destination,
1129 is_directory: false,
1130 }
1131 } else {
1132 StatusItem::Unlinked {
1133 origin: relative_path,
1134 is_directory: false,
1135 }
811 } 1136 }
812 }; 1137 };
813 if let Err(e) = status_result { 1138 Ok(item)
814 println!("error while displaying status : {e}");
815 }
816 } 1139 }
817 1140 fn status_print_item(&self, item: StatusItem, depth: u32) -> anyhow::Result<()> {
818 fn print_status_for(&self, path: &Path, depth: u32) -> anyhow::Result<()> {
819 fn print_depth(d: u32) { 1141 fn print_depth(d: u32) {
820 for _ in 0..d { 1142 for _ in 0..d.saturating_sub(1) {
821 print!(" "); 1143 print!(" ");
822 } 1144 }
823 } 1145 }
824 1146 fn origin_color(exists: bool, is_installed: bool) -> Color {
825 let origin = self.prepare_origin_path(path)?; 1147 if !exists {
826 if path.is_dir() { 1148 return Color::Red;
827 print_depth(depth); 1149 } else if is_installed {
828 let file_name = path.file_name().unwrap().to_str().unwrap_or_default(); 1150 Color::Green
829 if let Some(link_id) = self.depot.find(&origin)? {
830 let installed = self.symlink_is_installed_by_link_id(link_id)?;
831 let link_view = self.depot.link_view(link_id);
832 let destination = link_view.destination().display().to_string();
833 let color = if installed { Color::Green } else { Color::Red };
834 println!(
835 "{}/ ---> {}",
836 color.paint(file_name),
837 Color::Blue.paint(destination)
838 );
839 } else { 1151 } else {
840 println!("{}/", file_name); 1152 Color::Cyan
841 let (dirs, files) = utils::collect_read_dir_split(path)?;
842 for path in dirs.iter().chain(files.iter()) {
843 self.print_status_for(&path, depth + 1)?;
844 }
845 } 1153 }
846 } else if path.is_file() || path.is_symlink() { 1154 }
847 print_depth(depth);
848 let file_name = path.file_name().unwrap().to_str().unwrap_or_default();
849 if let Some(link_id) = self.depot.find(&origin)? {
850 let installed = self.symlink_is_installed_by_link_id(link_id)?;
851 let link_view = self.depot.link_view(link_id);
852 let destination = link_view.destination().display().to_string();
853 let color = if installed { Color::Green } else { Color::Red };
854 1155
1156 let destination_color = Color::Blue;
1157
1158 print_depth(depth);
1159 match item {
1160 StatusItem::Link {
1161 origin,
1162 destination,
1163 is_directory,
1164 } => {
1165 let canonical_origin = self.depot_dir.join(&origin);
1166 let canonical_destination = self.install_base.join(&destination);
1167 let file_name = Self::status_get_filename(&canonical_origin);
1168 let is_installed =
1169 self.symlink_is_installed(&canonical_origin, &canonical_destination)?;
1170 let exists = canonical_origin.exists();
1171 let origin_color = origin_color(exists, is_installed);
1172 let directory_extra = if is_directory { "/" } else { "" };
855 println!( 1173 println!(
856 "{} ---> {}", 1174 "{}{} -> {}",
857 color.paint(file_name), 1175 origin_color.paint(file_name),
858 Color::Blue.paint(destination) 1176 directory_extra,
1177 destination_color.paint(destination.display().to_string())
859 ); 1178 );
860 } else { 1179 }
861 println!("{}", file_name); 1180 StatusItem::Directory { origin, mut items } => {
1181 items.sort_by(|a, b| StatusItem::display_ord_cmp(a, b).reverse());
1182 let directory_name = Self::status_get_filename(&origin);
1183 if depth != 0 {
1184 println!("{}/", directory_name);
1185 }
1186 for item in items {
1187 self.status_print_item(item, depth + 1)?;
1188 }
1189 }
1190 StatusItem::Unlinked {
1191 origin,
1192 is_directory,
1193 } => {
1194 let file_name = Self::status_get_filename(&origin);
1195 let directory_extra = if is_directory { "/" } else { "" };
1196 println!("{}{}", file_name, directory_extra);
862 } 1197 }
863 } 1198 }
864 Ok(()) 1199 Ok(())
865 } 1200 }
1201 fn status_get_filename(path: &Path) -> &str {
1202 path.file_name()
1203 .and_then(|s| s.to_str())
1204 .unwrap_or_default()
1205 }
866 1206
867 fn prepare_origin_path(&self, origin: &Path) -> anyhow::Result<PathBuf> { 1207 fn prepare_origin_path(&self, origin: &Path) -> anyhow::Result<PathBuf> {
868 let canonical = utils::weakly_canonical(origin); 1208 let canonical = utils::weakly_canonical(origin);
@@ -994,10 +1334,16 @@ mod utils {
994 pub fn collect_read_dir_split( 1334 pub fn collect_read_dir_split(
995 dir: impl AsRef<Path>, 1335 dir: impl AsRef<Path>,
996 ) -> anyhow::Result<(Vec<PathBuf>, Vec<PathBuf>)> { 1336 ) -> anyhow::Result<(Vec<PathBuf>, Vec<PathBuf>)> {
1337 Ok(collect_paths_in_dir(dir)?
1338 .into_iter()
1339 .partition(|p| p.is_dir()))
1340 }
1341
1342 pub fn collect_paths_in_dir(dir: impl AsRef<Path>) -> anyhow::Result<Vec<PathBuf>> {
997 Ok(std::fs::read_dir(dir)? 1343 Ok(std::fs::read_dir(dir)?
998 .filter_map(|e| e.ok()) 1344 .filter_map(|e| e.ok())
999 .map(|e| e.path()) 1345 .map(|e| e.path())
1000 .partition(|p| p.is_dir())) 1346 .collect())
1001 } 1347 }
1002 1348
1003 pub fn read_dotup(flags: &Flags) -> anyhow::Result<Dotup> { 1349 pub fn read_dotup(flags: &Flags) -> anyhow::Result<Dotup> {