QOL fixes & Tex caption

This commit is contained in:
ef3d0c3e 2024-07-30 09:03:10 +02:00
parent b252610fbd
commit 7a2c19af66
10 changed files with 161 additions and 98 deletions

View file

@ -1,10 +1,10 @@
@import docs/template.nml @import ../template.nml
@compiler.output = latex.html @compiler.output = latex.html
@nav.title = LaTeX @nav.title = LaTeX
@nav.category = External Tools @nav.category = External Tools
@html.page_title = Documentation | LaTeX @html.page_title = Documentation | LaTeX
@LaTeX = $|[kind=inline]\LaTeX|$ @LaTeX = $|[kind=inline, caption=LaTeX]\LaTeX|$
*Bring some %LaTeX% unto your document!* *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=main] 1+1 = 2$`` → $[env=main] 1+1 = 2$
* ``$[env=other] 1+1 = 2$`` → $[env=other] 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 # LaTeX cache

View file

@ -1,4 +1,4 @@
@import docs/template.nml @import template.nml
@compiler.output = index.html @compiler.output = index.html
@nav.title = Documentation @nav.title = Documentation
@html.page_title = Documentation | Index @html.page_title = Documentation | Index

View file

@ -1,4 +1,4 @@
@import docs/template.nml @import ../template.nml
@compiler.output = lua.html @compiler.output = lua.html
@nav.title = Lua @nav.title = Lua
@nav.category = Lua @nav.category = Lua

View file

@ -1,4 +1,4 @@
@import docs/template.nml @import ../template.nml
@compiler.output = basic.html @compiler.output = basic.html
@nav.title = Basic @nav.title = Basic
@nav.category = Styles @nav.category = Styles

View file

@ -1,4 +1,4 @@
@import docs/template.nml @import ../template.nml
@compiler.output = user-defined.html @compiler.output = user-defined.html
@nav.title = User-Defined @nav.title = User-Defined
@nav.category = Styles @nav.category = Styles

View file

@ -1,4 +1,4 @@
@'html.css = style.css @html.css = ../style.css
@tex.main.fontsize = 9 @tex.main.fontsize = 9
@tex.main.preamble = \usepackage{xcolor, tikz, pgfplots} \\ @tex.main.preamble = \usepackage{xcolor, tikz, pgfplots} \\

View file

@ -227,10 +227,10 @@ impl CompiledDocument {
con.execute(Self::sql_table(), []) con.execute(Self::sql_table(), [])
} }
pub fn from_cache(con: &Connection, input: &String) -> Option<Self> { pub fn from_cache(con: &Connection, input: &str) -> Option<Self> {
con.query_row(Self::sql_get_query(), [input], |row| { con.query_row(Self::sql_get_query(), [input], |row| {
Ok(CompiledDocument { Ok(CompiledDocument {
input: input.clone(), input: input.to_string(),
mtime: row.get_unwrap::<_, u64>(1), mtime: row.get_unwrap::<_, u64>(1),
variables: serde_json::from_str(row.get_unwrap::<_, String>(2).as_str()).unwrap(), variables: serde_json::from_str(row.get_unwrap::<_, String>(2).as_str()).unwrap(),
header: row.get_unwrap::<_, String>(3), header: row.get_unwrap::<_, String>(3),

View file

@ -91,9 +91,6 @@ impl Variable for PathVariable
fn to_string(&self) -> String { self.path.to_str().unwrap().to_string() } 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) { 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( let source = Rc::new(VirtualSource::new(
location, location,
self.name().to_string(), self.name().to_string(),
@ -105,42 +102,3 @@ impl Variable for PathVariable
))); )));
} }
} }
/*
struct ConfigVariable<T>
{
value: T,
name: String,
desc: String,
validator: Box<dyn Fn(&Self, &T) -> Option<&String>>,
}
impl<T> ConfigVariable<T>
{
fn description(&self) -> &String { &self.desc }
}
impl<T> Variable for ConfigVariable<T>
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<String> {
match str.parse::<T>()
{
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() }
}
*/

View file

@ -165,8 +165,6 @@ impl Element for Tex {
} }
}); });
// TODO: Do something with the caption
let exec = document let exec = document
.get_variable(format!("tex.{}.exec", self.env).as_str()) .get_variable(format!("tex.{}.exec", self.env).as_str())
.map_or("latex2svg".to_string(), |var| var.to_string()); .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)) 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)) { match latex.cached(&mut con, |s| s.latex_to_svg(&exec, &fontsize)) {
Ok(s) => Ok(s), Ok(s) => Ok(s),
Err(e) => match e { Err(e) => match e {
@ -203,7 +201,22 @@ impl Element for Tex {
} }
} else { } else {
latex.latex_to_svg(&exec, &fontsize) 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!(
"<title>{}</title>",
Compiler::sanitize(Target::HTML, caption)
)
.as_str(),
);
} }
result
})
} }
_ => todo!("Unimplemented"), _ => todo!("Unimplemented"),
} }

