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
|
||||
|
||||
- [ ] Strip/render some syntax in Tree text preview
|
||||
- [ ] Richer text formatting
|
||||
- [x] Headers
|
||||
- [ ] Code blocks
|
||||
- [x] Preformatted blocks
|
||||
- [ ] Inline code
|
||||
- [ ] Anchor rendering
|
||||
- [x] Anchor rendering
|
||||
- [ ] Automatic anchors
|
||||
- [ ] External anchors
|
||||
- [x] External anchors
|
||||
- [ ] Bold, italics, underline, strikethrough
|
||||
- [ ] Lists
|
||||
- [ ] 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
|
||||
- [ ] Add tests
|
||||
- [ ] Multi-file graphs
|
||||
- [ ] Themes
|
||||
- [ ] Multi-graph
|
||||
- [ ] Themes
|
||||
- [x] Array syntax for lightweight connections
|
||||
- [x] Automatic IDs
|
||||
- [x] Automatic titles
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::handlers;
|
|||
|
||||
/// # Panics
|
||||
/// Will panic if file read fails.
|
||||
#[expect(clippy::unused_async)]
|
||||
pub async fn file(file_path: &str, content_type: &str) -> Response<Body> {
|
||||
let content = match std::fs::read(file_path) {
|
||||
Ok(s) => s,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
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::parsers::compound::elements::literal::Literal;
|
||||
use crate::{formats::populate_graph, handlers, types::Node};
|
||||
|
||||
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("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);
|
||||
|
||||
let not_found = *node == empty_node;
|
||||
|
|
|
|||
|
|
@ -5,18 +5,7 @@ use axum::{
|
|||
Form,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
formats::populate_graph,
|
||||
handlers,
|
||||
syntax::content::{
|
||||
self,
|
||||
parsers::{
|
||||
line::elements::{paragraph::Paragraph, span::Span},
|
||||
compound::elements::literal::Literal,
|
||||
},
|
||||
},
|
||||
types::{Config, Node},
|
||||
};
|
||||
use crate::{formats::populate_graph, handlers, types::Node};
|
||||
|
||||
#[expect(clippy::unused_async)]
|
||||
pub async fn page(template: &str) -> Response<Body> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use axum::{
|
|||
http::{header, Response, StatusCode},
|
||||
};
|
||||
|
||||
use crate::handlers::raw::make_response;
|
||||
use crate::{prelude::*, handlers::raw::make_response};
|
||||
|
||||
pub(in crate::handlers) fn by_filename(
|
||||
name: &str,
|
||||
|
|
@ -39,8 +39,8 @@ pub(in crate::handlers) fn render(
|
|||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
let early_error_message = format!("{e:#?}");
|
||||
crate::dev::log(&by_filename, &early_error_message);
|
||||
return (emergency_wrap(&e), 500)
|
||||
log!("{}", early_error_message);
|
||||
return (emergency_wrap(&e), 500);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -79,7 +79,8 @@ pub(in crate::handlers) fn render(
|
|||
}
|
||||
|
||||
fn emergency_wrap(message: &tera::Error) -> String {
|
||||
format!(r#"<!DOCTYPE html>
|
||||
format!(
|
||||
r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Pre-Templating Error</title>
|
||||
|
|
@ -105,5 +106,6 @@ fn emergency_wrap(message: &tera::Error) -> String {
|
|||
</p>
|
||||
</body>
|
||||
</html>
|
||||
"#)
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Arguments {
|
||||
pub hostname: String,
|
||||
|
|
@ -47,10 +49,7 @@ fn parse(defaults: &Arguments, args: &[String]) -> Arguments {
|
|||
} else if argument.eq("-g") || argument.eq("--graph") {
|
||||
out_args.graph_path = PathBuf::from(parameter);
|
||||
} else {
|
||||
crate::dev::log(
|
||||
&parse,
|
||||
&format!("Dropped unrecognized argument {argument}"),
|
||||
);
|
||||
log!("Dropped unrecognized argument {argument}");
|
||||
}
|
||||
} else {
|
||||
panic!("Argument {arg:?} has no corresponding value")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
use token::{Token};
|
||||
use parsers::{line::Line, word::Word};
|
||||
use lexeme::Lexeme;
|
||||
use parser::{token::Token, lexeme::Lexeme};
|
||||
|
||||
mod token;
|
||||
pub mod lexeme;
|
||||
pub mod parsers;
|
||||
pub mod parser;
|
||||
|
||||
pub trait Parseable: Into<Token> {
|
||||
fn probe(lexeme: &Lexeme) -> bool;
|
||||
|
|
@ -16,22 +12,6 @@ type Probe = fn(&Lexeme) -> bool;
|
|||
type Lexer = fn(&Lexeme) -> Token;
|
||||
type LexMap<'lm> = &'lm [(Probe, Lexer)];
|
||||
|
||||
fn make_lexmap<DefaultToken: Parseable>(base: LexMap) -> Vec<(Probe, Lexer)> {
|
||||
let mut vector: Vec<(Probe, Lexer)> = base.to_vec();
|
||||
|
||||
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),
|
||||
)
|
||||
pub fn parse(text: &str) -> String {
|
||||
parser::read(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 crate::syntax::content::{Parseable, lexeme::Lexeme};
|
||||
use crate::syntax::content::{Parseable, parser::lexeme::Lexeme};
|
||||
|
||||
pub struct Literal {
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Parseable for Literal {
|
||||
fn probe(lexeme: &Lexeme) -> bool {
|
||||
!lexeme.to_raw().is_empty()
|
||||
fn probe(_lexeme: &Lexeme) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn lex(lexeme: &Lexeme) -> 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 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)]
|
||||
pub struct Graph {
|
||||
|
|
@ -155,14 +155,9 @@ impl Node {
|
|||
impl Config {
|
||||
#[must_use]
|
||||
pub fn parse_text(self) -> Config {
|
||||
|
||||
Config {
|
||||
footer_text: crate::syntax::content::parse::<Span, Literal>(
|
||||
&self.footer_text,
|
||||
),
|
||||
about_text: crate::syntax::content::parse::<Paragraph, Literal>(
|
||||
&self.about_text,
|
||||
),
|
||||
footer_text: content::parse(&self.footer_text),
|
||||
about_text: content::parse(&self.about_text),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ text = """
|
|||
|
||||
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
|
||||
cd en
|
||||
cargo build --release
|
||||
`
|
||||
|
||||
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:
|
||||
|
||||
`
|
||||
[nodes.Computer]
|
||||
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:
|
||||
|
||||
`
|
||||
[nodes.Computer]
|
||||
text = \"""
|
||||
A computer is a machine capable of executing arbitrary instructions.
|
||||
\"""
|
||||
`
|
||||
|
||||
Nodes can have connections between each other.
|
||||
|
||||
To add a simple connection without any associated properties, you can simply add links:
|
||||
|
||||
`
|
||||
[nodes.Quark]
|
||||
text = "A subatomic particle that forms hadrons."
|
||||
|
||||
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.
|
||||
|
||||
If you want to add properties to the connection, you can use the connection syntax:
|
||||
|
||||
`
|
||||
[[nodes.Quark.connections]]
|
||||
to = "Particle physics"
|
||||
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.
|
||||
|
||||
|
|
@ -54,30 +64,38 @@ You can set the hostname, port and graph file path using CLI options:
|
|||
|
||||
For the hostname, use -h or --hostname:
|
||||
|
||||
`
|
||||
en -h localhost
|
||||
en --hostname 10.120.0.5
|
||||
`
|
||||
|
||||
If unspecified, the default is 0.0.0.0.
|
||||
|
||||
For the port, use -p or --port:
|
||||
|
||||
`
|
||||
en -p 3003
|
||||
en --port 3000
|
||||
`
|
||||
|
||||
If unspecified, the default is to use a random available port assigned by the operating system.
|
||||
|
||||
For the graph path, use -g or --graph:
|
||||
|
||||
`
|
||||
en -g graph.toml
|
||||
en --g ./static/my-graph.toml
|
||||
`
|
||||
|
||||
If unspecified, the default is ./static/graph.toml.
|
||||
|
||||
You can combine these options as you wish:
|
||||
|
||||
`
|
||||
en -h localhost -p 3000
|
||||
en -p 3003 --host localhost --graph ./graph.toml
|
||||
en --g ./graph.toml -p 1312
|
||||
`
|
||||
|
||||
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 = """
|
||||
en is only possible thanks to a number of projects and people:
|
||||
|
||||
- [The Rust Programing Language](https://rust-lang.org/)
|
||||
- [Tokio](https://tokio.rs/)
|
||||
- [Axum](https://github.com/tokio-rs/axum)
|
||||
- [Tera](https://keats.github.io/tera/)
|
||||
- [Serde](https://serde.rs/) and the [toml crate](https://github.com/toml-rs/toml)
|
||||
- [Bacon](https://dystroy.org/bacon/config/)
|
||||
- |The Rust Programing Language|https://rust-lang.org/
|
||||
- Tokio|https://tokio.rs/
|
||||
- Axum|https://github.com/tokio-rs/axum
|
||||
- Tera|https://keats.github.io/tera/
|
||||
- Serde|https://serde.rs/ and the |toml crate|https://github.com/toml-rs/toml
|
||||
- Bacon|https://dystroy.org/bacon/config/
|
||||
"""
|
||||
|
||||
[meta.config]
|
||||
footer_credits = false
|
||||
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 {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue