Compare commits
No commits in common. "7a2c19af66c45911353133b096f08f094e308670" and "554a83a63c6d719bc99698dc6575ac613aaf6729" have entirely different histories.
7a2c19af66
...
554a83a63c
22 changed files with 133 additions and 870 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -737,7 +737,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-lsp",
|
"tower-lsp",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"walkdir",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -36,4 +36,3 @@ tokio = { version = "1.38.1", features = ["macros", "rt-multi-thread", "io-std"]
|
||||||
|
|
||||||
tower-lsp = "0.20.0"
|
tower-lsp = "0.20.0"
|
||||||
unicode-segmentation = "1.11.0"
|
unicode-segmentation = "1.11.0"
|
||||||
walkdir = "2.5.0"
|
|
||||||
|
|
102
docs/external/latex.nml
vendored
102
docs/external/latex.nml
vendored
|
@ -1,102 +0,0 @@
|
||||||
@import ../template.nml
|
|
||||||
@compiler.output = latex.html
|
|
||||||
@nav.title = LaTeX
|
|
||||||
@nav.category = External Tools
|
|
||||||
@html.page_title = Documentation | LaTeX
|
|
||||||
|
|
||||||
@LaTeX = $|[kind=inline, caption=LaTeX]\LaTeX|$
|
|
||||||
|
|
||||||
*Bring some %LaTeX% unto your document!*
|
|
||||||
|
|
||||||
# Inline Math
|
|
||||||
|
|
||||||
You can add inline math by enclosing %LaTeX% between two ``$``:
|
|
||||||
* ``$\lim_{n \to \infty} \Big(1 + \frac{1}{n}\Big)^n = e$`` → $\lim_{n \to \infty} \Big(1 + \frac{1}{n}\Big)^n = e$
|
|
||||||
* ``$\pi = \sqrt{\sum_{n=1}^\infty \frac{1}{n^2}}$`` → $\pi = \sqrt{\sum_{n=1}^\infty \frac{1}{n^2}}$
|
|
||||||
|
|
||||||
You can make the %LaTeX% non inline by specifying `kind=block` in it's property: ``$[kind=block] 1+1=2$`` → $[kind=block] 1+1=2$
|
|
||||||
*(notice how it's not inside a paragraph)*
|
|
||||||
|
|
||||||
# Non Math LaTeX
|
|
||||||
|
|
||||||
You can write %LaTeX% outside of %LaTeX%'s math environment, by enclosing your code between ``$|...|$``:
|
|
||||||
``LaTeX,
|
|
||||||
$|\begin{tikzpicture}
|
|
||||||
\begin{axis}
|
|
||||||
\addplot3[patch,patch refines=3,
|
|
||||||
shader=faceted interp,
|
|
||||||
patch type=biquadratic]
|
|
||||||
table[z expr=x^2-y^2]
|
|
||||||
{
|
|
||||||
x y
|
|
||||||
-2 -2
|
|
||||||
2 -2
|
|
||||||
2 2
|
|
||||||
-2 2
|
|
||||||
0 -2
|
|
||||||
2 0
|
|
||||||
0 2
|
|
||||||
-2 0
|
|
||||||
0 0
|
|
||||||
};
|
|
||||||
\end{axis}
|
|
||||||
\end{tikzpicture}|$
|
|
||||||
``
|
|
||||||
Gives the following:
|
|
||||||
|
|
||||||
$|\begin{tikzpicture}
|
|
||||||
\begin{axis}
|
|
||||||
\addplot3[patch,patch refines=3,
|
|
||||||
shader=faceted interp,
|
|
||||||
patch type=biquadratic]
|
|
||||||
table[z expr=x^2-y^2]
|
|
||||||
{
|
|
||||||
x y
|
|
||||||
-2 -2
|
|
||||||
2 -2
|
|
||||||
2 2
|
|
||||||
-2 2
|
|
||||||
0 -2
|
|
||||||
2 0
|
|
||||||
0 2
|
|
||||||
-2 0
|
|
||||||
0 0
|
|
||||||
};
|
|
||||||
\end{axis}
|
|
||||||
\end{tikzpicture}|$
|
|
||||||
|
|
||||||
# LaTeX environment
|
|
||||||
|
|
||||||
You can define multiple %LaTeX% environment, the default being `main`
|
|
||||||
* ``@tex.env.fontsize`` The fontsize (in pt) specified to `latex2svg` (default: `12`).
|
|
||||||
* ``@tex.env.preamble`` The preamble prepended to every %LaTeX% code.
|
|
||||||
* ``@tex.env.block_prepend`` Text to prepend to every non math %LaTeX% code.
|
|
||||||
* ``@tex.env.exec`` The `latex2svg` executable path, defaults to `latex2svg` (need to be in your `\$PATH`)
|
|
||||||
Replace ``env`` with the name of the custom environment you wish to define.
|
|
||||||
|
|
||||||
Here's a preamble to render %LaTeX% gray:
|
|
||||||
``
|
|
||||||
@tex.main.fontsize = 9
|
|
||||||
@tex.main.preamble = \usepackage{xcolor} \\
|
|
||||||
\usepgfplotslibrary{patchplots} \\
|
|
||||||
\definecolor{__color1}{HTML}{d5d5d5} \\
|
|
||||||
\everymath{\color{__color1}}
|
|
||||||
@tex.main.block_prepend = \color{__color1}
|
|
||||||
``
|
|
||||||
|
|
||||||
To set the environment you wish to use for a particular %LaTeX% element, set the `env` property:
|
|
||||||
* ``$[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
|
|
||||||
|
|
||||||
%LaTeX% elements that have been successfully rendered to **svg** are stored in the cache database, to avoid processing them a second time.
|
|
||||||
Note that this cache is shared between documents, so you don't need to reprocess them if they share the same environment.
|
|
||||||
They are stored under the table named ``cached_tex``, if you modify the `env` all elements will be reprocessed which may take a while...
|
|
|
@ -1,6 +0,0 @@
|
||||||
@import template.nml
|
|
||||||
@compiler.output = index.html
|
|
||||||
@nav.title = Documentation
|
|
||||||
@html.page_title = Documentation | Index
|
|
||||||
|
|
||||||
# Welcome to the NML documentation!
|
|
|
@ -1,20 +0,0 @@
|
||||||
@import ../template.nml
|
|
||||||
@compiler.output = lua.html
|
|
||||||
@nav.title = Lua
|
|
||||||
@nav.category = Lua
|
|
||||||
@html.page_title = Documentation | Lua
|
|
||||||
|
|
||||||
# Running lua code
|
|
||||||
|
|
||||||
Running lua code is done using the following syntax:
|
|
||||||
``Lua, %<print("Hello World!")>%``
|
|
||||||
|
|
||||||
## Lua to text
|
|
||||||
To convert the return value of your lua code, append ``"`` at the start of your lua expression:
|
|
||||||
* ``Lua, %<"return "Hello World">%`` → %<"return "Hello World">%
|
|
||||||
* ``Lua, %<" "Hello, " .. "World">%`` → %<" "Hello, " .. "World">%
|
|
||||||
|
|
||||||
## Parse lua string
|
|
||||||
Additionnaly, you can output lua to be parsed by the document's parser. To do so, append ``!`` at the start of your lua expression:
|
|
||||||
* ``Lua, %<!"**" .. "Bold from lua?" .. "**">%`` → %<!"**" .. "Bold from lua?" .. "**">%
|
|
||||||
* ``Lua, %<!"[" .. "Link from Lua" .. "](#)">%`` → %<!"[" .. "Link from Lua" .. "](#)">%
|
|
|
@ -1,31 +0,0 @@
|
||||||
@import ../template.nml
|
|
||||||
@compiler.output = basic.html
|
|
||||||
@nav.title = Basic
|
|
||||||
@nav.category = Styles
|
|
||||||
@html.page_title = Documentation | Basic Styles
|
|
||||||
|
|
||||||
# Basic styles
|
|
||||||
## Bold
|
|
||||||
|
|
||||||
Enclose text between two ``**`` to render it **bold**!
|
|
||||||
* ``**Bold text**`` → **Bold text**
|
|
||||||
* ``**Bold [link](#)**`` → **Bold [link](#)**
|
|
||||||
|
|
||||||
## Italic
|
|
||||||
|
|
||||||
Enclose text between two ``*`` to render it *italic*!
|
|
||||||
* ``*Italic text*`` → *Italic text*
|
|
||||||
* ``**Bold + *Italic***`` → **Bold + *Italic***
|
|
||||||
|
|
||||||
## Underline
|
|
||||||
|
|
||||||
Enclose text between two ``__`` to render it __underlined__!
|
|
||||||
* ``__Underlined text__`` → __Underlined text__
|
|
||||||
* ``__Underline + *Italic*__`` → __Underline + *Italic*__
|
|
||||||
|
|
||||||
## Highlighted
|
|
||||||
|
|
||||||
Enclose text between two `` ` `` to render it `overlined`!
|
|
||||||
* `` `Highlighted text` `` → `Highlighted text`
|
|
||||||
* `` `Highlight + **Bold**` `` → `Highlight + **Bold**`
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
@import ../template.nml
|
|
||||||
@compiler.output = user-defined.html
|
|
||||||
@nav.title = User-Defined
|
|
||||||
@nav.category = Styles
|
|
||||||
@html.page_title = Documentation | User-Defined Styles
|
|
||||||
|
|
||||||
# TODO
|
|
|
@ -1,8 +0,0 @@
|
||||||
@html.css = ../style.css
|
|
||||||
|
|
||||||
@tex.main.fontsize = 9
|
|
||||||
@tex.main.preamble = \usepackage{xcolor, tikz, pgfplots} \\
|
|
||||||
\usepgfplotslibrary{patchplots} \\
|
|
||||||
\definecolor{__color1}{HTML}{d5d5d5} \\
|
|
||||||
\everymath{\color{__color1}\displaystyle}
|
|
||||||
@tex.main.block_prepend = \color{__color1}
|
|
|
@ -19,7 +19,6 @@ pub struct Compiler {
|
||||||
target: Target,
|
target: Target,
|
||||||
cache: Option<RefCell<Connection>>,
|
cache: Option<RefCell<Connection>>,
|
||||||
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 {
|
||||||
|
@ -74,8 +73,8 @@ impl Compiler {
|
||||||
self.cache.as_ref().map(RefCell::borrow_mut)
|
self.cache.as_ref().map(RefCell::borrow_mut)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sanitize<S: AsRef<str>>(target: Target, str: S) -> String {
|
pub fn sanitize<S: AsRef<str>>(&self, str: S) -> String {
|
||||||
match target {
|
match self.target {
|
||||||
Target::HTML => str
|
Target::HTML => str
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.replace("&", "&")
|
.replace("&", "&")
|
||||||
|
@ -109,18 +108,18 @@ impl Compiler {
|
||||||
result += "<!DOCTYPE HTML><html><head>";
|
result += "<!DOCTYPE HTML><html><head>";
|
||||||
result += "<meta charset=\"UTF-8\">";
|
result += "<meta charset=\"UTF-8\">";
|
||||||
if let Some(page_title) = get_variable_or_error(document, "html.page_title") {
|
if let Some(page_title) = get_variable_or_error(document, "html.page_title") {
|
||||||
result += format!("<title>{}</title>", Compiler::sanitize(self.target(), page_title.to_string()))
|
result += format!("<title>{}</title>", self.sanitize(page_title.to_string()))
|
||||||
.as_str();
|
.as_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(css) = document.get_variable("html.css") {
|
if let Some(css) = document.get_variable("html.css") {
|
||||||
result += format!(
|
result += format!(
|
||||||
"<link rel=\"stylesheet\" href=\"{}\">",
|
"<link rel=\"stylesheet\" href=\"{}\">",
|
||||||
Compiler::sanitize(self.target(), css.to_string())
|
self.sanitize(css.to_string())
|
||||||
)
|
)
|
||||||
.as_str();
|
.as_str();
|
||||||
}
|
}
|
||||||
result += r#"</head><body><div id="layout">"#;
|
result += "</head><body>";
|
||||||
|
|
||||||
// TODO: TOC
|
// TODO: TOC
|
||||||
// TODO: Author, Date, Title, Div
|
// TODO: Author, Date, Title, Div
|
||||||
|
@ -134,125 +133,42 @@ impl Compiler {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
match self.target() {
|
match self.target() {
|
||||||
Target::HTML => {
|
Target::HTML => {
|
||||||
result += "</div></body></html>";
|
result += "</body></html>";
|
||||||
}
|
}
|
||||||
Target::LATEX => todo!(""),
|
Target::LATEX => {}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile(&self, document: &dyn Document) -> CompiledDocument {
|
pub fn compile(&self, document: &dyn Document) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
let borrow = document.content().borrow();
|
let borrow = document.content().borrow();
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
let header = self.header(document);
|
out += self.header(document).as_str();
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
let mut body = r#"<div id="content">"#.to_string();
|
|
||||||
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) => body.push_str(result.as_str()),
|
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()),
|
Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body.push_str("</div>");
|
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
let footer = self.footer(document);
|
out += self.footer(document).as_str();
|
||||||
|
|
||||||
// Variables
|
out
|
||||||
let variables = document
|
|
||||||
.scope()
|
|
||||||
.borrow_mut()
|
|
||||||
.variables
|
|
||||||
.iter()
|
|
||||||
.map(|(key, var)| (key.clone(), var.to_string()))
|
|
||||||
.collect::<HashMap<String, String>>();
|
|
||||||
|
|
||||||
CompiledDocument {
|
|
||||||
input: document.source().name().clone(),
|
|
||||||
mtime: 0,
|
|
||||||
variables,
|
|
||||||
header,
|
|
||||||
body,
|
|
||||||
footer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CompiledDocument {
|
|
||||||
/// Input path relative to the input directory
|
|
||||||
pub input: String,
|
|
||||||
/// Modification time (i.e seconds since last epoch)
|
|
||||||
pub mtime: u64,
|
|
||||||
|
|
||||||
// TODO: Also store exported references
|
|
||||||
// so they can be referenced from elsewhere
|
|
||||||
// This will also require rebuilding in case some exported references have changed...
|
|
||||||
/// Variables exported to string, so they can be querried later
|
|
||||||
pub variables: HashMap<String, String>,
|
|
||||||
|
|
||||||
/// Compiled document's header
|
|
||||||
pub header: String,
|
|
||||||
/// Compiled document's body
|
|
||||||
pub body: String,
|
|
||||||
/// Compiled document's footer
|
|
||||||
pub footer: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompiledDocument {
|
|
||||||
pub fn get_variable(&self, name: &str) -> Option<&String> { self.variables.get(name) }
|
|
||||||
|
|
||||||
fn sql_table() -> &'static str {
|
|
||||||
"CREATE TABLE IF NOT EXISTS compiled_documents (
|
|
||||||
input TEXT PRIMARY KEY,
|
|
||||||
mtime INTEGER NOT NULL,
|
|
||||||
variables TEXT NOT NULL,
|
|
||||||
header TEXT NOT NULL,
|
|
||||||
body TEXT NOT NULL,
|
|
||||||
footer TEXT NOT NULL
|
|
||||||
);"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sql_get_query() -> &'static str { "SELECT * FROM compiled_documents WHERE input = (?1)" }
|
|
||||||
|
|
||||||
fn sql_insert_query() -> &'static str {
|
|
||||||
"INSERT OR REPLACE INTO compiled_documents (input, mtime, variables, header, body, footer) VALUES (?1, ?2, ?3, ?4, ?5, ?6)"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_cache(con: &Connection) -> Result<usize, rusqlite::Error> {
|
|
||||||
con.execute(Self::sql_table(), [])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_cache(con: &Connection, input: &str) -> Option<Self> {
|
|
||||||
con.query_row(Self::sql_get_query(), [input], |row| {
|
|
||||||
Ok(CompiledDocument {
|
|
||||||
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),
|
|
||||||
body: row.get_unwrap::<_, String>(4),
|
|
||||||
footer: row.get_unwrap::<_, String>(5),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts [`CompiledDocument`] into cache
|
|
||||||
pub fn insert_cache(&self, con: &Connection) -> Result<usize, rusqlite::Error> {
|
|
||||||
con.execute(
|
|
||||||
Self::sql_insert_query(),
|
|
||||||
(
|
|
||||||
&self.input,
|
|
||||||
&self.mtime,
|
|
||||||
serde_json::to_string(&self.variables).unwrap(),
|
|
||||||
&self.header,
|
|
||||||
&self.body,
|
|
||||||
&self.footer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
pub mod navigation;
|
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::compiler::compiler::Compiler;
|
|
||||||
|
|
||||||
use super::compiler::CompiledDocument;
|
|
||||||
use super::compiler::Target;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct NavEntry {
|
|
||||||
pub(self) entries: Vec<(String, String)>,
|
|
||||||
pub(self) children: HashMap<String, NavEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NavEntry {
|
|
||||||
// FIXME: Sanitize
|
|
||||||
pub fn compile(&self, target: Target, doc: &CompiledDocument) -> String {
|
|
||||||
let categories = vec![
|
|
||||||
doc.get_variable("nav.category").map_or("", |s| s.as_str()),
|
|
||||||
doc.get_variable("nav.subcategory")
|
|
||||||
.map_or("", |s| s.as_str()),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut result = String::new();
|
|
||||||
match target {
|
|
||||||
Target::HTML => {
|
|
||||||
result += r#"<div id="navbar"><ul>"#;
|
|
||||||
|
|
||||||
fn process(
|
|
||||||
target: Target,
|
|
||||||
categories: &Vec<&str>,
|
|
||||||
did_match: bool,
|
|
||||||
result: &mut String,
|
|
||||||
entry: &NavEntry,
|
|
||||||
depth: usize,
|
|
||||||
) {
|
|
||||||
// Orphans = Links
|
|
||||||
for (title, path) in &entry.entries {
|
|
||||||
result.push_str(
|
|
||||||
format!(
|
|
||||||
r#"<li><a href="{}">{}</a></li>"#,
|
|
||||||
Compiler::sanitize(target, path),
|
|
||||||
Compiler::sanitize(target, title)
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse
|
|
||||||
for (name, ent) in &entry.children {
|
|
||||||
let is_match = if did_match {
|
|
||||||
categories.get(depth) == Some(&name.as_str())
|
|
||||||
} else {
|
|
||||||
false || depth == 0
|
|
||||||
};
|
|
||||||
result.push_str("<li>");
|
|
||||||
result.push_str(
|
|
||||||
format!(
|
|
||||||
"<details{}><summary>{}</summary>",
|
|
||||||
["", " open"][is_match as usize],
|
|
||||||
Compiler::sanitize(target, name)
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
);
|
|
||||||
result.push_str("<ul>");
|
|
||||||
process(target, categories, is_match, result, ent, depth + 1);
|
|
||||||
result.push_str("</ul></details></li>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process(target, &categories, true, &mut result, self, 0);
|
|
||||||
|
|
||||||
result += r#"</ul></div>"#;
|
|
||||||
}
|
|
||||||
_ => todo!(""),
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, String> {
|
|
||||||
let mut nav = NavEntry {
|
|
||||||
entries: vec![],
|
|
||||||
children: 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 (title, path) = match (title, path) {
|
|
||||||
(Some(title), Some(path)) => (title, path),
|
|
||||||
_ => {
|
|
||||||
eprintln!("Skipping navigation generation for `{}`, must have a defined `@nav.title` and `@compiler.output`", doc.input);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let pent = if let Some(subcat) = subcat {
|
|
||||||
let cat = match cat {
|
|
||||||
Some(cat) => cat,
|
|
||||||
None => {
|
|
||||||
eprintln!(
|
|
||||||
"Skipping `{}`: No `@nav.category`, but `@nav.subcategory` is set",
|
|
||||||
doc.input
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut cat_ent = match nav.children.get_mut(cat.as_str()) {
|
|
||||||
Some(cat_ent) => cat_ent,
|
|
||||||
None => {
|
|
||||||
// Insert
|
|
||||||
nav.children.insert(cat.clone(), NavEntry::default());
|
|
||||||
nav.children.get_mut(cat.as_str()).unwrap()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match cat_ent.children.get_mut(subcat.as_str()) {
|
|
||||||
Some(subcat_ent) => subcat_ent,
|
|
||||||
None => {
|
|
||||||
// Insert
|
|
||||||
cat_ent.children.insert(subcat.clone(), NavEntry::default());
|
|
||||||
cat_ent.children.get_mut(subcat.as_str()).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(cat) = cat {
|
|
||||||
match nav.children.get_mut(cat.as_str()) {
|
|
||||||
Some(cat_ent) => cat_ent,
|
|
||||||
None => {
|
|
||||||
// Insert
|
|
||||||
nav.children.insert(cat.clone(), NavEntry::default());
|
|
||||||
nav.children.get_mut(cat.as_str()).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
&mut nav
|
|
||||||
};
|
|
||||||
|
|
||||||
pent.entries.push((title.clone(), path.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(nav)
|
|
||||||
}
|
|
|
@ -91,6 +91,9 @@ 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(),
|
||||||
|
@ -102,3 +105,42 @@ 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() }
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -117,7 +117,7 @@ impl Code {
|
||||||
if let Some(name) = &self.name {
|
if let Some(name) = &self.name {
|
||||||
result += format!(
|
result += format!(
|
||||||
"<div class=\"code-block-title\">{}</div>",
|
"<div class=\"code-block-title\">{}</div>",
|
||||||
Compiler::sanitize(compiler.target(), name.as_str())
|
compiler.sanitize(name.as_str())
|
||||||
)
|
)
|
||||||
.as_str();
|
.as_str();
|
||||||
}
|
}
|
||||||
|
@ -321,7 +321,7 @@ impl CodeRule {
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Regex::new(
|
Regex::new(
|
||||||
r"``(?:\[((?:\\.|[^\\\\])*?)\])?(?:(.*?),)?((?:\\(?:.|\n)|[^\\\\])*?)``",
|
r"``(?:\[((?:\\.|[^\[\]\\])*?)\])?(?:(.*?)(?:\n|,))?((?:\\(?:.|\n)|[^\\\\])*?)``",
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
],
|
],
|
||||||
|
@ -612,13 +612,7 @@ impl RegexRule for CodeRule {
|
||||||
bindings.push((
|
bindings.push((
|
||||||
"push_block".to_string(),
|
"push_block".to_string(),
|
||||||
lua.create_function(
|
lua.create_function(
|
||||||
|_,
|
|_, (language, name, content, line_offset): (String, Option<String>, String, Option<usize>)| {
|
||||||
(language, name, content, line_offset): (
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
String,
|
|
||||||
Option<usize>,
|
|
||||||
)| {
|
|
||||||
CTX.with_borrow(|ctx| {
|
CTX.with_borrow(|ctx| {
|
||||||
ctx.as_ref().map(|ctx| {
|
ctx.as_ref().map(|ctx| {
|
||||||
let theme = ctx
|
let theme = ctx
|
||||||
|
|
|
@ -45,13 +45,13 @@ impl Element for Link {
|
||||||
match compiler.target() {
|
match compiler.target() {
|
||||||
Target::HTML => Ok(format!(
|
Target::HTML => Ok(format!(
|
||||||
"<a href=\"{}\">{}</a>",
|
"<a href=\"{}\">{}</a>",
|
||||||
Compiler::sanitize(compiler.target(), self.url.as_str()),
|
compiler.sanitize(self.url.as_str()),
|
||||||
Compiler::sanitize(compiler.target(), self.name.as_str()),
|
compiler.sanitize(self.name.as_str()),
|
||||||
)),
|
)),
|
||||||
Target::LATEX => Ok(format!(
|
Target::LATEX => Ok(format!(
|
||||||
"\\href{{{}}}{{{}}}",
|
"\\href{{{}}}{{{}}}",
|
||||||
Compiler::sanitize(compiler.target(), self.url.as_str()),
|
compiler.sanitize(self.url.as_str()),
|
||||||
Compiler::sanitize(compiler.target(), self.name.as_str()),
|
compiler.sanitize(self.name.as_str()),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,26 +150,18 @@ impl Element for Medium {
|
||||||
.map_or(String::new(), |w| format!(r#" style="width:{w};""#));
|
.map_or(String::new(), |w| format!(r#" style="width:{w};""#));
|
||||||
result.push_str(format!(r#"<div class="medium"{width}>"#).as_str());
|
result.push_str(format!(r#"<div class="medium"{width}>"#).as_str());
|
||||||
result += match self.media_type {
|
result += match self.media_type {
|
||||||
MediaType::IMAGE => format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri),
|
MediaType::IMAGE =>
|
||||||
MediaType::VIDEO => format!(
|
format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri),
|
||||||
r#"<video controls{width}><source src="{0}"></video>"#,
|
MediaType::VIDEO =>
|
||||||
self.uri
|
format!(r#"<video controls{width}><source src="{0}"></video>"#, self.uri),
|
||||||
),
|
MediaType::AUDIO =>
|
||||||
MediaType::AUDIO => {
|
format!(r#"<audio controls src="{0}"{width}></audio>"#, self.uri),
|
||||||
format!(r#"<audio controls src="{0}"{width}></audio>"#, self.uri)
|
}.as_str();
|
||||||
}
|
|
||||||
}
|
|
||||||
.as_str();
|
|
||||||
|
|
||||||
let caption = self
|
let caption = self
|
||||||
.caption
|
.caption
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|cap| {
|
.and_then(|cap| Some(format!(" {}", compiler.sanitize(cap.as_str()))))
|
||||||
Some(format!(
|
|
||||||
" {}",
|
|
||||||
Compiler::sanitize(compiler.target(), cap.as_str())
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.unwrap_or(String::new());
|
.unwrap_or(String::new());
|
||||||
|
|
||||||
// Reference
|
// Reference
|
||||||
|
|
|
@ -41,7 +41,7 @@ impl Element for Section {
|
||||||
Target::HTML => Ok(format!(
|
Target::HTML => Ok(format!(
|
||||||
"<h{0}>{1}</h{0}>",
|
"<h{0}>{1}</h{0}>",
|
||||||
self.depth,
|
self.depth,
|
||||||
Compiler::sanitize(compiler.target(), self.title.as_str())
|
compiler.sanitize(self.title.as_str())
|
||||||
)),
|
)),
|
||||||
Target::LATEX => Err("Unimplemented compiler".to_string()),
|
Target::LATEX => Err("Unimplemented compiler".to_string()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,8 @@ 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());
|
||||||
|
@ -189,7 +191,7 @@ impl Element for Tex {
|
||||||
Tex::format_latex(&fontsize, &preamble, &format!("{prepend}{}", self.tex))
|
Tex::format_latex(&fontsize, &preamble, &format!("{prepend}{}", self.tex))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result = if let Some(mut con) = compiler.cache() {
|
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 {
|
||||||
|
@ -201,22 +203,7 @@ 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"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl Element for Text {
|
||||||
fn to_string(&self) -> String { format!("{self:#?}") }
|
fn to_string(&self) -> String { format!("{self:#?}") }
|
||||||
|
|
||||||
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
|
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
|
||||||
Ok(Compiler::sanitize(compiler.target(), self.content.as_str()))
|
Ok(compiler.sanitize(self.content.as_str()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub struct VariableRule {
|
||||||
impl VariableRule {
|
impl VariableRule {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
re: [Regex::new(r"(?:^|\n)@([^[:alpha:]])?(.*?)=((?:\\\n|.)*)").unwrap()],
|
re: [Regex::new(r"(?:^|\n)@([^[:alpha:]])?(.*)=((?:\\\n|.)*)").unwrap()],
|
||||||
kinds: vec![
|
kinds: vec![
|
||||||
("".into(), "Regular".into()),
|
("".into(), "Regular".into()),
|
||||||
("'".into(), "Path".into())
|
("'".into(), "Path".into())
|
||||||
|
@ -89,6 +89,8 @@ impl RegexRule for VariableRule {
|
||||||
|
|
||||||
fn regexes(&self) -> &[Regex] { &self.re }
|
fn regexes(&self) -> &[Regex] { &self.re }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn on_regex_match<'a>(&self, _: usize, parser: &dyn Parser, document: &'a dyn Document, token: Token, matches: regex::Captures) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>
|
fn on_regex_match<'a>(&self, _: usize, parser: &dyn Parser, document: &'a dyn Document, token: Token, matches: regex::Captures) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>
|
||||||
{
|
{
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
|
|
361
src/main.rs
361
src/main.rs
|
@ -7,29 +7,18 @@ mod lua;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
use std::env;
|
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::rc::Rc;
|
||||||
use std::time::UNIX_EPOCH;
|
|
||||||
|
|
||||||
use compiler::compiler::CompiledDocument;
|
|
||||||
use compiler::compiler::Compiler;
|
use compiler::compiler::Compiler;
|
||||||
use compiler::compiler::Target;
|
|
||||||
use compiler::navigation::create_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;
|
||||||
use rusqlite::Connection;
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
use crate::parser::source::SourceFile;
|
use crate::parser::source::SourceFile;
|
||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
|
|
||||||
fn print_usage(program: &str, opts: Options) {
|
fn print_usage(program: &str, opts: Options) {
|
||||||
let brief = format!("Usage: {} -i PATH -o PATH [options]", program);
|
let brief = format!("Usage: {} -i FILE [options]", program);
|
||||||
print!("{}", opts.usage(&brief));
|
print!("{}", opts.usage(&brief));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +36,40 @@ NML version: 0.4\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(input: &str, debug_opts: &Vec<String>) -> Result<Box<dyn Document<'static>>, String> {
|
fn main() {
|
||||||
println!("Parsing {input}...");
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let program = args[0].clone();
|
||||||
|
|
||||||
|
let mut opts = Options::new();
|
||||||
|
opts.optopt("i", "", "Input file", "FILE");
|
||||||
|
opts.optopt("d", "database", "Cache database location", "PATH");
|
||||||
|
opts.optmulti("z", "debug", "Debug options", "OPTS");
|
||||||
|
opts.optflag("h", "help", "Print this help menu");
|
||||||
|
opts.optflag("v", "version", "Print program version and licenses");
|
||||||
|
|
||||||
|
let matches = match opts.parse(&args[1..]) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(f) => {
|
||||||
|
panic!("{}", f.to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if matches.opt_present("v") {
|
||||||
|
print_version();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if matches.opt_present("h") {
|
||||||
|
print_usage(&program, opts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !matches.opt_present("i") {
|
||||||
|
print_usage(&program, opts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = matches.opt_str("i").unwrap();
|
||||||
|
let debug_opts = matches.opt_strs("z");
|
||||||
|
let db_path = matches.opt_str("d");
|
||||||
|
|
||||||
let parser = LangParser::default();
|
let parser = LangParser::default();
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
|
@ -82,312 +103,12 @@ fn parse(input: &str, debug_opts: &Vec<String>) -> Result<Box<dyn Document<'stat
|
||||||
}
|
}
|
||||||
|
|
||||||
if parser.has_error() {
|
if parser.has_error() {
|
||||||
return Err("Parsing failed aborted due to errors while parsing".to_string());
|
println!("Compilation aborted due to errors while parsing");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(doc)
|
let compiler = Compiler::new(compiler::compiler::Target::HTML, db_path);
|
||||||
}
|
let out = compiler.compile(doc.as_ref());
|
||||||
|
|
||||||
fn process(
|
std::fs::write("a.html", out).unwrap();
|
||||||
target: Target,
|
|
||||||
files: Vec<PathBuf>,
|
|
||||||
db_path: &Option<String>,
|
|
||||||
force_rebuild: bool,
|
|
||||||
debug_opts: &Vec<String>,
|
|
||||||
) -> Result<Vec<CompiledDocument>, 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))
|
|
||||||
.map_err(|err| format!("Unable to open connection to the database: {err}"))?;
|
|
||||||
CompiledDocument::init_cache(&con)
|
|
||||||
.map_err(|err| format!("Failed to initialize cached document table: {err}"))?;
|
|
||||||
|
|
||||||
for file in files {
|
|
||||||
let meta = std::fs::metadata(&file)
|
|
||||||
.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}"))?;
|
|
||||||
|
|
||||||
// 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> {
|
|
||||||
// Parse
|
|
||||||
let doc = parse(file.to_str().unwrap(), debug_opts)?;
|
|
||||||
|
|
||||||
// Compile
|
|
||||||
let compiler = Compiler::new(target, db_path.clone());
|
|
||||||
let mut compiled = compiler.compile(&*doc);
|
|
||||||
|
|
||||||
// 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}")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(compiled)
|
|
||||||
};
|
|
||||||
|
|
||||||
let cdoc = if force_rebuild {
|
|
||||||
parse_and_compile()?
|
|
||||||
} else {
|
|
||||||
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()?
|
|
||||||
} else {
|
|
||||||
compiled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => parse_and_compile()?,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
compiled.push(cdoc);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::env::set_current_dir(current_dir)
|
|
||||||
.map_err(|err| format!("Failed to set current directory: {err}"))?;
|
|
||||||
|
|
||||||
Ok(compiled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
let program = args[0].clone();
|
|
||||||
|
|
||||||
let mut opts = Options::new();
|
|
||||||
opts.optopt("i", "input", "Input path", "PATH");
|
|
||||||
opts.optopt("o", "output", "Output path", "PATH");
|
|
||||||
opts.optopt("d", "database", "Cache database location", "PATH");
|
|
||||||
opts.optflag("", "force-rebuild", "Force rebuilding of cached documents");
|
|
||||||
opts.optmulti("z", "debug", "Debug options", "OPTS");
|
|
||||||
opts.optflag("h", "help", "Print this help menu");
|
|
||||||
opts.optflag("v", "version", "Print program version and licenses");
|
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(f) => {
|
|
||||||
panic!("{}", f.to_string())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if matches.opt_present("v") {
|
|
||||||
print_version();
|
|
||||||
return ExitCode::SUCCESS;
|
|
||||||
}
|
|
||||||
if matches.opt_present("h") {
|
|
||||||
print_usage(&program, opts);
|
|
||||||
return ExitCode::SUCCESS;
|
|
||||||
}
|
|
||||||
if !matches.opt_present("i") || !matches.opt_present("o") {
|
|
||||||
print_usage(&program, opts);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = matches.opt_str("i").unwrap();
|
|
||||||
let input_meta = match std::fs::metadata(&input) {
|
|
||||||
Ok(meta) => meta,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Unable to get metadata for input: `{input}`");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let output = matches.opt_str("o").unwrap();
|
|
||||||
if input_meta.is_dir() {
|
|
||||||
// Create ouput directories
|
|
||||||
if !std::fs::exists(&output).unwrap_or(false) {
|
|
||||||
match std::fs::create_dir_all(&output) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Unable to create output directory `{output}`: {err}");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match std::fs::metadata(&output) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Unable to get metadata for output: `{output}`");
|
|
||||||
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 = 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!("Directory mode requires a database (-d)");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
for entry in WalkDir::new(&input) {
|
|
||||||
if let Err(err) = entry {
|
|
||||||
eprintln!("Failed to recursively walk over input directory: {err}");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
match entry.as_ref().unwrap().metadata() {
|
|
||||||
Ok(meta) => {
|
|
||||||
if !meta.is_file() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Faield to get metadata for `{entry:#?}`");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = match entry.as_ref().unwrap().path().to_str() {
|
|
||||||
Some(path) => path.to_string(),
|
|
||||||
None => {
|
|
||||||
eprintln!("Faield to convert input file `{entry:#?}` to UTF-8");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !path.ends_with(".nml") {
|
|
||||||
println!("Skipping '{path}'");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
files.push(std::fs::canonicalize(path).unwrap());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 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
|
|
||||||
let compiled = match process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) {
|
|
||||||
Ok(compiled) => compiled,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 nav = navigation.compile(Target::HTML, &doc);
|
|
||||||
let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ impl Parser for LangParser {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
paragraph.push(elem).unwrap();
|
paragraph.push(elem);
|
||||||
} else {
|
} else {
|
||||||
// Process paragraph events
|
// Process paragraph events
|
||||||
if doc.last_element::<Paragraph>().is_some_and(|_| true) {
|
if doc.last_element::<Paragraph>().is_some_and(|_| true) {
|
||||||
|
|
72
style.css
72
style.css
|
@ -7,7 +7,6 @@ body {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles */
|
|
||||||
em {
|
em {
|
||||||
padding-left: .1em;
|
padding-left: .1em;
|
||||||
padding-right: .1em;
|
padding-right: .1em;
|
||||||
|
@ -20,7 +19,9 @@ em {
|
||||||
background-color: #191f26;
|
background-color: #191f26;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.inline-code {
|
/* Styles */
|
||||||
|
a.inline-code
|
||||||
|
{
|
||||||
padding-left: .1em;
|
padding-left: .1em;
|
||||||
padding-right: .1em;
|
padding-right: .1em;
|
||||||
|
|
||||||
|
@ -28,73 +29,6 @@ a.inline-code {
|
||||||
background-color: #191f26;
|
background-color: #191f26;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navbar */
|
|
||||||
#navbar {
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: max(16vw, 20ch);
|
|
||||||
overflow-y: auto;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overscroll-behavior-y: contain;
|
|
||||||
|
|
||||||
background-color: #161a26;
|
|
||||||
color: #aaa;
|
|
||||||
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar a {
|
|
||||||
color: #ffb454;
|
|
||||||
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar li {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
padding-left: 1em;
|
|
||||||
margin-left: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar ul {
|
|
||||||
margin-left: 0em;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar summary{
|
|
||||||
display: block;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar summary::marker,
|
|
||||||
#navbar summary::-webkit-details-marker{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar summary:focus{
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar summary:focus-visible{
|
|
||||||
outline: 1px dotted #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar summary:before {
|
|
||||||
content: "+";
|
|
||||||
color: #ffb454;
|
|
||||||
float: left;
|
|
||||||
text-align: center;
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar details[open] > summary:before {
|
|
||||||
content: "–";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Code blocks */
|
/* Code blocks */
|
||||||
div.code-block-title {
|
div.code-block-title {
|
||||||
background-color: #20202a;
|
background-color: #20202a;
|
||||||
|
|
Loading…
Reference in a new issue