diff options
Diffstat (limited to 'dotup_cli/src')
| -rw-r--r-- | dotup_cli/src/commands/mod.rs | 1 | ||||
| -rw-r--r-- | dotup_cli/src/commands/status.rs | 177 | ||||
| -rw-r--r-- | dotup_cli/src/main.rs | 2 | ||||
| -rw-r--r-- | dotup_cli/src/utils.rs | 11 |
4 files changed, 191 insertions, 0 deletions
diff --git a/dotup_cli/src/commands/mod.rs b/dotup_cli/src/commands/mod.rs index ef7fd0f..94dc3fd 100644 --- a/dotup_cli/src/commands/mod.rs +++ b/dotup_cli/src/commands/mod.rs | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | pub mod init; | 1 | pub mod init; |
| 2 | pub mod install; | 2 | pub mod install; |
| 3 | pub mod link; | 3 | pub mod link; |
| 4 | pub mod status; | ||
| 4 | pub mod uninstall; | 5 | pub mod uninstall; |
| 5 | pub mod unlink; | 6 | pub mod unlink; |
| 6 | 7 | ||
diff --git a/dotup_cli/src/commands/status.rs b/dotup_cli/src/commands/status.rs new file mode 100644 index 0000000..e05ada9 --- /dev/null +++ b/dotup_cli/src/commands/status.rs | |||
| @@ -0,0 +1,177 @@ | |||
| 1 | use std::path::{Path, PathBuf}; | ||
| 2 | |||
| 3 | use ansi_term::Colour; | ||
| 4 | use clap::Parser; | ||
| 5 | use dotup::{Depot, Link}; | ||
| 6 | |||
| 7 | use crate::{utils, Config}; | ||
| 8 | |||
| 9 | /// Shows information about links | ||
| 10 | /// | ||
| 11 | /// If a link is created for a file that already had a link then the old link will be overwritten. | ||
| 12 | /// By default creating a link to a directory will recursively link all files under that | ||
| 13 | /// directory, to actually link a directory use the --directory flag. | ||
| 14 | #[derive(Parser)] | ||
| 15 | pub struct Opts { | ||
| 16 | /// The location where links will be installed to. | ||
| 17 | /// Defaults to the home directory. | ||
| 18 | #[clap(long)] | ||
| 19 | install_base: Option<PathBuf>, | ||
| 20 | |||
| 21 | /// The paths to show the status of | ||
| 22 | paths: Vec<PathBuf>, | ||
| 23 | } | ||
| 24 | |||
| 25 | #[derive(Debug)] | ||
| 26 | struct State { | ||
| 27 | max_depth: u32, | ||
| 28 | install_base: PathBuf, | ||
| 29 | } | ||
| 30 | |||
| 31 | pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { | ||
| 32 | let mut depot = utils::read_depot(&config.archive_path)?; | ||
| 33 | |||
| 34 | // walk dir | ||
| 35 | // if node is file: | ||
| 36 | // if linked | ||
| 37 | // print name in green and destination blue | ||
| 38 | // if invalid | ||
| 39 | // print name and destination red | ||
| 40 | // if not linked | ||
| 41 | // print name in gray | ||
| 42 | // if node is directory: | ||
| 43 | // if linked | ||
| 44 | // print name in green and destination blue | ||
| 45 | // if invalid | ||
| 46 | // print name and destination red | ||
| 47 | // if not linked: | ||
| 48 | // print name in gray | ||
| 49 | // if contains files that are linked/invalid: | ||
| 50 | // recurse into directory | ||
| 51 | // | ||
| 52 | |||
| 53 | let depot_base = depot.base_path(); | ||
| 54 | let mut paths = Vec::new(); | ||
| 55 | for path in opts.paths { | ||
| 56 | let canonical = dotup::utils::weakly_canonical(&path); | ||
| 57 | if canonical.starts_with(depot_base) { | ||
| 58 | paths.push(canonical); | ||
| 59 | } else { | ||
| 60 | log::warn!("Path '{}' is outside the depot", path.display()); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | if paths.is_empty() { | ||
| 65 | paths.push(PathBuf::from(".")); | ||
| 66 | } | ||
| 67 | |||
| 68 | let state = State { | ||
| 69 | max_depth: u32::MAX, | ||
| 70 | install_base: opts | ||
| 71 | .install_base | ||
| 72 | .unwrap_or(utils::home_directory().unwrap()), | ||
| 73 | }; | ||
| 74 | |||
| 75 | let (directories, files) = utils::collect_read_dir_split(".")?; | ||
| 76 | for path in directories.into_iter().chain(files.into_iter()) { | ||
| 77 | display_status_path(&depot, &state, &path, 0); | ||
| 78 | } | ||
| 79 | |||
| 80 | utils::write_depot(&depot)?; | ||
| 81 | |||
| 82 | Ok(()) | ||
| 83 | } | ||
| 84 | |||
| 85 | fn display_status_path(depot: &Depot, state: &State, path: &Path, depth: u32) { | ||
| 86 | if depth == state.max_depth { | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | |||
| 90 | if path.is_dir() { | ||
| 91 | display_status_directory(depot, state, path, depth); | ||
| 92 | } else { | ||
| 93 | display_status_file(depot, state, path, depth); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | fn display_status_directory(depot: &Depot, state: &State, path: &Path, depth: u32) { | ||
| 98 | assert!(path.is_dir()); | ||
| 99 | if depth == state.max_depth || !depot.subpath_has_links(path) { | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | |||
| 103 | if let Some(link) = depot.get_link_by_path(path) { | ||
| 104 | print_link(depot, state, link, depth); | ||
| 105 | } else { | ||
| 106 | for entry in std::fs::read_dir(path).unwrap() { | ||
| 107 | let entry = match entry { | ||
| 108 | Ok(entry) => entry, | ||
| 109 | Err(_) => continue, | ||
| 110 | }; | ||
| 111 | let entry_path = entry.path().canonicalize().unwrap(); | ||
| 112 | let entry_path_stripped = entry_path | ||
| 113 | .strip_prefix(std::env::current_dir().unwrap()) | ||
| 114 | .unwrap(); | ||
| 115 | |||
| 116 | print_identation(depth); | ||
| 117 | println!( | ||
| 118 | "{}", | ||
| 119 | Colour::Yellow.paint(&format!("{}", path.file_name().unwrap().to_string_lossy())) | ||
| 120 | ); | ||
| 121 | |||
| 122 | display_status_path(depot, state, &entry_path_stripped, depth + 1); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | fn display_status_file(depot: &Depot, state: &State, path: &Path, depth: u32) { | ||
| 128 | print_identation(depth); | ||
| 129 | if let Some(link) = depot.get_link_by_path(path) { | ||
| 130 | print_link(depot, state, link, depth); | ||
| 131 | } else { | ||
| 132 | print_unlinked(path, depth); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | fn print_link(depot: &Depot, state: &State, link: &Link, depth: u32) { | ||
| 137 | let origin = link.origin(); | ||
| 138 | let dest = link.destination(); | ||
| 139 | let filename = match origin.file_name() { | ||
| 140 | Some(filename) => filename, | ||
| 141 | None => return, | ||
| 142 | }; | ||
| 143 | |||
| 144 | print_identation(depth); | ||
| 145 | if depot.is_link_installed(link, &state.install_base) { | ||
| 146 | println!( | ||
| 147 | "{} -> {}", | ||
| 148 | Colour::Green.paint(&format!("{}", filename.to_string_lossy())), | ||
| 149 | Colour::Blue.paint(&format!("{}", dest.display())), | ||
| 150 | ); | ||
| 151 | } else { | ||
| 152 | println!( | ||
| 153 | "{}", | ||
| 154 | Colour::Red.paint(&format!( | ||
| 155 | "{} -> {}", | ||
| 156 | filename.to_string_lossy(), | ||
| 157 | dest.display() | ||
| 158 | )) | ||
| 159 | ); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | fn print_unlinked(path: &Path, depth: u32) { | ||
| 164 | let filename = match path.file_name() { | ||
| 165 | Some(filename) => filename, | ||
| 166 | None => return, | ||
| 167 | }; | ||
| 168 | |||
| 169 | print_identation(depth); | ||
| 170 | println!("{}", filename.to_string_lossy()); | ||
| 171 | } | ||
| 172 | |||
| 173 | fn print_identation(depth: u32) { | ||
| 174 | for i in 0..depth { | ||
| 175 | print!(" "); | ||
| 176 | } | ||
| 177 | } | ||
diff --git a/dotup_cli/src/main.rs b/dotup_cli/src/main.rs index fe2196e..05fa850 100644 --- a/dotup_cli/src/main.rs +++ b/dotup_cli/src/main.rs | |||
| @@ -53,6 +53,7 @@ struct Opts { | |||
| 53 | enum SubCommand { | 53 | enum SubCommand { |
| 54 | Init(commands::init::Opts), | 54 | Init(commands::init::Opts), |
| 55 | Link(commands::link::Opts), | 55 | Link(commands::link::Opts), |
| 56 | Status(commands::status::Opts), | ||
| 56 | Unlink(commands::unlink::Opts), | 57 | Unlink(commands::unlink::Opts), |
| 57 | Install(commands::install::Opts), | 58 | Install(commands::install::Opts), |
| 58 | Uninstall(commands::uninstall::Opts), | 59 | Uninstall(commands::uninstall::Opts), |
| @@ -86,6 +87,7 @@ fn main() -> anyhow::Result<()> { | |||
| 86 | match opts.subcmd { | 87 | match opts.subcmd { |
| 87 | SubCommand::Init(opts) => commands::init::main(config, opts), | 88 | SubCommand::Init(opts) => commands::init::main(config, opts), |
| 88 | SubCommand::Link(opts) => commands::link::main(config, opts), | 89 | SubCommand::Link(opts) => commands::link::main(config, opts), |
| 90 | SubCommand::Status(opts) => commands::status::main(config, opts), | ||
| 89 | SubCommand::Unlink(opts) => commands::unlink::main(config, opts), | 91 | SubCommand::Unlink(opts) => commands::unlink::main(config, opts), |
| 90 | SubCommand::Install(opts) => commands::install::main(config, opts), | 92 | SubCommand::Install(opts) => commands::install::main(config, opts), |
| 91 | SubCommand::Uninstall(opts) => commands::uninstall::main(config, opts), | 93 | SubCommand::Uninstall(opts) => commands::uninstall::main(config, opts), |
diff --git a/dotup_cli/src/utils.rs b/dotup_cli/src/utils.rs index a5c7647..be9f3a9 100644 --- a/dotup_cli/src/utils.rs +++ b/dotup_cli/src/utils.rs | |||
| @@ -149,3 +149,14 @@ pub fn collect_files_in_dir(dir: impl Into<PathBuf>) -> anyhow::Result<Vec<PathB | |||
| 149 | 149 | ||
| 150 | Ok(paths) | 150 | Ok(paths) |
| 151 | } | 151 | } |
| 152 | |||
| 153 | /// Collects the result of std::fs::read_dir into two vecs | ||
| 154 | /// The first one contains all the directories and the second one all the files | ||
| 155 | pub fn collect_read_dir_split( | ||
| 156 | dir: impl AsRef<Path>, | ||
| 157 | ) -> anyhow::Result<(Vec<PathBuf>, Vec<PathBuf>)> { | ||
| 158 | Ok(std::fs::read_dir(dir)? | ||
| 159 | .filter_map(|e| e.ok()) | ||
| 160 | .map(|e| e.path()) | ||
| 161 | .partition(|p| p.is_dir())) | ||
| 162 | } | ||
