Compare commits

..

No commits in common. "e4ce3edc4df0227ad7878386ab47aa84d010d9c3" and "a00db70bf64d7dc2d29b4ae1452247b45a2147d9" have entirely different histories.

15 changed files with 195 additions and 829 deletions

View file

@ -1,31 +1,28 @@
@import ../template.nml @import ../template.nml
%<make_doc({"External Tools"}, "Graphviz", "Graphviz")>% %<make_doc({"External Tools"}, "Graphviz", "Graphvis")>%
# Graphs from graphviz # Graphs from graphviz
#+LAYOUT_BEGIN Centered [graph]
[graph][width=50%]
digraph { digraph {
bgcolor=transparent; bgcolor=transparent;
graph[fontcolor=darkgray];
node[fontcolor=darkgray];
edge[fontcolor=darkgray, color=gray90];
filelist [shape=box, color=orange, label="File List"]; filelist [color=green, label="File List"];
doclist [shape=box, color=orange, label="Document List"]; doclist [color=green, label="Document List"];
iscached [shape=diamond, color=red, label="Cached?"]; iscached [shape=diamond, color=red, label="Cached?"];
parse [shape=box, color=white, label=Parse]; parse [color=white, label=Parse];
compile [shape=box,color=white, label=Compile]; compile [color=white, label=Compile];
cache [shape=box, color=orange, label=Cache]; cache [shape=box, color=blue, label=Cache];
edge [color=gray]
filelist -> iscached; filelist -> iscached;
iscached -> cache[dir=both]; iscached -> cache[dir=both];
iscached -> doclist[label="Yes"]; iscached -> doclist[label="+"];
iscached -> parse[label="No"]; iscached -> parse[label="No"];
parse -> compile; parse -> compile;
compile -> cache[label=""]; compile -> cache[label="+"];
compile -> doclist[label=""]; compile -> doclist[label="+"];
buildnav [color=white, label="Build Navigation"]; buildnav [color=white, label="Build Navigation"];
doclist -> buildnav; doclist -> buildnav;
@ -33,279 +30,3 @@ digraph {
buildnav -> output; buildnav -> output;
} }
[/graph] [/graph]
#+LAYOUT_END
Graphs blocks are delimited by `` [graph]...[/graph]``
# Properties
* ``layout`` The layout engine, defaults to `dot`
see [Graphviz's documentation](https://graphviz.org/docs/layouts/), allowed values:
*- [`dot`](https://graphviz.org/docs/layouts/dot/)
*- [`neato`](https://graphviz.org/docs/layouts/neato/)
*- [`fdp`](https://graphviz.org/docs/layouts/fdp/)
*- [`sfdp`](https://graphviz.org/docs/layouts/sfdp/)
*- [`circo`](https://graphviz.org/docs/layouts/circo/)
*- [`twopi`](https://graphviz.org/docs/layouts/twopi/)
*- [`osage`](https://graphviz.org/docs/layouts/osage/)
*- [`patchwork`](https://graphviz.org/docs/layouts/patchwork/)
* ``width`` The resulting svg's width property, defaults to `100%`
# Examples
#+LAYOUT_BEGIN[style=flex:0.33] Split
[graph]
digraph UML_Class_diagram {
bgcolor=transparent;
graph[fontcolor=darkgray];
node[fontcolor=darkgray];
edge[fontcolor=darkgray, color=gray90];
graph [
label="UML Class diagram demo"
labelloc="t"
fontname="Helvetica,Arial,sans-serif"
]
node [
fontname="Helvetica,Arial,sans-serif"
shape=record
style=filled
fillcolor=gray95
]
edge [fontname="Helvetica,Arial,sans-serif"]
edge [arrowhead=vee style=dashed]
Client -> Interface1 [label=dependency]
Client -> Interface2
edge [dir=back arrowtail=empty style=""]
Interface1 -> Class1 [xlabel=inheritance]
Interface2 -> Class1 [dir=none]
Interface2 [label="" xlabel="Simple\ninterface" shape=circle]
Interface1[label = <{<b>«interface» I/O</b> | + property<br align="left"/>...<br align="left"/>|+ method<br align="left"/>...<br align="left"/>}>]
Class1[label = <{<b>I/O class</b> | + property<br align="left"/>...<br align="left"/>|+ method<br align="left"/>...<br align="left"/>}>]
edge [dir=back arrowtail=empty style=dashed]
Class1 -> System_1 [label=implementation]
System_1 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>System</b> </td> </tr>
<tr> <td>
<table border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left" >+ property</td> </tr>
<tr> <td port="ss1" align="left" >- Subsystem 1</td> </tr>
<tr> <td port="ss2" align="left" >- Subsystem 2</td> </tr>
<tr> <td port="ss3" align="left" >- Subsystem 3</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">+ method<br/>...<br align="left"/></td> </tr>
</table>>
]
edge [dir=back arrowtail=diamond]
System_1:ss1 -> Subsystem_1 [xlabel="composition"]
Subsystem_1 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>Subsystem 1</b> </td> </tr>
<tr> <td>
<table border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left">+ property</td> </tr>
<tr> <td align="left" port="r1">- resource</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">
+ method<br/>
...<br align="left"/>
</td> </tr>
</table>>
]
Subsystem_2 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>Subsystem 2</b> </td> </tr>
<tr> <td>
<table align="left" border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left">+ property</td> </tr>
<tr> <td align="left" port="r1">- resource</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">
+ method<br/>
...<br align="left"/>
</td> </tr>
</table>>
]
Subsystem_3 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>Subsystem 3</b> </td> </tr>
<tr> <td>
<table border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left">+ property</td> </tr>
<tr> <td align="left" port="r1">- resource</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">
+ method<br/>
...<br align="left"/>
</td> </tr>
</table>>
]
System_1:ss2 -> Subsystem_2;
System_1:ss3 -> Subsystem_3;
edge [xdir=back arrowtail=odiamond]
Subsystem_1:r1 -> "Shared resource" [label=aggregation]
Subsystem_2:r1 -> "Shared resource"
Subsystem_3:r1 -> "Shared resource"
"Shared resource" [
label = <{
<b>Shared resource</b>
|
+ property<br align="left"/>
...<br align="left"/>
|
+ method<br align="left"/>
...<br align="left"/>
}>
]
}
[/graph]
#+LAYOUT_NEXT[style=flex:0.66]
Generated by the following code:
``
[graph]
digraph UML_Class_diagram {
bgcolor=transparent;
graph[fontcolor=darkgray];
node[fontcolor=darkgray];
edge[fontcolor=darkgray, color=gray90];
graph [
label="UML Class diagram demo"
labelloc="t"
fontname="Helvetica,Arial,sans-serif"
]
node [
fontname="Helvetica,Arial,sans-serif"
shape=record
style=filled
fillcolor=gray95
]
edge [fontname="Helvetica,Arial,sans-serif"]
edge [arrowhead=vee style=dashed]
Client -> Interface1 [label=dependency]
Client -> Interface2
edge [dir=back arrowtail=empty style=""]
Interface1 -> Class1 [xlabel=inheritance]
Interface2 -> Class1 [dir=none]
Interface2 [label="" xlabel="Simple\ninterface" shape=circle]
Interface1[label = <{<b>«interface» I/O</b> | + property<br align="left"/>...<br align="left"/>|+ method<br align="left"/>...<br align="left"/>}>]
Class1[label = <{<b>I/O class</b> | + property<br align="left"/>...<br align="left"/>|+ method<br align="left"/>...<br align="left"/>}>]
edge [dir=back arrowtail=empty style=dashed]
Class1 -> System_1 [label=implementation]
System_1 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>System</b> </td> </tr>
<tr> <td>
<table border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left" >+ property</td> </tr>
<tr> <td port="ss1" align="left" >- Subsystem 1</td> </tr>
<tr> <td port="ss2" align="left" >- Subsystem 2</td> </tr>
<tr> <td port="ss3" align="left" >- Subsystem 3</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">+ method<br/>...<br align="left"/></td> </tr>
</table>>
]
edge [dir=back arrowtail=diamond]
System_1:ss1 -> Subsystem_1 [xlabel="composition"]
Subsystem_1 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>Subsystem 1</b> </td> </tr>
<tr> <td>
<table border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left">+ property</td> </tr>
<tr> <td align="left" port="r1">- resource</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">
+ method<br/>
...<br align="left"/>
</td> </tr>
</table>>
]
Subsystem_2 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>Subsystem 2</b> </td> </tr>
<tr> <td>
<table align="left" border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left">+ property</td> </tr>
<tr> <td align="left" port="r1">- resource</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">
+ method<br/>
...<br align="left"/>
</td> </tr>
</table>>
]
Subsystem_3 [
shape=plain
label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
<tr> <td> <b>Subsystem 3</b> </td> </tr>
<tr> <td>
<table border="0" cellborder="0" cellspacing="0" >
<tr> <td align="left">+ property</td> </tr>
<tr> <td align="left" port="r1">- resource</td> </tr>
<tr> <td align="left">...</td> </tr>
</table>
</td> </tr>
<tr> <td align="left">
+ method<br/>
...<br align="left"/>
</td> </tr>
</table>>
]
System_1:ss2 -> Subsystem_2;
System_1:ss3 -> Subsystem_3;
edge [xdir=back arrowtail=odiamond]
Subsystem_1:r1 -> "Shared resource" [label=aggregation]
Subsystem_2:r1 -> "Shared resource"
Subsystem_3:r1 -> "Shared resource"
"Shared resource" [
label = <{
<b>Shared resource</b>
|
+ property<br align="left"/>
...<br align="left"/>
|
+ method<br align="left"/>
...<br align="left"/>
}>
]
}
[/graph]
``
#+LAYOUT_END
# Graphiz cache
Rendered Graphviz graphs that have been rendered to **svg** are stored in the cache database, under table ``cached_dot``.
Unless you modify the graph or it's properties, it won't be rendered again, instead it will be sourced from the database.

View file

@ -41,7 +41,6 @@ $|\begin{tikzpicture}
`` ``
Gives the following: Gives the following:
#+LAYOUT_BEGIN Centered
$|\begin{tikzpicture} $|\begin{tikzpicture}
\begin{axis} \begin{axis}
\addplot3[patch,patch refines=3, \addplot3[patch,patch refines=3,
@ -62,7 +61,6 @@ $|\begin{tikzpicture}
}; };
\end{axis} \end{axis}
\end{tikzpicture}|$ \end{tikzpicture}|$
#+LAYOUT_END
# LaTeX environment # LaTeX environment

View file

@ -6,7 +6,7 @@
Enclose text between two ``**`` to render it **bold**! Enclose text between two ``**`` to render it **bold**!
* ``**Bold text**`` → **Bold text** * ``**Bold text**`` → **Bold text**
* ``Bold [**link**](#)`` → Bold [**link**](#) * ``**Bold [link](#)**`` → **Bold [link](#)**
## Italic ## Italic

View file

@ -1,62 +0,0 @@
@import ../template.nml
%<make_doc({"Styles"}, "Layouts", "Basic Layouts")>%
# Layouts
You can create layout blocks by using the following tokens:
* ``#+LAYOUT_BEGIN <layout_name>`` Starts layout `<layout_name>`
* ``#+LAYOUT_NEXT`` Advances layout to the next block
* ``#+LAYOUT_END`` Ends last created layout
Here's an example of what you can do using layouts (with flashy colors for show):
#+LAYOUT_BEGIN[style=background-color:#F00;flex:0.5] Split
First
#+LAYOUT_BEGIN[style=background-color:#FF0] Centered
Second
#+LAYOUT_END
#+LAYOUT_NEXT[style=background-color:#00F]
Third
#+LAYOUT_BEGIN[style=background-color:#0FF] Split
Fourth
#+LAYOUT_NEXT[style=background-color:#0F0]
Fifth
#+LAYOUT_END
#+LAYOUT_END
Given by the following code:
```Plain Text
#+LAYOUT_BEGIN[style=background-color:#F00;flex:0.5] Split
First
#+LAYOUT_BEGIN[style=background-color:#FF0] Centered
Second
#+LAYOUT_END
#+LAYOUT_NEXT[style=background-color:#00F]
Third
#+LAYOUT_BEGIN[style=background-color:#0FF] Split
Fourth
#+LAYOUT_NEXT[style=background-color:#0F0]
Fifth
#+LAYOUT_END
#+LAYOUT_END
```
*(indentation is for readability)*
# Available layouts
## Centered
Centered layout align text to the center of the current block.
#### Style
The ``Centered`` layout uses the `.centered` css class to center the text.
#### Properties
* ``style`` Added css style to the div (defaults to none)
## Split
#### Style
The ``Split`` layout uses the `.split-container` and `.split` css class to create the desired layout.
If you wish to modify the relative width of the splits: add `style=flex: 0.5` in the properties, this makes the following split half the width of the other splits.
#### Properties
* ``style`` Added css style to the div (defaults to none)

View file

@ -231,61 +231,3 @@ impl<'a> DocumentAccessors<'a> for dyn Document<'a> + '_ {
.ok() .ok()
} }
} }
#[cfg(test)]
pub mod tests {
#[macro_export]
macro_rules! validate_document {
($container:expr, $idx:expr,) => {};
($container:expr, $idx:expr, $t:ty; $($tail:tt)*) => {{
let elem = &$container[$idx];
assert!(elem.downcast_ref::<$t>().is_some(), "Invalid element at index {}, expected {}, got: {elem:#?}", $idx, stringify!($t));
validate_document!($container, ($idx+1), $($tail)*);
}};
($container:expr, $idx:expr, $t:ty { $($field:ident == $value:expr),* }; $($tail:tt)*) => {{
let elem = &$container[$idx];
assert!(elem.downcast_ref::<$t>().is_some(), "Invalid element at index {}, expected {}, got: {elem:#?}", $idx, stringify!($t));
$(
let val = &elem.downcast_ref::<$t>().unwrap().$field;
assert!(*val == $value, "Invalid field {} for {} at index {}, expected {:#?}, found {:#?}",
stringify!($field),
stringify!($t),
$idx,
$value,
val);
)*
validate_document!($container, ($idx+1), $($tail)*);
}};
($container:expr, $idx:expr, $t:ty { $($ts:tt)* }; $($tail:tt)*) => {{
let elem = &$container[$idx];
assert!(elem.downcast_ref::<$t>().is_some(), "Invalid container element at index {}, expected {}", $idx, stringify!($t));
let contained = elem.as_container().unwrap().contained();
validate_document!(contained, 0, $($ts)*);
validate_document!($container, ($idx+1), $($tail)*);
}};
($container:expr, $idx:expr, $t:ty { $($field:ident == $value:expr),* } { $($ts:tt)* }; $($tail:tt)*) => {{
let elem = &$container[$idx];
assert!(elem.downcast_ref::<$t>().is_some(), "Invalid element at index {}, expected {}, got: {elem:#?}", $idx, stringify!($t));
$(
let val = &elem.downcast_ref::<$t>().unwrap().$field;
assert!(*val == $value, "Invalid field {} for {} at index {}, expected {:#?}, found {:#?}",
stringify!($field),
stringify!($t),
$idx,
$value,
val);
)*
let contained = elem.as_container().unwrap().contained();
validate_document!(contained, 0, $($ts)*);
validate_document!($container, ($idx+1), $($tail)*);
}};
}
}

View file

@ -665,7 +665,7 @@ mod tests {
static int INT32_MIN = 0x80000000; static int INT32_MIN = 0x80000000;
``` ```
%<nml.code.push_block("Lua", "From Lua", "print(\"Hello, World!\")", nil)>% %<nml.code.push_block("Lua", "From Lua", "print(\"Hello, World!\")", nil)>%
``Rust, ``Rust
fn fact(n: usize) -> usize fn fact(n: usize) -> usize
{ {
match n match n
@ -681,6 +681,7 @@ fn fact(n: usize) -> usize
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
//let compiler = Compiler::new(Target::HTML, None);
let doc = parser.parse(source, None); let doc = parser.parse(source, None);
let borrow = doc.content().borrow(); let borrow = doc.content().borrow();

View file

@ -1,44 +1,33 @@
use crate::compiler::compiler::Compiler; use mlua::{Function, Lua};
use crate::document::document::Document; use regex::{Captures, Regex};
use crate::document::element::ElemKind; use crate::{document::document::Document, parser::{parser::Parser, rule::RegexRule, source::{Source, Token}}};
use crate::document::element::Element; use ariadne::{Report, Label, ReportKind};
use crate::parser::parser::Parser; use crate::{compiler::compiler::Compiler, document::element::{ElemKind, Element}};
use crate::parser::rule::RegexRule; use std::{ops::Range, rc::Rc};
use crate::parser::source::Source;
use crate::parser::source::Token;
use ariadne::Label;
use ariadne::Report;
use ariadne::ReportKind;
use mlua::Function;
use mlua::Lua;
use regex::Captures;
use regex::Regex;
use std::ops::Range;
use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
pub struct Comment { pub struct Comment {
location: Token, location: Token,
content: String, content: String,
} }
impl Comment { impl Comment
pub fn new(location: Token, content: String) -> Self { {
Self { pub fn new(location: Token, content: String ) -> Self {
location: location, Self { location: location, content }
content, }
}
}
} }
impl Element for Comment { impl Element for Comment
fn location(&self) -> &Token { &self.location } {
fn kind(&self) -> ElemKind { ElemKind::Invisible } fn location(&self) -> &Token { &self.location }
fn element_name(&self) -> &'static str { "Comment" } fn kind(&self) -> ElemKind { ElemKind::Invisible }
fn to_string(&self) -> String { format!("{self:#?}") } fn element_name(&self) -> &'static str { "Comment" }
fn compile(&self, _compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn to_string(&self) -> String { format!("{self:#?}") }
fn compile(&self, _compiler: &Compiler, _document: &dyn Document)
-> Result<String, String> {
Ok("".to_string()) Ok("".to_string())
} }
} }
pub struct CommentRule { pub struct CommentRule {
@ -47,9 +36,7 @@ pub struct CommentRule {
impl CommentRule { impl CommentRule {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { re: [Regex::new(r"\s*::(.*)").unwrap()] }
re: [Regex::new(r"(?:(?:^|\n)|[^\S\n]+)::(.*)").unwrap()],
}
} }
} }
@ -58,77 +45,40 @@ impl RegexRule for CommentRule {
fn regexes(&self) -> &[Regex] { &self.re } fn regexes(&self) -> &[Regex] { &self.re }
fn on_regex_match<'a>( fn on_regex_match<'a>(&self, _: usize, parser: &dyn Parser, document: &'a dyn Document, token: Token, matches: Captures)
&self, -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
_: usize,
parser: &dyn Parser,
document: &'a dyn Document,
token: Token,
matches: Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
let mut reports = vec![]; let mut reports = vec![];
let content = match matches.get(1) { let content = match matches.get(1)
{
None => panic!("Unknown error"), None => panic!("Unknown error"),
Some(comment) => { Some(comment) => {
let trimmed = comment.as_str().trim_start().trim_end().to_string(); let trimmed = comment.as_str().trim_start().trim_end().to_string();
if trimmed.is_empty() { if trimmed.is_empty()
{
reports.push( reports.push(
Report::build(ReportKind::Warning, token.source(), comment.start()) Report::build(ReportKind::Warning, token.source(), comment.start())
.with_message("Empty comment") .with_message("Empty comment")
.with_label( .with_label(
Label::new((token.source(), comment.range())) Label::new((token.source(), comment.range()))
.with_message("Comment is empty") .with_message("Comment is empty")
.with_color(parser.colors().warning), .with_color(parser.colors().warning))
) .finish());
.finish(),
);
} }
trimmed trimmed
} }
}; };
parser.push(document, Box::new(Comment::new(token.clone(), content))); parser.push(document, Box::new(
Comment::new(
token.clone(),
content
)
));
return reports; return reports;
} }
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
} }
#[cfg(test)]
mod tests {
use crate::elements::paragraph::Paragraph;
use crate::elements::style::Style;
use crate::elements::text::Text;
use crate::parser::langparser::LangParser;
use crate::parser::source::SourceFile;
use crate::validate_document;
use super::*;
#[test]
fn parser() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
NOT COMMENT: `std::cmp`
:: Commented line
COMMENT ::Test
"#
.to_string(),
None,
));
let parser = LangParser::default();
let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0,
Paragraph {
Text; Style; Text; Style;
Comment { content == "Commented line" };
Text; Comment { content == "Test" };
};
);
}
}

