"pandoc",
"pest",
"pest_derive",
+ "rayon",
"thiserror",
]
[[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",
eyre = "0.6"
pest = "2"
pest_derive = "2"
+rayon = "1.7"
[dev-dependencies]
criterion = "0.4"
[[bench]]
name = "pandoc"
harness = false
+
+[[bench]]
+name = "parallel"
+harness = false
-# metaforge - v0.1.1
+# metaforge - v0.1.2
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
--- /dev/null
+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);
${
author = 'huck boles'
- version = '0.1.1'
+ version = '0.1.2'
home = './index.html'
}
defaults to [root_dir]/pattern
-f, --file <FILENAME>
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
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.
- [flags](docs/flags.html)
## versions
+- 0.1.2: multithreading
- 0.1.1: initial release
};
source.map(&global_init)?;
- source.build_dir()
+
+ if opts.parallel {
+ source.par_dir()
+ } else {
+ source.build_dir()
+ }
}
pub fn single_file(opts: &Options) -> Result<String> {
-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::*;
pub files: Vec<MetaFile<'a>>,
pub dirs: Vec<DirNode<'a>>,
}
-
-impl<'a> DirNode<'a> {
- pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
- 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<MetaFile> = Vec::new();
- let dirs: Vec<DirNode> = 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(())
- }
-}
--- /dev/null
+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<Self> {
+ 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<MetaFile> = Vec::new();
+ let dirs: Vec<DirNode> = 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(())
+ }
+}
--- /dev/null
+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(())
+ }
+}
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]
/// pattern directory [current_dir/pattern]
#[arg(short, long, value_name = "PATTERN_DIR")]
pub pattern: Option<String>,
- /// only build a single file
+ /// builds a single file and outputs on stdout
#[arg(short, long, value_name = "FILENAME")]
pub file: Option<String>,
- /// 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<String>,
+ /// input filetype [markdown]
+ #[arg(short, long, value_name = "INPUT_FILETYPE")]
+ pub input: Option<String>,
/// 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<String>,
- /// input filetype [markdown]
- #[arg(short, long, value_name = "INPUT_FILETYPE")]
- pub input: Option<String>,
}
#[derive(Debug, Clone, Default)]
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(())
+}
opts.build = dir.join("build");
opts.pattern = dir.join("pattern");
opts.clean = true;
+ opts.parallel = true;
metaforge::build_site(&opts)
}