-use crate::{log, parse_file, MetaFile, Options, Src, Sub};
+use crate::{parse_file, MetaFile, Src, Sub};
 use color_eyre::{eyre::bail, Result};
 use pandoc::{InputFormat, InputKind, OutputFormat, OutputKind, Pandoc, PandocOutput};
-use std::{
-    collections::HashMap,
-    fs,
-    path::{Path, PathBuf},
-};
+use std::{collections::HashMap, fs};
 
 pub fn build_metafile(file: &MetaFile) -> Result<String> {
-    let html = get_source_html(file, file.opts)?;
+    if file.header.blank {
+        return Ok(String::new());
+    }
+
+    let html = get_source_html(file)?;
 
     let pattern = get_pattern("base", file)?;
     let mut base = parse_file(pattern, file.opts)?;
     Ok(output)
 }
 
-pub fn write_file(path: &Path, html: String, opts: &Options) -> Result<()> {
-    let dest = find_dest(path, opts)?;
-    // want newline to end file
-    fs::write(dest, html)?;
-    Ok(())
-}
-
 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;
 
     }
 
     if arrays {
-        log!(file.opts, "\t\t\texpanding arrays", 4);
         expand_arrays(output, file)
     } else {
         Ok(output)
     }
 }
 
-fn get_source_html(file: &MetaFile, opts: &Options) -> Result<String> {
-    log!(opts, "\tbuilding source", 2);
-    let file = metafile_to_string(file)?;
+fn get_source_html(file: &MetaFile) -> Result<String> {
+    let string = metafile_to_string(file)?;
 
-    if opts.no_pandoc {
-        return Ok(file);
+    if file.opts.no_pandoc || !file.header.pandoc {
+        return Ok(string);
     }
 
-    log!(opts, "\t\tcalling pandoc", 3);
     let mut pandoc = Pandoc::new();
     pandoc
-        .set_input(InputKind::Pipe(file))
+        .set_input(InputKind::Pipe(string))
         .set_output(OutputKind::Pipe)
         .set_input_format(InputFormat::Markdown, vec![])
         .set_output_format(OutputFormat::Html, vec![]);
 
 fn get_pattern(key: &str, file: &MetaFile) -> Result<String> {
     // SOURCE is already expanded in the initial build_metafile() call
-    // we just need to return that
     if key == "SOURCE" {
-        log!(file.opts, "\t\t\treturning SOURCE", 4);
         if let Some(source) = file.patterns.get("SOURCE") {
             return Ok(source.to_string());
         }
     }
 
-    log!(file.opts, format!("\t\tpattern: {}", key), 3);
-    // anything not defined should have a default.meta file to fall back to
     let mut filename: String;
     if let Some(name) = file.get_pat(key) {
         filename = name.to_string();
     } else {
+        // anything not defined should have a default.meta file to fall back to
         filename = "default".to_string()
     }
 
+    // BLANK returns nothing, so no more processing needs to be done
+    if filename == "BLANK" {
+        return Ok(String::from(""));
+    };
+
+    // DEFAULT override for patterns overriding globals
+    if filename == "DEFAULT" {
+        filename = "default".to_string();
+    }
+
     // if we're building from base pattern we need to wait on
     // parsing/expansion so we can build and convert source to html
     // we just want to return the string right now
     if key == "base" {
-        log!(file.opts, "\t\t\treturning base", 4);
         let pattern_path = key.to_string() + "/" + &filename;
         let mut path = file.opts.pattern.join(pattern_path);
         path.set_extension("meta");
 
-        let base = fs::read_to_string(&path)?;
-        return Ok(base);
-    }
-
-    // BLANK returns nothing, so no more processing needs to be done
-    if filename == "BLANK" {
-        return Ok(String::from(""));
-    };
-
-    // DEFAULT override for patterns defined higher in chain
-    if filename == "DEFAULT" {
-        filename = "default".to_string();
+        return match fs::read_to_string(&path) {
+            Ok(str) => Ok(str),
+            Err(_) => bail!("could not find base file {}", path.display()),
+        };
     }
 
     let pattern_path = key.replace('.', "/") + "/" + &filename;
     }
 }
 
-fn find_dest(path: &Path, opts: &Options) -> Result<PathBuf> {
-    let path = path.canonicalize()?;
-
-    let mut path = opts.build.join(path.strip_prefix(&opts.source)?);
-    path.set_extension("html");
-
-    Ok(path)
-}
-
 fn expand_arrays(input: String, file: &MetaFile) -> Result<String> {
     let map: HashMap<String, &[String]> = file
         .source
 #[cfg(test)]
 mod tests {
     use super::*;
-    fn build_options() -> Result<Options> {
-        let dir = PathBuf::from("files/site").canonicalize()?;
+    use crate::Options;
+    use std::path::PathBuf;
+
+    fn unit_test(test: &str, result: &str) -> Result<()> {
+        let dir = PathBuf::from("files/test_site").canonicalize()?;
 
         let mut opts = Options::new();
         opts.root = dir.clone();
         opts.pattern = dir.join("pattern");
         opts.clean = true;
 
-        Ok(opts)
+        let test_dir = opts.source.join("unit_tests");
+        let mut file_path = test_dir.join(test);
+        file_path.set_extension("meta");
+        let file = MetaFile::build(file_path, &opts)?;
+
+        let output = build_metafile(&file)?;
+
+        assert_eq!(output, result);
+
+        Ok(())
     }
 
     #[test]
     fn test_find_dest() -> Result<()> {
-        let opts = build_options()?;
-        let path = opts.source.join("dir1/dir.meta");
-        assert_eq!(find_dest(&path, &opts)?, opts.build.join("dir1/dir.html"));
+        unit_test("find_dest", "<html>\n\n</html>\n")
+    }
+
+    #[test]
+    fn test_blank() -> Result<()> {
+        unit_test("blank/blank_pattern", "")?;
+        unit_test("blank/blank_variable", "<html>\n</html>\n")?;
+        unit_test("blank/blank_array", "<html>\n</html>\n")?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_comment() -> Result<()> {
+        unit_test("blank/comment", "<html>\n\n</html>\n")?;
+        unit_test(
+            "blank/inline_comment",
+            "<html>\n<p>inline comment</p>\n</html>\n",
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_expand() -> Result<()> {
+        unit_test(
+            "expand/variable_in_source",
+            "<html>\n<p>GOOD</p>\n</html>\n",
+        )?;
+        unit_test("expand/variable_in_pattern", "<html>\nGOOD</html>\n")?;
+        unit_test("expand/array_in_source", "<html>\n<p>12345</p>\n</html>\n")?;
+        unit_test("expand/array_in_pattern", "<html>\n12345</html>\n")?;
+        unit_test("expand/pattern_in_source", "<p>GOOD</p>\n")?;
+        unit_test("expand/pattern_in_pattern", "<html>\nGOOD\nGOOD\n</html>\n")?;
         Ok(())
     }
 
     #[test]
-    fn test_metafile_to_string() -> Result<()> {
-        let opts = build_options()?;
-        let path = opts.source.join("dir1/sub_dir1/deep2/deep.meta");
-        let expanded = "<html><body>GOOD</body></html>";
-        assert_eq!(build_metafile(&MetaFile::build(path, &opts)?)?, expanded);
+    fn test_override() -> Result<()> {
+        unit_test("override/variable", "<html>\n<p>GOOD</p>\n</html>\n")?;
+        unit_test("override/pattern", "<html>\nGOOD\nGOOD\n</html>\n")?;
         Ok(())
     }
 
     #[test]
-    fn test_get_pattern() -> Result<()> {
-        let opts = build_options()?;
-        let file = MetaFile::new(&opts);
-        let pat = get_pattern("header", &file)?;
-        assert_eq!(pat, "<header>HEADER</header>");
+    #[ignore = "fix global variables"]
+    fn test_global() -> Result<()> {
+        unit_test("global/variable", "GOODGOOD\n")?;
+        unit_test("global/pattern", "GOODGOOD")?;
         Ok(())
     }
 }
 
-use crate::{build_metafile, parse_file, write_file, Options};
+use crate::{build_metafile, parse_file, Options};
 use color_eyre::{
     eyre::{bail, eyre},
     Result,
 use std::collections::HashMap;
 use std::{fs, path::PathBuf};
 
+#[derive(Debug, Clone, Default)]
+pub struct Header {
+    pub blank: bool,
+    pub panic_default: bool,
+    pub panic_undefined: bool,
+    pub filetype: String,
+    pub pandoc: bool,
+}
+
+impl Header {
+    pub fn new() -> Self {
+        Self {
+            blank: false,
+            panic_default: false,
+            panic_undefined: false,
+            filetype: String::from("html"),
+            pandoc: true,
+        }
+    }
+}
+
+impl From<HashMap<String, String>> for Header {
+    fn from(value: HashMap<String, String>) -> Self {
+        let mut header = Header::new();
+        for (key, val) in value.iter() {
+            match &key[..] {
+                "blank" => header.blank = val == "true",
+                "panic_default" => header.panic_default = val == "true",
+                "panic_undefined" => header.panic_undefined = val == "true",
+                "pandoc" => header.pandoc = val == "true",
+                "filetype" => header.filetype = val.to_string(),
+                _ => continue,
+            }
+        }
+        header
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct MetaFile<'a> {
     pub opts: &'a Options,
     pub path: PathBuf,
-    pub header: HashMap<String, String>,
+    pub header: Header,
     pub variables: HashMap<String, String>,
     pub arrays: HashMap<String, Vec<String>>,
     pub patterns: HashMap<String, String>,
         Self {
             opts,
             path: PathBuf::new(),
-            header: HashMap::new(),
+            header: Header::new(),
             variables: HashMap::new(),
             arrays: HashMap::new(),
             patterns: HashMap::new(),
         }
     }
 
+    pub fn dest(&self) -> Result<PathBuf> {
+        let mut path = self
+            .opts
+            .build
+            .join(self.path.strip_prefix(&self.opts.source)?);
+        path.set_extension("html");
+
+        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
                 .unwrap_or_default();
             Ok(name)
         } else {
-            color_eyre::eyre::bail!("could not get name from: {:#?}", self);
+            color_eyre::eyre::bail!("could not get name from: {}", self.path.display());
         }
     }
 
             file.merge(&self.global);
             match build_metafile(file) {
                 Ok(str) => {
-                    write_file(&file.path, str, file.opts)?;
+                    fs::write(file.dest()?, str)?;
                 }
                 Err(e) => {
                     if self.opts.force {