-use std::collections::HashMap;
+use std::{collections::HashMap, path::PathBuf};
 
 #[derive(Debug, Default, Clone)]
 pub struct MetaFile<'a> {
             source: Vec::new(),
         }
     }
+
+    pub fn get_var(&self, key: &str) -> Option<&str> {
+        self.variables.get(key).copied()
+    }
+
+    pub fn get_arr(&self, key: &str) -> Option<&[&str]> {
+        self.arrays.get(key).map(|val| &val[..])
+    }
+
+    pub fn get_pat(&self, key: &str) -> Option<&str> {
+        self.patterns.get(key).copied()
+    }
 }
 
+#[macro_export]
+macro_rules! source (
+    (var($s:expr)) => { Source::Sub(Substitution::Variable($s))};
+    (arr($s:expr)) => { Source::Sub(Substitution::Array($s))};
+    (pat($s:expr)) => { Source::Sub(Substitution::Pattern($s))};
+    ($s:expr) => { Source::Str($s)};
+);
+
 #[derive(Debug, Clone, PartialEq)]
 pub enum Source<'a> {
     Str(&'a str),
     Array(&'a str),
     Pattern(&'a str),
 }
+
+#[derive(Debug, Clone)]
+pub struct RootDirs {
+    pub source: PathBuf,
+    pub build: PathBuf,
+    pub pattern: PathBuf,
+}
 
-use crate::{MetaFile, Source, Substitution};
+use crate::{source, MetaFile, Source, Substitution};
 use color_eyre::Result;
 use pest::{
     iterators::{Pair, Pairs},
     map
 }
 
-fn parse_array_defs(pairs: Pairs<Rule>) -> HashMap<&'_ str, Vec<&'_ str>> {
+fn parse_array_defs(pairs: Pairs<Rule>) -> HashMap<&str, Vec<&str>> {
     let mut map = HashMap::new();
     for pair in pairs {
         if Rule::assign == pair.as_rule() {
 }
 
 fn parse_source(pairs: Pairs<Rule>) -> Vec<Source> {
-    let mut vec: Vec<Source> = Vec::new();
-
-    macro_rules! push_src (
-        (var($s:expr)) => { vec.push(Source::Sub(Substitution::Variable($s))) };
-        (arr($s:expr)) => { vec.push(Source::Sub(Substitution::Array($s))) };
-        (pat($s:expr)) => { vec.push(Source::Sub(Substitution::Pattern($s))) };
-        ($s:expr) => { vec.push(Source::Str($s)) };
-    );
-
+    let mut vec = Vec::new();
     for pair in pairs {
         match pair.as_rule() {
-            Rule::var_sub => push_src!(var(parse_substitution(pair))),
-            Rule::arr_sub => push_src!(arr(parse_substitution(pair))),
-            Rule::pat_sub => push_src!(pat(parse_substitution(pair))),
-            Rule::char_seq => push_src!(pair.as_str()),
+            Rule::var_sub => vec.push(source!(var(parse_sub(pair)))),
+            Rule::arr_sub => vec.push(source!(arr(parse_sub(pair)))),
+            Rule::pat_sub => vec.push(source!(pat(parse_sub(pair)))),
+            Rule::char_seq => vec.push(source!(pair.as_str())),
             // anything that isn't a substitution is a char_seq inside source
             _ => unreachable!(),
         }
     vec
 }
 
-fn parse_substitution(pair: Pair<Rule>) -> &'_ str {
+fn parse_sub(pair: Pair<Rule>) -> &'_ str {
     match pair.as_rule() {
         Rule::var_sub | Rule::arr_sub | Rule::pat_sub => {
             let str = pair.as_str();
             // return the value as the inner string for substitution
             // all substitutions have the format of
             //      *{ ... }
-            // we return everything except the first
-            // two characters: (sigil and preceding brace)
-            // and the trailing brace
+            // we return everything except:
+            //      first two characters: (sigil and preceding brace)
+            //      and the trailing brace
             &str[2..str.len() - 1]
         }
         // this function only gets called to parse substituiton patterns
+        // so anything else should never be called
         _ => unreachable!(),
     }
 }
             if tmp == "BLANK" || tmp == "DEFAULT" {
                 return ("", "");
             }
-            // remove surrounding quotes from values
+            // remove surrounding quotes from values by returning
+            // everything except first and last characters
+            // a string is defined as " ... " or ' ... '
+            // so it's safe to strip these characters
             val = &tmp[1..tmp.len() - 1];
         }
     }
     (key, val)
 }
 
-fn parse_assign_array(pair: Pair<Rule>) -> (&'_ str, Vec<&'_ str>) {
+fn parse_assign_array(pair: Pair<Rule>) -> (&str, Vec<&str>) {
     let mut key = "";
-    let mut val: Vec<&str> = Vec::default();
+    let mut val = Vec::default();
     for pair in pair.into_inner() {
         if Rule::key == pair.as_rule() {
             key = pair.as_str();
     (key, val)
 }
 
-fn parse_array(pairs: Pairs<Rule>) -> Vec<&'_ str> {
+fn parse_array(pairs: Pairs<Rule>) -> Vec<&str> {
     let mut vec: Vec<&str> = Vec::default();
 
     for pair in pairs {
 
--- /dev/null
+use crate::parse_file;
+use color_eyre::Result;
+
+static SOURCE: &str = include_str!("test_source.meta");
+
+#[test]
+fn test_metafile_gets() -> Result<()> {
+    let source = parse_file(SOURCE)?;
+
+    assert_eq!(source.get_var("var").unwrap(), "good");
+    assert_eq!(source.get_var("single").unwrap(), "quotes");
+    assert_eq!(source.get_var("blank"), None);
+    assert_eq!(source.get_var("not_defined"), None);
+
+    assert_eq!(source.get_arr("sub.array").unwrap(), ["sub", "value"]);
+    assert_eq!(source.get_arr("arr").unwrap(), ["split", "up", "values"]);
+    assert_eq!(
+        source.get_arr("with_spaces").unwrap(),
+        ["stuff", "with", "spaces"]
+    );
+    assert_eq!(source.get_arr("not_defined"), None);
+
+    assert_eq!(source.get_pat("pat").unwrap(), "pattern");
+    assert_eq!(source.get_pat("pat.sub_pat"), None);
+    assert_eq!(source.get_pat("blank_pat"), None);
+    assert_eq!(source.get_pat("not_defined"), None);
+
+    Ok(())
+}