View file

@ -30,9 +30,9 @@ use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LayoutToken { pub(crate) enum LayoutToken {
Begin, BEGIN,
Next, NEXT,
End, END,
} }
/// Represents the type of a layout /// Represents the type of a layout
@ -133,9 +133,9 @@ mod default_layouts {
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)), str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
}; };
match token { match token {
LayoutToken::Begin => Ok(format!(r#"<div class="centered"{style}>"#)), LayoutToken::BEGIN => Ok(format!(r#"<div class="centered"{style}>"#)),
LayoutToken::Next => panic!(), LayoutToken::NEXT => panic!(),
LayoutToken::End => Ok(r#"</div>"#.to_string()), LayoutToken::END => Ok(r#"</div>"#.to_string()),
} }
} }
_ => todo!(""), _ => todo!(""),
@ -211,11 +211,11 @@ mod default_layouts {
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)), str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
}; };
match token { match token {
LayoutToken::Begin => Ok(format!( LayoutToken::BEGIN => Ok(format!(
r#"<div class="split-container"><div class="split"{style}>"# r#"<div class="split-container"><div class="split"{style}>"#
)), )),
LayoutToken::Next => Ok(format!(r#"</div><div class="split"{style}>"#)), LayoutToken::NEXT => Ok(format!(r#"</div><div class="split"{style}>"#)),
LayoutToken::End => Ok(r#"</div></div>"#.to_string()), LayoutToken::END => Ok(r#"</div></div>"#.to_string()),
} }
} }
_ => todo!(""), _ => todo!(""),
@ -506,7 +506,7 @@ impl RegexRule for LayoutRule {
location: token.clone(), location: token.clone(),
layout: layout_type.clone(), layout: layout_type.clone(),
id: 0, id: 0,
token: LayoutToken::Begin, token: LayoutToken::BEGIN,
properties, properties,
}), }),
); );
@ -583,7 +583,7 @@ impl RegexRule for LayoutRule {
tokens.push(token.clone()); tokens.push(token.clone());
( (
tokens.len() - 1, tokens.len() - 1,
LayoutToken::Next, LayoutToken::NEXT,
layout_type.clone(), layout_type.clone(),
properties, properties,
) )
@ -646,7 +646,7 @@ impl RegexRule for LayoutRule {
let layout_type = layout_type.clone(); let layout_type = layout_type.clone();
let id = tokens.len(); let id = tokens.len();
state.stack.pop(); state.stack.pop();
(id, LayoutToken::End, layout_type, properties) (id, LayoutToken::END, layout_type, properties)
}; };
parser.push( parser.push(
@ -666,66 +666,3 @@ impl RegexRule for LayoutRule {
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
} }
#[cfg(test)]
mod tests {
use crate::elements::paragraph::Paragraph;
use crate::elements::text::Text;
use crate::parser::langparser::LangParser;
use crate::parser::source::SourceFile;
use crate::validate_document;
use super::*;
#[test]
fn parser() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
#+LAYOUT_BEGIN[style=A] Split
A
#+LAYOUT_BEGIN[style=B] Centered
B
#+LAYOUT_END
#+LAYOUT_NEXT[style=C]
C
#+LAYOUT_BEGIN[style=D] Split
D
#+LAYOUT_NEXT[style=E]
E
#+LAYOUT_END
#+LAYOUT_END
"#
.to_string(),
None,
));
let parser = LangParser::default();
let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0,
Layout { token == LayoutToken::Begin, id == 0 };
Paragraph {
Text { content == "A" };
};
Layout { token == LayoutToken::Begin, id == 0 };
Paragraph {
Text { content == "B" };
};
Layout { token == LayoutToken::End, id == 1 };
Layout { token == LayoutToken::Next, id == 1 };
Paragraph {
Text { content == "C" };
};
Layout { token == LayoutToken::Begin, id == 0 };
Paragraph {
Text { content == "D" };
};
Layout { token == LayoutToken::Next, id == 1 };
Paragraph {
Text { content == "E" };
};
Layout { token == LayoutToken::End, id == 2 };
Layout { token == LayoutToken::End, id == 2 };
);
}
}

