invoice_cli/commands/
config.rs1use crate::cli::ConfigCmd;
2use crate::config;
3use crate::error::{AppError, Result};
4use crate::output::{print_success, Ctx};
5
6pub fn run(cmd: ConfigCmd, ctx: Ctx) -> Result<()> {
7 match cmd {
8 ConfigCmd::Show => {
9 let cfg = config::load()?;
10 print_success(ctx, &cfg, |c| println!("{:#?}", c));
11 Ok(())
12 }
13 ConfigCmd::Path => {
14 let p = config::config_path()?;
15 print_success(ctx, &p, |p| println!("{}", p.display()));
16 Ok(())
17 }
18 ConfigCmd::Set { key, value } => {
19 let remove_key = matches!(
20 value.to_ascii_lowercase().as_str(),
21 "unset" | "none" | "null" | ""
22 );
23 validate_config_value(&key, &value, remove_key)?;
24
25 let path = config::config_path()?;
27 let existing = if path.exists() {
28 std::fs::read_to_string(&path)?
29 } else {
30 String::new()
31 };
32 let mut doc: toml::Value =
33 toml::from_str(&existing).unwrap_or(toml::Value::Table(Default::default()));
34 if let toml::Value::Table(ref mut t) = doc {
35 if remove_key {
36 t.remove(&key);
37 } else {
38 t.insert(key.clone(), parse_value(&value));
39 }
40 } else {
41 return Err(AppError::Config("config root is not a table".into()));
42 }
43 std::fs::write(&path, toml::to_string_pretty(&doc).unwrap())?;
44 print_success(
45 ctx,
46 &serde_json::json!({"key": key, "value": if remove_key { serde_json::Value::Null } else { serde_json::Value::String(value.clone()) }}),
47 |_| {
48 if remove_key {
49 println!("unset {key}");
50 } else {
51 println!("set {key} = {value}");
52 }
53 },
54 );
55 Ok(())
56 }
57 }
58}
59
60fn validate_config_value(key: &str, value: &str, remove_key: bool) -> Result<()> {
61 if key == "default_issuer" && !remove_key {
62 let conn = crate::db::open()?;
63 crate::db::issuer_by_slug(&conn, value).map_err(|_| {
64 AppError::InvalidInput(format!(
65 "unknown issuer '{value}' for config.default_issuer. Add it first or run `invoice issuer list`."
66 ))
67 })?;
68 }
69 Ok(())
70}
71
72fn parse_value(v: &str) -> toml::Value {
73 if v == "true" {
74 toml::Value::Boolean(true)
75 } else if v == "false" {
76 toml::Value::Boolean(false)
77 } else if let Ok(n) = v.parse::<i64>() {
78 toml::Value::Integer(n)
79 } else {
80 toml::Value::String(v.to_string())
81 }
82}