Skip to main content

rd_ast/
path.rs

1use std::fmt;
2
3/// A human-oriented structural path through a producer's Rd object tree.
4///
5/// The [`fmt::Display`] representation is intended for diagnostics, not as a
6/// machine-readable protocol.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct RdPath {
9    segments: Vec<RdPathSegment>,
10}
11
12impl RdPath {
13    pub fn new(segments: Vec<RdPathSegment>) -> Self {
14        Self { segments }
15    }
16
17    pub fn segments(&self) -> &[RdPathSegment] {
18        &self.segments
19    }
20
21    pub fn with_child(&self, index: usize) -> RdPath {
22        let mut path = self.clone();
23        path.segments.push(RdPathSegment::Child(index));
24        path
25    }
26
27    pub fn with_option(&self) -> RdPath {
28        let mut path = self.clone();
29        path.segments.push(RdPathSegment::Option);
30        path
31    }
32
33    pub(crate) fn with_character(&self, index: usize) -> RdPath {
34        let mut path = self.clone();
35        path.segments.push(RdPathSegment::CharacterElement(index));
36        path
37    }
38}
39
40impl From<Vec<RdPathSegment>> for RdPath {
41    fn from(segments: Vec<RdPathSegment>) -> Self {
42        Self::new(segments)
43    }
44}
45
46impl fmt::Display for RdPath {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        for (index, segment) in self.segments.iter().enumerate() {
49            if index != 0 {
50                f.write_str(" / ")?;
51            }
52            segment.fmt(f)?;
53        }
54        Ok(())
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59#[non_exhaustive]
60pub enum RdPathSegment {
61    TopLevel(usize),
62    Child(usize),
63    Option,
64    Attribute(String),
65    AttributeValue,
66    ListElement(usize),
67    CharacterElement(usize),
68}
69
70impl fmt::Display for RdPathSegment {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            Self::TopLevel(index) => write!(f, "top-level[{index}]"),
74            Self::Child(index) => write!(f, "child[{index}]"),
75            Self::Option => f.write_str("@option"),
76            Self::Attribute(name) => write!(f, "@attr({name})"),
77            Self::AttributeValue => f.write_str("value"),
78            Self::ListElement(index) => write!(f, "list[{index}]"),
79            Self::CharacterElement(index) => write!(f, "character[{index}]"),
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn displays_structural_paths() {
90        let path = RdPath::new(vec![
91            RdPathSegment::TopLevel(8),
92            RdPathSegment::Child(3),
93            RdPathSegment::Option,
94            RdPathSegment::Attribute("srcref".to_string()),
95            RdPathSegment::AttributeValue,
96            RdPathSegment::ListElement(2),
97            RdPathSegment::CharacterElement(0),
98        ]);
99        assert_eq!(
100            path.to_string(),
101            "top-level[8] / child[3] / @option / @attr(srcref) / value / list[2] / character[0]"
102        );
103    }
104}