winbrew_cli\commands/
config.rs

1//! Configuration command handlers.
2//!
3//! This module routes `config` subcommands, reads effective values,
4//! updates persisted values, and removes overrides when requested.
5
6use anyhow::Result;
7use std::io::Write;
8
9use crate::cli::ConfigCommand;
10use crate::commands::error::{reported, reported_with_hint};
11use crate::database::{Config, ConfigError, suggest_key};
12use crate::{CommandContext, app::config};
13use winbrew_ui::Ui;
14
15/// Dispatches a `config` subcommand to the appropriate handler.
16pub fn run(ctx: &CommandContext, config: &mut Config, command: ConfigCommand) -> Result<()> {
17    let mut ui = ctx.ui();
18    ui.page_title("Configuration");
19
20    match command {
21        ConfigCommand::List => list(config, &mut ui),
22        ConfigCommand::Get { key } => get(config, &mut ui, key.trim()),
23        ConfigCommand::Set { key, value } => set(config, &mut ui, key.trim(), value.as_deref()),
24        ConfigCommand::Unset { key } => unset(config, &mut ui, key.trim()),
25    }
26}
27
28/// Lists all configuration sections and their values.
29fn list<W: Write>(config: &Config, ui: &mut Ui<W>) -> Result<()> {
30    let sections = config::list_sections(config)?;
31
32    if sections.is_empty() {
33        ui.notice("No configuration values are set.");
34        return Ok(());
35    }
36
37    for config::ConfigSection { title, entries } in &sections {
38        ui.notice(title);
39        ui.display_key_values(entries);
40    }
41
42    Ok(())
43}
44
45/// Displays the effective value for a configuration key.
46fn get<W: Write>(config: &Config, ui: &mut Ui<W>, key: &str) -> Result<()> {
47    let value = config::get_display_value(config, key)
48        .map_err(|err| config_key_error("retrieve", key, err))?;
49
50    ui.info(format_args!(
51        "{key} = {}{}",
52        value.value,
53        source_suffix(value.source)
54    ));
55    Ok(())
56}
57
58/// Stores a configuration value, either from the CLI argument or an interactive prompt.
59///
60/// Empty values are rejected so callers use `unset` to remove keys instead of
61/// silently writing blank entries.
62fn set<W: Write>(
63    config: &mut Config,
64    ui: &mut Ui<W>,
65    key: &str,
66    value: Option<&str>,
67) -> Result<()> {
68    let owned_prompt;
69    let clean_value = match value {
70        Some(value) => value.trim(),
71        None => {
72            owned_prompt = ui.prompt_text(&format!("Enter value for {key}"), None)?;
73            owned_prompt.trim()
74        }
75    };
76
77    if clean_value.is_empty() {
78        return Err(reported(
79            "Empty value is not allowed. Use 'unset' to remove a configuration key.",
80        ));
81    }
82
83    config::set_value(config, key, clean_value).map_err(|err| config_key_error("set", key, err))?;
84
85    ui.success(format!("{key} updated."));
86    Ok(())
87}
88
89/// Removes a configuration key by restoring its default value.
90fn unset<W: Write>(config: &mut Config, ui: &mut Ui<W>, key: &str) -> Result<()> {
91    config::unset_value(config, key).map_err(|err| config_key_error("remove", key, err))?;
92
93    ui.success(format!("{key} removed."));
94    Ok(())
95}
96
97/// Returns the visual suffix used when a value is overridden by the environment.
98fn source_suffix(source: config::ConfigValueSource) -> &'static str {
99    match source {
100        config::ConfigValueSource::Env => " (overridden by environment)",
101        config::ConfigValueSource::File => "",
102    }
103}
104
105fn config_key_error(action: &str, key: &str, err: anyhow::Error) -> anyhow::Error {
106    if let Some(ConfigError::UnknownKey { key: unknown_key }) = err.downcast_ref::<ConfigError>() {
107        let hint = match suggest_key(unknown_key) {
108            Some(suggested) => format!(
109                "Did you mean '{suggested}'? Use 'winbrew config list' to browse the available keys."
110            ),
111            None => "Use 'winbrew config list' to browse the available keys.".to_string(),
112        };
113
114        return reported_with_hint(format!("unknown config key '{unknown_key}'"), hint);
115    }
116
117    err.context(format!("Failed to {action} configuration for key: '{key}'"))
118}