summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorQrius <[email protected]>2024-09-26 00:11:05 +0200
committerQrius <[email protected]>2024-09-26 00:11:05 +0200
commitfce39a1c9a44d4a7eb2234f5d15b86d1c3177ffa (patch)
tree8582ef41441a007fb5d59d71b6d8ed60b2696853 /src
parentf8df856d45acb619ebc7766b17bded03bac8d851 (diff)
downloadskaldpress-fce39a1c9a44d4a7eb2234f5d15b86d1c3177ffa.tar.gz
skaldpress-fce39a1c9a44d4a7eb2234f5d15b86d1c3177ffa.zip
Do a bunch of QOL things, add some skaldpress specific macros, add
second run, etc
Diffstat (limited to 'src')
-rw-r--r--src/macro_processor/error.rs8
-rw-r--r--src/macro_processor/macro_processor.rs4
-rw-r--r--src/skaldpress/main.rs171
-rw-r--r--src/skaldpress/parseopts.rs19
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);
}
_ => {