From 41ac587c3464f3d9f567ece673427d6449d76d57 Mon Sep 17 00:00:00 2001 From: Rasmus Rosengren Date: Fri, 12 Nov 2021 18:32:12 +0100 Subject: [PATCH] rewrite with diff --- Cargo.lock | 287 +++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 12 +-- rustfmt.toml | 1 + src/build.rs | 99 +++++------------ src/check.rs | 36 ------- src/config.rs | 10 ++ src/diff.rs | 53 +++++++++ src/install.rs | 44 ++++++++ src/link.rs | 52 --------- src/main.rs | 121 ++++++++------------- src/opts.rs | 22 ++++ src/tree.rs | 44 -------- src/utils.rs | 68 ++++++++++++ 13 files changed, 540 insertions(+), 309 deletions(-) create mode 100644 rustfmt.toml delete mode 100644 src/check.rs create mode 100644 src/config.rs create mode 100644 src/diff.rs create mode 100644 src/install.rs delete mode 100644 src/link.rs create mode 100644 src/opts.rs delete mode 100644 src/tree.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 043dc7c..e16da90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - [[package]] name = "async-recursion" version = "0.3.2" @@ -45,19 +36,47 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" -version = "2.33.3" +version = "3.0.0-beta.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" dependencies = [ - "ansi_term", "atty", "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", "strsim", + "termcolor", "textwrap", - "unicode-width", - "vec_map", + "unicase", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -66,11 +85,33 @@ version = "0.1.0" dependencies = [ "async-recursion", "clap", + "directories", "futures", + "terminal_size", "tokio", "xdg", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "futures" version = "0.3.16" @@ -165,6 +206,32 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -174,18 +241,74 @@ dependencies = [ "libc", ] +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -196,6 +319,21 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "os_str_bytes" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" +dependencies = [ + "memchr", +] + [[package]] name = "pin-project-lite" version = "0.2.7" @@ -208,6 +346,30 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -238,6 +400,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.4" @@ -246,9 +436,9 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -261,11 +451,30 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "textwrap" -version = "0.11.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" dependencies = [ "unicode-width", ] @@ -277,9 +486,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92036be488bb6594459f2e03b60e42df6f937fe6ca5c5ffdcb539c6b84dc40f5" dependencies = [ "autocfg", + "bytes", + "libc", + "mio", "num_cpus", + "once_cell", "pin-project-lite", + "signal-hook-registry", "tokio-macros", + "winapi", ] [[package]] @@ -293,6 +508,21 @@ dependencies = [ "syn", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + [[package]] name = "unicode-width" version = "0.1.8" @@ -306,10 +536,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] -name = "vec_map" -version = "0.8.2" +name = "version_check" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "winapi" @@ -327,6 +563,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 436e1d0..98fa364 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,14 @@ [package] name = "dfm" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[profile.release] -lto = true -codegen-units = 1 - [dependencies] async-recursion = "0.3" -clap = "2.33" +clap = "3.0.0-beta.5" +directories = "4.0" futures = "0.3" -tokio = { version = "1.10", features = ["fs", "macros", "rt-multi-thread"] } +terminal_size = "0.1" +tokio = { version = "1.10", features = ["fs", "macros", "process", "rt-multi-thread"] } xdg = "2.2" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/src/build.rs b/src/build.rs index ea77f35..39ed799 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,71 +1,30 @@ -use std::path::PathBuf; - -use async_recursion::async_recursion; -use futures::future::join_all; -use tokio::fs::{copy, create_dir, read_dir}; - -use crate::Context; - -pub async fn build_tree(context: &Context) -> std::io::Result<()> { - dir(context, PathBuf::new()).await -} - -#[async_recursion] -async fn dir(context: &Context, relative_path: PathBuf) -> std::io::Result<()> { - let source_path = context.tree_source_dir.join(&relative_path); - let build_path = context.tree_build_dir.join(&relative_path); - - match create_dir(build_path).await { - Err(e) if e.kind() != std::io::ErrorKind::AlreadyExists => { - return Err(e); - } - _ => (), - } - - let mut dir_walker = read_dir(source_path).await?; - - let mut dir_tasks = vec![]; - let mut file_tasks = vec![]; - - while let Some(entry) = dir_walker.next_entry().await? { - let metadata = entry.metadata().await?; - let os_name = entry.file_name(); - let name = os_name.to_str(); - if name == Some(".git") { - continue; - } - - let relative_path = relative_path.join(entry.file_name()); - - if metadata.is_dir() { - dir_tasks.push(dir(context, relative_path)); - } else if metadata.is_file() { - file_tasks.push(file(context, relative_path)); - } - } - - let dir_tasks = async { - join_all(dir_tasks) - .await - .into_iter() - .collect::>>() - }; - let file_tasks = async { - join_all(file_tasks) - .await - .into_iter() - .collect::>>() - }; - - tokio::try_join!(dir_tasks, file_tasks)?; - - Ok(()) -} - -async fn file(context: &Context, relative_path: PathBuf) -> std::io::Result<()> { - let source_path = context.tree_source_dir.join(&relative_path); - let build_path = context.tree_build_dir.join(&relative_path); - - copy(source_path, build_path).await?; - Ok(()) +use crate::{ + config::Config, + utils::{get_tree_files, remove_dir_if_empty}, +}; + +pub async fn build(config: &Config) -> std::io::Result<()> { + let source_files = get_tree_files(config, &config.source_dir).await?; + let build_files = get_tree_files(config, &config.build_dir).await?; + + for file_path in source_files.iter() { + if let Some(folder_path) = file_path.parent() { + let dir = config.build_dir.join(folder_path); + tokio::fs::create_dir_all(dir).await?; + } + + let from = config.source_dir.join(file_path); + let to = config.build_dir.join(file_path); + tokio::fs::copy(from, to).await?; + } + + for file_path in build_files { + if !source_files.contains(&file_path) { + let file = config.build_dir.join(file_path); + tokio::fs::remove_file(file.clone()).await?; + remove_dir_if_empty(file.parent().unwrap()).await?; + } + } + + Ok(()) } diff --git a/src/check.rs b/src/check.rs deleted file mode 100644 index e04f13c..0000000 --- a/src/check.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::path::PathBuf; - -use tokio::fs::read; - -use crate::{tree::get_tree_files, Context}; - -pub async fn check_tree( - context: &Context, -) -> std::io::Result<(Vec, Vec, Vec)> { - let source_files = get_tree_files(&context.tree_source_dir).await?; - let installed_files = get_tree_files(&context.tree_install_dir).await?; - - let mut deleted_files = vec![]; - let mut changed_files = vec![]; - for installed_file in installed_files.iter() { - if source_files.contains(installed_file) { - let source_file_content = read(context.tree_build_dir.join(installed_file)).await?; - let installed_file_content = - read(context.tree_install_dir.join(installed_file)).await?; - if source_file_content != installed_file_content { - changed_files.push(installed_file.clone()); - } - } else { - deleted_files.push(installed_file.clone()); - } - } - - let mut added_files = vec![]; - for source_file in source_files.iter() { - if !installed_files.contains(source_file) { - added_files.push(source_file.clone()); - } - } - - Ok((added_files, changed_files, deleted_files)) -} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..cb76863 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,10 @@ +use std::path::PathBuf; + +#[derive(Debug)] +pub struct Config { + pub source_dir: PathBuf, + pub build_dir: PathBuf, + pub install_dir: PathBuf, + pub link_dir: PathBuf, + pub ignored_dirs: Vec, +} diff --git a/src/diff.rs b/src/diff.rs new file mode 100644 index 0000000..97a34d3 --- /dev/null +++ b/src/diff.rs @@ -0,0 +1,53 @@ +use std::{io::Write, path::PathBuf}; + +use terminal_size::{terminal_size, Width}; + +use crate::{config::Config, utils::get_tree_files}; + +pub async fn diff(config: &Config, diff_command: String) -> std::io::Result<()> { + let built_files = get_tree_files(config, &config.build_dir).await?; + let installed_files = get_tree_files(config, &config.install_dir).await?; + + let mut all_files = built_files.clone(); + all_files.extend(installed_files.clone()); + let mut all_files: Vec<_> = all_files.iter().collect(); + all_files.sort(); + + let (Width(terminal_width), _) = terminal_size().expect("Could not get terminal size"); + for file in all_files { + let is_added = built_files.get(file).is_some() && installed_files.get(file).is_none(); + let is_removed = built_files.get(file).is_none() && installed_files.get(file).is_some(); + + let (workdir, first_path, second_path) = if is_added { + ( + config.build_dir.clone(), + file.clone(), + PathBuf::from("/dev/null"), + ) + } else if is_removed { + ( + config.install_dir.clone(), + PathBuf::from("/dev/null"), + file.clone(), + ) + } else { + ( + config.build_dir.clone(), + file.clone(), + config.install_dir.join(file), + ) + }; + + let output = tokio::process::Command::new(&diff_command) + .current_dir(workdir) + .arg(second_path) + .arg(first_path) + .arg("-w") + .arg(terminal_width.to_string()) + .output() + .await?; + std::io::stdout().write_all(&output.stdout)?; + } + + Ok(()) +} diff --git a/src/install.rs b/src/install.rs new file mode 100644 index 0000000..2672227 --- /dev/null +++ b/src/install.rs @@ -0,0 +1,44 @@ +use crate::{ + config::Config, + utils::{get_tree_files, remove_dir_if_empty}, +}; + +pub async fn install(config: &Config) -> std::io::Result<()> { + let built_files = get_tree_files(config, &config.build_dir).await?; + let installed_files = get_tree_files(config, &config.install_dir).await?; + + for file_path in built_files.iter() { + if let Some(folder_path) = file_path.parent() { + let dir = config.build_dir.join(folder_path); + tokio::fs::create_dir_all(dir).await?; + } + + let from = config.source_dir.join(file_path); + let to = config.build_dir.join(file_path); + tokio::fs::copy(from, to).await?; + + if let Some(folder_path) = file_path.parent() { + let dir = config.link_dir.join(folder_path); + tokio::fs::create_dir_all(dir).await?; + } + + tokio::fs::symlink( + config.install_dir.join(&file_path), + config.link_dir.join(&file_path), + ) + .await?; + } + + for file_path in installed_files { + if !built_files.contains(&file_path) { + let installed_file = config.install_dir.join(&file_path); + let linked_file = config.link_dir.join(&file_path); + tokio::fs::remove_file(linked_file.clone()).await?; + remove_dir_if_empty(linked_file.parent().unwrap()).await?; + tokio::fs::remove_file(installed_file.clone()).await?; + remove_dir_if_empty(installed_file.parent().unwrap()).await?; + } + } + + Ok(()) +} diff --git a/src/link.rs b/src/link.rs deleted file mode 100644 index 994056a..0000000 --- a/src/link.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::path::PathBuf; - -use tokio::fs::{copy, create_dir_all, remove_file, symlink}; - -use crate::{check::check_tree, Context}; - -pub async fn link_tree(context: &Context) -> std::io::Result<()> { - let (add, update, remove) = check_tree(context).await?; - for file in add { - copy_and_link(context, file).await?; - } - - for file in update { - copy_and_link(context, file).await?; - } - - for file in remove { - remove_file(context.link_root_dir.join(file)).await?; - } - - Ok(()) -} - -async fn copy_and_link(context: &Context, relative_path: PathBuf) -> std::io::Result<()> { - // Create all necessary directories that are accessed - let mut current_install_dir = context.tree_install_dir.join(&relative_path); - current_install_dir.pop(); - let mut current_link_dir = context.link_root_dir.join(&relative_path); - current_link_dir.pop(); - create_dir_all(current_install_dir).await?; - create_dir_all(current_link_dir).await?; - - // Copy from build to install - copy( - context.tree_build_dir.join(&relative_path), - context.tree_install_dir.join(&relative_path), - ) - .await?; - - // Make sure symlink doesn't exist before attempting to symlink - match remove_file(context.link_root_dir.join(&relative_path)).await { - Ok(_) => {} - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => return Err(e), - }; - symlink( - context.tree_install_dir.join(&relative_path), - context.link_root_dir.join(&relative_path), - ) - .await?; - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 79c96f0..3afd855 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,88 +1,51 @@ mod build; -mod check; -mod link; -mod tree; +mod config; +mod diff; +mod install; +mod opts; +mod utils; use std::path::PathBuf; -use build::build_tree; -use check::check_tree; -use link::link_tree; +use clap::Parser; +use directories::{ProjectDirs, UserDirs}; -const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); +use crate::{ + build::build, config::Config, diff::diff, install::install, opts::Opts, + utils::remove_dir_if_empty, +}; #[tokio::main] async fn main() -> std::io::Result<()> { - let app = clap::App::new("DotFiles Manager") - .version(APP_VERSION) - .author("Rasmus Rosengren ") - .about("Utility to manage dotfiles") - .arg( - clap::Arg::with_name("dry-run") - .long("dry-run") - .short("d") - .help("Report changes without installing"), - ) - .arg( - clap::Arg::with_name("repo-path") - .long("repo-path") - .short("r") - .default_value(".df") - .help("Location of repo, relative to $HOME"), - ); - - let matches = app.get_matches(); - - let home_dir = std::env::var("HOME").expect("$HOME variable not found"); - let xdg_dirs = xdg::BaseDirectories::with_prefix("dfm").expect("xdg dirs"); - - let context = Context { - tree_source_dir: format!("{}/{}", home_dir, matches.value_of("repo-path").unwrap()).into(), - tree_build_dir: xdg_dirs - .create_cache_directory("tree") - .expect("xdg cache dir"), - tree_install_dir: xdg_dirs - .create_config_directory("tree") - .expect("xdg config dir"), - link_root_dir: home_dir.into(), - }; - - build_tree(&context).await?; - if matches.is_present("dry-run") { - let (add, change, delete) = check_tree(&context).await?; - if !add.is_empty() { - println!("The following files have been added:"); - for file in add { - println!("\t{}", file.to_string_lossy()); - } - println!(); - } - - if !change.is_empty() { - println!("The following files have been changed:"); - for file in change { - println!("\t{}", file.to_string_lossy()); - } - println!(); - } - - if !delete.is_empty() { - println!("The following files have been deleted:"); - for file in delete { - println!("\t{}", file.to_string_lossy()); - } - println!(); - } - } else { - link_tree(&context).await?; - } - - Ok(()) -} - -pub struct Context { - tree_source_dir: PathBuf, - tree_build_dir: PathBuf, - tree_install_dir: PathBuf, - link_root_dir: PathBuf, + let opts = Opts::parse(); + + let user_dirs = UserDirs::new().expect("Could not find user directories"); + let project_dirs = + ProjectDirs::from("se", "rsrp", "dfm").expect("Could not find project directories"); + + let repo_path = if opts.repo_path.is_relative() { + let mut repo_path = PathBuf::from(user_dirs.home_dir()); + repo_path.push(opts.repo_path); + repo_path + } else { + opts.repo_path + }; + + let config = Config { + source_dir: repo_path, + build_dir: project_dirs.cache_dir().to_path_buf().join("tree"), + install_dir: project_dirs.config_dir().to_path_buf().join("tree"), + link_dir: user_dirs.home_dir().to_path_buf(), + ignored_dirs: vec![".git".to_string()], + }; + + remove_dir_if_empty(&PathBuf::from("/home/rosen/test")).await?; + build(&config).await?; + if opts.install { + install(&config).await?; + } else { + diff(&config, opts.diff_command).await?; + } + + Ok(()) } diff --git a/src/opts.rs b/src/opts.rs new file mode 100644 index 0000000..78dd3b2 --- /dev/null +++ b/src/opts.rs @@ -0,0 +1,22 @@ +use std::path::PathBuf; + +use clap::Parser; + +use crate::utils::APP_VERSION; + +#[derive(Debug, Parser)] +#[clap( + version = APP_VERSION, + author = "Rasmus Rosengren ", + about = "Utility to manage dotfiles" +)] +pub struct Opts { + #[clap(short, long)] + pub install: bool, + + #[clap(short, long, default_value = ".df")] + pub repo_path: PathBuf, + + #[clap(short, long, default_value = "delta")] + pub diff_command: String, +} diff --git a/src/tree.rs b/src/tree.rs deleted file mode 100644 index 12568b2..0000000 --- a/src/tree.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::{ - collections::HashSet, - path::{Path, PathBuf}, -}; - -use async_recursion::async_recursion; -use futures::future::try_join_all; -use tokio::fs::read_dir; - -pub async fn get_tree_files(tree_root_path: &Path) -> std::io::Result> { - dir(tree_root_path, PathBuf::new()).await -} - -#[async_recursion] -async fn dir(tree_root_path: &Path, relative_path: PathBuf) -> std::io::Result> { - let current_path = tree_root_path.join(&relative_path); - - let mut dir_walker = read_dir(¤t_path).await?; - let mut dir_tasks = vec![]; - - let mut files = HashSet::new(); - - while let Some(entry) = dir_walker.next_entry().await? { - let metadata = entry.metadata().await?; - let os_name = entry.file_name(); - let name = os_name.to_string_lossy().to_string(); - if name == ".git" { - continue; - } - - if metadata.is_dir() { - dir_tasks.push(dir(tree_root_path, relative_path.join(name))); - } else if metadata.is_file() { - files.insert(relative_path.join(name)); - } - } - - let file_sets = try_join_all(dir_tasks).await?; - for file_set in file_sets { - files.extend(file_set); - } - - Ok(files) -} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..3792c56 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,68 @@ +use std::{ + collections::HashSet, + path::{Path, PathBuf}, +}; + +use futures::future::try_join_all; +use tokio::fs::read_dir; + +use crate::config::Config; + +pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub async fn get_tree_files( + config: &Config, + tree_root: &Path, +) -> std::io::Result> { + get_tree_files_recursively(config, tree_root, PathBuf::new()).await +} + +#[async_recursion::async_recursion] +async fn get_tree_files_recursively( + config: &Config, + tree_root: &Path, + relative_path: PathBuf, +) -> std::io::Result> { + let current_path = tree_root.join(&relative_path); + + let mut dir_walker = read_dir(¤t_path).await?; + let mut dir_tasks = vec![]; + + let mut files = HashSet::new(); + + while let Some(entry) = dir_walker.next_entry().await? { + let metadata = entry.metadata().await?; + let os_name = entry.file_name(); + let name = os_name.to_string_lossy().to_string(); + if config.ignored_dirs.contains(&name) { + continue; + } + + if metadata.is_dir() { + dir_tasks.push(get_tree_files_recursively( + config, + tree_root, + relative_path.join(name), + )); + } else if metadata.is_file() { + files.insert(relative_path.join(name)); + } + } + + let file_sets = try_join_all(dir_tasks).await?; + for file_set in file_sets { + files.extend(file_set); + } + + Ok(files) +} + +pub async fn remove_dir_if_empty(path: &Path) -> std::io::Result<()> { + let mut dir_walker = tokio::fs::read_dir(path).await?; + + if dir_walker.next_entry().await?.is_none() { + tokio::fs::remove_dir(path).await?; + } + + Ok(()) +}