Experimental navigation

This commit is contained in:
ef3d0c3e 2024-07-28 14:20:58 +02:00
parent c62039dfdf
commit ac0c4050eb
4 changed files with 226 additions and 42 deletions

View file

@ -9,6 +9,9 @@ use crate::document::document::Document;
use crate::document::document::ElemReference; use crate::document::document::ElemReference;
use crate::document::variable::Variable; use crate::document::variable::Variable;
use super::navigation::NavEntry;
use super::navigation::Navigation;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Target { pub enum Target {
HTML, HTML,
@ -18,8 +21,8 @@ pub enum Target {
pub struct Compiler { pub struct Compiler {
target: Target, target: Target,
cache: Option<RefCell<Connection>>, cache: Option<RefCell<Connection>>,
// TODO:
reference_count: RefCell<HashMap<String, HashMap<String, usize>>>, reference_count: RefCell<HashMap<String, HashMap<String, usize>>>,
// TODO: External references, i.e resolved later
} }
impl Compiler { impl Compiler {
@ -130,37 +133,71 @@ impl Compiler {
result 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 { pub fn footer(&self, _document: &dyn Document) -> String {
let mut result = String::new(); let mut result = String::new();
match self.target() { match self.target() {
Target::HTML => { Target::HTML => {
result += "</body></html>"; result += "</body></html>";
} }
Target::LATEX => {} Target::LATEX => todo!("")
} }
result 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 mut out = String::new();
let borrow = document.content().borrow(); let borrow = document.content().borrow();
// Header // Header
out += self.header(document).as_str(); out += self.header(document).as_str();
// Navigation
out += self.navigation(navigation, document).as_str();
// Body // Body
for i in 0..borrow.len() { for i in 0..borrow.len() {
let elem = &borrow[i]; 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) { match elem.compile(self, document) {
Ok(result) => { Ok(result) => {
//println!("Elem: {}\nCompiled to: {result}", elem.to_string());
out.push_str(result.as_str()) out.push_str(result.as_str())
} }
Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()), Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()),

View file

@ -1 +1,2 @@
pub mod compiler; pub mod compiler;
pub mod navigation;

103
src/compiler/navigation.rs Normal file
View file

@ -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)
}

View file

@ -11,6 +11,10 @@ use std::process::ExitCode;
use std::rc::Rc; use std::rc::Rc;
use compiler::compiler::Compiler; 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 getopts::Options;
use parser::langparser::LangParser; use parser::langparser::LangParser;
use parser::parser::Parser; use parser::parser::Parser;
@ -38,15 +42,41 @@ NML version: 0.4\n"
); );
} }
fn process( fn compile(
parser: &LangParser, target: Target,
db_path: &Option<String>, doc: &Box<dyn Document>,
input: &String,
output: &String, output: &String,
debug_opts: &Vec<String>, db_path: &Option<String>,
navigation: &Navigation,
multi_mode: bool, multi_mode: bool,
) -> 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 // Parse
let source = SourceFile::new(input.to_string(), None).unwrap(); let source = SourceFile::new(input.to_string(), None).unwrap();
let doc = parser.parse(Rc::new(source), None); let doc = parser.parse(Rc::new(source), None);
@ -78,28 +108,10 @@ fn process(
} }
if parser.has_error() { if parser.has_error() {
println!("Compilation aborted due to errors while parsing"); return Err("Parsing failed aborted due to errors while parsing".to_string())
return false;
} }
let compiler = Compiler::new(compiler::compiler::Target::HTML, db_path.clone()); Ok(doc)
// 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()
}
} }
fn main() -> ExitCode { fn main() -> ExitCode {
@ -164,7 +176,8 @@ fn main() -> ExitCode {
let debug_opts = matches.opt_strs("z"); let debug_opts = matches.opt_strs("z");
let db_path = matches.opt_str("d"); let db_path = matches.opt_str("d");
let parser = LangParser::default();
let mut docs = vec![];
if input_meta.is_dir() { if input_meta.is_dir() {
if db_path.is_none() { if db_path.is_none() {
@ -208,14 +221,44 @@ fn main() -> ExitCode {
continue; continue;
} }
if !process(&parser, &db_path, &path, &output, &debug_opts, true) { match parse(&path, &debug_opts)
eprintln!("Processing aborted"); {
return ExitCode::FAILURE; Ok(doc) => docs.push(doc),
Err(e) => {
eprintln!("{e}");
return ExitCode::FAILURE;
}
} }
} }
} else { } else {
if !process(&parser, &db_path, &input, &output, &debug_opts, false) { match parse(&input, &debug_opts)
eprintln!("Processing aborted"); {
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; return ExitCode::FAILURE;
} }
} }