]> git.huck.website - metaforge.git/commitdiff
refactored: moved builder functions into methods for MetaFiles
authorHuck Boles <huck@huck.website>
Sun, 21 May 2023 00:54:30 +0000 (19:54 -0500)
committerHuck Boles <huck@huck.website>
Sun, 21 May 2023 00:54:30 +0000 (19:54 -0500)
17 files changed:
src/builder.rs [deleted file]
src/builder/array.rs [deleted file]
src/builder/pattern.rs [deleted file]
src/builder/source.rs [deleted file]
src/builder/variable.rs [deleted file]
src/lib.rs
src/metafile/dir.rs
src/metafile/file.rs
src/metafile/file/arrays.rs [new file with mode: 0644]
src/metafile/file/attributes.rs [new file with mode: 0644]
src/metafile/file/patterns.rs [new file with mode: 0644]
src/metafile/file/source.rs [new file with mode: 0644]
src/metafile/file/variables.rs [new file with mode: 0644]
src/options.rs
src/options/arg_parser.rs [deleted file]
src/options/opt_struct.rs [deleted file]
src/tests.rs [moved from src/builder/tests.rs with 97% similarity]

diff --git a/src/builder.rs b/src/builder.rs
deleted file mode 100644 (file)
index 694abd4..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-mod array;
-mod pattern;
-mod source;
-mod variable;
-
-use pattern::*;
-use source::*;
-use variable::*;
-
-#[cfg(test)]
-mod tests;
-
-use crate::{log, MetaError, MetaFile, Scope};
-use eyre::Result;
-
-pub fn build_metafile(file: &MetaFile) -> Result<String, Box<MetaError>> {
-    log!(file.opts, format!("building {}", file.path.display()), 1);
-
-    if file.header.blank {
-        return Ok(String::new());
-    } else if file.header.ignore {
-        return Err(Box::new(MetaError::Ignored));
-    }
-
-    let html = get_source_html(file).map_err(MetaError::from)?;
-
-    let pattern = get_pattern("base", file).map_err(MetaError::from)?;
-    let mut base = crate::parse_string(pattern, file.opts).map_err(MetaError::from)?;
-
-    base.merge(file);
-    base.patterns.insert(Scope::into_global("SOURCE"), html);
-
-    let output = metafile_to_string(&base).map_err(MetaError::from)?;
-
-    Ok(output)
-}
diff --git a/src/builder/array.rs b/src/builder/array.rs
deleted file mode 100644 (file)
index e4871d1..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-use crate::{log, MetaError, MetaFile, Scope, Src};
-use eyre::Result;
-use std::collections::HashMap;
-
-pub fn expand_arrays(input: String, file: &MetaFile) -> Result<String> {
-    log!(
-        file.opts,
-        format!("expanding arrays in {}", file.path.display()),
-        2
-    );
-
-    let map: HashMap<String, &[String]> = file
-        .source
-        .iter()
-        // filter out arrays from source vec
-        .filter_map(|x| {
-            if let Src::Arr(array) = x {
-                Some(array)
-            } else {
-                None
-            }
-        })
-        // make a hash map of [keys in source] -> [defined arrays]
-        .map(|key| {
-            // concat array to pattern name to get key in HashMap
-            let name = file.name().unwrap_or_default();
-            let long_key = name + "." + key;
-
-            let value = if let Some(val) = file.get_arr(&Scope::into_global(&long_key)) {
-                val
-            } else if let Some(val) = file.get_arr(&Scope::into_local(&long_key)) {
-                val
-            } else if let Some(val) = file.get_arr(&Scope::into_global(key)) {
-                val
-            } else if let Some(val) = file.get_arr(&Scope::into_local(key)) {
-                val
-            } else if file.opts.undefined {
-                panic!(
-                    "{}",
-                    MetaError::UndefinedExpand {
-                        val: key.to_string(),
-                        path: file.path.to_string_lossy().to_string(),
-                    }
-                )
-            } else {
-                &[]
-            };
-            (key.to_string(), value)
-        })
-        .collect();
-
-    // loop to duplicate the output template for each array member
-    let mut expanded = String::new();
-    let size = match get_array_size(&map, file.header.equal_arrays) {
-        Ok(num) => Ok(num),
-        Err(e) => match e.as_ref() {
-            &MetaError::Array => Err(MetaError::UnequalArrays {
-                path: file.path.to_string_lossy().to_string(),
-            }),
-            _ => Err(MetaError::Unknown),
-        },
-    }?;
-    for i in 0..size {
-        // get a fresh copy of the file
-        let mut str = input.clone();
-        // replace each key in the file
-        for (key, val) in map.iter() {
-            if let Some(value) = val.get(i) {
-                str = str.replace(&format!("-{{{key}}}"), value);
-            }
-        }
-        // concatenate to final file
-        expanded.push_str(&str);
-    }
-
-    Ok(expanded)
-}
-
-fn get_array_size(
-    map: &HashMap<String, &[String]>,
-    same_size: bool,
-) -> Result<usize, Box<MetaError>> {
-    if same_size {
-        let mut size = (0, false);
-        for val in map.values() {
-            if !size.1 {
-                size = (val.len(), true);
-            } else if size.0 != val.len() {
-                return Err(Box::new(MetaError::Array));
-            }
-        }
-        return Ok(size.0);
-    }
-
-    let mut max = 0;
-    for val in map.values() {
-        if max < val.len() {
-            max = val.len();
-        }
-    }
-    Ok(max)
-}
diff --git a/src/builder/pattern.rs b/src/builder/pattern.rs
deleted file mode 100644 (file)
index b9cda17..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-use crate::{log, MetaError, MetaFile, Scope};
-use eyre::Result;
-use std::fs;
-
-pub fn get_pattern(key: &str, file: &MetaFile) -> Result<String> {
-    log!(file.opts, format!("expanding {key}"), 2);
-    // SOURCE is already expanded in the initial build_metafile() call
-    if key == "SOURCE" {
-        if let Some(source) = file.patterns.get(&Scope::into_global("SOURCE")) {
-            return Ok(source.to_string());
-        } else {
-            return Ok(String::new());
-        }
-    }
-
-    let mut filename = if let Some(name) = file.get_pat(&Scope::into_local(key)) {
-        Ok(name.to_string())
-    } else if let Some(name) = file.get_pat(&Scope::into_global(key)) {
-        Ok(name.to_string())
-    } else if file.header.panic_default {
-        Err(MetaError::UndefinedDefault {
-            pattern: key.to_string(),
-            path: file.path.to_string_lossy().to_string(),
-        })
-    } else {
-        // anything not defined should have a default.meta file to fall back to
-        Ok("default".to_string())
-    }?;
-
-    // BLANK returns nothing, so no more processing needs to be done
-    if filename == "BLANK" {
-        return Ok(String::default());
-    };
-
-    // DEFAULT override for patterns overriding globals
-    if filename == "DEFAULT" {
-        filename = "default".to_string();
-    }
-
-    // if we're building the base pattern we need to wait on
-    // parsing/expansion so we can build and convert source to html
-    // for the SOURCE pattern. we just want to return the string right now
-    if key == "base" {
-        let pattern_path = key.to_string() + "/" + &filename;
-        let mut path = file.opts.pattern.join(pattern_path);
-        path.set_extension("meta");
-
-        return match fs::read_to_string(&path) {
-            Ok(str) => Ok(str),
-            Err(_) => Err(MetaError::FileNotFound {
-                path: path.to_string_lossy().to_string(),
-            }
-            .into()),
-        };
-    }
-
-    let pattern_path = key.replace('.', "/") + "/" + &filename;
-    let mut path = file.opts.pattern.join(pattern_path);
-    path.set_extension("meta");
-
-    let mut pattern = MetaFile::build(path, file.opts)?;
-
-    // copy over maps for expanding contained variables
-    pattern.merge(file);
-
-    super::metafile_to_string(&pattern)
-}
diff --git a/src/builder/source.rs b/src/builder/source.rs
deleted file mode 100644 (file)
index 820da11..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-use crate::{log, MetaError, MetaFile, Src};
-use eyre::Result;
-use pandoc::{InputFormat, InputKind, OutputFormat, OutputKind, Pandoc};
-
-use super::array::*;
-use super::*;
-
-pub fn get_source_html(file: &MetaFile) -> Result<String> {
-    let string = metafile_to_string(file)?;
-
-    if file.opts.no_pandoc || !file.header.pandoc || string.is_empty() {
-        return Ok(string);
-    }
-
-    let input: InputFormat;
-    let output: OutputFormat;
-    if let Ok(io) = get_pandoc_io(file) {
-        input = io.0;
-        output = io.1;
-    } else {
-        // don't run pandoc if a filetype that isn't supported gets requested
-        return Ok(string);
-    }
-
-    log!(file.opts, "calling pandoc", 3);
-
-    let mut pandoc = Pandoc::new();
-    pandoc
-        .set_input(InputKind::Pipe(string))
-        .set_output(OutputKind::Pipe)
-        .set_input_format(input, vec![])
-        .set_output_format(output, vec![]);
-
-    if let pandoc::PandocOutput::ToBuffer(s) = pandoc.execute()? {
-        Ok(s)
-    } else {
-        Err(MetaError::Pandoc { file: file.name()? }.into())
-    }
-}
-
-pub fn metafile_to_string(file: &MetaFile) -> Result<String> {
-    if file.header.blank {
-        return Ok(String::new());
-    }
-
-    let mut output = String::default();
-    let mut arrays = false;
-
-    for section in file.source.iter() {
-        let sec = match section {
-            // concatenate any char sequences
-            Src::Str(str) => str.to_string(),
-            // expand all variables and recursively expand patterns
-            Src::Var(key) => get_variable(key, file)?,
-            Src::Pat(key) => get_pattern(key, file)?,
-            Src::Arr(key) => {
-                arrays = true;
-                // comments have already been removed at this point,
-                // so we use them to mark keys for array substitution
-                format!("-{{{key}}}")
-            }
-        };
-
-        output.push_str(&sec);
-    }
-
-    if arrays {
-        expand_arrays(output, file)
-    } else {
-        Ok(output)
-    }
-}
-
-fn get_pandoc_io(
-    file: &MetaFile,
-) -> Result<(pandoc::InputFormat, pandoc::OutputFormat), Box<MetaError>> {
-    let mut source_type = "";
-    if !file.header.source.is_empty() {
-        source_type = &file.header.source;
-    } else if !file.opts.input.is_empty() {
-        source_type = &file.opts.input;
-    }
-
-    let input = match source_type {
-        "markdown" => Ok(InputFormat::Markdown),
-        "html" => Ok(InputFormat::Html),
-        "org" => Ok(InputFormat::Org),
-        "json" => Ok(InputFormat::Json),
-        "latex" => Ok(InputFormat::Latex),
-        _ => Err(Box::new(MetaError::Filetype)),
-    }?;
-
-    let mut filetype = "";
-    if !file.header.filetype.is_empty() {
-        filetype = &file.header.filetype;
-    } else if !file.opts.input.is_empty() {
-        filetype = &file.opts.output;
-    }
-
-    let output = match filetype {
-        "html" => Ok(OutputFormat::Html),
-        "markdown" => Ok(OutputFormat::Markdown),
-        "man" => Ok(OutputFormat::Man),
-        "txt" => Ok(OutputFormat::Plain),
-        "org" => Ok(OutputFormat::Org),
-        "json" => Ok(OutputFormat::Json),
-        "latex" => Ok(OutputFormat::Latex),
-        "asciidoc" => Ok(OutputFormat::Asciidoc),
-        "pdf" => Ok(OutputFormat::Pdf),
-        _ => Err(Box::new(MetaError::Filetype)),
-    }?;
-
-    Ok((input, output))
-}
diff --git a/src/builder/variable.rs b/src/builder/variable.rs
deleted file mode 100644 (file)
index b419577..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-use crate::{log, MetaError, MetaFile, Scope};
-use eyre::Result;
-
-pub fn get_variable(key: &str, file: &MetaFile) -> Result<String> {
-    log!(
-        file.opts,
-        format!("substituting {key} in {}", file.path.display()),
-        2
-    );
-    let long_key = file.name()? + "." + key;
-    if let Some(val) = file.get_var(&Scope::into_local(&long_key)) {
-        Ok(val.clone())
-    } else if let Some(val) = file.get_var(&Scope::into_global(&long_key)) {
-        Ok(val.clone())
-    } else if let Some(val) = file.get_var(&Scope::into_local(key)) {
-        Ok(val.clone())
-    } else if let Some(val) = file.get_var(&Scope::into_global(key)) {
-        Ok(val.clone())
-    } else if file.opts.undefined || file.header.panic_undefined {
-        return Err(MetaError::UndefinedExpand {
-            val: key.to_string(),
-            path: file.name()?,
-        }
-        .into());
-    } else {
-        Ok(String::new())
-    }
-}
index 27f6513d3c1e67571bf39dab08c15140190d2d45..379d1421b6c627e1586bedb01883bed41047b80b 100644 (file)
@@ -2,13 +2,14 @@ extern crate pest;
 #[macro_use]
 extern crate pest_derive;
 
