From: Huck Boles Date: Mon, 29 May 2023 03:00:27 +0000 (-0500) Subject: release: 0.1.2 X-Git-Url: https://git.huck.website/?a=commitdiff_plain;h=5a4f16ceb76534e1f873174d2fd4076caec3c387;p=metaforge.git release: 0.1.2 --- diff --git a/Cargo.lock b/Cargo.lock index b30d11b..f9c80f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -524,6 +524,7 @@ dependencies = [ "pandoc", "pest", "pest_derive", + "rayon", "thiserror", ] @@ -785,9 +786,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45b6ddbb36c5b969c182aec3c4a0bce7df3fbad4b77114706a49aacc80567388" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 175a564..955fee0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ thiserror = "1" eyre = "0.6" pest = "2" pest_derive = "2" +rayon = "1.7" [dev-dependencies] criterion = "0.4" @@ -25,3 +26,7 @@ harness = false [[bench]] name = "pandoc" harness = false + +[[bench]] +name = "parallel" +harness = false diff --git a/README.md b/README.md index 515eb5e..4614ad0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# metaforge - v0.1.1 +# metaforge - v0.1.2 a pattern driven static site generator for extensible snippet insertion. @@ -12,7 +12,6 @@ a pattern driven static site generator for extensible snippet insertion. $ cd metaforge $ cargo install --path . - ## about metaforge is a static site generator that lets you write something once, and re-use it diff --git a/benches/parallel.rs b/benches/parallel.rs new file mode 100644 index 0000000..b31ed61 --- /dev/null +++ b/benches/parallel.rs @@ -0,0 +1,32 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +pub fn parallel_build_dir(c: &mut Criterion) { + let dir = std::path::PathBuf::from("files/bench_site") + .canonicalize() + .unwrap(); + + let mut opts = metaforge::Options::new(); + opts.root = dir.clone(); + opts.source = dir.join("source"); + opts.build = dir.join("build"); + opts.pattern = dir.join("pattern"); + opts.clean = true; + opts.parallel = true; + + c.bench_function("build dir", |b| { + if opts.build.exists() { + std::fs::remove_dir_all(&opts.build).expect("clean build dir"); + } + + std::fs::create_dir(&opts.build).expect("create build dir"); + b.iter(|| metaforge::build_site(black_box(&opts)).unwrap()) + }); +} + +criterion_group! { + name = benches; + config = Criterion::default().sample_size(10).measurement_time(core::time::Duration::from_secs(135)); + targets = parallel_build_dir +} + +criterion_main!(benches); diff --git a/files/README/source/default.meta b/files/README/source/default.meta index 3427222..3c3a9fb 100644 --- a/files/README/source/default.meta +++ b/files/README/source/default.meta @@ -1,5 +1,5 @@ ${ author = 'huck boles' - version = '0.1.1' + version = '0.1.2' home = './index.html' } diff --git a/files/README/source/docs/flags.meta b/files/README/source/docs/flags.meta index ed7f148..4a00fbf 100644 --- a/files/README/source/docs/flags.meta +++ b/files/README/source/docs/flags.meta @@ -19,6 +19,9 @@ ${ defaults to [root_dir]/pattern -f, --file builds a single file and outputs it to stdout + -l --parallel + enable parallel processing for faster build times + interleaves output from files in verbose mode -v, --verbose enable extra output. repeated flags give more info v => list source files/directories being created diff --git a/files/README/source/index.meta b/files/README/source/index.meta index 079d07b..8a2b159 100644 --- a/files/README/source/index.meta +++ b/files/README/source/index.meta @@ -4,11 +4,10 @@ ${ this is the documentation for metaforge, generated by metaforge itself. -currently it's unstyled html rendered by your browser, so it's pretty bare-bones, but -you can change that easily. +currently it's unstyled html rendered by your browser, so it's pretty bare-bones. open **files/README** in metaforge's repository and explore the source and pattern -files to get some examples. +files to get some examples of how this site works. you can change anything in the **README** directory and see how it affects the site once you rebuild it. @@ -22,4 +21,5 @@ you rebuild it. - [flags](docs/flags.html) ## versions +- 0.1.2: multithreading - 0.1.1: initial release diff --git a/src/lib.rs b/src/lib.rs index 86a4eb2..1f34105 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,12 @@ pub fn build_site(opts: &Options) -> Result<()> { }; source.map(&global_init)?; - source.build_dir() + + if opts.parallel { + source.par_dir() + } else { + source.build_dir() + } } pub fn single_file(opts: &Options) -> Result { diff --git a/src/metafile/dir.rs b/src/metafile/dir.rs index fb034bc..197d964 100644 --- a/src/metafile/dir.rs +++ b/src/metafile/dir.rs @@ -1,6 +1,11 @@ -use crate::{error::*, Options}; -use eyre::Result; -use std::{fs, path::PathBuf}; +mod node; +mod parallel; + +pub use node::*; +pub use parallel::*; + +use crate::Options; +use std::path::PathBuf; use super::*; @@ -12,96 +17,3 @@ pub struct DirNode<'a> { pub files: Vec>, pub dirs: Vec>, } - -impl<'a> DirNode<'a> { - pub fn build(path: PathBuf, opts: &'a Options) -> Result { - assert!(path.is_dir() && path.exists()); - - // copy over directory structure from source dir - let build_dir = opts.build.join(path.strip_prefix(&opts.source)?); - if !build_dir.exists() { - fs::create_dir_all(build_dir)?; - } - - let files: Vec = Vec::new(); - let dirs: Vec = Vec::new(); - let global = MetaFile::new(opts); - - Ok(Self { - path, - opts, - global, - files, - dirs, - }) - } - - // parses all contained files and directories and pushes - // parsed structures into the files and directories vectors - pub fn map(&mut self, global: &'a MetaFile) -> Result<()> { - if self.path.join("default.meta").exists() { - if let Some(mut new_global) = check_ignore(MetaFile::build( - self.path.clone().join("default.meta"), - self.opts, - ))? { - new_global.merge(global); - self.global = new_global; - } - } - - for f in fs::read_dir(&self.path)? { - let file = f?.path(); - - if file.is_dir() { - let dir = DirNode::build(file, self.opts)?; - self.dirs.push(dir); - } else if file.file_name().and_then(|f| f.to_str()) == Some("default.meta") { - continue; - } else if file.extension().and_then(|f| f.to_str()) == Some("meta") { - if let Some(file) = check_ignore(MetaFile::build(file, self.opts))? { - self.files.push(file) - } - } - } - - Ok(()) - } - - pub fn build_files(&mut self) -> Result<()> { - for file in self.files.iter_mut() { - file.merge(&self.global); - match file.construct() { - Ok(str) => { - fs::write(file.dest()?, str)?; - } - Err(e) => { - // print a line to stderr about failure but continue with other files - if self.opts.force { - eprintln!("ignoring {}: {}", file.path.display(), e); - continue; - } else { - match *e { - MetaError::Ignored => continue, - e => { - eprintln!("{}", file.path.display()); - return Err(e.into()); - } - } - } - } - } - } - Ok(()) - } - - pub fn build_dir(&'a mut self) -> Result<()> { - self.build_files()?; - - for dir in self.dirs.iter_mut() { - dir.map(&self.global)?; - dir.build_dir()?; - } - - Ok(()) - } -} diff --git a/src/metafile/dir/node.rs b/src/metafile/dir/node.rs new file mode 100644 index 0000000..e57630e --- /dev/null +++ b/src/metafile/dir/node.rs @@ -0,0 +1,98 @@ +use crate::{error::*, Options}; +use eyre::Result; +use std::{fs, path::PathBuf}; + +use super::*; + +impl<'a> DirNode<'a> { + pub fn build(path: PathBuf, opts: &'a Options) -> Result { + assert!(path.is_dir() && path.exists()); + + // copy over directory structure from source dir + let build_dir = opts.build.join(path.strip_prefix(&opts.source)?); + if !build_dir.exists() { + fs::create_dir_all(build_dir)?; + } + + let files: Vec = Vec::new(); + let dirs: Vec = Vec::new(); + let global = MetaFile::new(opts); + + Ok(Self { + path, + opts, + global, + files, + dirs, + }) + } + + // parses all contained files and directories and pushes + // parsed structures into the files and directories vectors + pub fn map(&mut self, global: &'a MetaFile) -> Result<()> { + if self.path.join("default.meta").exists() { + if let Some(mut new_global) = check_ignore(MetaFile::build( + self.path.clone().join("default.meta"), + self.opts, + ))? { + new_global.merge(global); + self.global = new_global; + } + } + + for f in fs::read_dir(&self.path)? { + let file = f?.path(); + + if file.is_dir() { + let dir = DirNode::build(file, self.opts)?; + self.dirs.push(dir); + } else if file.file_name().and_then(|f| f.to_str()) == Some("default.meta") { + continue; + } else if file.extension().and_then(|f| f.to_str()) == Some("meta") { + if let Some(file) = check_ignore(MetaFile::build(file, self.opts))? { + self.files.push(file) + } + } + } + + Ok(()) + } + + pub fn build_files(&mut self) -> Result<()> { + for file in self.files.iter_mut() { + file.merge(&self.global); + match file.construct() { + Ok(str) => { + fs::write(file.dest()?, str)?; + } + Err(e) => { + // print a line to stderr about failure but continue with other files + if self.opts.force { + eprintln!("ignoring {}: {}", file.path.display(), e); + continue; + } else { + match *e { + MetaError::Ignored => continue, + e => { + eprintln!("{}", file.path.display()); + return Err(e.into()); + } + } + } + } + } + } + Ok(()) + } + + pub fn build_dir(&'a mut self) -> Result<()> { + self.build_files()?; + + for dir in self.dirs.iter_mut() { + dir.map(&self.global)?; + dir.build_dir()?; + } + + Ok(()) + } +} diff --git a/src/metafile/dir/parallel.rs b/src/metafile/dir/parallel.rs new file mode 100644 index 0000000..6784015 --- /dev/null +++ b/src/metafile/dir/parallel.rs @@ -0,0 +1,43 @@ +use crate::{DirNode, MetaError}; +use eyre::Result; +use rayon::prelude::*; +use std::fs; + +impl<'a> DirNode<'a> { + pub fn par_file(&mut self) -> Result<()> { + self.files.par_iter_mut().for_each(|file| { + file.merge(&self.global); + match file.construct() { + Ok(str) => { + fs::write(file.dest().unwrap(), str).unwrap(); + } + Err(e) => { + // print a line to stderr about failure but continue with other files + if self.opts.force { + eprintln!("ignoring {}: {}", file.path.display(), e); + } else { + match *e { + MetaError::Ignored => {} + e => { + eprintln!("{}", file.path.display()); + panic!("{}", e); + } + } + } + } + } + }); + Ok(()) + } + + pub fn par_dir(&'a mut self) -> Result<()> { + self.build_files()?; + + self.dirs.par_iter_mut().for_each(|dir| { + dir.map(&self.global).unwrap(); + dir.build_dir().unwrap(); + }); + + Ok(()) + } +} diff --git a/src/options.rs b/src/options.rs index 5737de6..704d2cb 100644 --- a/src/options.rs +++ b/src/options.rs @@ -3,9 +3,9 @@ use eyre::Result; use std::path::PathBuf; #[derive(Parser, Debug)] -#[command(author = "Huck Boles")] -#[command(version = "0.1.1")] -#[command(about = "A customizable template driven static site generator")] +#[command(author = "huck boles")] +#[command(version = "0.1.2")] +#[command(about = "customizable template driven static site generator")] #[command(long_about = None)] pub struct Opts { /// root directory [current_dir] @@ -20,39 +20,39 @@ pub struct Opts { /// pattern directory [current_dir/pattern] #[arg(short, long, value_name = "PATTERN_DIR")] pub pattern: Option, - /// only build a single file + /// builds a single file and outputs on stdout #[arg(short, long, value_name = "FILENAME")] pub file: Option, - /// parallel processing - #[arg(long, default_value_t = false)] - pub parallel: bool, - /// create a new skeleton directory - #[arg(long, default_value_t = false)] - pub new: bool, + /// output filetype [html] + #[arg(short, long, value_name = "OUTPUT_FILETYPE")] + pub output: Option, + /// input filetype [markdown] + #[arg(short, long, value_name = "INPUT_FILETYPE")] + pub input: Option, /// enable extra output. repeated flags give more info #[arg(short, long, action = clap::ArgAction::Count)] pub verbose: u8, - /// minimal output + /// minimal output [false] #[arg(short, long, default_value_t = false)] pub quiet: bool, + /// enable parallel processing [false] + #[arg(short = 'l', long, default_value_t = false)] + pub parallel: bool, + /// create a new skeleton directory [false] + #[arg(long, default_value_t = false)] + pub new: bool, + /// clean build directory before building site [false] + #[arg(long, default_value_t = false)] + pub clean: bool, /// don't stop on file failure [false] #[arg(long, default_value_t = false)] pub force: bool, /// stop on undefined variables and arrays [false] #[arg(long, default_value_t = false)] pub undefined: bool, - /// clean build directory before building site [false] - #[arg(long, default_value_t = false)] - pub clean: bool, /// don't call pandoc on source files #[arg(long, default_value_t = false)] pub no_pandoc: bool, - /// output filetype [html] - #[arg(short, long, value_name = "OUTPUT_FILETYPE")] - pub output: Option, - /// input filetype [markdown] - #[arg(short, long, value_name = "INPUT_FILETYPE")] - pub input: Option, } #[derive(Debug, Clone, Default)] diff --git a/tests/build_dir.rs b/tests/build_dir.rs index c2253c4..4874e89 100644 --- a/tests/build_dir.rs +++ b/tests/build_dir.rs @@ -32,3 +32,37 @@ fn build_test_site() -> Result<()> { Ok(()) } + +#[test] +fn parallel_build_test_site() -> Result<()> { + let dir = std::path::PathBuf::from("files/test_site") + .canonicalize() + .unwrap(); + + let mut opts = metaforge::Options::new(); + opts.root = dir.clone(); + opts.source = dir.join("source"); + opts.build = dir.join("build"); + opts.pattern = dir.join("pattern"); + opts.clean = true; + opts.parallel = true; + + metaforge::build_site(&opts)?; + + assert!(opts.build.join("unit_tests").exists()); + assert!(opts + .build + .join("unit_tests/blank/blank_array.html") + .exists()); + assert!(opts + .build + .join("unit_tests/expand/variable_in_source.html") + .exists()); + assert!(opts + .build + .join("unit_tests/override/variable.html") + .exists()); + assert!(opts.build.join("unit_tests/global/pattern.html").exists()); + + Ok(()) +} diff --git a/tests/readme.rs b/tests/readme.rs index a64228a..01279d7 100644 --- a/tests/readme.rs +++ b/tests/readme.rs @@ -13,6 +13,7 @@ fn readme() -> Result<()> { opts.build = dir.join("build"); opts.pattern = dir.join("pattern"); opts.clean = true; + opts.parallel = true; metaforge::build_site(&opts) }