Add documentation and move tests to their folder.

This commit is contained in:
Ethiraric 2023-08-17 02:17:40 +02:00
parent 91ed5dca21
commit 2d8c4e8fc9
4 changed files with 597 additions and 480 deletions

View file

@ -13,21 +13,14 @@
//! yaml-rust = "0.4"
//! ```
//!
//! And this in your crate root:
//!
//! ```rust
//! extern crate yaml_rust;
//! ```
//!
//! Parse a string into `Vec<Yaml>` and then serialize it as a YAML string.
//!
//! # Examples
//! Parse a string into `Vec<Yaml>` and then serialize it as a YAML string.
//!
//! ```
//! use yaml_rust::{YamlLoader, YamlEmitter};
//!
//! let docs = YamlLoader::load_from_str("[1, 2, 3]").unwrap();
//! let doc = &docs[0]; // select the first document
//! let doc = &docs[0]; // select the first YAML document
//! assert_eq!(doc[0].as_i64().unwrap(), 1); // access elements by index
//!
//! let mut out_str = String::new();
@ -61,65 +54,3 @@ pub use crate::emitter::{EmitError, YamlEmitter};
pub use crate::parser::Event;
pub use crate::scanner::ScanError;
pub use crate::yaml::{Yaml, YamlLoader};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api() {
let s = "
# from yaml-cpp example
- name: Ogre
position: [0, 5, 0]
powers:
- name: Club
damage: 10
- name: Fist
damage: 8
- name: Dragon
position: [1, 0, 10]
powers:
- name: Fire Breath
damage: 25
- name: Claws
damage: 15
- name: Wizard
position: [5, -3, 0]
powers:
- name: Acid Rain
damage: 50
- name: Staff
damage: 3
";
let docs = YamlLoader::load_from_str(s).unwrap();
let doc = &docs[0];
assert_eq!(doc[0]["name"].as_str().unwrap(), "Ogre");
let mut writer = String::new();
{
let mut emitter = YamlEmitter::new(&mut writer);
emitter.dump(doc).unwrap();
}
assert!(!writer.is_empty());
}
fn try_fail(s: &str) -> Result<Vec<Yaml>, ScanError> {
let t = YamlLoader::load_from_str(s)?;
Ok(t)
}
#[test]
fn test_fail() {
let s = "
# syntax error
scalar
key: [1, 2]]
key1:a2
";
assert!(YamlLoader::load_from_str(s).is_err());
assert!(try_fail(s).is_err());
}
}

View file

