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};
21pub 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 } else {
125 true }
127 } else {
128 true }
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(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, 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
488pub 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
570fn 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 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 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 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}