View file

@ -1,14 +1,12 @@
use crate::compiler::compiler::Compiler; use crate::compiler::compiler::Compiler;
use crate::compiler::compiler::Target; use crate::compiler::compiler::Target;
use crate::document::document::Document; use crate::document::document::Document;
use crate::document::element::ContainerElement;
use crate::document::element::ElemKind; use crate::document::element::ElemKind;
use crate::document::element::Element; use crate::document::element::Element;
use crate::parser::parser::Parser; use crate::parser::parser::Parser;
use crate::parser::rule::RegexRule; use crate::parser::rule::RegexRule;
use crate::parser::source::Source; use crate::parser::source::Source;
use crate::parser::source::Token; use crate::parser::source::Token;
use crate::parser::source::VirtualSource;
use crate::parser::util; use crate::parser::util;
use ariadne::Fmt; use ariadne::Fmt;
use ariadne::Label; use ariadne::Label;
@ -21,15 +19,21 @@ use regex::Regex;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use super::paragraph::Paragraph;
#[derive(Debug)] #[derive(Debug)]
pub struct Link { pub struct Link {
pub(self) location: Token, location: Token,
/// Display content of link name: String, // Link name
pub(self) display: Paragraph, url: String, // Link url
/// Url of link }
pub(self) url: String,
impl Link {
pub fn new(location: Token, name: String, url: String) -> Self {
Self {
location: location,
name,
url,
}
}
} }
impl Element for Link { impl Element for Link {
@ -37,39 +41,20 @@ impl Element for Link {
fn kind(&self) -> ElemKind { ElemKind::Inline } fn kind(&self) -> ElemKind { ElemKind::Inline }
fn element_name(&self) -> &'static str { "Link" } fn element_name(&self) -> &'static str { "Link" }
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> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => Ok(format!(
let mut result = format!( "<a href=\"{}\">{}</a>",
"<a href=\"{}\">", Compiler::sanitize(compiler.target(), self.url.as_str()),
Compiler::sanitize(compiler.target(), self.url.as_str()) Compiler::sanitize(compiler.target(), self.name.as_str()),
); )),
Target::LATEX => Ok(format!(
result += self "\\href{{{}}}{{{}}}",
.display Compiler::sanitize(compiler.target(), self.url.as_str()),
.compile(compiler, document) Compiler::sanitize(compiler.target(), self.name.as_str()),
.as_ref() )),
.map(|r| r.as_str())?;
result += "</a>";
Ok(result)
}
_ => todo!(""),
} }
} }
fn as_container(&self) -> Option<&dyn ContainerElement> { Some(self) }
}
impl ContainerElement for Link {
fn contained(&self) -> &Vec<Box<dyn Element>> { &self.display.content }
fn push(&mut self, elem: Box<dyn Element>) -> Result<(), String> {
if elem.downcast_ref::<Link>().is_some() {
return Err("Tried to push a link inside of a link".to_string());
}
self.display.push(elem)
}
} }
pub struct LinkRule { pub struct LinkRule {
@ -93,67 +78,47 @@ impl RegexRule for LinkRule {
&self, &self,
_: usize, _: usize,
parser: &dyn Parser, parser: &dyn Parser,
document: &'a (dyn Document<'a> + 'a), document: &'a dyn Document,
token: Token, token: Token,
matches: Captures, matches: Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> { ) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
let mut reports = vec![]; let mut result = vec![];
let link_name = match matches.get(1) {
let link_display = match matches.get(1) { Some(name) => {
Some(display) => { if name.as_str().is_empty() {
if display.as_str().is_empty() { result.push(
reports.push( Report::build(ReportKind::Error, token.source(), name.start())
Report::build(ReportKind::Error, token.source(), display.start())
.with_message("Empty link name") .with_message("Empty link name")
.with_label( .with_label(
Label::new((token.source().clone(), display.range())) Label::new((token.source().clone(), name.range()))
.with_message("Link name is empty") .with_message("Link name is empty")
.with_color(parser.colors().error), .with_color(parser.colors().error),
) )
.finish(), .finish(),
); );
return reports; return result;
} }
let processed = util::process_escaped('\\', "]", display.as_str()); // TODO: process into separate document...
if processed.is_empty() { let text_content = util::process_text(document, name.as_str());
reports.push(
Report::build(ReportKind::Error, token.source(), display.start()) if text_content.as_str().is_empty() {
result.push(
Report::build(ReportKind::Error, token.source(), name.start())
.with_message("Empty link name") .with_message("Empty link name")
.with_label( .with_label(
Label::new((token.source(), display.range())) Label::new((token.source(), name.range()))
.with_message(format!( .with_message(format!(
"Link name is empty. Once processed, `{}` yields `{}`", "Link name is empty. Once processed, `{}` yields `{}`",
display.as_str().fg(parser.colors().highlight), name.as_str().fg(parser.colors().highlight),
processed.fg(parser.colors().highlight), text_content.as_str().fg(parser.colors().highlight),
)) ))
.with_color(parser.colors().error), .with_color(parser.colors().error),
) )
.finish(), .finish(),
); );
return reports; return result;
}
let source = Rc::new(VirtualSource::new(
Token::new(display.range(), token.source()),
"Link Display".to_string(),
processed,
));
match util::parse_paragraph(parser, source, document) {
Err(err) => {
reports.push(
Report::build(ReportKind::Error, token.source(), display.start())
.with_message("Failed to parse link display")
.with_label(
Label::new((token.source(), display.range()))
.with_message(err.to_string())
.with_color(parser.colors().error),
)
.finish(),
);
return reports;
}
Ok(paragraph) => *paragraph,
} }
text_content
} }
_ => panic!("Empty link name"), _ => panic!("Empty link name"),
}; };
@ -161,7 +126,7 @@ impl RegexRule for LinkRule {
let link_url = match matches.get(2) { let link_url = match matches.get(2) {
Some(url) => { Some(url) => {
if url.as_str().is_empty() { if url.as_str().is_empty() {
reports.push( result.push(
Report::build(ReportKind::Error, token.source(), url.start()) Report::build(ReportKind::Error, token.source(), url.start())
.with_message("Empty link url") .with_message("Empty link url")
.with_label( .with_label(
@ -171,12 +136,12 @@ impl RegexRule for LinkRule {
) )
.finish(), .finish(),
); );
return reports; return result;
} }
let text_content = util::process_text(document, url.as_str()); let text_content = util::process_text(document, url.as_str());
if text_content.as_str().is_empty() { if text_content.as_str().is_empty() {
reports.push( result.push(
Report::build(ReportKind::Error, token.source(), url.start()) Report::build(ReportKind::Error, token.source(), url.start())
.with_message("Empty link url") .with_message("Empty link url")
.with_label( .with_label(
@ -190,7 +155,7 @@ impl RegexRule for LinkRule {
) )
.finish(), .finish(),
); );
return reports; return result;
} }
text_content text_content
} }
@ -199,55 +164,12 @@ impl RegexRule for LinkRule {
parser.push( parser.push(
document, document,
Box::new(Link { Box::new(Link::new(token.clone(), link_name, link_url)),
location: token,
display: link_display,
url: link_url,
}),
); );
return reports; return result;
} }
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
} }
#[cfg(test)]
mod tests {
use crate::elements::style::Style;
use crate::elements::text::Text;
use crate::parser::langparser::LangParser;
use crate::parser::source::SourceFile;
use crate::validate_document;
use super::*;
#[test]
fn parser() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
Some [link](url).
[**BOLD link**](another url)
"#
.to_string(),
None,
));
let parser = LangParser::default();
let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0,
Paragraph {
Text { content == "Some " };
Link { url == "url" } { Text { content == "link" }; };
Text { content == "." };
Link { url == "another url" } {
Style;
Text { content == "BOLD link" };
Style;
};
};
);
}
}

View file

@ -100,9 +100,6 @@ impl ContainerElement for Paragraph {
if elem.location().source() == self.location().source() { if elem.location().source() == self.location().source() {
self.location.range = self.location.start()..elem.location().end(); self.location.range = self.location.start()..elem.location().end();
} }
if elem.kind() == ElemKind::Block {
return Err("Attempted to push block element inside a paragraph".to_string());
}
self.content.push(elem); self.content.push(elem);
Ok(()) Ok(())
} }
@ -155,47 +152,3 @@ impl Rule for ParagraphRule {
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
} }
#[cfg(test)]
mod tests {
use crate::elements::paragraph::Paragraph;
use crate::elements::text::Text;
use crate::parser::langparser::LangParser;
use crate::parser::source::SourceFile;
use crate::validate_document;
use super::*;
#[test]
fn parse() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
First paragraph
Second line
Second paragraph\
<- literal \\n
Last paragraph
"#
.to_string(),
None,
));
let parser = LangParser::default();
let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0,
Paragraph {
Text { content == "First paragraph Second line" };
};
Paragraph {
Text { content == "Second paragraph\n<- literal \\n" };
};
Paragraph {
Text { content == "Last paragraph " };
};
);
}
}

View file

@ -222,54 +222,3 @@ impl RegexRule for StyleRule {
// TODO // TODO
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None } fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
} }
#[cfg(test)]
mod tests {
use crate::elements::text::Text;
use crate::parser::langparser::LangParser;
use crate::parser::source::SourceFile;
use crate::validate_document;
use super::*;
#[test]
fn parser() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
Some *style
terminated here*
**BOLD + *italic***
__`UNDERLINE+EM`__
"#
.to_string(),
None,
));
let parser = LangParser::default();
let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0,
Paragraph {
Text;
Style { kind == 1, close == false };
Text;
Style { kind == 1, close == true };
};
Paragraph {
Style { kind == 0, close == false }; // **
Text;
Style { kind == 1, close == false }; // *
Text;
Style { kind == 0, close == true }; // **
Style { kind == 1, close == true }; // *
Style { kind == 2, close == false }; // __
Style { kind == 3, close == false }; // `
Text;
Style { kind == 3, close == true }; // `
Style { kind == 2, close == true }; // __
};
);
}
}

View file

@ -253,7 +253,7 @@ impl TexRule {
.unwrap(), .unwrap(),
Regex::new(r"\$(?:\[((?:\\.|[^\\\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\$)?").unwrap(), Regex::new(r"\$(?:\[((?:\\.|[^\\\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\$)?").unwrap(),
], ],
properties: PropertyParser { properties: props }, properties: PropertyParser{ properties: props },
} }
} }
@ -435,10 +435,8 @@ impl RegexRule for TexRule {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::elements::paragraph::Paragraph;
use crate::parser::langparser::LangParser; use crate::parser::langparser::LangParser;
use crate::parser::source::SourceFile; use crate::parser::source::SourceFile;
use crate::validate_document;
use super::*; use super::*;
@ -448,7 +446,7 @@ mod tests {
"".to_string(), "".to_string(),
r#" r#"
$[kind=block, caption=Some\, text\\] 1+1=2 $ $[kind=block, caption=Some\, text\\] 1+1=2 $
$|[env=another] Non Math \LaTeX |$ $|[env=another] Non Math \LaTeX|$
$[kind=block,env=another] e^{i\pi}=-1$ $[kind=block,env=another] e^{i\pi}=-1$
"# "#
.to_string(), .to_string(),
@ -457,11 +455,19 @@ $[kind=block,env=another] e^{i\pi}=-1$
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(source, None); let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0, let borrow = doc.content().borrow();
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) }; let found = borrow
Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another" }; .iter()
Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" }; .filter_map(|e| e.downcast_ref::<Tex>())
); .collect::<Vec<_>>();
assert_eq!(found[0].tex, "1+1=2");
assert_eq!(found[0].env, "main");
assert_eq!(found[0].caption, Some("Some, text\\".to_string()));
assert_eq!(found[1].tex, "Non Math \\LaTeX");
assert_eq!(found[1].env, "another");
assert_eq!(found[2].tex, "e^{i\\pi}=-1");
assert_eq!(found[2].env, "another");
} }
#[test] #[test]
@ -479,12 +485,24 @@ $[env=another] e^{i\pi}=-1$
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(source, None); let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0, let borrow = doc.content().borrow();
Paragraph { let found = borrow
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) }; .first()
Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another" }; .unwrap()
Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" }; .as_container()
}; .unwrap()
); .contained()
.iter()
.filter_map(|e| e.downcast_ref::<Tex>())
.collect::<Vec<_>>();
assert_eq!(found[0].tex, "1+1=2");
assert_eq!(found[0].env, "main");
assert_eq!(found[0].caption, Some("Some, text\\".to_string()));
assert_eq!(found[1].tex, "Non Math \\LaTeX");
assert_eq!(found[1].env, "another");
assert_eq!(found[1].caption, Some("Enclosed ].".to_string()));
assert_eq!(found[2].tex, "e^{i\\pi}=-1");
assert_eq!(found[2].env, "another");
} }
} }

