diff --git a/Cargo.lock b/Cargo.lock index 8f950fa..ad6377d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "cc" version = "1.0.83" @@ -311,6 +321,20 @@ dependencies = [ "url", ] +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", + "serde", +] + [[package]] name = "handlebars" version = "5.0.0" @@ -488,9 +512,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miette" @@ -796,6 +820,7 @@ dependencies = [ "clap", "dirs", "git2", + "globset", "handlebars", "handlebars_switch", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 9d49fb3..b597398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" clap = { version = "4.4.14", features = ["derive", "env"] } dirs = "5.0.1" git2 = "0.18.1" +globset = { version = "0.4.14", features = ["serde", "serde1"] } handlebars = "5.0.0" handlebars_switch = "0.6.0" lazy_static = "1.4.0" diff --git a/src/repo.rs b/src/repo.rs index 45a87be..32e6d21 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -1,12 +1,15 @@ +use globset::{Glob, GlobSet, GlobSetBuilder}; use miette::{bail, Context, IntoDiagnostic, Result}; use serde::Deserialize; use std::{ env, fs::{self}, path::{Path, PathBuf}, + rc::Rc, }; use crate::templating; +use lazy_static::lazy_static; #[derive(Clone, Debug)] pub struct SiloRepo { @@ -20,7 +23,7 @@ impl SiloRepo { } Ok(Self { - root: DirEntry::parse(path.to_owned())?, + root: DirEntry::parse(Rc::new(ParseContext::default()), path.to_owned())?, }) } @@ -32,46 +35,81 @@ impl SiloRepo { } } +pub struct ParseContext { + ignored: GlobSet, +} + +impl ParseContext { + pub fn new(ignored: GlobSet) -> Self { + Self { ignored } + } +} + +impl Default for ParseContext { + fn default() -> Self { + Self { + ignored: GlobSet::empty(), + } + } +} + +lazy_static! { + static ref IGNORED_PATHS: GlobSet = GlobSetBuilder::new() + .add(Glob::new("**/.git").unwrap()) + .add(Glob::new("**/dir.{toml,toml.tmpl}").unwrap()) + .build() + .unwrap(); +} + #[derive(Clone, Debug)] pub enum DirEntry { File(FileEntry), Dir(PathBuf, Vec), Root(PathBuf, RootDirData, Vec), - Ignored(PathBuf), } impl DirEntry { - fn parse(path: PathBuf) -> Result { + fn parse(mut context: Rc, path: PathBuf) -> Result { if path.is_dir() { log::debug!("Parsing directory {path:?}"); - if path.file_name().unwrap() == ".git" { - log::debug!("Ignoring .git directory"); - return Ok(Self::Ignored(path)); - } - - let mut children = Vec::new(); - - for read_entry in fs::read_dir(&path).into_diagnostic()? { - let read_entry = read_entry.into_diagnostic()?; - children.push(DirEntry::parse(read_entry.path())?); - } - let meta_file = path.join("dir.toml"); let meta_tmpl = path.join("dir.toml.tmpl"); - if meta_file.exists() { + let metadata = if meta_file.exists() { log::debug!("Found metadata file"); let metadata = RootDirData::read(&meta_file)?; + context = Rc::new(ParseContext::new(metadata.ignored.clone())); - Ok(Self::Root(path, metadata, children)) + Some(metadata) } else if meta_tmpl.exists() { log::debug!("Found metadata template"); let metadata = RootDirData::read_template(&meta_tmpl)?; + context = Rc::new(ParseContext::new(metadata.ignored.clone())); - Ok(Self::Root(path, metadata, children)) + 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(&path).into_diagnostic()?; + + if !IGNORED_PATHS.is_match(&test_path) && !context.ignored.is_match(&test_path) { + children.push(DirEntry::parse(context.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 { @@ -116,10 +154,6 @@ impl DirEntry { } Ok(()) } - DirEntry::Ignored(p) => { - log::debug!("Ignoring {p:?}"); - Ok(()) - } } } } @@ -128,17 +162,11 @@ impl DirEntry { pub enum FileEntry { Template(PathBuf), Plain(PathBuf), - Ignored(PathBuf), } impl FileEntry { fn parse(path: PathBuf) -> Result { - let file_name = path.file_name().unwrap(); - - if file_name == "dir.toml" || file_name == "dir.toml.tmpl" { - log::debug!("File is metadata"); - Ok(Self::Ignored(path)) - } else if let Some(true) = path.extension().map(|e| e == "tmpl") { + if let Some(true) = path.extension().map(|e| e == "tmpl") { log::debug!("File is template"); Ok(Self::Template(path)) } else { @@ -176,9 +204,6 @@ impl FileEntry { .into_diagnostic() .with_context(|| format!("copy {path:?} to {dest:?}"))?; } - FileEntry::Ignored(p) => { - log::debug!("Ignoring {p:?}") - } } Ok(()) @@ -188,6 +213,8 @@ impl FileEntry { #[derive(Clone, Debug, Deserialize)] pub struct RootDirData { pub path: String, + #[serde(default)] + pub ignored: GlobSet, } impl RootDirData {