From 8d02282bf7679a6cdd8bda3965fff8313f76980b Mon Sep 17 00:00:00 2001 From: Seivan Date: Thu, 18 Jul 2024 15:41:10 +0200 Subject: [PATCH] feature: allow reading `RHACK_DIR` from `.cargo/config.toml` files. Order: `RHACK_DIR` env variable, local `.cargo/config.toml`, `$CARGO_HOME/config.toml`. --- .cargo/config.toml | 3 + Cargo.toml | 8 +-- src/cmd.rs | 160 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 154 insertions(+), 17 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2179117 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ + +[env] +RHACK_DIR = "./.rhack" diff --git a/Cargo.toml b/Cargo.toml index 9feefe2..f8c05ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ pr-run-mode = "plan" # see: https://nnethercote.github.io/perf-book/build-configuration.html [profile.dev] opt-level = 0 -debug = 0 # try for faster builds, usually 'true' -strip = "debuginfo" # try for faster builds +debug = 0 # try for faster builds, usually 'true' +strip = "debuginfo" # try for faster builds rpath = false lto = false debug-assertions = true @@ -71,7 +71,7 @@ debug = true [profile.release] opt-level = 3 -debug = false # true for profiling +debug = false # true for profiling rpath = false lto = "fat" debug-assertions = false @@ -89,7 +89,7 @@ codegen-units = 4 [profile.bench] opt-level = 3 -debug = true # true for profiling +debug = true # true for profiling rpath = false lto = true debug-assertions = false diff --git a/src/cmd.rs b/src/cmd.rs index 4438fae..ef90e88 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -1,14 +1,13 @@ mod edit; mod undo; +use anyhow::{anyhow, Result}; +use clap::{Parser, Subcommand}; +use serde_json::Value; use std::fs; use std::path::PathBuf; use std::process::Command; use std::{env, str::FromStr}; - -use anyhow::{anyhow, Result}; -use clap::{Parser, Subcommand}; -use serde_json::Value; use toml_edit::DocumentMut; pub use edit::Edit; @@ -18,6 +17,12 @@ const DEFAULT_RHACK_DIR_NAME: &str = ".rhack"; const RHACK_DIR_ENV_KEY: &str = "RHACK_DIR"; const PATCH_TABLE_NAME: &str = "patch"; const REGISTRY_TABLE_NAME: &str = "crates-io"; +const CARGO_HOME_ENV_KEY: &str = if cfg!(test) { + "CARGO_HOME_TEST" +} else { + "CARGO_HOME" +}; +const CARGO_CONFIG_FILE_NAME: &str = "config.toml"; pub trait Cmd { fn run(&self) -> Result<()>; @@ -54,18 +59,68 @@ impl Cmd for Commands { } } +pub fn load_path_from_config() -> Result { + let get_path_from_config_env: Result = { + let parse_config = |s: PathBuf| { + // type TomlMap = HashMap>; + Result::Ok(s) + .map(|cargo_dir| cargo_dir.join(CARGO_CONFIG_FILE_NAME)) + .and_then(fs::read_to_string) + .map_err(From::from) + .and_then(|config_text| Ok(config_text.parse::()?)) + .and_then(|mut x| { + x.remove("env") + .ok_or_else(|| anyhow!("Failed to find 'env' key in config.toml.")) + }) + .and_then(|x| { + let p = x + .get("RHACK_DIR") + .filter(|x| x.is_str()) + .ok_or_else(|| anyhow!("Failed to find 'RHACK_DIR' key in config.toml.")) + // .filter(|x| x.is_str()) + .and_then(|x| { + x.as_str() + .filter(|s| s.is_empty() == false) + .ok_or_else(|| anyhow!("RHACK_DIR is empty.")) + }) + .map(|x| x.to_string()); + + p + }) + .and_then(|p| Ok(PathBuf::from_str(&p)?)) + }; + let cwd_config = || { + env::current_dir() + .map_err(From::from) + .map(|cwd| cwd.join(".cargo")) + }; + let global_config = || { + env::var(CARGO_HOME_ENV_KEY) + .map_err(From::from) + .and_then(|x| Ok(PathBuf::from_str(&x)?)) + }; + + if cfg!(test) { + global_config().or_else(|_| cwd_config()) + } else { + cwd_config().or_else(|_| global_config()) + } + .and_then(parse_config) + }; + get_path_from_config_env +} // Gives back user-difined rhack dir path. If none, the default will be given. #[must_use] pub fn rhack_dir() -> PathBuf { - env::var(RHACK_DIR_ENV_KEY).map_or_else( - |_| { - let Some(home_dir) = home::home_dir() else { - panic!("failed to find home directory") - }; - home_dir.join(DEFAULT_RHACK_DIR_NAME) - }, - PathBuf::from, - ) + env::var(RHACK_DIR_ENV_KEY) + .map(PathBuf::from) + .or_else(|_| load_path_from_config()) + .or_else(|_| { + home::home_dir() + .map(|dir| dir.join(DEFAULT_RHACK_DIR_NAME)) + .ok_or(anyhow!("Failed to find home directory.")) + }) + .expect("Failed to get directory path.") } // Gives back the the path to Cargo.toml reffered from the working directory. @@ -112,3 +167,82 @@ pub fn load_manifest(manifest_path: &PathBuf) -> Result { )), } } + +#[cfg(test)] +mod tests { + + use super::*; + + fn run_tests_inside_path(run_test: impl Fn(PathBuf)) { + use std::time::{SystemTime, UNIX_EPOCH}; + + fn config_text(path: PathBuf) -> String { + format!( + r#" + [env] + RHACK_DIR = {path:?} + "# + ) + } + fn random() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.subsec_nanos()) + .map(|n| n.to_string()) + .map_err(From::from) + } + + let get_random_test_dir = random() + .map_err(anyhow::Error::from) + .map(|rand| env::temp_dir().join("rhack_tests").join(rand)) + .and_then(|test_dir| { + fs::create_dir_all(&test_dir)?; + Ok(test_dir) + }) + .and_then(|test_dir| { + fs::write( + &test_dir.join(CARGO_CONFIG_FILE_NAME), + config_text(test_dir.clone()), + )?; + Ok(test_dir) + }); + + let _b = get_random_test_dir + .inspect(|path| env::set_var(CARGO_HOME_ENV_KEY, path)) + .inspect(|path| run_test(path.clone())) + .inspect(|path| { + let _ = fs::remove_dir_all(&path); + }) + .inspect(|_| env::remove_var(CARGO_HOME_ENV_KEY)) + .expect("run_tests_inside_path"); + } + #[test] + fn local_file_test() { + env::remove_var(RHACK_DIR_ENV_KEY); + + let result = load_path_from_config().expect("local_file_test"); + + assert_eq!(result, PathBuf::from("./").join(DEFAULT_RHACK_DIR_NAME)); + } + #[test] + fn global_file_test() { + env::remove_var(RHACK_DIR_ENV_KEY); + + run_tests_inside_path(|path| { + let result = load_path_from_config().expect("global_file_test"); + assert_eq!(result, path); + }); + } + #[test] + fn rhack_env_override_test() { + env::remove_var(RHACK_DIR_ENV_KEY); + + run_tests_inside_path(|path| { + let modified_path = path.join("modified"); + env::set_var(RHACK_DIR_ENV_KEY, modified_path.clone()); + let result = rhack_dir(); + assert_eq!(result, modified_path); + assert_ne!(result, load_path_from_config().expect("rhack_env_dir_test")); + }); + } +}