QOL fixes & Tex caption
This commit is contained in:
parent
b252610fbd
commit
7a2c19af66
10 changed files with 161 additions and 98 deletions
10
docs/external/latex.nml
vendored
10
docs/external/latex.nml
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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} \\
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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() }
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -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"),
|
||||||
}
|
}
|
||||||
|
|
124
src/main.rs
124
src/main.rs
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue