From ffcb43df8f39a55be468cf4bdfecd72dd026d940 Mon Sep 17 00:00:00 2001 From: diogo464 Date: Fri, 24 Dec 2021 21:01:11 +0000 Subject: initial implementation of status command closes #1 --- Cargo.lock | 1 + dotup_cli/Cargo.toml | 1 + dotup_cli/src/commands/mod.rs | 1 + dotup_cli/src/commands/status.rs | 177 +++++++++++++++++++++++++++++++++++++++ dotup_cli/src/main.rs | 2 + dotup_cli/src/utils.rs | 11 +++ 6 files changed, 193 insertions(+) create mode 100644 dotup_cli/src/commands/status.rs diff --git a/Cargo.lock b/Cargo.lock index ec5730f..dd07c28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,6 +138,7 @@ dependencies = [ name = "dotup_cli" version = "0.1.0" dependencies = [ + "ansi_term", "anyhow", "assert_cmd", "clap", diff --git a/dotup_cli/Cargo.toml b/dotup_cli/Cargo.toml index a0c1a5d..89b8c27 100644 --- a/dotup_cli/Cargo.toml +++ b/dotup_cli/Cargo.toml @@ -11,6 +11,7 @@ doc = false [dependencies] anyhow = "1.0" log = "0.4" +ansi_term = "0.12.1" [dependencies.clap] features = ["derive"] 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 @@ pub mod init; pub mod install; pub mod link; +pub mod status; pub mod uninstall; pub mod unlink; 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 @@ +use std::path::{Path, PathBuf}; + +use ansi_term::Colour; +use clap::Parser; +use dotup::{Depot, Link}; + +use crate::{utils, Config}; + +/// Shows information about links +/// +/// If a link is created for a file that already had a link then the old link will be overwritten. +/// By default creating a link to a directory will recursively link all files under that +/// directory, to actually link a directory use the --directory flag. +#[derive(Parser)] +pub struct Opts { + /// The location where links will be installed to. + /// Defaults to the home directory. + #[clap(long)] + install_base: Option, + + /// The paths to show the status of + paths: Vec, +} + +#[derive(Debug)] +struct State { + max_depth: u32, + install_base: PathBuf, +} + +pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { + let mut depot = utils::read_depot(&config.archive_path)?; + + // walk dir + // if node is file: + // if linked + // print name in green and destination blue + // if invalid + // print name and destination red + // if not linked + // print name in gray + // if node is directory: + // if linked + // print name in green and destination blue + // if invalid + // print name and destination red + // if not linked: + // print name in gray + // if contains files that are linked/invalid: + // recurse into directory + // + + let depot_base = depot.base_path(); + let mut paths = Vec::new(); + for path in opts.paths { + let canonical = dotup::utils::weakly_canonical(&path); + if canonical.starts_with(depot_base) { + paths.push(canonical); + } else { + log::warn!("Path '{}' is outside the depot", path.display()); + } + } + + if paths.is_empty() { + paths.push(PathBuf::from(".")); + } + + let state = State { + max_depth: u32::MAX, + install_base: opts + .install_base + .unwrap_or(utils::home_directory().unwrap()), + }; + + let (directories, files) = utils::collect_read_dir_split(".")?; + for path in directories.into_iter().chain(files.into_iter()) { + display_status_path(&depot, &state, &path, 0); + } + + utils::write_depot(&depot)?; + + Ok(()) +} + +fn display_status_path(depot: &Depot, state: &State, path: &Path, depth: u32) { + if depth == state.max_depth { + return; + } + + if path.is_dir() { + display_status_directory(depot, state, path, depth); + } else { + display_status_file(depot, state, path, depth); + } +} + +fn display_status_directory(depot: &Depot, state: &State, path: &Path, depth: u32) { + assert!(path.is_dir()); + if depth == state.max_depth || !depot.subpath_has_links(path) { + return; + } + + if let Some(link) = depot.get_link_by_path(path) { + print_link(depot, state, link, depth); + } else { + for entry in std::fs::read_dir(path).unwrap() { + let entry = match entry { + Ok(entry) => entry, + Err(_) => continue, + }; + let entry_path = entry.path().canonicalize().unwrap(); + let entry_path_stripped = entry_path + .strip_prefix(std::env::current_dir().unwrap()) + .unwrap(); + + print_identation(depth); + println!( + "{}", + Colour::Yellow.paint(&format!("{}", path.file_name().unwrap().to_string_lossy())) + ); + + display_status_path(depot, state, &entry_path_stripped, depth + 1); + } + } +} + +fn display_status_file(depot: &Depot, state: &State, path: &Path, depth: u32) { + print_identation(depth); + if let Some(link) = depot.get_link_by_path(path) { + print_link(depot, state, link, depth); + } else { + print_unlinked(path, depth); + } +} + +fn print_link(depot: &Depot, state: &State, link: &Link, depth: u32) { + let origin = link.origin(); + let dest = link.destination(); + let filename = match origin.file_name() { + Some(filename) => filename, + None => return, + }; + + print_identation(depth); + if depot.is_link_installed(link, &state.install_base) { + println!( + "{} -> {}", + Colour::Green.paint(&format!("{}", filename.to_string_lossy())), + Colour::Blue.paint(&format!("{}", dest.display())), + ); + } else { + println!( + "{}", + Colour::Red.paint(&format!( + "{} -> {}", + filename.to_string_lossy(), + dest.display() + )) + ); + } +} + +fn print_unlinked(path: &Path, depth: u32) { + let filename = match path.file_name() { + Some(filename) => filename, + None => return, + }; + + print_identation(depth); + println!("{}", filename.to_string_lossy()); +} + +fn print_identation(depth: u32) { + for i in 0..depth { + print!(" "); + } +} 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 { enum SubCommand { Init(commands::init::Opts), Link(commands::link::Opts), + Status(commands::status::Opts), Unlink(commands::unlink::Opts), Install(commands::install::Opts), Uninstall(commands::uninstall::Opts), @@ -86,6 +87,7 @@ fn main() -> anyhow::Result<()> { match opts.subcmd { SubCommand::Init(opts) => commands::init::main(config, opts), SubCommand::Link(opts) => commands::link::main(config, opts), + SubCommand::Status(opts) => commands::status::main(config, opts), SubCommand::Unlink(opts) => commands::unlink::main(config, opts), SubCommand::Install(opts) => commands::install::main(config, opts), 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) -> anyhow::Result, +) -> anyhow::Result<(Vec, Vec)> { + Ok(std::fs::read_dir(dir)? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .partition(|p| p.is_dir())) +} -- cgit