diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/yaml-rust/src/emitter.rs | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/yaml-rust/src/emitter.rs')
-rw-r--r-- | third_party/rust/yaml-rust/src/emitter.rs | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/third_party/rust/yaml-rust/src/emitter.rs b/third_party/rust/yaml-rust/src/emitter.rs new file mode 100644 index 0000000000..f20a3ed679 --- /dev/null +++ b/third_party/rust/yaml-rust/src/emitter.rs @@ -0,0 +1,635 @@ +use std::convert::From; +use std::error::Error; +use std::fmt::{self, Display}; +use crate::yaml::{Hash, Yaml}; + +#[derive(Copy, Clone, Debug)] +pub enum EmitError { + FmtError(fmt::Error), + BadHashmapKey, +} + +impl Error for EmitError { + fn cause(&self) -> Option<&dyn Error> { + None + } +} + +impl Display for EmitError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match *self { + EmitError::FmtError(ref err) => Display::fmt(err, formatter), + EmitError::BadHashmapKey => formatter.write_str("bad hashmap key"), + } + } +} + +impl From<fmt::Error> for EmitError { + fn from(f: fmt::Error) -> Self { + EmitError::FmtError(f) + } +} + +pub struct YamlEmitter<'a> { + writer: &'a mut dyn fmt::Write, + best_indent: usize, + compact: bool, + + level: isize, +} + +pub type EmitResult = Result<(), EmitError>; + +// from serialize::json +fn escape_str(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error> { + wr.write_str("\"")?; + + let mut start = 0; + + for (i, byte) in v.bytes().enumerate() { + let escaped = match byte { + b'"' => "\\\"", + b'\\' => "\\\\", + b'\x00' => "\\u0000", + b'\x01' => "\\u0001", + b'\x02' => "\\u0002", + b'\x03' => "\\u0003", + b'\x04' => "\\u0004", + b'\x05' => "\\u0005", + b'\x06' => "\\u0006", + b'\x07' => "\\u0007", + b'\x08' => "\\b", + b'\t' => "\\t", + b'\n' => "\\n", + b'\x0b' => "\\u000b", + b'\x0c' => "\\f", + b'\r' => "\\r", + b'\x0e' => "\\u000e", + b'\x0f' => "\\u000f", + b'\x10' => "\\u0010", + b'\x11' => "\\u0011", + b'\x12' => "\\u0012", + b'\x13' => "\\u0013", + b'\x14' => "\\u0014", + b'\x15' => "\\u0015", + b'\x16' => "\\u0016", + b'\x17' => "\\u0017", + b'\x18' => "\\u0018", + b'\x19' => "\\u0019", + b'\x1a' => "\\u001a", + b'\x1b' => "\\u001b", + b'\x1c' => "\\u001c", + b'\x1d' => "\\u001d", + b'\x1e' => "\\u001e", + b'\x1f' => "\\u001f", + b'\x7f' => "\\u007f", + _ => continue, + }; + + if start < i { + wr.write_str(&v[start..i])?; + } + + wr.write_str(escaped)?; + + start = i + 1; + } + + if start != v.len() { + wr.write_str(&v[start..])?; + } + + wr.write_str("\"")?; + Ok(()) +} + +impl<'a> YamlEmitter<'a> { + pub fn new(writer: &'a mut dyn fmt::Write) -> YamlEmitter { + YamlEmitter { + writer, + best_indent: 2, + compact: true, + level: -1, + } + } + + /// Set 'compact inline notation' on or off, as described for block + /// [sequences](http://www.yaml.org/spec/1.2/spec.html#id2797382) + /// and + /// [mappings](http://www.yaml.org/spec/1.2/spec.html#id2798057). + /// + /// In this form, blocks cannot have any properties (such as anchors + /// or tags), which should be OK, because this emitter doesn't + /// (currently) emit those anyways. + pub fn compact(&mut self, compact: bool) { + self.compact = compact; + } + + /// Determine if this emitter is using 'compact inline notation'. + pub fn is_compact(&self) -> bool { + self.compact + } + + pub fn dump(&mut self, doc: &Yaml) -> EmitResult { + // write DocumentStart + writeln!(self.writer, "---")?; + self.level = -1; + self.emit_node(doc) + } + + fn write_indent(&mut self) -> EmitResult { + if self.level <= 0 { + return Ok(()); + } + for _ in 0..self.level { + for _ in 0..self.best_indent { + write!(self.writer, " ")?; + } + } + Ok(()) + } + + fn emit_node(&mut self, node: &Yaml) -> EmitResult { + match *node { + Yaml::Array(ref v) => self.emit_array(v), + Yaml::Hash(ref h) => self.emit_hash(h), + Yaml::String(ref v) => { + if need_quotes(v) { + escape_str(self.writer, v)?; + } else { + write!(self.writer, "{}", v)?; + } + Ok(()) + } + Yaml::Boolean(v) => { + if v { + self.writer.write_str("true")?; + } else { + self.writer.write_str("false")?; + } + Ok(()) + } + Yaml::Integer(v) => { + write!(self.writer, "{}", v)?; + Ok(()) + } + Yaml::Real(ref v) => { + write!(self.writer, "{}", v)?; + Ok(()) + } + Yaml::Null | Yaml::BadValue => { + write!(self.writer, "~")?; + Ok(()) + } + // XXX(chenyh) Alias + _ => Ok(()), + } + } + + fn emit_array(&mut self, v: &[Yaml]) -> EmitResult { + if v.is_empty() { + write!(self.writer, "[]")?; + } else { + self.level += 1; + for (cnt, x) in v.iter().enumerate() { + if cnt > 0 { + writeln!(self.writer)?; + self.write_indent()?; + } + write!(self.writer, "-")?; + self.emit_val(true, x)?; + } + self.level -= 1; + } + Ok(()) + } + + fn emit_hash(&mut self, h: &Hash) -> EmitResult { + if h.is_empty() { + self.writer.write_str("{}")?; + } else { + self.level += 1; + for (cnt, (k, v)) in h.iter().enumerate() { + let complex_key = match *k { + Yaml::Hash(_) | Yaml::Array(_) => true, + _ => false, + }; + if cnt > 0 { + writeln!(self.writer)?; + self.write_indent()?; + } + if complex_key { + write!(self.writer, "?")?; + self.emit_val(true, k)?; + writeln!(self.writer)?; + self.write_indent()?; + write!(self.writer, ":")?; + self.emit_val(true, v)?; + } else { + self.emit_node(k)?; + write!(self.writer, ":")?; + self.emit_val(false, v)?; + } + } + self.level -= 1; + } + Ok(()) + } + + /// Emit a yaml as a hash or array value: i.e., which should appear + /// following a ":" or "-", either after a space, or on a new line. + /// If `inline` is true, then the preceding characters are distinct + /// and short enough to respect the compact flag. + fn emit_val(&mut self, inline: bool, val: &Yaml) -> EmitResult { + match *val { + Yaml::Array(ref v) => { + if (inline && self.compact) || v.is_empty() { + write!(self.writer, " ")?; + } else { + writeln!(self.writer)?; + self.level += 1; + self.write_indent()?; + self.level -= 1; + } + self.emit_array(v) + } + Yaml::Hash(ref h) => { + if (inline && self.compact) || h.is_empty() { + write!(self.writer, " ")?; + } else { + writeln!(self.writer)?; + self.level += 1; + self.write_indent()?; + self.level -= 1; + } + self.emit_hash(h) + } + _ => { + write!(self.writer, " ")?; + self.emit_node(val) + } + } + } +} + +/// Check if the string requires quoting. +/// Strings starting with any of the following characters must be quoted. +/// :, &, *, ?, |, -, <, >, =, !, %, @ +/// Strings containing any of the following characters must be quoted. +/// {, }, [, ], ,, #, ` +/// +/// If the string contains any of the following control characters, it must be escaped with double quotes: +/// \0, \x01, \x02, \x03, \x04, \x05, \x06, \a, \b, \t, \n, \v, \f, \r, \x0e, \x0f, \x10, \x11, \x12, \x13, \x14, \x15, \x16, \x17, \x18, \x19, \x1a, \e, \x1c, \x1d, \x1e, \x1f, \N, \_, \L, \P +/// +/// Finally, there are other cases when the strings must be quoted, no matter if you're using single or double quotes: +/// * When the string is true or false (otherwise, it would be treated as a boolean value); +/// * When the string is null or ~ (otherwise, it would be considered as a null value); +/// * When the string looks like a number, such as integers (e.g. 2, 14, etc.), floats (e.g. 2.6, 14.9) and exponential numbers (e.g. 12e7, etc.) (otherwise, it would be treated as a numeric value); +/// * When the string looks like a date (e.g. 2014-12-31) (otherwise it would be automatically converted into a Unix timestamp). +fn need_quotes(string: &str) -> bool { + fn need_quotes_spaces(string: &str) -> bool { + string.starts_with(' ') || string.ends_with(' ') + } + + string == "" + || need_quotes_spaces(string) + || string.starts_with(|character: char| match character { + '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@' => true, + _ => false, + }) + || string.contains(|character: char| match character { + ':' + | '{' + | '}' + | '[' + | ']' + | ',' + | '#' + | '`' + | '\"' + | '\'' + | '\\' + | '\0'..='\x06' + | '\t' + | '\n' + | '\r' + | '\x0e'..='\x1a' + | '\x1c'..='\x1f' => true, + _ => false, + }) + || [ + // http://yaml.org/type/bool.html + // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse + // them as string, not booleans, although it is violating the YAML 1.1 specification. + // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088. + "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", + "false", "on", "On", "ON", "off", "Off", "OFF", + // http://yaml.org/type/null.html + "null", "Null", "NULL", "~", + ] + .contains(&string) + || string.starts_with('.') + || string.starts_with("0x") + || string.parse::<i64>().is_ok() + || string.parse::<f64>().is_ok() +} + +#[cfg(test)] +mod test { + use super::*; + use crate::YamlLoader; + + #[test] + fn test_emit_simple() { + let s = " +# comment +a0 bb: val +a1: + b1: 4 + b2: d +a2: 4 # i'm comment +a3: [1, 2, 3] +a4: + - [a1, a2] + - 2 +"; + + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.dump(doc).unwrap(); + } + println!("original:\n{}", s); + println!("emitted:\n{}", writer); + let docs_new = match YamlLoader::load_from_str(&writer) { + Ok(y) => y, + Err(e) => panic!(format!("{}", e)), + }; + let doc_new = &docs_new[0]; + + assert_eq!(doc, doc_new); + } + + #[test] + fn test_emit_complex() { + let s = r#" +cataloge: + product: &coffee { name: Coffee, price: 2.5 , unit: 1l } + product: &cookies { name: Cookies!, price: 3.40 , unit: 400g} + +products: + *coffee: + amount: 4 + *cookies: + amount: 4 + [1,2,3,4]: + array key + 2.4: + real key + true: + bool key + {}: + empty hash key + "#; + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.dump(doc).unwrap(); + } + let docs_new = match YamlLoader::load_from_str(&writer) { + Ok(y) => y, + Err(e) => panic!(format!("{}", e)), + }; + let doc_new = &docs_new[0]; + assert_eq!(doc, doc_new); + } + + #[test] + fn test_emit_avoid_quotes() { + let s = r#"--- +a7: 你好 +boolean: "true" +boolean2: "false" +date: 2014-12-31 +empty_string: "" +empty_string1: " " +empty_string2: " a" +empty_string3: " a " +exp: "12e7" +field: ":" +field2: "{" +field3: "\\" +field4: "\n" +field5: "can't avoid quote" +float: "2.6" +int: "4" +nullable: "null" +nullable2: "~" +products: + "*coffee": + amount: 4 + "*cookies": + amount: 4 + ".milk": + amount: 1 + "2.4": real key + "[1,2,3,4]": array key + "true": bool key + "{}": empty hash key +x: test +y: avoid quoting here +z: string with spaces"#; + + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.dump(doc).unwrap(); + } + + assert_eq!(s, writer, "actual:\n\n{}\n", writer); + } + + #[test] + fn emit_quoted_bools() { + let input = r#"--- +string0: yes +string1: no +string2: "true" +string3: "false" +string4: "~" +null0: ~ +[true, false]: real_bools +[True, TRUE, False, FALSE, y,Y,yes,Yes,YES,n,N,no,No,NO,on,On,ON,off,Off,OFF]: false_bools +bool0: true +bool1: false"#; + let expected = r#"--- +string0: "yes" +string1: "no" +string2: "true" +string3: "false" +string4: "~" +null0: ~ +? - true + - false +: real_bools +? - "True" + - "TRUE" + - "False" + - "FALSE" + - y + - Y + - "yes" + - "Yes" + - "YES" + - n + - N + - "no" + - "No" + - "NO" + - "on" + - "On" + - "ON" + - "off" + - "Off" + - "OFF" +: false_bools +bool0: true +bool1: false"#; + + let docs = YamlLoader::load_from_str(&input).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.dump(doc).unwrap(); + } + + assert_eq!( + expected, writer, + "expected:\n{}\nactual:\n{}\n", + expected, writer + ); + } + + #[test] + fn test_empty_and_nested() { + test_empty_and_nested_flag(false) + } + + #[test] + fn test_empty_and_nested_compact() { + test_empty_and_nested_flag(true) + } + + fn test_empty_and_nested_flag(compact: bool) { + let s = if compact { + r#"--- +a: + b: + c: hello + d: {} +e: + - f + - g + - h: []"# + } else { + r#"--- +a: + b: + c: hello + d: {} +e: + - f + - g + - + h: []"# + }; + + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.compact(compact); + emitter.dump(doc).unwrap(); + } + + assert_eq!(s, writer); + } + + #[test] + fn test_nested_arrays() { + let s = r#"--- +a: + - b + - - c + - d + - - e + - f"#; + + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.dump(doc).unwrap(); + } + println!("original:\n{}", s); + println!("emitted:\n{}", writer); + + assert_eq!(s, writer); + } + + #[test] + fn test_deeply_nested_arrays() { + let s = r#"--- +a: + - b + - - c + - d + - - e + - - f + - - e"#; + + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.dump(doc).unwrap(); + } + println!("original:\n{}", s); + println!("emitted:\n{}", writer); + + assert_eq!(s, writer); + } + + #[test] + fn test_nested_hashes() { + let s = r#"--- +a: + b: + c: + d: + e: f"#; + + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitter::new(&mut writer); + emitter.dump(doc).unwrap(); + } + println!("original:\n{}", s); + println!("emitted:\n{}", writer); + + assert_eq!(s, writer); + } + +} |