diff options
| author | diogo464 <[email protected]> | 2022-02-07 09:09:57 +0000 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2022-02-07 09:09:57 +0000 |
| commit | a2117085b26557a27e8068c6caa4037e2f9f1a7f (patch) | |
| tree | 83a05069f8c965301becb92fdc74d54c577ba67c | |
| parent | 2462119defef2d7f28cd5b15b09917c9a46e20b6 (diff) | |
snapshot
| -rw-r--r-- | Cargo.lock | 10 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/main.rs | 368 |
3 files changed, 327 insertions, 52 deletions
| @@ -3,6 +3,15 @@ | |||
| 3 | version = 3 | 3 | version = 3 |
| 4 | 4 | ||
| 5 | [[package]] | 5 | [[package]] |
| 6 | name = "ansi_term" | ||
| 7 | version = "0.12.1" | ||
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" | ||
| 10 | dependencies = [ | ||
| 11 | "winapi", | ||
| 12 | ] | ||
| 13 | |||
| 14 | [[package]] | ||
| 6 | name = "anyhow" | 15 | name = "anyhow" |
| 7 | version = "1.0.53" | 16 | version = "1.0.53" |
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | 17 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -65,6 +74,7 @@ dependencies = [ | |||
| 65 | name = "dotup" | 74 | name = "dotup" |
| 66 | version = "0.0.0" | 75 | version = "0.0.0" |
| 67 | dependencies = [ | 76 | dependencies = [ |
| 77 | "ansi_term", | ||
| 68 | "anyhow", | 78 | "anyhow", |
| 69 | "clap", | 79 | "clap", |
| 70 | "serde", | 80 | "serde", |
| @@ -6,6 +6,7 @@ edition = "2021" | |||
| 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
| 7 | 7 | ||
| 8 | [dependencies] | 8 | [dependencies] |
| 9 | ansi_term = "0.12.1" | ||
| 9 | anyhow = "1.0.53" | 10 | anyhow = "1.0.53" |
| 10 | clap = { version = "3.0.14", features = ["derive"] } | 11 | clap = { version = "3.0.14", features = ["derive"] } |
| 11 | serde = { version = "1.0.136", features = ["derive"] } | 12 | serde = { version = "1.0.136", features = ["derive"] } |
diff --git a/src/main.rs b/src/main.rs index e2a659b..1559ede 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -1,4 +1,7 @@ | |||
| 1 | #![feature(try_blocks)] | 1 | #![feature(try_blocks)] |
| 2 | |||
| 3 | // TODO: rewrite all errors so they start with lower case | ||
| 4 | |||
| 2 | pub mod depot { | 5 | pub mod depot { |
| 3 | use std::{ | 6 | use std::{ |
| 4 | collections::HashSet, | 7 | collections::HashSet, |
| @@ -7,7 +10,7 @@ pub mod depot { | |||
| 7 | path::{Path, PathBuf}, | 10 | path::{Path, PathBuf}, |
| 8 | }; | 11 | }; |
| 9 | 12 | ||
| 10 | use slotmap::SlotMap; | 13 | use slotmap::{Key, SlotMap}; |
| 11 | 14 | ||
| 12 | pub use disk::{read, write}; | 15 | pub use disk::{read, write}; |
| 13 | 16 | ||
| @@ -134,6 +137,10 @@ pub mod depot { | |||
| 134 | } | 137 | } |
| 135 | } | 138 | } |
| 136 | 139 | ||
| 140 | /// moves the link specified by `link_id` to the path at `destination`. | ||
| 141 | /// if the link is already at the destination nothing is done. | ||
| 142 | /// if the destination is another link that that link is removed. | ||
| 143 | /// if the destination is under another link then an error is returned. | ||
| 137 | pub fn move_link( | 144 | pub fn move_link( |
| 138 | &mut self, | 145 | &mut self, |
| 139 | link_id: LinkID, | 146 | link_id: LinkID, |
| @@ -158,10 +165,12 @@ pub mod depot { | |||
| 158 | let node_parent_id = node.parent; | 165 | let node_parent_id = node.parent; |
| 159 | let node_link_id = *node_link_id; | 166 | let node_link_id = *node_link_id; |
| 160 | assert_ne!(link_id, node_link_id); | 167 | assert_ne!(link_id, node_link_id); |
| 161 | self.remove(node_link_id); | ||
| 162 | self.node_child_remove(link_parent_node_id, link_node_id); | 168 | self.node_child_remove(link_parent_node_id, link_node_id); |
| 163 | self.node_child_add(node_parent_id, link_node_id); | 169 | self.node_child_add(node_parent_id, link_node_id); |
| 164 | self.node_set_parent(link_node_id, node_parent_id); | 170 | self.node_set_parent(link_node_id, node_parent_id); |
| 171 | self.remove(node_link_id); | ||
| 172 | let new_origin = self.node_build_path(link_node_id); | ||
| 173 | self.links[link_id].origin = new_origin; | ||
| 165 | Ok(()) | 174 | Ok(()) |
| 166 | } | 175 | } |
| 167 | NodeKind::Directory(..) => Err(anyhow::anyhow!( | 176 | NodeKind::Directory(..) => Err(anyhow::anyhow!( |
| @@ -176,8 +185,10 @@ pub mod depot { | |||
| 176 | )), | 185 | )), |
| 177 | NodeKind::Directory(_) => { | 186 | NodeKind::Directory(_) => { |
| 178 | let new_node_id = self.node_create_link(destination, link_id); | 187 | let new_node_id = self.node_create_link(destination, link_id); |
| 188 | let new_origin = self.node_build_path(new_node_id); | ||
| 179 | self.node_remove(link_node_id); | 189 | self.node_remove(link_node_id); |
| 180 | self.links[link_id].node_id = new_node_id; | 190 | self.links[link_id].node_id = new_node_id; |
| 191 | self.links[link_id].origin = new_origin; | ||
| 181 | Ok(()) | 192 | Ok(()) |
| 182 | } | 193 | } |
| 183 | } | 194 | } |
| @@ -207,6 +218,14 @@ pub mod depot { | |||
| 207 | Ok(self.search_unchecked(&origin)) | 218 | Ok(self.search_unchecked(&origin)) |
| 208 | } | 219 | } |
| 209 | 220 | ||
| 221 | /// finds the link at origin. | ||
| 222 | pub fn find(&self, origin: impl AsRef<Path>) -> anyhow::Result<Option<LinkID>> { | ||
| 223 | match self.search(origin)? { | ||
| 224 | SearchResult::Found(link_id) => Ok(Some(link_id)), | ||
| 225 | SearchResult::Ancestor(_) | SearchResult::NotFound => Ok(None), | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 210 | /// returns an iterator for all the links at or under the given path. | 229 | /// returns an iterator for all the links at or under the given path. |
| 211 | pub fn links_under( | 230 | pub fn links_under( |
| 212 | &self, | 231 | &self, |
| @@ -391,6 +410,22 @@ pub mod depot { | |||
| 391 | } | 410 | } |
| 392 | } | 411 | } |
| 393 | 412 | ||
| 413 | /// build the path that references this node. | ||
| 414 | fn node_build_path(&self, node_id: NodeID) -> PathBuf { | ||
| 415 | fn recursive_helper(nodes: &SlotMap<NodeID, Node>, nid: NodeID, pbuf: &mut PathBuf) { | ||
| 416 | if nid.is_null() { | ||
| 417 | return; | ||
| 418 | } | ||
| 419 | let parent_id = nodes[nid].parent; | ||
| 420 | recursive_helper(nodes, parent_id, pbuf); | ||
| 421 | pbuf.push(&nodes[nid].comp); | ||
| 422 | } | ||
| 423 | |||
| 424 | let mut node_path = PathBuf::default(); | ||
| 425 | recursive_helper(&self.nodes, node_id, &mut node_path); | ||
| 426 | node_path | ||
| 427 | } | ||
| 428 | |||
| 394 | fn node_set_parent(&mut self, node_id: NodeID, parent: NodeID) { | 429 | fn node_set_parent(&mut self, node_id: NodeID, parent: NodeID) { |
| 395 | self.nodes[node_id].parent = parent; | 430 | self.nodes[node_id].parent = parent; |
| 396 | } | 431 | } |
| @@ -461,6 +496,10 @@ pub mod depot { | |||
| 461 | } | 496 | } |
| 462 | } | 497 | } |
| 463 | 498 | ||
| 499 | /// a verified path is a path that: | ||
| 500 | /// + is not empty | ||
| 501 | /// + is relative | ||
| 502 | /// + does not contain Prefix/RootDir/ParentDir | ||
| 464 | fn verify_path(path: &Path) -> anyhow::Result<()> { | 503 | fn verify_path(path: &Path) -> anyhow::Result<()> { |
| 465 | // make sure the path is not empty | 504 | // make sure the path is not empty |
| 466 | // make sure the path is relative | 505 | // make sure the path is relative |
| @@ -523,11 +562,14 @@ pub mod depot { | |||
| 523 | let f1 = depot.create("d1/f1", "d1/f1").unwrap(); | 562 | let f1 = depot.create("d1/f1", "d1/f1").unwrap(); |
| 524 | let _f2 = depot.create("d1/f2", "d1/f2").unwrap(); | 563 | let _f2 = depot.create("d1/f2", "d1/f2").unwrap(); |
| 525 | 564 | ||
| 565 | depot.move_link(f1, "").unwrap_err(); | ||
| 526 | depot.move_link(f1, "d1/f2/f1").unwrap_err(); | 566 | depot.move_link(f1, "d1/f2/f1").unwrap_err(); |
| 527 | depot.move_link(f1, "d1").unwrap_err(); | ||
| 528 | 567 | ||
| 529 | depot.move_link(f1, "").unwrap(); | 568 | depot.move_link(f1, "d1/f2").unwrap(); |
| 530 | depot.move_link(f1, "d2/f1").unwrap(); | 569 | depot.move_link(f1, "f1").unwrap(); |
| 570 | assert_eq!(depot.link_view(f1).origin(), Path::new("f1")); | ||
| 571 | depot.move_link(f1, "f2").unwrap(); | ||
| 572 | assert_eq!(depot.link_view(f1).origin(), Path::new("f2")); | ||
| 531 | } | 573 | } |
| 532 | 574 | ||
| 533 | #[test] | 575 | #[test] |
| @@ -576,9 +618,13 @@ mod dotup { | |||
| 576 | path::{Path, PathBuf}, | 618 | path::{Path, PathBuf}, |
| 577 | }; | 619 | }; |
| 578 | 620 | ||
| 621 | use ansi_term::Color; | ||
| 579 | use anyhow::Context; | 622 | use anyhow::Context; |
| 580 | 623 | ||
| 581 | use crate::depot::{self, Depot, LinkID}; | 624 | use crate::{ |
| 625 | depot::{self, Depot, LinkID}, | ||
| 626 | utils, | ||
| 627 | }; | ||
| 582 | 628 | ||
| 583 | #[derive(Debug)] | 629 | #[derive(Debug)] |
| 584 | struct CanonicalPair { | 630 | struct CanonicalPair { |
| @@ -658,7 +704,7 @@ mod dotup { | |||
| 658 | if already_linked.contains(&pair.link_id) { | 704 | if already_linked.contains(&pair.link_id) { |
| 659 | continue; | 705 | continue; |
| 660 | } | 706 | } |
| 661 | self.install_symlink(&pair.origin, &pair.destination)?; | 707 | self.symlink_install(&pair.origin, &pair.destination)?; |
| 662 | already_linked.insert(pair.link_id); | 708 | already_linked.insert(pair.link_id); |
| 663 | } | 709 | } |
| 664 | }; | 710 | }; |
| @@ -674,7 +720,7 @@ mod dotup { | |||
| 674 | let origin = self.prepare_origin_path(origin.as_ref())?; | 720 | let origin = self.prepare_origin_path(origin.as_ref())?; |
| 675 | let canonical_pairs = self.canonical_pairs_under(&origin)?; | 721 | let canonical_pairs = self.canonical_pairs_under(&origin)?; |
| 676 | for pair in canonical_pairs { | 722 | for pair in canonical_pairs { |
| 677 | self.uninstall_symlink(&pair.origin, &pair.destination)?; | 723 | self.symlink_uninstall(&pair.origin, &pair.destination)?; |
| 678 | } | 724 | } |
| 679 | }; | 725 | }; |
| 680 | if let Err(e) = uninstall_result { | 726 | if let Err(e) = uninstall_result { |
| @@ -686,50 +732,182 @@ mod dotup { | |||
| 686 | } | 732 | } |
| 687 | } | 733 | } |
| 688 | 734 | ||
| 689 | pub fn mv(&mut self, from: impl Iterator<Item = impl AsRef<Path>>, to: impl AsRef<Path>) { | 735 | pub fn mv( |
| 690 | let to = to.as_ref(); | 736 | &mut self, |
| 691 | let from: Vec<_> = from.map(|p| p.as_ref().to_owned()).collect(); | 737 | origins: impl Iterator<Item = impl AsRef<Path>>, |
| 692 | match from.as_slice() { | 738 | destination: impl AsRef<Path>, |
| 693 | [] => unreachable!(), | 739 | ) { |
| 694 | [from] => self.mv_one(from, to), | 740 | let origins = { |
| 695 | [from @ ..] => self.mv_many(from, to), | 741 | let mut v = Vec::new(); |
| 742 | for origin in origins { | ||
| 743 | match self.prepare_origin_path(origin.as_ref()) { | ||
| 744 | Ok(origin) => v.push(origin), | ||
| 745 | Err(e) => { | ||
| 746 | println!("invalid link {} : {e}", origin.as_ref().display()); | ||
| 747 | return; | ||
| 748 | } | ||
| 749 | } | ||
| 750 | } | ||
| 751 | v | ||
| 752 | }; | ||
| 753 | let destination = destination.as_ref(); | ||
| 754 | |||
| 755 | // if we are moving multiple links then the destination must be a directory | ||
| 756 | if origins.len() > 1 && destination.is_dir() { | ||
| 757 | println!("destination must be a directory"); | ||
| 758 | return; | ||
| 759 | } | ||
| 760 | |||
| 761 | for origin in origins { | ||
| 762 | if let Err(e) = self.mv_one(&origin, destination) { | ||
| 763 | println!("error moving link {} : {e}", origin.display()); | ||
| 764 | } | ||
| 765 | } | ||
| 766 | } | ||
| 767 | |||
| 768 | fn mv_one(&mut self, origin: &Path, destination: &Path) -> anyhow::Result<()> { | ||
| 769 | let link_id = match self.depot.find(origin)? { | ||
| 770 | Some(link_id) => link_id, | ||
| 771 | None => { | ||
| 772 | return Err(anyhow::anyhow!(format!( | ||
| 773 | "{} is not a link", | ||
| 774 | origin.display() | ||
| 775 | ))) | ||
| 776 | } | ||
| 777 | }; | ||
| 778 | 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(); | ||
| 780 | self.depot.move_link(link_id, destination)?; | ||
| 781 | // move the actual file on disk | ||
| 782 | 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 | ||
| 784 | self.depot.move_link(link_id, original_origin).unwrap(); | ||
| 785 | return Err(e); | ||
| 786 | } | ||
| 787 | // reinstall because we just moved the origin | ||
| 788 | if is_installed { | ||
| 789 | self.symlink_install_by_link_id(link_id) | ||
| 790 | .context("failed to reinstall link while moving")?; | ||
| 791 | } | ||
| 792 | Ok(()) | ||
| 793 | } | ||
| 794 | |||
| 795 | pub fn status(&self) { | ||
| 796 | let status_result: anyhow::Result<()> = try { | ||
| 797 | let curr_dir = utils::current_working_directory(); | ||
| 798 | let (dirs, files) = utils::collect_read_dir_split(curr_dir)?; | ||
| 799 | }; | ||
| 800 | if let Err(e) = status_result { | ||
| 801 | println!("error while displaying status : {e}"); | ||
| 696 | } | 802 | } |
| 697 | } | 803 | } |
| 698 | 804 | ||
| 699 | fn mv_one(&mut self, from: &Path, to: &Path) {} | 805 | pub fn status2(&self) { |
| 806 | let status_result: anyhow::Result<()> = try { | ||
| 807 | let curr_dir = &std::env::current_dir()?; | ||
| 808 | let (dirs, files) = utils::collect_read_dir_split(curr_dir)?; | ||
| 809 | for path in dirs.iter().chain(files.iter()) { | ||
| 810 | self.print_status_for(&path, 0)?; | ||
| 811 | } | ||
| 812 | }; | ||
| 813 | if let Err(e) = status_result { | ||
| 814 | println!("error while displaying status : {e}"); | ||
| 815 | } | ||
| 816 | } | ||
| 700 | 817 | ||
| 701 | fn mv_many(&mut self, from: &[PathBuf], to: &Path) {} | 818 | fn print_status_for(&self, path: &Path, depth: u32) -> anyhow::Result<()> { |
| 819 | fn print_depth(d: u32) { | ||
| 820 | for _ in 0..d { | ||
| 821 | print!(" "); | ||
| 822 | } | ||
| 823 | } | ||
| 824 | |||
| 825 | let origin = self.prepare_origin_path(path)?; | ||
| 826 | if path.is_dir() { | ||
| 827 | print_depth(depth); | ||
| 828 | let file_name = path.file_name().unwrap().to_str().unwrap_or_default(); | ||
| 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 { | ||
| 840 | println!("{}/", file_name); | ||
| 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 | } | ||
| 846 | } else if path.is_file() || path.is_symlink() { | ||
| 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 | |||
| 855 | println!( | ||
| 856 | "{} ---> {}", | ||
| 857 | color.paint(file_name), | ||
| 858 | Color::Blue.paint(destination) | ||
| 859 | ); | ||
| 860 | } else { | ||
| 861 | println!("{}", file_name); | ||
| 862 | } | ||
| 863 | } | ||
| 864 | Ok(()) | ||
| 865 | } | ||
| 702 | 866 | ||
| 703 | fn prepare_origin_path(&self, origin: &Path) -> anyhow::Result<PathBuf> { | 867 | fn prepare_origin_path(&self, origin: &Path) -> anyhow::Result<PathBuf> { |
| 704 | let canonical = origin | 868 | let canonical = utils::weakly_canonical(origin); |
| 705 | .canonicalize() | ||
| 706 | .context("Failed to canonicalize origin path")?; | ||
| 707 | let relative = canonical | 869 | let relative = canonical |
| 708 | .strip_prefix(&self.depot_dir) | 870 | .strip_prefix(&self.depot_dir) |
| 709 | .context("Invalid origin path, not under depot directory")?; | 871 | .context("Invalid origin path, not under depot directory")?; |
| 710 | Ok(relative.to_owned()) | 872 | Ok(relative.to_owned()) |
| 711 | } | 873 | } |
| 712 | 874 | ||
| 713 | // returns the canonical pairs (origin, destination) for all links under `path`. | 875 | // returns the canonical pairs for all links under `path`. |
| 714 | fn canonical_pairs_under(&self, path: &Path) -> anyhow::Result<Vec<CanonicalPair>> { | 876 | fn canonical_pairs_under(&self, path: &Path) -> anyhow::Result<Vec<CanonicalPair>> { |
| 715 | let origin = self.prepare_origin_path(path)?; | 877 | let origin = self.prepare_origin_path(path)?; |
| 716 | let mut paths = Vec::new(); | 878 | let mut canonical_pairs = Vec::new(); |
| 717 | for link_id in self.depot.links_under(origin)? { | 879 | for link_id in self.depot.links_under(origin)? { |
| 718 | let link_view = self.depot.link_view(link_id); | 880 | canonical_pairs.push(self.canonical_pair_from_link_id(link_id)); |
| 719 | let relative_origin = link_view.origin(); | ||
| 720 | let relative_destination = link_view.destination(); | ||
| 721 | let canonical_origin = self.depot_dir.join(relative_origin); | ||
| 722 | let canonical_destination = self.install_base.join(relative_destination); | ||
| 723 | paths.push(CanonicalPair { | ||
| 724 | link_id, | ||
| 725 | origin: canonical_origin, | ||
| 726 | destination: canonical_destination, | ||
| 727 | }); | ||
| 728 | } | 881 | } |
| 729 | Ok(paths) | 882 | Ok(canonical_pairs) |
| 883 | } | ||
| 884 | |||
| 885 | fn symlink_is_installed_by_link_id(&self, link_id: LinkID) -> anyhow::Result<bool> { | ||
| 886 | let canonical_pair = self.canonical_pair_from_link_id(link_id); | ||
| 887 | self.symlink_is_installed(&canonical_pair.origin, &canonical_pair.destination) | ||
| 730 | } | 888 | } |
| 731 | 889 | ||
| 732 | fn install_symlink(&self, origin: &Path, destination: &Path) -> anyhow::Result<()> { | 890 | fn symlink_is_installed(&self, origin: &Path, destination: &Path) -> anyhow::Result<bool> { |
| 891 | debug_assert!(origin.is_absolute()); | ||
| 892 | debug_assert!(destination.is_absolute()); | ||
| 893 | |||
| 894 | if destination.is_symlink() { | ||
| 895 | let symlink_destination = destination.read_link()?; | ||
| 896 | match symlink_destination.canonicalize() { | ||
| 897 | Ok(canonicalized) => Ok(origin == canonicalized), | ||
| 898 | Err(_) => Ok(false), | ||
| 899 | } | ||
| 900 | } else { | ||
| 901 | Ok(false) | ||
| 902 | } | ||
| 903 | } | ||
| 904 | |||
| 905 | fn symlink_install_by_link_id(&self, link_id: LinkID) -> anyhow::Result<()> { | ||
| 906 | let canonical_pair = self.canonical_pair_from_link_id(link_id); | ||
| 907 | self.symlink_install(&canonical_pair.origin, &canonical_pair.destination) | ||
| 908 | } | ||
| 909 | |||
| 910 | fn symlink_install(&self, origin: &Path, destination: &Path) -> anyhow::Result<()> { | ||
| 733 | debug_assert!(origin.is_absolute()); | 911 | debug_assert!(origin.is_absolute()); |
| 734 | debug_assert!(destination.is_absolute()); | 912 | debug_assert!(destination.is_absolute()); |
| 735 | 913 | ||
| @@ -753,7 +931,7 @@ mod dotup { | |||
| 753 | Ok(()) | 931 | Ok(()) |
| 754 | } | 932 | } |
| 755 | 933 | ||
| 756 | fn uninstall_symlink(&self, origin: &Path, destination: &Path) -> anyhow::Result<()> { | 934 | fn symlink_uninstall(&self, origin: &Path, destination: &Path) -> anyhow::Result<()> { |
| 757 | debug_assert!(origin.is_absolute()); | 935 | debug_assert!(origin.is_absolute()); |
| 758 | debug_assert!(destination.is_absolute()); | 936 | debug_assert!(destination.is_absolute()); |
| 759 | 937 | ||
| @@ -766,6 +944,19 @@ mod dotup { | |||
| 766 | 944 | ||
| 767 | Ok(()) | 945 | Ok(()) |
| 768 | } | 946 | } |
| 947 | |||
| 948 | fn canonical_pair_from_link_id(&self, link_id: LinkID) -> CanonicalPair { | ||
| 949 | let link_view = self.depot.link_view(link_id); | ||
| 950 | let relative_origin = link_view.origin(); | ||
| 951 | let relative_destination = link_view.destination(); | ||
| 952 | let canonical_origin = self.depot_dir.join(relative_origin); | ||
| 953 | let canonical_destination = self.install_base.join(relative_destination); | ||
| 954 | CanonicalPair { | ||
| 955 | link_id, | ||
| 956 | origin: canonical_origin, | ||
| 957 | destination: canonical_destination, | ||
| 958 | } | ||
| 959 | } | ||
| 769 | } | 960 | } |
| 770 | 961 | ||
| 771 | pub fn read(depot_path: PathBuf, install_base: PathBuf) -> anyhow::Result<Dotup> { | 962 | pub fn read(depot_path: PathBuf, install_base: PathBuf) -> anyhow::Result<Dotup> { |
| @@ -789,16 +980,26 @@ mod dotup { | |||
| 789 | } | 980 | } |
| 790 | 981 | ||
| 791 | mod utils { | 982 | mod utils { |
| 792 | use std::path::PathBuf; | 983 | use std::path::{Component, Path, PathBuf}; |
| 793 | 984 | ||
| 794 | use crate::{ | 985 | use crate::{ |
| 795 | depot::{self, Depot}, | ||
| 796 | dotup::{self, Dotup}, | 986 | dotup::{self, Dotup}, |
| 797 | Flags, | 987 | Flags, |
| 798 | }; | 988 | }; |
| 799 | 989 | ||
| 800 | pub const DEFAULT_DEPOT_FILE_NAME: &str = ".depot"; | 990 | pub const DEFAULT_DEPOT_FILE_NAME: &str = ".depot"; |
| 801 | 991 | ||
| 992 | /// collects the result of std::fs::read_dir into two vecs, the first one contains all the | ||
| 993 | /// directories and the second one all the files. | ||
| 994 | pub fn collect_read_dir_split( | ||
| 995 | dir: impl AsRef<Path>, | ||
| 996 | ) -> anyhow::Result<(Vec<PathBuf>, Vec<PathBuf>)> { | ||
| 997 | Ok(std::fs::read_dir(dir)? | ||
| 998 | .filter_map(|e| e.ok()) | ||
| 999 | .map(|e| e.path()) | ||
| 1000 | .partition(|p| p.is_dir())) | ||
| 1001 | } | ||
| 1002 | |||
| 802 | pub fn read_dotup(flags: &Flags) -> anyhow::Result<Dotup> { | 1003 | pub fn read_dotup(flags: &Flags) -> anyhow::Result<Dotup> { |
| 803 | let depot_path = depot_path_from_flags(flags)?; | 1004 | let depot_path = depot_path_from_flags(flags)?; |
| 804 | let install_base = install_base_from_flags(flags); | 1005 | let install_base = install_base_from_flags(flags); |
| @@ -809,18 +1010,6 @@ mod utils { | |||
| 809 | dotup::write(dotup) | 1010 | dotup::write(dotup) |
| 810 | } | 1011 | } |
| 811 | 1012 | ||
| 812 | pub fn read_depot(flags: &Flags) -> anyhow::Result<Depot> { | ||
| 813 | let depot_path = depot_path_from_flags(flags)?; | ||
| 814 | let depot = depot::read(&depot_path)?; | ||
| 815 | Ok(depot) | ||
| 816 | } | ||
| 817 | |||
| 818 | pub fn write_depot(flags: &Flags, depot: &Depot) -> anyhow::Result<()> { | ||
| 819 | let depot_path = depot_path_from_flags(flags)?; | ||
| 820 | depot::write(&depot_path, depot)?; | ||
| 821 | Ok(()) | ||
| 822 | } | ||
| 823 | |||
| 824 | pub fn depot_path_from_flags(flags: &Flags) -> anyhow::Result<PathBuf> { | 1013 | pub fn depot_path_from_flags(flags: &Flags) -> anyhow::Result<PathBuf> { |
| 825 | match flags.depot { | 1014 | match flags.depot { |
| 826 | Some(ref path) => Ok(path.clone()), | 1015 | Some(ref path) => Ok(path.clone()), |
| @@ -855,10 +1044,76 @@ mod utils { | |||
| 855 | pub fn default_install_base() -> PathBuf { | 1044 | pub fn default_install_base() -> PathBuf { |
| 856 | PathBuf::from(std::env::var("HOME").expect("Failed to obtain HOME environment variable")) | 1045 | PathBuf::from(std::env::var("HOME").expect("Failed to obtain HOME environment variable")) |
| 857 | } | 1046 | } |
| 1047 | pub fn weakly_canonical(path: impl AsRef<Path>) -> PathBuf { | ||
| 1048 | let cwd = current_working_directory(); | ||
| 1049 | weakly_canonical_cwd(path, cwd) | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | fn weakly_canonical_cwd(path: impl AsRef<Path>, cwd: PathBuf) -> PathBuf { | ||
| 1053 | // Adapated from | ||
| 1054 | // https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61 | ||
| 1055 | let path = path.as_ref(); | ||
| 1056 | |||
| 1057 | let mut components = path.components().peekable(); | ||
| 1058 | let mut canonical = cwd; | ||
| 1059 | let prefix = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { | ||
| 1060 | components.next(); | ||
| 1061 | PathBuf::from(c.as_os_str()) | ||
| 1062 | } else { | ||
| 1063 | PathBuf::new() | ||
| 1064 | }; | ||
| 1065 | |||
| 1066 | for component in components { | ||
| 1067 | match component { | ||
| 1068 | Component::Prefix(_) => unreachable!(), | ||
| 1069 | Component::RootDir => { | ||
| 1070 | canonical = prefix.clone(); | ||
| 1071 | canonical.push(component.as_os_str()) | ||
| 1072 | } | ||
| 1073 | Component::CurDir => {} | ||
| 1074 | Component::ParentDir => { | ||
| 1075 | canonical.pop(); | ||
| 1076 | } | ||
| 1077 | Component::Normal(p) => canonical.push(p), | ||
| 1078 | }; | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | canonical | ||
| 1082 | } | ||
| 858 | 1083 | ||
| 859 | fn current_working_directory() -> PathBuf { | 1084 | pub fn current_working_directory() -> PathBuf { |
| 860 | std::env::current_dir().expect("Failed to obtain current working directory") | 1085 | std::env::current_dir().expect("Failed to obtain current working directory") |
| 861 | } | 1086 | } |
| 1087 | |||
| 1088 | #[cfg(test)] | ||
| 1089 | mod tests { | ||
| 1090 | use super::*; | ||
| 1091 | |||
| 1092 | #[test] | ||
| 1093 | fn weak_canonical_test() { | ||
| 1094 | let cwd = PathBuf::from("/home/user"); | ||
| 1095 | assert_eq!( | ||
| 1096 | PathBuf::from("/home/dest"), | ||
| 1097 | weakly_canonical_cwd("../dest", cwd.clone()) | ||
| 1098 | ); | ||
| 1099 | assert_eq!( | ||
| 1100 | PathBuf::from("/home/dest/configs/init.vim"), | ||
| 1101 | weakly_canonical_cwd("../dest/configs/init.vim", cwd.clone()) | ||
| 1102 | ); | ||
| 1103 | assert_eq!( | ||
| 1104 | PathBuf::from("/dest/configs/init.vim"), | ||
| 1105 | weakly_canonical_cwd("/dest/configs/init.vim", cwd.clone()) | ||
| 1106 | ); | ||
| 1107 | assert_eq!( | ||
| 1108 | PathBuf::from("/home/user/configs/nvim/lua/setup.lua"), | ||
| 1109 | weakly_canonical_cwd("./configs/nvim/lua/setup.lua", cwd.clone()) | ||
| 1110 | ); | ||
| 1111 | assert_eq!( | ||
| 1112 | PathBuf::from("/home/user/configs/nvim/lua/setup.lua"), | ||
| 1113 | weakly_canonical_cwd("configs/nvim/lua/setup.lua", cwd.clone()) | ||
| 1114 | ); | ||
| 1115 | } | ||
| 1116 | } | ||
| 862 | } | 1117 | } |
| 863 | 1118 | ||
| 864 | use std::path::PathBuf; | 1119 | use std::path::PathBuf; |
| @@ -891,6 +1146,7 @@ enum SubCommand { | |||
| 891 | Install(InstallArgs), | 1146 | Install(InstallArgs), |
| 892 | Uninstall(UninstallArgs), | 1147 | Uninstall(UninstallArgs), |
| 893 | Mv(MvArgs), | 1148 | Mv(MvArgs), |
| 1149 | Status(StatusArgs), | ||
| 894 | } | 1150 | } |
| 895 | 1151 | ||
| 896 | fn main() -> anyhow::Result<()> { | 1152 | fn main() -> anyhow::Result<()> { |
| @@ -902,6 +1158,7 @@ fn main() -> anyhow::Result<()> { | |||
| 902 | SubCommand::Install(cmd_args) => command_install(args.flags, cmd_args), | 1158 | SubCommand::Install(cmd_args) => command_install(args.flags, cmd_args), |
| 903 | SubCommand::Uninstall(cmd_args) => command_uninstall(args.flags, cmd_args), | 1159 | SubCommand::Uninstall(cmd_args) => command_uninstall(args.flags, cmd_args), |
| 904 | SubCommand::Mv(cmd_args) => command_mv(args.flags, cmd_args), | 1160 | SubCommand::Mv(cmd_args) => command_mv(args.flags, cmd_args), |
| 1161 | SubCommand::Status(cmd_args) => command_status(args.flags, cmd_args), | ||
| 905 | } | 1162 | } |
| 906 | } | 1163 | } |
| 907 | 1164 | ||
| @@ -962,7 +1219,6 @@ struct InstallArgs { | |||
| 962 | fn command_install(global_flags: Flags, args: InstallArgs) -> anyhow::Result<()> { | 1219 | fn command_install(global_flags: Flags, args: InstallArgs) -> anyhow::Result<()> { |
| 963 | let dotup = utils::read_dotup(&global_flags)?; | 1220 | let dotup = utils::read_dotup(&global_flags)?; |
| 964 | dotup.install(args.paths.into_iter()); | 1221 | dotup.install(args.paths.into_iter()); |
| 965 | utils::write_dotup(&dotup)?; | ||
| 966 | Ok(()) | 1222 | Ok(()) |
| 967 | } | 1223 | } |
| 968 | 1224 | ||
| @@ -974,7 +1230,6 @@ struct UninstallArgs { | |||
| 974 | fn command_uninstall(global_flags: Flags, args: UninstallArgs) -> anyhow::Result<()> { | 1230 | fn command_uninstall(global_flags: Flags, args: UninstallArgs) -> anyhow::Result<()> { |
| 975 | let dotup = utils::read_dotup(&global_flags)?; | 1231 | let dotup = utils::read_dotup(&global_flags)?; |
| 976 | dotup.uninstall(args.paths.into_iter()); | 1232 | dotup.uninstall(args.paths.into_iter()); |
| 977 | utils::write_dotup(&dotup)?; | ||
| 978 | Ok(()) | 1233 | Ok(()) |
| 979 | } | 1234 | } |
| 980 | 1235 | ||
| @@ -995,3 +1250,12 @@ fn command_mv(global_flags: Flags, args: MvArgs) -> anyhow::Result<()> { | |||
| 995 | utils::write_dotup(&dotup)?; | 1250 | utils::write_dotup(&dotup)?; |
| 996 | Ok(()) | 1251 | Ok(()) |
| 997 | } | 1252 | } |
| 1253 | |||
| 1254 | #[derive(Parser, Debug)] | ||
| 1255 | struct StatusArgs {} | ||
| 1256 | |||
| 1257 | fn command_status(global_flags: Flags, _args: StatusArgs) -> anyhow::Result<()> { | ||
| 1258 | let dotup = utils::read_dotup(&global_flags)?; | ||
| 1259 | dotup.status(); | ||
| 1260 | Ok(()) | ||
| 1261 | } | ||