View file

@ -9,6 +9,7 @@ mod parser;
use std::env; use std::env;
use std::io::BufWriter; use std::io::BufWriter;
use std::io::Write; use std::io::Write;
use std::path::PathBuf;
use std::process::ExitCode; use std::process::ExitCode;
use std::rc::Rc; use std::rc::Rc;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
@ -46,7 +47,7 @@ NML version: 0.4\n"
); );
} }
fn parse(input: &String, debug_opts: &Vec<String>) -> Result<Box<dyn Document<'static>>, String> { fn parse(input: &str, debug_opts: &Vec<String>) -> Result<Box<dyn Document<'static>>, String> {
println!("Parsing {input}..."); println!("Parsing {input}...");
let parser = LangParser::default(); let parser = LangParser::default();
@ -89,13 +90,16 @@ fn parse(input: &String, debug_opts: &Vec<String>) -> Result<Box<dyn Document<'s
fn process( fn process(
target: Target, target: Target,
files: Vec<String>, files: Vec<PathBuf>,
db_path: &Option<String>, db_path: &Option<String>,
force_rebuild: bool, force_rebuild: bool,
debug_opts: &Vec<String>, debug_opts: &Vec<String>,
) -> Result<Vec<CompiledDocument>, String> { ) -> Result<Vec<CompiledDocument>, String> {
let mut compiled = vec![]; 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 let con = db_path
.as_ref() .as_ref()
.map_or(Connection::open_in_memory(), |path| Connection::open(path)) .map_or(Connection::open_in_memory(), |path| Connection::open(path))
@ -105,15 +109,22 @@ fn process(
for file in files { for file in files {
let meta = std::fs::metadata(&file) 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 let modified = meta
.modified() .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<CompiledDocument, String> { let parse_and_compile = || -> Result<CompiledDocument, String> {
// Parse // Parse
let doc = parse(&file, debug_opts)?; let doc = parse(file.to_str().unwrap(), debug_opts)?;
// Compile // Compile
let compiler = Compiler::new(target, db_path.clone()); let compiler = Compiler::new(target, db_path.clone());
@ -122,7 +133,7 @@ fn process(
// Insert into cache // Insert into cache
compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs(); compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs();
compiled.insert_cache(&con).map_err(|err| { 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) Ok(compiled)
@ -131,7 +142,7 @@ fn process(
let cdoc = if force_rebuild { let cdoc = if force_rebuild {
parse_and_compile()? parse_and_compile()?
} else { } else {
match CompiledDocument::from_cache(&con, &file) { match CompiledDocument::from_cache(&con, file.to_str().unwrap()) {
Some(compiled) => { Some(compiled) => {
if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() { if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() {
parse_and_compile()? parse_and_compile()?
@ -146,6 +157,9 @@ fn process(
compiled.push(cdoc); compiled.push(cdoc);
} }
std::env::set_current_dir(current_dir)
.map_err(|err| format!("Failed to set current directory: {err}"))?;
Ok(compiled) Ok(compiled)
} }
@ -208,25 +222,73 @@ fn main() -> ExitCode {
return ExitCode::FAILURE; 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 force_rebuild = matches.opt_present("force-rebuild");
let debug_opts = matches.opt_strs("z"); let debug_opts = matches.opt_strs("z");
let mut files = vec![]; let mut files = vec![];
if input_meta.is_dir() { if input_meta.is_dir() {
if db_path.is_none() { if db_path.is_none() {
eprintln!("Please specify a database (-d) for directory mode."); eprintln!("Directory mode requires a database (-d)");
}
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; return ExitCode::FAILURE;
} }
};
for entry in WalkDir::new(&input) { for entry in WalkDir::new(&input) {
if let Err(err) = entry { if let Err(err) = entry {
@ -257,10 +319,19 @@ fn main() -> ExitCode {
continue; continue;
} }
files.push(path); files.push(std::fs::canonicalize(path).unwrap());
} }
} else { } 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 // Parse, compile using the cache
@ -272,6 +343,9 @@ fn main() -> ExitCode {
} }
}; };
if input_meta.is_dir()
// Batch mode
{
// Build navigation // Build navigation
let navigation = match create_navigation(&compiled) { let navigation = match create_navigation(&compiled) {
Ok(nav) => nav, Ok(nav) => nav,
@ -295,13 +369,25 @@ fn main() -> ExitCode {
}; };
let nav = navigation.compile(Target::HTML, &doc); let nav = navigation.compile(Target::HTML, &doc);
let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap(); let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap();
let mut writer = BufWriter::new(file); let mut writer = BufWriter::new(file);
write!(writer, "{}{}{}{}", doc.header, nav, doc.body, doc.footer).unwrap(); write!(writer, "{}{}{}{}", doc.header, nav, doc.body, doc.footer).unwrap();
writer.flush().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; return ExitCode::SUCCESS;
} }