From c69b17d3436fa4d03182003038c805b6c5570699 Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Sun, 14 May 2023 13:13:52 -0500 Subject: [PATCH] restructure options into metafiles --- src/builder.rs | 126 ++++++++++++++++++++++-------------------------- src/metafile.rs | 66 ++++++++++++++----------- src/options.rs | 2 +- src/parser.rs | 16 +++--- 4 files changed, 106 insertions(+), 104 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 7a353ed..b1b757a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -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 { - 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 { + 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 { 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 { +fn metafile_to_string(file: &MetaFile) -> Result { 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 { 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 { .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 { } } -fn get_pattern(key: &str, file: &MetaFile, opts: &Options) -> Result { +fn get_pattern(key: &str, file: &MetaFile) -> Result { // 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 { // 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 { + todo!() } fn find_dest(path: &Path, opts: &Options) -> Result { - 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 { Ok(path) } -fn expand_arrays(output: String, file: &MetaFile, name: Option<&str>) -> Result { +fn expand_arrays(input: String, file: &MetaFile) -> Result { let map: HashMap = 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 = "\n\nGOOD\n\n\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
"); Ok(()) } diff --git a/src/metafile.rs b/src/metafile.rs index d1a349a..8a76317 100644 --- a/src/metafile.rs +++ b/src/metafile.rs @@ -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, pub variables: HashMap, pub arrays: HashMap>, pub patterns: HashMap, - pub source: Vec, + pub source: Vec, } -impl MetaFile { - pub fn build(path: PathBuf) -> Result { +impl<'a> MetaFile<'a> { + pub fn build(path: PathBuf, opts: &'a Options) -> Result { 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 { - 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 { + 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, + global: MetaFile<'a>, + files: Vec>, dirs: Vec>, } @@ -106,7 +118,7 @@ impl<'a> DirNode<'a> { let files: Vec = Vec::new(); let dirs: Vec = 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); diff --git a/src/options.rs b/src/options.rs index b9137f0..eb9bf46 100644 --- a/src/options.rs +++ b/src/options.rs @@ -114,7 +114,7 @@ impl TryFrom 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); } diff --git a/src/parser.rs b/src/parser.rs index 281e0dd..1eab219 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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 { +pub fn parse_file<'a>(file: String, opts: &'a Options) -> Result> { 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) -> MetaFile { - let mut meta_file = MetaFile::new(); +fn parse_pair<'a>(pair: Pair, 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) -> HashMap> { map } -fn parse_source(pairs: Pairs) -> Vec { +fn parse_source(pairs: Pairs) -> Vec { 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(); }; ); -- 2.44.2