use std::io::{self, Write};
use inquire::InquireError;
use crate::{
keys::{check_key_conflicts, is_encrypted},
typedetect::{FileFormat, detect_format},
types::{ProcessHandling, TypedValue, ValueType},
vaultfunc,
};
pub fn edit(
input: &str,
output: &str,
password: &str,
keys: &[String],
) -> Result<(), Box<dyn std::error::Error>> {
check_key_conflicts(keys)?;
println!("Editing: {input}");
println!();
let mut processor = make_edit_processor(password);
match detect_format(input)? {
FileFormat::Json => crate::json::process_file(input, output, keys, &mut processor),
FileFormat::Yaml => crate::yaml::process_file(input, output, keys, &mut processor),
FileFormat::Vault => Err("cannot edit a raw vault-encrypted file (level-0)".into()),
}
}
pub fn edit_fuzzy(
input: &str,
output: &str,
password: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let keys = match detect_format(input)? {
FileFormat::Json => crate::json::get_encrypted_keys(input)?,
FileFormat::Yaml => crate::yaml::get_encrypted_keys(input)?,
FileFormat::Vault => {
return Err("cannot edit a raw vault-encrypted file (level-0)".into())
}
};
if keys.is_empty() {
println!("No encrypted values found in '{}'.", input);
return Ok(());
}
let selected = match inquire::MultiSelect::new("Select keys to edit (Space to select, Enter to confirm):", keys).prompt() {
Ok(s) => s,
Err(InquireError::OperationCanceled | InquireError::OperationInterrupted) => {
println!("Cancelled.");
return Ok(());
}
Err(e) => return Err(e.into()),
};
if selected.is_empty() {
println!("No keys selected.");
return Ok(());
}
edit(input, output, password, &selected)
}
fn display_value(v: &TypedValue) -> String {
match v {
TypedValue::String(s) => s.clone(),
TypedValue::Integer(i) => i.to_string(),
TypedValue::Bool(b) => b.to_string(),
TypedValue::Number(f) => f.to_string(),
TypedValue::Null => "null".to_string(),
}
}
pub(crate) fn make_edit_processor(
password: &str,
) -> impl FnMut(
TypedValue,
ValueType,
&str,
) -> Result<(TypedValue, ValueType, ProcessHandling), Box<dyn std::error::Error>>
+ '_ {
move |typed, vt, key_path| {
let (encrypted, vault_str) = is_encrypted(&typed);
if !encrypted {
return Ok((typed, vt, ProcessHandling::Skip));
}
let vault_str = vault_str.unwrap();
let (decrypted_val, _) = match vaultfunc::decrypt(&vault_str, password) {
Ok(result) => result,
Err(e) => {
eprintln!("[!] could not decrypt '{key_path}': {e}");
return Ok((typed, vt, ProcessHandling::Skip));
}
};
println!("key: {key_path}");
println!("current: {}", display_value(&decrypted_val));
print!("new value (empty = keep, q = quit): ");
io::stdout().flush()?;
let mut line = String::new();
io::stdin().read_line(&mut line)?;
let trimmed = line.trim();
println!();
if trimmed.is_empty() {
return Ok((typed, vt, ProcessHandling::Skip));
}
if trimmed.eq_ignore_ascii_case("q") {
println!("Cancelled.");
return Ok((typed, vt, ProcessHandling::Cancel));
}
let (new_typed, _) = vaultfunc::input_type(trimmed);
match vaultfunc::encrypt(&new_typed, password) {
Ok(enc) => Ok((TypedValue::String(enc), ValueType::String, ProcessHandling::Process)),
Err(e) => {
eprintln!("[!] could not encrypt new value for '{key_path}': {e}");
Ok((typed, vt, ProcessHandling::Skip))
}
}
}
}