]> git.huck.website - metaforge.git/commitdiff
restructure options into metafiles
authorHuck Boles <huck@huck.website>
Sun, 14 May 2023 18:13:52 +0000 (13:13 -0500)
committerHuck Boles <huck@huck.website>
Sun, 14 May 2023 18:13:52 +0000 (13:13 -0500)
src/builder.rs
src/metafile.rs
src/options.rs
src/parser.rs

index 7a353ed12dfa340b2a08442a18c9f14b3d796ce6..b1b757ad630b04b29a79c74189a81bcf3753d4a6 100644 (file)
@@ -1,9 +1,5 @@
-use crate::{log, parse_file, MetaFile, Options, Source, Substitution};
-use color_eyre::{
-    eyre::bail,
-    eyre::{eyre, WrapErr},
-    Result,
-};
+use crate::{log, parse_file, MetaFile, Options, Src, Sub};
+use color_eyre::{eyre::bail, Result};
 use pandoc::{InputFormat, InputKind, OutputFormat, OutputKind, Pandoc, PandocOutput};
 use std::{
     collections::HashMap,
@@ -11,18 +7,16 @@ use std::{
     path::{Path, PathBuf},
 };
 
-pub fn build_metafile(file: &MetaFile, opts: &Options) -> Result<String> {
-    let html = get_source_html(file, opts)
-        .wrap_err_with(|| eyre!("failed converting to html: {}\n", file.path.display()))?;
+pub fn build_metafile(file: &MetaFile) -> Result<String> {
+    let html = get_source_html(file, file.opts)?;
 
-    let pattern = get_pattern("base", &file, opts).wrap_err("failed to get base pattern\n")?;
-    let mut base = parse_file(pattern).wrap_err("failed to parse base pattern\n")?;
+    let pattern = get_pattern("base", &file)?;
+    let mut base = parse_file(pattern, file.opts)?;
 
     base.merge(&file);
     base.patterns.insert("SOURCE".to_string(), html);
 
-    let output = metafile_to_string(&base, opts, Some("base"))
-        .wrap_err_with(|| eyre!("failed to build: {}\n", file.path.display()))?;
+    let output = metafile_to_string(&base)?;
 
     Ok(output)
 }
@@ -30,47 +24,40 @@ pub fn build_metafile(file: &MetaFile, opts: &Options) -> Result<String> {
 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 + "\n")
-        .wrap_err_with(|| eyre!("could not write to: {}\n", dest.display()))?;
+    fs::write(&dest, html + "\n")?;
     Ok(())
 }
 
-fn metafile_to_string(file: &MetaFile, opts: &Options, name: Option<&str>) -> Result<String> {
+fn metafile_to_string(file: &MetaFile) -> Result<String> {
     let mut output = String::default();
     let mut arrays = false;
 
     for section in file.source.iter() {
         match section {
             // concatenate any char sequences
-            Source::Str(str) => {
+            Src::Str(str) => {
                 output.push_str(str);
             }
             // expand all variables and recursively expand patterns
-            Source::Sub(sub) => {
+            Src::Sub(sub) => {
                 let expanded = match sub {
-                    Substitution::Variable(key) => file
-                        .get_var(key)
-                        // blank and default dont need to be processed
-                        .filter(|val| *val != "BLANK" && *val != "DEFAULT")
-                        .map(|val| val.to_string())
-                        .unwrap_or_default(),
-                    Substitution::Pattern(key) => get_pattern(key, file, opts)
-                        .wrap_err_with(|| eyre!("could not find pattern for: {}\n", key))?,
-                    // comments have already been removed at this point,
-                    // so we use them to mark keys for array substitution
-                    Substitution::Array(key) => {
+                    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(&format!("\n{}\n", expanded));
+                output.push_str(&expanded);
             }
         }
     }
 
     if arrays {
-        log!(opts, "\t\t\texpanding arrays", 4);
-        expand_arrays(output, file, name)
+        log!(file.opts, "\t\t\texpanding arrays", 4);
+        expand_arrays(output, file)
     } else {
         Ok(output)
     }
@@ -78,14 +65,13 @@ fn metafile_to_string(file: &MetaFile, opts: &Options, name: Option<&str>) -> Re
 
 fn get_source_html(file: &MetaFile, opts: &Options) -> Result<String> {
     log!(opts, "\tbuilding source", 2);
-    let file = metafile_to_string(file, opts, Some("SOURCE")).wrap_err("failed building source")?;
+    let file = metafile_to_string(file)?;
 
     if opts.no_pandoc {
         return Ok(file);
     }
 
     log!(opts, "\t\tcalling pandoc", 3);
-    log!(opts, "\t\t\tbuilding pandoc command", 4);
     let mut pandoc = Pandoc::new();
     pandoc
         .set_input(InputKind::Pipe(file))
@@ -93,7 +79,6 @@ fn get_source_html(file: &MetaFile, opts: &Options) -> Result<String> {
         .set_input_format(InputFormat::Markdown, vec![])
         .set_output_format(OutputFormat::Html, vec![]);
 
-    log!(opts, "\t\t\texecuting pandoc command", 4);
     if let Ok(PandocOutput::ToBuffer(html)) = pandoc.execute() {
         Ok(html)
     } else {
@@ -101,17 +86,17 @@ fn get_source_html(file: &MetaFile, opts: &Options) -> Result<String> {
     }
 }
 
-fn get_pattern(key: &str, file: &MetaFile, opts: &Options) -> Result<String> {
+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!(opts, "\t\t\treturning SOURCE", 4);
+        log!(file.opts, "\t\t\treturning SOURCE", 4);
         if let Some(source) = file.patterns.get("SOURCE") {
             return Ok(source.to_string());
         }
     }
 
-    log!(opts, format!("\t\tpattern: {}", key), 3);
+    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) {
@@ -124,47 +109,43 @@ fn get_pattern(key: &str, file: &MetaFile, opts: &Options) -> Result<String> {
     // parsing/expansion so we can build and convert source to html
     // we just want to return the string right now
     if key == "base" {
-        log!(opts, "\t\t\treturning base", 4);
+        log!(file.opts, "\t\t\treturning base", 4);
         let pattern_path = key.to_string() + "/" + &filename;
-        let mut path = opts.pattern.join(pattern_path);
+        let mut path = file.opts.pattern.join(pattern_path);
         path.set_extension("meta");
 
-        let base = fs::read_to_string(&path)
-            .wrap_err_with(|| eyre!("base pattern does not exist: {}\n", path.display()))?;
+        let base = fs::read_to_string(&path)?;
         return Ok(base);
     }
 
     // BLANK returns nothing, so no more processing needs to be done
     if filename == "BLANK" {
-        log!(opts, format!("\t\t\treturning blank: {}", key), 4);
         return Ok(String::new());
     };
 
     // DEFAULT override for patterns defined higher in chain
     if filename == "DEFAULT" {
-        log!(opts, "\t\t\tdefault pattern", 4);
         filename = "default".to_string();
     }
 
-    log!(opts, "\t\t\tbuilding path from key", 4);
     let pattern_path = key.replace('.', "/") + "/" + &filename;
-    let mut path = opts.pattern.join(pattern_path);
+    let mut path = file.opts.pattern.join(pattern_path);
     path.set_extension("meta");
 
-    log!(opts, "\t\t\tparsing file", 4);
-    let mut pattern = MetaFile::build(path)?;
+    let mut pattern = MetaFile::build(path, file.opts)?;
 
     // copy over maps for expanding contained variables
     pattern.merge(&file);
 
-    log!(opts, "\t\t\tbuilding pattern", 4);
-    metafile_to_string(&pattern, opts, Some(key))
+    metafile_to_string(&pattern)
+}
+
+fn get_variable(key: &str, file: &MetaFile) -> Result<String> {
+    todo!()
 }
 
 fn find_dest(path: &Path, opts: &Options) -> Result<PathBuf> {
-    let path = path
-        .canonicalize()
-        .wrap_err_with(|| eyre!("could not get absolute path: {}\n", path.display()))?;
+    let path = path.canonicalize()?;
 
     let path = opts.build.join(path.strip_prefix(&opts.source)?);
     let mut path = PathBuf::from(path);
@@ -174,38 +155,45 @@ fn find_dest(path: &Path, opts: &Options) -> Result<PathBuf> {
     Ok(path)
 }
 
-fn expand_arrays(output: String, file: &MetaFile, name: Option<&str>) -> Result<String> {
+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(|section| {
-            if let Source::Sub(Substitution::Array(array)) = section {
+        .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(|array| {
-            let key: String;
+        .map(|key| {
+            let y: String;
             // concat array to pattern name to get key in HashMap
-            if let Some(name) = name {
-                key = name.to_owned() + "." + array;
+            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 {
-                // keys for arrays in this file don't have a preceding pattern
-                key = array.to_string();
+                value = &[];
             }
-            let value = file.get_arr(&key).unwrap_or_default();
-            (array.to_string(), value)
+
+            (key.to_string(), value)
         })
         .collect();
 
-    let mut expanded = String::new();
     // 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 = output.clone();
+        let mut str = input.clone();
         // replace each key in the file
         for (key, val) in map.iter() {
             if let Some(value) = val.get(i) {
@@ -258,7 +246,7 @@ mod tests {
         let opts = build_options()?;
         let path = opts.source.join("dir1/sub_dir1/deep2/deep.meta");
         let expanded = "<html>\n<body>\nGOOD\n</body>\n</html>\n";
-        build_metafile(&MetaFile::build(path)?, &opts)?;
+        build_metafile(&MetaFile::build(path, &opts)?)?;
         assert_eq!(
             std::fs::read_to_string(opts.build.join("dir1/sub_dir1/deep2/deep.html"))?,
             expanded
@@ -269,8 +257,8 @@ mod tests {
     #[test]
     fn test_get_pattern() -> Result<()> {
         let opts = build_options()?;
-        let file = MetaFile::new();
-        let pat = get_pattern("header", &file, &opts)?;
+        let file = MetaFile::new(&opts);
+        let pat = get_pattern("header", &file)?;
         assert_eq!(pat, "<header>HEADER</header>");
         Ok(())
     }
index d1a349aa623cd04c9fd2c7ba98469e05b7f952fc..8a76317d0209615214c6dc8c2c7cd256424a7886 100644 (file)
@@ -4,30 +4,30 @@ use std::collections::HashMap;
 use std::{fs, path::PathBuf};
 
 #[derive(Debug, Clone)]
-pub struct MetaFile {
+pub struct MetaFile<'a> {
+    pub opts: &'a Options,
     pub path: PathBuf,
+    pub header: HashMap<String, String>,
     pub variables: HashMap<String, String>,
     pub arrays: HashMap<String, Vec<String>>,
     pub patterns: HashMap<String, String>,
-    pub source: Vec<Source>,
+    pub source: Vec<Src>,
 }
 
-impl MetaFile {
-    pub fn build(path: PathBuf) -> Result<Self> {
+impl<'a> MetaFile<'a> {
+    pub fn build(path: PathBuf, opts: &'a Options) -> Result<Self> {
         let str = fs::read_to_string(&path)?;
-        let mut metafile = MetaFile::build_from_string(str)?;
+        let mut metafile = parse_file(str, opts)?;
         metafile.path = path.to_path_buf();
+        metafile.opts = opts;
         Ok(metafile)
     }
 
-    fn build_from_string(string: String) -> Result<Self> {
-        let metafile = parse_file(string)?;
-        Ok(metafile)
-    }
-
-    pub fn new() -> Self {
+    pub fn new(opts: &'a Options) -> Self {
         Self {
+            opts,
             path: PathBuf::new(),
+            header: HashMap::new(),
             variables: HashMap::new(),
             arrays: HashMap::new(),
             patterns: HashMap::new(),
@@ -35,6 +35,18 @@ impl MetaFile {
         }
     }
 
+    pub fn name(&self) -> Result<String> {
+        if self.path.starts_with(&self.opts.source) {
+            let name = self.path.strip_prefix(&self.opts.source)?;
+            let name = name.to_string_lossy().to_string().replace('/', ".");
+            Ok(name)
+        } else {
+            let name = self.path.strip_prefix(&self.opts.pattern)?;
+            let name = name.to_string_lossy().to_string().replace('/', ".");
+            Ok(name)
+        }
+    }
+
     pub fn get_var(&self, key: &str) -> Option<&String> {
         self.variables.get(key)
     }
@@ -71,30 +83,30 @@ impl MetaFile {
 
 #[macro_export]
 macro_rules! source (
-    (var($s:expr)) => { Source::Sub(Substitution::Variable($s.to_string()))};
-    (arr($s:expr)) => { Source::Sub(Substitution::Array($s.to_string()))};
-    (pat($s:expr)) => { Source::Sub(Substitution::Pattern($s.to_string()))};
-    ($s:expr) => { Source::Str($s.to_string())};
+    (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()))};
+    ($s:expr) => { Src::Str($s.to_string())};
 );
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum Source {
+pub enum Src {
     Str(String),
-    Sub(Substitution),
+    Sub(Sub),
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum Substitution {
-    Variable(String),
-    Array(String),
-    Pattern(String),
+pub enum Sub {
+    Var(String),
+    Arr(String),
+    Pat(String),
 }
 
 pub struct DirNode<'a> {
     path: PathBuf,
     opts: &'a Options,
-    global: MetaFile,
-    files: Vec<MetaFile>,
+    global: MetaFile<'a>,
+    files: Vec<MetaFile<'a>>,
     dirs: Vec<DirNode<'a>>,
 }
 
@@ -106,7 +118,7 @@ impl<'a> DirNode<'a> {
 
         let files: Vec<MetaFile> = Vec::new();
         let dirs: Vec<DirNode> = Vec::new();
-        let global = MetaFile::new();
+        let global = MetaFile::new(opts);
 
         Ok(Self {
             path: path.to_path_buf(),
@@ -127,11 +139,11 @@ impl<'a> DirNode<'a> {
                 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)?;
+                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)?;
+                let file = MetaFile::build(file, self.opts)?;
                 self.files.push(file)
             }
         }
@@ -142,7 +154,7 @@ impl<'a> DirNode<'a> {
     pub fn build_files(&mut self) -> Result<()> {
         for file in self.files.iter_mut() {
             file.merge(&self.global);
-            if let Err(e) = build_metafile(file, self.opts) {
+            if let Err(e) = build_metafile(file) {
                 if self.opts.force {
                     // print a line to stderr about failure but continue with other files
                     eprintln!("ignoring {}: {}", file.path.display(), e);
index b9137f0af0133b0fec4615847c414c2a8d848682..eb9bf46d34d97696f02a3abe96b747427204a1fe 100644 (file)
@@ -114,7 +114,7 @@ impl TryFrom<Opts> for Options {
 
 #[macro_export]
 macro_rules! log {
-    ($opts:ident, $string:expr, $level:expr) => {
+    ($opts:expr, $string:expr, $level:expr) => {
         if $opts.verbose >= $level && !$opts.quiet {
             println!("{}", $string);
         }
index 281e0dd31b9ab3629558b26e9f7b582e10ea0841..1eab21919413d660be0604ce1c55aeca0ad39b9d 100644 (file)
@@ -1,4 +1,4 @@
-use crate::{source, MetaFile, Source, Substitution};
+use crate::{source, MetaFile, Options, Src, Sub};
 use color_eyre::{eyre::WrapErr, Result};
 use pest::{
     iterators::{Pair, Pairs},
@@ -10,23 +10,24 @@ use std::collections::HashMap;
 #[grammar = "meta.pest"]
 pub struct MetaParser;
 
-pub fn parse_file<'a>(file: String) -> Result<MetaFile> {
+pub fn parse_file<'a>(file: String, opts: &'a Options) -> Result<MetaFile<'a>> {
     let meta_source = MetaParser::parse(Rule::file, &file)
         .wrap_err("parser error")?
         .next()
         .unwrap();
 
-    let metafile = parse_pair(meta_source);
+    let metafile = parse_pair(meta_source, opts);
     Ok(metafile)
 }
 
-fn parse_pair(pair: Pair<Rule>) -> MetaFile {
-    let mut meta_file = MetaFile::new();
+fn parse_pair<'a>(pair: Pair<Rule>, opts: &'a Options) -> MetaFile<'a> {
+    let mut meta_file = MetaFile::new(opts);
 
     if Rule::file == pair.as_rule() {
         for pair in pair.into_inner() {
             match pair.as_rule() {
                 Rule::source => meta_file.source = parse_source(pair.into_inner()),
+                Rule::header => meta_file.header = 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()),
@@ -64,7 +65,7 @@ fn parse_array_defs(pairs: Pairs<Rule>) -> HashMap<String, Vec<String>> {
     map
 }
 
-fn parse_source(pairs: Pairs<Rule>) -> Vec<Source> {
+fn parse_source(pairs: Pairs<Rule>) -> Vec<Src> {
     let mut vec = Vec::new();
     for pair in pairs {
         match pair.as_rule() {
@@ -161,8 +162,9 @@ mod tests {
 
     macro_rules! test_str (
         ($s: expr) => {
+            let opts = Options::new();
             let str = $s.to_string();
-            parse_file(str).unwrap();
+            parse_file(str, &opts).unwrap();
         };
     );