1use crate::config::resolve_config_path;
4use anyhow::{Context, Result};
5use clap::Subcommand;
6
7#[derive(Subcommand, Debug)]
9pub enum ConfigCommand {
10 Show,
12 Set {
14 key: String,
16 value: String,
18 },
19}
20
21impl ConfigCommand {
22 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 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}