Skip to main content

walrus_cli/cmd/
config.rs

1//! Config management commands: show, set.
2
3use crate::config::resolve_config_path;
4use anyhow::{Context, Result};
5use clap::Subcommand;
6
7/// Config management subcommands.
8#[derive(Subcommand, Debug)]
9pub enum ConfigCommand {
10    /// Show current configuration.
11    Show,
12    /// Set a configuration value.
13    Set {
14        /// Configuration key.
15        key: String,
16        /// Configuration value.
17        value: String,
18    },
19}
20
21impl ConfigCommand {
22    /// Dispatch config management subcommands.
23    pub fn run(&self) -> Result<()> {
24        match self {
25            Self::Show => show(),
26            Self::Set { key, value } => set(key, value),
27        }
28    }
29}
30
31fn show() -> Result<()> {
32    let path = resolve_config_path();
33    if !path.exists() {
34        println!("No config file at {}", path.display());
35        return Ok(());
36    }
37    let contents =
38        std::fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?;
39    print!("{contents}");
40    Ok(())
41}
42
43fn set(key: &str, value: &str) -> Result<()> {
44    let path = resolve_config_path();
45    let contents = if path.exists() {
46        std::fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?
47    } else {
48        String::new()
49    };
50
51    let mut doc: toml::Table = contents
52        .parse()
53        .with_context(|| format!("parsing {}", path.display()))?;
54
55    // Support dotted keys: "llm.model" -> doc["llm"]["model"].
56    let parts: Vec<&str> = key.split('.').collect();
57    match parts.as_slice() {
58        [section, field] => {
59            let table = doc
60                .entry(*section)
61                .or_insert_with(|| toml::Value::Table(toml::Table::new()))
62                .as_table_mut()
63                .ok_or_else(|| anyhow::anyhow!("'{section}' is not a table"))?;
64            table.insert((*field).to_owned(), toml::Value::String(value.to_owned()));
65        }
66        [field] => {
67            doc.insert((*field).to_owned(), toml::Value::String(value.to_owned()));
68        }
69        _ => anyhow::bail!("invalid key format: '{key}' (use 'section.field' or 'field')"),
70    }
71
72    if let Some(parent) = path.parent() {
73        std::fs::create_dir_all(parent)
74            .with_context(|| format!("creating {}", parent.display()))?;
75    }
76    std::fs::write(&path, doc.to_string())
77        .with_context(|| format!("writing {}", path.display()))?;
78    println!("Set {key} = {value} in {}", path.display());
79    Ok(())
80}