From ed0baec0a3f953c99445f6842dadc5566e89cb75 Mon Sep 17 00:00:00 2001 From: diogo464 Date: Thu, 8 Jul 2021 17:11:46 -0400 Subject: Initial commit --- dotup_cli/src/commands/init.rs | 20 ++++ dotup_cli/src/commands/install.rs | 31 ++++++ dotup_cli/src/commands/link.rs | 181 ++++++++++++++++++++++++++++++++++++ dotup_cli/src/commands/mod.rs | 11 +++ dotup_cli/src/commands/uninstall.rs | 31 ++++++ dotup_cli/src/commands/unlink.rs | 38 ++++++++ dotup_cli/src/commands/utils.rs | 100 ++++++++++++++++++++ 7 files changed, 412 insertions(+) create mode 100644 dotup_cli/src/commands/init.rs create mode 100644 dotup_cli/src/commands/install.rs create mode 100644 dotup_cli/src/commands/link.rs create mode 100644 dotup_cli/src/commands/mod.rs create mode 100644 dotup_cli/src/commands/uninstall.rs create mode 100644 dotup_cli/src/commands/unlink.rs create mode 100644 dotup_cli/src/commands/utils.rs (limited to 'dotup_cli/src/commands') diff --git a/dotup_cli/src/commands/init.rs b/dotup_cli/src/commands/init.rs new file mode 100644 index 0000000..bfec6ca --- /dev/null +++ b/dotup_cli/src/commands/init.rs @@ -0,0 +1,20 @@ +use clap::Clap; + +use super::prelude::*; + +#[derive(Clap)] +pub struct Opts {} + +pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { + if !dotup::utils::is_file(&config.archive_path)? { + let archive = Archive::default(); + log::info!("Creating archive"); + utils::write_archive(&config.archive_path, &archive)?; + } else { + log::info!( + "Archive file already exists : {}", + config.archive_path.display() + ); + } + Ok(()) +} diff --git a/dotup_cli/src/commands/install.rs b/dotup_cli/src/commands/install.rs new file mode 100644 index 0000000..72cabf3 --- /dev/null +++ b/dotup_cli/src/commands/install.rs @@ -0,0 +1,31 @@ +use clap::Clap; +use std::path::PathBuf; + +use super::prelude::*; + +#[derive(Clap)] +pub struct Opts { + /// The location where links will be installed to. + /// Defaults to home directory. + #[clap(long)] + install_base: Option, + + /// The files/directories to install + #[clap(required = true, min_values = 1)] + paths: Vec, +} + +pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { + let install_base = match opts.install_base { + Some(path) => path, + None => utils::home_directory()?, + }; + let depot = utils::read_depot(&config.archive_path)?; + + for link in utils::collect_links_by_base_paths(&depot, &opts.paths) { + log::info!("Installing link {}", link); + depot.install_link(link, &install_base)?; + } + + Ok(()) +} diff --git a/dotup_cli/src/commands/link.rs b/dotup_cli/src/commands/link.rs new file mode 100644 index 0000000..fd99253 --- /dev/null +++ b/dotup_cli/src/commands/link.rs @@ -0,0 +1,181 @@ +use clap::Clap; +use std::{ + fs::{DirEntry, Metadata}, + path::{Path, PathBuf}, +}; + +use super::prelude::*; + +#[derive(Clap)] +pub struct Opts { + #[clap(long)] + directory: bool, + + paths: Vec, +} + +/* + config/ + nvim/ + init.vim + lua/ + setup.lua + bash/ + .bashrc + + link nvim .config/nvim + nvim/init.vim -> .config/nvim/init.vim + nvim/lua/setup.lua -> config/nvim/lua/setup.lua + + link bash . + bash/.bashrc -> ./.bashrc + + link --directory scripts .scripts + scripts/ -> ./.scripts +*/ + +pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { + let mut depot = utils::read_depot(&config.archive_path)?; + + let (origins, destination) = match opts.paths.as_slice() { + p @ [] | p @ [_] => (p, None), + [o @ .., dest] => (o, Some(dest)), + _ => unreachable!(), + }; + + if let Some(destination) = destination { + for path in collect_file_type(origins, FileType::File)? { + let link_desc = LinkDesc { + origin: path, + destination: destination.clone(), + }; + log::info!("Creating link : {}", link_desc); + depot.create_link(link_desc)?; + } + } else { + let base_path = match origins { + [] => std::env::current_dir()?, + [path] => path.clone(), + _ => unreachable!(), + }; + + for link in utils::collect_links_by_base_paths(&depot, std::iter::once(base_path)) { + log::info!("{}", link); + } + } + + //if let Some(destination) = destination { + // for origin in origins { + // let origin_canonical = origin.canonicalize()?; + // let base = if origin_canonical.is_file() { + // origin_canonical.parent().unwrap().to_path_buf() + // } else { + // origin_canonical.to_path_buf() + // }; + + // link(&mut depot, origin.as_path(), destination.as_path(), &base)?; + // } + //} else { + // log::warn!("Missing destination"); + //} + + utils::write_depot(&depot)?; + + Ok(()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum FileType { + File, + Directory, +} + +/// Collects canonical files of the given type starting from, and including, entry_paths +fn collect_file_type( + entry_paths: impl IntoIterator>, + collect_type: FileType, +) -> anyhow::Result> { + let entry_paths: Vec = entry_paths + .into_iter() + .map(|p| p.as_ref().to_path_buf()) + .collect(); + let mut collected = Vec::new(); + let mut pending: Vec<_> = entry_paths.iter().cloned().filter(|p| p.is_dir()).collect(); + + for path in entry_paths { + let path = path.canonicalize()?; + if (path.is_file() && collect_type == FileType::File) + || (path.is_dir() && collect_type == FileType::Directory) + { + collected.push(path); + } + } + + while let Some(dir_path) = pending.pop() { + for entry in dir_path.read_dir()? { + let entry = entry?; + let filetype = entry.file_type()?; + + if filetype.is_file() && collect_type == FileType::File { + collected.push(entry.path()); + } else if filetype.is_dir() { + if collect_type == FileType::Directory { + collected.push(entry.path()); + } + pending.push(entry.path()); + } + } + } + + Ok(collected) +} + +fn link(depot: &mut Depot, origin: &Path, destination: &Path, base: &Path) -> anyhow::Result<()> { + let metadata = std::fs::metadata(origin)?; + if metadata.is_file() { + link_file(depot, origin, destination, base)?; + } else if metadata.is_dir() { + link_directory_recursive(depot, origin, destination, base)?; + } else { + unimplemented!() + } + Ok(()) +} + +fn link_file( + depot: &mut Depot, + origin: &Path, + destination: &Path, + base: &Path, +) -> anyhow::Result<()> { + let origin_canonical = origin + .canonicalize() + .expect("Failed to canonicalize origin path"); + let partial = origin_canonical + .strip_prefix(base) + .expect("Failed to remove prefix from origin path"); + let destination = destination.join(partial); + + let link_desc = LinkDesc { + origin: origin_canonical, + destination, + }; + + log::debug!("Linking file {:#?}", link_desc); + depot.create_link(link_desc)?; + + Ok(()) +} + +fn link_directory_recursive( + depot: &mut Depot, + dir_path: &Path, + destination: &Path, + base: &Path, +) -> anyhow::Result<()> { + for origin in dir_path.read_dir()? { + let origin = origin?.path(); + link(depot, &origin, destination, base)?; + } + Ok(()) +} diff --git a/dotup_cli/src/commands/mod.rs b/dotup_cli/src/commands/mod.rs new file mode 100644 index 0000000..f372662 --- /dev/null +++ b/dotup_cli/src/commands/mod.rs @@ -0,0 +1,11 @@ +pub mod init; +pub mod install; +pub mod link; +pub mod uninstall; +pub mod unlink; +pub mod utils; + +mod prelude { + pub use super::utils; + pub use crate::prelude::*; +} diff --git a/dotup_cli/src/commands/uninstall.rs b/dotup_cli/src/commands/uninstall.rs new file mode 100644 index 0000000..dad55a5 --- /dev/null +++ b/dotup_cli/src/commands/uninstall.rs @@ -0,0 +1,31 @@ +use clap::Clap; +use std::path::PathBuf; + +use super::prelude::*; + +#[derive(Clap)] +pub struct Opts { + /// The location where links will be uninstalled from. + /// Defaults to home directory. + #[clap(long)] + install_base: Option, + + /// The files/directories to uninstall + #[clap(required = true, min_values = 1)] + paths: Vec, +} + +pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { + let install_base = match opts.install_base { + Some(path) => path, + None => utils::home_directory()?, + }; + let depot = utils::read_depot(&config.archive_path)?; + + for link in utils::collect_links_by_base_paths(&depot, &opts.paths) { + log::info!("Uninstalling link : {}", link); + depot.uninstall_link(link, &install_base)?; + } + + Ok(()) +} diff --git a/dotup_cli/src/commands/unlink.rs b/dotup_cli/src/commands/unlink.rs new file mode 100644 index 0000000..7ebb19c --- /dev/null +++ b/dotup_cli/src/commands/unlink.rs @@ -0,0 +1,38 @@ +use clap::Clap; +use std::{ + fs::{DirEntry, Metadata}, + path::{Path, PathBuf}, +}; + +use super::prelude::*; + +#[derive(Clap)] +pub struct Opts { + /// Specifies the install base if the links are also to be uninstalled. + #[clap(long)] + uninstall: Option, + + #[clap(required = true, min_values = 1)] + paths: Vec, +} + +pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> { + let mut depot = utils::read_depot(&config.archive_path)?; + + for link_id in utils::collect_link_ids_by_base_paths(&depot, &opts.paths) { + let link = depot.get_link(link_id).unwrap(); + log::info!( + "Unlinking(uninstall = {}) : {}", + opts.uninstall.is_some(), + link + ); + if let Some(ref install_base) = opts.uninstall { + depot.uninstall_link(link, &install_base)?; + } + depot.remove_link(link_id); + } + + utils::write_depot(&depot)?; + + Ok(()) +} diff --git a/dotup_cli/src/commands/utils.rs b/dotup_cli/src/commands/utils.rs new file mode 100644 index 0000000..4160078 --- /dev/null +++ b/dotup_cli/src/commands/utils.rs @@ -0,0 +1,100 @@ +use std::path::{Path, PathBuf}; + +use crate::prelude::*; + +pub fn home_directory() -> anyhow::Result { + match std::env::var("HOME") { + Ok(val) => Ok(PathBuf::from(val)), + Err(e) => { + log::error!("Failed to get home directory from enviornment variable"); + Err(e.into()) + } + } +} + +pub fn write_archive(path: impl AsRef, archive: &Archive) -> anyhow::Result<()> { + let path = path.as_ref(); + log::debug!("Writing archive to {}", path.display()); + match dotup::archive_write(path, archive) { + Ok(_) => Ok(()), + Err(e) => { + log::error!( + "Failed to write archive to : {}\nError : {}", + path.display(), + e + ); + Err(e.into()) + } + } +} + +pub fn write_depot(depot: &Depot) -> anyhow::Result<()> { + let write_path = depot.archive_path(); + let archive = depot.archive(); + match dotup::archive_write(write_path, &archive) { + Ok(_) => Ok(()), + Err(e) => { + log::error!( + "Failed to write depot archive to : {}\nError : {}", + write_path.display(), + e + ); + Err(e.into()) + } + } +} + +pub fn read_archive(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref(); + match dotup::archive_read(path) { + Ok(archive) => Ok(archive), + Err(e) => { + log::error!( + "Failed to read archive from : {}\nError : {}", + path.display(), + e + ); + Err(e.into()) + } + } +} + +pub fn read_depot(archive_path: impl AsRef) -> anyhow::Result { + let archive_path = archive_path.as_ref().to_path_buf(); + let archive = read_archive(&archive_path)?; + let depot_config = DepotConfig { + archive_path, + archive, + }; + let depot = Depot::new(depot_config)?; + Ok(depot) +} + +pub fn collect_links_by_base_paths( + depot: &Depot, + paths: impl IntoIterator>, +) -> Vec<&Link> { + let canonical_paths: Vec<_> = paths + .into_iter() + .map(|p| p.as_ref().canonicalize().unwrap()) + .collect(); + + depot + .links() + .filter(|&l| { + canonical_paths + .iter() + .any(|p| l.origin_canonical().starts_with(p)) + }) + .collect() +} + +pub fn collect_link_ids_by_base_paths( + depot: &Depot, + paths: impl IntoIterator>, +) -> Vec { + collect_links_by_base_paths(depot, paths) + .into_iter() + .map(|l| l.id()) + .collect() +} -- cgit