Skip to main content

yaml_subset/yaml/
mod.rs

1mod aliased;
2mod array_data;
3mod document;
4mod hash_data;
5mod hash_element;
6mod insert;
7mod parser;
8pub mod string;
9mod types;
10#[cfg(feature = "serde")]
11pub mod serializer;
12#[cfg(feature = "serde")]
13pub mod deserializer;
14
15pub use aliased::AliasedYaml;
16pub use array_data::ArrayData;
17pub use document::{Document, DocumentData};
18pub use hash_data::HashData;
19pub use hash_element::HashElement;
20pub use insert::{MyVec, YamlInsert};
21//pub use old_parser::parse_yaml_file;
22pub use parser::{parse_yaml_file, DocumentResult, YamlError, YamlResult};
23pub use types::YamlTypes;
24#[cfg(feature = "serde")]
25pub use serializer::YamlSerializer;
26#[cfg(feature = "serde")]
27pub use deserializer::{from_yaml, from_yaml_str, YamlDeserializer, YamlDeserializeError};
28
29use crate::path::Condition;
30use crate::utils::indent;
31use crate::yaml::insert::Additive;
32use crate::yaml::string::parse_double_quoted_string;
33use crate::yaml::string::parse_single_quoted_string;
34use crate::yaml::string::{create_folded, create_literal};
35use crate::yaml::string::SingleQuotedStringEscapedChar;
36use crate::YamlPath;
37use std::fmt::Write;
38
39use aliased::Parent;
40pub use string::DoubleQuotedStringPart;
41pub use string::SingleQuotedStringPart;
42
43#[derive(Debug, Clone, PartialEq)]
44pub enum Yaml {
45    InlineHash(Vec<(String, Yaml)>),
46    Hash(Vec<HashData>),
47    InlineArray(Vec<Yaml>),
48    Array(Vec<ArrayData>),
49    SingleQuotedString(Vec<SingleQuotedStringPart>),
50    DoubleQuotedString(Vec<DoubleQuotedStringPart>),
51    UnquotedString(String),
52    FoldedString(Vec<String>, BlockChomping),
53    LiteralString(Vec<String>, BlockChomping),
54    Anchor(String),
55}
56
57impl Yaml {
58    fn key_index(&self, key: &String) -> Option<usize> {
59        match self {
60            Yaml::Hash(data) => data
61                .iter()
62                .enumerate()
63                .find(|(_, v)| match v {
64                    HashData::Element(e) => &e.key == key,
65                    _ => false,
66                })
67                .map(|(i, _)| i),
68            _ => None,
69        }
70    }
71    fn key_value(&self, key: &String) -> Option<&AliasedYaml> {
72        match self {
73            Yaml::Hash(data) => data.iter().enumerate().find_map(|(_, v)| match v {
74                HashData::Element(e) => {
75                    if &e.key == key {
76                        Some(&e.value)
77                    } else {
78                        None
79                    }
80                }
81                _ => None,
82            }),
83            _ => None,
84        }
85    }
86    fn key_value_owned(&self, key: &String) -> Option<AliasedYaml> {
87        match self {
88            Yaml::Hash(data) => data.iter().enumerate().find_map(|(_, v)| match v {
89                HashData::Element(e) => {
90                    if &e.key == key {
91                        Some(e.value.clone())
92                    } else {
93                        None
94                    }
95                }
96                _ => None,
97            }),
98            _ => None,
99        }
100    }
101    fn key_value_mut(&mut self, key: &String) -> Option<&mut AliasedYaml> {
102        match self {
103            Yaml::Hash(data) => data.iter_mut().enumerate().find_map(|(_, v)| match v {
104                HashData::Element(e) => {
105                    if &e.key == key {
106                        Some(&mut e.value)
107                    } else {
108                        None
109                    }
110                }
111                _ => None,
112            }),
113            _ => None,
114        }
115    }
116    fn fits_conditions(&self, conditions: &Vec<Condition>) -> bool {
117        conditions
118            .iter()
119            .find(|c| {
120                let index_opt = self.key_value(&c.field);
121                if let Some(yaml) = index_opt {
122                    if let Some(s) = yaml.as_string() {
123                        s != c.value // invalid, not equal to filter
124                    } else {
125                        true // invalid, field is not a string
126                    }
127                } else {
128                    true // invalid, field not found
129                }
130            })
131            .is_none()
132    }
133}
134impl YamlInsert for Yaml {
135    fn for_hash<F, R, A: Additive>(&mut self, path: &YamlPath, f: &F, r: &R) -> A
136    where
137        F: Fn(&mut HashElement) -> A,
138        R: Fn(&mut Yaml) -> A,
139    {
140        match path {
141            YamlPath::Root(conditions) => {
142                if !self.fits_conditions(conditions) {
143                    return A::zero();
144                }
145                r(self)
146            }
147            YamlPath::Key(k, conditions, _) => {
148                if !self.fits_conditions(conditions) {
149                    return A::zero();
150                }
151                let key_exists = self.key_index(&k);
152                match self {
153                    Yaml::Hash(data) => {
154                        if let Some(index) = key_exists {
155                            data[index].for_hash(path, f, r)
156                        } else {
157                            A::zero()
158                        }
159                    }
160                    _ => A::zero(),
161                }
162            }
163            YamlPath::Indexes(indexes, conditions, _) => match self {
164                Yaml::InlineArray(elements) => {
165                    let mut count = A::zero();
166                    for index in indexes.iter() {
167                        let element_opt = elements.get_mut(*index);
168                        if let Some(element) = element_opt {
169                            if element.fits_conditions(conditions) {
170                                count = count + element.for_hash(path, f, r)
171                            }
172                        }
173                    }
174                    count
175                }
176                Yaml::Array(elements) => {
177                    let mut elements: Vec<_> = elements
178                        .iter_mut()
179                        .filter(|e| matches!(e, ArrayData::Element(_)))
180                        .collect();
181                    let mut count = A::zero();
182                    for index in indexes.iter() {
183                        let element_opt = elements.get_mut(*index);
184                        if let Some(element) = element_opt {
185                            if element.fits_conditions(conditions) {
186                                count = count + (*element).for_hash(path, f, r)
187                            }
188                        }
189                    }
190                    count
191                }
192                _ => A::zero(),
193            },
194            //YamlPath::AllIndexes(Some(other_path)) => match self {
195            YamlPath::AllIndexes(conditions, _) => match self {
196                Yaml::InlineArray(elements) => {
197                    let mut count = A::zero();
198                    for element in elements.iter_mut() {
199                        if element.fits_conditions(conditions) {
200                            count = count + element.for_hash(path, f, r)
201                        }
202                    }
203                    count
204                }
205                Yaml::Array(elements) => {
206                    let mut count = A::zero();
207                    for element in elements.iter_mut() {
208                        if element.fits_conditions(conditions) {
209                            count = count + element.for_hash(path, f, r)
210                        }
211                    }
212                    count
213                }
214                _ => A::zero(),
215            },
216        }
217    }
218
219    fn edit_hash_structure<F>(&mut self, path: &YamlPath, f: &F) -> usize
220    where
221        F: Fn(&mut Vec<HashData>, String, Option<usize>) -> usize,
222    {
223        match path {
224            YamlPath::Root(_) => 0, // can't change structure of root
225            YamlPath::Key(k, conditions, other_path_opt) => {
226                if !self.fits_conditions(conditions) {
227                    return 0;
228                }
229                let key_exists = self.key_index(&k);
230                match self {
231                    Yaml::Hash(data) => {
232                        if let Some(other_path) = other_path_opt {
233                            if let Some(index) = key_exists {
234                                data[index].edit_hash_structure(&*other_path, f)
235                            } else {
236                                0
237                            }
238                        } else {
239                            f(data, k.clone(), key_exists)
240                        }
241                    }
242                    _ => 0,
243                }
244            }
245            YamlPath::Indexes(indexes, conditions, Some(other_path)) => match self {
246                Yaml::InlineArray(elements) => {
247                    let mut count = 0;
248                    for index in indexes.iter() {
249                        let element_opt = elements.get_mut(*index);
250                        if let Some(element) = element_opt {
251                            if element.fits_conditions(conditions) {
252                                count += element.edit_hash_structure(&*other_path, f)
253                            }
254                        }
255                    }
256                    count
257                }
258                Yaml::Array(elements) => {
259                    let mut elements: Vec<_> = elements
260                        .iter_mut()
261                        .filter(|e| matches!(e, ArrayData::Element(_)))
262                        .collect();
263                    let mut count = 0;
264                    for index in indexes.iter() {
265                        let element_opt = elements.get_mut(*index);
266                        if let Some(element) = element_opt {
267                            if element.fits_conditions(conditions) {
268                                count += (*element).edit_hash_structure(&*other_path, f)
269                            }
270                        }
271                    }
272                    count
273                }
274                _ => 0,
275            },
276            YamlPath::AllIndexes(conditions, Some(other_path)) => match self {
277                Yaml::InlineArray(elements) => {
278                    let mut count = 0;
279                    for element in elements.iter_mut() {
280                        if element.fits_conditions(conditions) {
281                            count += element.edit_hash_structure(&*other_path, f)
282                        }
283                    }
284                    count
285                }
286                Yaml::Array(elements) => {
287                    let mut count = 0;
288                    for element in elements.iter_mut() {
289                        match element {
290                            ArrayData::Element(element) => {
291                                if element.fits_conditions(conditions) {
292                                    count += element.edit_hash_structure(&*other_path, f)
293                                }
294                            }
295                            _ => (),
296                        }
297                    }
298                    count
299                }
300                _ => 0,
301            },
302            YamlPath::Indexes(_, _, None) | YamlPath::AllIndexes(_, None) => 0,
303        }
304    }
305}
306
307impl Yaml {
308    pub fn format(
309        &self,
310        f: &mut String,
311        spaces: usize,
312        parent: Option<Parent>,
313    ) -> std::fmt::Result {
314        match self {
315            Yaml::InlineHash(v) => {
316                write!(f, " {{")?;
317                for (idx, element) in v.iter().enumerate() {
318                    if idx > 0 {
319                        write!(f, ",")?;
320                    }
321                    write!(f, " {}:", element.0)?;
322                    element.1.format(f, 0, None)?;
323                }
324                if !v.is_empty() {
325                    write!(f, " ")?;
326                }
327                write!(f, "}}")
328            }
329            Yaml::Hash(v) => {
330                if v.is_empty() {
331                    return write!(f, " {{}}");
332                }
333                for (idx, element) in v.iter().enumerate() {
334                    match element {
335                        HashData::Comment(c) => {
336                            write!(f, "\n{}#{}", indent(spaces), c)
337                        }
338                        HashData::InlineComment(c) => write!(f, " #{}", c),
339                        HashData::Element(v) => {
340                            let element = Some(Parent::Hash);
341                            let same_line = match (element, parent) {
342                                (Some(_), Some(Parent::Array)) => idx == 0,
343                                _ => false,
344                            };
345
346                            if same_line {
347                                write!(f, " {}:", v.key)?;
348                            } else {
349                                writeln!(f, "")?;
350                                write!(f, "{}{}:", indent(spaces), v.key)?;
351                            };
352
353                            v.value.format(f, spaces, element)
354                        }
355                    }?;
356                }
357                Ok(())
358            }
359            Yaml::InlineArray(v) => {
360                write!(f, " [")?;
361                for (idx, element) in v.iter().enumerate() {
362                    if idx > 0 {
363                        write!(f, ",")?;
364                    }
365                    element.format(f, 0, None)?;
366                }
367                if !v.is_empty() {
368                    write!(f, " ")?;
369                }
370                write!(f, "]")
371            }
372            Yaml::Array(v) => {
373                if v.is_empty() {
374                    return write!(f, " []");
375                }
376                for (idx, element) in v.iter().enumerate() {
377                    match element {
378                        ArrayData::Comment(c) => write!(f, "\n{}#{}", indent(spaces), c),
379                        ArrayData::InlineComment(c) => write!(f, " #{}", c),
380                        ArrayData::Element(v) => {
381                            let element = Some(Parent::Array);
382                            let same_line = match (element, parent) {
383                                (Some(_), Some(Parent::Array)) => idx == 0,
384                                _ => false,
385                            };
386
387                            if same_line {
388                                write!(f, " -")?;
389                            } else {
390                                writeln!(f, "")?;
391                                write!(f, "{}-", indent(spaces))?;
392                            };
393
394                            v.format(f, spaces, element)
395                        }
396                    }?
397                }
398                Ok(())
399            }
400            Yaml::UnquotedString(s) => {
401                write!(f, " {}", s)
402            }
403            Yaml::DoubleQuotedString(parts) => {
404                write!(f, " \"")?;
405                for part in parts.iter() {
406                    match part {
407                        DoubleQuotedStringPart::String(s) => {
408                            write!(f, "{}", s)?;
409                        }
410                        DoubleQuotedStringPart::EscapedChar(c) => {
411                            write!(f, "\\{}", c.char())?;
412                        }
413                        DoubleQuotedStringPart::BlankLines(nb) => {
414                            for _ in 0..(*nb + 1) {
415                                writeln!(f, "")?;
416                            }
417                        }
418                        DoubleQuotedStringPart::RemovableNewline => {
419                            writeln!(f, "")?;
420                        }
421                    }
422                }
423                write!(f, "\"")
424            }
425            Yaml::SingleQuotedString(parts) => {
426                write!(f, " '")?;
427                for part in parts.iter() {
428                    match part {
429                        SingleQuotedStringPart::String(s) => {
430                            write!(f, "{}", s)?;
431                        }
432                        SingleQuotedStringPart::EscapedChar(c) => {
433                            write!(f, "'{}", c.char())?;
434                        }
435                        SingleQuotedStringPart::BlankLines(nb) => {
436                            for _ in 0..(*nb + 1) {
437                                writeln!(f, "")?;
438                            }
439                        }
440                        SingleQuotedStringPart::RemovableNewline => {
441                            writeln!(f, "")?;
442                        }
443                    }
444                }
445                write!(f, "'")
446            }
447            Yaml::LiteralString(lines, chomping) => {
448                for (idx, line) in lines.iter().enumerate() {
449                    if idx == 0 {
450                        write!(f, " |")?;
451                        chomping.write(f)?;
452                        if line.starts_with(" ") {
453                            write!(f, "2")?;
454                        }
455                    }
456                    write!(f, "\n{}{}", indent(spaces), line)?;
457                }
458                Ok(())
459            }
460            Yaml::FoldedString(lines, chomping) => {
461                for (idx, line) in lines.iter().enumerate() {
462                    if idx == 0 {
463                        write!(f, " >")?;
464                        chomping.write(f)?;
465                        if line.starts_with(" ") {
466                            write!(f, "2")?;
467                        }
468                    }
469                    write!(f, "\n{}{}", indent(spaces), line)?;
470                }
471                Ok(())
472            }
473            Yaml::Anchor(a) => write!(f, " *{}", a),
474        }
475    }
476}
477
478pub trait Pretty {
479    fn pretty_with_options(self, in_inline: bool, child_of_array: bool) -> Self;
480    fn pretty(self) -> Self
481    where
482        Self: Sized,
483    {
484        self.pretty_with_options(false, false)
485    }
486}
487
488/// Apply a closure bottom-up to every [`Yaml`] node (children first, then the
489/// current node). Implement this trait on container types to enable recursive
490/// rewrites without manual match arms for every variant.
491pub trait MapYaml {
492    fn map_yaml<F: FnMut(Yaml) -> Yaml>(self, f: &mut F) -> Self;
493}
494
495impl<T: MapYaml> MapYaml for Vec<T> {
496    fn map_yaml<F: FnMut(Yaml) -> Yaml>(self, f: &mut F) -> Self {
497        self.into_iter().map(|x| x.map_yaml(f)).collect()
498    }
499}
500
501impl MapYaml for Yaml {
502    fn map_yaml<F: FnMut(Yaml) -> Yaml>(self, f: &mut F) -> Yaml {
503        let descended = match self {
504            Yaml::Hash(data) => Yaml::Hash(data.map_yaml(f)),
505            Yaml::InlineHash(data) => Yaml::InlineHash(
506                data.into_iter().map(|(k, v)| (k, v.map_yaml(f))).collect(),
507            ),
508            Yaml::Array(data) => Yaml::Array(data.map_yaml(f)),
509            Yaml::InlineArray(data) => Yaml::InlineArray(data.map_yaml(f)),
510            leaf => leaf,
511        };
512        f(descended)
513    }
514}
515
516pub trait VisitYaml {
517    fn visit_yaml<F: FnMut(&AliasedYaml)>(&self, f: &mut F);
518}
519
520impl<T: VisitYaml> VisitYaml for Vec<T> {
521    fn visit_yaml<F: FnMut(&AliasedYaml)>(&self, f: &mut F) {
522        for item in self {
523            item.visit_yaml(f);
524        }
525    }
526}
527
528impl VisitYaml for Yaml {
529    fn visit_yaml<F: FnMut(&AliasedYaml)>(&self, f: &mut F) {
530        match self {
531            Yaml::Hash(data) => data.visit_yaml(f),
532            Yaml::InlineHash(data) => {
533                for (_, v) in data {
534                    v.visit_yaml(f);
535                }
536            }
537            Yaml::Array(data) => data.visit_yaml(f),
538            Yaml::InlineArray(data) => {
539                for v in data {
540                    v.visit_yaml(f);
541                }
542            }
543            Yaml::SingleQuotedString(_)
544            | Yaml::DoubleQuotedString(_)
545            | Yaml::UnquotedString(_)
546            | Yaml::FoldedString(_, _)
547            | Yaml::LiteralString(_, _)
548            | Yaml::Anchor(_) => {}
549        }
550    }
551}
552
553fn is_inlineable(yaml: &Yaml) -> bool {
554    matches!(
555        yaml,
556        Yaml::UnquotedString(_)
557            | Yaml::DoubleQuotedString(_)
558            | Yaml::SingleQuotedString(_)
559            | Yaml::InlineArray(_)
560            | Yaml::InlineHash(_)
561    )
562}
563
564impl<T: Pretty> Pretty for Vec<T> {
565    fn pretty_with_options(self, in_inline: bool, child_of_array: bool) -> Self {
566        self.into_iter().map(|x| x.pretty_with_options(in_inline, child_of_array)).collect()
567    }
568}
569
570// Returns true when a string can be written as a plain (unquoted) YAML scalar in block context.
571// Rules based on yaml_subset's grammar:
572//   - start disallowed: [ ] { } " ' # > | newline
573//   - anywhere disallowed: ": " (colon-space), # , newlines
574// Also rejects strings that serde's untagged-enum Content mechanism misinterprets:
575// untagged enum deserialization calls deserialize_any to collect into Content, and
576// deserialize_any maps UnquotedString("2") → visit_i64(2) → Content::I64(2).
577// serde's ContentDeserializer::deserialize_string then fails on Content::I64 because
578// it has no integer-to-string coercion. Quoting ensures Content::String is produced
579// so Vec<String> fields next to integer fields in the same struct both work.
580// Note: inline_breaking_chars must also be checked for inline (flow) context.
581fn can_be_unquoted_in_block(s: &str) -> bool {
582    if s.is_empty() || s.starts_with(' ') || s.ends_with(' ') {
583        return false;
584    }
585    match s.chars().next().unwrap() {
586        '[' | ']' | '{' | '}' | '"' | '\'' | '#' | '>' | '|' => return false,
587        _ => {}
588    }
589    if s.contains(": ") || s.contains('#') || s.contains('\n') || s.contains('\r') {
590        return false;
591    }
592    !s.parse::<i64>().is_ok() && !s.parse::<f64>().is_ok()
593        && !matches!(s, "true" | "false" | "null" | "~")
594}
595
596fn inline_breaking_chars(s: &str) -> bool {
597    s.contains(',') || s.contains('[') || s.contains(']')
598        || s.contains('{') || s.contains('}') || s.contains('"')
599        || s.contains('>') || s.contains('|')
600}
601
602fn str_to_single_quoted_parts(s: &str) -> Vec<SingleQuotedStringPart> {
603    let mut parts: Vec<SingleQuotedStringPart> = Vec::new();
604    let mut buf = String::new();
605    for ch in s.chars() {
606        if ch == '\'' {
607            if !buf.is_empty() {
608                parts.push(SingleQuotedStringPart::String(std::mem::take(&mut buf)));
609            }
610            parts.push(SingleQuotedStringPart::EscapedChar(SingleQuotedStringEscapedChar::SingleQuote));
611        } else {
612            buf.push(ch);
613        }
614    }
615    if !buf.is_empty() {
616        parts.push(SingleQuotedStringPart::String(buf));
617    }
618    parts
619}
620
621impl Pretty for Yaml {
622    fn pretty_with_options(self, in_inline: bool, child_of_array: bool) -> Yaml {
623        let max_line_length = 90;
624        match self {
625            Yaml::InlineHash(h) => Yaml::InlineHash(
626                h.into_iter().map(|(k, v)| (k, v.pretty_with_options(true, false))).collect(),
627            ),
628            Yaml::Hash(v) => {
629                if v.is_empty() {
630                    Yaml::InlineHash(Vec::new())
631                } else if in_inline {
632                    // In flow context, convert to InlineHash (dropping block comments).
633                    let entries: Vec<(String, Yaml)> = v
634                        .into_iter()
635                        .filter_map(|d| match d {
636                            HashData::Element(e) => {
637                                Some((e.key, e.value.value.pretty_with_options(true, false)))
638                            }
639                            _ => None,
640                        })
641                        .collect();
642                    Yaml::InlineHash(entries)
643                } else {
644                    Yaml::Hash(v.pretty_with_options(false, false))
645                }
646            }
647            Yaml::InlineArray(v) => Yaml::InlineArray(v.pretty_with_options(true, false)),
648            Yaml::Array(v) => {
649                if v.is_empty() {
650                    return Yaml::InlineArray(Vec::new());
651                }
652                if in_inline {
653                    // In flow context, must return InlineArray — process children inline too.
654                    let elements: Vec<Yaml> = v
655                        .into_iter()
656                        .filter_map(|d| match d {
657                            ArrayData::Element(e) => Some(e.value.pretty_with_options(true, false)),
658                            _ => None,
659                        })
660                        .collect();
661                    return Yaml::InlineArray(elements);
662                }
663                let prettied: Vec<ArrayData> = v.pretty_with_options(false, true);
664                // Auto-inline when this array is a direct child of another block array.
665                if child_of_array {
666                    let can_inline = prettied.iter().all(|d| match d {
667                        ArrayData::Element(e) => e.alias.is_none() && is_inlineable(&e.value),
668                        _ => false,
669                    });
670                    if can_inline {
671                        let elems: Vec<Yaml> = prettied
672                            .iter()
673                            .filter_map(|d| match d {
674                                ArrayData::Element(e) => Some(e.value.clone()),
675                                _ => None,
676                            })
677                            .collect();
678                        let candidate = Yaml::InlineArray(elems.clone());
679                        let mut rendered = String::new();
680                        if candidate.format(&mut rendered, 0, None).is_ok()
681                            && rendered.len() <= max_line_length
682                        {
683                            return Yaml::InlineArray(elems);
684                        }
685                    }
686                }
687                Yaml::Array(prettied)
688            }
689            Yaml::UnquotedString(s) => {
690                if in_inline && inline_breaking_chars(&s) {
691                    Yaml::SingleQuotedString(str_to_single_quoted_parts(&s))
692                } else {
693                    Yaml::UnquotedString(s)
694                }
695            }
696            Yaml::DoubleQuotedString(parts) => {
697                if in_inline {
698                    return Yaml::DoubleQuotedString(parts);
699                }
700                let contains_escape = parts
701                    .iter()
702                    .any(|x| matches!(x, DoubleQuotedStringPart::EscapedChar(_)));
703                let s = parse_double_quoted_string(&parts);
704                let total_length = s.len();
705                let chomping = if s.ends_with('\n') { BlockChomping::Keep } else { BlockChomping::Strip };
706                if contains_escape {
707                    Yaml::LiteralString(create_literal(s), chomping)
708                } else if total_length > max_line_length {
709                    Yaml::FoldedString(create_folded(s, max_line_length), chomping)
710                } else {
711                    Yaml::DoubleQuotedString(parts)
712                }
713            }
714            Yaml::SingleQuotedString(parts) => {
715                let contains_newline = parts
716                    .iter()
717                    .any(|x| matches!(x, SingleQuotedStringPart::BlankLines(_)));
718                let s = parse_single_quoted_string(&parts);
719                let total_length = s.len();
720                let chomping = if s.ends_with('\n') { BlockChomping::Keep } else { BlockChomping::Strip };
721                if contains_newline {
722                    return Yaml::LiteralString(create_literal(s), chomping);
723                }
724                if !in_inline && can_be_unquoted_in_block(&s) && !inline_breaking_chars(&s) && total_length <= max_line_length {
725                    return Yaml::UnquotedString(s);
726                }
727                if in_inline {
728                    return Yaml::SingleQuotedString(parts);
729                }
730                if total_length > max_line_length {
731                    Yaml::FoldedString(create_folded(s, max_line_length), chomping)
732                } else {
733                    Yaml::SingleQuotedString(parts)
734                }
735            }
736            Yaml::LiteralString(lines, chomping) => Yaml::LiteralString(lines, chomping),
737            Yaml::FoldedString(lines, chomping) => Yaml::FoldedString(lines, chomping),
738            Yaml::Anchor(a) => Yaml::Anchor(a),
739        }
740    }
741}
742
743#[derive(Debug, Clone, PartialEq, Copy)]
744pub enum BlockChomping {
745    Clip,
746    Strip,
747    Keep,
748}
749
750impl BlockChomping {
751    fn write(&self, f: &mut String) -> std::fmt::Result {
752        match self {
753            Self::Clip => Ok(()),
754            Self::Strip => write!(f, "-"),
755            Self::Keep => write!(f, "+"),
756        }
757    }
758}
759
760impl Default for BlockChomping {
761    fn default() -> Self {
762        Self::Clip
763    }
764}