Skip to main content

rsp_cli/
peeler.rs

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}