)?)
 }
 
-fn metafile_to_string(file: &MetaFile, dirs: &RootDirs, name: Option<&str>) -> Result<String> {
+pub fn metafile_to_string(file: &MetaFile, dirs: &RootDirs, name: Option<&str>) -> Result<String> {
     let mut output = String::default();
 
     for section in file.source.iter() {
 
     Ok(parse_pair(meta_source))
 }
 
-pub fn parse_pair(pair: Pair<Rule>) -> MetaFile {
+fn parse_pair(pair: Pair<Rule>) -> MetaFile {
     let mut meta_file = MetaFile::new();
 
     if Rule::file == pair.as_rule() {
                 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::EOI => (),
+                // 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!(),
 fn parse_assign(pair: Pair<Rule>) -> (&'_ str, &'_ str) {
     let mut key = "";
     let mut val = "";
+
     for pair in pair.into_inner() {
         if Rule::key == pair.as_rule() {
             key = pair.as_str();
         if Rule::value == pair.as_rule() {
             let tmp = pair.as_str();
             // blank and default shoud be handled by whoever is getting the value
-            // set it to empty strings do remove it from the HashMap
+            // set it to empty strings to remove it from the HashMap
             if tmp == "BLANK" || tmp == "DEFAULT" {
                 return ("", "");
             }
 fn parse_assign_array(pair: Pair<Rule>) -> (&str, Vec<&str>) {
     let mut key = "";
     let mut val = Vec::default();
+
     for pair in pair.into_inner() {
         if Rule::key == pair.as_rule() {
             key = pair.as_str();
         if Rule::string == pair.as_rule() {
             let tmp = pair.as_str();
             // remove surrounding quotes from values
+            // see parse_assign() for reasoning
             let val = &tmp[1..tmp.len() - 1];
             vec.push(val);
         }
     }
+
     vec
 }
 
--- /dev/null
+${
+    var1 = "val1"
+    var2 = BLANK
+    var3 = 'value with spaces'
+}
+
+@{
+    arr1 = ['val1']
+    arr2 = []
+    pat1.arr = ["value", "value with spaces"]
+}
+
+&{
+    pat1 = "pattern"
+    pat2.sub_pat = 'sub_pattern'
+    pat2.default = DEFAULT
+    pat2.blank = BLANK
+}
+
+TESTS:
+
+var1 [val1]: ${var1}
+var2 [BLANK]: ${var2}
+var3 [value with spaces]: ${var3}
+
+arr1 [val1]: @{arr1}
+arr3 [BLANK]: @{arr2}
+
+Pattern subs:
+
+pat1 [with array]: &{pat1}
+pat2: 
+    .sub_pat: &{pat2.sub_pat}
+    .default: &{pat2.default}
+    .blank: &{pat2.blank}
+
+This comment should not be rendered: -{arr1}
 
-use crate::parse_file;
+use crate::{metafile_to_string, parse_file, RootDirs};
 use color_eyre::Result;
+use std::path::PathBuf;
 
 static SOURCE: &str = include_str!("test_source.meta");
+static PRE_EXPAND: &str = include_str!("test_expand.meta");
+static POST_EXPAND: &str = include_str!("test_expanded");
 
 #[test]
 fn test_metafile_gets() -> Result<()> {
 
     Ok(())
 }
+
+#[test]
+fn test_metafile_to_str() -> Result<()> {
+    let metafile = parse_file(PRE_EXPAND)?;
+    let dirs = RootDirs {
+        source: PathBuf::new(),
+        build: PathBuf::new(),
+        pattern: PathBuf::new(),
+    };
+
+    let file = metafile_to_string(&metafile, &dirs, None)?;
+
+    assert_eq!(file, "");
+
+    Ok(())
+}
 
 static PATTERN: &str = include_str!("test_pattern.meta");
 
 #[test]
-fn build_meta_file() -> Result<()> {
+fn parse_meta_file() -> Result<()> {
     let source = parse_file(SOURCE)?;
 
     assert_eq!(source.variables.get("var").unwrap(), &"good");
 }
 
 #[test]
-fn build_pattern_file() -> Result<()> {
+fn parse_pattern_file() -> Result<()> {
     let mut pattern_src = parse_file(PATTERN)?.source.into_iter();
 
     pattern_src.next();