Files
df/manager/src/builder.rs

140 lines
3.8 KiB
Rust

use crate::error::{Error, ErrorLocation, Errors};
use crate::{ColorMode, Config};
use async_recursion::async_recursion;
use futures::future::join_all;
use serde::Serialize;
use std::ffi::OsStr;
use std::io::ErrorKind;
use std::path::PathBuf;
use templar::{Context, InnerData, StandardContext, Templar};
use tokio::fs::{copy, create_dir, read_dir, read_to_string, write};
use tokio::join;
#[derive(Serialize)]
struct TemplateContext<'a> {
hostname: &'a str,
lightmode: bool,
darkmode: bool,
}
const TEMPLATE_EXTENSION: &str = "tpl";
pub async fn build_tree(cfg: &Config) -> Result<(), Errors> {
let tpl = Templar::global();
let hostname_path: PathBuf = "/etc/hostname".into();
let hostname = read_to_string(&hostname_path)
.await
.with_location(&hostname_path)?;
let darkmode = cfg.color == ColorMode::Dark;
let ctx = StandardContext::new();
ctx.set(
InnerData::new(TemplateContext {
hostname: hostname.trim(),
darkmode,
lightmode: !darkmode,
})
.expect("serialize template context"),
)
.expect("set template context");
dir(cfg, &ctx, &tpl, PathBuf::new()).await
}
#[async_recursion]
async fn dir(
cfg: &Config,
ctx: &StandardContext,
tpl: &Templar,
relative: PathBuf,
) -> Result<(), Errors> {
let template_path = cfg.template_dir.join(&relative);
let build_path = cfg.build_dir.join(&relative);
info!("traversing {:?}", template_path);
match create_dir(&build_path).await {
Ok(_) => {}
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
Err(e) => return Err(e.with_location(&build_path).into()),
}
let mut walker = read_dir(&template_path)
.await
.with_location(&template_path)?;
let mut dir_tasks = vec![];
let mut file_tasks = vec![];
while let Some(entry) = walker.next_entry().await.with_location(&&template_path)? {
let meta = entry.metadata().await.with_location(&entry.path())?;
let new_relative = relative.join(entry.file_name());
if meta.is_dir() {
dir_tasks.push(dir(cfg, ctx, tpl, new_relative));
} else if meta.is_file() {
file_tasks.push(file(cfg, ctx, tpl, new_relative));
}
}
let dirs = async { join_all(dir_tasks).await.into_iter().collect::<Vec<_>>() };
let files = async { join_all(file_tasks).await.into_iter().collect::<Vec<_>>() };
let (dirs, files) = join!(dirs, files);
let mut errors: Errors = files
.into_iter()
.filter_map(|r| r.err())
.collect::<Vec<_>>()
.into();
for error in dirs.into_iter().filter_map(|r| r.err()) {
errors.join(error);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
async fn file(
cfg: &Config,
ctx: &StandardContext,
tpl: &Templar,
relative: PathBuf,
) -> Result<(), Error> {
let template_path = cfg.template_dir.join(&relative);
let mut new_path = cfg.build_dir.join(&relative);
info!("rendering {:?}", template_path);
if template_path.extension() == Some(OsStr::new(TEMPLATE_EXTENSION)) {
// perform templating
let file_str = read_to_string(&template_path)
.await
.with_location(&template_path)?;
let rendered = tpl
.parse_template(&file_str)
.with_location(&template_path)?
.render(ctx)
.with_location(&template_path)?;
// remove template file extension
new_path.set_extension("");
// write the rendered file
write(&new_path, &rendered).await.with_location(&new_path)?;
} else {
// else just copy the file
info!("copying {:?}", template_path);
copy(&template_path, &new_path)
.await
.with_location(&template_path)?;
}
Ok(())
}