You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
7.8 KiB
Rust
251 lines
7.8 KiB
Rust
use std::{
|
|
fs::{self},
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
};
|
|
|
|
use crate::{config::SiloConfig, scripting::create_lua, templating, utils::Describe};
|
|
|
|
use super::{ApplyContext, ParseContext, ReadMode};
|
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
|
use lazy_static::lazy_static;
|
|
use miette::{Context, IntoDiagnostic, Result};
|
|
use mlua::LuaSerdeExt;
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Contents {
|
|
pub root: DirEntry,
|
|
}
|
|
|
|
impl Contents {
|
|
pub fn parse(pctx: ParseContext, path: PathBuf) -> Result<Self> {
|
|
let root = DirEntry::parse(Rc::new(pctx), path.to_owned())?;
|
|
Ok(Self { root })
|
|
}
|
|
|
|
pub fn apply(&self, actx: &mut ApplyContext, cwd: &Path) -> Result<()> {
|
|
self.root.apply(actx, cwd)
|
|
}
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref IGNORED_PATHS: GlobSet = GlobSetBuilder::new()
|
|
.add(Glob::new("**/.git").unwrap())
|
|
.add(Glob::new("**/dir.{toml,toml.tmpl}").unwrap())
|
|
.add(Glob::new("**/silo.{dir,config}.lua").unwrap())
|
|
.build()
|
|
.unwrap();
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum DirEntry {
|
|
File(FileEntry),
|
|
Dir(PathBuf, Vec<DirEntry>),
|
|
Root(PathBuf, RootDirData, Vec<DirEntry>),
|
|
}
|
|
|
|
impl DirEntry {
|
|
fn parse(mut ctx: Rc<ParseContext>, path: PathBuf) -> Result<Self> {
|
|
if path.is_dir() {
|
|
log::debug!("Parsing directory {path:?}");
|
|
|
|
let meta_file = path.join("dir.toml");
|
|
let meta_tmpl = path.join("dir.toml.tmpl");
|
|
let script_tmpl = path.join("silo.dir.lua");
|
|
|
|
let metadata = if script_tmpl.exists() {
|
|
log::debug!("Found script template");
|
|
let metadata = RootDirData::read_lua(&script_tmpl, &ctx.config)?;
|
|
ctx = Rc::new(ParseContext::new(
|
|
path.clone(),
|
|
metadata.read_mode(),
|
|
ctx.config.clone(),
|
|
));
|
|
|
|
Some(metadata)
|
|
} else if meta_file.exists() {
|
|
log::debug!("Found metadata file");
|
|
log::warn!("Old toml metadata files are deprecated. Please migrate to the `silo.dir.lua` syntax");
|
|
let metadata = RootDirData::read(&meta_file)?;
|
|
ctx = Rc::new(ParseContext::new(
|
|
path.clone(),
|
|
metadata.read_mode(),
|
|
ctx.config.clone(),
|
|
));
|
|
|
|
Some(metadata)
|
|
} else if meta_tmpl.exists() {
|
|
log::debug!("Found metadata template");
|
|
log::warn!("Old template metadata files are deprecated. Please migrate to the `silo.dir.lua` syntax");
|
|
let metadata = RootDirData::read_template(&meta_tmpl, &ctx.config)?;
|
|
ctx = Rc::new(ParseContext::new(
|
|
path.clone(),
|
|
metadata.read_mode(),
|
|
ctx.config.clone(),
|
|
));
|
|
|
|
Some(metadata)
|
|
} else {
|
|
log::debug!("Directory is child");
|
|
None
|
|
};
|
|
|
|
let mut children = Vec::new();
|
|
|
|
for read_entry in fs::read_dir(&path).into_diagnostic()? {
|
|
let read_entry = read_entry.into_diagnostic()?;
|
|
let entry_path = read_entry.path();
|
|
let test_path = entry_path.strip_prefix(&ctx.base).into_diagnostic()?;
|
|
|
|
if !IGNORED_PATHS.is_match(test_path) && ctx.is_included(test_path) {
|
|
children.push(DirEntry::parse(ctx.clone(), entry_path)?);
|
|
} else {
|
|
log::debug!("Entry {entry_path:?} is ignored")
|
|
}
|
|
}
|
|
|
|
if let Some(metadata) = metadata {
|
|
Ok(Self::Root(path, metadata, children))
|
|
} else {
|
|
Ok(Self::Dir(path, children))
|
|
}
|
|
} else {
|
|
log::debug!("Parsing file {path:?}");
|
|
Ok(Self::File(FileEntry::parse(path)?))
|
|
}
|
|
}
|
|
|
|
fn apply(&self, ctx: &mut ApplyContext, cwd: &Path) -> Result<()> {
|
|
match self {
|
|
DirEntry::File(file) => file.apply(ctx, cwd),
|
|
DirEntry::Dir(p, children) => {
|
|
let cwd = if p != cwd {
|
|
cwd.join(p.file_name().unwrap())
|
|
} else {
|
|
p.to_owned()
|
|
};
|
|
for child in children {
|
|
child.apply(ctx, &cwd)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
DirEntry::Root(_, data, children) => {
|
|
let rendered_path = templating::render(&data.path, &ctx.config)?;
|
|
let cwd = PathBuf::from(rendered_path);
|
|
|
|
for child in children {
|
|
child.apply(ctx, &cwd)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum FileEntry {
|
|
Template(PathBuf),
|
|
Plain(PathBuf),
|
|
}
|
|
|
|
impl FileEntry {
|
|
fn parse(path: PathBuf) -> Result<Self> {
|
|
if let Some(true) = path.extension().map(|e| e == "tmpl") {
|
|
log::debug!("File is template");
|
|
Ok(Self::Template(path))
|
|
} else {
|
|
log::debug!("File is plain");
|
|
Ok(Self::Plain(path))
|
|
}
|
|
}
|
|
|
|
fn apply(&self, ctx: &mut ApplyContext, cwd: &Path) -> Result<()> {
|
|
match self {
|
|
FileEntry::Template(path) => {
|
|
log::debug!("Processing template {path:?}");
|
|
|
|
let contents = fs::read_to_string(path).into_diagnostic()?;
|
|
let new_path = path.with_extension("");
|
|
let filename = new_path.file_name().unwrap();
|
|
|
|
let dest = cwd.join(filename);
|
|
let render_contents = templating::render(&contents, &ctx.config.userdata)?;
|
|
|
|
ctx.fs.write_all(&dest, &render_contents.into_bytes())?;
|
|
ctx.fs
|
|
.set_permissions(&dest, fs::metadata(path).into_diagnostic()?.permissions())?;
|
|
}
|
|
FileEntry::Plain(path) => {
|
|
let filename = path.file_name().unwrap();
|
|
let dest = cwd.join(filename);
|
|
ctx.fs.copy(path, &dest)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
pub struct RootDirData {
|
|
pub path: String,
|
|
#[serde(default)]
|
|
pub mode: Mode,
|
|
#[serde(default, alias = "ignored")]
|
|
pub exclude: GlobSet,
|
|
#[serde(default)]
|
|
pub include: GlobSet,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
pub enum Mode {
|
|
#[serde(alias = "include")]
|
|
Include,
|
|
#[serde(alias = "exclude")]
|
|
Exclude,
|
|
}
|
|
|
|
impl Default for Mode {
|
|
fn default() -> Self {
|
|
Self::Exclude
|
|
}
|
|
}
|
|
|
|
impl RootDirData {
|
|
fn read(path: &Path) -> Result<Self> {
|
|
let contents = fs::read_to_string(path)
|
|
.into_diagnostic()
|
|
.with_context(|| format!("reading metadata file {path:?}"))?;
|
|
toml::from_str(&contents)
|
|
.into_diagnostic()
|
|
.with_context(|| format!("parsing metadata file {path:?}"))
|
|
}
|
|
|
|
fn read_template(path: &Path, cfg: &SiloConfig) -> Result<Self> {
|
|
let contents = fs::read_to_string(path)
|
|
.into_diagnostic()
|
|
.with_context(|| format!("reading metadata file {path:?}"))?;
|
|
let rendered = templating::render(&contents, cfg)?;
|
|
toml::from_str(&rendered)
|
|
.into_diagnostic()
|
|
.with_context(|| format!("parsing metadata file {path:?}"))
|
|
}
|
|
|
|
fn read_lua(path: &Path, cfg: &SiloConfig) -> Result<Self> {
|
|
let lua = create_lua(cfg)?;
|
|
let cfg: Self = lua
|
|
.from_value(lua.load(path).eval().describe("evaluating script")?)
|
|
.describe("deserialize lua value")?;
|
|
|
|
Ok(cfg)
|
|
}
|
|
|
|
fn read_mode(&self) -> ReadMode {
|
|
match self.mode {
|
|
Mode::Include => ReadMode::Include(self.include.clone()),
|
|
Mode::Exclude => ReadMode::Exclude(self.exclude.clone()),
|
|
}
|
|
}
|
|
}
|