summaryrefslogtreecommitdiff
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
commit6af9f573fce9c167487e10c7327feff357327d6a (patch)
tree1969c1bfad6de89ad7da3b9904c20ef78b14df9c
parentc7e3570f90ddd495c0a27969e738de5a21bbccff (diff)
downloadskaldpress-6af9f573fce9c167487e10c7327feff357327d6a.tar.gz
skaldpress-6af9f573fce9c167487e10c7327feff357327d6a.zip
Change around some things, don't clone MacroProcessor for nested templates, so we can keep state from parents
-rw-r--r--skaldpress.15
-rw-r--r--src/macro_processor/macro_processor.rs188
-rw-r--r--src/skaldpress/main.rs73
3 files changed, 244 insertions, 22 deletions
diff --git a/skaldpress.1 b/skaldpress.1
index dfa8ea8..eec8dd3 100644
--- a/skaldpress.1
+++ b/skaldpress.1
@@ -49,6 +49,11 @@ since it is recursive, any metadata in templates will overwrite any metadata fro
but will keep metadata that is not overwritten.
This means templates can add additional context.
+.IP "\fBkeep_states\fR"
+List or string where every listed state/variable will be kept for subsequenct compilations.
+Meaning that you can e.g. construct a array in the first compilation, which is then used the second time around.
+Setting this, means that the file will always be recompiled, regardeless of other instances (unless a \fB--filter\fR is set).
+
.SH OPTIONS
.IP "\fB-o, --out, --output\fR \fIpath\fR
Specifies the directory to output the compiled files to, defaults to \fIbuild\fR.
diff --git a/src/macro_processor/macro_processor.rs b/src/macro_processor/macro_processor.rs
index 7839963..ee7e269 100644
--- a/src/macro_processor/macro_processor.rs
+++ b/src/macro_processor/macro_processor.rs
@@ -56,6 +56,52 @@ fn smp_builtin_define(
Ok(String::new())
}
+/// Builtin for undefining a macro
+fn smp_builtin_undefine(
+ smp: &mut MacroProcessor,
+ macro_name: &str,
+ args: &mut [String],
+) -> Result<String, SMPError> {
+ if args.len() < 1 {
+ smp.warnings
+ .push(MacroProcessorWarning::from_macro_invocation(
+ macro_name,
+ args,
+ format!("Wrong number of arguments, expected at least 1"),
+ ));
+ return Ok(macro_name.to_string());
+ }
+ if let None = smp.macros.remove(&args[0]) {
+ smp.warnings
+ .push(MacroProcessorWarning::from_macro_invocation(
+ macro_name,
+ args,
+ format!("Macro already not defined"),
+ ));
+ }
+ Ok(String::new())
+}
+
+/// Builtin for defining a new macro
+fn smp_builtin_define_array(
+ smp: &mut MacroProcessor,
+ macro_name: &str,
+ args: &mut [String],
+) -> Result<String, SMPError> {
+ if args.len() < 1 {
+ smp.warnings
+ .push(MacroProcessorWarning::from_macro_invocation(
+ macro_name,
+ args,
+ format!("Wrong number of arguments, expected at least 1"),
+ ));
+ return Ok(macro_name.to_string());
+ }
+ let arg0 = smp.process_input(&args[0])?;
+ smp.define_macro(arg0, MacroType::Array(Vec::new()));
+ Ok(String::new())
+}
+
/// If macro is defined, return second argument, else return third argument if provided
fn smp_builtin_ifdef(
smp: &mut MacroProcessor,
@@ -295,19 +341,77 @@ fn smp_builtin_array_push(
macro_name: &str,
args: &mut [String],
) -> Result<String, SMPError> {
- for arg in args.iter() {
- if let Err(e) = smp.array_push(macro_name, MacroType::String(arg.to_string())) {
- smp.warnings
- .push(MacroProcessorWarning::from_macro_invocation(
- macro_name,
- args,
- format!("Error executing array_push ({:?})", e),
- ));
- }
+ let mut args_iter = args.iter();
+ let Some(array_name) = args_iter.next() else {
+ smp.warnings
+ .push(MacroProcessorWarning::from_macro_invocation(
+ macro_name,
+ args,
+ format!("Invalid arguments to array_push"),
+ ));
+ return Ok(String::new());
+ };
+ let mut def = Vec::new();
+ while let Some(arg) = args_iter.next() {
+ def.push(MacroType::String(smp.process_input(arg)?));
+ }
+ if let Err(e) = smp.array_push(array_name, MacroType::Array(def)) {
+ smp.warnings
+ .push(MacroProcessorWarning::from_macro_invocation(
+ macro_name,
+ args,
+ format!("Error executing array_push ({:?})", e),
+ ));
}
Ok(String::new())
}
+/// Push any arguments to array macro
+/// Process each element in a array as a macro-invokation on the second argument
+/// Not the best way to do this, it is not sensibly recursive.
+fn smp_builtin_array_each(
+ smp: &mut MacroProcessor,
+ macro_name: &str,
+ args: &mut [String],
+) -> Result<String, SMPError> {
+ let Some(macro_body) = smp.macros.get(&args[0]) else {
+ smp.warnings
+ .push(MacroProcessorWarning::from_macro_invocation(
+ macro_name,
+ args,
+ format!("{:?} is not a macro", args[0]),
+ ));
+ return Ok(String::new());
+ };
+ let MacroType::Array(array) = macro_body else {
+ smp.warnings
+ .push(MacroProcessorWarning::from_macro_invocation(
+ macro_name,
+ args,
+ format!("{:?} is not a macro of type array", args[0]),
+ ));
+ return Ok(String::new());
+ };
+ let mut out = String::new();
+ for el in array.clone() {
+ let exp = match el {
+ MacroType::String(s) => smp.expand_macro(&args[1], &mut [s])?,
+ MacroType::Array(a) => {
+ let mut out = Vec::new();
+ for _el in a {
+ out.push(_el.to_string());
+ }
+ let expanded = smp.expand_macro(&args[1], &mut out)?;
+ smp.process_input(&expanded)?
+ }
+ _ => String::new(),
+ };
+ out.push_str(&exp);
+ }
+
+ Ok(out)
+}
+
#[cfg(feature = "time")]
fn smp_builtin_format_time(
smp: &mut MacroProcessor,
@@ -354,7 +458,7 @@ fn macro_name_clean<'a>(macro_name: &'a str) -> &'a str {
}
/// Types of macros, this is to make it easy to store both functions and strings
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub enum MacroType {
/// When expanded, the associated function will be expanded
Function(
@@ -369,10 +473,31 @@ pub enum MacroType {
Array(Vec<MacroType>),
}
+use std::string::ToString;
+impl ToString for MacroType {
+ fn to_string(&self) -> String {
+ match self {
+ MacroType::String(s) => s.to_string(),
+ MacroType::Array(a) => {
+ let mut out = String::from("[");
+ for (i, el) in a.iter().enumerate() {
+ out.push_str(&el.to_string());
+ if i < (a.len() - 1) {
+ out.push_str(", ");
+ }
+ }
+ out
+ }
+ MacroType::Function(_a) => String::from("Function()"),
+ }
+ }
+}
+
/// Possible parser states
#[derive(Debug, PartialEq)]
enum ParserState {
Normal,
+ InQuotes,
InMacro,
InMacroArgs,
DNL,
@@ -432,6 +557,14 @@ impl MacroProcessor {
MacroType::Function(smp_builtin_define),
);
self.define_macro(
+ String::from("define_array"),
+ MacroType::Function(smp_builtin_define_array),
+ );
+ self.define_macro(
+ String::from("undefine"),
+ MacroType::Function(smp_builtin_undefine),
+ );
+ self.define_macro(
String::from("ifdef"),
MacroType::Function(smp_builtin_ifdef),
);
@@ -465,11 +598,21 @@ impl MacroProcessor {
String::from("array_push"),
MacroType::Function(smp_builtin_array_push),
);
+ self.define_macro(
+ String::from("array_each"),
+ MacroType::Function(smp_builtin_array_each),
+ );
#[cfg(feature = "time")]
self.define_macro(
String::from("format_time"),
MacroType::Function(smp_builtin_format_time),
);
+ #[cfg(feature = "webring")]
+ self.define_macro(
+ String::from("webring_rss"),
+ MacroType::Function(smp_builtin_webring_rss),
+ );
+
// format('Result id %d', 3282)
}
@@ -548,7 +691,7 @@ impl MacroProcessor {
// The below is a okay _idea_, but I am not sure if I want to have this syntax for
// functions defined in normal smp code
for (i, arg) in args.iter().enumerate() {
- let placeholder = format!("${}", i + 1);
+ let placeholder = format!("${}", i);
expanded = expanded.replace(&placeholder, arg);
}
self.process_input(&expanded)
@@ -584,6 +727,9 @@ impl MacroProcessor {
let mut in_quote_single = false;
let mut in_quote_double = false;
+ const QUOTE_START: char = '`';
+ const QUOTE_END: char = '\'';
+ let mut quote_level = 0;
let mut parens_level = 0;
for (i, c) in input.char_indices() {
@@ -606,10 +752,30 @@ impl MacroProcessor {
if c.is_alphanumeric() {
state = ParserState::InMacro;
macro_name.push(c);
+ } else if c == QUOTE_START {
+ state = ParserState::InQuotes;
+ quote_level += 1;
} else {
output.push(c);
}
}
+ ParserState::InQuotes => match c {
+ QUOTE_START => {
+ quote_level += 1;
+ output.push(c);
+ }
+ QUOTE_END => {
+ quote_level -= 1;
+ if quote_level == 0 {
+ state = ParserState::Normal;
+ } else {
+ output.push(c);
+ }
+ }
+ _ => {
+ output.push(c);
+ }
+ },
ParserState::InMacro => {
if c.is_alphanumeric() || c == '_' {
macro_name.push(c);
diff --git a/src/skaldpress/main.rs b/src/skaldpress/main.rs
index 425b2d7..69f934a 100644
--- a/src/skaldpress/main.rs
+++ b/src/skaldpress/main.rs
@@ -31,6 +31,7 @@ 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,
+ stored_smp_state: HashMap<String, MacroType>,
source_path: String,
needs_recompilation: bool,
}
@@ -142,7 +143,8 @@ fn sp_all_tagged_by(
for doc_i in tagged_files {
let file = &compiled_files[doc_i];
- let mut smp_local = macro_processor(&file.metadata, Some(smp));
+ let mut smp_local = smp.clone();
+ macro_processor_initialize(&file.metadata, &mut smp_local, None);
out.push_str(&sp_template(
&mut smp_local,
"template",
@@ -153,15 +155,12 @@ fn sp_all_tagged_by(
Ok(out)
}
-fn macro_processor(
+fn macro_processor_initialize(
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(),
- };
-
+ old_macro_processor: &mut MacroProcessor,
+ additional_state: Option<&HashMap<String, MacroType>>,
+) {
+ let macro_processor = old_macro_processor;
macro_processor.define_macro(
String::from("all_tagged_by"),
MacroType::Function(sp_all_tagged_by),
@@ -170,8 +169,18 @@ fn macro_processor(
for (key, value) in metadata {
macro_processor.define_macro_string(format!("METADATA_{}", key), value.to_string());
}
+ if let Some(additional_state) = additional_state {
+ for (key, value) in additional_state {
+ macro_processor.define_macro(key.to_string(), value.clone());
+ }
+ }
- macro_processor
+ if let Some(publish_date) = metadata.get("publish_date") {
+ if !metadata.contains_key("change_date") {
+ macro_processor
+ .define_macro_string(format!("METADATA_change_date"), publish_date.to_string());
+ }
+ }
}
fn get_template_path(template: &str, opts: &Opts) -> String {
@@ -224,6 +233,9 @@ fn wrap_template(
}
fn needs_recompilation(macro_processor: &MacroProcessor) -> bool {
+ if macro_processor.macros.contains_key("METADATA_keep_states") {
+ return true;
+ }
for (macro_name, _) in &macro_processor.macro_invocations {
if macro_name == "all_tagged_by" {
return true;
@@ -238,6 +250,34 @@ fn print_warnings(macro_processor: &MacroProcessor) {
}
}
+fn extract_requested_macro_processor_state(
+ macro_processor: &mut MacroProcessor,
+ metadata: &HashMap<String, YamlValue>,
+) -> HashMap<String, MacroType> {
+ if let Some(requested_keys) = metadata.get("keep_states") {
+ let requested_keys = match requested_keys {
+ YamlValue::List(l) => l.clone(),
+ YamlValue::Scalar(s) => vec![s.to_string()],
+ _ => {
+ macro_processor
+ .warnings
+ .push(MacroProcessorWarning::new(format!(
+ "keep_states specification must be list or scalar",
+ )));
+ return HashMap::new();
+ }
+ };
+ let mut res = HashMap::new();
+ for stored_key in requested_keys {
+ if let Some(stored_value) = macro_processor.macros.get(&stored_key) {
+ res.insert(stored_key.to_string(), stored_value.clone());
+ }
+ }
+ return res;
+ }
+ HashMap::new()
+}
+
/// Will attempt to compile a specific file, potentially storing some state about the file
fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, SkaldpressError> {
let extension = file_path.extension().unwrap_or(std::ffi::OsStr::new(""));
@@ -283,12 +323,21 @@ fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, Skaldpres
extension: String::from(extension.to_str().unwrap_or("")),
source_path: String::from(file_path.to_str().unwrap_or("")),
needs_recompilation: false,
+ stored_smp_state: HashMap::new(),
});
}
}
}
- let mut macro_processor = macro_processor(&map, None);
+ let mut stored_smp_state = None;
+ if let Some(cfile_i) = cached_file_id_by_path(&file_path.to_str().unwrap_or("").to_string()) {
+ unsafe {
+ stored_smp_state = Some(&COMPILED_FILES[cfile_i].stored_smp_state);
+ }
+ }
+
+ let mut macro_processor = MacroProcessor::new();
+ macro_processor_initialize(&map, &mut macro_processor, stored_smp_state);
let Some(template) = &map.get("template") else {
let file_content = macro_processor
@@ -297,6 +346,7 @@ fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, Skaldpres
print_warnings(&macro_processor);
return Ok(CompiledFile {
content: file_content,
+ stored_smp_state: extract_requested_macro_processor_state(&mut macro_processor, &map),
metadata: map,
extension: String::from(extension.to_str().unwrap_or("")),
source_path: String::from(file_path.to_str().unwrap_or("")),
@@ -316,6 +366,7 @@ fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, Skaldpres
print_warnings(&macro_processor);
Ok(CompiledFile {
content,
+ stored_smp_state: extract_requested_macro_processor_state(&mut macro_processor, &map),
metadata: map,
extension: String::from(template_extension),
source_path: String::from(file_path.to_str().unwrap_or("")),