use clap::{Args, Subcommand};
use serde::Serialize;
use serde_json::Value;
use homeboy::defaults::{self, Defaults, HomeboyConfig};
use super::CmdResult;
#[derive(Args)]
pub struct ConfigArgs {
#[command(subcommand)]
command: ConfigCommand,
}
#[derive(Subcommand)]
enum ConfigCommand {
Show {
#[arg(long)]
builtin: bool,
},
Set {
pointer: String,
value: String,
},
Remove {
pointer: String,
},
Reset,
Path,
}
#[derive(Debug, Serialize)]
pub struct ConfigOutput {
command: String,
#[serde(skip_serializing_if = "Option::is_none")]
config: Option<HomeboyConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
defaults: Option<Defaults>,
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
exists: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pointer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
deleted: Option<bool>,
}
pub fn run(args: ConfigArgs, _global: &crate::commands::GlobalArgs) -> CmdResult<ConfigOutput> {
match args.command {
ConfigCommand::Show { builtin } => show(builtin),
ConfigCommand::Set { pointer, value } => set(&pointer, &value),
ConfigCommand::Remove { pointer } => remove(&pointer),
ConfigCommand::Reset => reset(),
ConfigCommand::Path => path(),
}
}
fn show(builtin: bool) -> CmdResult<ConfigOutput> {
if builtin {
Ok((
ConfigOutput {
command: "config.show".to_string(),
defaults: Some(defaults::builtin_defaults()),
config: None,
path: None,
exists: None,
pointer: None,
value: None,
deleted: None,
},
0,
))
} else {
let config = defaults::load_config();
Ok((
ConfigOutput {
command: "config.show".to_string(),
config: Some(config),
defaults: None,
path: None,
exists: None,
pointer: None,
value: None,
deleted: None,
},
0,
))
}
}
fn set(pointer: &str, value_str: &str) -> CmdResult<ConfigOutput> {
if !pointer.starts_with('/') {
return Err(homeboy::Error::validation_invalid_argument(
"pointer",
"JSON pointer must start with '/'",
None,
None,
));
}
let value: Value = serde_json::from_str(value_str).map_err(|e| {
homeboy::Error::validation_invalid_json(
e,
Some("parse value".to_string()),
Some(value_str.chars().take(200).collect::<String>()),
)
})?;
let mut config = defaults::load_config();
let mut config_json = serde_json::to_value(&config).map_err(|e| {
homeboy::Error::internal_unexpected(format!("Failed to serialize config: {}", e))
})?;
homeboy::config::set_json_pointer(&mut config_json, pointer, value.clone())?;
config = serde_json::from_value(config_json).map_err(|e| {
homeboy::Error::validation_invalid_json(e, Some("deserialize config".to_string()), None)
})?;
defaults::save_config(&config)?;
Ok((
ConfigOutput {
command: "config.set".to_string(),
config: Some(config),
defaults: None,
path: None,
exists: None,
pointer: Some(pointer.to_string()),
value: Some(value),
deleted: None,
},
0,
))
}
fn remove(pointer: &str) -> CmdResult<ConfigOutput> {
if !pointer.starts_with('/') {
return Err(homeboy::Error::validation_invalid_argument(
"pointer",
"JSON pointer must start with '/'",
None,
None,
));
}
let mut config = defaults::load_config();
let mut config_json = serde_json::to_value(&config).map_err(|e| {
homeboy::Error::internal_unexpected(format!("Failed to serialize config: {}", e))
})?;
homeboy::config::remove_json_pointer(&mut config_json, pointer)?;
config = serde_json::from_value(config_json).map_err(|e| {
homeboy::Error::validation_invalid_json(e, Some("deserialize config".to_string()), None)
})?;
defaults::save_config(&config)?;
Ok((
ConfigOutput {
command: "config.remove".to_string(),
config: Some(config),
defaults: None,
path: None,
exists: None,
pointer: Some(pointer.to_string()),
value: None,
deleted: None,
},
0,
))
}
fn reset() -> CmdResult<ConfigOutput> {
let deleted = defaults::reset_config()?;
Ok((
ConfigOutput {
command: "config.reset".to_string(),
config: None,
defaults: Some(defaults::builtin_defaults()),
path: Some(defaults::config_path()?),
exists: None,
pointer: None,
value: None,
deleted: Some(deleted),
},
0,
))
}
fn path() -> CmdResult<ConfigOutput> {
let path = defaults::config_path()?;
let exists = defaults::config_exists();
Ok((
ConfigOutput {
command: "config.path".to_string(),
config: None,
defaults: None,
path: Some(path),
exists: Some(exists),
pointer: None,
value: None,
deleted: None,
},
0,
))
}