use crate::util;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PathElement {
Field(String),
Repeated(String, usize),
Variant(String, String),
Index(usize),
}
impl std::fmt::Display for PathElement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !f.alternate() {
match self {
PathElement::Index(_) => {}
_ => write!(f, ".")?,
}
}
match self {
PathElement::Field(field) => write!(f, "{}", util::string::as_ident_or_string(field)),
PathElement::Repeated(field, index) => {
write!(f, "{}[{index}]", util::string::as_ident_or_string(field))
}
PathElement::Variant(field, variant) => write!(
f,
"{}<{}>",
util::string::as_ident_or_string(field),
util::string::as_ident_or_string(variant)
),
PathElement::Index(index) => write!(f, "[{index}]"),
}
}
}
impl PathElement {
pub fn to_string_without_dot(&self) -> String {
format!("{:#}", self)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PathBuf {
pub root: &'static str,
pub elements: Vec<PathElement>,
}
impl std::fmt::Display for PathBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.root)?;
for element in self.elements.iter() {
write!(f, "{element}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Path<'a> {
Root(&'static str),
Select(&'a Path<'a>, PathElement),
}
impl Default for Path<'_> {
fn default() -> Self {
Path::Root("")
}
}
impl Path<'_> {
pub fn with(&self, element: PathElement) -> Path {
Path::Select(self, element)
}
pub fn with_field<S: Into<String>>(&self, name: S) -> Path {
self.with(PathElement::Field(name.into()))
}
pub fn with_repeated<S: Into<String>>(&self, name: S, index: usize) -> Path {
self.with(PathElement::Repeated(name.into(), index))
}
pub fn with_variant<S: Into<String>, V: Into<String>>(&self, name: S, variant: V) -> Path {
self.with(PathElement::Variant(name.into(), variant.into()))
}
pub fn with_index(&self, index: usize) -> Path {
self.with(PathElement::Index(index))
}
}
impl std::fmt::Display for Path<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Path::Root(name) => write!(f, "{name}"),
Path::Select(parent, element) => write!(f, "{parent}{element}"),
}
}
}
impl Path<'_> {
pub fn end_to_string(&self) -> String {
match self {
Path::Root(name) => name.to_string(),
Path::Select(_, element) => element.to_string(),
}
}
pub fn to_path_buf(&self) -> PathBuf {
match self {
Path::Root(name) => PathBuf {
root: name,
elements: vec![],
},
Path::Select(parent, element) => {
let mut parent = parent.to_path_buf();
parent.elements.push(element.clone());
parent
}
}
}
}
impl From<Path<'_>> for PathBuf {
fn from(path: Path<'_>) -> Self {
path.to_path_buf()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paths() {
let a = Path::Root("a");
let b = a.with_field("b");
let c = b.with_repeated("c", 42);
let d = c.with_variant("d", "e");
let e = d.with_index(33);
let buf: PathBuf = e.to_path_buf();
assert_eq!(e.to_string(), "a.b.c[42].d<e>[33]");
assert_eq!(buf.to_string(), "a.b.c[42].d<e>[33]");
}
#[test]
fn non_ident_paths() {
let a = Path::Root("a");
let b = a.with_field("4");
let c = b.with_repeated("8", 15);
let d = c.with_variant("16", "23");
let e = d.with_index(42);
let buf: PathBuf = e.to_path_buf();
assert_eq!(e.to_string(), "a.\"4\".\"8\"[15].\"16\"<\"23\">[42]");
assert_eq!(buf.to_string(), "a.\"4\".\"8\"[15].\"16\"<\"23\">[42]");
}
}