v0.4.3 : persistance du theme actif choisi
- le REPL 'theme <name>' applique ET sauvegarde le theme (settings.theme dans le fichier de config actif, cree s'il manque) via persist_setting - correction racine : config::merge ignorait settings.theme - la valeur persistee n etait jamais relue au chargement, le theme par defaut etait toujours reapplique apres un redemarrage - 'am --theme <name>' reste un one-shot ; config set settings.theme <nom> fonctionne egalement - aides mises a jour (banniere, help theme) - tests de persistance + relecture au demarrage, man pages regenerees, version 0.4.3
This commit is contained in:
+112
-5
@@ -103,7 +103,28 @@ pub fn run(app: &App, cmd: &ConfigCmd) -> Result<i32> {
|
||||
/// configuration file, then validate the result.
|
||||
pub fn set_value(app: &App, key: &str, value: &str) -> Result<i32> {
|
||||
let path = user_config_path(app)?;
|
||||
let text = std::fs::read_to_string(&path)
|
||||
write_dotted_key(&path, key, value)?;
|
||||
reload_and_validate(&path)?;
|
||||
app.log.success(&format!("{key} = {value}"));
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Persist one setting into the configuration file that is actually in
|
||||
/// effect (the loaded origin), creating the user config file when none
|
||||
/// exists. Used by the REPL 'theme <name>' command so a switched theme
|
||||
/// survives a restart.
|
||||
pub fn persist_setting(app: &App, key: &str, value: &str) -> Result<()> {
|
||||
let path = match &app.config_origin {
|
||||
Some(p) => p.clone(),
|
||||
None => user_config_path(app)?,
|
||||
};
|
||||
write_dotted_key(&path, key, value)?;
|
||||
reload_and_validate(&path)
|
||||
}
|
||||
|
||||
/// Set one dotted key (e.g. settings.default_shell) in a YAML config file.
|
||||
fn write_dotted_key(path: &Path, key: &str, value: &str) -> Result<()> {
|
||||
let text = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("cannot read {}", path.display()))?;
|
||||
let mut root: serde_yaml::Value = serde_yaml::from_str(&text)
|
||||
.with_context(|| format!("{} is not valid YAML", path.display()))?;
|
||||
@@ -128,16 +149,18 @@ pub fn set_value(app: &App, key: &str, value: &str) -> Result<i32> {
|
||||
serde_yaml::Value::String(parts.last().unwrap().to_string()),
|
||||
parsed,
|
||||
);
|
||||
std::fs::write(&path, serde_yaml::to_string(&root)?)
|
||||
std::fs::write(path, serde_yaml::to_string(&root)?)
|
||||
.with_context(|| format!("cannot write {}", path.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Validate the new configuration end to end.
|
||||
/// Reload a written config file to make sure the change stays valid.
|
||||
fn reload_and_validate(path: &Path) -> Result<()> {
|
||||
let cli = crate::cli::Cli::parse_from(["am", "--config", path.to_str().unwrap_or_default()]);
|
||||
config::load(&cli).map_err(|e| {
|
||||
anyhow!("the new value makes the configuration invalid: {e:#}")
|
||||
})?;
|
||||
app.log.success(&format!("{key} = {value}"));
|
||||
Ok(0)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse a scalar: booleans and integers keep their type, everything else
|
||||
@@ -235,3 +258,87 @@ fn add_file(app: &App, file: &Path) -> Result<i32> {
|
||||
));
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use clap::Parser;
|
||||
|
||||
/// App whose active config is a file inside a leaked TempDir (the same
|
||||
/// pattern as profile_cmd's tests: the guard would delete the directory
|
||||
/// when the helper returns).
|
||||
fn app_with_config(text: &str) -> (App, PathBuf) {
|
||||
let guard = tempfile::tempdir().unwrap();
|
||||
let dir = guard.path().to_path_buf();
|
||||
std::mem::forget(guard);
|
||||
let cfg = dir.join("config.yaml");
|
||||
std::fs::write(&cfg, text).unwrap();
|
||||
let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
|
||||
let mut app = App::from_cli(cli).unwrap();
|
||||
let mut p = app.paths.clone();
|
||||
p.install_dir = dir.join("agents");
|
||||
p.bin_dir = dir.join("agents").join("bin");
|
||||
p.log_dir = dir.join("logs");
|
||||
p.state_file = dir.join("state.json");
|
||||
p.probe_cache_file = dir.join("probe-cache.json");
|
||||
p.config_dir = Some(dir.join("config"));
|
||||
app.paths = p;
|
||||
(app, cfg)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persist_setting_writes_into_the_active_config() {
|
||||
let (app, cfg) = app_with_config("version: \"1.0\"
|
||||
settings:
|
||||
theme: default
|
||||
agents: []
|
||||
");
|
||||
persist_setting(&app, "settings.theme", "ocean").unwrap();
|
||||
let text = std::fs::read_to_string(&cfg).unwrap();
|
||||
let value: serde_yaml::Value = serde_yaml::from_str(&text).unwrap();
|
||||
assert_eq!(value["settings"]["theme"], "ocean");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisted_theme_is_used_on_next_start() {
|
||||
let (app, cfg) = app_with_config("version: \"1.0\"
|
||||
settings:
|
||||
theme: default
|
||||
agents: []
|
||||
");
|
||||
persist_setting(&app, "settings.theme", "ocean").unwrap();
|
||||
// A fresh start reads settings.theme from the config file.
|
||||
let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
|
||||
let reloaded = App::from_cli(cli).unwrap();
|
||||
assert_eq!(reloaded.theme().name, "ocean");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persist_setting_creates_the_user_config_when_none_exists() {
|
||||
let (mut app, _cfg) = app_with_config("version: \"1.0\"
|
||||
agents: []
|
||||
");
|
||||
// Simulate a run without any user/local config: no origin.
|
||||
app.config_origin = None;
|
||||
persist_setting(&app, "settings.theme", "mono").unwrap();
|
||||
let user_file = app.paths.config_dir.as_ref().unwrap().join(config::CONFIG_FILE_NAME);
|
||||
assert!(user_file.is_file(), "user config was not created");
|
||||
let text = std::fs::read_to_string(&user_file).unwrap();
|
||||
let value: serde_yaml::Value = serde_yaml::from_str(&text).unwrap();
|
||||
assert_eq!(value["settings"]["theme"], "mono");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_dotted_key_descends_into_nested_mappings() {
|
||||
let guard = tempfile::tempdir().unwrap();
|
||||
let path = guard.path().join("c.yaml");
|
||||
std::fs::write(&path, "version: \"1.0\"
|
||||
agents: []
|
||||
").unwrap();
|
||||
write_dotted_key(&path, "settings.default_shell", "pwsh").unwrap();
|
||||
let value: serde_yaml::Value =
|
||||
serde_yaml::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
assert_eq!(value["settings"]["default_shell"], "pwsh");
|
||||
assert!(value["agents"].is_sequence());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user