diff --git a/README.md b/README.md index a378f35..8dddb30 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,27 @@ Options: ``` -### Example config.json +### Config file This is useful if you don't want a super long CLI command and your configs do not change often, note that all the options can be specified via the CLI as well and are fully optional in this config file (will be merged with the CLI options if specified) +There are multiple default locations where the config file will be searched for, in this order (once found it will not look for the config file in the other locations): +1. The path specified via the --config-file CLI option +2. In the same folder as the red_oxide executable +3. `%APPDATA%/red_oxide/red_oxide.config.json` (only on Windows) +4. `$XDG_CONFIG_HOME/red_oxide/red_oxide.config.json` +5. `HOME/.config/red_oxide/red_oxide.config.json` +6. `HOME/red_oxide.config.json` + +HOME is determined by these environment variables on Windows in this order: +1. `%HOME%` +2. `%USERPROFILE%` +3. `%HOMEDRIVE%\%HOMEPATH%` + +HOME is determined by these environment variables on Linux in this order: +1. `$HOME` + + ```json { "api_key": "YOUR_API_KEY", diff --git a/src/config/config.rs b/src/config/config.rs index ec48a39..a05ef1e 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -1,12 +1,117 @@ +use crate::config::constants::{ + CONFIG_FILE_NAME, CONFIG_PATH, HOME_ENV, PROJECT_NAME, WINDOWS_APPDATA_ENV, + WINDOWS_HOMEDRIVE_ENV, WINDOWS_HOMEPATH_ENV, WINDOWS_USERPROFILE_ENV, XDG_CONFIG_ENV, +}; use crate::config::models::RedOxideConfig; use crate::redacted::models::ReleaseType::{Flac, Mp3320, Mp3V0}; use crate::{TranscodeCommand, ERROR}; use console::Term; +use std::env; +use std::path::PathBuf; use tokio::fs::File; use tokio::io::AsyncReadExt; +pub fn search_config_in_default_locations() -> anyhow::Result> { + let mut path; + + let current_dir = env::current_dir()?; + let current_dir_config = current_dir.join(CONFIG_FILE_NAME); + + if current_dir_config.exists() { + path = Some(current_dir_config); + return Ok(path); + } + + if cfg!(windows) { + path = get_config_path_from_default_location_by_env(WINDOWS_APPDATA_ENV); + + if let Some(path) = path { + return Ok(Some(path)); + } + } + + path = get_config_path_from_default_location_by_env(XDG_CONFIG_ENV); + + if let Some(path) = path { + return Ok(Some(path)); + } + + let home_env = get_home_env(); + + if let Some(home) = home_env { + let mut home_config = PathBuf::from(home.clone()) + .join(CONFIG_PATH) + .join(PROJECT_NAME) + .join(CONFIG_FILE_NAME); + + if home_config.exists() { + path = Some(home_config); + return Ok(path); + } + + home_config = PathBuf::from(home).join(CONFIG_FILE_NAME); + + if home_config.exists() { + path = Some(home_config); + return Ok(path); + } + } + + Ok(path) +} + +fn get_config_path_from_default_location_by_env(env: &str) -> Option { + let env_resolved = env::var(env).unwrap_or(String::new()); + + if !env_resolved.is_empty() { + let env_config_home = PathBuf::from(env_resolved) + .join(PROJECT_NAME) + .join(CONFIG_FILE_NAME); + + if env_config_home.exists() { + return Some(env_config_home); + } + } + + None +} + +#[cfg(target_os = "windows")] +fn get_home_env() -> Option { + if let Ok(home) = env::var(HOME_ENV) { + return Some(home); + } + + if let Ok(user_profile) = env::var(WINDOWS_USERPROFILE_ENV) { + return Some(user_profile); + } + + let home_drive = env::var(WINDOWS_HOMEDRIVE_ENV).unwrap_or(String::new()); + let home_path = env::var(WINDOWS_HOMEPATH_ENV).unwrap_or(String::new()); + + if !home_drive.is_empty() && !home_path.is_empty() { + return Some(format!("{}\\{}", home_drive, home_path)); + } + + None +} + +#[cfg(not(target_os = "windows"))] +fn get_home_env() -> Option { + if let Ok(home) = env::var(UNIX_HOME_ENV) { + return Some(home); + } + + None +} + pub async fn apply_config(cmd: &mut TranscodeCommand, term: &Term) -> anyhow::Result<()> { - if let Some(config_path) = &cmd.config_file { + let found_config = match &cmd.config_file { + None => search_config_in_default_locations()?, + Some(config_file) => Some(config_file.clone()), + }; + + if let Some(config_path) = found_config { let mut file = File::open(config_path).await?; let mut contents = vec![]; file.read_to_end(&mut contents).await?; diff --git a/src/config/constants.rs b/src/config/constants.rs new file mode 100644 index 0000000..ab11705 --- /dev/null +++ b/src/config/constants.rs @@ -0,0 +1,13 @@ +pub const CONFIG_FILE_NAME: &str = "red_oxide.config.json"; +pub const PROJECT_NAME: &str = "red_oxide"; + +pub const CONFIG_PATH: &str = ".config"; + +pub const XDG_CONFIG_ENV: &str = "XDG_CONFIG_HOME"; + +pub const HOME_ENV: &str = "HOME"; + +pub const WINDOWS_APPDATA_ENV: &str = "APPDATA"; +pub const WINDOWS_USERPROFILE_ENV: &str = "USERPROFILE"; +pub const WINDOWS_HOMEDRIVE_ENV: &str = "HOMEDRIVE"; +pub const WINDOWS_HOMEPATH_ENV: &str = "HOMEPATH"; diff --git a/src/config/mod.rs b/src/config/mod.rs index b6a44ac..1659031 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,2 +1,3 @@ pub mod config; +mod constants; pub mod models;