diff options
| author | diogo464 <[email protected]> | 2022-02-07 14:35:11 +0000 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2022-02-07 14:35:11 +0000 |
| commit | cbb2edb0b523f2494fd543857195792a8eda1b62 (patch) | |
| tree | 600fd0a9eda4c01a64d5d3aa0ae9fc6d62e63b70 /src/main.rs | |
| parent | a2117085b26557a27e8068c6caa4037e2f9f1a7f (diff) | |
snapshot
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 572 |
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 | ||
| 615 | mod dotup { | 791 | pub 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> { |
