]> git.huck.website - metaforge.git/commitdiff
restructure
authorHuck Boles <huck@huck.website>
Wed, 17 May 2023 16:05:03 +0000 (11:05 -0500)
committerHuck Boles <huck@huck.website>
Wed, 17 May 2023 16:05:03 +0000 (11:05 -0500)
21 files changed:
src/builder.rs
src/builder/array.rs [new file with mode: 0644]
src/builder/pattern.rs [new file with mode: 0644]
src/builder/source.rs [new file with mode: 0644]
src/builder/tests.rs [new file with mode: 0644]
src/builder/variable.rs [new file with mode: 0644]
src/metafile.rs
src/metafile/dir.rs [new file with mode: 0644]
src/metafile/file.rs [new file with mode: 0644]
src/metafile/header.rs [new file with mode: 0644]
src/metafile/tests.rs [new file with mode: 0644]
src/options.rs
src/options/arg_parser.rs [new file with mode: 0644]
src/options/opt_struct.rs [new file with mode: 0644]
src/parser.rs
src/parser/array.rs [new file with mode: 0644]
src/parser/def_block.rs [new file with mode: 0644]
src/parser/header.rs [new file with mode: 0644]
src/parser/meta.pest [moved from src/meta.pest with 100% similarity]
src/parser/source.rs [new file with mode: 0644]
src/parser/tests.rs [new file with mode: 0644]

