From ac0c4050eb845c958d20b84f1dfcbe9290eeb71d Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Sun, 28 Jul 2024 14:20:58 +0200 Subject: [PATCH] Experimental navigation --- src/compiler/compiler.rs | 57 ++++++++++++++++---- src/compiler/mod.rs | 1 + src/compiler/navigation.rs | 103 +++++++++++++++++++++++++++++++++++ src/main.rs | 107 ++++++++++++++++++++++++++----------- 4 files changed, 226 insertions(+), 42 deletions(-) create mode 100644 src/compiler/navigation.rs diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index a36b89f..d12bf43 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -9,6 +9,9 @@ use crate::document::document::Document; use crate::document::document::ElemReference; use crate::document::variable::Variable; +use super::navigation::NavEntry; +use super::navigation::Navigation; + #[derive(Clone, Copy)] pub enum Target { HTML, @@ -18,8 +21,8 @@ pub enum Target { pub struct Compiler { target: Target, cache: Option>, - // TODO: reference_count: RefCell>>, + // TODO: External references, i.e resolved later } impl Compiler { @@ -130,37 +133,71 @@ impl Compiler { result } + pub fn navigation(&self, navigation: &Navigation, document: &dyn Document) -> String + { + let mut result = String::new(); + match self.target() + { + Target::HTML => { + result += r#""#; + }, + _ => todo!("") + } + result + } + pub fn footer(&self, _document: &dyn Document) -> String { let mut result = String::new(); match self.target() { Target::HTML => { result += ""; } - Target::LATEX => {} + Target::LATEX => todo!("") } result } - pub fn compile(&self, document: &dyn Document) -> String { + pub fn compile(&self, navigation: &Navigation, document: &dyn Document) -> String { let mut out = String::new(); let borrow = document.content().borrow(); // Header out += self.header(document).as_str(); + // Navigation + out += self.navigation(navigation, document).as_str(); + // Body for i in 0..borrow.len() { let elem = &borrow[i]; - //let prev = match i - //{ - // 0 => None, - // _ => borrow.get(i-1), - //}; - //let next = borrow.get(i+1); match elem.compile(self, document) { Ok(result) => { - //println!("Elem: {}\nCompiled to: {result}", elem.to_string()); out.push_str(result.as_str()) } Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()), diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 59d8df7..ecf558f 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1 +1,2 @@ pub mod compiler; +pub mod navigation; diff --git a/src/compiler/navigation.rs b/src/compiler/navigation.rs new file mode 100644 index 0000000..722d563 --- /dev/null +++ b/src/compiler/navigation.rs @@ -0,0 +1,103 @@ +use std::collections::HashMap; + +use crate::document::document::Document; + +#[derive(Debug)] +pub struct NavEntry { + pub(crate) name: String, + pub(crate) path: Option, + pub(crate) children: Option>, +} + +#[derive(Debug)] +pub struct Navigation { + pub(crate) entries: HashMap, +} + +pub fn create_navigation(docs: &Vec>) -> Result { + let mut nav = Navigation { + entries: HashMap::new(), + }; + + for doc in docs { + let cat = doc.get_variable("nav.category"); + let subcat = doc.get_variable("nav.subcategory"); + let title = doc + .get_variable("nav.title") + .or(doc.get_variable("doc.title")); + let path = doc.get_variable("compiler.output"); + + let (cat, title, path) = match (cat, title, path) { + (Some(cat), Some(title), Some(path)) => (cat, title, path), + _ => { + println!( + "Skipping navigation generation for `{}`", + doc.source().name() + ); + continue; + } + }; + + if let Some(subcat) = subcat { + // Get parent entry + let cat_name = cat.to_string(); + let mut pent = match nav.entries.get_mut(cat_name.as_str()) { + Some(pent) => pent, + None => { + // Create parent entry + nav.entries.insert( + cat_name.clone(), + NavEntry { + name: cat_name.clone(), + path: None, + children: Some(HashMap::new()), + }, + ); + nav.entries.get_mut(cat_name.as_str()).unwrap() + } + }; + + // Insert into parent + let subcat_name = subcat.to_string(); + if let Some(previous) = pent.children.as_mut().unwrap().insert( + subcat_name.clone(), + NavEntry { + name: subcat_name.clone(), + path: Some(path.to_string()), + children: None, + }, + ) { + return Err(format!( + "Duplicate subcategory:\n{subcat:#?}\nclashes with:\n{previous:#?}" + )); + } + } else { + // Get entry + let cat_name = cat.to_string(); + let mut ent = match nav.entries.get_mut(cat_name.as_str()) { + Some(ent) => ent, + None => { + // Create parent entry + nav.entries.insert( + cat_name.clone(), + NavEntry { + name: cat_name.clone(), + path: None, + children: Some(HashMap::new()), + }, + ); + nav.entries.get_mut(cat_name.as_str()).unwrap() + } + }; + + if let Some(path) = ent.path.as_ref() { + return Err(format!( + "Duplicate category:\n{subcat:#?}\nwith previous path:\n{path}" + )); + } + ent.path = Some(path.to_string()); + } + } + + Ok(nav) +} diff --git a/src/main.rs b/src/main.rs index d00b9bc..a19a37d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,10 @@ use std::process::ExitCode; use std::rc::Rc; use compiler::compiler::Compiler; +use compiler::compiler::Target; +use compiler::navigation::create_navigation; +use compiler::navigation::Navigation; +use document::document::Document; use getopts::Options; use parser::langparser::LangParser; use parser::parser::Parser; @@ -38,15 +42,41 @@ NML version: 0.4\n" ); } -fn process( - parser: &LangParser, - db_path: &Option, - input: &String, +fn compile( + target: Target, + doc: &Box, output: &String, - debug_opts: &Vec, + db_path: &Option, + navigation: &Navigation, multi_mode: bool, ) -> bool { - println!("Processing {input}..."); + let compiler = Compiler::new(target, db_path.clone()); + + // Get output from file + if multi_mode { + let out_file = match doc.get_variable("compiler.output") { + None => { + eprintln!("Missing required variable `compiler.output` for multifile mode"); + return false; + } + Some(var) => output.clone() + "/" + var.to_string().as_str(), + }; + + let out = compiler.compile(navigation, doc.as_ref()); + std::fs::write(out_file, out).is_ok() + } else { + let out = compiler.compile(navigation, doc.as_ref()); + std::fs::write(output, out).is_ok() + } +} + +fn parse( + input: &String, + debug_opts: &Vec, +) -> Result>, String> { + println!("Parsing {input}..."); + let parser = LangParser::default(); + // Parse let source = SourceFile::new(input.to_string(), None).unwrap(); let doc = parser.parse(Rc::new(source), None); @@ -78,28 +108,10 @@ fn process( } if parser.has_error() { - println!("Compilation aborted due to errors while parsing"); - return false; + return Err("Parsing failed aborted due to errors while parsing".to_string()) } - let compiler = Compiler::new(compiler::compiler::Target::HTML, db_path.clone()); - - // Get output from file - if multi_mode { - let out_file = match doc.get_variable("compiler.output") { - None => { - eprintln!("Missing required variable `compiler.output` for multifile mode"); - return false; - } - Some(var) => output.clone() + "/" + var.to_string().as_str(), - }; - - let out = compiler.compile(doc.as_ref()); - std::fs::write(out_file, out).is_ok() - } else { - let out = compiler.compile(doc.as_ref()); - std::fs::write(output, out).is_ok() - } + Ok(doc) } fn main() -> ExitCode { @@ -164,7 +176,8 @@ fn main() -> ExitCode { let debug_opts = matches.opt_strs("z"); let db_path = matches.opt_str("d"); - let parser = LangParser::default(); + + let mut docs = vec![]; if input_meta.is_dir() { if db_path.is_none() { @@ -208,14 +221,44 @@ fn main() -> ExitCode { continue; } - if !process(&parser, &db_path, &path, &output, &debug_opts, true) { - eprintln!("Processing aborted"); - return ExitCode::FAILURE; + match parse(&path, &debug_opts) + { + Ok(doc) => docs.push(doc), + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; + } } } } else { - if !process(&parser, &db_path, &input, &output, &debug_opts, false) { - eprintln!("Processing aborted"); + match parse(&input, &debug_opts) + { + Ok(doc) => docs.push(doc), + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; + } + } + } + + // Build navigation + let navigation = match create_navigation(&docs) + { + Ok(nav) => nav, + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; + } + }; + + println!("{navigation:#?}"); + + let multi_mode = input_meta.is_dir(); + for doc in docs + { + if !compile(Target::HTML, &doc, &output, &db_path, &navigation, multi_mode) + { + eprintln!("Compilation failed, processing aborted"); return ExitCode::FAILURE; } }