use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Yaml {
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
Seq(Vec<Spanned>),
Map(Vec<(Spanned<String>, Spanned)>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Spanned<T = Yaml> {
pub value: T,
pub line: usize,
}
impl<T> Spanned<T> {
pub fn new(value: T, line: usize) -> Self {
Spanned { value, line }
}
}
impl Spanned {
pub fn as_str(&self) -> Option<&str> {
match &self.value {
Yaml::Str(s) => Some(s),
_ => None,
}
}
pub fn as_map(&self) -> Option<&[(Spanned<String>, Spanned)]> {
match &self.value {
Yaml::Map(m) => Some(m),
_ => None,
}
}
pub fn as_seq(&self) -> Option<&[Spanned]> {
match &self.value {
Yaml::Seq(s) => Some(s),
_ => None,
}
}
pub fn as_int(&self) -> Option<i64> {
match &self.value {
Yaml::Int(i) => Some(*i),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match &self.value {
Yaml::Bool(b) => Some(*b),
_ => None,
}
}
pub fn get(&self, key: &str) -> Option<&Spanned> {
self.entry(key).map(|(_, v)| v)
}
pub fn key_line(&self, key: &str) -> Option<usize> {
self.entry(key).map(|(k, _)| k.line)
}
fn entry(&self, key: &str) -> Option<&(Spanned<String>, Spanned)> {
self.as_map()?.iter().find(|(k, _)| k.value == key)
}
pub fn as_str_or_list(&self) -> Option<Vec<String>> {
match &self.value {
Yaml::Str(s) => Some(vec![s.clone()]),
Yaml::Seq(items) => items
.iter()
.map(|i| i.as_str().map(str::to_string))
.collect(),
_ => None,
}
}
pub fn type_name(&self) -> &'static str {
match &self.value {
Yaml::Null => "null",
Yaml::Bool(_) => "boolean",
Yaml::Int(_) => "integer",
Yaml::Float(_) => "float",
Yaml::Str(_) => "string",
Yaml::Seq(_) => "list",
Yaml::Map(_) => "map",
}
}
pub fn scalar_text(&self) -> Option<String> {
match &self.value {
Yaml::Null => Some("null".into()),
Yaml::Bool(b) => Some(b.to_string()),
Yaml::Int(i) => Some(i.to_string()),
Yaml::Float(f) => Some(f.to_string()),
Yaml::Str(s) => Some(s.clone()),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum YamlError {
#[error("YAML does not parse at line {line}: {message}")]
Syntax {
line: usize,
message: String,
},
#[error("YAML anchor at line {line}: anchors are not read")]
Anchor {
line: usize,
},
#[error("YAML alias at line {line}: aliases are not read")]
Alias {
line: usize,
},
#[error("YAML tag at line {line}: tags are not read")]
Tag {
line: usize,
},
#[error("YAML mapping key at line {line} is not a string")]
NonStringKey {
line: usize,
},
}
impl YamlError {
pub fn is_refused(&self) -> bool {
matches!(
self,
YamlError::Anchor { .. } | YamlError::Alias { .. } | YamlError::Tag { .. }
)
}
pub fn line(&self) -> usize {
match self {
YamlError::Syntax { line, .. }
| YamlError::Anchor { line }
| YamlError::Alias { line }
| YamlError::Tag { line }
| YamlError::NonStringKey { line } => *line,
}
}
}