1use crate::error::RspError;
2use serde_yaml::{Mapping, Value};
3use std::fs;
4use std::io::{self, Read};
5
6pub struct Peeler;
7
8impl Default for Peeler {
9 fn default() -> Self {
10 Self::new()
11 }
12}
13
14impl Peeler {
15 pub fn new() -> Self {
16 Self
17 }
18
19 pub fn peel_file(
20 &self,
21 input_file: &str,
22 output_file: Option<&String>,
23 ) -> Result<(), RspError> {
24 let content = fs::read_to_string(input_file)
25 .map_err(|_| RspError::FileNotFound(input_file.to_string()))?;
26
27 self.peel_content(&content, output_file)
28 }
29
30 pub fn peel_stdin(&self, output_file: Option<&String>) -> Result<(), RspError> {
31 let mut content = String::new();
32 io::stdin()
33 .read_to_string(&mut content)
34 .map_err(|e| RspError::Processing(format!("Failed to read from stdin: {e}")))?;
35
36 self.peel_content(&content, output_file)
37 }
38
39 fn peel_content(&self, content: &str, output_file: Option<&String>) -> Result<(), RspError> {
40 let mut yaml_value: Value = serde_yaml::from_str(content)?;
41
42 self.process_yaml_value(&mut yaml_value)?;
43
44 let output = self.serialize_yaml_with_pipes(&yaml_value)?;
45
46 match output_file {
47 Some(file_path) => {
48 fs::write(file_path, output)?;
49 println!("Output written to {file_path}");
50 }
51 None => {
52 print!("{output}");
53 }
54 }
55
56 Ok(())
57 }
58
59 pub fn process_yaml_value(&self, value: &mut Value) -> Result<(), RspError> {
60 match value {
61 Value::Mapping(map) => {
62 self.process_configmap(map)?;
63 }
64 _ => {
65 return Err(RspError::InvalidFormat(
66 "Expected YAML mapping at root level".to_string(),
67 ));
68 }
69 }
70 Ok(())
71 }
72
73 fn process_configmap(&self, map: &mut Mapping) -> Result<(), RspError> {
74 if let Some(Value::String(kind)) = map.get(Value::String("kind".to_string())) {
75 if kind == "ConfigMap" {
76 if let Some(Value::Mapping(data_map)) =
77 map.get_mut(Value::String("data".to_string()))
78 {
79 self.process_data_section(data_map)?;
80 }
81 }
82 }
83 Ok(())
84 }
85
86 fn process_data_section(&self, data_map: &mut Mapping) -> Result<(), RspError> {
87 let keys_to_process: Vec<_> = data_map
88 .iter()
89 .filter_map(|(key, value)| {
90 if let (Value::String(key_str), Value::String(value_str)) = (key, value) {
91 if self.should_process_key(key_str) {
92 Some((key.clone(), value_str.clone()))
93 } else {
94 None
95 }
96 } else {
97 None
98 }
99 })
100 .collect();
101
102 for (key, value_str) in keys_to_process {
103 let processed = self.process_raw_string(&value_str)?;
104 data_map.insert(key, Value::String(processed));
105 }
106
107 Ok(())
108 }
109
110 pub fn should_process_key(&self, key: &str) -> bool {
111 key.ends_with(".yaml")
112 || key.ends_with(".yml")
113 || key.ends_with(".json")
114 || key.ends_with(".toml")
115 }
116
117 fn process_raw_string(&self, raw_string: &str) -> Result<String, RspError> {
118 let unescaped = self.unescape_string(raw_string)?;
119 Ok(unescaped)
120 }
121
122 pub fn unescape_string(&self, escaped: &str) -> Result<String, RspError> {
123 let mut result = String::new();
124 let mut chars = escaped.chars().peekable();
125
126 while let Some(ch) = chars.next() {
127 if ch == '\\' {
128 match chars.next() {
129 Some('n') => result.push('\n'),
130 Some('t') => result.push('\t'),
131 Some('r') => result.push('\r'),
132 Some('\\') => result.push('\\'),
133 Some('"') => result.push('"'),
134 Some(c) => {
135 result.push('\\');
136 result.push(c);
137 }
138 None => result.push('\\'),
139 }
140 } else {
141 result.push(ch);
142 }
143 }
144
145 Ok(result)
146 }
147
148 pub fn serialize_yaml_with_pipes(&self, value: &Value) -> Result<String, RspError> {
149 let mut output = String::new();
150 self.serialize_value(value, &mut output, 0, &mut std::collections::HashSet::new())?;
151 Ok(output)
152 }
153
154 fn serialize_value(
155 &self,
156 value: &Value,
157 output: &mut String,
158 indent: usize,
159 _processed_keys: &mut std::collections::HashSet<String>,
160 ) -> Result<(), RspError> {
161 match value {
162 Value::Mapping(map) => {
163 for (key, val) in map {
164 if let Value::String(key_str) = key {
165 self.write_indent(output, indent);
166 output.push_str(&format!("{key_str}:"));
167
168 if let Value::String(string_val) = val {
169 if self.should_process_key(key_str) && string_val.contains('\n') {
170 output.push_str(" |\n");
171 for line in string_val.lines() {
172 self.write_indent(output, indent + 1);
173 output.push_str(line);
174 output.push('\n');
175 }
176 } else {
177 output.push(' ');
178 self.serialize_value(val, output, 0, _processed_keys)?;
179 output.push('\n');
180 }
181 } else {
182 output.push('\n');
183 self.serialize_value(val, output, indent + 1, _processed_keys)?;
184 }
185 }
186 }
187 }
188 Value::String(s) => {
189 if s.contains('\n') || s.contains('"') || s.starts_with(' ') || s.ends_with(' ') {
190 output.push_str(&format!("\"{}\"", s.replace('"', "\\\"")));
191 } else {
192 output.push_str(s);
193 }
194 }
195 _ => {
196 let serialized = serde_yaml::to_string(value)?;
197 output.push_str(serialized.trim());
198 if !serialized.ends_with('\n') {
199 output.push('\n');
200 }
201 }
202 }
203 Ok(())
204 }
205
206 fn write_indent(&self, output: &mut String, indent: usize) {
207 output.push_str(&" ".repeat(indent));
208 }
209}