diff --git a/docs/config.md b/docs/config.md index a9bc9673..475696fc 100644 --- a/docs/config.md +++ b/docs/config.md @@ -22,6 +22,11 @@ Configuration files are located in the application's configuration directory, wh A sample `app.toml` is available at [examples/app.toml](../examples/app.toml). `spotify_player` uses `app.toml` for application settings. Available options: +`spotify_player` also supports cli config overriding using -o / --config-override flag. Example: + +```bash +spotify_player -o device.volume=80 -o theme=dracula +``` | Option | Description | Default | | --------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | diff --git a/spotify_player/src/cli/mod.rs b/spotify_player/src/cli/mod.rs index f8f6f385..333b530a 100644 --- a/spotify_player/src/cli/mod.rs +++ b/spotify_player/src/cli/mod.rs @@ -201,6 +201,14 @@ pub fn init_cli() -> anyhow::Result { .value_name("FOLDER") .default_value(default_cache_folder.into_os_string()) .help("Path to the application's cache folder"), + ) + .arg( + clap::Arg::new("config-override") + .short('o') + .long("config-override") + .value_name("KEY=VALUE") + .action(clap::ArgAction::Append) + .help("Override a config option (e.g. -o device.volume=80 -o theme=dracula)"), ); #[cfg(feature = "daemon")] diff --git a/spotify_player/src/config/mod.rs b/spotify_player/src/config/mod.rs index f6edfc9f..1987d900 100644 --- a/spotify_player/src/config/mod.rs +++ b/spotify_player/src/config/mod.rs @@ -17,6 +17,7 @@ use std::{ sync::OnceLock, }; +use anyhow::Context; use keymap::KeymapConfig; use theme::ThemeConfig; @@ -517,3 +518,36 @@ pub fn set_config(configs: Configs) { .set(configs) .expect("configs should be initialized only once"); } + +// Apply a CLI config override to the application config. +// Serializes the config to TOML, navigates to the key via dot-notation, +// overrides the value, and deserializes back into AppConfig. +// Returns an error if the key path is invalid or the value type mismatches. +pub fn apply_config_override(config: &mut AppConfig, key: &str, value: &str) -> anyhow::Result<()> { + let mut config_value = toml::Value::try_from(&*config)?; + + let parts: Vec<&str> = key.split('.').collect(); + let mut current = &mut config_value; + + for (i, part) in parts.iter().enumerate() { + if i == parts.len() - 1 { + let table = current + .as_table_mut() + .context(format!("'{key}' is not a valid config path"))?; + + let parsed_value: toml::Value = value + .parse() + .unwrap_or_else(|_| toml::Value::String(value.to_string())); + + table.insert(part.to_string(), parsed_value); + } else { + current = current + .get_mut(part) + .context(format!("Config key '{part}' not found in path '{key}'"))?; + } + } + + *config = config_value.try_into()?; + + Ok(()) +} diff --git a/spotify_player/src/main.rs b/spotify_player/src/main.rs index 91a6e3ab..27dfe8f2 100644 --- a/spotify_player/src/main.rs +++ b/spotify_player/src/main.rs @@ -22,6 +22,8 @@ use std::{collections::VecDeque, io::Write, sync::Arc}; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; +use crate::config::apply_config_override; + fn init_spotify( client_pub: &flume::Sender, client: &client::AppClient, @@ -270,9 +272,14 @@ fn main() -> Result<()> { // set the log folder to be the cache folder if it is not set configs.app_config.log_folder = Some(cache_folder); } - if let Some(theme) = args.get_one::("theme") { - // override the theme config if user specifies a `theme` cli argument - theme.clone_into(&mut configs.app_config.theme); + if let Some(overrides) = args.get_many::("config-override") { + for override_str in overrides { + let (key, value) = override_str.split_once('=').context(format!( + "Invalid override format: '{override_str}'. Expected KEY=VALUE" + ))?; + + apply_config_override(&mut configs.app_config, key, value)?; + } } config::set_config(configs); }