-mod builder;
 mod error;
 mod metafile;
 mod options;
 mod parser;
 
-pub use builder::*;
+#[cfg(test)]
+mod tests;
+
 pub use error::*;
 pub use metafile::*;
 pub use options::*;
@@ -18,6 +19,15 @@ use clap::Parser;
 use eyre::Result;
 use std::fs;
 
+#[macro_export]
+macro_rules! log {
+    ($opts:expr, $string:expr, $level:expr) => {
+        if $opts.verbose >= $level && !$opts.quiet {
+            println!("{}", $string);
+        }
+    };
+}
+
 pub fn get_opts() -> Result<Options> {
     let opts = Options::try_from(Opts::parse())?;
 
@@ -65,12 +75,12 @@ pub fn single_file(opts: &Options) -> Result<String> {
 
     let file = parse_string(source, opts)?;
 
-    Ok(build_metafile(&file)?)
+    Ok(file.construct()?)
 }
 
 pub fn new_site(opts: &Options) -> Result<()> {
     log!(
-        &opts,
+        opts,
         format!("building new site skeleton in {}", opts.root.display()),
         1
     );
index e8ab48fc04f0ff5988e10aff54cabe29628f4f55..c24654d1142e3dc5c40d8245350f94c3899ed350 100644 (file)
@@ -1,4 +1,4 @@
-use crate::{build_metafile, MetaError, Options};
+use crate::{MetaError, Options};
 use eyre::Result;
 use std::{fs, path::PathBuf};
 
@@ -61,7 +61,7 @@ impl<'a> DirNode<'a> {
     pub fn build_files(&mut self) -> Result<()> {
         for file in self.files.iter_mut() {
             file.merge(&self.global);
-            match build_metafile(file) {
+            match file.construct() {
                 Ok(str) => {
                     fs::write(file.dest()?, str)?;
                 }
index d7b7162a25ca6e296d6fabf023fb2099b94758a7..bfe86b40008fbaa95c3cf57794acac4aeba86c40 100644 (file)
@@ -1,5 +1,12 @@
-use crate::{parse_string, MetaError, Options};
+mod arrays;
+mod attributes;
+mod patterns;
+mod source;
+mod variables;
+
+use crate::{log, parse_string, MetaError, Options};
 use eyre::Result;
+use pandoc::{InputFormat, InputKind, OutputFormat, OutputKind, Pandoc};
 use std::{collections::HashMap, path::PathBuf};
 
 use super::*;
@@ -15,6 +22,18 @@ pub struct MetaFile<'a> {
 }
 
 impl<'a> MetaFile<'a> {
+    pub fn new(opts: &'a Options) -> Self {
+        Self {
+            opts,
+            path: PathBuf::new(),
+            header: Header::new(),
+            variables: HashMap::new(),
+            arrays: HashMap::new(),
+            patterns: HashMap::new(),
+            source: Vec::new(),
+        }
+    }
+
     pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
         let str = match std::fs::read_to_string(&path) {
             Ok(str) => str,
@@ -35,85 +54,26 @@ impl<'a> MetaFile<'a> {
         Ok(metafile)
     }
 
-    pub fn new(opts: &'a Options) -> Self {
-        Self {
-            opts,
-            path: PathBuf::new(),
-            header: Header::new(),
-            variables: HashMap::new(),
-            arrays: HashMap::new(),
-            patterns: HashMap::new(),
-            source: Vec::new(),
-        }
-    }
-
-    pub fn dest(&self) -> Result<PathBuf> {
-        let mut path = self
-            .opts
-            .build
-            .join(self.path.strip_prefix(&self.opts.source)?);
-        path.set_extension(&self.header.filetype);
-
-        Ok(path)
-    }
+    pub fn construct(&self) -> Result<String, Box<MetaError>> {
+        log!(self.opts, format!("building {}", self.path.display()), 1);
 
-    pub fn name(&self) -> Result<String> {
-        if self.path.starts_with(&self.opts.source) {
-            // in source dir, we want the file name without the '.meta' extension
-            let name: String = self
-                .path
-                .strip_prefix(&self.opts.source)?
-                .components()
-                .map(|x| {
-                    x.as_os_str()
-                        .to_string_lossy()
-                        .to_string()
-                        .replace(".meta", "")
-                })
-                .collect::<Vec<String>>()
-                .join(".");
-            Ok(name)
-        } else if self.path.starts_with(&self.opts.pattern) {
-            // in pattern dir, we want the parent dir
-            let name = self.path.strip_prefix(&self.opts.pattern)?;
-            let name = name
-                .parent()
-                .map(|s| s.to_string_lossy().to_string().replace('/', "."))
-                .unwrap_or_default();
-            Ok(name)
-        } else {
-            Err(MetaError::Name {
-                file: self.path.to_string_lossy().to_string(),
-            }
-            .into())
+        if self.header.blank {
+            return Ok(String::new());
+        } else if self.header.ignore {
+            return Err(Box::new(MetaError::Ignored));
         }
-    }
-
-    pub fn get_var(&self, key: &Scope) -> Option<&String> {
-        self.variables.get(key)
-    }
 
-    pub fn get_arr(&self, key: &Scope) -> Option<&[String]> {
-        self.arrays.get(key).map(|a| &a[..])
-    }
+        let html = self.to_html().map_err(MetaError::from)?;
 
-    pub fn get_pat(&self, key: &Scope) -> Option<&String> {
-        self.patterns.get(key)
-    }
+        let pattern = self.get_pattern("base").map_err(MetaError::from)?;
+        let mut base = crate::parse_string(pattern, self.opts).map_err(MetaError::from)?;
 
-    pub fn var_defined(&self, key: &str) -> bool {
-        self.variables.contains_key(&Scope::into_local(key))
-            || self.variables.contains_key(&Scope::into_global(key))
-    }
+        base.merge(self);
+        base.patterns.insert(Scope::into_global("SOURCE"), html);
 
-    pub fn arr_defined(&self, key: &str) -> bool {
-        self.arrays.contains_key(&Scope::into_local(key))
-            || self.arrays.contains_key(&Scope::into_global(key))
-    }
+        let output = base.get_source().map_err(MetaError::from)?;
 
-    pub fn pat_defined(&self, key: &str) -> bool {
-        self.patterns.contains_key(&Scope::into_local(key))
-            || self.patterns.contains_key(&Scope::into_global(key))
+        Ok(output)
     }
 
     pub fn merge(&mut self, other: &Self) {
diff --git a/src/metafile/file/arrays.rs b/src/metafile/file/arrays.rs
new file mode 100644 (file)
index 0000000..ab130ef
--- /dev/null
@@ -0,0 +1,88 @@
+use super::*;
+
+impl<'a> MetaFile<'a> {
+    pub fn expand_arrays(&self, input: String) -> Result<String> {
+        log!(
+            self.opts,
+            format!("expanding arrays in {}", self.path.display()),
+            2
+        );
+
+        let map: HashMap<String, &[String]> = self
+            .source
+            .iter()
+            // filter out arrays from source vec
+            .filter_map(|x| {
+                if let Src::Arr(array) = x {
+                    Some(array)
+                } else {
+                    None
+                }
+            })
+            // make a hash map of [keys in source] -> [defined arrays]
+            .map(|key| {
+                // concat array to pattern name to get key in HashMap
+                let name = self.name().unwrap_or_default();
+                let long_key = name + "." + key;
+
+                let value = if let Some(val) = self.arrays.get(&Scope::into_global(&long_key)) {
+                    &val[..]
+                } else if let Some(val) = self.arrays.get(&Scope::into_local(&long_key)) {
+                    &val[..]
+                } else if let Some(val) = self.arrays.get(&Scope::into_global(key)) {
+                    &val[..]
+                } else if let Some(val) = self.arrays.get(&Scope::into_local(key)) {
+                    &val[..]
+                } else if self.opts.undefined {
+                    panic!(
+                        "{}",
+                        MetaError::UndefinedExpand {
+                            val: key.to_string(),
+                            path: self.path.to_string_lossy().to_string(),
+                        }
+                    )
+                } else {
+                    &[]
+                };
+                (key.to_string(), value)
+            })
+            .collect();
+
+        // loop to duplicate the output template for each array member
+        let mut expanded = String::new();
+        let size = if self.header.equal_arrays {
+            let mut size = (0, false);
+            for val in map.values() {
+                if !size.1 {
+                    size = (val.len(), true);
+                } else if size.0 != val.len() {
+                    return Err(eyre::Error::from(MetaError::Array));
+                }
+            }
+            Ok::<usize, eyre::Error>(size.0)
+        } else {
+            let mut max = 0;
+            for val in map.values() {
+                if max < val.len() {
+                    max = val.len();
+                }
+            }
+            Ok(max)
+        }?;
+
+        for i in 0..size {
+            // get a fresh copy of the file
+            let mut str = input.clone();
+            // replace each key in the file
+            for (key, val) in map.iter() {
+                if let Some(value) = val.get(i) {
+                    str = str.replace(&format!("-{{{key}}}"), value);
+                }
+            }
+            // concatenate to final file
+            expanded.push_str(&str);
+        }
+
+        Ok(expanded)
+    }
+}
diff --git a/src/metafile/file/attributes.rs b/src/metafile/file/attributes.rs
new file mode 100644 (file)
index 0000000..2fecd5e
--- /dev/null
@@ -0,0 +1,45 @@
+use super::*;
+
+impl<'a> MetaFile<'a> {
+    pub fn dest(&self) -> Result<PathBuf> {
+        let mut path = self
+            .opts
+            .build
+            .join(self.path.strip_prefix(&self.opts.source)?);
+        path.set_extension(&self.header.filetype);
+
+        Ok(path)
+    }
+
+    pub fn name(&self) -> Result<String> {
+        if self.path.starts_with(&self.opts.source) {
+            // in source dir, we want the file name without the '.meta' extension
+            let name: String = self
+                .path
+                .strip_prefix(&self.opts.source)?
+                .components()
+                .map(|x| {
+                    x.as_os_str()
+                        .to_string_lossy()
+                        .to_string()
+                        .replace(".meta", "")
+                })
+                .collect::<Vec<String>>()
+                .join(".");
+            Ok(name)
+        } else if self.path.starts_with(&self.opts.pattern) {
+            // in pattern dir, we want the parent dir
+            let name = self.path.strip_prefix(&self.opts.pattern)?;
+            let name = name
+                .parent()
+                .map(|s| s.to_string_lossy().to_string().replace('/', "."))
+                .unwrap_or_default();
+            Ok(name)
+        } else {
+            Err(MetaError::Name {
+                file: self.path.to_string_lossy().to_string(),
+            }
+            .into())
+        }
+    }
+}
diff --git a/src/metafile/file/patterns.rs b/src/metafile/file/patterns.rs
new file mode 100644 (file)
index 0000000..b3a2871
--- /dev/null
@@ -0,0 +1,67 @@
+use super::*;
+
+impl<'a> MetaFile<'a> {
+    pub fn get_pattern(&self, key: &str) -> Result<String> {
+        log!(self.opts, format!("expanding {key}"), 2);
+        // SOURCE is already expanded in the initial construct() call
+        if key == "SOURCE" {
+            if let Some(source) = self.patterns.get(&Scope::into_global("SOURCE")) {
+                return Ok(source.to_string());
+            } else {
+                return Ok(String::new());
+            }
+        }
+
+        let mut filename = if let Some(name) = self.patterns.get(&Scope::into_local(key)) {
+            Ok(name.to_string())
+        } else if let Some(name) = self.patterns.get(&Scope::into_global(key)) {
+            Ok(name.to_string())
+        } else if self.header.panic_default {
+            Err(MetaError::UndefinedDefault {
+                pattern: key.to_string(),
+                path: self.path.to_string_lossy().to_string(),
+            })
+        } else {
+            // anything not defined should have a default.meta file to fall back to
+            Ok("default".to_string())
+        }?;
+
+        // BLANK returns nothing, so no more processing needs to be done
+        if filename == "BLANK" {
+            return Ok(String::default());
+        };
+
+        // DEFAULT override for patterns overriding globals
+        if filename == "DEFAULT" {
+            filename = "default".to_string();
+        }
+
+        // if we're building the base pattern we need to wait on
+        // parsing/expansion so we can build and convert source to html
+        // for the SOURCE pattern. we just want to return the string right now
+        if key == "base" {
+            let pattern_path = key.to_string() + "/" + &filename;
+            let mut path = self.opts.pattern.join(pattern_path);
+            path.set_extension("meta");
+
+            return match std::fs::read_to_string(&path) {
+                Ok(str) => Ok(str),
+                Err(_) => Err(MetaError::FileNotFound {
+                    path: path.to_string_lossy().to_string(),
+                }
+                .into()),
+            };
+        }
+
+        let pattern_path = key.replace('.', "/") + "/" + &filename;
+        let mut path = self.opts.pattern.join(pattern_path);
+        path.set_extension("meta");
+
+        let mut pattern = MetaFile::build(path, self.opts)?;
+
+        // copy over maps for expanding contained variables
+        pattern.merge(self);
+
+        pattern.get_source()
+    }
+}
diff --git a/src/metafile/file/source.rs b/src/metafile/file/source.rs
new file mode 100644 (file)
index 0000000..6ffad58
--- /dev/null
@@ -0,0 +1,109 @@
+use super::*;
+
+impl<'a> MetaFile<'a> {
+    pub fn to_html(&self) -> Result<String> {
+        let string = self.get_source()?;
+
+        if self.opts.no_pandoc || !self.header.pandoc || string.is_empty() {
+            return Ok(string);
+        }
+
+        let input: InputFormat;
+        let output: OutputFormat;
+        if let Ok(io) = self.pandoc_io() {
+            input = io.0;
+            output = io.1;
+        } else {
+            // don't run pandoc if a filetype that isn't supported gets requested
+            return Ok(string);
+        }
+
+        log!(self.opts, "calling pandoc", 3);
+
+        let mut pandoc = Pandoc::new();
+        pandoc
+            .set_input(InputKind::Pipe(string))
+            .set_output(OutputKind::Pipe)
+            .set_input_format(input, vec![])
+            .set_output_format(output, vec![]);
+
+        if let pandoc::PandocOutput::ToBuffer(s) = pandoc.execute()? {
+            Ok(s)
+        } else {
+            Err(MetaError::Pandoc { file: self.name()? }.into())
+        }
+    }
+
+    fn pandoc_io(&self) -> Result<(pandoc::InputFormat, pandoc::OutputFormat), Box<MetaError>> {
+        let mut source_type = "";
+        if !self.header.source.is_empty() {
+            source_type = &self.header.source;
+        } else if !self.opts.input.is_empty() {
+            source_type = &self.opts.input;
+        }
+
+        let input = match source_type {
+            "markdown" => Ok(InputFormat::Markdown),
+            "html" => Ok(InputFormat::Html),
+            "org" => Ok(InputFormat::Org),
+            "json" => Ok(InputFormat::Json),
+            "latex" => Ok(InputFormat::Latex),
+            _ => Err(Box::new(MetaError::Filetype)),
+        }?;
+
+        let mut filetype = "";
+        if !self.header.filetype.is_empty() {
+            filetype = &self.header.filetype;
+        } else if !self.opts.input.is_empty() {
+            filetype = &self.opts.output;
+        }
+
+        let output = match filetype {
+            "html" => Ok(OutputFormat::Html),
+            "markdown" => Ok(OutputFormat::Markdown),
+            "man" => Ok(OutputFormat::Man),
+            "txt" => Ok(OutputFormat::Plain),
+            "org" => Ok(OutputFormat::Org),
+            "json" => Ok(OutputFormat::Json),
+            "latex" => Ok(OutputFormat::Latex),
+            "asciidoc" => Ok(OutputFormat::Asciidoc),
+            "pdf" => Ok(OutputFormat::Pdf),
+            _ => Err(Box::new(MetaError::Filetype)),
+        }?;
+
+        Ok((input, output))
+    }
+
+    pub fn get_source(&self) -> Result<String> {
+        if self.header.blank {
+            return Ok(String::new());
+        }
+
+        let mut output = String::default();
+        let mut arrays = false;
+
+        for section in self.source.iter() {
+            let sec = match section {
+                // concatenate any char sequences
+                Src::Str(str) => str.to_string(),
+                // expand all variables and recursively expand patterns
+                Src::Var(key) => self.get_variable(key)?,
+                Src::Pat(key) => self.get_pattern(key)?,
+                Src::Arr(key) => {
+                    arrays = true;
+                    // comments have already been removed at this point,
+                    // so we use them to mark keys for array substitution
+                    format!("-{{{key}}}")
+                }
+            };
+
+            output.push_str(&sec);
+        }
+
+        if arrays {
+            self.expand_arrays(output)
+        } else {
+            Ok(output)
+        }
+    }
+}
diff --git a/src/metafile/file/variables.rs b/src/metafile/file/variables.rs
new file mode 100644 (file)
index 0000000..033f38c
--- /dev/null
@@ -0,0 +1,33 @@
+use super::*;
+
+impl<'a> MetaFile<'a> {
+    pub fn get_variable(&self, key: &str) -> Result<String> {
+        log!(
+            self.opts,
+            format!(
+                "substituting {} in {}",
+                key.to_string(),
+                self.path.display()
+            ),
+            2
+        );
+        let long_key = self.name()? + "." + &key.to_string();
+        if let Some(val) = self.variables.get(&Scope::into_local(&long_key)) {
+            Ok(val.clone())
+        } else if let Some(val) = self.variables.get(&Scope::into_global(&long_key)) {
+            Ok(val.clone())
+        } else if let Some(val) = self.variables.get(&Scope::into_local(key)) {
+            Ok(val.clone())
+        } else if let Some(val) = self.variables.get(&Scope::into_global(key)) {
+            Ok(val.clone())
+        } else if self.opts.undefined || self.header.panic_undefined {
+            return Err(MetaError::UndefinedExpand {
+                val: key.to_string(),
+                path: self.name()?,
+            }
+            .into());
+        } else {
+            Ok(String::new())
+        }
+    }
+}
index 3fbdd4f8277c6202b0883ba0ea297e6933603b06..b66300acd47e089542820bfdbab2a8a80971119f 100644 (file)
-pub mod arg_parser;
-pub mod opt_struct;
+use clap::Parser;
+use eyre::Result;
+use std::path::PathBuf;
 
-pub use arg_parser::*;
-pub use opt_struct::*;
+#[derive(Parser, Debug)]
+#[command(author = "Huck Boles")]
+#[command(version = "0.1.1")]
+#[command(about = "A customizable template driven static site generator")]
+#[command(long_about = None)]
+pub struct Opts {
+    /// root directory [current_dir]
+    #[arg(short, long, value_name = "ROOT_DIR")]
+    pub root: Option<String>,
+    /// source file directory [current_dir/source]
+    #[arg(short, long, value_name = "SOURCE_DIR")]
+    pub source: Option<String>,
+    /// build directory [current_dir/build]
+    #[arg(short, long, value_name = "BUILD_DIR")]
+    pub build: Option<String>,
+    /// pattern directory [current_dir/pattern]
+    #[arg(short, long, value_name = "PATTERN_DIR")]
+    pub pattern: Option<String>,
+    /// only build a single file
+    #[arg(short, long, value_name = "FILENAME")]
+    pub file: Option<String>,
+    /// create a new skeleton directory
+    #[arg(long, default_value_t = false)]
+    pub new: bool,
+    /// enable extra output. repeated flags give more info
+    #[arg(short, long, action = clap::ArgAction::Count)]
+    pub verbose: u8,
+    /// minimal output
+    #[arg(short, long, default_value_t = false)]
+    pub quiet: 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)]
+pub struct Options {
+    pub root: PathBuf,
+    pub source: PathBuf,
+    pub build: PathBuf,
+    pub pattern: PathBuf,
+    pub file: Option<PathBuf>,
+    pub input: String,
+    pub output: String,
+    pub verbose: u8,
+    pub quiet: bool,
+    pub force: bool,
+    pub undefined: bool,
+    pub clean: bool,
+    pub no_pandoc: bool,
+    pub new: bool,
+}
 
-#[macro_export]
-macro_rules! log {
-    ($opts:expr, $string:expr, $level:expr) => {
-        if $opts.verbose >= $level && !$opts.quiet {
-            println!("{}", $string);
+impl Options {
+    pub fn new() -> Self {
+        Self {
+            root: PathBuf::new(),
+            source: PathBuf::new(),
+            build: PathBuf::new(),
+            pattern: PathBuf::new(),
+            file: None,
+            input: String::default(),
+            output: String::default(),
+            verbose: 0,
+            quiet: false,
+            force: false,
+            undefined: false,
+            clean: false,
+            no_pandoc: false,
+            new: false,
         }
-    };
+    }
+}
+
+impl TryFrom<crate::Opts> for Options {
+    type Error = eyre::Error;
+    fn try_from(value: crate::Opts) -> Result<Self, Self::Error> {
+        let mut opts = Options::new();
+
+        opts.verbose = value.verbose;
+        opts.quiet = value.quiet;
+        opts.force = value.force;
+        opts.undefined = value.undefined;
+        opts.clean = value.clean;
+        opts.no_pandoc = value.no_pandoc;
+        opts.new = value.new;
+
+        if let Some(root) = value.root.as_deref() {
+            opts.root = PathBuf::from(root).canonicalize()?;
+        } else {
+            opts.root = std::env::current_dir()?;
+        }
+
+        if let Some(source) = value.source.as_deref() {
+            opts.source = PathBuf::from(source).canonicalize()?;
+        } else {
+            opts.source = opts.root.join("source");
+        }
+
+        if let Some(build) = value.build.as_deref() {
+            opts.build = PathBuf::from(build).canonicalize()?;
+        } else {
+            opts.build = opts.root.join("build");
+        }
+
+        if let Some(pattern) = value.pattern.as_deref() {
+            opts.pattern = PathBuf::from(pattern).canonicalize()?;
+        } else {
+            opts.pattern = opts.root.join("pattern");
+        }
+
+        if let Some(file) = value.file.as_deref() {
+            opts.file = Some(PathBuf::from(file).canonicalize()?);
+        }
+
+        if let Some(input) = value.input {
+            opts.input = input;
+        } else {
+            opts.input = String::from("html");
+        }
+
+        if let Some(output) = value.output {
+            opts.output = output;
+        } else {
+            opts.output = String::from("markdown");
+        }
+
+        Ok(opts)
+    }
 }
diff --git a/src/options/arg_parser.rs b/src/options/arg_parser.rs
deleted file mode 100644 (file)
index 3093c2e..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-use clap::Parser;
-
-#[derive(Parser, Debug)]
-#[command(author = "Huck Boles")]
-#[command(version = "0.1.1")]
-#[command(about = "A customizable template driven static site generator")]
-#[command(long_about = None)]
-pub struct Opts {
-    /// root directory [current_dir]
-    #[arg(short, long, value_name = "ROOT_DIR")]
-    pub root: Option<String>,
-    /// source file directory [current_dir/source]
-    #[arg(short, long, value_name = "SOURCE_DIR")]
-    pub source: Option<String>,
-    /// build directory [current_dir/build]
-    #[arg(short, long, value_name = "BUILD_DIR")]
-    pub build: Option<String>,
-    /// pattern directory [current_dir/pattern]
-    #[arg(short, long, value_name = "PATTERN_DIR")]
-    pub pattern: Option<String>,
-    /// only build a single file
-    #[arg(short, long, value_name = "FILENAME")]
-    pub file: Option<String>,
-    /// create a new skeleton directory
-    #[arg(long, default_value_t = false)]
-    pub new: bool,
-    /// enable extra output. repeated flags give more info
-    #[arg(short, long, action = clap::ArgAction::Count)]
-    pub verbose: u8,
-    /// minimal output
-    #[arg(short, long, default_value_t = false)]
-    pub quiet: 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>,
-}
diff --git a/src/options/opt_struct.rs b/src/options/opt_struct.rs
deleted file mode 100644 (file)
index 0f6da5a..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-use eyre::Result;
-use std::path::PathBuf;
-
-#[derive(Debug, Clone, Default)]
-pub struct Options {
-    pub root: PathBuf,
-    pub source: PathBuf,
-    pub build: PathBuf,
-    pub pattern: PathBuf,
-    pub file: Option<PathBuf>,
-    pub input: String,
-    pub output: String,
-    pub verbose: u8,
-    pub quiet: bool,
-    pub force: bool,
-    pub undefined: bool,
-    pub clean: bool,
-    pub no_pandoc: bool,
-    pub new: bool,
-}
-
-impl Options {
-    pub fn new() -> Self {
-        Self {
-            root: PathBuf::new(),
-            source: PathBuf::new(),
-            build: PathBuf::new(),
-            pattern: PathBuf::new(),
-            file: None,
-            input: String::default(),
-            output: String::default(),
-            verbose: 0,
-            quiet: false,
-            force: false,
-            undefined: false,
-            clean: false,
-            no_pandoc: false,
-            new: false,
-        }
-    }
-}
-
-impl TryFrom<crate::Opts> for Options {
-    type Error = eyre::Error;
-    fn try_from(value: crate::Opts) -> Result<Self, Self::Error> {
-        let mut opts = Options::new();
-
-        opts.verbose = value.verbose;
-        opts.quiet = value.quiet;
-        opts.force = value.force;
-        opts.undefined = value.undefined;
-        opts.clean = value.clean;
-        opts.no_pandoc = value.no_pandoc;
-        opts.new = value.new;
-
-        if let Some(root) = value.root.as_deref() {
-            opts.root = PathBuf::from(root).canonicalize()?;
-        } else {
-            opts.root = std::env::current_dir()?;
-        }
-
-        if let Some(source) = value.source.as_deref() {
-            opts.source = PathBuf::from(source).canonicalize()?;
-        } else {
-            opts.source = opts.root.join("source");
-        }
-
-        if let Some(build) = value.build.as_deref() {
-            opts.build = PathBuf::from(build).canonicalize()?;
-        } else {
-            opts.build = opts.root.join("build");
-        }
-
-        if let Some(pattern) = value.pattern.as_deref() {
-            opts.pattern = PathBuf::from(pattern).canonicalize()?;
-        } else {
-            opts.pattern = opts.root.join("pattern");
-        }
-
-        if let Some(file) = value.file.as_deref() {
-            opts.file = Some(PathBuf::from(file).canonicalize()?);
-        }
-
-        if let Some(input) = value.input {
-            opts.input = input;
-        } else {
-            opts.input = String::from("html");
-        }
-
-        if let Some(output) = value.output {
-            opts.output = output;
-        } else {
-            opts.output = String::from("markdown");
-        }
-
-        Ok(opts)
-    }
-}
similarity index 97%
rename from src/builder/tests.rs
rename to src/tests.rs
index 1d7d2ed37fe0ea784d87e36eb2104c7822b46a6a..179dc700513a4927cb5cae2a66fb3a32231ac336 100644 (file)
@@ -1,4 +1,4 @@
-use crate::{build_metafile, MetaFile, Options};
+use crate::{MetaFile, Options};
 use eyre::{Result, WrapErr};
 use std::{error::Error, fs, path::PathBuf};
 
@@ -18,7 +18,7 @@ fn unit_test(test: (&str, &str)) -> Result<()> {
     file_path.set_extension("meta");
     let file = MetaFile::build(file_path, &opts)?;
 
-    let output = build_metafile(&file).wrap_err_with(|| test.0.to_string())?;
+    let output = file.construct().wrap_err_with(|| test.0.to_string())?;
 
     if output == test.1 {
         Ok(())