Add documentation and move tests to their folder.
This commit is contained in:
parent
91ed5dca21
commit
2d8c4e8fc9
4 changed files with 597 additions and 480 deletions
|
@ -13,21 +13,14 @@
|
||||||
//! yaml-rust = "0.4"
|
//! 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
|
//! # Examples
|
||||||
|
//! Parse a string into `Vec<Yaml>` and then serialize it as a YAML string.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use yaml_rust::{YamlLoader, YamlEmitter};
|
//! use yaml_rust::{YamlLoader, YamlEmitter};
|
||||||
//!
|
//!
|
||||||
//! let docs = YamlLoader::load_from_str("[1, 2, 3]").unwrap();
|
//! 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
|
//! assert_eq!(doc[0].as_i64().unwrap(), 1); // access elements by index
|
||||||
//!
|
//!
|
||||||
//! let mut out_str = String::new();
|
//! let mut out_str = String::new();
|
||||||
|
@ -61,65 +54,3 @@ pub use crate::emitter::{EmitError, YamlEmitter};
|
||||||
pub use crate::parser::Event;
|
pub use crate::parser::Event;
|
||||||
pub use crate::scanner::ScanError;
|
pub use crate::scanner::ScanError;
|
||||||
pub use crate::yaml::{Yaml, YamlLoader};
|
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -29,57 +29,145 @@ enum State {
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Event` is used with the low-level event base parsing API,
|
/// An event generated by the YAML parser.
|
||||||
/// see `EventReceiver` trait.
|
///
|
||||||
|
/// Events are used in the low-level event-based API (push parser). The API entrypoint is the
|
||||||
|
/// [`EventReceiver`] trait.
|
||||||
#[derive(Clone, PartialEq, Debug, Eq)]
|
#[derive(Clone, PartialEq, Debug, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
/// Reserved for internal use
|
/// Reserved for internal use.
|
||||||
Nothing,
|
Nothing,
|
||||||
|
/// Event generated at the very beginning of parsing.
|
||||||
StreamStart,
|
StreamStart,
|
||||||
|
/// Last event that will be generated by the parser. Signals EOF.
|
||||||
StreamEnd,
|
StreamEnd,
|
||||||
|
/// The YAML start document directive (`---`).
|
||||||
DocumentStart,
|
DocumentStart,
|
||||||
|
/// The YAML end document directive (`...`).
|
||||||
DocumentEnd,
|
DocumentEnd,
|
||||||
/// Refer to an anchor ID
|
/// A YAML Alias.
|
||||||
Alias(usize),
|
Alias(
|
||||||
|
/// The anchor ID the alias refers to.
|
||||||
|
usize,
|
||||||
|
),
|
||||||
/// Value, style, anchor_id, tag
|
/// Value, style, anchor_id, tag
|
||||||
Scalar(String, TScalarStyle, usize, Option<TokenType>),
|
Scalar(String, TScalarStyle, usize, Option<TokenType>),
|
||||||
/// Anchor ID
|
SequenceStart(
|
||||||
SequenceStart(usize),
|
/// The anchor ID of the start of the squence.
|
||||||
|
usize,
|
||||||
|
),
|
||||||
SequenceEnd,
|
SequenceEnd,
|
||||||
/// Anchor ID
|
MappingStart(
|
||||||
MappingStart(usize),
|
/// The anchor ID of the start of the mapping.
|
||||||
|
usize,
|
||||||
|
),
|
||||||
MappingEnd,
|
MappingEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
|
/// Create an empty scalar.
|
||||||
fn empty_scalar() -> Event {
|
fn empty_scalar() -> Event {
|
||||||
// a null scalar
|
// a null scalar
|
||||||
Event::Scalar("~".to_owned(), TScalarStyle::Plain, 0, None)
|
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 {
|
fn empty_scalar_with_anchor(anchor: usize, tag: Option<TokenType>) -> Event {
|
||||||
Event::Scalar(String::new(), TScalarStyle::Plain, anchor, tag)
|
Event::Scalar(String::new(), TScalarStyle::Plain, anchor, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A YAML parser.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct Parser<T> {
|
pub struct Parser<T> {
|
||||||
scanner: Scanner<T>,
|
scanner: Scanner<T>,
|
||||||
states: Vec<State>,
|
states: Vec<State>,
|
||||||
state: State,
|
state: State,
|
||||||
marks: Vec<Marker>,
|
|
||||||
token: Option<Token>,
|
token: Option<Token>,
|
||||||
current: Option<(Event, Marker)>,
|
current: Option<(Event, Marker)>,
|
||||||
anchors: HashMap<String, usize>,
|
anchors: HashMap<String, usize>,
|
||||||
anchor_id: 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 {
|
pub trait EventReceiver {
|
||||||
|
/// Handler called for each YAML event that is emitted by the parser.
|
||||||
fn on_event(&mut self, ev: Event);
|
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 {
|
pub trait MarkedEventReceiver {
|
||||||
|
/// Handler called for each event that occurs.
|
||||||
fn on_event(&mut self, ev: Event, _mark: Marker);
|
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>;
|
pub type ParseResult = Result<(Event, Marker), ScanError>;
|
||||||
|
|
||||||
impl<T: Iterator<Item = char>> Parser<T> {
|
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> {
|
pub fn new(src: T) -> Parser<T> {
|
||||||
Parser {
|
Parser {
|
||||||
scanner: Scanner::new(src),
|
scanner: Scanner::new(src),
|
||||||
states: Vec::new(),
|
states: Vec::new(),
|
||||||
state: State::StreamStart,
|
state: State::StreamStart,
|
||||||
marks: Vec::new(),
|
|
||||||
token: None,
|
token: None,
|
||||||
current: 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> {
|
pub fn peek(&mut self) -> Result<&(Event, Marker), ScanError> {
|
||||||
if let Some(ref x) = self.current {
|
if let Some(ref x) = self.current {
|
||||||
Ok(x)
|
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 {
|
pub fn next(&mut self) -> ParseResult {
|
||||||
match self.current {
|
match self.current.take() {
|
||||||
None => self.parse(),
|
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))
|
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>(
|
pub fn load<R: MarkedEventReceiver>(
|
||||||
&mut self,
|
&mut self,
|
||||||
recv: &mut R,
|
recv: &mut R,
|
||||||
|
|
|
@ -4,12 +4,8 @@ use crate::parser::{Event, MarkedEventReceiver, Parser};
|
||||||
use crate::scanner::{Marker, ScanError, TScalarStyle, TokenType};
|
use crate::scanner::{Marker, ScanError, TScalarStyle, TokenType};
|
||||||
use linked_hash_map::LinkedHashMap;
|
use linked_hash_map::LinkedHashMap;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::f64;
|
|
||||||
use std::i64;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::Index;
|
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
|
/// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to
|
||||||
/// access your YAML document.
|
/// access your YAML document.
|
||||||
|
@ -30,20 +26,20 @@ use std::vec;
|
||||||
#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
|
#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
|
||||||
pub enum Yaml {
|
pub enum Yaml {
|
||||||
/// Float types are stored as String and parsed on demand.
|
/// 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.
|
/// Note that `f64` does NOT implement Eq trait and can NOT be stored in `BTreeMap`.
|
||||||
Real(string::String),
|
Real(String),
|
||||||
/// YAML int is stored as i64.
|
/// YAML int is stored as i64.
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
/// YAML scalar.
|
/// YAML scalar.
|
||||||
String(string::String),
|
String(String),
|
||||||
/// YAML bool, e.g. `true` or `false`.
|
/// YAML bool, e.g. `true` or `false`.
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
/// YAML array, can be accessed as a `Vec`.
|
/// YAML array, can be accessed as a `Vec`.
|
||||||
Array(self::Array),
|
Array(Array),
|
||||||
/// YAML hash, can be accessed as a `LinkedHashMap`.
|
/// YAML hash, can be accessed as a `LinkedHashMap`.
|
||||||
///
|
///
|
||||||
/// Insertion order will match the order of insertion into the map.
|
/// Insertion order will match the order of insertion into the map.
|
||||||
Hash(self::Hash),
|
Hash(Hash),
|
||||||
/// Alias, not fully supported yet.
|
/// Alias, not fully supported yet.
|
||||||
Alias(usize),
|
Alias(usize),
|
||||||
/// YAML null, e.g. `null` or `~`.
|
/// 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 {
|
pub struct YamlLoader {
|
||||||
docs: Vec<Yaml>,
|
docs: Vec<Yaml>,
|
||||||
// states
|
// 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> {
|
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 {
|
let mut loader = YamlLoader {
|
||||||
docs: Vec::new(),
|
docs: Vec::new(),
|
||||||
doc_stack: Vec::new(),
|
doc_stack: Vec::new(),
|
||||||
key_stack: Vec::new(),
|
key_stack: Vec::new(),
|
||||||
anchor_map: BTreeMap::new(),
|
anchor_map: BTreeMap::new(),
|
||||||
};
|
};
|
||||||
let mut parser = Parser::new(source.chars());
|
let mut parser = Parser::new(source);
|
||||||
parser.load(&mut loader, true)?;
|
parser.load(&mut loader, true)?;
|
||||||
Ok(loader.docs)
|
Ok(loader.docs)
|
||||||
}
|
}
|
||||||
|
@ -251,23 +264,28 @@ impl Yaml {
|
||||||
define_into!(into_hash, Hash, Hash);
|
define_into!(into_hash, Hash, Hash);
|
||||||
define_into!(into_vec, Array, Array);
|
define_into!(into_vec, Array, Array);
|
||||||
|
|
||||||
/// Returns the is null of this [`Yaml`].
|
/// Return whether `self` is a [`Yaml::Null`] node.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_null(&self) -> bool {
|
pub fn is_null(&self) -> bool {
|
||||||
matches!(*self, Yaml::Null)
|
matches!(*self, Yaml::Null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the is badvalue of this [`Yaml`].
|
/// Return whether `self` is a [`Yaml::BadValue`] node.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_badvalue(&self) -> bool {
|
pub fn is_badvalue(&self) -> bool {
|
||||||
matches!(*self, Yaml::BadValue)
|
matches!(*self, Yaml::BadValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return whether `self` is a [`Yaml::Array`] node.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_array(&self) -> bool {
|
pub fn is_array(&self) -> bool {
|
||||||
matches!(*self, Yaml::Array(_))
|
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]
|
#[must_use]
|
||||||
pub fn as_f64(&self) -> Option<f64> {
|
pub fn as_f64(&self) -> Option<f64> {
|
||||||
if let Yaml::Real(ref v) = self {
|
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]
|
#[must_use]
|
||||||
pub fn into_f64(self) -> Option<f64> {
|
pub fn into_f64(self) -> Option<f64> {
|
||||||
if let Yaml::Real(ref v) = self {
|
self.as_f64()
|
||||||
parse_f64(v)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::should_implement_trait))]
|
#[cfg_attr(feature = "cargo-clippy", allow(clippy::should_implement_trait))]
|
||||||
impl Yaml {
|
impl Yaml {
|
||||||
// Not implementing FromStr because there is no possibility of Error.
|
/// Convert a string to a [`Yaml`] node.
|
||||||
// This function falls back to Yaml::String if nothing else matches.
|
///
|
||||||
|
/// [`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]
|
#[must_use]
|
||||||
pub fn from_str(v: &str) -> Yaml {
|
pub fn from_str(v: &str) -> Yaml {
|
||||||
if let Some(number) = v.strip_prefix("0x") {
|
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 {
|
pub struct YamlIter {
|
||||||
yaml: vec::IntoIter<Yaml>,
|
yaml: std::vec::IntoIter<Yaml>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for YamlIter {
|
impl Iterator for YamlIter {
|
||||||
|
@ -373,378 +407,3 @@ impl Iterator for YamlIter {
|
||||||
self.yaml.next()
|
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
424
parser/tests/basic.rs
Normal 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());
|
||||||
|
}
|
Loading…
Reference in a new issue