@ -29,57 +29,145 @@ enum State {
End,
}
/// `Event` is used with the low-level event base parsing API,
/// see `EventReceiver` trait.
/// An event generated by the YAML parser.
///
/// Events are used in the low-level event-based API (push parser). The API entrypoint is the
/// [`EventReceiver`] trait.
#[derive(Clone, PartialEq, Debug, Eq)]
pub enum Event {
/// Reserved for internal use
/// Reserved for internal use.
Nothing,
/// Event generated at the very beginning of parsing.
StreamStart,
/// Last event that will be generated by the parser. Signals EOF.
StreamEnd,
/// The YAML start document directive (`---`).
DocumentStart,
/// The YAML end document directive (`...`).
DocumentEnd,
/// Refer to an anchor ID
Alias(usize),
/// A YAML Alias.
Alias(
/// The anchor ID the alias refers to.
usize,
),
/// Value, style, anchor_id, tag
Scalar(String, TScalarStyle, usize, Option<TokenType>),
/// Anchor ID
SequenceStart(usize),
SequenceStart(
/// The anchor ID of the start of the squence.
usize,
),
SequenceEnd,
/// Anchor ID
MappingStart(usize),
MappingStart(
/// The anchor ID of the start of the mapping.
usize,
),
MappingEnd,
}
impl Event {
/// Create an empty scalar.
fn empty_scalar() -> Event {
// a null scalar
Event::Scalar("~".to_owned(), TScalarStyle::Plain, 0, None)
}
/// Create an empty scalar with the given anchor.
fn empty_scalar_with_anchor(anchor: usize, tag: Option<TokenType>) -> Event {
Event::Scalar(String::new(), TScalarStyle::Plain, anchor, tag)
}
}
/// A YAML parser.
#[derive(Debug)]
#[allow(dead_code)]
pub struct Parser<T> {
scanner: Scanner<T>,
states: Vec<State>,
state: State,
marks: Vec<Marker>,
token: Option<Token>,
current: Option<(Event, Marker)>,
anchors: HashMap<String, usize>,
anchor_id: usize,
}
/// Trait to be implemented in order to use the low-level parsing API.
///
/// The low-level parsing API is event-based (a push parser), calling [`EventReceiver::on_event`]
/// for each YAML [`Event`] that occurs.
/// The [`EventReceiver`] trait only receives events. In order to receive both events and their
/// location in the source, use [`MarkedEventReceiver`]. Note that [`EventReceiver`]s implement
/// [`MarkedEventReceiver`] automatically.
///
/// # Event hierarchy
/// The event stream starts with an [`Event::StreamStart`] event followed by an
/// [`Event::DocumentStart`] event. If the YAML document starts with a mapping (an object), an
/// [`Event::MappingStart`] event is emitted. If it starts with a sequence (an array), an
/// [`Event::SequenceStart`] event is emitted. Otherwise, an [`Event::Scalar`] event is emitted.
///
/// In a mapping, key-values are sent as consecutive events. The first event after an
/// [`Event::MappingStart`] will be the key, and following its value. If the mapping contains no
/// sub-mapping or sub-sequence, then even events (starting from 0) will always be keys and odd
/// ones will always be values. The mapping ends when an [`Event::MappingEnd`] event is received.
///
/// In a sequence, values are sent consecutively until the [`Event::SequenceEnd`] event.
///
/// If a value is a sub-mapping or a sub-sequence, an [`Event::MappingStart`] or
/// [`Event::SequenceStart`] event will be sent respectively. Following events until the associated
/// [`Event::MappingStart`] or [`Event::SequenceEnd`] (beware of nested mappings or sequences) will
/// be part of the value and not another key-value pair or element in the sequence.
///
/// For instance, the following yaml:
/// ```yaml
/// a: b
/// c:
/// d: e
/// f:
/// - g
/// - h
/// ```
/// will emit (indented and commented for lisibility):
/// ```text
/// StreamStart, DocumentStart, MappingStart,
/// Scalar("a", ..), Scalar("b", ..)
/// Scalar("c", ..), MappingStart, Scalar("d", ..), Scalar("e", ..), MappingEnd,
/// Scalar("f", ..), SequenceStart, Scalar("g", ..), Scalar("h", ..), SequenceEnd,
/// MappingEnd, DocumentEnd, StreamEnd
/// ```
///
/// # Example
/// ```
/// # use yaml_rust::parser::{Event, EventReceiver, Parser};
/// #
/// /// Sink of events. Collects them into an array.
/// struct EventSink {
/// events: Vec<Event>,
/// }
///
/// /// Implement `on_event`, pushing into `self.events`.
/// impl EventReceiver for EventSink {
/// fn on_event(&mut self, ev: Event) {
/// self.events.push(ev);
/// }
/// }
///
/// /// Load events from a yaml string.
/// fn str_to_events(yaml: &str) -> Vec<Event> {
/// let mut sink = EventSink { events: Vec::new() };
/// let mut parser = Parser::new(yaml.chars());
/// // Load events using our sink as the receiver.
/// parser.load(&mut sink, true).unwrap();
/// sink.events
/// }
/// ```
pub trait EventReceiver {
/// Handler called for each YAML event that is emitted by the parser.
fn on_event(&mut self, ev: Event);
}
/// Trait to be implemented for using the low-level parsing API.
///
/// Functionally similar to [`EventReceiver`], but receives a [`Marker`] as well as the event.
pub trait MarkedEventReceiver {
/// Handler called for each event that occurs.
fn on_event(&mut self, ev: Event, _mark: Marker);
}
@ -92,12 +180,12 @@ impl<R: EventReceiver> MarkedEventReceiver for R {
pub type ParseResult = Result<(Event, Marker), ScanError>;
impl<T: Iterator<Item = char>> Parser<T> {
/// Crate a new instance of a parser from the given input of characters.
pub fn new(src: T) -> Parser<T> {
Parser {
scanner: Scanner::new(src),
states: Vec::new(),
state: State::StreamStart,
marks: Vec::new(),
token: None,
current: None,
@ -107,6 +195,10 @@ impl<T: Iterator<Item = char>> Parser<T> {
}
}
/// Try to load the next event and return it, but do not consuming it from `self`.
///
/// Any subsequent call to [`Parser::peek`] will return the same value, until a call to
/// [`Parser::next`] or [`Parser::load`].
pub fn peek(&mut self) -> Result<&(Event, Marker), ScanError> {
if let Some(ref x) = self.current {
Ok(x)
@ -116,10 +208,11 @@ impl<T: Iterator<Item = char>> Parser<T> {
}
}
/// Try to load the next event and return it, consuming it from `self`.
pub fn next(&mut self) -> ParseResult {
match self.current {
match self.current.take() {
None => self.parse(),
Some(_) => Ok(self.current.take().unwrap()),
Some(v) => Ok(v),
}
}
@ -170,6 +263,16 @@ impl<T: Iterator<Item = char>> Parser<T> {
Ok((ev, mark))
}
/// Load the YAML from the stream in `self`, pushing events into `recv`.
///
/// The contents of the stream are parsed and the corresponding events are sent into the
/// recveiver. For detailed explanations about how events work, see [`EventReceiver`].
///
/// If `multi` is set to `true`, the parser will allow parsing of multiple YAML documents
/// inside the stream.
///
/// Note that any [`EventReceiver`] is also a [`MarkedEventReceiver`], so implementing the
/// former is enough to call this function.
pub fn load<R: MarkedEventReceiver>(
&mut self,
recv: &mut R,

View file

@ -4,12 +4,8 @@ use crate::parser::{Event, MarkedEventReceiver, Parser};
use crate::scanner::{Marker, ScanError, TScalarStyle, TokenType};
use linked_hash_map::LinkedHashMap;
use std::collections::BTreeMap;
use std::f64;
use std::i64;
use std::mem;
use std::ops::Index;
use std::string;
use std::vec;
/// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to
/// access your YAML document.
@ -30,20 +26,20 @@ use std::vec;
#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
pub enum Yaml {
/// Float types are stored as String and parsed on demand.
/// Note that f64 does NOT implement Eq trait and can NOT be stored in BTreeMap.
Real(string::String),
/// Note that `f64` does NOT implement Eq trait and can NOT be stored in `BTreeMap`.
Real(String),
/// YAML int is stored as i64.
Integer(i64),
/// YAML scalar.
String(string::String),
String(String),
/// YAML bool, e.g. `true` or `false`.
Boolean(bool),
/// YAML array, can be accessed as a `Vec`.
Array(self::Array),
Array(Array),
/// YAML hash, can be accessed as a `LinkedHashMap`.
///
/// Insertion order will match the order of insertion into the map.
Hash(self::Hash),
Hash(Hash),
/// Alias, not fully supported yet.
Alias(usize),
/// YAML null, e.g. `null` or `~`.
@ -68,6 +64,9 @@ fn parse_f64(v: &str) -> Option<f64> {
}
}
/// Main structure for quickly parsing YAML.
///
/// See [`YamlLoader::load_from_str`].
pub struct YamlLoader {
docs: Vec<Yaml>,
// states
@ -188,14 +187,28 @@ impl YamlLoader {
}
}
/// Load the given string as a set of YAML documents.
///
/// The `source` is interpreted as YAML documents and is parsed. Parsing succeeds if and only
/// if all documents are parsed successfully. An error in a latter document prevents the former
/// from being returned.
pub fn load_from_str(source: &str) -> Result<Vec<Yaml>, ScanError> {
Self::load_from_iter(source.chars())
}
/// Load the contents of the given iterator as a set of YAML documents.
///
/// The `source` is interpreted as YAML documents and is parsed. Parsing succeeds if and only
/// if all documents are parsed successfully. An error in a latter document prevents the former
/// from being returned.
pub fn load_from_iter<I: Iterator<Item = char>>(source: I) -> Result<Vec<Yaml>, ScanError> {
let mut loader = YamlLoader {
docs: Vec::new(),
doc_stack: Vec::new(),
key_stack: Vec::new(),
anchor_map: BTreeMap::new(),
};
let mut parser = Parser::new(source.chars());
let mut parser = Parser::new(source);
parser.load(&mut loader, true)?;
Ok(loader.docs)
}
@ -251,23 +264,28 @@ impl Yaml {
define_into!(into_hash, Hash, Hash);
define_into!(into_vec, Array, Array);
/// Returns the is null of this [`Yaml`].
/// Return whether `self` is a [`Yaml::Null`] node.
#[must_use]
pub fn is_null(&self) -> bool {
matches!(*self, Yaml::Null)
}
/// Returns the is badvalue of this [`Yaml`].
/// Return whether `self` is a [`Yaml::BadValue`] node.
#[must_use]
pub fn is_badvalue(&self) -> bool {
matches!(*self, Yaml::BadValue)
}
/// Return whether `self` is a [`Yaml::Array`] node.
#[must_use]
pub fn is_array(&self) -> bool {
matches!(*self, Yaml::Array(_))
}
/// Return the `f64` value contained in this YAML node.
///
/// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string,
/// `None` is returned.
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
if let Yaml::Real(ref v) = self {
@ -277,20 +295,35 @@ impl Yaml {
}
}
/// Return the `f64` value contained in this YAML node.
///
/// If the node is not a [`Yaml::Real`] YAML node or its contents is not a valid `f64` string,
/// `None` is returned.
#[must_use]
pub fn into_f64(self) -> Option<f64> {
if let Yaml::Real(ref v) = self {
parse_f64(v)
} else {
None
}
self.as_f64()
}
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::should_implement_trait))]
impl Yaml {
// Not implementing FromStr because there is no possibility of Error.
// This function falls back to Yaml::String if nothing else matches.
/// Convert a string to a [`Yaml`] node.
///
/// [`Yaml`] does not implement [`std::str::FromStr`] since conversion may not fail. This
/// function falls back to [`Yaml::String`] if nothing else matches.
///
/// # Examples
/// ```
/// # use yaml_rust::yaml::Yaml;
/// assert!(matches!(Yaml::from_str("42"), Yaml::Integer(42)));
/// assert!(matches!(Yaml::from_str("0x2A"), Yaml::Integer(42)));
/// assert!(matches!(Yaml::from_str("0o52"), Yaml::Integer(42)));
/// assert!(matches!(Yaml::from_str("~"), Yaml::Null));
/// assert!(matches!(Yaml::from_str("null"), Yaml::Null));
/// assert!(matches!(Yaml::from_str("true"), Yaml::Boolean(true)));
/// assert!(matches!(Yaml::from_str("3.14"), Yaml::Real(_)));
/// assert!(matches!(Yaml::from_str("foo"), Yaml::String(_)));
/// ```
#[must_use]
pub fn from_str(v: &str) -> Yaml {
if let Some(number) = v.strip_prefix("0x") {
@ -362,8 +395,9 @@ impl IntoIterator for Yaml {
}
}
/// An iterator over a [`Yaml`] node.
pub struct YamlIter {
yaml: vec::IntoIter<Yaml>,
yaml: std::vec::IntoIter<Yaml>,
}
impl Iterator for YamlIter {
@ -373,378 +407,3 @@ impl Iterator for YamlIter {
self.yaml.next()
}
}
#[cfg(test)]
#[allow(clippy::bool_assert_comparison)]
#[allow(clippy::float_cmp)]
mod test {
use crate::yaml::{vec, Yaml, YamlLoader};
use std::f64;
#[test]
fn test_coerce() {
let s = "---
a: 1
b: 2.2
c: [1, 2]
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
assert_eq!(doc["b"].as_f64().unwrap(), 2.2f64);
assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
assert!(doc["d"][0].is_badvalue());
}
#[test]
fn test_empty_doc() {
let s: String = String::new();
YamlLoader::load_from_str(&s).unwrap();
let s: String = "---".to_owned();
assert_eq!(YamlLoader::load_from_str(&s).unwrap()[0], Yaml::Null);
}
#[test]
fn test_parser() {
let s: String = "
# comment
a0 bb: val
a1:
b1: 4
b2: d
a2: 4 # i'm comment
a3: [1, 2, 3]
a4:
- - a1
- a2
- 2
a5: 'single_quoted'
a6: \"double_quoted\"
a7:
"
.to_owned();
let out = YamlLoader::load_from_str(&s).unwrap();
let doc = &out[0];
assert_eq!(doc["a7"].as_str().unwrap(), "你好");
}
#[test]
fn test_multi_doc() {
let s = "
'a scalar'
---
'a scalar'
---
'a scalar'
";
let out = YamlLoader::load_from_str(s).unwrap();
assert_eq!(out.len(), 3);
}
#[test]
fn test_anchor() {
let s = "
a1: &DEFAULT
b1: 4
b2: d
a2: *DEFAULT
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc["a2"]["b1"].as_i64().unwrap(), 4);
}
#[test]
fn test_bad_anchor() {
let s = "
a1: &DEFAULT
b1: 4
b2: *DEFAULT
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc["a1"]["b2"], Yaml::BadValue);
}
#[test]
fn test_github_27() {
// https://github.com/chyh1990/yaml-rust/issues/27
let s = "&a";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc.as_str().unwrap(), "");
}
#[test]
fn test_plain_datatype() {
let s = "
- 'string'
- \"string\"
- string
- 123
- -321
- 1.23
- -1e4
- ~
- null
- true
- false
- !!str 0
- !!int 100
- !!float 2
- !!null ~
- !!bool true
- !!bool false
- 0xFF
# bad values
- !!int string
- !!float string
- !!bool null
- !!null val
- 0o77
- [ 0xF, 0xF ]
- +12345
- [ true, false ]
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc[0].as_str().unwrap(), "string");
assert_eq!(doc[1].as_str().unwrap(), "string");
assert_eq!(doc[2].as_str().unwrap(), "string");
assert_eq!(doc[3].as_i64().unwrap(), 123);
assert_eq!(doc[4].as_i64().unwrap(), -321);
assert_eq!(doc[5].as_f64().unwrap(), 1.23);
assert_eq!(doc[6].as_f64().unwrap(), -1e4);
assert!(doc[7].is_null());
assert!(doc[8].is_null());
assert_eq!(doc[9].as_bool().unwrap(), true);
assert_eq!(doc[10].as_bool().unwrap(), false);
assert_eq!(doc[11].as_str().unwrap(), "0");
assert_eq!(doc[12].as_i64().unwrap(), 100);
assert_eq!(doc[13].as_f64().unwrap(), 2.0);
assert!(doc[14].is_null());
assert_eq!(doc[15].as_bool().unwrap(), true);
assert_eq!(doc[16].as_bool().unwrap(), false);
assert_eq!(doc[17].as_i64().unwrap(), 255);
assert!(doc[18].is_badvalue());
assert!(doc[19].is_badvalue());
assert!(doc[20].is_badvalue());
assert!(doc[21].is_badvalue());
assert_eq!(doc[22].as_i64().unwrap(), 63);
assert_eq!(doc[23][0].as_i64().unwrap(), 15);
assert_eq!(doc[23][1].as_i64().unwrap(), 15);
assert_eq!(doc[24].as_i64().unwrap(), 12345);
assert!(doc[25][0].as_bool().unwrap());
assert!(!doc[25][1].as_bool().unwrap());
}
#[test]
fn test_bad_hyphen() {
// See: https://github.com/chyh1990/yaml-rust/issues/23
let s = "{-";
assert!(YamlLoader::load_from_str(s).is_err());
}
#[test]
fn test_issue_65() {
// See: https://github.com/chyh1990/yaml-rust/issues/65
let b = "\n\"ll\\\"ll\\\r\n\"ll\\\"ll\\\r\r\r\rU\r\r\rU";
assert!(YamlLoader::load_from_str(b).is_err());
}
#[test]
fn test_bad_docstart() {
assert!(YamlLoader::load_from_str("---This used to cause an infinite loop").is_ok());
assert_eq!(
YamlLoader::load_from_str("----"),
Ok(vec![Yaml::String(String::from("----"))])
);
assert_eq!(
YamlLoader::load_from_str("--- #here goes a comment"),
Ok(vec![Yaml::Null])
);
assert_eq!(
YamlLoader::load_from_str("---- #here goes a comment"),
Ok(vec![Yaml::String(String::from("----"))])
);
}
#[test]
fn test_plain_datatype_with_into_methods() {
let s = "
- 'string'
- \"string\"
- string
- 123
- -321
- 1.23
- -1e4
- true
- false
- !!str 0
- !!int 100
- !!float 2
- !!bool true
- !!bool false
- 0xFF
- 0o77
- +12345
- -.INF
- .NAN
- !!float .INF
";
let mut out = YamlLoader::load_from_str(s).unwrap().into_iter();
let mut doc = out.next().unwrap().into_iter();
assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 123);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), -321);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), 1.23);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), -1e4);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
assert_eq!(doc.next().unwrap().into_string().unwrap(), "0");
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 100);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), 2.0);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 255);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 63);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 12345);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::NEG_INFINITY);
assert!(doc.next().unwrap().into_f64().is_some());
assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::INFINITY);
}
#[test]
fn test_hash_order() {
let s = "---
b: ~
a: ~
c: ~
";
let out = YamlLoader::load_from_str(s).unwrap();
let first = out.into_iter().next().unwrap();
let mut iter = first.into_hash().unwrap().into_iter();
assert_eq!(
Some((Yaml::String("b".to_owned()), Yaml::Null)),
iter.next()
);
assert_eq!(
Some((Yaml::String("a".to_owned()), Yaml::Null)),
iter.next()
);
assert_eq!(
Some((Yaml::String("c".to_owned()), Yaml::Null)),
iter.next()
);
assert_eq!(None, iter.next());
}
#[test]
fn test_integer_key() {
let s = "
0:
important: true
1:
important: false
";
let out = YamlLoader::load_from_str(s).unwrap();
let first = out.into_iter().next().unwrap();
assert_eq!(first[0]["important"].as_bool().unwrap(), true);
}
#[test]
fn test_indentation_equality() {
let four_spaces = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
let two_spaces = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
let one_space = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
let mixed_spaces = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
assert_eq!(four_spaces, two_spaces);
assert_eq!(two_spaces, one_space);
assert_eq!(four_spaces, mixed_spaces);
}
#[test]
fn test_two_space_indentations() {
// https://github.com/kbknapp/clap-rs/issues/965
let s = r#"
subcommands:
- server:
about: server related commands
subcommands2:
- server:
about: server related commands
subcommands3:
- server:
about: server related commands
"#;
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out.into_iter().next().unwrap();
println!("{doc:#?}");
assert_eq!(doc["subcommands"][0]["server"], Yaml::Null);
assert!(doc["subcommands2"][0]["server"].as_hash().is_some());
assert!(doc["subcommands3"][0]["server"].as_hash().is_some());
}
#[test]
fn test_recursion_depth_check_objects() {
let s = "{a:".repeat(10_000) + &"}".repeat(10_000);
assert!(YamlLoader::load_from_str(&s).is_err());
}
#[test]
fn test_recursion_depth_check_arrays() {
let s = "[".repeat(10_000) + &"]".repeat(10_000);
assert!(YamlLoader::load_from_str(&s).is_err());
}
}