index 6e93bb31f787b745315d1b136d4ebe7a8238fcbf..79dfe27656907b8068fd45394200c53684dd6c16 100644 (file)
@@ -1,6 +1,16 @@
-use crate::{MetaFile, Src, Sub};
-use color_eyre::{eyre::bail, Result};
-use std::{collections::HashMap, fs};
+use crate::MetaFile;
+use color_eyre::Result;
+
+mod array;
+mod pattern;
+mod source;
+mod variable;
+
+use pattern::*;
+use source::*;
+
+#[cfg(test)]
+mod tests;
 
 pub fn build_metafile(file: &MetaFile) -> Result<String> {
     if file.header.blank {
@@ -10,7 +20,7 @@ pub fn build_metafile(file: &MetaFile) -> Result<String> {
     let html = get_source_html(file)?;
 
     let pattern = get_pattern("base", file)?;
-    let mut base = crate::parse_file(pattern, file.opts)?;
+    let mut base = crate::parse_string(pattern, file.opts)?;
 
     base.merge(file);
     base.patterns.insert("SOURCE".to_string(), html);
@@ -19,319 +29,3 @@ pub fn build_metafile(file: &MetaFile) -> Result<String> {
 
     Ok(output)
 }
-
-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() {
-        match section {
-            // concatenate any char sequences
-            Src::Str(str) => {
-                output.push_str(str);
-            }
-            // expand all variables and recursively expand patterns
-            Src::Sub(sub) => {
-                let expanded = match sub {
-                    Sub::Var(key) => get_variable(key, file)?,
-                    Sub::Pat(key) => get_pattern(key, file)?,
-                    Sub::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(&expanded);
-            }
-        }
-    }
-
-    if arrays {
-        expand_arrays(output, file)
-    } else {
-        Ok(output)
-    }
-}
-
-fn get_source_html(file: &MetaFile) -> Result<String> {
-    let string = metafile_to_string(file)?;
-
-    if file.opts.no_pandoc || !file.header.pandoc {
-        return Ok(string);
-    }
-
-    let mut pandoc = pandoc::Pandoc::new();
-    pandoc
-        .set_input(pandoc::InputKind::Pipe(string))
-        .set_output(pandoc::OutputKind::Pipe)
-        .set_input_format(pandoc::InputFormat::Markdown, vec![])
-        .set_output_format(pandoc::OutputFormat::Html, vec![]);
-
-    if let Ok(pandoc::PandocOutput::ToBuffer(html)) = pandoc.execute() {
-        Ok(html)
-    } else {
-        bail!("pandoc could not write to buffer")
-    }
-}
-
-fn get_pattern(key: &str, file: &MetaFile) -> Result<String> {
-    // SOURCE is already expanded in the initial build_metafile() call
-    if key == "SOURCE" {
-        if let Some(source) = file.patterns.get("SOURCE") {
-            return Ok(source.to_string());
-        }
-    }
-
-    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" {
-        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(_) => bail!("could not find base file {}", path.display()),
-        };
-    }
-
-    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);
-
-    metafile_to_string(&pattern)
-}
-
-fn get_variable(key: &str, file: &MetaFile) -> Result<String> {
-    let long_key = file.name()? + "." + key;
-    if let Some(val) = file.get_var(&long_key) {
-        Ok(val.clone())
-    } else if let Some(val) = file.get_var(key) {
-        Ok(val.clone())
-    } else if file.opts.undefined {
-        bail!("undefined variable: {}, {}", key, long_key)
-    } else {
-        Ok(String::new())
-    }
-}
-
-fn expand_arrays(input: String, file: &MetaFile) -> Result<String> {
-    let map: HashMap<String, &[String]> = file
-        .source
-        .iter()
-        // filter out arrays from source vec
-        .filter_map(|x| {
-            if let Src::Sub(Sub::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();
-            let long_key = name + "." + key;
-
-            let value: &[String];
-            if let Some(val) = file.get_arr(&long_key) {
-                value = val;
-            } else if let Some(val) = file.get_arr(key) {
-                value = val;
-            } else if file.opts.undefined {
-                panic!("undefined array called: {}, {}", key, long_key);
-            } else {
-                value = &[];
-            }
-
-            (key.to_string(), value)
-        })
-        .collect();
-
-    // loop to duplicate the output template for each array member
-    let mut expanded = String::new();
-    for i in 0..get_max_size(&map) {
-        // 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_max_size(map: &HashMap<String, &[String]>) -> usize {
-    let mut max = 0;
-    for val in map.values() {
-        if max < val.len() {
-            max = val.len();
-        }
-    }
-    max
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    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.source = dir.join("source");
-        opts.build = dir.join("build");
-        opts.pattern = dir.join("pattern");
-        opts.clean = true;
-
-        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<()> {
-        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_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_headers() -> Result<()> {
-        unit_test("header/pandoc", "# This should not become html\n")?;
-        unit_test("header/blank", "")?;
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_filetype_header() -> Result<()> {
-        let dir = PathBuf::from("files/test_site").canonicalize()?;
-
-        let mut opts = Options::new();
-        opts.root = dir.clone();
-        opts.source = dir.join("source");
-        opts.build = dir.join("build");
-
-        let path = opts.source.join("unit_tests/header/filetype.meta");
-        let file = MetaFile::build(path, &opts)?;
-
-        assert_eq!(
-            file.dest()?,
-            PathBuf::from(
-                "/home/huck/repos/metaforge/files/test_site/build/unit_tests/header/filetype.rss"
-            )
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_global() -> Result<()> {
-        let dir = PathBuf::from("files/test_site/").canonicalize()?;
-
-        let mut opts = Options::new();
-        opts.root = dir.clone();
-        opts.source = dir.join("source");
-        opts.build = dir.join("build");
-        opts.pattern = dir.join("pattern");
-
-        let mut dir_node = crate::DirNode::build(dir.join("source/unit_tests/global"), &opts)?;
-        let global = MetaFile::build(dir.join("source/default.meta"), &opts)?;
-        dir_node.map(&global)?;
-        dir_node.build_dir()?;
-
-        assert_eq!(
-            fs::read_to_string(dir.join("build/unit_tests/global/pattern.html"))?,
-            "<p>GOOD GOOD</p>\n"
-        );
-
-        assert_eq!(
-            fs::read_to_string(dir.join("build/unit_tests/global/variable.html"))?,
-            "<p>GOODGOOD</p>\n"
-        );
-
-        Ok(())
-    }
-}
diff --git a/src/builder/array.rs b/src/builder/array.rs
new file mode 100644 (file)
index 0000000..b1ff436
--- /dev/null
@@ -0,0 +1,64 @@
+use crate::{MetaFile, Src, Sub};
+use color_eyre::Result;
+use std::collections::HashMap;
+
+pub fn expand_arrays(input: String, file: &MetaFile) -> Result<String> {
+    let map: HashMap<String, &[String]> = file
+        .source
+        .iter()
+        // filter out arrays from source vec
+        .filter_map(|x| {
+            if let Src::Sub(Sub::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();
+            let long_key = name + "." + key;
+
+            let value: &[String];
+            if let Some(val) = file.get_arr(&long_key) {
+                value = val;
+            } else if let Some(val) = file.get_arr(key) {
+                value = val;
+            } else if file.opts.undefined {
+                panic!("undefined array called: {}, {}", key, long_key);
+            } else {
+                value = &[];
+            }
+
+            (key.to_string(), value)
+        })
+        .collect();
+
+    // loop to duplicate the output template for each array member
+    let mut expanded = String::new();
+    for i in 0..get_max_size(&map) {
+        // 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_max_size(map: &HashMap<String, &[String]>) -> usize {
+    let mut max = 0;
+    for val in map.values() {
+        if max < val.len() {
+            max = val.len();
+        }
+    }
+    max
+}
diff --git a/src/builder/pattern.rs b/src/builder/pattern.rs
new file mode 100644 (file)
index 0000000..5576584
--- /dev/null
@@ -0,0 +1,55 @@
+use crate::MetaFile;
+use color_eyre::{eyre::bail, Result};
+use std::fs;
+
+pub fn get_pattern(key: &str, file: &MetaFile) -> Result<String> {
+    // SOURCE is already expanded in the initial build_metafile() call
+    if key == "SOURCE" {
+        if let Some(source) = file.patterns.get("SOURCE") {
+            return Ok(source.to_string());
+        }
+    }
+
+    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" {
+        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(_) => bail!("could not find base file {}", path.display()),
+        };
+    }
+
+    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
new file mode 100644 (file)
index 0000000..48a5517
--- /dev/null
@@ -0,0 +1,64 @@
+use crate::{MetaFile, Src, Sub};
+use color_eyre::{eyre::bail, Result};
+
+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 {
+        return Ok(string);
+    }
+
+    let mut pandoc = pandoc::Pandoc::new();
+    pandoc
+        .set_input(pandoc::InputKind::Pipe(string))
+        .set_output(pandoc::OutputKind::Pipe)
+        .set_input_format(pandoc::InputFormat::Markdown, vec![])
+        .set_output_format(pandoc::OutputFormat::Html, vec![]);
+
+    if let Ok(pandoc::PandocOutput::ToBuffer(html)) = pandoc.execute() {
+        Ok(html)
+    } else {
+        bail!("pandoc could not write to buffer")
+    }
+}
+
+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() {
+        match section {
+            // concatenate any char sequences
+            Src::Str(str) => {
+                output.push_str(str);
+            }
+            // expand all variables and recursively expand patterns
+            Src::Sub(sub) => {
+                let expanded = match sub {
+                    Sub::Var(key) => super::variable::get_variable(key, file)?,
+                    Sub::Pat(key) => get_pattern(key, file)?,
+                    Sub::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(&expanded);
+            }
+        }
+    }
+
+    if arrays {
+        expand_arrays(output, file)
+    } else {
+        Ok(output)
+    }
+}
diff --git a/src/builder/tests.rs b/src/builder/tests.rs
new file mode 100644 (file)
index 0000000..0d6feb0
--- /dev/null
@@ -0,0 +1,108 @@
+use crate::{build_metafile, MetaFile, Options};
+use color_eyre::{eyre::WrapErr, Result};
+use std::path::PathBuf;
+
+fn unit_test(test: (&str, &str)) -> Result<()> {
+    let dir = PathBuf::from("files/test_site").canonicalize()?;
+
+    let mut opts = Options::new();
+    opts.root = dir.clone();
+    opts.source = dir.join("source");
+    opts.build = dir.join("build");
+    opts.pattern = dir.join("pattern");
+    opts.clean = true;
+
+    let test_dir = opts.source.join("unit_tests");
+    let mut file_path = test_dir.join(test.0);
+    file_path.set_extension("meta");
+    let file = MetaFile::build(file_path, &opts)?;
+
+    let output = build_metafile(&file).wrap_err_with(|| test.0.to_string())?;
+
+    assert_eq!(output, test.1);
+
+    Ok(())
+}
+
+#[test]
+fn builder_tests() -> Result<()> {
+    let mut tests: Vec<(&str, &str)> = Vec::new();
+    tests.push(("find_dest", "<html>\n\n</html>\n"));
+    tests.push(("blank/blank_pattern", ""));
+    tests.push(("blank/blank_variable", "<html>\n</html>\n"));
+    tests.push(("blank/blank_array", "<html>\n</html>\n"));
+    tests.push(("blank/comment", "<html>\n\n</html>\n"));
+    tests.push((
+        "blank/inline_comment",
+        "<html>\n<p>inline comment</p>\n</html>\n",
+    ));
+    tests.push((
+        "expand/variable_in_source",
+        "<html>\n<p>GOOD</p>\n</html>\n",
+    ));
+    tests.push(("expand/variable_in_pattern", "<html>\nGOOD</html>\n"));
+    tests.push(("expand/array_in_source", "<html>\n<p>12345</p>\n</html>\n"));
+    tests.push(("expand/array_in_pattern", "<html>\n12345</html>\n"));
+    tests.push(("expand/pattern_in_source", "<p>GOOD</p>\n"));
+    tests.push(("expand/pattern_in_pattern", "<html>\nGOOD\nGOOD\n</html>\n"));
+    tests.push(("override/variable", "<html>\n<p>GOOD</p>\n</html>\n"));
+    tests.push(("override/pattern", "<html>\nGOOD\nGOOD\n</html>\n"));
+    tests.push(("header/pandoc", "# This should not become html\n"));
+    tests.push(("header/blank", ""));
+
+    for test in tests.iter() {
+        unit_test(*test)?;
+    }
+
+    Ok(())
+}
+
+#[test]
+fn test_filetype_header() -> Result<()> {
+    let dir = PathBuf::from("files/test_site").canonicalize()?;
+
+    let mut opts = Options::new();
+    opts.root = dir.clone();
+    opts.source = dir.join("source");
+    opts.build = dir.join("build");
+
+    let path = opts.source.join("unit_tests/header/filetype.meta");
+    let file = MetaFile::build(path, &opts)?;
+
+    assert_eq!(
+        file.dest()?,
+        PathBuf::from(
+            "/home/huck/repos/metaforge/files/test_site/build/unit_tests/header/filetype.rss"
+        )
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_global() -> Result<()> {
+    let dir = PathBuf::from("files/test_site/").canonicalize()?;
+
+    let mut opts = Options::new();
+    opts.root = dir.clone();
+    opts.source = dir.join("source");
+    opts.build = dir.join("build");
+    opts.pattern = dir.join("pattern");
+
+    let mut dir_node = crate::DirNode::build(dir.join("source/unit_tests/global"), &opts)?;
+    let global = MetaFile::build(dir.join("source/default.meta"), &opts)?;
+    dir_node.map(&global)?;
+    dir_node.build_dir()?;
+
+    assert_eq!(
+        std::fs::read_to_string(dir.join("build/unit_tests/global/pattern.html"))?,
+        "<p>GOOD GOOD</p>\n"
+    );
+
+    assert_eq!(
+        std::fs::read_to_string(dir.join("build/unit_tests/global/variable.html"))?,
+        "<p>GOODGOOD</p>\n"
+    );
+
+    Ok(())
+}
diff --git a/src/builder/variable.rs b/src/builder/variable.rs
new file mode 100644 (file)
index 0000000..260cf97
--- /dev/null
@@ -0,0 +1,15 @@
+use crate::MetaFile;
+use color_eyre::{eyre::bail, Result};
+
+pub fn get_variable(key: &str, file: &MetaFile) -> Result<String> {
+    let long_key = file.name()? + "." + key;
+    if let Some(val) = file.get_var(&long_key) {
+        Ok(val.clone())
+    } else if let Some(val) = file.get_var(key) {
+        Ok(val.clone())
+    } else if file.opts.undefined {
+        bail!("undefined variable: {}, {}", key, long_key)
+    } else {
+        Ok(String::new())
+    }
+}
index 25323823dcd3f9b0d72ec258dfd926e92b49c22d..ced91bd48acb4a750f669765c15ef25d2ce106e0 100644 (file)
-use crate::{build_metafile, parse_file, Options};
-use color_eyre::{
-    eyre::{bail, eyre},
-    Result,
-};
-use std::collections::HashMap;
-use std::{fs, path::PathBuf};
+mod dir;
+mod file;
+mod header;
 
-#[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: Header,
-    pub variables: HashMap<String, String>,
-    pub arrays: HashMap<String, Vec<String>>,
-    pub patterns: HashMap<String, String>,
-    pub source: Vec<Src>,
-}
-
-impl<'a> MetaFile<'a> {
-    pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
-        let str = match fs::read_to_string(&path) {
-            Ok(str) => str,
-            Err(_) => bail!("{} does not exist", path.display()),
-        };
-        let mut metafile = parse_file(str, opts)?;
-        metafile.path = path;
-        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);
+pub use dir::*;
+pub use file::*;
+pub use header::*;
 
-        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 {
-            color_eyre::eyre::bail!("could not get name from: {}", self.path.display());
-        }
-    }
-
-    pub fn get_var(&self, key: &str) -> Option<&String> {
-        self.variables.get(key)
-    }
-
-    pub fn get_arr(&self, key: &str) -> Option<&[String]> {
-        self.arrays.get(key).map(|a| &a[..])
-    }
-
-    pub fn get_pat(&self, key: &str) -> Option<&String> {
-        self.patterns.get(key)
-    }
-
-    pub fn merge(&mut self, other: &Self) {
-        for (key, val) in other.variables.iter() {
-            match self.variables.get(key) {
-                Some(_) => continue,
-                None => self.variables.insert(key.to_string(), val.to_string()),
-            };
-        }
-        for (key, val) in other.arrays.iter() {
-            match self.arrays.get(key) {
-                Some(_) => continue,
-                None => self.arrays.insert(key.to_string(), val.to_vec()),
-            };
-        }
-        for (key, val) in other.patterns.iter() {
-            match self.patterns.get(key) {
-                Some(_) => continue,
-                None => self.patterns.insert(key.to_string(), val.to_string()),
-            };
-        }
-    }
-}
+#[cfg(test)]
+mod tests;
 
 #[macro_export]
 macro_rules! source (
-    (var($s:expr)) => { Src::Sub(Sub::Var($s.to_string()))};
-    (arr($s:expr)) => { Src::Sub(Sub::Arr($s.to_string()))};
-    (pat($s:expr)) => { Src::Sub(Sub::Pat($s.to_string()))};
+    (var($s:expr)) => { crate::Src::Sub(crate::Sub::Var($s.to_string()))};
+    (arr($s:expr)) => { crate::Src::Sub(crate::Sub::Arr($s.to_string()))};
+    (pat($s:expr)) => { crate::Src::Sub(crate::Sub::Pat($s.to_string()))};
     ($s:expr) => { Src::Str($s.to_string())};
 );
 
@@ -171,119 +29,3 @@ pub enum Sub {
     Arr(String),
     Pat(String),
 }
-
-#[derive(Debug, Clone)]
-pub struct DirNode<'a> {
-    path: PathBuf,
-    opts: &'a Options,
-    global: MetaFile<'a>,
-    files: Vec<MetaFile<'a>>,
-    dirs: Vec<DirNode<'a>>,
-}
-
-impl<'a> DirNode<'a> {
-    pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
-        assert!(path.is_dir() && path.exists());
-
-        let build_dir = opts.build.join(path.strip_prefix(&opts.source)?);
-        if !build_dir.exists() {
-            fs::create_dir(build_dir)?;
-        }
-
-        let files: Vec<MetaFile> = Vec::new();
-        let dirs: Vec<DirNode> = Vec::new();
-        let global = MetaFile::new(opts);
-
-        Ok(Self {
-            path,
-            opts,
-            global,
-            files,
-            dirs,
-        })
-    }
-
-    // parses all contained files and directories and pushes
-    // parsed structures into the files and directories vectors
-    pub fn map(&mut self, global: &'a MetaFile) -> Result<()> {
-        for f in fs::read_dir(&self.path)? {
-            let file = f?.path();
-
-            if file.is_dir() {
-                let dir = DirNode::build(file, self.opts)?;
-                self.dirs.push(dir);
-            } else if file.file_name().and_then(|f| f.to_str()) == Some("default.meta") {
-                let mut new_global = MetaFile::build(file, self.opts)?;
-                new_global.merge(global);
-                self.global = new_global;
-            } else if file.extension().and_then(|f| f.to_str()) == Some("meta") {
-                let file = MetaFile::build(file, self.opts)?;
-                self.files.push(file)
-            }
-        }
-
-        Ok(())
-    }
-
-    pub fn build_files(&mut self) -> Result<()> {
-        for file in self.files.iter_mut() {
-            file.merge(&self.global);
-            match build_metafile(file) {
-                Ok(str) => {
-                    fs::write(file.dest()?, str)?;
-                }
-                Err(e) => {
-                    if self.opts.force {
-                        // print a line to stderr about failure but continue with other files
-                        eprintln!("ignoring {}: {}", file.path.display(), e);
-                        continue;
-                    } else {
-                        return Err(e.wrap_err(eyre!("{}:", file.path.display())));
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-
-    pub fn build_dir(&'a mut self) -> Result<()> {
-        self.build_files()?;
-
-        for dir in self.dirs.iter_mut() {
-            dir.map(&self.global)?;
-            dir.build_dir()?;
-        }
-
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    #[test]
-    fn test_name() -> Result<()> {
-        let mut opts = Options::new();
-
-        opts.source = "/tmp/source".into();
-        opts.build = "/tmp/build".into();
-        opts.pattern = "/tmp/pattern".into();
-
-        let src_path = PathBuf::from("/tmp/source/test/file.meta");
-        let pat1_path = PathBuf::from("/tmp/pattern/base/test.meta");
-        let pat2_path = PathBuf::from("/tmp/pattern/test/class/file.meta");
-
-        let mut src = MetaFile::new(&opts);
-        src.path = src_path;
-        let mut pat1 = MetaFile::new(&opts);
-        pat1.path = pat1_path;
-        let mut pat2 = MetaFile::new(&opts);
-        pat2.path = pat2_path;
-
-        assert_eq!(src.name()?, "test.file");
-        assert_eq!(pat1.name()?, "base");
-        assert_eq!(pat2.name()?, "test.class");
-
-        Ok(())
-    }
-}
diff --git a/src/metafile/dir.rs b/src/metafile/dir.rs
new file mode 100644 (file)
index 0000000..7f36ef6
--- /dev/null
@@ -0,0 +1,91 @@
+use crate::{build_metafile, Options};
+use color_eyre::{eyre::eyre, Result};
+use std::{fs, path::PathBuf};
+
+use super::*;
+
+#[derive(Debug, Clone)]
+pub struct DirNode<'a> {
+    path: PathBuf,
+    opts: &'a Options,
+    global: MetaFile<'a>,
+    files: Vec<MetaFile<'a>>,
+    dirs: Vec<DirNode<'a>>,
+}
+
+impl<'a> DirNode<'a> {
+    pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
+        assert!(path.is_dir() && path.exists());
+
+        let build_dir = opts.build.join(path.strip_prefix(&opts.source)?);
+        if !build_dir.exists() {
+            fs::create_dir(build_dir)?;
+        }
+
+        let files: Vec<MetaFile> = Vec::new();
+        let dirs: Vec<DirNode> = Vec::new();
+        let global = MetaFile::new(opts);
+
+        Ok(Self {
+            path,
+            opts,
+            global,
+            files,
+            dirs,
+        })
+    }
+
+    // parses all contained files and directories and pushes
+    // parsed structures into the files and directories vectors
+    pub fn map(&mut self, global: &'a MetaFile) -> Result<()> {
+        for f in fs::read_dir(&self.path)? {
+            let file = f?.path();
+
+            if file.is_dir() {
+                let dir = DirNode::build(file, self.opts)?;
+                self.dirs.push(dir);
+            } else if file.file_name().and_then(|f| f.to_str()) == Some("default.meta") {
+                let mut new_global = MetaFile::build(file, self.opts)?;
+                new_global.merge(global);
+                self.global = new_global;
+            } else if file.extension().and_then(|f| f.to_str()) == Some("meta") {
+                let file = MetaFile::build(file, self.opts)?;
+                self.files.push(file)
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn build_files(&mut self) -> Result<()> {
+        for file in self.files.iter_mut() {
+            file.merge(&self.global);
+            match build_metafile(file) {
+                Ok(str) => {
+                    fs::write(file.dest()?, str)?;
+                }
+                Err(e) => {
+                    if self.opts.force {
+                        // print a line to stderr about failure but continue with other files
+                        eprintln!("ignoring {}: {}", file.path.display(), e);
+                        continue;
+                    } else {
+                        return Err(e.wrap_err(eyre!("{}:", file.path.display())));
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+
+    pub fn build_dir(&'a mut self) -> Result<()> {
+        self.build_files()?;
+
+        for dir in self.dirs.iter_mut() {
+            dir.map(&self.global)?;
+            dir.build_dir()?;
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/metafile/file.rs b/src/metafile/file.rs
new file mode 100644 (file)
index 0000000..a3fe74a
--- /dev/null
@@ -0,0 +1,112 @@
+use crate::{parse_string, Options};
+use color_eyre::{eyre::bail, Result};
+use std::{collections::HashMap, path::PathBuf};
+
+use super::*;
+
+#[derive(Debug, Clone)]
+pub struct MetaFile<'a> {
+    pub opts: &'a Options,
+    pub path: PathBuf,
+    pub header: Header,
+    pub variables: HashMap<String, String>,
+    pub arrays: HashMap<String, Vec<String>>,
+    pub patterns: HashMap<String, String>,
+    pub source: Vec<Src>,
+}
+
+impl<'a> MetaFile<'a> {
+    pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
+        let str = match std::fs::read_to_string(&path) {
+            Ok(str) => str,
+            Err(_) => bail!("{} does not exist", path.display()),
+        };
+        let mut metafile = parse_string(str, opts)?;
+        metafile.path = path;
+        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 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 {
+            color_eyre::eyre::bail!("could not get name from: {}", self.path.display());
+        }
+    }
+
+    pub fn get_var(&self, key: &str) -> Option<&String> {
+        self.variables.get(key)
+    }
+
+    pub fn get_arr(&self, key: &str) -> Option<&[String]> {
+        self.arrays.get(key).map(|a| &a[..])
+    }
+
+    pub fn get_pat(&self, key: &str) -> Option<&String> {
+        self.patterns.get(key)
+    }
+
+    pub fn merge(&mut self, other: &Self) {
+        for (key, val) in other.variables.iter() {
+            match self.variables.get(key) {
+                Some(_) => continue,
+                None => self.variables.insert(key.to_string(), val.to_string()),
+            };
+        }
+        for (key, val) in other.arrays.iter() {
+            match self.arrays.get(key) {
+                Some(_) => continue,
+                None => self.arrays.insert(key.to_string(), val.to_vec()),
+            };
+        }
+        for (key, val) in other.patterns.iter() {
+            match self.patterns.get(key) {
+                Some(_) => continue,
+                None => self.patterns.insert(key.to_string(), val.to_string()),
+            };
+        }
+    }
+}
diff --git a/src/metafile/header.rs b/src/metafile/header.rs
new file mode 100644 (file)
index 0000000..c00f39e
--- /dev/null
@@ -0,0 +1,39 @@
+use std::collections::HashMap;
+
+#[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
+    }
+}
diff --git a/src/metafile/tests.rs b/src/metafile/tests.rs
new file mode 100644 (file)
index 0000000..870ba48
--- /dev/null
@@ -0,0 +1,31 @@
+use crate::Options;
+use color_eyre::Result;
+use std::path::PathBuf;
+
+use super::*;
+
+#[test]
+fn test_name() -> Result<()> {
+    let mut opts = Options::new();
+
+    opts.source = "/tmp/source".into();
+    opts.build = "/tmp/build".into();
+    opts.pattern = "/tmp/pattern".into();
+
+    let src_path = PathBuf::from("/tmp/source/test/file.meta");
+    let pat1_path = PathBuf::from("/tmp/pattern/base/test.meta");
+    let pat2_path = PathBuf::from("/tmp/pattern/test/class/file.meta");
+
+    let mut src = MetaFile::new(&opts);
+    src.path = src_path;
+    let mut pat1 = MetaFile::new(&opts);
+    pat1.path = pat1_path;
+    let mut pat2 = MetaFile::new(&opts);
+    pat2.path = pat2_path;
+
+    assert_eq!(src.name()?, "test.file");
+    assert_eq!(pat1.name()?, "base");
+    assert_eq!(pat2.name()?, "test.class");
+
+    Ok(())
+}
index 4d9f22ea3b12c9c30db0809ecd7e69fd7ef53dd8..3fbdd4f8277c6202b0883ba0ea297e6933603b06 100644 (file)
@@ -1,117 +1,8 @@
-use clap::Parser;
-use color_eyre::Result;
-use std::path::PathBuf;
+pub mod arg_parser;
+pub mod 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")]
-    source: Option<String>,
-    /// Build directory [CURRENT_DIR/build]
-    #[arg(short, long, value_name = "BUILD_DIR")]
-    build: Option<String>,
-    /// Pattern directory [CURRENT_DIR/pattern]
-    #[arg(short, long, value_name = "PATTERN_DIR")]
-    pattern: Option<String>,
-    /// Enable extra output.
-    /// Repeated flags give more info
-    #[arg(short, long, action = clap::ArgAction::Count)]
-    verbose: u8,
-    /// Minimal output
-    #[arg(short, long, default_value_t = false)]
-    quiet: bool,
-    /// Don't stop on file failure [FALSE]
-    #[arg(long, default_value_t = false)]
-    force: bool,
-    /// Stop on undefined variables and arrays [FALSE]
-    #[arg(long, default_value_t = false)]
-    undefined: bool,
-    /// Clean build directory before building site [FALSE]
-    #[arg(long, default_value_t = false)]
-    clean: bool,
-    /// Don't convert markdown to html.
-    /// Runs even if pandoc isn't installed [FALSE]
-    #[arg(long, default_value_t = false)]
-    no_pandoc: bool,
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Options {
-    pub root: PathBuf,
-    pub source: PathBuf,
-    pub build: PathBuf,
-    pub pattern: PathBuf,
-    pub verbose: u8,
-    pub quiet: bool,
-    pub force: bool,
-    pub undefined: bool,
-    pub clean: bool,
-    pub no_pandoc: bool,
-}
-
-impl Options {
-    pub fn new() -> Self {
-        Self {
-            root: PathBuf::new(),
-            source: PathBuf::new(),
-            build: PathBuf::new(),
-            pattern: PathBuf::new(),
-            verbose: 0,
-            quiet: false,
-            force: false,
-            undefined: false,
-            clean: false,
-            no_pandoc: false,
-        }
-    }
-}
-
-impl TryFrom<Opts> for Options {
-    type Error = color_eyre::eyre::Error;
-    fn try_from(value: Opts) -> Result<Self, Self::Error> {
-        let mut options = Options::new();
-
-        options.verbose = value.verbose;
-        options.quiet = value.quiet;
-        options.force = value.force;
-        options.undefined = value.undefined;
-        options.clean = value.clean;
-        options.no_pandoc = value.no_pandoc;
-
-        if let Some(root) = value.root.as_deref() {
-            options.root = PathBuf::from(root).canonicalize()?;
-        } else {
-            options.root = std::env::current_dir()?;
-        }
-
-        if let Some(source) = value.source.as_deref() {
-            options.source = PathBuf::from(source).canonicalize()?;
-        } else {
-            options.source = options.root.join("source");
-        }
-
-        if let Some(build) = value.build.as_deref() {
-            options.build = PathBuf::from(build).canonicalize()?;
-        } else {
-            options.build = options.root.join("build");
-        }
-
-        if let Some(pattern) = value.pattern.as_deref() {
-            options.pattern = PathBuf::from(pattern).canonicalize()?;
-        } else {
-            options.pattern = options.root.join("pattern");
-        }
-
-        Ok(options)
-    }
-}
+pub use arg_parser::*;
+pub use opt_struct::*;
 
 #[macro_export]
 macro_rules! log {
diff --git a/src/options/arg_parser.rs b/src/options/arg_parser.rs
new file mode 100644 (file)
index 0000000..868fdbd
--- /dev/null
@@ -0,0 +1,41 @@
+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>,
+    /// 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 convert markdown to html.
+    /// Runs even if pandoc isn't installed [FALSE]
+    #[arg(long, default_value_t = false)]
+    pub no_pandoc: bool,
+}
diff --git a/src/options/opt_struct.rs b/src/options/opt_struct.rs
new file mode 100644 (file)
index 0000000..e37aa63
--- /dev/null
@@ -0,0 +1,73 @@
+use color_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 verbose: u8,
+    pub quiet: bool,
+    pub force: bool,
+    pub undefined: bool,
+    pub clean: bool,
+    pub no_pandoc: bool,
+}
+
+impl Options {
+    pub fn new() -> Self {
+        Self {
+            root: PathBuf::new(),
+            source: PathBuf::new(),
+            build: PathBuf::new(),
+            pattern: PathBuf::new(),
+            verbose: 0,
+            quiet: false,
+            force: false,
+            undefined: false,
+            clean: false,
+            no_pandoc: false,
+        }
+    }
+}
+
+impl TryFrom<crate::Opts> for Options {
+    type Error = color_eyre::eyre::Error;
+    fn try_from(value: crate::Opts) -> Result<Self, Self::Error> {
+        let mut options = Options::new();
+
+        options.verbose = value.verbose;
+        options.quiet = value.quiet;
+        options.force = value.force;
+        options.undefined = value.undefined;
+        options.clean = value.clean;
+        options.no_pandoc = value.no_pandoc;
+
+        if let Some(root) = value.root.as_deref() {
+            options.root = PathBuf::from(root).canonicalize()?;
+        } else {
+            options.root = std::env::current_dir()?;
+        }
+
+        if let Some(source) = value.source.as_deref() {
+            options.source = PathBuf::from(source).canonicalize()?;
+        } else {
+            options.source = options.root.join("source");
+        }
+
+        if let Some(build) = value.build.as_deref() {
+            options.build = PathBuf::from(build).canonicalize()?;
+        } else {
+            options.build = options.root.join("build");
+        }
+
+        if let Some(pattern) = value.pattern.as_deref() {
+            options.pattern = PathBuf::from(pattern).canonicalize()?;
+        } else {
+            options.pattern = options.root.join("pattern");
+        }
+
+        Ok(options)
+    }
+}
index c2c12625fc688b67ff420a8420ba5b02495c49b5..bc7d61c490bdbe4eadf5d9054017e36c508cd0e6 100644 (file)
@@ -1,26 +1,38 @@
-use crate::{source, Header, MetaFile, Options, Src, Sub};
+use crate::{Header, MetaFile, Options};
 use color_eyre::{eyre::WrapErr, Result};
 use pest::{
     iterators::{Pair, Pairs},
     Parser,
 };
-use std::collections::HashMap;
+
+mod array;
+mod def_block;
+mod header;
+mod source;
+
+use array::*;
+use def_block::*;
+use header::*;
+use source::*;
+
+#[cfg(test)]
+mod tests;
 
 #[derive(Parser)]
-#[grammar = "meta.pest"]
+#[grammar = "parser/meta.pest"]
 pub struct MetaParser;
 
-pub fn parse_file(file: String, opts: &Options) -> Result<MetaFile> {
+pub fn parse_string(file: String, opts: &Options) -> Result<MetaFile> {
     let meta_source = MetaParser::parse(Rule::file, &file)
         .wrap_err("parser error")?
         .next()
         .unwrap();
 
-    let metafile = parse_pair(meta_source, opts);
+    let metafile = parse_file(meta_source, opts);
     Ok(metafile)
 }
 
-fn parse_pair<'a>(pair: Pair<Rule>, opts: &'a Options) -> MetaFile<'a> {
+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() {
@@ -44,230 +56,3 @@ fn parse_pair<'a>(pair: Pair<Rule>, opts: &'a Options) -> MetaFile<'a> {
 
     meta_file
 }
-
-fn parse_defs(pairs: Pairs<Rule>) -> HashMap<String, String> {
-    let mut map = HashMap::new();
-    for pair in pairs {
-        if Rule::assign == pair.as_rule() {
-            let (key, val) = parse_assign(pair);
-            map.insert(key.to_string(), val.to_string());
-        }
-    }
-    map
-}
-
-fn parse_header_defs(pairs: Pairs<Rule>) -> HashMap<String, String> {
-    let mut map = HashMap::new();
-    for pair in pairs {
-        if Rule::header_assign == pair.as_rule() {
-            let (key, val) = parse_header_assign(pair);
-            map.insert(key.to_string(), val.to_string());
-        }
-    }
-    map
-}
-
-fn parse_array_defs(pairs: Pairs<Rule>) -> HashMap<String, Vec<String>> {
-    let mut map = HashMap::new();
-    for pair in pairs {
-        if Rule::assign == pair.as_rule() {
-            let (key, val) = parse_assign_array(pair);
-            map.insert(key.to_string(), val);
-        }
-    }
-    map
-}
-
-fn parse_source(pairs: Pairs<Rule>) -> Vec<Src> {
-    let mut vec = Vec::new();
-    for pair in pairs {
-        match pair.as_rule() {
-            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_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:
-            //      first two chars (sigil and preceding brace)
-            //      last char (trailing brace)
-            &str[2..str.len() - 1]
-        }
-        // this function only gets called to parse substituiton patterns
-        // so anything else should never be called
-        _ => 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
-            if tmp == "BLANK" || tmp == "DEFAULT" {
-                return (key, tmp);
-            }
-            // 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_header_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::header_value == pair.as_rule() {
-            let tmp = pair.as_str();
-            // blank and default shoud be handled by whoever is getting the value
-            if tmp == "BLANK" || tmp == "true" || tmp == "false" {
-                return (key, tmp);
-            }
-            // 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>) -> (String, Vec<String>) {
-    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::value == pair.as_rule() {
-            val = parse_array(pair.into_inner());
-        }
-    }
-
-    (key.to_string(), val)
-}
-
-fn parse_array(pairs: Pairs<Rule>) -> Vec<String> {
-    let mut vec: Vec<String> = Vec::default();
-
-    for pair in pairs {
-        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.to_string());
-        }
-    }
-
-    vec
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    macro_rules! test_str (
-        ($s: expr) => {
-            let opts = Options::new();
-            let str = $s.to_string();
-            parse_file(str, &opts).unwrap();
-        };
-    );
-
-    #[test]
-    fn no_spaces_in_def() {
-        test_str!(r#"${v='v'}@{a=['a']}&{p='p'}"#);
-    }
-
-    #[test]
-    fn just_source_string() {
-        test_str!(r#"This is just a &{source} snippet"#);
-    }
-
-    #[test]
-    fn one_line() {
-        test_str!(
-            r#"${variable = 'var' } @{array = ['array']} &{ pattern = "pattern"} And some extra text"#
-        );
-    }
-
-    #[test]
-    #[should_panic]
-    fn key_with_spaces() {
-        test_str!(r#"${ key with spaces = "value" }"#);
-    }
-
-    #[test]
-    #[should_panic]
-    fn value_missing_quote() {
-        test_str!(r#"${ key = "value missing quote }"#);
-    }
-
-    #[test]
-    #[should_panic]
-    fn mixed_quotes() {
-        test_str!(r#"${ key = "value mixing quotes' }"#);
-    }
-
-    #[test]
-    #[should_panic]
-    fn spaces_in_substitution() {
-        test_str!(r#"This ${variable is not allowed}"#);
-    }
-
-    #[test]
-    #[should_panic]
-    fn missing_closing_brace() {
-        test_str!(r#"${ key = "value" "#);
-    }
-
-    #[test]
-    #[should_panic]
-    fn map_in_source() {
-        test_str!(r#"This map: ${ is = "invalid" }"#);
-    }
-
-    #[test]
-    #[should_panic]
-    fn map_source_map() {
-        test_str!(r#"${var='v'} Some text @{array = ['a']}"#);
-    }
-
-    #[test]
-    #[should_panic]
-    fn header_not_first() {
-        test_str!(r#"${v='v'} #{ type = 'html'} @{a=['a']}"#);
-    }
-}
diff --git a/src/parser/array.rs b/src/parser/array.rs
new file mode 100644 (file)
index 0000000..5120511
--- /dev/null
@@ -0,0 +1,46 @@
+use crate::Rule;
+use pest::iterators::{Pair, Pairs};
+use std::collections::HashMap;
+
+pub fn parse_array_defs(pairs: Pairs<Rule>) -> HashMap<String, Vec<String>> {
+    let mut map = HashMap::new();
+    for pair in pairs {
+        if Rule::assign == pair.as_rule() {
+            let (key, val) = parse_assign_array(pair);
+            map.insert(key.to_string(), val);
+        }
+    }
+    map
+}
+
+fn parse_assign_array(pair: Pair<Rule>) -> (String, Vec<String>) {
+    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::value == pair.as_rule() {
+            val = parse_array(pair.into_inner());
+        }
+    }
+
+    (key.to_string(), val)
+}
+
+fn parse_array(pairs: Pairs<Rule>) -> Vec<String> {
+    let mut vec: Vec<String> = Vec::default();
+
+    for pair in pairs {
+        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.to_string());
+        }
+    }
+
+    vec
+}
diff --git a/src/parser/def_block.rs b/src/parser/def_block.rs
new file mode 100644 (file)
index 0000000..7917998
--- /dev/null
@@ -0,0 +1,39 @@
+use crate::Rule;
+use pest::iterators::{Pair, Pairs};
+use std::collections::HashMap;
+
+pub fn parse_defs(pairs: Pairs<Rule>) -> HashMap<String, String> {
+    let mut map = HashMap::new();
+    for pair in pairs {
+        if Rule::assign == pair.as_rule() {
+            let (key, val) = parse_assign(pair);
+            map.insert(key.to_string(), val.to_string());
+        }
+    }
+    map
+}
+
+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
+            if tmp == "BLANK" || tmp == "DEFAULT" {
+                return (key, tmp);
+            }
+            // 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)
+}
diff --git a/src/parser/header.rs b/src/parser/header.rs
new file mode 100644 (file)
index 0000000..d487511
--- /dev/null
@@ -0,0 +1,39 @@
+use crate::Rule;
+use pest::iterators::{Pair, Pairs};
+use std::collections::HashMap;
+
+pub fn parse_header_defs(pairs: Pairs<Rule>) -> HashMap<String, String> {
+    let mut map = HashMap::new();
+    for pair in pairs {
+        if Rule::header_assign == pair.as_rule() {
+            let (key, val) = parse_header_assign(pair);
+            map.insert(key.to_string(), val.to_string());
+        }
+    }
+    map
+}
+
+fn parse_header_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::header_value == pair.as_rule() {
+            let tmp = pair.as_str();
+            // blank and default shoud be handled by whoever is getting the value
+            if tmp == "BLANK" || tmp == "true" || tmp == "false" {
+                return (key, tmp);
+            }
+            // 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)
+}
similarity index 100%
rename from src/meta.pest
rename to src/parser/meta.pest
diff --git a/src/parser/source.rs b/src/parser/source.rs
new file mode 100644 (file)
index 0000000..36a2c44
--- /dev/null
@@ -0,0 +1,38 @@
+use crate::{
+    parser::{Pair, Pairs},
+    source, Rule, Src,
+};
+
+pub fn parse_source(pairs: Pairs<Rule>) -> Vec<Src> {
+    let mut vec = Vec::new();
+    for pair in pairs {
+        match pair.as_rule() {
+            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_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:
+            //      first two chars (sigil and preceding brace)
+            //      last char (trailing brace)
+            &str[2..str.len() - 1]
+        }
+        // this function only gets called to parse substituiton patterns
+        // so anything else should never be called
+        _ => unreachable!(),
+    }
+}
diff --git a/src/parser/tests.rs b/src/parser/tests.rs
new file mode 100644 (file)
index 0000000..03f929f
--- /dev/null
@@ -0,0 +1,72 @@
+macro_rules! test_str (
+    ($s: expr) => {
+        let opts = crate::Options::new();
+        let str = $s.to_string();
+        crate::parse_string(str, &opts).unwrap();
+    };
+);
+
+#[test]
+fn no_spaces_in_def() {
+    test_str!(r#"${v='v'}@{a=['a']}&{p='p'}"#);
+}
+
+#[test]
+fn just_source_string() {
+    test_str!(r#"This is just a &{source} snippet"#);
+}
+
+#[test]
+fn one_line() {
+    test_str!(
+        r#"${variable = 'var' } @{array = ['array']} &{ pattern = "pattern"} And some extra text"#
+    );
+}
+
+#[test]
+#[should_panic]
+fn key_with_spaces() {
+    test_str!(r#"${ key with spaces = "value" }"#);
+}
+
+#[test]
+#[should_panic]
+fn value_missing_quote() {
+    test_str!(r#"${ key = "value missing quote }"#);
+}
+
+#[test]
+#[should_panic]
+fn mixed_quotes() {
+    test_str!(r#"${ key = "value mixing quotes' }"#);
+}
+
+#[test]
+#[should_panic]
+fn spaces_in_substitution() {
+    test_str!(r#"This ${variable is not allowed}"#);
+}
+
+#[test]
+#[should_panic]
+fn missing_closing_brace() {
+    test_str!(r#"${ key = "value" "#);
+}
+
+#[test]
+#[should_panic]
+fn map_in_source() {
+    test_str!(r#"This map: ${ is = "invalid" }"#);
+}
+
+#[test]
+#[should_panic]
+fn map_source_map() {
+    test_str!(r#"${var='v'} Some text @{array = ['a']}"#);
+}
+
+#[test]
+#[should_panic]
+fn header_not_first() {
+    test_str!(r#"${v='v'} #{ type = 'html'} @{a=['a']}"#);
+}