View file

@ -19,8 +19,8 @@ use crate::parser::source::Token;
#[derive(Debug)] #[derive(Debug)]
pub struct Text { pub struct Text {
pub location: Token, pub(self) location: Token,
pub content: String, pub(self) content: String,
} }
impl Text { impl Text {

View file

@ -147,8 +147,6 @@ pub fn parse_paragraph<'a>(
return Err("Parsed document is empty"); return Err("Parsed document is empty");
} else if parsed.last_element::<Paragraph>().is_none() { } else if parsed.last_element::<Paragraph>().is_none() {
return Err("Parsed element is not a paragraph"); return Err("Parsed element is not a paragraph");
} else if parser.has_error() {
return Err("Parser error");
} }
let paragraph = parsed.content().borrow_mut().pop().unwrap(); let paragraph = parsed.content().borrow_mut().pop().unwrap();

View file

@ -1,7 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""latex2svg """latex2svg
-- This version of latex2svg comes with NML and has been modified to work with it only --
-- The original version can be found here : https://github.com/Moonbase59/latex2svg --
Read LaTeX code from stdin and render a SVG using LaTeX, dvisvgm and svgo. Read LaTeX code from stdin and render a SVG using LaTeX, dvisvgm and svgo.
@ -25,6 +23,38 @@ import re
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from ctypes.util import find_library from ctypes.util import find_library
default_template = r"""
\documentclass[{{ fontsize }}pt,preview]{standalone}
{{ preamble }}
\begin{document}
\begin{preview}
{{ code }}
\end{preview}
\end{document}
"""
default_preamble = r"""
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amstext}
\usepackage{newtxtext}
\usepackage[libertine]{newtxmath}
% prevent errors from old font commands
\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm}
\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf}
\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt}
\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf}
\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit}
\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl}
\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc}
% prevent errors from undefined shortcuts
\newcommand{\N}{\mathbb{N}}
\newcommand{\R}{\mathbb{R}}
\newcommand{\Z}{\mathbb{Z}}
"""
default_svgo_config = r""" default_svgo_config = r"""
module.exports = { module.exports = {
plugins: [ plugins: [
@ -54,6 +84,8 @@ svgo_cmd = 'svgo'
default_params = { default_params = {
'fontsize': 12, # TeX pt 'fontsize': 12, # TeX pt
'template': default_template,
'preamble': default_preamble,
'latex_cmd': latex_cmd, 'latex_cmd': latex_cmd,
'dvisvgm_cmd': dvisvgm_cmd, 'dvisvgm_cmd': dvisvgm_cmd,
'svgo_cmd': svgo_cmd, 'svgo_cmd': svgo_cmd,
@ -205,15 +237,22 @@ def main():
""") """)
parser.add_argument('--version', action='version', parser.add_argument('--version', action='version',
version='%(prog)s {version}'.format(version=__version__)) version='%(prog)s {version}'.format(version=__version__))
parser.add_argument('--preamble',
help="LaTeX preamble code to read from file")
parser.add_argument('--fontsize', parser.add_argument('--fontsize',
help="LaTeX fontsize in pt") help="LaTeX fontsize in pt")
args = parser.parse_args() args = parser.parse_args()
preamble = default_preamble
if args.preamble is not None:
with open(args.preamble) as f:
preamble = f.read()
fontsize = 12 fontsize = 12
if args.fontsize is not None: if args.fontsize is not None:
fontsize = int(args.fontsize) fontsize = int(args.fontsize)
latex = sys.stdin.read() latex = sys.stdin.read()
try: try:
params = default_params.copy() params = default_params.copy()
params['preamble'] = preamble
params['fontsize'] = fontsize params['fontsize'] = fontsize
out = latex2svg(latex, params) out = latex2svg(latex, params)
sys.stdout.write(out['svg']) sys.stdout.write(out['svg'])