424
parser/tests/basic.rs Normal file
View file

@ -0,0 +1,424 @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::float_cmp)]
use std::vec;
use yaml_rust::{Yaml, YamlEmitter, YamlLoader};
#[test]
fn test_api() {
let s = "
# from yaml-cpp example
- name: Ogre
position: [0, 5, 0]
powers:
- name: Club
damage: 10
- name: Fist
damage: 8
- name: Dragon
position: [1, 0, 10]
powers:
- name: Fire Breath
damage: 25
- name: Claws
damage: 15
- name: Wizard
position: [5, -3, 0]
powers:
- name: Acid Rain
damage: 50
- name: Staff
damage: 3
";
let docs = YamlLoader::load_from_str(s).unwrap();
let doc = &docs[0];
assert_eq!(doc[0]["name"].as_str().unwrap(), "Ogre");
let mut writer = String::new();
{
let mut emitter = YamlEmitter::new(&mut writer);
emitter.dump(doc).unwrap();
}
assert!(!writer.is_empty());
}
#[test]
fn test_fail() {
let s = "
# syntax error
scalar
key: [1, 2]]
key1:a2
";
assert!(YamlLoader::load_from_str(s).is_err());
}
#[test]
fn test_coerce() {
let s = "---
a: 1
b: 2.2
c: [1, 2]
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
assert_eq!(doc["b"].as_f64().unwrap(), 2.2f64);
assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
assert!(doc["d"][0].is_badvalue());
}
#[test]
fn test_empty_doc() {
let s: String = String::new();
YamlLoader::load_from_str(&s).unwrap();
let s: String = "---".to_owned();
assert_eq!(YamlLoader::load_from_str(&s).unwrap()[0], Yaml::Null);
}
#[test]
fn test_parser() {
let s: String = "
# comment
a0 bb: val
a1:
b1: 4
b2: d
a2: 4 # i'm comment
a3: [1, 2, 3]
a4:
- - a1
- a2
- 2
a5: 'single_quoted'
a6: \"double_quoted\"
a7:
"
.to_owned();
let out = YamlLoader::load_from_str(&s).unwrap();
let doc = &out[0];
assert_eq!(doc["a7"].as_str().unwrap(), "你好");
}
#[test]
fn test_multi_doc() {
let s = "
'a scalar'
---
'a scalar'
---
'a scalar'
";
let out = YamlLoader::load_from_str(s).unwrap();
assert_eq!(out.len(), 3);
}
#[test]
fn test_anchor() {
let s = "
a1: &DEFAULT
b1: 4
b2: d
a2: *DEFAULT
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc["a2"]["b1"].as_i64().unwrap(), 4);
}
#[test]
fn test_bad_anchor() {
let s = "
a1: &DEFAULT
b1: 4
b2: *DEFAULT
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc["a1"]["b2"], Yaml::BadValue);
}
#[test]
fn test_github_27() {
// https://github.com/chyh1990/yaml-rust/issues/27
let s = "&a";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc.as_str().unwrap(), "");
}
#[test]
fn test_plain_datatype() {
let s = "
- 'string'
- \"string\"
- string
- 123
- -321
- 1.23
- -1e4
- ~
- null
- true
- false
- !!str 0
- !!int 100
- !!float 2
- !!null ~
- !!bool true
- !!bool false
- 0xFF
# bad values
- !!int string
- !!float string
- !!bool null
- !!null val
- 0o77
- [ 0xF, 0xF ]
- +12345
- [ true, false ]
";
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out[0];
assert_eq!(doc[0].as_str().unwrap(), "string");
assert_eq!(doc[1].as_str().unwrap(), "string");
assert_eq!(doc[2].as_str().unwrap(), "string");
assert_eq!(doc[3].as_i64().unwrap(), 123);
assert_eq!(doc[4].as_i64().unwrap(), -321);
assert_eq!(doc[5].as_f64().unwrap(), 1.23);
assert_eq!(doc[6].as_f64().unwrap(), -1e4);
assert!(doc[7].is_null());
assert!(doc[8].is_null());
assert_eq!(doc[9].as_bool().unwrap(), true);
assert_eq!(doc[10].as_bool().unwrap(), false);
assert_eq!(doc[11].as_str().unwrap(), "0");
assert_eq!(doc[12].as_i64().unwrap(), 100);
assert_eq!(doc[13].as_f64().unwrap(), 2.0);
assert!(doc[14].is_null());
assert_eq!(doc[15].as_bool().unwrap(), true);
assert_eq!(doc[16].as_bool().unwrap(), false);
assert_eq!(doc[17].as_i64().unwrap(), 255);
assert!(doc[18].is_badvalue());
assert!(doc[19].is_badvalue());
assert!(doc[20].is_badvalue());
assert!(doc[21].is_badvalue());
assert_eq!(doc[22].as_i64().unwrap(), 63);
assert_eq!(doc[23][0].as_i64().unwrap(), 15);
assert_eq!(doc[23][1].as_i64().unwrap(), 15);
assert_eq!(doc[24].as_i64().unwrap(), 12345);
assert!(doc[25][0].as_bool().unwrap());
assert!(!doc[25][1].as_bool().unwrap());
}
#[test]
fn test_bad_hyphen() {
// See: https://github.com/chyh1990/yaml-rust/issues/23
let s = "{-";
assert!(YamlLoader::load_from_str(s).is_err());
}
#[test]
fn test_issue_65() {
// See: https://github.com/chyh1990/yaml-rust/issues/65
let b = "\n\"ll\\\"ll\\\r\n\"ll\\\"ll\\\r\r\r\rU\r\r\rU";
assert!(YamlLoader::load_from_str(b).is_err());
}
#[test]
fn test_bad_docstart() {
assert!(YamlLoader::load_from_str("---This used to cause an infinite loop").is_ok());
assert_eq!(
YamlLoader::load_from_str("----"),
Ok(vec![Yaml::String(String::from("----"))])
);
assert_eq!(
YamlLoader::load_from_str("--- #here goes a comment"),
Ok(vec![Yaml::Null])
);
assert_eq!(
YamlLoader::load_from_str("---- #here goes a comment"),
Ok(vec![Yaml::String(String::from("----"))])
);
}
#[test]
fn test_plain_datatype_with_into_methods() {
let s = "
- 'string'
- \"string\"
- string
- 123
- -321
- 1.23
- -1e4
- true
- false
- !!str 0
- !!int 100
- !!float 2
- !!bool true
- !!bool false
- 0xFF
- 0o77
- +12345
- -.INF
- .NAN
- !!float .INF
";
let mut out = YamlLoader::load_from_str(s).unwrap().into_iter();
let mut doc = out.next().unwrap().into_iter();
assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 123);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), -321);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), 1.23);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), -1e4);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
assert_eq!(doc.next().unwrap().into_string().unwrap(), "0");
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 100);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), 2.0);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 255);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 63);
assert_eq!(doc.next().unwrap().into_i64().unwrap(), 12345);
assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::NEG_INFINITY);
assert!(doc.next().unwrap().into_f64().is_some());
assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::INFINITY);
}
#[test]
fn test_hash_order() {
let s = "---
b: ~
a: ~
c: ~
";
let out = YamlLoader::load_from_str(s).unwrap();
let first = out.into_iter().next().unwrap();
let mut iter = first.into_hash().unwrap().into_iter();
assert_eq!(
Some((Yaml::String("b".to_owned()), Yaml::Null)),
iter.next()
);
assert_eq!(
Some((Yaml::String("a".to_owned()), Yaml::Null)),
iter.next()
);
assert_eq!(
Some((Yaml::String("c".to_owned()), Yaml::Null)),
iter.next()
);
assert_eq!(None, iter.next());
}
#[test]
fn test_integer_key() {
let s = "
0:
important: true
1:
important: false
";
let out = YamlLoader::load_from_str(s).unwrap();
let first = out.into_iter().next().unwrap();
assert_eq!(first[0]["important"].as_bool().unwrap(), true);
}
#[test]
fn test_indentation_equality() {
let four_spaces = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
let two_spaces = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
let one_space = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
let mixed_spaces = YamlLoader::load_from_str(
r#"
hash:
with:
indentations
"#,
)
.unwrap()
.into_iter()
.next()
.unwrap();
assert_eq!(four_spaces, two_spaces);
assert_eq!(two_spaces, one_space);
assert_eq!(four_spaces, mixed_spaces);
}
#[test]
fn test_two_space_indentations() {
// https://github.com/kbknapp/clap-rs/issues/965
let s = r#"
subcommands:
- server:
about: server related commands
subcommands2:
- server:
about: server related commands
subcommands3:
- server:
about: server related commands
"#;
let out = YamlLoader::load_from_str(s).unwrap();
let doc = &out.into_iter().next().unwrap();
println!("{doc:#?}");
assert_eq!(doc["subcommands"][0]["server"], Yaml::Null);
assert!(doc["subcommands2"][0]["server"].as_hash().is_some());
assert!(doc["subcommands3"][0]["server"].as_hash().is_some());
}
#[test]
fn test_recursion_depth_check_objects() {
let s = "{a:".repeat(10_000) + &"}".repeat(10_000);
assert!(YamlLoader::load_from_str(&s).is_err());
}
#[test]
fn test_recursion_depth_check_arrays() {
let s = "[".repeat(10_000) + &"]".repeat(10_000);
assert!(YamlLoader::load_from_str(&s).is_err());
}