#[cfg(test)]
 mod tests;
 
-use crate::{MetaError, MetaFile, Scope};
+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 {
 
-use crate::{MetaError, MetaFile, Scope, Src};
+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()
 
-use crate::{MetaError, MetaFile, Scope};
+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")) {
 
-use crate::{MetaError, MetaFile, Src};
+use crate::{log, MetaError, MetaFile, Src};
 use eyre::Result;
 use pandoc::{InputFormat, InputKind, OutputFormat, OutputKind, Pandoc};
 
         return Ok(string);
     }
 
+    log!(file.opts, "calling pandoc", 3);
+
     let mut pandoc = Pandoc::new();
     pandoc
         .set_input(InputKind::Pipe(string))
 
-use crate::{MetaError, MetaFile, Scope};
+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())
 
 pub fn get_opts() -> Result<Options> {
     let opts = Options::try_from(Opts::parse())?;
 
+    log!(&opts, "getting options", 3);
+
     let exists = opts.build.exists();
     if exists && opts.clean {
         fs::remove_dir_all(&opts.build)?;
 }
 
 pub fn build_site(opts: &Options) -> Result<()> {
-    let mut source = DirNode::build(opts.source.clone(), opts)?;
+    log!(
+        opts,
+        format!("building site in {}", opts.build.display()),
+        1
+    );
 
+    let mut source = DirNode::build(opts.source.clone(), opts)?;
     let global_init = MetaFile::new(opts);
 
     source.map(&global_init)?;
-
     source.build_dir()
 }
 
 pub fn single_file(opts: &Options) -> Result<String> {
     let path = opts.file.as_ref().ok_or(MetaError::Unknown)?;
+    log!(
+        opts,
+        format!("building file {}", opts.file.as_ref().unwrap().display()),
+        1
+    );
+
     let source = match fs::read_to_string(path) {
         Ok(str) => Ok(str),
         Err(_) => Err(eyre::Error::from(MetaError::FileNotFound {
 }
 
 pub fn new_site(opts: &Options) -> Result<()> {
+    log!(
+        &opts,
+        format!("building new site skeleton in {}", opts.root.display()),
+        1
+    );
     macro_rules! exist_or_build(
         ($p:expr) => {
             if !$p.exists() {
 
 #[cfg(test)]
 mod tests;
 
-use crate::{Header, MetaFile, Options};
+use crate::{log, Header, MetaError, MetaFile, Options};
 use eyre::Result;
 use pest::{
     iterators::{Pair, Pairs},
 pub struct MetaParser;
 
 pub fn parse_string(file: String, opts: &Options) -> Result<MetaFile> {
-    let meta_source = MetaParser::parse(Rule::file, &file)?.next().unwrap();
+    log!(opts, "parsing file", 3);
 
-    let metafile = parse_file(meta_source, opts);
-    Ok(metafile)
-}
+    let pair = MetaParser::parse(Rule::file, &file)?.next().unwrap();
 
-fn parse_file<'a>(pair: Pair<Rule>, opts: &'a Options) -> MetaFile<'a> {
     let mut meta_file = MetaFile::new(opts);
 
     if Rule::file == pair.as_rule() {
                 Rule::header => {
                     meta_file.header = Header::from(parse_header_defs(pair.into_inner()))
                 }
-                Rule::var_def => meta_file.variables = parse_defs(pair.into_inner()),
-                Rule::arr_def => meta_file.arrays = parse_array_defs(pair.into_inner()),
-                Rule::pat_def => meta_file.patterns = parse_defs(pair.into_inner()),
+                Rule::var_def => meta_file.variables = parse_defs(pair.into_inner())?,
+                Rule::arr_def => meta_file.arrays = parse_array_defs(pair.into_inner())?,
+                Rule::pat_def => meta_file.patterns = parse_defs(pair.into_inner())?,
                 // do nothing on end of file
                 Rule::EOI => continue,
                 // anything else is either hidden or children of previous nodes and will be dealt with
                 // in respective parse functions
-                _ => unreachable!(),
+                _ => {
+                    return Err(MetaError::UnreachableRule {
+                        input: pair.to_string(),
+                    }
+                    .into())
+                }
             }
         }
     }
 
-    meta_file
+    Ok(meta_file)
 }