Emit multi-line string values as block scalars
This commit is contained in:
parent
a33d0ffe68
commit
5369a002ba
3 changed files with 92 additions and 2 deletions
|
@ -109,3 +109,16 @@ pub(crate) fn is_uri_char(c: char) -> bool {
|
|||
pub(crate) fn is_tag_char(c: char) -> bool {
|
||||
is_uri_char(c) && !is_flow(c) && c != '!'
|
||||
}
|
||||
|
||||
/// Check if the string can be expressed a valid literal block scalar.
|
||||
/// The YAML spec supports all of the following in block literals except #xFEFF:
|
||||
/// ```ignore
|
||||
/// #x9 | #xA | [#x20-#x7E] /* 8 bit */
|
||||
/// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] /* 16 bit */
|
||||
/// | [#x10000-#x10FFFF] /* 32 bit */
|
||||
/// ```
|
||||
#[inline]
|
||||
pub(crate) fn is_valid_literal_block_scalar(string: &str) -> bool {
|
||||
string.chars().all(|character: char|
|
||||
matches!(character, '\t' | '\n' | '\x20'..='\x7e' | '\u{0085}' | '\u{00a0}'..='\u{d7fff}'))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::char_traits;
|
||||
use crate::yaml::{Hash, Yaml};
|
||||
use std::convert::From;
|
||||
use std::error::Error;
|
||||
|
@ -35,8 +36,8 @@ pub struct YamlEmitter<'a> {
|
|||
writer: &'a mut dyn fmt::Write,
|
||||
best_indent: usize,
|
||||
compact: bool,
|
||||
|
||||
level: isize,
|
||||
multiline_strings: bool,
|
||||
}
|
||||
|
||||
pub type EmitResult = Result<(), EmitError>;
|
||||
|
@ -111,6 +112,7 @@ impl<'a> YamlEmitter<'a> {
|
|||
best_indent: 2,
|
||||
compact: true,
|
||||
level: -1,
|
||||
multiline_strings: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,6 +134,40 @@ impl<'a> YamlEmitter<'a> {
|
|||
self.compact
|
||||
}
|
||||
|
||||
/// Render strings containing multiple lines in [literal style].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use yaml_rust2::{Yaml, YamlEmitter, YamlLoader};
|
||||
///
|
||||
/// let input = r#"{foo: "bar!\nbar!", baz: 42}"#;
|
||||
/// let parsed = YamlLoader::load_from_str(input).unwrap();
|
||||
/// eprintln!("{:?}", parsed);
|
||||
///
|
||||
/// let mut output = String::new();
|
||||
/// let mut emitter = YamlEmitter::new(&mut output);
|
||||
/// emitter.multiline_strings(true);
|
||||
/// emitter.dump(&parsed[0]).unwrap();
|
||||
/// assert_eq!(output.as_str(), "\
|
||||
/// ---
|
||||
/// foo: |
|
||||
/// bar!
|
||||
/// bar!
|
||||
/// baz: 42");
|
||||
/// ```
|
||||
///
|
||||
/// [literal style]: https://yaml.org/spec/1.2/spec.html#id2795688
|
||||
pub fn multiline_strings(&mut self, multiline_strings: bool) {
|
||||
self.multiline_strings = multiline_strings;
|
||||
}
|
||||
|
||||
/// Determine if this emitter will emit multiline strings when appropriate.
|
||||
#[must_use]
|
||||
pub fn is_multiline_strings(&self) -> bool {
|
||||
self.multiline_strings
|
||||
}
|
||||
|
||||
pub fn dump(&mut self, doc: &Yaml) -> EmitResult {
|
||||
// write DocumentStart
|
||||
writeln!(self.writer, "---")?;
|
||||
|
@ -156,7 +192,20 @@ impl<'a> YamlEmitter<'a> {
|
|||
Yaml::Array(ref v) => self.emit_array(v),
|
||||
Yaml::Hash(ref h) => self.emit_hash(h),
|
||||
Yaml::String(ref v) => {
|
||||
if need_quotes(v) {
|
||||
if self.multiline_strings
|
||||
&& v.contains('\n')
|
||||
&& char_traits::is_valid_literal_block_scalar(v)
|
||||
{
|
||||
write!(self.writer, "|")?;
|
||||
self.level += 1;
|
||||
for line in v.lines() {
|
||||
writeln!(self.writer)?;
|
||||
self.write_indent()?;
|
||||
// It's literal text, so don't escape special chars.
|
||||
write!(self.writer, "{line}")?;
|
||||
}
|
||||
self.level -= 1;
|
||||
} else if need_quotes(v) {
|
||||
escape_str(self.writer, v)?;
|
||||
} else {
|
||||
write!(self.writer, "{v}")?;
|
||||
|
@ -334,3 +383,19 @@ fn need_quotes(string: &str) -> bool {
|
|||
|| string.parse::<i64>().is_ok()
|
||||
|| string.parse::<f64>().is_ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::YamlEmitter;
|
||||
use crate::YamlLoader;
|
||||
|
||||
#[test]
|
||||
fn test_multiline_string() {
|
||||
let input = r#"{foo: "bar!\nbar!", baz: 42}"#;
|
||||
let parsed = YamlLoader::load_from_str(input).unwrap();
|
||||
let mut output = String::new();
|
||||
let mut emitter = YamlEmitter::new(&mut output);
|
||||
emitter.multiline_strings(true);
|
||||
emitter.dump(&parsed[0]).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,3 +68,15 @@ fn test_issue133() {
|
|||
let doc2 = YamlLoader::load_from_str(&out_str).unwrap().pop().unwrap();
|
||||
assert_eq!(doc, doc2); // This failed because the type has changed to a number now
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newline() {
|
||||
let y = Yaml::Array(vec![Yaml::String("\n".to_owned())]);
|
||||
roundtrip(&y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_crlf() {
|
||||
let y = Yaml::Array(vec![Yaml::String("\r\n".to_owned())]);
|
||||
roundtrip(&y);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue