1use std::io::{self, Write};
2
3use inquire::InquireError;
4
5use crate::{
6 keys::{check_key_conflicts, is_encrypted},
7 typedetect::{FileFormat, detect_format},
8 types::{ProcessHandling, TypedValue, ValueType},
9 vaultfunc,
10};
11
12pub fn edit(
23 input: &str,
24 output: &str,
25 password: &str,
26 keys: &[String],
27) -> Result<(), Box<dyn std::error::Error>> {
28 check_key_conflicts(keys)?;
29 println!("Editing: {input}");
30 println!();
31 let mut processor = make_edit_processor(password);
32 match detect_format(input)? {
33 FileFormat::Json => crate::json::process_file(input, output, keys, &mut processor),
34 FileFormat::Yaml => crate::yaml::process_file(input, output, keys, &mut processor),
35 FileFormat::Vault => Err("cannot edit a raw vault-encrypted file (level-0)".into()),
36 }
37}
38
39pub fn edit_fuzzy(
44 input: &str,
45 output: &str,
46 password: &str,
47) -> Result<(), Box<dyn std::error::Error>> {
48 let keys = match detect_format(input)? {
49 FileFormat::Json => crate::json::get_encrypted_keys(input)?,
50 FileFormat::Yaml => crate::yaml::get_encrypted_keys(input)?,
51 FileFormat::Vault => {
52 return Err("cannot edit a raw vault-encrypted file (level-0)".into())
53 }
54 };
55
56 if keys.is_empty() {
57 println!("No encrypted values found in '{}'.", input);
58 return Ok(());
59 }
60
61 let selected = match inquire::MultiSelect::new("Select keys to edit (Space to select, Enter to confirm):", keys).prompt() {
62 Ok(s) => s,
63 Err(InquireError::OperationCanceled | InquireError::OperationInterrupted) => {
64 println!("Cancelled.");
65 return Ok(());
66 }
67 Err(e) => return Err(e.into()),
68 };
69
70 if selected.is_empty() {
71 println!("No keys selected.");
72 return Ok(());
73 }
74
75 edit(input, output, password, &selected)
76}
77
78fn display_value(v: &TypedValue) -> String {
79 match v {
80 TypedValue::String(s) => s.clone(),
81 TypedValue::Integer(i) => i.to_string(),
82 TypedValue::Bool(b) => b.to_string(),
83 TypedValue::Number(f) => f.to_string(),
84 TypedValue::Null => "null".to_string(),
85 }
86}
87
88pub(crate) fn make_edit_processor(
89 password: &str,
90) -> impl FnMut(
91 TypedValue,
92 ValueType,
93 &str,
94) -> Result<(TypedValue, ValueType, ProcessHandling), Box<dyn std::error::Error>>
95+ '_ {
96 move |typed, vt, key_path| {
97 let (encrypted, vault_str) = is_encrypted(&typed);
98 if !encrypted {
99 return Ok((typed, vt, ProcessHandling::Skip));
100 }
101 let vault_str = vault_str.unwrap();
102
103 let (decrypted_val, _) = match vaultfunc::decrypt(&vault_str, password) {
104 Ok(result) => result,
105 Err(e) => {
106 eprintln!("[!] could not decrypt '{key_path}': {e}");
107 return Ok((typed, vt, ProcessHandling::Skip));
108 }
109 };
110
111 println!("key: {key_path}");
112 println!("current: {}", display_value(&decrypted_val));
113 print!("new value (empty = keep, q = quit): ");
114 io::stdout().flush()?;
115
116 let mut line = String::new();
117 io::stdin().read_line(&mut line)?;
118 let trimmed = line.trim();
119
120 println!();
121
122 if trimmed.is_empty() {
123 return Ok((typed, vt, ProcessHandling::Skip));
124 }
125 if trimmed.eq_ignore_ascii_case("q") {
126 println!("Cancelled.");
127 return Ok((typed, vt, ProcessHandling::Cancel));
128 }
129
130 let (new_typed, _) = vaultfunc::input_type(trimmed);
131 match vaultfunc::encrypt(&new_typed, password) {
132 Ok(enc) => Ok((TypedValue::String(enc), ValueType::String, ProcessHandling::Process)),
133 Err(e) => {
134 eprintln!("[!] could not encrypt new value for '{key_path}': {e}");
135 Ok((typed, vt, ProcessHandling::Skip))
136 }
137 }
138 }
139}