2024-03-20 14:50:48 +00:00
//! YAML serialization helpers.
2024-03-17 07:47:11 +00:00
use crate ::char_traits ;
2023-08-11 23:54:46 +00:00
use crate ::yaml ::{ Hash , Yaml } ;
2015-05-31 04:56:45 +00:00
use std ::convert ::From ;
2017-05-08 18:43:42 +00:00
use std ::error ::Error ;
2018-09-15 16:49:04 +00:00
use std ::fmt ::{ self , Display } ;
2015-05-31 04:56:45 +00:00
2024-03-20 14:50:48 +00:00
/// An error when emitting YAML.
2015-05-31 04:56:45 +00:00
#[ derive(Copy, Clone, Debug) ]
pub enum EmitError {
2024-03-20 14:50:48 +00:00
/// A formatting error.
2018-09-15 16:49:04 +00:00
FmtError ( fmt ::Error ) ,
2015-05-31 04:56:45 +00:00
}
2017-05-08 18:43:42 +00:00
impl Error for EmitError {
2020-05-27 06:15:28 +00:00
fn cause ( & self ) -> Option < & dyn Error > {
2017-11-12 17:01:39 +00:00
None
}
2017-05-08 18:43:42 +00:00
}
impl Display for EmitError {
fn fmt ( & self , formatter : & mut fmt ::Formatter ) -> fmt ::Result {
match * self {
EmitError ::FmtError ( ref err ) = > Display ::fmt ( err , formatter ) ,
}
}
}
2015-05-31 04:56:45 +00:00
impl From < fmt ::Error > for EmitError {
fn from ( f : fmt ::Error ) -> Self {
EmitError ::FmtError ( f )
}
}
2024-03-20 14:50:48 +00:00
/// The YAML serializer.
///
/// ```
/// # use yaml_rust2::{YamlLoader, YamlEmitter};
/// let input_string = "a: b\nc: d";
/// let yaml = YamlLoader::load_from_str(input_string).unwrap();
///
/// let mut output = String::new();
/// YamlEmitter::new(&mut output).dump(&yaml[0]).unwrap();
///
/// assert_eq!(output, r#"---
/// a: b
/// c: d"#);
/// ```
2023-08-11 23:54:46 +00:00
#[ allow(clippy::module_name_repetitions) ]
2015-05-31 04:56:45 +00:00
pub struct YamlEmitter < ' a > {
2020-05-27 06:15:28 +00:00
writer : & ' a mut dyn fmt ::Write ,
2015-05-31 04:56:45 +00:00
best_indent : usize ,
2017-05-23 18:17:50 +00:00
compact : bool ,
2015-05-31 04:56:45 +00:00
level : isize ,
2024-03-17 07:47:11 +00:00
multiline_strings : bool ,
2015-05-31 04:56:45 +00:00
}
2024-03-20 14:50:48 +00:00
/// A convenience alias for emitter functions that may fail without returning a value.
2015-05-31 04:56:45 +00:00
pub type EmitResult = Result < ( ) , EmitError > ;
// from serialize::json
2020-05-27 06:15:28 +00:00
fn escape_str ( wr : & mut dyn fmt ::Write , v : & str ) -> Result < ( ) , fmt ::Error > {
2018-09-16 06:58:48 +00:00
wr . write_str ( " \" " ) ? ;
2015-05-31 04:56:45 +00:00
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 " ,
2018-09-15 16:49:04 +00:00
_ = > continue ,
2015-05-31 04:56:45 +00:00
} ;
if start < i {
2018-09-16 06:58:48 +00:00
wr . write_str ( & v [ start .. i ] ) ? ;
2015-05-31 04:56:45 +00:00
}
2018-09-16 06:58:48 +00:00
wr . write_str ( escaped ) ? ;
2015-05-31 04:56:45 +00:00
start = i + 1 ;
}
if start ! = v . len ( ) {
2018-09-16 06:58:48 +00:00
wr . write_str ( & v [ start .. ] ) ? ;
2015-05-31 04:56:45 +00:00
}
2018-09-16 06:58:48 +00:00
wr . write_str ( " \" " ) ? ;
2015-05-31 04:56:45 +00:00
Ok ( ( ) )
}
impl < ' a > YamlEmitter < ' a > {
2024-03-20 14:50:48 +00:00
/// Create a nwe emitter serializing into `writer`.
2020-05-27 06:15:28 +00:00
pub fn new ( writer : & ' a mut dyn fmt ::Write ) -> YamlEmitter {
2015-05-31 04:56:45 +00:00
YamlEmitter {
2018-09-15 17:03:55 +00:00
writer ,
2015-05-31 04:56:45 +00:00
best_indent : 2 ,
2017-05-23 18:17:50 +00:00
compact : true ,
2018-09-15 16:49:04 +00:00
level : - 1 ,
2024-03-17 07:47:11 +00:00
multiline_strings : false ,
2015-05-31 04:56:45 +00:00
}
}
2017-05-23 18:17:50 +00:00
/// 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 ) {
2018-09-15 16:49:04 +00:00
self . compact = compact ;
2017-05-23 18:17:50 +00:00
}
/// Determine if this emitter is using 'compact inline notation'.
2023-08-11 23:54:46 +00:00
#[ must_use ]
2017-05-23 18:17:50 +00:00
pub fn is_compact ( & self ) -> bool {
2018-09-15 16:49:04 +00:00
self . compact
2017-05-23 18:17:50 +00:00
}
2024-03-17 07:47:11 +00:00
/// 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
}
2024-03-17 10:15:56 +00:00
/// Dump Yaml to an output stream.
/// # Errors
/// Returns `EmitError` when an error occurs.
2015-05-31 04:56:45 +00:00
pub fn dump ( & mut self , doc : & Yaml ) -> EmitResult {
// write DocumentStart
2018-09-16 07:00:48 +00:00
writeln! ( self . writer , " --- " ) ? ;
2015-05-31 04:56:45 +00:00
self . level = - 1 ;
self . emit_node ( doc )
}
fn write_indent ( & mut self ) -> EmitResult {
2018-09-15 16:49:04 +00:00
if self . level < = 0 {
return Ok ( ( ) ) ;
}
2015-05-31 04:56:45 +00:00
for _ in 0 .. self . level {
for _ in 0 .. self . best_indent {
2018-09-16 06:58:48 +00:00
write! ( self . writer , " " ) ? ;
2015-05-31 04:56:45 +00:00
}
}
Ok ( ( ) )
}
fn emit_node ( & mut self , node : & Yaml ) -> EmitResult {
2016-02-28 00:30:13 +00:00
match * node {
2017-05-02 07:54:46 +00:00
Yaml ::Array ( ref v ) = > self . emit_array ( v ) ,
Yaml ::Hash ( ref h ) = > self . emit_hash ( h ) ,
2016-02-28 00:30:13 +00:00
Yaml ::String ( ref v ) = > {
2024-03-17 07:47:11 +00:00
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 ) {
2018-09-16 06:58:48 +00:00
escape_str ( self . writer , v ) ? ;
2018-09-15 16:49:04 +00:00
} else {
2023-08-11 23:54:46 +00:00
write! ( self . writer , " {v} " ) ? ;
2016-11-02 02:03:35 +00:00
}
2015-05-31 04:56:45 +00:00
Ok ( ( ) )
2018-09-15 16:49:04 +00:00
}
2016-02-28 00:30:13 +00:00
Yaml ::Boolean ( v ) = > {
2015-05-31 04:56:45 +00:00
if v {
2018-09-16 06:58:48 +00:00
self . writer . write_str ( " true " ) ? ;
2015-05-31 04:56:45 +00:00
} else {
2018-09-16 06:58:48 +00:00
self . writer . write_str ( " false " ) ? ;
2015-05-31 04:56:45 +00:00
}
Ok ( ( ) )
2018-09-15 16:49:04 +00:00
}
2016-02-28 00:30:13 +00:00
Yaml ::Integer ( v ) = > {
2023-08-11 23:54:46 +00:00
write! ( self . writer , " {v} " ) ? ;
2015-05-31 04:56:45 +00:00
Ok ( ( ) )
2018-09-15 16:49:04 +00:00
}
2016-02-28 00:30:13 +00:00
Yaml ::Real ( ref v ) = > {
2023-08-11 23:54:46 +00:00
write! ( self . writer , " {v} " ) ? ;
2015-05-31 04:56:45 +00:00
Ok ( ( ) )
2018-09-15 16:49:04 +00:00
}
2016-02-28 00:30:13 +00:00
Yaml ::Null | Yaml ::BadValue = > {
2018-09-16 06:58:48 +00:00
write! ( self . writer , " ~ " ) ? ;
2015-05-31 04:56:45 +00:00
Ok ( ( ) )
2018-09-15 16:49:04 +00:00
}
2015-05-31 04:56:45 +00:00
// XXX(chenyh) Alias
2023-08-11 23:54:46 +00:00
Yaml ::Alias ( _ ) = > Ok ( ( ) ) ,
2015-05-31 04:56:45 +00:00
}
}
2017-05-02 07:54:46 +00:00
2017-05-08 18:35:53 +00:00
fn emit_array ( & mut self , v : & [ Yaml ] ) -> EmitResult {
2017-05-02 07:54:46 +00:00
if v . is_empty ( ) {
2018-09-16 06:58:48 +00:00
write! ( self . writer , " [] " ) ? ;
2017-05-02 07:54:46 +00:00
} else {
2017-05-12 05:29:41 +00:00
self . level + = 1 ;
2017-05-02 07:54:46 +00:00
for ( cnt , x ) in v . iter ( ) . enumerate ( ) {
if cnt > 0 {
2018-09-16 07:00:48 +00:00
writeln! ( self . writer ) ? ;
2018-09-16 06:58:48 +00:00
self . write_indent ( ) ? ;
2017-05-02 07:54:46 +00:00
}
2018-09-16 06:58:48 +00:00
write! ( self . writer , " - " ) ? ;
self . emit_val ( true , x ) ? ;
2017-05-02 07:54:46 +00:00
}
2017-05-12 05:29:41 +00:00
self . level - = 1 ;
2017-05-02 07:54:46 +00:00
}
Ok ( ( ) )
}
fn emit_hash ( & mut self , h : & Hash ) -> EmitResult {
if h . is_empty ( ) {
2018-09-16 06:58:48 +00:00
self . writer . write_str ( " {} " ) ? ;
2017-05-02 07:54:46 +00:00
} else {
self . level + = 1 ;
for ( cnt , ( k , v ) ) in h . iter ( ) . enumerate ( ) {
2023-08-11 23:54:46 +00:00
let complex_key = matches! ( * k , Yaml ::Hash ( _ ) | Yaml ::Array ( _ ) ) ;
2017-05-02 07:54:46 +00:00
if cnt > 0 {
2018-09-16 07:00:48 +00:00
writeln! ( self . writer ) ? ;
2018-09-16 06:58:48 +00:00
self . write_indent ( ) ? ;
2017-05-02 07:54:46 +00:00
}
2017-05-11 18:36:38 +00:00
if complex_key {
2018-09-16 06:58:48 +00:00
write! ( self . writer , " ? " ) ? ;
self . emit_val ( true , k ) ? ;
2018-09-16 07:00:48 +00:00
writeln! ( self . writer ) ? ;
2018-09-16 06:58:48 +00:00
self . write_indent ( ) ? ;
write! ( self . writer , " : " ) ? ;
self . emit_val ( true , v ) ? ;
2017-05-11 18:36:38 +00:00
} else {
2018-09-16 06:58:48 +00:00
self . emit_node ( k ) ? ;
write! ( self . writer , " : " ) ? ;
self . emit_val ( false , v ) ? ;
2017-05-02 07:54:46 +00:00
}
}
self . level - = 1 ;
}
Ok ( ( ) )
}
2017-05-12 05:29:41 +00:00
/// 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.
2020-06-01 12:59:27 +00:00
/// If `inline` is true, then the preceding characters are distinct
2017-05-23 18:17:50 +00:00
/// and short enough to respect the compact flag.
2017-05-12 05:29:41 +00:00
fn emit_val ( & mut self , inline : bool , val : & Yaml ) -> EmitResult {
match * val {
Yaml ::Array ( ref v ) = > {
2017-05-23 18:17:50 +00:00
if ( inline & & self . compact ) | | v . is_empty ( ) {
2018-09-16 06:58:48 +00:00
write! ( self . writer , " " ) ? ;
2017-05-12 05:29:41 +00:00
} else {
2018-09-16 07:00:48 +00:00
writeln! ( self . writer ) ? ;
2017-05-12 05:29:41 +00:00
self . level + = 1 ;
2018-09-16 06:58:48 +00:00
self . write_indent ( ) ? ;
2017-05-12 05:29:41 +00:00
self . level - = 1 ;
}
self . emit_array ( v )
2018-09-15 16:49:04 +00:00
}
2017-05-12 05:29:41 +00:00
Yaml ::Hash ( ref h ) = > {
2017-05-23 18:17:50 +00:00
if ( inline & & self . compact ) | | h . is_empty ( ) {
2018-09-16 06:58:48 +00:00
write! ( self . writer , " " ) ? ;
2017-05-12 05:29:41 +00:00
} else {
2018-09-16 07:00:48 +00:00
writeln! ( self . writer ) ? ;
2017-05-12 05:29:41 +00:00
self . level + = 1 ;
2018-09-16 06:58:48 +00:00
self . write_indent ( ) ? ;
2017-05-12 05:29:41 +00:00
self . level - = 1 ;
}
self . emit_hash ( h )
2018-09-15 16:49:04 +00:00
}
2017-05-12 05:29:41 +00:00
_ = > {
2018-09-16 06:58:48 +00:00
write! ( self . writer , " " ) ? ;
2017-05-12 05:29:41 +00:00
self . emit_node ( val )
}
}
}
2015-05-31 04:56:45 +00:00
}
2016-11-02 02:03:35 +00:00
/// Check if the string requires quoting.
2018-04-20 06:06:55 +00:00
/// Strings starting with any of the following characters must be quoted.
/// :, &, *, ?, |, -, <, >, =, !, %, @
2016-11-02 02:03:35 +00:00
/// Strings containing any of the following characters must be quoted.
2018-04-20 06:06:55 +00:00
/// {, }, [, ], ,, #, `
2016-11-02 02:03:35 +00:00
///
/// 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).
2023-08-11 23:54:46 +00:00
#[ allow(clippy::doc_markdown) ]
2016-11-02 02:03:35 +00:00
fn need_quotes ( string : & str ) -> bool {
2016-11-14 03:17:14 +00:00
fn need_quotes_spaces ( string : & str ) -> bool {
2018-09-15 16:49:04 +00:00
string . starts_with ( ' ' ) | | string . ends_with ( ' ' )
2016-11-14 03:17:14 +00:00
}
2023-08-11 23:54:46 +00:00
string . is_empty ( )
2018-09-15 16:49:04 +00:00
| | need_quotes_spaces ( string )
2023-08-11 23:54:46 +00:00
| | string . starts_with ( | character : char | {
matches! (
character ,
'&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'
)
2018-09-15 16:49:04 +00:00
} )
2023-08-11 23:54:46 +00:00
| | string . contains ( | character : char | {
matches! ( character , ':'
2018-09-15 19:20:11 +00:00
| '{'
2018-09-15 16:49:04 +00:00
| '}'
| '['
| ']'
| ','
| '#'
| '`'
| '\"'
| '\''
| '\\'
2020-05-27 06:15:28 +00:00
| '\0' ..= '\x06'
2018-09-15 16:49:04 +00:00
| '\t'
| '\n'
| '\r'
2020-05-27 06:15:28 +00:00
| '\x0e' ..= '\x1a'
2023-08-11 23:54:46 +00:00
| '\x1c' ..= '\x1f' )
2018-09-15 16:49:04 +00:00
} )
| | [
// http://yaml.org/type/bool.html
// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse
2020-06-01 12:59:27 +00:00
// them as string, not booleans, although it is violating the YAML 1.1 specification.
2018-09-15 16:49:04 +00:00
// 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 " , " ~ " ,
]
2018-12-13 07:35:01 +00:00
. contains ( & string )
2018-09-15 16:49:04 +00:00
| | string . starts_with ( '.' )
2019-07-24 14:36:15 +00:00
| | string . starts_with ( " 0x " )
2018-09-15 16:49:04 +00:00
| | string . parse ::< i64 > ( ) . is_ok ( )
| | string . parse ::< f64 > ( ) . is_ok ( )
2016-11-02 02:03:35 +00:00
}
2024-03-17 07:47:11 +00:00
#[ 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 ( ) ;
}
}