diff options
author | Qrius <[email protected]> | 2024-09-26 00:11:05 +0200 |
---|---|---|
committer | Qrius <[email protected]> | 2024-09-26 00:11:05 +0200 |
commit | fce39a1c9a44d4a7eb2234f5d15b86d1c3177ffa (patch) | |
tree | 8582ef41441a007fb5d59d71b6d8ed60b2696853 | |
parent | f8df856d45acb619ebc7766b17bded03bac8d851 (diff) | |
download | skaldpress-fce39a1c9a44d4a7eb2234f5d15b86d1c3177ffa.tar.gz skaldpress-fce39a1c9a44d4a7eb2234f5d15b86d1c3177ffa.zip |
Do a bunch of QOL things, add some skaldpress specific macros, add
second run, etc
-rw-r--r-- | src/macro_processor/error.rs | 8 | ||||
-rw-r--r-- | src/macro_processor/macro_processor.rs | 4 | ||||
-rw-r--r-- | src/skaldpress/main.rs | 171 | ||||
-rw-r--r-- | src/skaldpress/parseopts.rs | 19 |
4 files changed, 165 insertions, 37 deletions
diff --git a/src/macro_processor/error.rs b/src/macro_processor/error.rs index 074a8ef..cd94c4d 100644 --- a/src/macro_processor/error.rs +++ b/src/macro_processor/error.rs @@ -5,6 +5,7 @@ use std::fmt; pub enum SMPError { IncludeError(u8, std::io::Error, String), ShellCommandError(u8, Box<dyn Error>), + UnknownError(u8, Option<Box<dyn Error>>), } impl fmt::Display for SMPError { @@ -16,6 +17,13 @@ impl fmt::Display for SMPError { SMPError::ShellCommandError(code, e) => { write!(f, "[SMP{}] Error running shell command \"{:#?}\"", code, e) } + SMPError::UnknownError(code, e) => { + write!( + f, + "[SMP{}] Unknown macro processing error occurred \"{:#?}\"", + code, e + ) + } } } } diff --git a/src/macro_processor/macro_processor.rs b/src/macro_processor/macro_processor.rs index 82a94ef..68347d0 100644 --- a/src/macro_processor/macro_processor.rs +++ b/src/macro_processor/macro_processor.rs @@ -193,6 +193,7 @@ fn smp_builtin_expr( } /// Types of macros, this is to make it easy to store both functions and strings +#[derive(Clone)] pub enum MacroType { /// When expanded, the associated function will be expanded Function( @@ -217,9 +218,10 @@ enum ParserState { /// Defines a MacroProcessor object, with it's associated state /// the state mostly includes the defined macros +#[derive(Clone)] pub struct MacroProcessor { /// All currently defined macros in this MacroProcessor - macros: HashMap<String, MacroType>, + pub macros: HashMap<String, MacroType>, } impl MacroProcessor { diff --git a/src/skaldpress/main.rs b/src/skaldpress/main.rs index 6f00963..1664272 100644 --- a/src/skaldpress/main.rs +++ b/src/skaldpress/main.rs @@ -1,3 +1,5 @@ +use skaldpress::macro_processor::error::SMPError; +use skaldpress::macro_processor::macro_processor::MacroType; use skaldpress::skaldpress::parseopts::{parseopts, Opts}; use std::collections::HashMap; use std::fs; @@ -13,10 +15,10 @@ use skaldpress::skaldpress::error::{ use skaldpress::skaldpress::metadata_parser::extract_parse_yaml_metadata; use skaldpress::skaldpress::metadata_parser::YamlValue; -/// Cache of all compiled docs -static mut COMPILED_DOCS: Vec<CompiledFile> = Vec::new(); -/// Keeps all of the compiled docs sorted by the tag name, variable will be ASSUMED initialized!!! -static mut COMPILED_DOCS_BY_TAG: Option<HashMap<String, Vec<usize>>> = None; +/// Cache of all compiled files +static mut COMPILED_FILES: Vec<CompiledFile> = Vec::new(); +/// Keeps all of the compiled files sorted by the tag name, variable will be ASSUMED initialized!!! +static mut COMPILED_FILES_BY_TAG: Option<HashMap<String, Vec<usize>>> = None; /// Information about a file that has been compiled, this is likely going to be registered in the /// global space. @@ -28,6 +30,79 @@ struct CompiledFile { /// The compiled files destination-extension, this will be the templates extension, /// or the file itself if it didn't have a extension extension: String, + source_path: String, +} + +fn sp_template( + smp: &mut MacroProcessor, + macro_name: &str, + args: &mut [String], +) -> Result<String, SMPError> { + if args.len() < 1 { + return Ok(macro_name.to_string()); + } + + let template_file = &args[0]; + let template = fs::read_to_string(&template_file) + .map_err(|e| SMPError::UnknownError(10, Some(Box::new(e))))?; + + smp.define_macro_string(String::from("CONTENT"), args[1].clone()); + smp.process_input(&template) +} + +fn sp_all_tagged_by( + smp: &mut MacroProcessor, + macro_name: &str, + args: &mut [String], +) -> Result<String, SMPError> { + if args.len() < 1 { + return Ok(macro_name.to_string()); + } + + let compiled_files: &Vec<CompiledFile>; + let compiled_files_by_tag; + unsafe { + compiled_files_by_tag = COMPILED_FILES_BY_TAG.as_ref().unwrap(); + compiled_files = COMPILED_FILES.as_ref(); + } + let Some(tagged_files) = compiled_files_by_tag.get(&args[0]) else { + println!("\x1b[35mNo tags for {}\x1b[0m", args[0]); + return Ok(String::new()); + }; + + let mut out = String::new(); + + for doc_i in tagged_files { + let file = &compiled_files[*doc_i]; + let mut smp_local = macro_processor(&file.metadata, Some(smp)); + out.push_str(&sp_template( + &mut smp_local, + "template", + &mut [args[1].clone(), file.content.clone()], + )?); + } + Ok(out) +} + +fn macro_processor( + metadata: &HashMap<String, YamlValue>, + old_macro_processor: Option<&MacroProcessor>, +) -> MacroProcessor { + let mut macro_processor = match old_macro_processor { + Some(macro_processor) => macro_processor.clone(), + None => MacroProcessor::new(), + }; + + macro_processor.define_macro( + String::from("all_tagged_by"), + MacroType::Function(sp_all_tagged_by), + ); + macro_processor.define_macro(String::from("template"), MacroType::Function(sp_template)); + for (key, value) in metadata { + macro_processor.define_macro_string(format!("METADATA_{}", key), value.to_string()); + } + + macro_processor } /// Will attempt to compile a specific file, potentially storing some state about the file @@ -52,10 +127,7 @@ fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, Skaldpres _ => file_content.to_string(), }; - let mut macro_processor = MacroProcessor::new(); - for (key, value) in &map { - macro_processor.define_macro_string(format!("METADATA_{}", key), value.to_string()); - } + let mut macro_processor = macro_processor(&map, None); let Some(template) = &map.get("template") else { let file_content = macro_processor @@ -65,6 +137,7 @@ fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, Skaldpres content: file_content, metadata: map, extension: String::from(extension.to_str().unwrap_or("")), + source_path: String::from(file_path.to_str().unwrap_or("")), }); }; @@ -88,38 +161,63 @@ fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, Skaldpres content, metadata: map, extension: String::from(template_extension.to_str().unwrap_or("")), + source_path: String::from(file_path.to_str().unwrap_or("")), }) } +fn cached_file_id_by_path(source_path: &String) -> Option<usize> { + let compiled_files: &Vec<CompiledFile>; + unsafe { + compiled_files = COMPILED_FILES.as_ref(); + } + + for i in 0..compiled_files.len() { + if &compiled_files[i].source_path == source_path { + return Some(i); + } + } + None +} + fn compile_file_and_write( source_file_path: &Path, opts: &Opts, ) -> Result<(), Box<dyn std::error::Error>> { - let compiled_file = compile_file(&source_file_path, opts)?; + let mut compiled_file = compile_file(&source_file_path, opts)?; let cfile: &CompiledFile; let cfile_i: usize; - // If this program ever becomes threaded, make sure to add a mutex for this - unsafe { - COMPILED_DOCS.push(compiled_file); - cfile_i = COMPILED_DOCS.len() - 1; - cfile = &COMPILED_DOCS[cfile_i]; - } - if let Some(tags) = &cfile.metadata.get("tags") { - if let YamlValue::List(tags) = tags { - let compiled_docs_by_tag: &mut HashMap<String, Vec<usize>>; - unsafe { - compiled_docs_by_tag = COMPILED_DOCS_BY_TAG.as_mut().unwrap(); - } - for tag in tags { - if !compiled_docs_by_tag.contains_key(tag) { - (*compiled_docs_by_tag).insert(tag.clone(), Vec::new()); + if opts.first_run { + // If this program ever becomes threaded, make sure to add a mutex for this + unsafe { + COMPILED_FILES.push(compiled_file); + cfile_i = COMPILED_FILES.len() - 1; + cfile = &COMPILED_FILES[cfile_i]; + } + + if let Some(tags) = &cfile.metadata.get("tags") { + if let YamlValue::List(tags) = tags { + let compiled_files_by_tag: &mut HashMap<String, Vec<usize>>; + unsafe { + compiled_files_by_tag = COMPILED_FILES_BY_TAG.as_mut().unwrap(); + } + for tag in tags { + if !compiled_files_by_tag.contains_key(tag) { + (*compiled_files_by_tag).insert(tag.clone(), Vec::new()); + } + // Assuming the creation of the tag was sucessfull, so just unwrapping + (*compiled_files_by_tag).get_mut(tag).unwrap().push(cfile_i); } - // Assuming the creation of the tag was sucessfull, so just unwrapping - (*compiled_docs_by_tag).get_mut(tag).unwrap().push(cfile_i); } } + } else { + cfile_i = cached_file_id_by_path(&compiled_file.source_path).unwrap(); + unsafe { + std::mem::swap(&mut COMPILED_FILES[cfile_i], &mut compiled_file); + cfile = &COMPILED_FILES[cfile_i]; + } + drop(compiled_file); } if let Some(skip_build) = &cfile.metadata.get("skip_build") { @@ -183,7 +281,11 @@ fn compile_files_in_directory(directory: &Path, opts: &Opts) -> Result<(), Skald } }; - if metadata.is_file() { + let should_compile = (opts.filter.len() == 0) + || opts + .filter + .contains(&path.as_path().to_str().unwrap_or("").to_string()); + if metadata.is_file() && should_compile { println!("< Compiling {:#?}", path.as_path()); if let Err(e) = compile_file_and_write(path.as_path(), opts) { println!( @@ -207,20 +309,19 @@ fn compile_files_in_directory(directory: &Path, opts: &Opts) -> Result<(), Skald fn main() { unsafe { - COMPILED_DOCS_BY_TAG = Some(HashMap::new()); + COMPILED_FILES_BY_TAG = Some(HashMap::new()); } - let opts = parseopts().build(); + let mut opts = parseopts().build(); println!("Removing {:#?}", opts.build_dir); let _ = std::fs::remove_dir_all(Path::new(&opts.build_dir)); - // Should run twice, or at least the files which uses macros which has to be generated during - // runtime + // Running twice, we should make macro_processor emit list of macros which was executed, such + // that we can re-compile only the files we need to + let _ = compile_files_in_directory(Path::new(&opts.content_dir), &opts); + println!("Rerun compilation"); + opts.first_run = false; let _ = compile_files_in_directory(Path::new(&opts.content_dir), &opts); - - unsafe { - println!("{:#?}", COMPILED_DOCS_BY_TAG); - } // Just for testing //compile_file_and_write(Path::new("content/test.html")); diff --git a/src/skaldpress/parseopts.rs b/src/skaldpress/parseopts.rs index bca6a73..36fbf13 100644 --- a/src/skaldpress/parseopts.rs +++ b/src/skaldpress/parseopts.rs @@ -6,6 +6,10 @@ pub struct Opts { pub content_dir: String, /// Directory where to put result, THIS WILL BE DELETED FIRST pub build_dir: String, + /// Filters, limit to compiling specified files + pub filter: Vec<String>, + + pub first_run: bool, } /// Struct containing command line options @@ -17,6 +21,8 @@ pub struct OptsBuilder { pub content_dir: String, /// Directory where to put result, THIS WILL BE DELETED FIRST pub build_dir: String, + /// Filters, limit to compiling specified files + pub filter: Vec<String>, } impl OptsBuilder { @@ -25,6 +31,8 @@ impl OptsBuilder { template_dir: self.template_dir, content_dir: self.content_dir, build_dir: self.build_dir, + filter: self.filter, + first_run: true, } } } @@ -41,6 +49,7 @@ macro_rules! parseopts_panic { println!(" -s, --source DIR Location of content to compile"); println!(" -t, --templates DIR Location of templates"); println!(" -o, --out DIR Location to write compiled data, THIS WILL BE DELETED AND RECREATED!"); + println!(" -f, --filter FILES Only run on files, comma separated string"); std::process::exit(1); }; } @@ -77,6 +86,13 @@ pub fn parseopt(opts: &mut OptsBuilder, arg: &str, value: Option<String>, progna }; opts.template_dir = templates; } + "f" | "filter" => { + let Some(filter) = value else { + println!("Missing value for {}\n", arg); + parseopts_panic!(progname); + }; + opts.filter = filter.split(",").map(|x| x.to_string()).collect(); + } "help" => { parseopts_panic!(progname); } @@ -97,6 +113,7 @@ pub fn parseopts() -> OptsBuilder { build_dir: String::from("build/"), template_dir: String::from("templates/"), content_dir: String::from("content/"), + filter: Vec::new(), }; let mut it = std::env::args(); @@ -129,7 +146,7 @@ pub fn parseopts() -> OptsBuilder { arg.remove(0); for arg_name in arg.chars() { match arg_name { - 'o' | 's' | 't' => { + 'o' | 's' | 't' | 'f' => { parseopt(&mut opts, &arg_name.to_string(), it.next(), &progname); } _ => { |