saphyr-serde/saphyr/src/yaml.rs

277 lines
8.8 KiB
Rust
Raw Normal View History

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-02-08 06:12:14 +00:00
2024-06-10 16:05:25 +00:00
use crate::loader::parse_f64;
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());
/// }
/// ```
#[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.
/// 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.
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`.
Array(Array),
2017-01-28 04:50:52 +00:00
/// YAML hash, can be accessed as a `LinkedHashMap`.
///
/// Insertion order will match the order of insertion into the map.
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 {
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);
define_as_ref!(as_str, &str, String);
2015-05-24 17:34:18 +00:00
define_as_ref!(as_vec, &Array, Array);
define_as_mut_ref!(as_mut_hash, &mut Hash, Hash);
define_as_mut_ref!(as_mut_vec, &mut Array, Array);
define_into!(into_bool, bool, Boolean);
define_into!(into_hash, Hash, Hash);
2016-08-08 21:52:24 +00:00
define_into!(into_i64, i64, Integer);
define_into!(into_string, String, String);
define_into!(into_vec, Array, Array);
define_is!(is_alias, Self::Alias(_));
define_is!(is_array, Self::Array(_));
define_is!(is_badvalue, Self::BadValue);
define_is!(is_boolean, Self::Boolean(_));
define_is!(is_hash, Self::Hash(_));
define_is!(is_integer, Self::Integer(_));
define_is!(is_null, Self::Null);
define_is!(is_real, Self::Real(_));
define_is!(is_string, Self::String(_));
/// 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
}
}
/// 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]
pub fn into_f64(self) -> Option<f64> {
self.as_f64()
}
/// 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;
///
/// 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,
}
}
/// See [`Self::or`] for behavior.
///
/// This performs the same operations, but with borrowed values for less linear pipelines.
#[must_use]
pub fn borrowed_or<'a>(&'a self, other: &'a Self) -> &'a Self {
match self {
Yaml::BadValue | Yaml::Null => other,
this => this,
}
}
}
2015-05-24 18:16:28 +00:00
2024-06-10 16:05:25 +00:00
#[allow(clippy::should_implement_trait)]
impl Yaml {
/// 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;
/// 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 {
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
}
}
}
impl<'a> IndexMut<&'a str> for Yaml {
/// 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`].
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 {
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());
v.get(&key).unwrap_or(&BAD_VALUE)
} else {
&BAD_VALUE
2015-05-24 18:16:28 +00:00
}
}
}
impl IndexMut<usize> for Yaml {
/// Perform indexing if `self` is a sequence or a mapping.
///
/// # Panics
/// This function panics if the index given is out of range (as per [`IndexMut`]). If `self` is
/// a [`Yaml::Array`], this is when the index is bigger or equal to the length of the
/// underlying `Vec`. If `self` is a [`Yaml::Hash`], this is when the mapping sequence does not
/// 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
}
}
/// An iterator over a [`Yaml`] node.
2016-08-08 21:31:36 +00:00
pub struct YamlIter {
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
}
}