diskplan_schema/
expression.rs

1use std::{fmt::Display, vec};
2
3/// A string expression made from one or more [`Token`]s
4#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
5pub struct Expression<'t>(Vec<Token<'t>>);
6
7impl<'t> Expression<'t> {
8    /// Provides access to the slice of tokens that make up this expression
9    pub fn tokens(&self) -> &[Token<'t>] {
10        &self.0[..]
11    }
12}
13
14impl<'t> From<Vec<Token<'t>>> for Expression<'t> {
15    fn from(tokens: Vec<Token<'t>>) -> Self {
16        Expression(tokens)
17    }
18}
19
20impl<'t> From<&[Token<'t>]> for Expression<'t> {
21    fn from(tokens: &[Token<'t>]) -> Self {
22        Expression(tokens.into())
23    }
24}
25
26impl Display for Expression<'_> {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        for token in self.0.iter() {
29            write!(f, "{token}")?;
30        }
31        Ok(())
32    }
33}
34
35impl PartialEq<&str> for Expression<'_> {
36    fn eq(&self, other: &&str) -> bool {
37        // Expression is equal to a string only if it is a single text token
38        // with the same inner value
39        match &self.0[..] {
40            [Token::Text(text)] => *text == *other,
41            _ => false,
42        }
43    }
44}
45
46/// Part of an [`Expression`]; a constant string, or a variable for later expansion to a string
47#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
48pub enum Token<'t> {
49    /// A constant string of plain text
50    Text(&'t str),
51    /// The name of a variable
52    Variable(Identifier<'t>),
53    /// A special variable whose value is provided by the current scope
54    Special(Special),
55}
56
57impl Display for Token<'_> {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            Token::Text(s) => f.write_str(s),
61            Token::Variable(v) => write!(f, "${{{v}}}"),
62            Token::Special(sp) => write!(f, "${{{sp}}}"),
63        }
64    }
65}
66
67/// A choice of built-in variables that are used to provide context information during traversal
68#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
69pub enum Special {
70    /// The current path relative to the active root
71    PathRelative,
72    /// The current absolute path
73    PathAbsolute,
74    /// The final component of the current path
75    PathNameOnly,
76    /// The current relative path without the final component
77    ParentRelative,
78    /// The current absolute path without the final component
79    ParentAbsolute,
80    /// The penultimate component of the current path
81    ParentNameOnly,
82    /// The absolute path of the active root
83    RootPath,
84}
85
86impl Special {
87    /// The current path relative to the active root
88    pub const SAME_PATH_RELATIVE: &'static str = "PATH";
89    /// The current absolute path
90    pub const SAME_PATH_ABSOLUTE: &'static str = "FULL_PATH";
91    /// The final component of the current path
92    pub const SAME_PATH_NAME: &'static str = "NAME";
93    /// The current relative path without the final component
94    pub const PARENT_PATH_RELATIVE: &'static str = "PARENT_PATH";
95    /// The current absolute path without the final component
96    pub const PARENT_PATH_ABSOLUTE: &'static str = "PARENT_FULL_PATH";
97    /// The penultimate component of the current path
98    pub const PARENT_PATH_NAME: &'static str = "PARENT_NAME";
99    /// The absolute path of the active root
100    pub const ROOT_PATH: &'static str = "ROOT_PATH";
101}
102
103impl Display for Special {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        f.write_str(match self {
106            Special::PathRelative => Special::SAME_PATH_RELATIVE,
107            Special::PathAbsolute => Special::SAME_PATH_ABSOLUTE,
108            Special::PathNameOnly => Special::SAME_PATH_NAME,
109            Special::ParentRelative => Special::PARENT_PATH_RELATIVE,
110            Special::ParentAbsolute => Special::PARENT_PATH_ABSOLUTE,
111            Special::ParentNameOnly => Special::PARENT_PATH_NAME,
112            Special::RootPath => Special::ROOT_PATH,
113        })
114    }
115}
116
117/// The name given to a variable
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
119pub struct Identifier<'t>(&'t str);
120
121impl<'t> Identifier<'t> {
122    /// Creates a new Identifier from the given string
123    pub fn new(s: &'t str) -> Self {
124        Identifier(s)
125    }
126
127    /// Returns a reference to the underlying string
128    pub fn value(&self) -> &'t str {
129        self.0
130    }
131}
132
133impl Display for Identifier<'_> {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        write!(f, "{}", self.0)
136    }
137}
138
139impl<'a> From<&'a str> for Identifier<'a> {
140    fn from(s: &'a str) -> Self {
141        Identifier::new(s)
142    }
143}
144
145impl<'a> From<Identifier<'a>> for Expression<'a> {
146    fn from(identifier: Identifier<'a>) -> Self {
147        Expression(vec![Token::Variable(identifier)])
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    fn test_expression() -> Expression<'static> {
156        Expression(vec![
157            Token::Text("normal text/"),
158            Token::Variable(Identifier("a_variable")),
159            Token::Text("/"),
160            Token::Special(Special::ParentRelative),
161            Token::Text("_AAA"),
162        ])
163    }
164
165    #[test]
166    fn format_identifier() {
167        assert_eq!(&format!("{}", Identifier("something")), "something");
168    }
169
170    #[test]
171    fn format_variable() {
172        let something = Identifier("something");
173        assert_eq!(&format!("{}", Token::Variable(something)), "${something}");
174    }
175
176    #[test]
177    fn format_expression_all_types() {
178        let expr = test_expression();
179        assert_eq!(
180            &format!("{expr}"),
181            "normal text/${a_variable}/${PARENT_PATH}_AAA"
182        );
183    }
184
185    #[test]
186    fn formatted_expression_is_valid_schema_expression() {
187        let expr = test_expression();
188        let schema_text = format!("symlink/ -> {expr}");
189        let schema_node = crate::parse_schema(&schema_text).unwrap();
190        let directory_schema = schema_node.schema.as_directory().unwrap();
191        let (_, symlink_node) = directory_schema.entries().first().unwrap();
192        let symlink_expression = symlink_node.symlink.as_ref().unwrap();
193
194        assert_eq!(*symlink_expression, expr);
195    }
196}