From 7a2c19af66c45911353133b096f08f094e308670 Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Tue, 30 Jul 2024 09:03:10 +0200 Subject: [PATCH] QOL fixes & Tex caption --- docs/external/latex.nml | 10 +- docs/index.nml | 2 +- docs/lua/lua.nml | 2 +- docs/styles/basic.nml | 2 +- docs/styles/user-defined.nml | 2 +- docs/template.nml | 2 +- src/compiler/compiler.rs | 4 +- src/document/variable.rs | 42 --------- src/elements/tex.rs | 21 ++++- src/main.rs | 172 ++++++++++++++++++++++++++--------- 10 files changed, 161 insertions(+), 98 deletions(-) diff --git a/docs/external/latex.nml b/docs/external/latex.nml index a2cffe6..5f32a73 100644 --- a/docs/external/latex.nml +++ b/docs/external/latex.nml @@ -1,10 +1,10 @@ -@import docs/template.nml +@import ../template.nml @compiler.output = latex.html @nav.title = LaTeX @nav.category = External Tools @html.page_title = Documentation | LaTeX -@LaTeX = $|[kind=inline]\LaTeX|$ +@LaTeX = $|[kind=inline, caption=LaTeX]\LaTeX|$ *Bring some %LaTeX% unto your document!* @@ -88,6 +88,12 @@ To set the environment you wish to use for a particular %LaTeX% element, set the * ``$[env=main] 1+1 = 2$`` → $[env=main] 1+1 = 2$ * ``$[env=other] 1+1 = 2$`` → $[env=other] 1+1 = 2$ +# Properties + * ``env`` The %LaTeX% environment to use, defaults to `main`. + * ``kind`` The display kind of the rendered element: + *- `inline` (default for math mode) displays %LaTeX% as part of the current paragraph. + *- `block` (default for non math mode) display %LaTeX% on it's own line. + * ``caption`` Caption for accessibility, defaults to `none`. # LaTeX cache diff --git a/docs/index.nml b/docs/index.nml index e316267..89dd307 100644 --- a/docs/index.nml +++ b/docs/index.nml @@ -1,4 +1,4 @@ -@import docs/template.nml +@import template.nml @compiler.output = index.html @nav.title = Documentation @html.page_title = Documentation | Index diff --git a/docs/lua/lua.nml b/docs/lua/lua.nml index e13eecc..8766885 100644 --- a/docs/lua/lua.nml +++ b/docs/lua/lua.nml @@ -1,4 +1,4 @@ -@import docs/template.nml +@import ../template.nml @compiler.output = lua.html @nav.title = Lua @nav.category = Lua diff --git a/docs/styles/basic.nml b/docs/styles/basic.nml index 6d11646..1284409 100644 --- a/docs/styles/basic.nml +++ b/docs/styles/basic.nml @@ -1,4 +1,4 @@ -@import docs/template.nml +@import ../template.nml @compiler.output = basic.html @nav.title = Basic @nav.category = Styles diff --git a/docs/styles/user-defined.nml b/docs/styles/user-defined.nml index 7847a56..5980851 100644 --- a/docs/styles/user-defined.nml +++ b/docs/styles/user-defined.nml @@ -1,4 +1,4 @@ -@import docs/template.nml +@import ../template.nml @compiler.output = user-defined.html @nav.title = User-Defined @nav.category = Styles diff --git a/docs/template.nml b/docs/template.nml index 5d9a977..8957313 100644 --- a/docs/template.nml +++ b/docs/template.nml @@ -1,4 +1,4 @@ -@'html.css = style.css +@html.css = ../style.css @tex.main.fontsize = 9 @tex.main.preamble = \usepackage{xcolor, tikz, pgfplots} \\ diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 7f27d7a..2b491ee 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -227,10 +227,10 @@ impl CompiledDocument { con.execute(Self::sql_table(), []) } - pub fn from_cache(con: &Connection, input: &String) -> Option { + pub fn from_cache(con: &Connection, input: &str) -> Option { con.query_row(Self::sql_get_query(), [input], |row| { Ok(CompiledDocument { - input: input.clone(), + input: input.to_string(), mtime: row.get_unwrap::<_, u64>(1), variables: serde_json::from_str(row.get_unwrap::<_, String>(2).as_str()).unwrap(), header: row.get_unwrap::<_, String>(3), diff --git a/src/document/variable.rs b/src/document/variable.rs index bae520f..28cc223 100644 --- a/src/document/variable.rs +++ b/src/document/variable.rs @@ -91,9 +91,6 @@ impl Variable for PathVariable fn to_string(&self) -> String { self.path.to_str().unwrap().to_string() } fn parse<'a>(&self, location: Token, parser: &dyn Parser, document: &'a dyn Document) { - // TODO: Avoid copying the content... - // Maybe create a special VirtualSource where the `content()` method - // calls `Variable::to_string()` let source = Rc::new(VirtualSource::new( location, self.name().to_string(), @@ -105,42 +102,3 @@ impl Variable for PathVariable ))); } } - -/* -struct ConfigVariable -{ - value: T, - name: String, - - desc: String, - validator: Box Option<&String>>, -} - -impl ConfigVariable -{ - fn description(&self) -> &String { &self.desc } -} - -impl Variable for ConfigVariable -where T: FromStr + Display -{ - fn name(&self) -> &str { self.name.as_str() } - - /// Parse variable from string, returns an error message on failure - fn from_string(&mut self, str: &str) -> Option { - match str.parse::() - { - Ok(value) => { - (self.validator)(self, &value).or_else(|| { - self.value = value; - None - }) - }, - Err(_) => return Some(format!("Unable to parse `{str}` into variable `{}`", self.name)) - } - } - - /// Converts variable to a string - fn to_string(&self) -> String { self.value.to_string() } -} -*/ diff --git a/src/elements/tex.rs b/src/elements/tex.rs index 3aee337..b8af559 100644 --- a/src/elements/tex.rs +++ b/src/elements/tex.rs @@ -165,8 +165,6 @@ impl Element for Tex { } }); - // TODO: Do something with the caption - let exec = document .get_variable(format!("tex.{}.exec", self.env).as_str()) .map_or("latex2svg".to_string(), |var| var.to_string()); @@ -191,7 +189,7 @@ impl Element for Tex { Tex::format_latex(&fontsize, &preamble, &format!("{prepend}{}", self.tex)) }; - if let Some(mut con) = compiler.cache() { + let mut result = if let Some(mut con) = compiler.cache() { match latex.cached(&mut con, |s| s.latex_to_svg(&exec, &fontsize)) { Ok(s) => Ok(s), Err(e) => match e { @@ -203,7 +201,22 @@ impl Element for Tex { } } else { latex.latex_to_svg(&exec, &fontsize) - } + }; + + // Caption + result.map(|mut result| { + if let (Some(caption), Some(start)) = (&self.caption, result.find('>')) { + result.insert_str( + start + 1, + format!( + "{}", + Compiler::sanitize(Target::HTML, caption) + ) + .as_str(), + ); + } + result + }) } _ => todo!("Unimplemented"), } diff --git a/src/main.rs b/src/main.rs index eb7e1eb..f02bf01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod parser; use std::env; use std::io::BufWriter; use std::io::Write; +use std::path::PathBuf; use std::process::ExitCode; use std::rc::Rc; use std::time::UNIX_EPOCH; @@ -46,7 +47,7 @@ NML version: 0.4\n" ); } -fn parse(input: &String, debug_opts: &Vec) -> Result>, String> { +fn parse(input: &str, debug_opts: &Vec) -> Result>, String> { println!("Parsing {input}..."); let parser = LangParser::default(); @@ -89,13 +90,16 @@ fn parse(input: &String, debug_opts: &Vec) -> Result, + files: Vec, db_path: &Option, force_rebuild: bool, debug_opts: &Vec, ) -> Result, String> { let mut compiled = vec![]; + let current_dir = std::env::current_dir() + .map_err(|err| format!("Unable to get the current working directory: {err}"))?; + let con = db_path .as_ref() .map_or(Connection::open_in_memory(), |path| Connection::open(path)) @@ -105,15 +109,22 @@ fn process( for file in files { let meta = std::fs::metadata(&file) - .map_err(|err| format!("Failed to get metadata for `{file}`: {err}"))?; + .map_err(|err| format!("Failed to get metadata for `{file:#?}`: {err}"))?; let modified = meta .modified() - .map_err(|err| format!("Unable to query modification time for `{file}`: {err}"))?; + .map_err(|err| format!("Unable to query modification time for `{file:#?}`: {err}"))?; + + // Move to file's directory + let file_parent_path = file + .parent() + .ok_or(format!("Failed to get parent path for `{file:#?}`"))?; + std::env::set_current_dir(file_parent_path) + .map_err(|err| format!("Failed to move to path `{file_parent_path:#?}`: {err}"))?; let parse_and_compile = || -> Result { // Parse - let doc = parse(&file, debug_opts)?; + let doc = parse(file.to_str().unwrap(), debug_opts)?; // Compile let compiler = Compiler::new(target, db_path.clone()); @@ -122,7 +133,7 @@ fn process( // Insert into cache compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs(); compiled.insert_cache(&con).map_err(|err| { - format!("Failed to insert compiled document from `{file}` into cache: {err}") + format!("Failed to insert compiled document from `{file:#?}` into cache: {err}") })?; Ok(compiled) @@ -131,7 +142,7 @@ fn process( let cdoc = if force_rebuild { parse_and_compile()? } else { - match CompiledDocument::from_cache(&con, &file) { + match CompiledDocument::from_cache(&con, file.to_str().unwrap()) { Some(compiled) => { if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() { parse_and_compile()? @@ -146,6 +157,9 @@ fn process( compiled.push(cdoc); } + std::env::set_current_dir(current_dir) + .map_err(|err| format!("Failed to set current directory: {err}"))?; + Ok(compiled) } @@ -208,26 +222,74 @@ fn main() -> ExitCode { return ExitCode::FAILURE; } } + } else if std::fs::exists(&output).unwrap_or(false) { + let output_meta = match std::fs::metadata(&output) { + Ok(meta) => meta, + Err(e) => { + eprintln!("Unable to get metadata for output: `{output}`"); + return ExitCode::FAILURE; + } + }; + + if output_meta.is_dir() { + eprintln!("Input `{input}` is a file, but output `{output}` is a directory"); + return ExitCode::FAILURE; + } } - let db_path = matches.opt_str("d"); + let db_path = match matches.opt_str("d") { + Some(db) => { + if std::fs::exists(&db).unwrap_or(false) { + match std::fs::canonicalize(&db) + .map_err(|err| format!("Failed to cannonicalize database path `{db}`: {err}")) + .as_ref() + .map(|path| path.to_str()) + { + Ok(Some(path)) => Some(path.to_string()), + Ok(None) => { + eprintln!("Failed to transform path to string `{db}`"); + return ExitCode::FAILURE; + } + Err(err) => { + eprintln!("{err}"); + return ExitCode::FAILURE; + } + } + } else + // Cannonicalize parent path, then append the database name + { + match std::fs::canonicalize(".") + .map_err(|err| { + format!("Failed to cannonicalize database parent path `{db}`: {err}") + }) + .map(|path| path.join(&db)) + .as_ref() + .map(|path| path.to_str()) + { + Ok(Some(path)) => Some(path.to_string()), + Ok(None) => { + eprintln!("Failed to transform path to string `{db}`"); + return ExitCode::FAILURE; + } + Err(err) => { + eprintln!("{err}"); + return ExitCode::FAILURE; + } + } + } + } + None => None, + }; let force_rebuild = matches.opt_present("force-rebuild"); let debug_opts = matches.opt_strs("z"); let mut files = vec![]; if input_meta.is_dir() { if db_path.is_none() { - eprintln!("Please specify a database (-d) for directory mode."); + eprintln!("Directory mode requires a database (-d)"); + return ExitCode::FAILURE; } - let input_it = match std::fs::read_dir(&input) { - Ok(it) => it, - Err(e) => { - eprintln!("Failed to read input directory `{input}`: {e}"); - return ExitCode::FAILURE; - } - }; - for entry in WalkDir::new(&input) { if let Err(err) = entry { eprintln!("Failed to recursively walk over input directory: {err}"); @@ -257,10 +319,19 @@ fn main() -> ExitCode { continue; } - files.push(path); + files.push(std::fs::canonicalize(path).unwrap()); } } else { - files.push(input); + // Single file mode + files.push(std::fs::canonicalize(input).unwrap()); + } + + // Check that all files have a valid unicode path + for file in &files { + if file.to_str().is_none() { + eprintln!("Invalid unicode for file: `{file:#?}`"); + return ExitCode::FAILURE; + } } // Parse, compile using the cache @@ -272,35 +343,50 @@ fn main() -> ExitCode { } }; - // Build navigation - let navigation = match create_navigation(&compiled) { - Ok(nav) => nav, - Err(e) => { - eprintln!("{e}"); - return ExitCode::FAILURE; - } - }; - - // Output - for doc in compiled { - let out_path = match doc - .get_variable("compiler.output") - .or(input_meta.is_file().then_some(&output)) - { - Some(path) => path.clone(), - None => { - eprintln!("Unable to get output file for `{}`", doc.input); - continue; + if input_meta.is_dir() + // Batch mode + { + // Build navigation + let navigation = match create_navigation(&compiled) { + Ok(nav) => nav, + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; } }; - let nav = navigation.compile(Target::HTML, &doc); + // Output + for doc in compiled { + let out_path = match doc + .get_variable("compiler.output") + .or(input_meta.is_file().then_some(&output)) + { + Some(path) => path.clone(), + None => { + eprintln!("Unable to get output file for `{}`", doc.input); + continue; + } + }; - let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap(); - let mut writer = BufWriter::new(file); + let nav = navigation.compile(Target::HTML, &doc); + let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap(); - write!(writer, "{}{}{}{}", doc.header, nav, doc.body, doc.footer).unwrap(); - writer.flush().unwrap(); + let mut writer = BufWriter::new(file); + + write!(writer, "{}{}{}{}", doc.header, nav, doc.body, doc.footer).unwrap(); + writer.flush().unwrap(); + } + } else + // Single file + { + for doc in compiled { + let file = std::fs::File::create(output.clone()).unwrap(); + + let mut writer = BufWriter::new(file); + + write!(writer, "{}{}{}", doc.header, doc.body, doc.footer).unwrap(); + writer.flush().unwrap(); + } } return ExitCode::SUCCESS;