diskplan_traversal/
stack.rs

1use std::{
2    collections::HashMap,
3    fmt::{Debug, Display},
4};
5
6use crate::eval::Value;
7use diskplan_config::Config;
8use diskplan_filesystem::Mode;
9use diskplan_schema::{DirectorySchema, Identifier, SchemaNode};
10
11/// Keeps track of variables and provides access to definitions from parent
12/// nodes
13///
14/// Example:
15/// ```
16/// use diskplan_schema::DirectorySchema;
17/// use diskplan_traversal::{StackFrame, VariableSource};
18///
19/// // The stack lifetimes allow us to have a function that takes a stack...
20/// fn __<'g>(stack: &StackFrame<'g, '_, '_>, d: &'g DirectorySchema) {
21///     // ...provides access to items referenced by parent scopes...
22///     let var = stack.lookup(&"variable".into()).unwrap();
23///
24///     // ...can be extended with a mutable local scope...
25///     let mut local = stack.push(VariableSource::Directory(d));
26///
27///     // ...and capture local modifications...
28///     let owner = "root";
29///     local.put_owner(owner);
30/// }
31/// ```
32pub struct StackFrame<'g, 'p, 'l>
33where
34    'g: 'p, // The shared values pointed to live longer than the whole stack
35    'p: 'l, // The local variables live within this frame, so can be shorter lived
36{
37    parent: Option<&'p StackFrame<'g, 'p, 'p>>,
38
39    /// A reference to the shared config
40    pub config: &'g Config<'g>,
41
42    /// Collection of variables and values at this level of the stack
43    variables: VariableSource<'g>,
44
45    /// The owner (after mapping) of this level, inherited by children
46    owner: &'l str,
47    /// The group (after mapping) of this level, inherited by children
48    group: &'l str,
49    /// The mode of this level, inherited by children
50    mode: Mode,
51}
52
53impl<'g, 'p, 'l> StackFrame<'g, 'p, 'l> {
54    /// Constructs a new stack
55    pub fn stack(
56        config: &'g Config<'g>,
57        variables: VariableSource<'g>,
58        owner: &'l str,
59        group: &'l str,
60        mode: Mode,
61    ) -> Self {
62        StackFrame {
63            parent: None,
64            config,
65            variables,
66            owner,
67            group,
68            mode,
69        }
70    }
71
72    /// Adds a new scope onto the stack, returning it
73    pub fn push<'s, 'r>(&'s self, variables: VariableSource<'g>) -> StackFrame<'g, 'r, 'r>
74    where
75        'g: 'r,
76        's: 'r,
77    {
78        StackFrame {
79            parent: Some(self),
80            variables,
81            owner: self.owner,
82            group: self.group,
83            mode: self.mode,
84            config: self.config,
85        }
86    }
87
88    /// Changes the owner in the current scope
89    pub fn put_owner(&mut self, owner: &'l str) {
90        self.owner = owner;
91    }
92
93    /// Changes the group in the current scope
94    pub fn put_group(&mut self, group: &'l str) {
95        self.group = group;
96    }
97
98    /// Returns the owner in the current scope
99    pub fn owner(&self) -> &'l str {
100        self.owner
101    }
102
103    /// Returns the group in the current scope
104    pub fn group(&self) -> &'l str {
105        self.group
106    }
107
108    /// Returns the UNIX permissions set for the current scope
109    pub fn mode(&self) -> Mode {
110        self.mode
111    }
112
113    /// Provides access to variables in the current scope
114    pub fn variables(&self) -> &VariableSource<'l> {
115        &self.variables
116    }
117
118    /// Looks up the value of a variable in the current or parent scope(s)
119    pub fn lookup<'a>(&'a self, var: &Identifier<'a>) -> Option<Value<'a>> {
120        match &self.variables {
121            VariableSource::Empty => None,
122            VariableSource::Directory(directory) => directory.get_var(var).map(Value::Expression),
123            VariableSource::Binding(bind, ref value) => {
124                if *bind == var {
125                    Some(Value::String(value))
126                } else {
127                    None
128                }
129            }
130            VariableSource::Map(map) => map.get(var.value()).map(|s| Value::String(s.as_str())),
131        }
132        .or_else(|| self.parent.and_then(|parent| parent.lookup(var)))
133    }
134
135    /// Looks up the definition of a sub-schema in the current or parent scope(s)
136    pub fn find_definition<'a>(&self, var: &Identifier<'a>) -> Option<&'a SchemaNode<'g>> {
137        match self.variables {
138            VariableSource::Directory(directory) => directory.get_def(var),
139            _ => None,
140        }
141        .or_else(|| self.parent.and_then(|parent| parent.find_definition(var)))
142    }
143}
144
145/// Ways in which variables may be provided by the current scope
146#[derive(Debug)]
147pub enum VariableSource<'a> {
148    /// No available variables
149    Empty,
150    /// A directory schema description, with its own variables
151    Directory(&'a DirectorySchema<'a>),
152    /// A binding of a schema to a single name
153    Binding(&'a Identifier<'a>, String),
154    /// A simple key-value map
155    Map(HashMap<String, String>),
156}
157
158impl Default for VariableSource<'_> {
159    fn default() -> Self {
160        VariableSource::Empty
161    }
162}
163
164impl From<HashMap<String, String>> for VariableSource<'_> {
165    fn from(map: HashMap<String, String>) -> Self {
166        VariableSource::Map(map)
167    }
168}
169
170impl<'a> VariableSource<'a> {
171    /// Convenience for attempting to cast to a single schema binding
172    pub fn as_binding(&self) -> Option<(&Identifier<'a>, &String)> {
173        match self {
174            VariableSource::Binding(id, value) => Some((id, value)),
175            _ => None,
176        }
177    }
178}
179
180impl Display for StackFrame<'_, '_, '_> {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        match &self.variables {
183            VariableSource::Empty => {}
184            VariableSource::Directory(directory_schema) => {
185                write!(f, "Directory variables:",)?;
186                let mut no_vars = true;
187                for (ident, expr) in directory_schema.vars() {
188                    no_vars = false;
189                    write!(f, "\n  ${ident} = \"{expr}\"")?;
190                }
191                if no_vars {
192                    write!(f, "\n  (no variables)",)?;
193                }
194            }
195            VariableSource::Binding(ident, value) => {
196                write!(f, "Schema binding:")?;
197                write!(f, "\n  ${ident} = \"{value}\"",)?;
198            }
199            VariableSource::Map(map) => {
200                write!(f, "Variable map:")?;
201                for (key, value) in map.iter() {
202                    write!(f, "\n  ${key} = \"{value}\"")?;
203                }
204            }
205        }
206        Ok(())
207    }
208}