]> git.huck.website - metaforge.git/commitdiff
release: 0.1.2
authorHuck Boles <huck@huck.website>
Mon, 29 May 2023 03:00:27 +0000 (22:00 -0500)
committerHuck Boles <huck@huck.website>
Mon, 29 May 2023 03:00:27 +0000 (22:00 -0500)
14 files changed:
Cargo.lock
Cargo.toml
README.md
benches/parallel.rs [new file with mode: 0644]
files/README/source/default.meta
files/README/source/docs/flags.meta
files/README/source/index.meta
src/lib.rs
src/metafile/dir.rs
src/metafile/dir/node.rs [new file with mode: 0644]
src/metafile/dir/parallel.rs [new file with mode: 0644]
src/options.rs
tests/build_dir.rs
tests/readme.rs

index b30d11bc7a44585527dde8c3757f3f86667237ee..f9c80f59d71c4d9fd5b6f4f223c680fe5290daf2 100644 (file)
@@ -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",
index 175a564968b25563f41f2b4b9dbbf323bcf6721f..955fee007ba8f9cf7dfa0d8ce4aac335011e8021 100644 (file)
@@ -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
index 515eb5e69692bd13e49d3ad1332f8217513b3078..4614ad0b27226f2ead2beb1c7622b26af21c6430 100644 (file)
--- 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 (file)
index 0000000..b31ed61
--- /dev/null
@@ -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);
index 3427222a8ac73e49661953af25cf58aa08e29e48..3c3a9fbb1ebdbb07c08859e9e6f00cc24b52f401 100644 (file)
@@ -1,5 +1,5 @@
 ${
     author = 'huck boles'
-    version = '0.1.1'
+    version = '0.1.2'
     home = './index.html'
 }
index ed7f148c4cb0be4c3cdc7ff5b948cb18ba8f8d73..4a00fbfa13faacdf58389a65e389d77c7424ee28 100644 (file)
@@ -19,6 +19,9 @@ ${
             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
index 079d07bd6f2046ac63e6082fe2b01bb6b6c9ccdb..8a2b15974ec8062a202133018d222c8eb22f7ca6 100644 (file)
@@ -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
index 86a4eb2a06a92e3ca79f256914cb8287ea83923b..1f341057ef98043d89212ae7b63a3b8f849131df 100644 (file)
@@ -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<String> {
index fb034bcbd63a14dd5b1bb0099832f6755d22b50d..197d9648712f1f3114fc08a26a73cd7fd9f37266 100644 (file)
@@ -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<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(())
-    }
-}
diff --git a/src/metafile/dir/node.rs b/src/metafile/dir/node.rs
new file mode 100644 (file)
index 0000000..e57630e
--- /dev/null
@@ -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<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(())
+    }
+}
diff --git a/src/metafile/dir/parallel.rs b/src/metafile/dir/parallel.rs
new file mode 100644 (file)
index 0000000..6784015
--- /dev/null
@@ -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(())
+    }
+}
index 5737de680b510c3a49f0262a8849360ca9aaf316..704d2cbc8404d9c659afbe38c75c511f17fdb288 100644 (file)
@@ -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 = "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<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)]
index c2253c46b2eb9c9b985930076398b4011028727d..4874e898d4dc387699360341bb8403059eefbeb6 100644 (file)
@@ -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(())
+}
index a64228a43cb6dcc6fd7169e96462389bd0e4122d..01279d7a915a5c5b73579936b967b70927933eed 100644 (file)
@@ -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)
 }