From ac0c4050eb845c958d20b84f1dfcbe9290eeb71d Mon Sep 17 00:00:00 2001
From: ef3d0c3e <ef3d0c3e@pundalik.org>
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<RefCell<Connection>>,
- // TODO:
reference_count: RefCell<HashMap<String, HashMap<String, usize>>>,
+ // 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#"<ul id="navbar">"#;
+
+ fn process(result: &mut String, name: &String, ent: &NavEntry, depth: usize)
+ {
+ let ent_path = ent.path.as_ref()
+ .map_or("#".to_string(),|path| path.clone());
+ result.push_str(format!(r#"<li><a href="{ent_path}">{name}</a></li>"#).as_str());
+
+ if let Some(children) = ent.children.as_ref()
+ {
+ result.push_str("<ul>");
+ for (name, ent) in children
+ {
+ process(result, name, ent, depth+1);
+ }
+ result.push_str("</ul>");
+ }
+ }
+
+ for (name, ent) in &navigation.entries
+ {
+ process(&mut result, name, ent, 0);
+ }
+
+
+ result += r#"</ul>"#;
+ },
+ _ => todo!("")
+ }
+ result
+ }
+
pub fn footer(&self, _document: &dyn Document) -> String {
let mut result = String::new();
match self.target() {
Target::HTML => {
result += "</body></html>";
}
- 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<String>,
+ pub(crate) children: Option<HashMap<String, NavEntry>>,
+}
+
+#[derive(Debug)]
+pub struct Navigation {
+ pub(crate) entries: HashMap<String, NavEntry>,
+}
+
+pub fn create_navigation(docs: &Vec<Box<dyn Document>>) -> Result<Navigation, String> {
+ 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<String>,
- input: &String,
+fn compile(
+ target: Target,
+ doc: &Box<dyn Document>,
output: &String,
- debug_opts: &Vec<String>,
+ db_path: &Option<String>,
+ 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<String>,
+) -> Result<Box<dyn Document<'static>>, 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;
}
}