1use super::BlockChomping;
2
3fn handle_ending_newlines(s: String, chomping: BlockChomping) -> String {
5 match chomping {
6 BlockChomping::Keep => {
7 let mut result = s;
8 result.push('\n');
9 result
10 }
11 BlockChomping::Clip => {
12 let mut result = s.trim_end_matches('\n').to_string();
13 result.push('\n');
14 result
15 }
16 BlockChomping::Strip => s.trim_end_matches('\n').to_string(),
17 }
18}
19
20pub fn parse_literal(lines: Vec<&str>, chomping: BlockChomping) -> String {
22 let s = lines.join("\n");
23 handle_ending_newlines(s, chomping)
24}
25
26pub fn create_literal(s: String) -> Vec<String> {
30 let mut lines: Vec<String> = s.split('\n').map(str::to_string).collect();
31 if s.ends_with('\n') {
32 if lines.last().map(|l| l.is_empty()).unwrap_or(false) {
33 lines.pop();
34 }
35 }
36 lines
37}
38
39pub fn parse_folded(lines: Vec<&str>, chomping: BlockChomping) -> String {
41 if lines.is_empty() {
42 return String::new();
43 }
44 let start = lines[0].to_string();
45
46 let result = lines
47 .into_iter()
48 .skip(1)
49 .fold((start, false), |(s, prev_was_empty), element| {
50 let mut c = s.clone(); let is_empty = if element.is_empty() {
52 c.push_str("\n");
53 true
54 } else {
55 let without_indent = &element; if without_indent.starts_with(" ") {
58 c.push_str("\n");
59 c.push_str(without_indent);
60 c.push_str("\n");
61 true
62 } else {
63 if !prev_was_empty {
64 c.push_str(" ");
65 }
66 c.push_str(without_indent);
67 false
68 }
69 };
70 (c, is_empty)
71 })
72 .0;
73 handle_ending_newlines(result, chomping)
74}
75
76fn split_on_length(input: String, length: usize) -> Vec<String> {
77 let mut result = Vec::new();
78 let mut s = String::new();
79 let mut first = true;
80 for word in input.split(" ") {
81 if s.len() + word.len() + 1 > length {
82 result.push(s.clone());
83 s = word.to_string();
84 } else {
85 if !first {
86 s.push_str(" ");
87 }
88 s.push_str(word);
89 first = false;
90 }
91 }
92 if !s.is_empty() {
93 result.push(s.clone());
94 }
95 result
96}
97
98pub fn create_folded(s: String, line_length: usize) -> Vec<String> {
103 let ends_with_newline = s.ends_with('\n');
104 let mut lines: Vec<String> = s
105 .split('\n')
106 .flat_map(|x| {
107 split_on_length(x.to_string(), line_length)
108 .into_iter()
109 .chain(std::iter::once(String::new()))
110 })
111 .collect();
112 if ends_with_newline {
113 if lines.last().map(|l| l.is_empty()).unwrap_or(false) { lines.pop(); }
114 if lines.last().map(|l| l.is_empty()).unwrap_or(false) { lines.pop(); }
115 }
116 lines
117}
118
119#[derive(Debug, Clone, PartialEq)]
120pub enum SingleQuotedStringPart {
121 String(String),
122 EscapedChar(SingleQuotedStringEscapedChar),
123 BlankLines(usize),
124 RemovableNewline,
125}
126
127#[derive(Debug, PartialEq, Clone, Copy)]
128pub enum SingleQuotedStringEscapedChar {
129 SingleQuote,
130}
131
132impl SingleQuotedStringEscapedChar {
133 pub fn char(&self) -> char {
134 match self {
135 SingleQuotedStringEscapedChar::SingleQuote => '\'',
136 }
137 }
138}
139
140pub fn parse_single_quoted_string(parts: &Vec<SingleQuotedStringPart>) -> String {
141 let mut result = String::new();
142 for part in parts.iter() {
143 match part {
144 SingleQuotedStringPart::String(s) => result.push_str(&s),
145 SingleQuotedStringPart::EscapedChar(c) => result.push(c.char()),
146 SingleQuotedStringPart::BlankLines(nb) => {
147 for _ in 0..*nb {
148 result.push_str("\n");
149 }
150 }
151 SingleQuotedStringPart::RemovableNewline => (),
152 }
153 }
154 result
155}
156
157#[derive(Debug, Clone, PartialEq)]
158pub enum DoubleQuotedStringPart {
159 String(String),
160 EscapedChar(DoubleQuotedStringEscapedChar),
161 BlankLines(usize),
162 RemovableNewline,
163}
164
165#[derive(Debug, PartialEq, Clone, Copy)]
166pub enum DoubleQuotedStringEscapedChar {
167 Quote,
168 Backslash,
169 Tab,
170 CarriageReturn,
171 Newline,
172 RealNewline,
173}
174
175impl DoubleQuotedStringEscapedChar {
176 pub fn char(&self) -> char {
177 match self {
178 DoubleQuotedStringEscapedChar::Quote => '"',
179 DoubleQuotedStringEscapedChar::Backslash => '\\',
180 DoubleQuotedStringEscapedChar::Tab => 't',
181 DoubleQuotedStringEscapedChar::CarriageReturn => 'r',
182 DoubleQuotedStringEscapedChar::Newline => 'n',
183 DoubleQuotedStringEscapedChar::RealNewline => '\n',
184 }
185 }
186 pub fn real_char(&self) -> char {
187 match self {
188 DoubleQuotedStringEscapedChar::Quote => '"',
189 DoubleQuotedStringEscapedChar::Backslash => '\\',
190 DoubleQuotedStringEscapedChar::Tab => '\t',
191 DoubleQuotedStringEscapedChar::CarriageReturn => '\r',
192 DoubleQuotedStringEscapedChar::Newline => '\n',
193 DoubleQuotedStringEscapedChar::RealNewline => '\n',
194 }
195 }
196}
197
198pub fn parse_double_quoted_string(parts: &Vec<DoubleQuotedStringPart>) -> String {
199 let mut s = String::new();
200 for part in parts {
201 match part {
202 DoubleQuotedStringPart::String(s2) => s.push_str(&s2),
203 DoubleQuotedStringPart::EscapedChar(c) => s.push(c.real_char()),
204 DoubleQuotedStringPart::BlankLines(n) => {
205 for _ in 0..*n {
206 s.push_str("\n");
207 }
208 }
209 DoubleQuotedStringPart::RemovableNewline => (),
210 }
211 }
212 s
213}
214
215#[cfg(test)]
216mod tests {
217 use crate::yaml::BlockChomping;
218
219 #[test]
220 fn parse_folded() {
221 let input = vec![
222 "Several lines of text,",
223 r#"with some "quotes" of various 'types',"#,
224 "and also a blank line:",
225 "",
226 "and some text with",
227 " extra indentation",
228 "on the next line,",
229 "plus another line at the end.",
230 "",
231 "",
232 ];
233 assert_eq!(
234 super::parse_folded(input, BlockChomping::Clip),
235 r#"Several lines of text, with some "quotes" of various 'types', and also a blank line:
236and some text with
237 extra indentation
238on the next line, plus another line at the end.
239"#
240 )
241 }
242 #[test]
268 fn create_folded() {
269 let input = r#"Several lines of text, with some "quotes" of various 'types', and also a blank line:
270and some text with
271 extra indentation
272on the next line, plus another line at the end.
273"#;
274 assert_eq!(
275 super::create_folded(input.to_string(), 40),
276 vec![
277 r#"Several lines of text, with some"#,
278 r#""quotes" of various 'types', and also a"#,
279 "blank line:",
280 "",
281 "and some text with",
282 "",
283 " extra indentation",
284 "",
285 "on the next line, plus another line at",
286 "the end.",
287 ]
288 )
289 }
290
291 #[test]
292 fn parse_literal() {
293 let mut input = vec![
294 "Several lines of text,",
295 r#"with some "quotes" of various 'types',"#,
296 "and also a blank line:",
297 "",
298 "and some text with",
299 " extra indentation",
300 "on the next line,",
301 "plus another line at the end.",
302 "",
303 "",
304 ];
305 let result = r#"Several lines of text,
306with some "quotes" of various 'types',
307and also a blank line:
308
309and some text with
310 extra indentation
311on the next line,
312plus another line at the end.
313"#;
314 assert_eq!(
315 super::parse_literal(input.clone(), BlockChomping::default()),
316 result
317 );
318 input.pop();
321 input.pop();
322 assert_eq!(input, super::create_literal(result.to_string()));
323
324 let with_leading = format!(" {}", result);
326 let lines_with_leading = super::create_literal(with_leading);
327 assert!(lines_with_leading[0].starts_with(' '), "leading space must be preserved")
328 }
329
330 }