#![doc = include_str!("../examples/quickstart/diskplan.toml")]
#![warn(missing_docs)]
use std::{collections::HashMap, fmt::Write as _, ops::Deref};
use anyhow::{anyhow, Context as _, Result};
use camino::{Utf8Path, Utf8PathBuf};
use diskplan_filesystem::Root;
use diskplan_schema::SchemaNode;
mod cache;
mod file;
pub use self::{
cache::SchemaCache,
file::{ConfigFile, ConfigStem},
};
pub struct Config<'t> {
target: Utf8PathBuf,
apply: bool,
schema_directory: Utf8PathBuf,
usermap: HashMap<String, String>,
groupmap: HashMap<String, String>,
stems: Stems<'t>,
}
impl<'t> Config<'t> {
pub fn new(target: impl AsRef<Utf8Path>, apply: bool) -> Self {
Config {
target: target.as_ref().to_owned(),
apply,
schema_directory: Utf8PathBuf::from("/"),
usermap: Default::default(),
groupmap: Default::default(),
stems: Default::default(),
}
}
pub fn load(&mut self, path: impl AsRef<Utf8Path>) -> Result<()> {
let ConfigFile {
stems,
schema_directory,
} = ConfigFile::load(path.as_ref())?;
self.schema_directory = schema_directory.unwrap_or_else(|| {
path.as_ref()
.parent()
.expect("No parent directory for config file")
.to_owned()
});
for (_, stem) in stems.into_iter() {
let schema_path = self.schema_directory.join(stem.schema());
self.stems.add(stem.root().to_owned(), schema_path)
}
Ok(())
}
pub fn apply_user_map(&mut self, usermap: HashMap<String, String>) {
self.usermap.extend(usermap.into_iter())
}
pub fn apply_group_map(&mut self, groupmap: HashMap<String, String>) {
self.groupmap.extend(groupmap.into_iter())
}
pub fn target_path(&self) -> &Utf8Path {
self.target.as_ref()
}
pub fn will_apply(&self) -> bool {
self.apply
}
pub fn add_stem(&mut self, root: Root, schema_path: impl AsRef<Utf8Path>) {
self.stems.add(root, schema_path)
}
pub fn add_precached_stem(
&mut self,
root: Root,
schema_path: impl AsRef<Utf8Path>,
schema: SchemaNode<'t>,
) {
self.stems.add_precached(root, schema_path, schema)
}
pub fn stem_roots(&self) -> impl Iterator<Item = &Root> {
self.stems.roots()
}
pub fn schema_for<'s, 'p>(&'s self, path: &'p Utf8Path) -> Result<(&SchemaNode<'t>, &Root)>
where
's: 't,
{
self.stems.schema_for(path)
}
pub fn map_user<'a>(&'a self, name: &'a str) -> &'a str {
self.usermap.get(name).map(|s| s.deref()).unwrap_or(name)
}
pub fn map_group<'a>(&'a self, name: &'a str) -> &'a str {
self.groupmap.get(name).map(|s| s.deref()).unwrap_or(name)
}
}
#[derive(Default)]
pub struct Stems<'t> {
path_map: HashMap<Root, Utf8PathBuf>,
cache: SchemaCache<'t>,
}
impl<'t> Stems<'t> {
pub fn new() -> Self {
Default::default()
}
pub fn add(&mut self, root: Root, schema_path: impl AsRef<Utf8Path>) {
self.path_map.insert(root, schema_path.as_ref().to_owned());
}
pub fn add_precached(
&mut self,
root: Root,
schema_path: impl AsRef<Utf8Path>,
schema: SchemaNode<'t>,
) {
let schema_path = schema_path.as_ref();
self.cache.inject(schema_path, schema);
self.add(root, schema_path);
}
pub fn roots(&self) -> impl Iterator<Item = &Root> {
self.path_map.keys()
}
pub fn schema_for<'s, 'p>(&'s self, path: &'p Utf8Path) -> Result<(&SchemaNode<'t>, &Root)>
where
's: 't,
{
let mut longest_candidate = None;
for (root, schema_path) in self.path_map.iter() {
if path.starts_with(root.path()) {
match longest_candidate {
None => longest_candidate = Some((root, schema_path)),
Some(prev) => {
if root.path().as_str().len() > prev.0.path().as_str().len() {
longest_candidate = Some((root, schema_path))
}
}
}
}
}
if let Some((root, schema_path)) = longest_candidate {
tracing::trace!(
r#"Schema for path "{}", found root "{}", schema "{}""#,
path,
root.path(),
schema_path
);
let schema = self.cache.load(schema_path).with_context(|| {
format!(
"Failed to load schema {} for configured root {} (for target path {})",
schema_path,
root.path(),
path
)
})?;
Ok((schema, root))
} else {
let mut roots = String::new();
for root in self.roots() {
write!(roots, "\n - {}", root.path())?;
}
Err(anyhow!(
"No root/schema for path {}\nConfigured roots:{}",
path,
roots
))
}
}
}