1#![doc = include_str!("../examples/quickstart/diskplan.toml")]
8#![warn(missing_docs)]
15
16use std::{collections::HashMap, fmt::Write as _, ops::Deref};
17
18use anyhow::{anyhow, Context as _, Result};
19use camino::{Utf8Path, Utf8PathBuf};
20
21use diskplan_filesystem::Root;
22use diskplan_schema::SchemaNode;
23
24mod cache;
25mod file;
26pub use self::{
27 cache::SchemaCache,
28 file::{ConfigFile, ConfigStem},
29};
30
31pub struct Config<'t> {
33 target: Utf8PathBuf,
35
36 apply: bool,
38
39 schema_directory: Utf8PathBuf,
41
42 usermap: HashMap<String, String>,
44
45 groupmap: HashMap<String, String>,
47
48 stems: Stems<'t>,
49}
50
51impl<'t> Config<'t> {
52 pub fn new(target: impl AsRef<Utf8Path>, apply: bool) -> Self {
59 Config {
60 target: target.as_ref().to_owned(),
61 apply,
62 schema_directory: Utf8PathBuf::from("/"),
63 usermap: Default::default(),
64 groupmap: Default::default(),
65 stems: Default::default(),
66 }
67 }
68
69 pub fn load(&mut self, path: impl AsRef<Utf8Path>) -> Result<()> {
71 let ConfigFile {
72 stems,
73 schema_directory,
74 } = ConfigFile::load(path.as_ref())?;
75 self.schema_directory = schema_directory.unwrap_or_else(|| {
76 path.as_ref()
77 .parent()
78 .expect("No parent directory for config file")
79 .to_owned()
80 });
81 for (_, stem) in stems.into_iter() {
82 let schema_path = self.schema_directory.join(stem.schema());
83 self.stems.add(stem.root().to_owned(), schema_path)
84 }
85 Ok(())
86 }
87
88 pub fn apply_user_map(&mut self, usermap: HashMap<String, String>) {
90 self.usermap.extend(usermap.into_iter())
91 }
92
93 pub fn apply_group_map(&mut self, groupmap: HashMap<String, String>) {
95 self.groupmap.extend(groupmap.into_iter())
96 }
97
98 pub fn target_path(&self) -> &Utf8Path {
100 self.target.as_ref()
101 }
102
103 pub fn will_apply(&self) -> bool {
105 self.apply
106 }
107
108 pub fn add_stem(&mut self, root: Root, schema_path: impl AsRef<Utf8Path>) {
110 self.stems.add(root, schema_path)
111 }
112
113 pub fn add_precached_stem(
117 &mut self,
118 root: Root,
119 schema_path: impl AsRef<Utf8Path>,
120 schema: SchemaNode<'t>,
121 ) {
122 self.stems.add_precached(root, schema_path, schema)
123 }
124
125 pub fn stem_roots(&self) -> impl Iterator<Item = &Root> {
127 self.stems.roots()
128 }
129
130 pub fn schema_for<'s, 'p>(&'s self, path: &'p Utf8Path) -> Result<(&SchemaNode<'t>, &Root)>
133 where
134 's: 't,
135 {
136 self.stems.schema_for(path)
137 }
138
139 pub fn map_user<'a>(&'a self, name: &'a str) -> &'a str {
142 self.usermap.get(name).map(|s| s.deref()).unwrap_or(name)
143 }
144
145 pub fn map_group<'a>(&'a self, name: &'a str) -> &'a str {
148 self.groupmap.get(name).map(|s| s.deref()).unwrap_or(name)
149 }
150}
151
152#[derive(Default)]
154pub struct Stems<'t> {
155 path_map: HashMap<Root, Utf8PathBuf>,
157
158 cache: SchemaCache<'t>,
160}
161
162impl<'t> Stems<'t> {
163 pub fn new() -> Self {
165 Default::default()
166 }
167
168 pub fn add(&mut self, root: Root, schema_path: impl AsRef<Utf8Path>) {
170 self.path_map.insert(root, schema_path.as_ref().to_owned());
171 }
172
173 pub fn add_precached(
178 &mut self,
179 root: Root,
180 schema_path: impl AsRef<Utf8Path>,
181 schema: SchemaNode<'t>,
182 ) {
183 let schema_path = schema_path.as_ref();
184 self.cache.inject(schema_path, schema);
185 self.add(root, schema_path);
186 }
187
188 pub fn roots(&self) -> impl Iterator<Item = &Root> {
190 self.path_map.keys()
191 }
192
193 pub fn schema_for<'s, 'p>(&'s self, path: &'p Utf8Path) -> Result<(&SchemaNode<'t>, &Root)>
195 where
196 's: 't,
197 {
198 let mut longest_candidate = None;
199 for (root, schema_path) in self.path_map.iter() {
200 if path.starts_with(root.path()) {
201 match longest_candidate {
202 None => longest_candidate = Some((root, schema_path)),
203 Some(prev) => {
204 if root.path().as_str().len() > prev.0.path().as_str().len() {
205 longest_candidate = Some((root, schema_path))
206 }
207 }
208 }
209 }
210 }
211
212 if let Some((root, schema_path)) = longest_candidate {
213 tracing::trace!(
214 r#"Schema for path "{}", found root "{}", schema "{}""#,
215 path,
216 root.path(),
217 schema_path
218 );
219 let schema = self.cache.load(schema_path).with_context(|| {
220 format!(
221 "Failed to load schema {} for configured root {} (for target path {})",
222 schema_path,
223 root.path(),
224 path
225 )
226 })?;
227 Ok((schema, root))
228 } else {
229 let mut roots = String::new();
230 for root in self.roots() {
231 write!(roots, "\n - {}", root.path())?;
232 }
233 Err(anyhow!(
234 "No root/schema for path {}\nConfigured roots:{}",
235 path,
236 roots
237 ))
238 }
239 }
240}