Add word-level parsing
This commit is contained in:
parent
0d66b1ee7c
commit
198bc12507
34 changed files with 743 additions and 446 deletions
|
|
@ -10,13 +10,14 @@ You can learn more and see what en looks like by visiting the [homepage](https:/
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
|
- [ ] Strip/render some syntax in Tree text preview
|
||||||
- [ ] Richer text formatting
|
- [ ] Richer text formatting
|
||||||
- [x] Headers
|
- [x] Headers
|
||||||
- [ ] Code blocks
|
- [x] Preformatted blocks
|
||||||
- [ ] Inline code
|
- [ ] Inline code
|
||||||
- [ ] Anchor rendering
|
- [x] Anchor rendering
|
||||||
- [ ] Automatic anchors
|
- [ ] Automatic anchors
|
||||||
- [ ] External anchors
|
- [x] External anchors
|
||||||
- [ ] Bold, italics, underline, strikethrough
|
- [ ] Bold, italics, underline, strikethrough
|
||||||
- [ ] Lists
|
- [ ] Lists
|
||||||
- [ ] Checkboxes
|
- [ ] Checkboxes
|
||||||
|
|
@ -34,8 +35,8 @@ You can learn more and see what en looks like by visiting the [homepage](https:/
|
||||||
- [ ] Reduce O(n) calls in the formats module
|
- [ ] Reduce O(n) calls in the formats module
|
||||||
- [ ] Add tests
|
- [ ] Add tests
|
||||||
- [ ] Multi-file graphs
|
- [ ] Multi-file graphs
|
||||||
- [ ] Themes
|
|
||||||
- [ ] Multi-graph
|
- [ ] Multi-graph
|
||||||
|
- [ ] Themes
|
||||||
- [x] Array syntax for lightweight connections
|
- [x] Array syntax for lightweight connections
|
||||||
- [x] Automatic IDs
|
- [x] Automatic IDs
|
||||||
- [x] Automatic titles
|
- [x] Automatic titles
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::handlers;
|
||||||
|
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Will panic if file read fails.
|
/// Will panic if file read fails.
|
||||||
|
#[expect(clippy::unused_async)]
|
||||||
pub async fn file(file_path: &str, content_type: &str) -> Response<Body> {
|
pub async fn file(file_path: &str, content_type: &str) -> Response<Body> {
|
||||||
let content = match std::fs::read(file_path) {
|
let content = match std::fs::read(file_path) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use axum::{body::Body, extract::Path, http::Response};
|
use axum::{body::Body, extract::Path, http::Response};
|
||||||
use crate::syntax::content::parsers::line::elements::paragraph::Paragraph;
|
|
||||||
use crate::syntax::content;
|
use crate::syntax::content;
|
||||||
|
|
||||||
use crate::syntax::content::parsers::compound::elements::literal::Literal;
|
|
||||||
use crate::{formats::populate_graph, handlers, types::Node};
|
use crate::{formats::populate_graph, handlers, types::Node};
|
||||||
|
|
||||||
pub async fn node(Path(id): Path<String>) -> Response<Body> {
|
pub async fn node(Path(id): Path<String>) -> Response<Body> {
|
||||||
|
|
@ -17,7 +15,7 @@ pub async fn node(Path(id): Path<String>) -> Response<Body> {
|
||||||
context.insert("incoming", &graph.incoming.get(&id));
|
context.insert("incoming", &graph.incoming.get(&id));
|
||||||
context.insert("config", &graph.meta.config.parse_text());
|
context.insert("config", &graph.meta.config.parse_text());
|
||||||
|
|
||||||
let out_text = content::parse::<Paragraph, Literal>(&node.text);
|
let out_text = content::parse(&node.text);
|
||||||
context.insert("text", &out_text);
|
context.insert("text", &out_text);
|
||||||
|
|
||||||
let not_found = *node == empty_node;
|
let not_found = *node == empty_node;
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,7 @@ use axum::{
|
||||||
Form,
|
Form,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{formats::populate_graph, handlers, types::Node};
|
||||||
formats::populate_graph,
|
|
||||||
handlers,
|
|
||||||
syntax::content::{
|
|
||||||
self,
|
|
||||||
parsers::{
|
|
||||||
line::elements::{paragraph::Paragraph, span::Span},
|
|
||||||
compound::elements::literal::Literal,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
types::{Config, Node},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[expect(clippy::unused_async)]
|
#[expect(clippy::unused_async)]
|
||||||
pub async fn page(template: &str) -> Response<Body> {
|
pub async fn page(template: &str) -> Response<Body> {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use axum::{
|
||||||
http::{header, Response, StatusCode},
|
http::{header, Response, StatusCode},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::handlers::raw::make_response;
|
use crate::{prelude::*, handlers::raw::make_response};
|
||||||
|
|
||||||
pub(in crate::handlers) fn by_filename(
|
pub(in crate::handlers) fn by_filename(
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|
@ -39,8 +39,8 @@ pub(in crate::handlers) fn render(
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let early_error_message = format!("{e:#?}");
|
let early_error_message = format!("{e:#?}");
|
||||||
crate::dev::log(&by_filename, &early_error_message);
|
log!("{}", early_error_message);
|
||||||
return (emergency_wrap(&e), 500)
|
return (emergency_wrap(&e), 500);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -79,7 +79,8 @@ pub(in crate::handlers) fn render(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emergency_wrap(message: &tera::Error) -> String {
|
fn emergency_wrap(message: &tera::Error) -> String {
|
||||||
format!(r#"<!DOCTYPE html>
|
format!(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Pre-Templating Error</title>
|
<title>Pre-Templating Error</title>
|
||||||
|
|
@ -105,5 +106,6 @@ fn emergency_wrap(message: &tera::Error) -> String {
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"#)
|
"#
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Arguments {
|
pub struct Arguments {
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
|
|
@ -47,10 +49,7 @@ fn parse(defaults: &Arguments, args: &[String]) -> Arguments {
|
||||||
} else if argument.eq("-g") || argument.eq("--graph") {
|
} else if argument.eq("-g") || argument.eq("--graph") {
|
||||||
out_args.graph_path = PathBuf::from(parameter);
|
out_args.graph_path = PathBuf::from(parameter);
|
||||||
} else {
|
} else {
|
||||||
crate::dev::log(
|
log!("Dropped unrecognized argument {argument}");
|
||||||
&parse,
|
|
||||||
&format!("Dropped unrecognized argument {argument}"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Argument {arg:?} has no corresponding value")
|
panic!("Argument {arg:?} has no corresponding value")
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
use token::{Token};
|
use parser::{token::Token, lexeme::Lexeme};
|
||||||
use parsers::{line::Line, word::Word};
|
|
||||||
use lexeme::Lexeme;
|
|
||||||
|
|
||||||
mod token;
|
pub mod parser;
|
||||||
pub mod lexeme;
|
|
||||||
pub mod parsers;
|
|
||||||
|
|
||||||
pub trait Parseable: Into<Token> {
|
pub trait Parseable: Into<Token> {
|
||||||
fn probe(lexeme: &Lexeme) -> bool;
|
fn probe(lexeme: &Lexeme) -> bool;
|
||||||
|
|
@ -16,22 +12,6 @@ type Probe = fn(&Lexeme) -> bool;
|
||||||
type Lexer = fn(&Lexeme) -> Token;
|
type Lexer = fn(&Lexeme) -> Token;
|
||||||
type LexMap<'lm> = &'lm [(Probe, Lexer)];
|
type LexMap<'lm> = &'lm [(Probe, Lexer)];
|
||||||
|
|
||||||
fn make_lexmap<DefaultToken: Parseable>(base: LexMap) -> Vec<(Probe, Lexer)> {
|
pub fn parse(text: &str) -> String {
|
||||||
let mut vector: Vec<(Probe, Lexer)> = base.to_vec();
|
parser::read(text)
|
||||||
|
|
||||||
fn adapter<D: Parseable>(lex: &Lexeme) -> Token {
|
|
||||||
D::lex(lex).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
vector.push((DefaultToken::probe, adapter::<DefaultToken>));
|
|
||||||
vector
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse<DefaultLineToken: Parseable, DefaultWordToken: Parseable>(
|
|
||||||
text: &str,
|
|
||||||
) -> String {
|
|
||||||
let escaped_text = tera::escape_html(text);
|
|
||||||
parsers::line::parser::read::<DefaultLineToken>(
|
|
||||||
&parsers::word::parser::read::<DefaultWordToken>(&escaped_text),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
use super::parsers::{line::Line, word::Word};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Lexeme {
|
|
||||||
Line(Line),
|
|
||||||
Word(Word),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Lexeme {
|
|
||||||
pub fn to_raw(&self) -> String {
|
|
||||||
match *self {
|
|
||||||
Lexeme::Line(ref d) => d.raw.clone(),
|
|
||||||
Lexeme::Word(ref d) => d.raw.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vec(self) -> Vec<String> {
|
|
||||||
self.to_raw().split(' ').map(str::to_string).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first(self) -> Option<String> {
|
|
||||||
self.to_vec().first().map(String::to_owned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
186
src/syntax/content/parser.rs
Normal file
186
src/syntax/content/parser.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
use std::slice::Iter;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use super::{Parseable as _, Token, LexMap};
|
||||||
|
use token::{
|
||||||
|
anchor::Anchor, linebreak::LineBreak, paragraph::Paragraph, header::Header,
|
||||||
|
preformat::PreFormat, literal::Literal,
|
||||||
|
};
|
||||||
|
use lexeme::{Lexeme, compound::Compound};
|
||||||
|
|
||||||
|
pub mod token;
|
||||||
|
pub mod lexeme;
|
||||||
|
|
||||||
|
const LEXMAP: LexMap = &[
|
||||||
|
(Anchor::probe, |word| Token::Anchor(Anchor::lex(word))),
|
||||||
|
(LineBreak::probe, |word| {
|
||||||
|
Token::LineBreak(LineBreak::lex(word))
|
||||||
|
}),
|
||||||
|
(Literal::probe, |word| Token::Literal(Literal::lex(word))),
|
||||||
|
];
|
||||||
|
|
||||||
|
enum Context {
|
||||||
|
None,
|
||||||
|
Paragraph,
|
||||||
|
Header(u8),
|
||||||
|
PreFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(text: &str, map: LexMap) -> Vec<Token> {
|
||||||
|
let mut tokens: Vec<Token> = Vec::new();
|
||||||
|
let mut state = Context::None;
|
||||||
|
|
||||||
|
let splits = split(text);
|
||||||
|
let mut iter = splits.iter();
|
||||||
|
while let Some(word) = iter.next() {
|
||||||
|
let compound = cluster(word, &mut iter);
|
||||||
|
let lexeme = Lexeme::Compound(compound);
|
||||||
|
|
||||||
|
match state {
|
||||||
|
Context::None => {
|
||||||
|
if Header::probe(&lexeme) {
|
||||||
|
let header = Header::lex(&lexeme);
|
||||||
|
state = Context::Header(header.get_level());
|
||||||
|
tokens.push(Token::Header(header));
|
||||||
|
continue;
|
||||||
|
} else if PreFormat::probe(&lexeme) {
|
||||||
|
tokens.push(Token::PreFormat(PreFormat::new(true)));
|
||||||
|
state = Context::PreFormat;
|
||||||
|
continue;
|
||||||
|
} else if Paragraph::probe(&lexeme) {
|
||||||
|
tokens.push(Token::Paragraph(Paragraph::new(true)));
|
||||||
|
state = Context::Paragraph;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Context::Paragraph => {
|
||||||
|
if word == "\n" {
|
||||||
|
tokens.push(Token::Paragraph(Paragraph::new(false)));
|
||||||
|
state = Context::None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Context::Header(n) => {
|
||||||
|
if word == "\n" {
|
||||||
|
tokens.push(Token::Header(Header::from_u8(n, false)));
|
||||||
|
state = Context::None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Context::PreFormat => {
|
||||||
|
if PreFormat::probe(&lexeme) {
|
||||||
|
tokens.push(Token::PreFormat(PreFormat::new(false)));
|
||||||
|
state = Context::None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for &(ref probe, lex) in map {
|
||||||
|
if probe(&lexeme) {
|
||||||
|
tokens.push(lex(&lexeme));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split(text: &str) -> Vec<String> {
|
||||||
|
text.replace("\n", " \n ")
|
||||||
|
.split(' ')
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// this could be eliminated if space were a token
|
||||||
|
fn join<'i, Iterator>(rendered_tokens: Iterator) -> String
|
||||||
|
where
|
||||||
|
Iterator: IntoIterator<Item = &'i str>,
|
||||||
|
{
|
||||||
|
fn stick(current: &str, next: &str) -> bool {
|
||||||
|
// this could be in a dedicated type
|
||||||
|
fn is_tag(s: &str) -> bool {
|
||||||
|
s.starts_with("<") && s.ends_with('>')
|
||||||
|
}
|
||||||
|
fn is_opening(s: &str) -> bool {
|
||||||
|
is_tag(s) && !s.contains("</")
|
||||||
|
}
|
||||||
|
fn is_closing(s: &str) -> bool {
|
||||||
|
is_tag(s) && s.contains("</")
|
||||||
|
}
|
||||||
|
fn is_inline(s: &str) -> bool {
|
||||||
|
is_tag(s) && s.starts_with("<a")
|
||||||
|
}
|
||||||
|
|
||||||
|
log!("On {current}[?]{next}");
|
||||||
|
if is_inline(next) {
|
||||||
|
log!("Pushing space because {next} is inline");
|
||||||
|
false
|
||||||
|
} else if is_closing(next) {
|
||||||
|
log!("Not pushing space because {next} is closing");
|
||||||
|
true
|
||||||
|
} else if is_opening(current) {
|
||||||
|
log!("Not pushing space because {current} is opening");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut iterator = rendered_tokens.into_iter();
|
||||||
|
let mut out_string = String::new();
|
||||||
|
|
||||||
|
if let Some(mut current) = iterator.next() {
|
||||||
|
out_string.push_str(current);
|
||||||
|
for next in iterator {
|
||||||
|
if stick(current, next) {
|
||||||
|
out_string.push_str(next);
|
||||||
|
} else {
|
||||||
|
out_string.push(' ');
|
||||||
|
out_string.push_str(next);
|
||||||
|
}
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out_string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(tokens: &[Token]) -> String {
|
||||||
|
let rendered: Vec<String> = tokens.iter().map(Token::render).collect();
|
||||||
|
|
||||||
|
join(rendered.iter().map(String::as_str))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cluster<'c>(word: &str, iter: &mut Iter<'c, String>) -> Compound {
|
||||||
|
if word.starts_with('|') {
|
||||||
|
log!("Found opener {word}");
|
||||||
|
let mut parts = vec![word];
|
||||||
|
|
||||||
|
if let Some(first) = parts.first()
|
||||||
|
&& first.ends_with('|')
|
||||||
|
{
|
||||||
|
log!("Returning atomic cluster");
|
||||||
|
Compound::new(&parts.join(" "))
|
||||||
|
} else {
|
||||||
|
log!("Seeking a boundary");
|
||||||
|
for next_raw in iter {
|
||||||
|
if next_raw.contains('|') {
|
||||||
|
log!("Found end of cluster {next_raw:?}");
|
||||||
|
parts.push(next_raw);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
parts.push(next_raw);
|
||||||
|
log!("Onto next word from {next_raw}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log!("Returning cluster {parts:?}");
|
||||||
|
|
||||||
|
Compound::new(&parts.join(" "))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Compound::new(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn read(text: &str) -> String {
|
||||||
|
parse(&lex(text, LEXMAP))
|
||||||
|
}
|
||||||
39
src/syntax/content/parser/lexeme.rs
Normal file
39
src/syntax/content/parser/lexeme.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Lexeme {
|
||||||
|
Compound(compound::Compound),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod compound;
|
||||||
|
|
||||||
|
impl Lexeme {
|
||||||
|
pub fn to_raw(&self) -> String {
|
||||||
|
match *self {
|
||||||
|
Lexeme::Compound(ref d) => d.raw.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if number of chars for a single lexeme exceeds `i2::MAX`
|
||||||
|
pub fn count_char(&self, c: char) -> i32 {
|
||||||
|
let count = self.to_raw().chars().filter(|&n| n == c).count();
|
||||||
|
match i32::try_from(count) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Wild char number {count} is a bit much: {e:#?}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_chars(&self) -> Vec<char> {
|
||||||
|
let vector: Vec<char> = self.to_raw().chars().collect();
|
||||||
|
vector
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_words(self) -> Vec<String> {
|
||||||
|
self.to_raw().split(' ').map(str::to_string).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn first(self) -> Option<String> {
|
||||||
|
self.split_words().first().map(String::to_owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/syntax/content/parser/lexeme/compound.rs
Normal file
12
src/syntax/content/parser/lexeme/compound.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Compound {
|
||||||
|
pub raw: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compound {
|
||||||
|
pub fn new(text: &str) -> Compound {
|
||||||
|
Compound {
|
||||||
|
raw: text.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/syntax/content/parser/token.rs
Normal file
75
src/syntax/content/parser/token.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
use crate::syntax::content::Parseable as _;
|
||||||
|
|
||||||
|
pub mod literal;
|
||||||
|
pub mod anchor;
|
||||||
|
pub mod linebreak;
|
||||||
|
pub mod paragraph;
|
||||||
|
pub mod span;
|
||||||
|
pub mod header;
|
||||||
|
pub mod preformat;
|
||||||
|
|
||||||
|
pub enum Token {
|
||||||
|
Anchor(anchor::Anchor),
|
||||||
|
Header(header::Header),
|
||||||
|
LineBreak(linebreak::LineBreak),
|
||||||
|
Literal(literal::Literal),
|
||||||
|
Paragraph(paragraph::Paragraph),
|
||||||
|
PreFormat(preformat::PreFormat),
|
||||||
|
Span(span::Span),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
pub fn render(&self) -> String {
|
||||||
|
match *self {
|
||||||
|
Token::Anchor(ref d) => d.render(),
|
||||||
|
Token::Header(ref d) => d.render(),
|
||||||
|
Token::LineBreak(ref d) => d.render(),
|
||||||
|
Token::Literal(ref d) => d.render(),
|
||||||
|
Token::Paragraph(ref d) => d.render(),
|
||||||
|
Token::PreFormat(ref d) => d.render(),
|
||||||
|
Token::Span(ref d) => d.render(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<paragraph::Paragraph> for Token {
|
||||||
|
fn from(d: paragraph::Paragraph) -> Token {
|
||||||
|
Token::Paragraph(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<header::Header> for Token {
|
||||||
|
fn from(d: header::Header) -> Token {
|
||||||
|
Token::Header(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<span::Span> for Token {
|
||||||
|
fn from(d: span::Span) -> Token {
|
||||||
|
Token::Span(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<literal::Literal> for Token {
|
||||||
|
fn from(d: literal::Literal) -> Token {
|
||||||
|
Token::Literal(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<anchor::Anchor> for Token {
|
||||||
|
fn from(d: anchor::Anchor) -> Token {
|
||||||
|
Token::Anchor(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<linebreak::LineBreak> for Token {
|
||||||
|
fn from(d: linebreak::LineBreak) -> Token {
|
||||||
|
Token::LineBreak(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<preformat::PreFormat> for Token {
|
||||||
|
fn from(d: preformat::PreFormat) -> Token {
|
||||||
|
Token::PreFormat(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/syntax/content/parser/token/anchor.rs
Normal file
68
src/syntax/content/parser/token/anchor.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
use crate::syntax::content::{Parseable, parser::lexeme::Lexeme};
|
||||||
|
|
||||||
|
pub struct Anchor {
|
||||||
|
text: String,
|
||||||
|
destination: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parseable for Anchor {
|
||||||
|
fn probe(lexeme: &Lexeme) -> bool {
|
||||||
|
let pipe_count = lexeme.count_char('|');
|
||||||
|
let chars = lexeme.split_chars();
|
||||||
|
let c1 = *match chars.first() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
let cn = *match chars.last() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !(1_i32..=3_i32).contains(&pipe_count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if lexeme.to_raw().matches("||").count() > 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if pipe_count == 1 {
|
||||||
|
c1 != '|' && cn != '|'
|
||||||
|
} else if pipe_count == 2 {
|
||||||
|
c1 == '|' && cn != '|'
|
||||||
|
} else if pipe_count == 3 {
|
||||||
|
c1 == '|' && cn == '|'
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(lexeme: &Lexeme) -> Anchor {
|
||||||
|
let parts: Vec<String> = lexeme
|
||||||
|
.to_raw()
|
||||||
|
.split('|')
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert!(parts.len() == 2, "Parts should always be 2: {parts:?}");
|
||||||
|
|
||||||
|
let text = parts.first().unwrap_or_else(|| unreachable!());
|
||||||
|
let destination = parts.get(1).unwrap_or_else(|| unreachable!());
|
||||||
|
|
||||||
|
Anchor {
|
||||||
|
text: text.to_owned(),
|
||||||
|
destination: destination.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> String {
|
||||||
|
format!(r#"<a href="{}">{}</a>"#, &self.destination, &self.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Anchor {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "Anchor: <{}> to <{}>", &self.text, &self.destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/syntax/content/parser/token/header.rs
Normal file
137
src/syntax/content/parser/token/header.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
use crate::{
|
||||||
|
prelude::*,
|
||||||
|
syntax::content::{Parseable, Lexeme},
|
||||||
|
};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub struct Header {
|
||||||
|
open: Option<bool>,
|
||||||
|
level: Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
pub fn new(level: Level, open: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
level,
|
||||||
|
open: Some(open),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_u8(level: u8, open: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
level: Level::from_u8(level),
|
||||||
|
open: Some(open),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_level(&self) -> u8 {
|
||||||
|
match self.level {
|
||||||
|
Level::One => 1,
|
||||||
|
Level::Two => 2,
|
||||||
|
Level::Three => 3,
|
||||||
|
Level::Four => 4,
|
||||||
|
Level::Five => 5,
|
||||||
|
Level::Six => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parseable for Header {
|
||||||
|
fn probe(lexeme: &Lexeme) -> bool {
|
||||||
|
if lexeme
|
||||||
|
.split_chars()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|e| *e != '#')
|
||||||
|
.count()
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
let level = lexeme.to_raw().len();
|
||||||
|
lexeme.clone().split_words().len() == 1 && level > 0 && level <= 6
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(lexeme: &Lexeme) -> Header {
|
||||||
|
Header::new(lexeme.to_raw().len().into(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> String {
|
||||||
|
if let Some(open) = self.open {
|
||||||
|
if open {
|
||||||
|
format!("<h{}>", &self.level)
|
||||||
|
} else {
|
||||||
|
format!("</h{}>", &self.level)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Attempt to render a header tag while open state is unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Header {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
if let Some(open) = self.open {
|
||||||
|
if open {
|
||||||
|
write!(f, "Level {} Open Header", self.level)
|
||||||
|
} else {
|
||||||
|
write!(f, "Level {} Closed Header", self.level)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write!(f, "Level {} Header (Unknown open state)", self.level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Level {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
Four,
|
||||||
|
Five,
|
||||||
|
Six,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Level {
|
||||||
|
fn from_u8(u: u8) -> Level {
|
||||||
|
if u <= 1 {
|
||||||
|
Level::One
|
||||||
|
} else if u == 2 {
|
||||||
|
Level::Two
|
||||||
|
} else if u == 3 {
|
||||||
|
Level::Three
|
||||||
|
} else if u == 4 {
|
||||||
|
Level::Four
|
||||||
|
} else if u == 5 {
|
||||||
|
Level::Five
|
||||||
|
} else {
|
||||||
|
Level::Six
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for Level {
|
||||||
|
fn from(z: usize) -> Level {
|
||||||
|
let u8 = match u8::try_from(z) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
log!("Truncating header level {z} to 6: {e:?}");
|
||||||
|
6_u8
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Level::from_u8(u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Level {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Level::One => write!(f, "1"),
|
||||||
|
Level::Two => write!(f, "2"),
|
||||||
|
Level::Three => write!(f, "3"),
|
||||||
|
Level::Four => write!(f, "4"),
|
||||||
|
Level::Five => write!(f, "5"),
|
||||||
|
Level::Six => write!(f, "6"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/syntax/content/parser/token/linebreak.rs
Normal file
26
src/syntax/content/parser/token/linebreak.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
use crate::{
|
||||||
|
syntax::content::{Parseable, parser::lexeme::Lexeme},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct LineBreak {}
|
||||||
|
|
||||||
|
impl Parseable for LineBreak {
|
||||||
|
fn probe(lexeme: &Lexeme) -> bool {
|
||||||
|
lexeme.to_raw() == "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(_lexeme: &Lexeme) -> LineBreak {
|
||||||
|
LineBreak {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> String {
|
||||||
|
"\n".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LineBreak {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "Line Break")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use crate::syntax::content::{Parseable, lexeme::Lexeme};
|
use crate::syntax::content::{Parseable, parser::lexeme::Lexeme};
|
||||||
|
|
||||||
pub struct Literal {
|
pub struct Literal {
|
||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parseable for Literal {
|
impl Parseable for Literal {
|
||||||
fn probe(lexeme: &Lexeme) -> bool {
|
fn probe(_lexeme: &Lexeme) -> bool {
|
||||||
!lexeme.to_raw().is_empty()
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex(lexeme: &Lexeme) -> Literal {
|
fn lex(lexeme: &Lexeme) -> Literal {
|
||||||
Literal {
|
Literal {
|
||||||
text: lexeme.to_raw().trim().to_owned(),
|
text: lexeme.to_raw(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
53
src/syntax/content/parser/token/paragraph.rs
Normal file
53
src/syntax/content/parser/token/paragraph.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
use crate::syntax::content::{Parseable, parser::lexeme::Lexeme};
|
||||||
|
|
||||||
|
pub struct Paragraph {
|
||||||
|
open: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Paragraph {
|
||||||
|
pub fn new(open: bool) -> Paragraph {
|
||||||
|
Paragraph { open: Some(open) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parseable for Paragraph {
|
||||||
|
fn probe(lexeme: &Lexeme) -> bool {
|
||||||
|
// lexeme for paragraph is any non-whitespace, parser knows the context
|
||||||
|
let raw = lexeme.to_raw();
|
||||||
|
let trimmed = raw.trim();
|
||||||
|
!trimmed.is_empty() && trimmed != "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(_lexeme: &Lexeme) -> Paragraph {
|
||||||
|
Paragraph { open: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> String {
|
||||||
|
if let Some(open) = self.open {
|
||||||
|
if open {
|
||||||
|
"<p>".to_owned()
|
||||||
|
} else {
|
||||||
|
"</p>".to_owned()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Attempt to render a paragraph tag while open state is unknown"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Paragraph {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
if let Some(open) = self.open {
|
||||||
|
if open {
|
||||||
|
write!(f, "Open Paragraph")
|
||||||
|
} else {
|
||||||
|
write!(f, "Closed Paragraph")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write!(f, "Unitialized Paragraph (Unknown open state)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/syntax/content/parser/token/preformat.rs
Normal file
43
src/syntax/content/parser/token/preformat.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
use crate::{
|
||||||
|
syntax::content::{Parseable, Lexeme},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct PreFormat {
|
||||||
|
open: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreFormat {
|
||||||
|
pub fn new(open: bool) -> PreFormat {
|
||||||
|
PreFormat { open: Some(open) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parseable for PreFormat {
|
||||||
|
fn probe(lexeme: &Lexeme) -> bool {
|
||||||
|
let chars = lexeme.split_chars();
|
||||||
|
|
||||||
|
if let Some(first_char) = chars.first() {
|
||||||
|
*first_char == '`'
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(_lexeme: &Lexeme) -> PreFormat {
|
||||||
|
PreFormat { open: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> String {
|
||||||
|
if let Some(o) = self.open {
|
||||||
|
if o {
|
||||||
|
"<pre>".to_owned()
|
||||||
|
} else {
|
||||||
|
"</pre>".to_owned()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Attempt to render a preformat tag while open state is unknown"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/syntax/content/parser/token/span.rs
Normal file
49
src/syntax/content/parser/token/span.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
use crate::syntax::content::{Parseable, parser::lexeme::Lexeme};
|
||||||
|
|
||||||
|
pub struct Span {
|
||||||
|
open: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
pub fn new(open: bool) -> Span {
|
||||||
|
Span { open: Some(open) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parseable for Span {
|
||||||
|
fn probe(_lexeme: &Lexeme) -> bool {
|
||||||
|
// there is no lexeme for span
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(_lexeme: &Lexeme) -> Span {
|
||||||
|
Span { open: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> String {
|
||||||
|
if let Some(open) = self.open {
|
||||||
|
if open {
|
||||||
|
"<span>".to_owned()
|
||||||
|
} else {
|
||||||
|
"</span>".to_owned()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Attempt to render a span tag while open state is unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Span {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
if let Some(open) = self.open {
|
||||||
|
if open {
|
||||||
|
write!(f, "Open Span")
|
||||||
|
} else {
|
||||||
|
write!(f, "Closed Span")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write!(f, "Span (Unknown open state)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod line;
|
|
||||||
pub mod word;
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
use crate::syntax::content::lexeme::Lexeme;
|
|
||||||
|
|
||||||
pub mod parser;
|
|
||||||
pub mod elements;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Line {
|
|
||||||
pub raw: String,
|
|
||||||
pub first: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Line {
|
|
||||||
pub fn new(text: &str) -> Line {
|
|
||||||
let vec: Vec<&str> = text.split(" ").collect();
|
|
||||||
|
|
||||||
Line {
|
|
||||||
raw: text.to_owned(),
|
|
||||||
first: vec.first().unwrap_or_else(|| unreachable!()).to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Lexeme> for Line {
|
|
||||||
fn from(lexeme: Lexeme) -> Line {
|
|
||||||
match lexeme {
|
|
||||||
Lexeme::Word(w) => Line {
|
|
||||||
raw: w.raw.clone(),
|
|
||||||
first: w.raw.split(' ').next().unwrap_or_default().to_owned(),
|
|
||||||
},
|
|
||||||
Lexeme::Line(l) => l,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod header;
|
|
||||||
pub mod paragraph;
|
|
||||||
pub mod span;
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
use crate::{
|
|
||||||
dev::log,
|
|
||||||
syntax::content::{Parseable, Lexeme},
|
|
||||||
};
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
enum Level {
|
|
||||||
One,
|
|
||||||
Two,
|
|
||||||
Three,
|
|
||||||
Four,
|
|
||||||
Five,
|
|
||||||
Six,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Level {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Level::One => write!(f, "1"),
|
|
||||||
Level::Two => write!(f, "2"),
|
|
||||||
Level::Three => write!(f, "3"),
|
|
||||||
Level::Four => write!(f, "4"),
|
|
||||||
Level::Five => write!(f, "5"),
|
|
||||||
Level::Six => write!(f, "6"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Header {
|
|
||||||
level: Level,
|
|
||||||
text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header {
|
|
||||||
fn new(level: usize, text: &str) -> Header {
|
|
||||||
Header {
|
|
||||||
level: match level {
|
|
||||||
1 => Level::One,
|
|
||||||
2 => Level::Two,
|
|
||||||
3 => Level::Three,
|
|
||||||
4 => Level::Four,
|
|
||||||
5 => Level::Five,
|
|
||||||
6 => Level::Six,
|
|
||||||
_ => {
|
|
||||||
panic!("Attempted to construct a header with invalid level")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
text: text.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parseable for Header {
|
|
||||||
fn probe(lexeme: &Lexeme) -> bool {
|
|
||||||
let first = lexeme.clone().first().unwrap_or_default();
|
|
||||||
!first.trim().is_empty()
|
|
||||||
&& first.replace("#", "").is_empty()
|
|
||||||
&& first.len() <= 6
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lex(lexeme: &Lexeme) -> Header {
|
|
||||||
let first = lexeme.clone().first().unwrap_or_else(|| unreachable!());
|
|
||||||
let header_level = &first.len();
|
|
||||||
log(&Header::lex, &format!("Header level is {header_level}"));
|
|
||||||
|
|
||||||
let header_text = lexeme.to_raw().replace(&first, "");
|
|
||||||
|
|
||||||
Header::new(*header_level, &header_text)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self) -> String {
|
|
||||||
format!("<h{}>{}</h{0}>", &self.level, self.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Header {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "Level {} Header: <{}>", &self.level, self.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
use std::fmt::Display;
|
|
||||||
use crate::syntax::content::{Parseable, lexeme::Lexeme};
|
|
||||||
|
|
||||||
pub struct Paragraph {
|
|
||||||
text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parseable for Paragraph {
|
|
||||||
fn probe(lexeme: &Lexeme) -> bool {
|
|
||||||
!lexeme.to_raw().trim().is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lex(lexeme: &Lexeme) -> Paragraph {
|
|
||||||
Paragraph {
|
|
||||||
text: lexeme.to_raw().trim().to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self) -> String {
|
|
||||||
format!("<p>{}</p>", &self.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Paragraph {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "Paragraph: <{}>", &self.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
use std::fmt::Display;
|
|
||||||
use crate::syntax::content::{Parseable, lexeme::Lexeme};
|
|
||||||
|
|
||||||
pub struct Span {
|
|
||||||
text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parseable for Span {
|
|
||||||
fn probe(lexeme: &Lexeme) -> bool {
|
|
||||||
!lexeme.to_raw().trim().is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lex(lexeme: &Lexeme) -> Span {
|
|
||||||
Span {
|
|
||||||
text: lexeme.to_raw().trim().to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self) -> String {
|
|
||||||
format!("<span>{}</span>", &self.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Span {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "Span: <{}>", &self.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
use crate::syntax::content::{
|
|
||||||
LexMap, Line, Parseable, Token, parsers::line::elements::header::Header,
|
|
||||||
make_lexmap, Lexeme,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LEXMAP: LexMap =
|
|
||||||
&[(Header::probe, |line| Token::Header(Header::lex(line)))];
|
|
||||||
|
|
||||||
pub(in crate::syntax::content) fn read<DefaultToken: Parseable>(
|
|
||||||
text: &str,
|
|
||||||
) -> String {
|
|
||||||
parse(&lex(text, &make_lexmap::<DefaultToken>(LEXMAP)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lex(text: &str, map: LexMap) -> Vec<Token> {
|
|
||||||
let mut tokens: Vec<Token> = Vec::new();
|
|
||||||
|
|
||||||
for raw_line in text.lines() {
|
|
||||||
let line = Lexeme::Line(Line::new(raw_line));
|
|
||||||
|
|
||||||
for &(ref probe, lex) in map {
|
|
||||||
if probe(&line) {
|
|
||||||
tokens.push(lex(&line));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(tokens: &[Token]) -> String {
|
|
||||||
tokens
|
|
||||||
.iter()
|
|
||||||
.map(Token::render)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
pub mod parser;
|
|
||||||
pub mod elements;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Word {
|
|
||||||
pub raw: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Word {
|
|
||||||
pub fn new(text: &str) -> Word {
|
|
||||||
Word {
|
|
||||||
raw: text.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod literal;
|
|
||||||
pub mod anchor;
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// use std::fmt::Display;
|
|
||||||
// use crate::syntax::content::{Parseable, Line};
|
|
||||||
//
|
|
||||||
// pub struct Anchor {
|
|
||||||
// text: String,
|
|
||||||
// destination: String,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// impl Parseable for Anchor {
|
|
||||||
// fn probe(line: &Line) -> bool {
|
|
||||||
// let candidate = line.raw.split(' ');
|
|
||||||
// !line.first.trim().is_empty()
|
|
||||||
// && line.first.replace("#", "").is_empty()
|
|
||||||
// && line.first.len() <= 6
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn lex(line: &Line) -> Self {
|
|
||||||
// Self {
|
|
||||||
// text: line.raw.trim().to_owned(),
|
|
||||||
// destination: t
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fn render(&self) -> String {
|
|
||||||
// format!(r#"<a href="{}">{}</a>"#, &self.destination, &self.text)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// impl Display for Anchor {
|
|
||||||
// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
// write!(f, "Anchor: <{}>", &self.text)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
use crate::syntax::content::parsers::word::elements::literal::Literal;
|
|
||||||
use crate::syntax::content::{Parseable, Token, Word, LexMap, make_lexmap};
|
|
||||||
use crate::syntax::content::lexeme::Lexeme;
|
|
||||||
|
|
||||||
const LEXMAP: LexMap =
|
|
||||||
&[(Literal::probe, |line| Token::Literal(Literal::lex(line)))];
|
|
||||||
|
|
||||||
pub(in crate::syntax::content) fn read<DefaultToken: Parseable>(
|
|
||||||
text: &str,
|
|
||||||
) -> String {
|
|
||||||
parse(&lex(text, &make_lexmap::<DefaultToken>(LEXMAP)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lex(text: &str, map: LexMap) -> Vec<Token> {
|
|
||||||
let mut tokens: Vec<Token> = Vec::new();
|
|
||||||
|
|
||||||
for raw_word in text.split(" ") {
|
|
||||||
let word = Lexeme::Word(Word::new(raw_word));
|
|
||||||
|
|
||||||
for &(ref probe, lex) in map {
|
|
||||||
if probe(&word) {
|
|
||||||
tokens.push(lex(&word));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(tokens: &[Token]) -> String {
|
|
||||||
tokens
|
|
||||||
.iter()
|
|
||||||
.map(Token::render)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" ")
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
use super::Parseable as _;
|
|
||||||
use super::parsers::word::elements::{literal::Literal};
|
|
||||||
use super::parsers::line::elements::{
|
|
||||||
paragraph::Paragraph, header::Header, span::Span,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub enum Token {
|
|
||||||
Paragraph(Paragraph),
|
|
||||||
Header(Header),
|
|
||||||
Span(Span),
|
|
||||||
Literal(Literal),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
|
||||||
pub fn render(&self) -> String {
|
|
||||||
match *self {
|
|
||||||
Token::Paragraph(ref d) => d.render(),
|
|
||||||
Token::Header(ref d) => d.render(),
|
|
||||||
Token::Span(ref d) => d.render(),
|
|
||||||
Token::Literal(ref d) => d.render(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Paragraph> for Token {
|
|
||||||
fn from(d: Paragraph) -> Token {
|
|
||||||
Token::Paragraph(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Header> for Token {
|
|
||||||
fn from(d: Header) -> Token {
|
|
||||||
Token::Header(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Span> for Token {
|
|
||||||
fn from(d: Span) -> Token {
|
|
||||||
Token::Span(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Literal> for Token {
|
|
||||||
fn from(d: Literal) -> Token {
|
|
||||||
Token::Literal(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
src/types.rs
11
src/types.rs
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use crate::syntax::content::parsers::{compound::elements::literal::Literal, line::elements::{paragraph::Paragraph, span::Span}};
|
use crate::syntax::content;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
||||||
pub struct Graph {
|
pub struct Graph {
|
||||||
|
|
@ -155,14 +155,9 @@ impl Node {
|
||||||
impl Config {
|
impl Config {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn parse_text(self) -> Config {
|
pub fn parse_text(self) -> Config {
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
footer_text: crate::syntax::content::parse::<Span, Literal>(
|
footer_text: content::parse(&self.footer_text),
|
||||||
&self.footer_text,
|
about_text: content::parse(&self.about_text),
|
||||||
),
|
|
||||||
about_text: crate::syntax::content::parse::<Paragraph, Literal>(
|
|
||||||
&self.about_text,
|
|
||||||
),
|
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ text = """
|
||||||
|
|
||||||
For now, if you want to try en, you must build it yourself.
|
For now, if you want to try en, you must build it yourself.
|
||||||
|
|
||||||
In an environment with a Rust toolchain and Git installed, run:
|
In an environment with a |Rust toolchain|https://rustup.rs/ and Git installed, run:
|
||||||
|
|
||||||
|
`
|
||||||
git clone https://codeberg.org/jutty/en
|
git clone https://codeberg.org/jutty/en
|
||||||
cd en
|
cd en
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
`
|
||||||
|
|
||||||
The en binary will be in target/release/en.
|
The en binary will be in target/release/en.
|
||||||
|
|
||||||
|
|
@ -19,32 +21,40 @@ The en binary will be in target/release/en.
|
||||||
|
|
||||||
The graph is a TOML file. You can create nodes by adding text such as:
|
The graph is a TOML file. You can create nodes by adding text such as:
|
||||||
|
|
||||||
|
`
|
||||||
[nodes.Computer]
|
[nodes.Computer]
|
||||||
text = "A computer is a machine capable of executing arbitrary instructions."
|
text = "A computer is a machine capable of executing arbitrary instructions."
|
||||||
|
`
|
||||||
|
|
||||||
If you need longer text, it's more convenient to use triple-quoted syntax:
|
If you need longer text, it's more convenient to use triple-quoted syntax:
|
||||||
|
|
||||||
|
`
|
||||||
[nodes.Computer]
|
[nodes.Computer]
|
||||||
text = \"""
|
text = \"""
|
||||||
A computer is a machine capable of executing arbitrary instructions.
|
A computer is a machine capable of executing arbitrary instructions.
|
||||||
\"""
|
\"""
|
||||||
|
`
|
||||||
|
|
||||||
Nodes can have connections between each other.
|
Nodes can have connections between each other.
|
||||||
|
|
||||||
To add a simple connection without any associated properties, you can simply add links:
|
To add a simple connection without any associated properties, you can simply add links:
|
||||||
|
|
||||||
|
`
|
||||||
[nodes.Quark]
|
[nodes.Quark]
|
||||||
text = "A subatomic particle that forms hadrons."
|
text = "A subatomic particle that forms hadrons."
|
||||||
|
|
||||||
links = [ "Particle", "Hadron" ]
|
links = [ "Particle", "Hadron" ]
|
||||||
|
`
|
||||||
|
|
||||||
This will create two outgoing connections from Quark: to Particle and to Hadron. It will also list Quark as an incoming connection in these nodes' pages.
|
This will create two outgoing connections from Quark: to Particle and to Hadron. It will also list Quark as an incoming connection in these nodes' pages.
|
||||||
|
|
||||||
If you want to add properties to the connection, you can use the connection syntax:
|
If you want to add properties to the connection, you can use the connection syntax:
|
||||||
|
|
||||||
|
`
|
||||||
[[nodes.Quark.connections]]
|
[[nodes.Quark.connections]]
|
||||||
to = "Particle physics"
|
to = "Particle physics"
|
||||||
anchor = "particle"
|
anchor = "particle"
|
||||||
|
`
|
||||||
|
|
||||||
This will create a connection from Quark to "Particle physics", and the first occurrence of the word "particle" in the text of Quark gets anchored to this connection.
|
This will create a connection from Quark to "Particle physics", and the first occurrence of the word "particle" in the text of Quark gets anchored to this connection.
|
||||||
|
|
||||||
|
|
@ -54,30 +64,38 @@ You can set the hostname, port and graph file path using CLI options:
|
||||||
|
|
||||||
For the hostname, use -h or --hostname:
|
For the hostname, use -h or --hostname:
|
||||||
|
|
||||||
|
`
|
||||||
en -h localhost
|
en -h localhost
|
||||||
en --hostname 10.120.0.5
|
en --hostname 10.120.0.5
|
||||||
|
`
|
||||||
|
|
||||||
If unspecified, the default is 0.0.0.0.
|
If unspecified, the default is 0.0.0.0.
|
||||||
|
|
||||||
For the port, use -p or --port:
|
For the port, use -p or --port:
|
||||||
|
|
||||||
|
`
|
||||||
en -p 3003
|
en -p 3003
|
||||||
en --port 3000
|
en --port 3000
|
||||||
|
`
|
||||||
|
|
||||||
If unspecified, the default is to use a random available port assigned by the operating system.
|
If unspecified, the default is to use a random available port assigned by the operating system.
|
||||||
|
|
||||||
For the graph path, use -g or --graph:
|
For the graph path, use -g or --graph:
|
||||||
|
|
||||||
|
`
|
||||||
en -g graph.toml
|
en -g graph.toml
|
||||||
en --g ./static/my-graph.toml
|
en --g ./static/my-graph.toml
|
||||||
|
`
|
||||||
|
|
||||||
If unspecified, the default is ./static/graph.toml.
|
If unspecified, the default is ./static/graph.toml.
|
||||||
|
|
||||||
You can combine these options as you wish:
|
You can combine these options as you wish:
|
||||||
|
|
||||||
|
`
|
||||||
en -h localhost -p 3000
|
en -h localhost -p 3000
|
||||||
en -p 3003 --host localhost --graph ./graph.toml
|
en -p 3003 --host localhost --graph ./graph.toml
|
||||||
en --g ./graph.toml -p 1312
|
en --g ./graph.toml -p 1312
|
||||||
|
`
|
||||||
|
|
||||||
If an option is specified more than once, the last use will override any previous ones.
|
If an option is specified more than once, the last use will override any previous ones.
|
||||||
|
|
||||||
|
|
@ -127,16 +145,16 @@ To see the TOML declaration that translates into the rendered graph you are read
|
||||||
text = """
|
text = """
|
||||||
en is only possible thanks to a number of projects and people:
|
en is only possible thanks to a number of projects and people:
|
||||||
|
|
||||||
- [The Rust Programing Language](https://rust-lang.org/)
|
- |The Rust Programing Language|https://rust-lang.org/
|
||||||
- [Tokio](https://tokio.rs/)
|
- Tokio|https://tokio.rs/
|
||||||
- [Axum](https://github.com/tokio-rs/axum)
|
- Axum|https://github.com/tokio-rs/axum
|
||||||
- [Tera](https://keats.github.io/tera/)
|
- Tera|https://keats.github.io/tera/
|
||||||
- [Serde](https://serde.rs/) and the [toml crate](https://github.com/toml-rs/toml)
|
- Serde|https://serde.rs/ and the |toml crate|https://github.com/toml-rs/toml
|
||||||
- [Bacon](https://dystroy.org/bacon/config/)
|
- Bacon|https://dystroy.org/bacon/config/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[meta.config]
|
[meta.config]
|
||||||
footer_credits = false
|
footer_credits = false
|
||||||
footer_text = """
|
footer_text = """
|
||||||
made by jutty • acknowledgements • source code
|
made by jutty|https://jutty.dev • acknowledgements|Acknowledgments • |source code|https://codeberg.org/jutty/en
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
* {
|
|
||||||
line-height: 1.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue