From db8c02df04a59a8404ede240fccc2a36ccb4d51d Mon Sep 17 00:00:00 2001 From: jutty Date: Sun, 11 Jan 2026 21:31:51 -0300 Subject: [PATCH] Break up 'types' module --- Cargo.toml | 1 - src/graph.rs | 109 +++++++++++ src/graph/edge.rs | 12 ++ src/{types.rs => graph/meta.rs} | 183 ++----------------- src/graph/node.rs | 53 ++++++ src/lib.rs | 2 +- src/router.rs | 4 +- src/router/handlers/graph.rs | 5 +- src/router/handlers/navigation.rs | 2 +- src/router/handlers/template.rs | 2 +- src/syntax/content.rs | 8 +- src/syntax/content/parser.rs | 21 ++- src/syntax/content/parser/context.rs | 8 +- src/syntax/content/parser/context/anchor.rs | 8 +- src/syntax/content/parser/context/block.rs | 21 +-- src/syntax/content/parser/context/inline.rs | 10 +- src/syntax/content/parser/context/list.rs | 20 +- src/syntax/content/parser/point.rs | 10 +- src/syntax/content/parser/state.rs | 2 +- src/syntax/content/parser/token.rs | 126 +++++++------ src/syntax/content/parser/token/anchor.rs | 20 +- src/syntax/content/parser/token/bold.rs | 2 +- src/syntax/content/parser/token/checkbox.rs | 2 +- src/syntax/content/parser/token/code.rs | 2 +- src/syntax/content/parser/token/header.rs | 10 +- src/syntax/content/parser/token/item.rs | 2 +- src/syntax/content/parser/token/linebreak.rs | 4 +- src/syntax/content/parser/token/list.rs | 7 +- src/syntax/content/parser/token/literal.rs | 4 +- src/syntax/content/parser/token/oblique.rs | 2 +- src/syntax/content/parser/token/paragraph.rs | 4 +- src/syntax/content/parser/token/preformat.rs | 2 +- src/syntax/content/parser/token/strike.rs | 2 +- src/syntax/content/parser/token/underline.rs | 2 +- src/syntax/serial.rs | 37 +++- templates/node.html | 2 +- 36 files changed, 382 insertions(+), 329 deletions(-) create mode 100644 src/graph.rs create mode 100644 src/graph/edge.rs rename src/{types.rs => graph/meta.rs} (50%) create mode 100644 src/graph/node.rs diff --git a/Cargo.toml b/Cargo.toml index d21f591..3bcf37a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -181,7 +181,6 @@ needless_raw_strings = "warn" non_zero_suggestions = "warn" panic_in_result_fn = "warn" pathbuf_init_then_push = "warn" -pattern_type_mismatch = "warn" pub_without_shorthand = "warn" redundant_test_prefix = "warn" redundant_type_annotations = "warn" diff --git a/src/graph.rs b/src/graph.rs new file mode 100644 index 0000000..d3f0031 --- /dev/null +++ b/src/graph.rs @@ -0,0 +1,109 @@ +use std::collections::HashMap; + +use serde::{Serialize, Deserialize}; + +use crate::syntax::content; +pub use { + node::Node, + edge::Edge, + meta::{Meta, Config}, +}; + +pub mod node; +pub mod edge; +pub mod meta; + +#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] +pub struct Graph { + pub nodes: HashMap, + pub root_node: String, + #[serde(skip_deserializing)] + pub incoming: HashMap>, + #[serde(skip_deserializing)] + pub lowercase_keymap: HashMap, + #[serde(default)] + pub meta: Meta, +} + +#[derive(Clone)] +pub struct QueryResult { + pub node: Option, + pub redirect: bool, +} + +impl Graph { + pub fn new(message: Option<&str>) -> Graph { + Graph { + nodes: HashMap::default(), + root_node: "VoidNode".to_string(), + incoming: HashMap::default(), + lowercase_keymap: HashMap::default(), + meta: Meta { + config: Config::default(), + version: (0, 1, 0), + messages: message.map_or(vec![], |m| vec![m.to_string()]), + }, + } + } + + pub fn find_node(&self, query: &str) -> QueryResult { + let collapsed_query = query.trim().replace(" ", ""); + + let candidate = if let Some(exact_match) = self.nodes.get(query) { + QueryResult { + node: Some(exact_match.clone()), + redirect: false, + } + } else if let Some(lower_key) = + self.lowercase_keymap.get(&collapsed_query.to_lowercase()) + { + QueryResult { + node: self.nodes.get(lower_key).cloned(), + redirect: true, + } + } else { + QueryResult { + node: None, + redirect: false, + } + }; + + if let Some(candidate_node) = &candidate.node + && !candidate_node.redirect.is_empty() + { + QueryResult { + node: self.find_node(&candidate_node.redirect).node, + redirect: true, + } + } else { + candidate + } + } + + pub fn get_root(&self) -> Option { + self.nodes.get(&self.root_node).cloned() + } + + pub fn parse(&mut self) { + self.meta.config.footer_text = + content::parse(&self.meta.config.footer_text, self); + self.meta.config.about_text = + content::parse(&self.meta.config.about_text, self); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_graph() { + let graph = Graph::new(Some("ISryQFd9peG6eYz9CFRQFWeD1GnPo0oj")); + assert!(graph.nodes.is_empty()); + assert!(graph.incoming.is_empty()); + assert_eq!( + graph.meta.messages.first().unwrap(), + "ISryQFd9peG6eYz9CFRQFWeD1GnPo0oj" + ); + } +} diff --git a/src/graph/edge.rs b/src/graph/edge.rs new file mode 100644 index 0000000..6f5b924 --- /dev/null +++ b/src/graph/edge.rs @@ -0,0 +1,12 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] +pub struct Edge { + pub to: String, + #[serde(default)] + pub anchor: String, + #[serde(default)] + pub from: String, + #[serde(default)] + pub detached: bool, +} diff --git a/src/types.rs b/src/graph/meta.rs similarity index 50% rename from src/types.rs rename to src/graph/meta.rs index 31aa78d..7a689d1 100644 --- a/src/types.rs +++ b/src/graph/meta.rs @@ -1,53 +1,5 @@ -use std::collections::HashMap; - use serde::{Serialize, Deserialize}; -use crate::syntax::content; - -#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] -pub struct Graph { - pub nodes: HashMap, - pub root_node: String, - #[serde(skip_deserializing)] - pub incoming: HashMap>, - #[serde(skip_deserializing)] - pub lowercase_keymap: HashMap, - #[serde(default)] - pub meta: Meta, -} - -#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] -pub struct Node { - #[serde(default)] - pub id: String, - #[serde(default)] - pub text: String, - #[serde(default)] - pub summary: String, - #[serde(default)] - pub title: String, - #[serde(default)] - pub links: Vec, - #[serde(default)] - pub redirect: String, - #[serde(default)] - pub hidden: bool, - - #[serde(skip_serializing_if = "Option::is_none")] - pub connections: Option>, -} - -#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] -pub struct Edge { - pub to: String, - #[serde(default)] - pub anchor: String, - #[serde(default)] - pub from: String, - #[serde(default)] - pub detached: bool, -} - #[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] pub struct Meta { pub config: Config, @@ -57,11 +9,6 @@ pub struct Meta { pub messages: Vec, } -// See: https://github.com/serde-rs/serde/issues/368 -fn mkversion() -> (u8, u8, u8) { - (0, 0, 0) -} - #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub struct Config { #[serde(default)] @@ -112,102 +59,6 @@ pub struct Config { pub tree_node_summary: bool, } -// See: https://github.com/serde-rs/serde/issues/368 -fn mktrue() -> bool { - true -} -fn mkfalse() -> bool { - false -} -fn mk8() -> u16 { - 8 -} - -#[derive(Clone)] -pub struct QueryResult { - pub node: Option, - pub redirect: bool, -} - -impl Graph { - pub fn new(message: Option<&str>) -> Graph { - Graph { - nodes: HashMap::default(), - root_node: "VoidNode".to_string(), - incoming: HashMap::default(), - lowercase_keymap: HashMap::default(), - meta: Meta { - config: Config::default(), - version: (0, 1, 0), - messages: message.map_or(vec![], |m| vec![m.to_string()]), - }, - } - } - - pub fn find_node(&self, query: &str) -> QueryResult { - let collapsed_query = query.trim().replace(" ", ""); - - let candidate = if let Some(exact_match) = self.nodes.get(query) { - QueryResult { - node: Some(exact_match.clone()), - redirect: false, - } - } else if let Some(lower_key) = - self.lowercase_keymap.get(&collapsed_query.to_lowercase()) - { - QueryResult { - node: self.nodes.get(lower_key).cloned(), - redirect: true, - } - } else { - QueryResult { - node: None, - redirect: false, - } - }; - - if let Some(ref candidate_node) = candidate.node - && !candidate_node.redirect.is_empty() - { - QueryResult { - node: self.find_node(&candidate_node.redirect).node, - redirect: true, - } - } else { - candidate - } - } - - pub fn get_root(&self) -> Option { - self.nodes.get(&self.root_node).cloned() - } - - pub fn parse(&mut self) { - self.meta.config.footer_text = - content::parse(&self.meta.config.footer_text, self); - self.meta.config.about_text = - content::parse(&self.meta.config.about_text, self); - } -} - -impl Node { - pub fn new(message: Option) -> Node { - Node { - id: "404".to_string(), - title: "Not Found".to_string(), - text: match message { - Some(s) => s, - None => "Node not found.".to_string(), - }, - connections: None, - links: vec![], - redirect: String::default(), - hidden: false, - summary: String::default(), - } - } -} - impl Default for Config { fn default() -> Config { Config { @@ -238,28 +89,24 @@ impl Default for Config { } } +// See: https://github.com/serde-rs/serde/issues/368 +fn mktrue() -> bool { + true +} +fn mkfalse() -> bool { + false +} +fn mk8() -> u16 { + 8 +} +fn mkversion() -> (u8, u8, u8) { + (0, 0, 0) +} + #[cfg(test)] mod tests { - use crate::syntax::serial::populate_graph; - use super::*; - - #[test] - fn empty_graph() { - let graph = Graph::new(Some("ISryQFd9peG6eYz9CFRQFWeD1GnPo0oj")); - assert!(graph.nodes.is_empty()); - assert!(graph.incoming.is_empty()); - assert_eq!( - graph.meta.messages.first().unwrap(), - "ISryQFd9peG6eYz9CFRQFWeD1GnPo0oj" - ); - } - - #[test] - fn empty_node_message() { - let node = Node::new(None); - assert_eq!(node.text, "Node not found."); - } + use crate::syntax::serial::populate_graph; #[test] fn empty_footer_text() { diff --git a/src/graph/node.rs b/src/graph/node.rs new file mode 100644 index 0000000..3efe26b --- /dev/null +++ b/src/graph/node.rs @@ -0,0 +1,53 @@ +use serde::{Serialize, Deserialize}; + +use super::edge::Edge; + +#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] +pub struct Node { + #[serde(default)] + pub id: String, + #[serde(default)] + pub text: String, + #[serde(default)] + pub summary: String, + #[serde(default)] + pub title: String, + #[serde(default)] + pub links: Vec, + #[serde(default)] + pub redirect: String, + #[serde(default)] + pub hidden: bool, + + #[serde(skip_serializing_if = "Option::is_none")] + pub connections: Option>, +} + +impl Node { + pub fn new(message: Option) -> Node { + Node { + id: "404".to_string(), + title: "Not Found".to_string(), + text: match message { + Some(s) => s, + None => "Node not found.".to_string(), + }, + connections: None, + links: vec![], + redirect: String::default(), + hidden: false, + summary: String::default(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_node_message() { + let node = Node::new(None); + assert_eq!(node.text, "Node not found."); + } +} diff --git a/src/lib.rs b/src/lib.rs index 630bf0c..b5d5f34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ pub mod prelude { pub use crate::log; } -pub mod types; +pub mod graph; pub mod router; pub mod syntax; pub mod dev; diff --git a/src/router.rs b/src/router.rs index f35ed97..b0f70dd 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,6 +1,6 @@ use axum::{routing::get, Router}; -use crate::{syntax::serial::Format, types::Graph}; +use crate::{syntax::serial::Format, graph::Graph}; mod handlers { pub mod graph; @@ -70,7 +70,7 @@ pub fn new(graph: &Graph) -> Router { mod tests { use crate::{ syntax::serial::populate_graph, - types::{Config, Meta}, + graph::{Config, Meta}, }; use super::*; diff --git a/src/router/handlers/graph.rs b/src/router/handlers/graph.rs index 3079a8e..c373034 100644 --- a/src/router/handlers/graph.rs +++ b/src/router/handlers/graph.rs @@ -1,9 +1,7 @@ use axum::response::IntoResponse as _; use axum::{body::Body, extract::Path, http::Response, response::Redirect}; -use crate::syntax::content; - -use crate::{syntax::serial::populate_graph, router::handlers, types::Node}; +use crate::{syntax::serial::populate_graph, router::handlers, graph::Node}; pub async fn node(Path(id): Path) -> Response { let graph = populate_graph(); @@ -29,7 +27,6 @@ pub async fn node(Path(id): Path) -> Response { let mut context = tera::Context::default(); context.insert("node", &node); context.insert("nodes", &nodes); - context.insert("text", &content::parse(&node.text, &graph)); context.insert("incoming", &graph.incoming.get(&id)); context.insert("config", &graph.meta.config); diff --git a/src/router/handlers/navigation.rs b/src/router/handlers/navigation.rs index 73f39b8..913a3ab 100644 --- a/src/router/handlers/navigation.rs +++ b/src/router/handlers/navigation.rs @@ -5,7 +5,7 @@ use axum::{ Form, }; -use crate::{syntax::serial::populate_graph, router::handlers, types::Node}; +use crate::{syntax::serial::populate_graph, router::handlers, graph::Node}; #[expect(clippy::unused_async)] pub async fn page(template: &str) -> Response { diff --git a/src/router/handlers/template.rs b/src/router/handlers/template.rs index cc4cb7a..8549bb4 100644 --- a/src/router/handlers/template.rs +++ b/src/router/handlers/template.rs @@ -153,7 +153,7 @@ mod tests { fn render_with_context() { let payload = "dBgIw8DnNHxJojiXzu445qUC4UpxwZCy"; let mut context = tera::Context::default(); - let node = crate::types::Node::new(Some(payload.to_string())); + let node = crate::graph::Node::new(Some(payload.to_string())); let graph = crate::syntax::serial::populate_graph(); context.insert("node", &node); context diff --git a/src/syntax/content.rs b/src/syntax/content.rs index b802efa..081f6f7 100644 --- a/src/syntax/content.rs +++ b/src/syntax/content.rs @@ -1,6 +1,6 @@ -use parser::{token::Token, lexeme::Lexeme}; +use parser::{Token, Lexeme}; -use crate::types::Graph; +use crate::graph::Graph; pub mod parser; @@ -18,3 +18,7 @@ type LexMap<'lm> = &'lm [(Probe, Lexer)]; pub fn parse(text: &str, graph: &Graph) -> String { parser::read(text, graph) } + +pub fn rich_parse(text: &str, graph: &Graph) -> (String, Vec) { + parser::rich_read(text, graph) +} diff --git a/src/syntax/content/parser.rs b/src/syntax/content/parser.rs index f00de4f..9d110f4 100644 --- a/src/syntax/content/parser.rs +++ b/src/syntax/content/parser.rs @@ -1,8 +1,8 @@ -use crate::{prelude::*, types::Graph}; -use super::{Parseable as _, Token, LexMap}; -use token::{linebreak::LineBreak, literal::Literal}; -use lexeme::Lexeme; +use crate::{prelude::*, graph::Graph}; +use super::{Parseable as _, LexMap}; +use token::{LineBreak, Literal}; use context::{Block, Inline}; +pub use {lexeme::Lexeme, token::Token, state::State}; pub mod token; pub mod lexeme; @@ -22,7 +22,7 @@ const LEXMAP: LexMap = &[ fn lex(text: &str, map: LexMap, graph: &Graph, blocking: bool) -> Vec { let mut tokens: Vec = Vec::default(); - let mut state = state::State::default(); + let mut state = State::default(); let segments = segment::segment(text); let lexemes = Lexeme::collect(&segments); @@ -64,7 +64,7 @@ fn lex(text: &str, map: LexMap, graph: &Graph, blocking: bool) -> Vec { continue; } - for &(ref probe, lex) in map { + for (probe, lex) in map { if probe(lexeme) { let token = lex(lexeme); log!("Lexmap lexed {lexeme} into {token}"); @@ -82,9 +82,14 @@ pub(super) fn read(input: &str, graph: &Graph) -> String { parse(&lex(input, LEXMAP, graph, true)) } +pub(super) fn rich_read(input: &str, graph: &Graph) -> (String, Vec) { + let tokens = lex(input, LEXMAP, graph, true); + let text = parse(&tokens); + (text, tokens) +} /// Apply end-to-end point and inline parsing for nested formatting, such as /// inside the display text of anchors and list items -pub fn nest(input: &str, graph: &Graph) -> String { +pub fn format(input: &str, graph: &Graph) -> String { parse(&lex(input, LEXMAP, graph, false)) } @@ -103,7 +108,7 @@ fn parse(tokens: &[Token]) -> String { #[cfg(test)] mod tests { use crate::{ - types::Graph, + graph::Graph, syntax::content::parser::{token::header::Level}, }; diff --git a/src/syntax/content/parser/context.rs b/src/syntax/content/parser/context.rs index 3e8d30e..e536bd2 100644 --- a/src/syntax/content/parser/context.rs +++ b/src/syntax/content/parser/context.rs @@ -1,8 +1,6 @@ use crate::syntax::content::parser::{ - state::State, - token::{ - Token, header::Header, paragraph::Paragraph, preformat::PreFormat, - }, + State, Token, + token::{Header, Paragraph, PreFormat}, }; pub mod block; @@ -54,7 +52,7 @@ pub fn close(state: &State, tokens: &mut Vec) { #[cfg(test)] mod tests { - use crate::syntax::content::parser::{context::Block, state::State}; + use crate::syntax::content::parser::{context::Block, State}; #[test] #[should_panic(expected = "End of input with open list")] diff --git a/src/syntax/content/parser/context/anchor.rs b/src/syntax/content/parser/context/anchor.rs index 1a45d3e..be6f59b 100644 --- a/src/syntax/content/parser/context/anchor.rs +++ b/src/syntax/content/parser/context/anchor.rs @@ -1,9 +1,7 @@ use crate::{ prelude::*, - syntax::content::parser::{ - context::Inline, lexeme::Lexeme, state::State, token::Token, - }, - types::Graph, + syntax::content::parser::{context::Inline, Lexeme, State, Token}, + graph::Graph, }; /// Handles open anchor contexts until an anchor token is fully parsed. @@ -154,7 +152,7 @@ fn push( #[cfg(test)] mod tests { - use crate::{syntax::content::parser, types::Graph}; + use crate::{syntax::content::parser, graph::Graph}; fn read(input: &str) -> String { parser::read(input, &Graph::default()) diff --git a/src/syntax/content/parser/context/block.rs b/src/syntax/content/parser/context/block.rs index 03a6c97..d6eb9e8 100644 --- a/src/syntax/content/parser/context/block.rs +++ b/src/syntax/content/parser/context/block.rs @@ -5,16 +5,11 @@ use crate::{ syntax::content::{ Parseable as _, parser::{ - Block, - lexeme::Lexeme, - state::State, - token::{ - Token, header::Header, list::List, literal::Literal, - paragraph::Paragraph, preformat::PreFormat, - }, + Block, Token, Lexeme, State, + token::{Header, List, Literal, Paragraph, PreFormat}, }, }, - types::Graph, + graph::Graph, }; pub fn parse( @@ -91,14 +86,10 @@ mod tests { use crate::{ syntax::content::parser::{ - self, Block, Token, context, - state::State, - token::{ - header::{Header, Level}, - preformat::PreFormat, - }, + self, Block, Token, context, State, + token::{Header, header::Level, PreFormat}, }, - types::Graph, + graph::Graph, }; fn read(input: &str) -> String { diff --git a/src/syntax/content/parser/context/inline.rs b/src/syntax/content/parser/context/inline.rs index eb339ef..e98e9e2 100644 --- a/src/syntax/content/parser/context/inline.rs +++ b/src/syntax/content/parser/context/inline.rs @@ -5,13 +5,13 @@ use crate::{ syntax::content::{ Parseable as _, parser::{ - Inline, context, - lexeme::Lexeme, - state::{AnchorBuffer, State}, - token::{Token, anchor::Anchor, code::Code, literal::Literal}, + Lexeme, State, + state::AnchorBuffer, + Inline, context, Token, + token::{Anchor, Code, Literal}, }, }, - types::Graph, + graph::Graph, }; pub fn parse( diff --git a/src/syntax/content/parser/context/list.rs b/src/syntax/content/parser/context/list.rs index bb36915..e2cce76 100644 --- a/src/syntax/content/parser/context/list.rs +++ b/src/syntax/content/parser/context/list.rs @@ -3,13 +3,9 @@ use std::{iter::Peekable, slice::Iter}; use crate::{ prelude::*, syntax::content::parser::{ - context::Block, - lexeme::Lexeme, - nest, - state::{ListBuffer, State}, - token::{Token, item::Item}, + context::Block, Token, Lexeme, State, state, token::Item, format, }, - types::Graph, + graph::Graph, }; /// Handles open list contexts until a list is fully parsed. @@ -52,7 +48,7 @@ pub fn parse( } if item_candidate.depth.is_some() { // if the current item candidate has a known depth, push it - item_candidate.text = nest(&item_candidate.text, graph); + item_candidate.text = format(&item_candidate.text, graph); candidate.items.push(item_candidate.clone()); } // push list candidate, reset state and exit context @@ -60,11 +56,11 @@ pub fn parse( tokens.push(Token::List(candidate.clone())); state.context.block = Block::None; iterator.next(); - *buffer = ListBuffer::default(); + *buffer = state::ListBuffer::default(); } else if lexeme.match_char('\n') { // found end of item, push it and reset state log!("Accepting item candidate {item_candidate}"); - item_candidate.text = nest(&item_candidate.text, graph); + item_candidate.text = format(&item_candidate.text, graph); candidate.items.push(item_candidate.clone()); *item_candidate = Item::default(); buffer.depth = 0; @@ -86,10 +82,8 @@ pub fn parse( #[cfg(test)] mod tests { use crate::{ - syntax::content::parser::{ - self, context::list::parse, lexeme::Lexeme, state::State, - }, - types::Graph, + syntax::content::parser::{self, context::list::parse, Lexeme, State}, + graph::Graph, }; fn read(input: &str) -> String { diff --git a/src/syntax/content/parser/point.rs b/src/syntax/content/parser/point.rs index 1dab1dd..2895e47 100644 --- a/src/syntax/content/parser/point.rs +++ b/src/syntax/content/parser/point.rs @@ -5,12 +5,8 @@ use crate::{ syntax::content::{ Parseable as _, parser::{ - lexeme::Lexeme, - state::State, - token::{ - Token, bold::Bold, checkbox::CheckBox, oblique::Oblique, - strike::Strike, underline::Underline, - }, + Lexeme, State, Token, + token::{Bold, CheckBox, Oblique, Strike, Underline}, }, }, }; @@ -62,7 +58,7 @@ pub fn parse( #[cfg(test)] mod tests { - use crate::{syntax::content::parser, types::Graph}; + use crate::{syntax::content::parser, graph::Graph}; fn read(input: &str) -> String { parser::read(input, &Graph::default()) diff --git a/src/syntax/content/parser/state.rs b/src/syntax/content/parser/state.rs index bd620c8..7148036 100644 --- a/src/syntax/content/parser/state.rs +++ b/src/syntax/content/parser/state.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::syntax::content::parser::{ context::{Block, Context, Inline}, - token::{anchor::Anchor, item::Item, list::List}, + token::{Anchor, Item, List}, }; #[derive(Clone, Debug)] diff --git a/src/syntax/content/parser/token.rs b/src/syntax/content/parser/token.rs index a6e9124..85ddee5 100644 --- a/src/syntax/content/parser/token.rs +++ b/src/syntax/content/parser/token.rs @@ -12,85 +12,91 @@ pub mod literal; pub mod oblique; pub mod paragraph; pub mod preformat; -pub mod span; pub mod strike; pub mod underline; +pub use { + anchor::Anchor, bold::Bold, checkbox::CheckBox, code::Code, header::Header, + item::Item, linebreak::LineBreak, list::List, literal::Literal, + oblique::Oblique, paragraph::Paragraph, preformat::PreFormat, + strike::Strike, underline::Underline, +}; + #[derive(Debug, Eq, PartialEq, Clone)] pub enum Token { - Anchor(Box), - Bold(bold::Bold), - CheckBox(checkbox::CheckBox), - Code(code::Code), - Strike(strike::Strike), - Header(header::Header), - Item(item::Item), - LineBreak(linebreak::LineBreak), - List(list::List), - Literal(literal::Literal), - Oblique(oblique::Oblique), - Paragraph(paragraph::Paragraph), - PreFormat(preformat::PreFormat), - Underline(underline::Underline), + Anchor(Box), + Bold(Bold), + CheckBox(CheckBox), + Code(Code), + Strike(Strike), + Header(Header), + Item(Item), + LineBreak(LineBreak), + List(List), + Literal(Literal), + Oblique(Oblique), + Paragraph(Paragraph), + PreFormat(PreFormat), + Underline(Underline), } impl Token { pub fn render(&self) -> String { - match *self { - Token::Anchor(ref d) => d.render(), - Token::Bold(ref d) => d.render(), - Token::CheckBox(ref d) => d.render(), - Token::Code(ref d) => d.render(), - Token::Strike(ref d) => d.render(), - Token::Header(ref d) => d.render(), - Token::Item(ref d) => d.render(), - Token::LineBreak(ref d) => d.render(), - Token::List(ref d) => d.render(), - Token::Literal(ref d) => d.render(), - Token::Oblique(ref d) => d.render(), - Token::Paragraph(ref d) => d.render(), - Token::PreFormat(ref d) => d.render(), - Token::Underline(ref d) => d.render(), + match self { + Token::Anchor(d) => d.render(), + Token::Bold(d) => d.render(), + Token::CheckBox(d) => d.render(), + Token::Code(d) => d.render(), + Token::Strike(d) => d.render(), + Token::Header(d) => d.render(), + Token::Item(d) => d.render(), + Token::LineBreak(d) => d.render(), + Token::List(d) => d.render(), + Token::Literal(d) => d.render(), + Token::Oblique(d) => d.render(), + Token::Paragraph(d) => d.render(), + Token::PreFormat(d) => d.render(), + Token::Underline(d) => d.render(), } } pub fn flatten(&self) -> String { - match *self { - Token::Anchor(ref d) => d.flatten(), - Token::Bold(ref d) => d.flatten(), - Token::CheckBox(ref d) => d.flatten(), - Token::Code(ref d) => d.flatten(), - Token::Strike(ref d) => d.flatten(), - Token::Header(ref d) => d.flatten(), - Token::Item(ref d) => d.flatten(), - Token::LineBreak(ref d) => d.flatten(), - Token::List(ref d) => d.flatten(), - Token::Literal(ref d) => d.flatten(), - Token::Oblique(ref d) => d.flatten(), - Token::Paragraph(ref d) => d.flatten(), - Token::PreFormat(ref d) => d.flatten(), - Token::Underline(ref d) => d.flatten(), + match self { + Token::Anchor(d) => d.flatten(), + Token::Bold(d) => d.flatten(), + Token::CheckBox(d) => d.flatten(), + Token::Code(d) => d.flatten(), + Token::Strike(d) => d.flatten(), + Token::Header(d) => d.flatten(), + Token::Item(d) => d.flatten(), + Token::LineBreak(d) => d.flatten(), + Token::List(d) => d.flatten(), + Token::Literal(d) => d.flatten(), + Token::Oblique(d) => d.flatten(), + Token::Paragraph(d) => d.flatten(), + Token::PreFormat(d) => d.flatten(), + Token::Underline(d) => d.flatten(), } } } impl std::fmt::Display for Token { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let data = match *self { - Token::Anchor(ref d) => format!("{d}"), - Token::Bold(ref d) => format!("{d}"), - Token::CheckBox(ref d) => format!("{d}"), - Token::Code(ref d) => format!("{d}"), - Token::Strike(ref d) => format!("{d}"), - Token::Header(ref d) => format!("{d}"), - Token::Item(ref d) => format!("{d}"), - Token::LineBreak(ref d) => format!("{d}"), - Token::List(ref d) => format!("{d}"), - Token::Literal(ref d) => format!("{d}"), - Token::Oblique(ref d) => format!("{d}"), - Token::Paragraph(ref d) => format!("{d}"), - Token::PreFormat(ref d) => format!("{d}"), - Token::Underline(ref d) => format!("{d}"), + let data = match self { + Token::Anchor(d) => format!("{d}"), + Token::Bold(d) => format!("{d}"), + Token::CheckBox(d) => format!("{d}"), + Token::Code(d) => format!("{d}"), + Token::Strike(d) => format!("{d}"), + Token::Header(d) => format!("{d}"), + Token::Item(d) => format!("{d}"), + Token::LineBreak(d) => format!("{d}"), + Token::List(d) => format!("{d}"), + Token::Literal(d) => format!("{d}"), + Token::Oblique(d) => format!("{d}"), + Token::Paragraph(d) => format!("{d}"), + Token::PreFormat(d) => format!("{d}"), + Token::Underline(d) => format!("{d}"), }; write!(f, "Tk:{data}") diff --git a/src/syntax/content/parser/token/anchor.rs b/src/syntax/content/parser/token/anchor.rs index 4569aee..6d9397e 100644 --- a/src/syntax/content/parser/token/anchor.rs +++ b/src/syntax/content/parser/token/anchor.rs @@ -1,6 +1,6 @@ use crate::{ - syntax::content::{Parseable, parser::lexeme::Lexeme}, - types::Node, + syntax::content::{Parseable, parser::Lexeme}, + graph::Node, }; #[derive(Default, Debug, Clone, Eq, PartialEq)] @@ -79,6 +79,10 @@ impl Anchor { self.leading = leading; } + pub fn node(&self) -> Option { + self.node.clone() + } + pub fn set_node(&mut self, node: &Node) { self.node = Some(node.to_owned()); } @@ -87,6 +91,10 @@ impl Anchor { self.node_id.clone() } + pub fn set_node_id(&mut self, id: &str) { + self.node_id = Some(id.to_owned()); + } + fn route(&mut self) { self.destination = if let Some(destination) = self.destination.clone() { if destination.contains(":") || destination.contains("/") { @@ -118,7 +126,7 @@ impl Parseable for Anchor { } fn render(&self) -> String { - let Some(ref destination) = self.destination else { + let Some(destination) = &self.destination else { panic!( "Attempt to render anchor {self:#?} without knowing its destination." ) @@ -162,8 +170,8 @@ impl std::fmt::Display for Anchor { wrapped_text.as_str() }; - let display_destination = match self.destination { - Some(ref destination) => { + let display_destination = match &self.destination { + Some(destination) => { if destination.is_empty() { String::from("") } else { @@ -192,7 +200,7 @@ impl std::fmt::Display for Anchor { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/bold.rs b/src/syntax/content/parser/token/bold.rs index 1003733..e2a7efc 100644 --- a/src/syntax/content/parser/token/bold.rs +++ b/src/syntax/content/parser/token/bold.rs @@ -44,7 +44,7 @@ impl std::fmt::Display for Bold { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/checkbox.rs b/src/syntax/content/parser/token/checkbox.rs index 829996b..a8f410d 100644 --- a/src/syntax/content/parser/token/checkbox.rs +++ b/src/syntax/content/parser/token/checkbox.rs @@ -48,7 +48,7 @@ impl std::fmt::Display for CheckBox { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/code.rs b/src/syntax/content/parser/token/code.rs index 6dcd24b..4aef324 100644 --- a/src/syntax/content/parser/token/code.rs +++ b/src/syntax/content/parser/token/code.rs @@ -44,7 +44,7 @@ impl std::fmt::Display for Code { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/header.rs b/src/syntax/content/parser/token/header.rs index 5c141ae..c9c7661 100644 --- a/src/syntax/content/parser/token/header.rs +++ b/src/syntax/content/parser/token/header.rs @@ -4,7 +4,7 @@ use std::{ use crate::{ prelude::*, - types::Config, + graph::Config, syntax::content::{Parseable, Lexeme}, }; @@ -101,7 +101,7 @@ impl Parseable for Header { fn render(&self) -> String { if let Some(open) = self.open { - if open && let Some(ref id) = self.dom_id { + if open && let Some(id) = &self.dom_id { format!(r#""#, self.level, id) } else if open { format!("", self.level) @@ -126,8 +126,8 @@ impl std::fmt::Display for Header { "unknown" }; - let display_dom_id = match self.dom_id { - Some(ref dom_id) => format!(" DOM ID {dom_id}"), + let display_dom_id = match &self.dom_id { + Some(dom_id) => format!(" DOM ID {dom_id}"), None => String::default(), }; @@ -195,7 +195,7 @@ impl Display for Level { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/item.rs b/src/syntax/content/parser/token/item.rs index 585c981..02b8431 100644 --- a/src/syntax/content/parser/token/item.rs +++ b/src/syntax/content/parser/token/item.rs @@ -50,7 +50,7 @@ impl std::fmt::Display for Item { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/linebreak.rs b/src/syntax/content/parser/token/linebreak.rs index 2ec7123..9026b7c 100644 --- a/src/syntax/content/parser/token/linebreak.rs +++ b/src/syntax/content/parser/token/linebreak.rs @@ -1,5 +1,5 @@ use crate::{ - syntax::content::{Parseable, parser::lexeme::Lexeme}, + syntax::content::{Parseable, parser::Lexeme}, }; #[derive(Default, Debug, Clone, Eq, PartialEq)] @@ -31,7 +31,7 @@ impl std::fmt::Display for LineBreak { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/list.rs b/src/syntax/content/parser/token/list.rs index ece3fd2..721d226 100644 --- a/src/syntax/content/parser/token/list.rs +++ b/src/syntax/content/parser/token/list.rs @@ -1,5 +1,8 @@ use crate::{ - syntax::content::{Lexeme, Parseable, parser::token::item::Item}, + syntax::content::{ + Parseable, + parser::{Lexeme, token::Item}, + }, }; #[derive(Default, Debug, Clone, Eq, PartialEq)] @@ -94,7 +97,7 @@ impl std::fmt::Display for List { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/literal.rs b/src/syntax/content/parser/token/literal.rs index 12ef542..b6af9d8 100644 --- a/src/syntax/content/parser/token/literal.rs +++ b/src/syntax/content/parser/token/literal.rs @@ -1,4 +1,4 @@ -use crate::syntax::content::{Parseable, parser::lexeme::Lexeme}; +use crate::syntax::content::{Parseable, parser::Lexeme}; #[derive(Debug, Clone, Eq, PartialEq)] pub struct Literal { @@ -33,7 +33,7 @@ impl std::fmt::Display for Literal { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/oblique.rs b/src/syntax/content/parser/token/oblique.rs index 49b7cad..320626b 100644 --- a/src/syntax/content/parser/token/oblique.rs +++ b/src/syntax/content/parser/token/oblique.rs @@ -44,7 +44,7 @@ impl std::fmt::Display for Oblique { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/paragraph.rs b/src/syntax/content/parser/token/paragraph.rs index 6b60c16..1c51d87 100644 --- a/src/syntax/content/parser/token/paragraph.rs +++ b/src/syntax/content/parser/token/paragraph.rs @@ -1,4 +1,4 @@ -use crate::syntax::content::{Parseable, parser::lexeme::Lexeme}; +use crate::syntax::content::{Parseable, parser::Lexeme}; #[derive(Debug, Clone, Eq, PartialEq)] pub struct Paragraph { @@ -62,7 +62,7 @@ impl std::fmt::Display for Paragraph { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/preformat.rs b/src/syntax/content/parser/token/preformat.rs index 422f26c..a55995b 100644 --- a/src/syntax/content/parser/token/preformat.rs +++ b/src/syntax/content/parser/token/preformat.rs @@ -54,7 +54,7 @@ impl Parseable for PreFormat { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/strike.rs b/src/syntax/content/parser/token/strike.rs index ced9e7d..c1a925a 100644 --- a/src/syntax/content/parser/token/strike.rs +++ b/src/syntax/content/parser/token/strike.rs @@ -41,7 +41,7 @@ impl std::fmt::Display for Strike { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/content/parser/token/underline.rs b/src/syntax/content/parser/token/underline.rs index 690f94d..a86e4b4 100644 --- a/src/syntax/content/parser/token/underline.rs +++ b/src/syntax/content/parser/token/underline.rs @@ -44,7 +44,7 @@ impl std::fmt::Display for Underline { #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::Token; + use crate::syntax::content::parser::Token; use super::*; diff --git a/src/syntax/serial.rs b/src/syntax/serial.rs index 4cf8e46..0af35a6 100644 --- a/src/syntax/serial.rs +++ b/src/syntax/serial.rs @@ -1,8 +1,14 @@ use std::collections::HashMap; use crate::{ - syntax::{command::Arguments, content::parser::flatten}, - types::{Edge, Graph, Node}, + syntax::{ + command::Arguments, + content::{ + self, + parser::{flatten, Token, token::Anchor}, + }, + }, + graph::{Edge, Graph, Node}, }; pub fn populate_graph() -> Graph { @@ -38,6 +44,10 @@ fn modulate_nodes(graph: &Graph) -> HashMap { let connections = node.connections.clone().unwrap_or_default(); let mut new_edges = connections.clone(); + // Parse node text + let (text, tokens) = content::rich_parse(&node.text, graph); + + // Modulate connections for (i, edge) in connections.iter().enumerate() { let mut new_edge = edge.clone(); @@ -66,6 +76,28 @@ fn modulate_nodes(graph: &Graph) -> HashMap { }); } + // Create connections for each anchor + let parsed_anchors = + tokens.iter().filter(|t| matches!(t, Token::Anchor(_))); + + let mut anchors: Vec = vec![]; + for anchor in parsed_anchors { + if let Token::Anchor(a) = anchor { + anchors.push(*a.clone()); + } + } + + for anchor in anchors { + if let Some(anchor_node) = anchor.node() { + new_edges.push(Edge { + from: key.clone(), + to: anchor_node.id, + anchor: anchor.text(), + detached: false, + }); + } + } + // Populate empty titles with IDs let new_title = if node.title.is_empty() { key.clone() @@ -104,6 +136,7 @@ fn modulate_nodes(graph: &Graph) -> HashMap { title: new_title, summary: flatten(&summary, graph), connections: Some(new_edges), + text, ..node.clone() }; diff --git a/templates/node.html b/templates/node.html index ab1a6d4..8d38268 100644 --- a/templates/node.html +++ b/templates/node.html @@ -11,7 +11,7 @@ {% if node.hidden %}Hidden{% endif %} - {{ text | safe }} + {{ node.text | safe }} {% if node.connections or incoming %}