2024-03-20 14:50:48 +00:00
|
|
|
//! YAML objects manipulation utilities.
|
|
|
|
|
2023-08-11 23:54:46 +00:00
|
|
|
#![allow(clippy::module_name_repetitions)]
|
|
|
|
|
2024-06-10 16:05:25 +00:00
|
|
|
use std::{convert::TryFrom, ops::Index, ops::IndexMut};
|
2024-02-08 06:12:14 +00:00
|
|
|
|
2020-05-26 10:35:06 +00:00
|
|
|
use hashlink::LinkedHashMap;
|
2024-06-13 20:37:56 +00:00
|
|
|
use saphyr_parser::{Parser, ScanError};
|
2024-02-08 06:12:14 +00:00
|
|
|
|
2024-06-13 20:37:56 +00:00
|
|
|
use crate::{loader::parse_f64, YamlLoader};
|
2015-05-24 06:27:42 +00:00
|
|
|
|
2016-03-10 09:55:21 +00:00
|
|
|
/// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to
|
2015-05-31 09:59:43 +00:00
|
|
|
/// access your YAML document.
|
2015-06-29 16:31:22 +00:00
|
|
|
///
|
2015-05-31 09:59:43 +00:00
|
|
|
/// # Examples
|
2015-06-29 16:31:22 +00:00
|
|
|
///
|
2015-05-31 09:59:43 +00:00
|
|
|
/// ```
|
2024-04-02 16:49:52 +00:00
|
|
|
/// use saphyr::Yaml;
|
2015-05-31 09:59:43 +00:00
|
|
|
/// let foo = Yaml::from_str("-123"); // convert the string to the appropriate YAML type
|
|
|
|
/// assert_eq!(foo.as_i64().unwrap(), -123);
|
2015-06-29 16:31:22 +00:00
|
|
|
///
|
2016-03-10 09:55:21 +00:00
|
|
|
/// // iterate over an Array
|
2015-05-31 09:59:43 +00:00
|
|
|
/// let vec = Yaml::Array(vec![Yaml::Integer(1), Yaml::Integer(2)]);
|
|
|
|
/// for v in vec.as_vec().unwrap() {
|
|
|
|
/// assert!(v.as_i64().is_some());
|
|
|
|
/// }
|
|
|
|
/// ```
|
2016-03-20 05:06:44 +00:00
|
|
|
#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
|
2015-05-24 06:27:42 +00:00
|
|
|
pub enum Yaml {
|
2016-03-10 09:55:21 +00:00
|
|
|
/// Float types are stored as String and parsed on demand.
|
2023-08-17 00:17:40 +00:00
|
|
|
/// Note that `f64` does NOT implement Eq trait and can NOT be stored in `BTreeMap`.
|
|
|
|
Real(String),
|
2016-03-10 09:55:21 +00:00
|
|
|
/// YAML int is stored as i64.
|
2015-05-30 14:39:50 +00:00
|
|
|
Integer(i64),
|
2016-03-10 09:55:21 +00:00
|
|
|
/// YAML scalar.
|
2023-08-17 00:17:40 +00:00
|
|
|
String(String),
|
2016-03-10 09:55:21 +00:00
|
|
|
/// YAML bool, e.g. `true` or `false`.
|
2015-05-24 06:27:42 +00:00
|
|
|
Boolean(bool),
|
2016-03-10 09:55:21 +00:00
|
|
|
/// YAML array, can be accessed as a `Vec`.
|
2023-08-17 00:17:40 +00:00
|
|
|
Array(Array),
|
2017-01-28 04:50:52 +00:00
|
|
|
/// YAML hash, can be accessed as a `LinkedHashMap`.
|
2016-03-20 05:06:44 +00:00
|
|
|
///
|
2019-02-20 07:23:31 +00:00
|
|
|
/// Insertion order will match the order of insertion into the map.
|
2023-08-17 00:17:40 +00:00
|
|
|
Hash(Hash),
|
2015-05-31 09:59:43 +00:00
|
|
|
/// Alias, not fully supported yet.
|
2015-05-28 17:56:03 +00:00
|
|
|
Alias(usize),
|
2016-03-10 09:55:21 +00:00
|
|
|
/// YAML null, e.g. `null` or `~`.
|
2015-05-24 06:27:42 +00:00
|
|
|
Null,
|
2016-03-10 09:55:21 +00:00
|
|
|
/// Accessing a nonexistent node via the Index trait returns `BadValue`. This
|
|
|
|
/// simplifies error handling in the calling code. Invalid type conversion also
|
|
|
|
/// returns `BadValue`.
|
2015-05-24 18:16:28 +00:00
|
|
|
BadValue,
|
2015-05-24 06:27:42 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 14:50:48 +00:00
|
|
|
/// The type contained in the `Yaml::Array` variant. This corresponds to YAML sequences.
|
2015-05-24 06:27:42 +00:00
|
|
|
pub type Array = Vec<Yaml>;
|
2024-03-20 14:50:48 +00:00
|
|
|
/// The type contained in the `Yaml::Hash` variant. This corresponds to YAML mappings.
|
2017-01-28 04:50:52 +00:00
|
|
|
pub type Hash = LinkedHashMap<Yaml, Yaml>;
|
2015-05-24 06:27:42 +00:00
|
|
|
|
2015-05-24 17:34:18 +00:00
|
|
|
impl Yaml {
|
2024-06-13 20:37:56 +00:00
|
|
|
/// Load the given string as an array 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.
|
|
|
|
///
|
|
|
|
/// Most often, only one document is loaded in a YAML string. In this case, only the first element
|
|
|
|
/// of the returned `Vec` will be used. Otherwise, each element in the `Vec` is a document:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use saphyr::Yaml;
|
|
|
|
///
|
|
|
|
/// let docs = Yaml::load_from_str(r#"
|
|
|
|
/// First document
|
|
|
|
/// ---
|
|
|
|
/// - Second document
|
|
|
|
/// "#).unwrap();
|
|
|
|
/// let first_document = &docs[0]; // Select the first YAML document
|
|
|
|
/// // The document is a string containing "First document".
|
|
|
|
/// assert_eq!(*first_document, Yaml::String("First document".to_owned()));
|
|
|
|
///
|
|
|
|
/// let second_document = &docs[1]; // Select the second YAML document
|
|
|
|
/// // The document is an array containing a single string, "Second document".
|
|
|
|
/// assert_eq!(second_document[0], Yaml::String("Second document".to_owned()));
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
/// Returns `ScanError` when loading fails.
|
|
|
|
pub fn load_from_str(source: &str) -> Result<Vec<Self>, ScanError> {
|
|
|
|
Self::load_from_iter(source.chars())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Load the contents of the given iterator as an array of YAML documents.
|
|
|
|
///
|
|
|
|
/// See [`Self::load_from_str`] for details.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
/// Returns `ScanError` when loading fails.
|
|
|
|
pub fn load_from_iter<I: Iterator<Item = char>>(source: I) -> Result<Vec<Yaml>, ScanError> {
|
|
|
|
let mut parser = Parser::new(source);
|
|
|
|
Self::load_from_parser(&mut parser)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Load the contents from the specified [`Parser`] as an array of YAML documents.
|
|
|
|
///
|
|
|
|
/// See [`Self::load_from_str`] for details.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
/// Returns `ScanError` when loading fails.
|
|
|
|
pub fn load_from_parser<I: Iterator<Item = char>>(
|
|
|
|
parser: &mut Parser<I>,
|
|
|
|
) -> Result<Vec<Yaml>, ScanError> {
|
|
|
|
let mut loader = YamlLoader::default();
|
|
|
|
parser.load(&mut loader, true)?;
|
|
|
|
Ok(loader.into_documents())
|
|
|
|
}
|
|
|
|
|
2015-05-24 17:34:18 +00:00
|
|
|
define_as!(as_bool, bool, Boolean);
|
2015-05-30 14:39:50 +00:00
|
|
|
define_as!(as_i64, i64, Integer);
|
2015-05-24 17:34:18 +00:00
|
|
|
|
|
|
|
define_as_ref!(as_hash, &Hash, Hash);
|
2024-06-10 20:39:13 +00:00
|
|
|
define_as_ref!(as_str, &str, String);
|
2015-05-24 17:34:18 +00:00
|
|
|
define_as_ref!(as_vec, &Array, Array);
|
|
|
|
|
2024-03-30 17:51:16 +00:00
|
|
|
define_as_mut_ref!(as_mut_hash, &mut Hash, Hash);
|
|
|
|
define_as_mut_ref!(as_mut_vec, &mut Array, Array);
|
|
|
|
|
2016-08-08 02:25:30 +00:00
|
|
|
define_into!(into_bool, bool, Boolean);
|
2024-06-10 20:39:13 +00:00
|
|
|
define_into!(into_hash, Hash, Hash);
|
2016-08-08 21:52:24 +00:00
|
|
|
define_into!(into_i64, i64, Integer);
|
2016-08-08 02:25:30 +00:00
|
|
|
define_into!(into_string, String, String);
|
|
|
|
define_into!(into_vec, Array, Array);
|
|
|
|
|
2024-06-10 20:39:13 +00:00
|
|
|
define_is!(is_alias, Self::Alias(_));
|
|
|
|
define_is!(is_array, Self::Array(_));
|
|
|
|
define_is!(is_badvalue, Self::BadValue);
|
|
|
|
define_is!(is_boolean, Self::Boolean(_));
|
2024-06-13 20:23:05 +00:00
|
|
|
define_is!(is_hash, Self::Hash(_));
|
2024-06-10 20:39:13 +00:00
|
|
|
define_is!(is_integer, Self::Integer(_));
|
|
|
|
define_is!(is_null, Self::Null);
|
|
|
|
define_is!(is_real, Self::Real(_));
|
|
|
|
define_is!(is_string, Self::String(_));
|
2017-06-10 21:39:07 +00:00
|
|
|
|
2023-08-17 00:17:40 +00:00
|
|
|
/// 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.
|
2023-08-11 23:54:46 +00:00
|
|
|
#[must_use]
|
2015-05-30 14:39:50 +00:00
|
|
|
pub fn as_f64(&self) -> Option<f64> {
|
2023-08-11 23:54:46 +00:00
|
|
|
if let Yaml::Real(ref v) = self {
|
|
|
|
parse_f64(v)
|
|
|
|
} else {
|
|
|
|
None
|
2015-05-24 17:34:18 +00:00
|
|
|
}
|
|
|
|
}
|
2016-08-08 02:25:30 +00:00
|
|
|
|
2023-08-17 00:17:40 +00:00
|
|
|
/// 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.
|
2023-08-11 23:54:46 +00:00
|
|
|
#[must_use]
|
2016-08-08 02:25:30 +00:00
|
|
|
pub fn into_f64(self) -> Option<f64> {
|
2023-08-17 00:17:40 +00:00
|
|
|
self.as_f64()
|
2016-08-08 02:25:30 +00:00
|
|
|
}
|
2021-09-29 05:06:37 +00:00
|
|
|
|
|
|
|
/// If a value is null or otherwise bad (see variants), consume it and
|
|
|
|
/// replace it with a given value `other`. Otherwise, return self unchanged.
|
|
|
|
///
|
|
|
|
/// ```
|
2024-04-02 16:49:52 +00:00
|
|
|
/// use saphyr::Yaml;
|
2021-09-29 05:06:37 +00:00
|
|
|
///
|
|
|
|
/// assert_eq!(Yaml::BadValue.or(Yaml::Integer(3)), Yaml::Integer(3));
|
|
|
|
/// assert_eq!(Yaml::Integer(3).or(Yaml::BadValue), Yaml::Integer(3));
|
|
|
|
/// ```
|
|
|
|
#[must_use]
|
|
|
|
pub fn or(self, other: Self) -> Self {
|
|
|
|
match self {
|
|
|
|
Yaml::BadValue | Yaml::Null => other,
|
|
|
|
this => this,
|
2021-10-02 01:11:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-10 20:39:13 +00:00
|
|
|
/// See [`Self::or`] for behavior.
|
|
|
|
///
|
|
|
|
/// This performs the same operations, but with borrowed values for less linear pipelines.
|
2021-10-02 01:11:09 +00:00
|
|
|
#[must_use]
|
|
|
|
pub fn borrowed_or<'a>(&'a self, other: &'a Self) -> &'a Self {
|
|
|
|
match self {
|
|
|
|
Yaml::BadValue | Yaml::Null => other,
|
|
|
|
this => this,
|
2021-09-29 05:06:37 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-28 00:30:13 +00:00
|
|
|
}
|
2015-05-24 18:16:28 +00:00
|
|
|
|
2024-06-10 16:05:25 +00:00
|
|
|
#[allow(clippy::should_implement_trait)]
|
2016-02-28 00:30:13 +00:00
|
|
|
impl Yaml {
|
2023-08-17 00:17:40 +00:00
|
|
|
/// 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
|
|
|
|
/// ```
|
2024-04-02 16:49:52 +00:00
|
|
|
/// # use saphyr::Yaml;
|
2023-08-17 00:17:40 +00:00
|
|
|
/// 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(_)));
|
|
|
|
/// ```
|
2023-08-11 23:54:46 +00:00
|
|
|
#[must_use]
|
2015-05-30 14:39:50 +00:00
|
|
|
pub fn from_str(v: &str) -> Yaml {
|
2023-08-11 23:54:46 +00:00
|
|
|
if let Some(number) = v.strip_prefix("0x") {
|
|
|
|
if let Ok(i) = i64::from_str_radix(number, 16) {
|
2020-06-01 12:34:13 +00:00
|
|
|
return Yaml::Integer(i);
|
2016-02-07 21:52:20 +00:00
|
|
|
}
|
2023-08-11 23:54:46 +00:00
|
|
|
} else if let Some(number) = v.strip_prefix("0o") {
|
|
|
|
if let Ok(i) = i64::from_str_radix(number, 8) {
|
2020-06-01 12:34:13 +00:00
|
|
|
return Yaml::Integer(i);
|
2016-02-07 21:52:20 +00:00
|
|
|
}
|
2023-08-11 23:54:46 +00:00
|
|
|
} else if let Some(number) = v.strip_prefix('+') {
|
|
|
|
if let Ok(i) = number.parse::<i64>() {
|
2020-06-01 12:34:13 +00:00
|
|
|
return Yaml::Integer(i);
|
|
|
|
}
|
2016-02-07 22:21:05 +00:00
|
|
|
}
|
2015-05-30 14:39:50 +00:00
|
|
|
match v {
|
|
|
|
"~" | "null" => Yaml::Null,
|
|
|
|
"true" => Yaml::Boolean(true),
|
|
|
|
"false" => Yaml::Boolean(false),
|
2023-08-11 23:54:46 +00:00
|
|
|
_ => {
|
|
|
|
if let Ok(integer) = v.parse::<i64>() {
|
|
|
|
Yaml::Integer(integer)
|
|
|
|
} else if parse_f64(v).is_some() {
|
|
|
|
Yaml::Real(v.to_owned())
|
|
|
|
} else {
|
|
|
|
Yaml::String(v.to_owned())
|
|
|
|
}
|
|
|
|
}
|
2015-05-30 14:39:50 +00:00
|
|
|
}
|
2015-05-24 18:16:28 +00:00
|
|
|
}
|
2015-05-24 17:34:18 +00:00
|
|
|
}
|
|
|
|
|
2015-05-24 18:16:28 +00:00
|
|
|
static BAD_VALUE: Yaml = Yaml::BadValue;
|
|
|
|
impl<'a> Index<&'a str> for Yaml {
|
|
|
|
type Output = Yaml;
|
|
|
|
|
|
|
|
fn index(&self, idx: &'a str) -> &Yaml {
|
2016-02-28 00:30:13 +00:00
|
|
|
let key = Yaml::String(idx.to_owned());
|
2015-05-24 18:16:28 +00:00
|
|
|
match self.as_hash() {
|
|
|
|
Some(h) => h.get(&key).unwrap_or(&BAD_VALUE),
|
2018-09-15 16:49:04 +00:00
|
|
|
None => &BAD_VALUE,
|
2015-05-24 18:16:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-30 17:51:16 +00:00
|
|
|
impl<'a> IndexMut<&'a str> for Yaml {
|
2024-06-10 20:39:13 +00:00
|
|
|
/// Perform indexing if `self` is a mapping.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
/// This function panics if the key given does not exist within `self` (as per [`Index`]).
|
|
|
|
///
|
|
|
|
/// This function also panics if `self` is not a [`Yaml::Hash`].
|
2024-03-30 17:51:16 +00:00
|
|
|
fn index_mut(&mut self, idx: &'a str) -> &mut Yaml {
|
|
|
|
let key = Yaml::String(idx.to_owned());
|
|
|
|
match self.as_mut_hash() {
|
|
|
|
Some(h) => h.get_mut(&key).unwrap(),
|
|
|
|
None => panic!("Not a hash type"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-24 18:16:28 +00:00
|
|
|
impl Index<usize> for Yaml {
|
|
|
|
type Output = Yaml;
|
|
|
|
|
|
|
|
fn index(&self, idx: usize) -> &Yaml {
|
2017-05-13 13:55:32 +00:00
|
|
|
if let Some(v) = self.as_vec() {
|
|
|
|
v.get(idx).unwrap_or(&BAD_VALUE)
|
|
|
|
} else if let Some(v) = self.as_hash() {
|
2024-02-08 06:12:14 +00:00
|
|
|
let key = Yaml::Integer(i64::try_from(idx).unwrap());
|
2017-05-13 13:55:32 +00:00
|
|
|
v.get(&key).unwrap_or(&BAD_VALUE)
|
|
|
|
} else {
|
|
|
|
&BAD_VALUE
|
2015-05-24 18:16:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-30 17:51:16 +00:00
|
|
|
impl IndexMut<usize> for Yaml {
|
|
|
|
/// Perform indexing if `self` is a sequence or a mapping.
|
|
|
|
///
|
|
|
|
/// # Panics
|
2024-06-10 20:39:13 +00:00
|
|
|
/// This function panics if the index given is out of range (as per [`IndexMut`]). If `self` is
|
2024-03-30 17:51:16 +00:00
|
|
|
/// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the
|
2024-06-10 20:39:13 +00:00
|
|
|
/// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not
|
2024-03-30 17:51:16 +00:00
|
|
|
/// contain [`Yaml::Integer`]`(idx)` as a key.
|
|
|
|
///
|
|
|
|
/// This function also panics if `self` is not a [`Yaml::Array`] nor a [`Yaml::Hash`].
|
|
|
|
fn index_mut(&mut self, idx: usize) -> &mut Yaml {
|
|
|
|
match self {
|
|
|
|
Yaml::Array(sequence) => sequence.index_mut(idx),
|
|
|
|
Yaml::Hash(mapping) => {
|
|
|
|
let key = Yaml::Integer(i64::try_from(idx).unwrap());
|
|
|
|
mapping.get_mut(&key).unwrap()
|
|
|
|
}
|
|
|
|
_ => panic!("Attempting to index but `self` is not a sequence nor a mapping"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-08 21:31:36 +00:00
|
|
|
impl IntoIterator for Yaml {
|
|
|
|
type Item = Yaml;
|
|
|
|
type IntoIter = YamlIter;
|
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
2016-09-22 08:54:51 +00:00
|
|
|
YamlIter {
|
2023-08-11 23:54:46 +00:00
|
|
|
yaml: self.into_vec().unwrap_or_default().into_iter(),
|
2016-09-22 08:54:51 +00:00
|
|
|
}
|
2016-08-08 21:31:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-17 00:17:40 +00:00
|
|
|
/// An iterator over a [`Yaml`] node.
|
2016-08-08 21:31:36 +00:00
|
|
|
pub struct YamlIter {
|
2023-08-17 00:17:40 +00:00
|
|
|
yaml: std::vec::IntoIter<Yaml>,
|
2016-08-08 21:31:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Iterator for YamlIter {
|
|
|
|
type Item = Yaml;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Yaml> {
|
2016-08-08 22:21:57 +00:00
|
|
|
self.yaml.next()
|
2016-08-08 21:31:36 +00:00
|
|
|
}
|
|
|
|
}
|