Break up 'types' module

This commit is contained in:
Juno Takano 2026-01-11 21:31:51 -03:00
commit db8c02df04
36 changed files with 382 additions and 329 deletions

109
src/graph.rs Normal file
View file

@ -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<String, Node>,
pub root_node: String,
#[serde(skip_deserializing)]
pub incoming: HashMap<String, Vec<Edge>>,
#[serde(skip_deserializing)]
pub lowercase_keymap: HashMap<String, String>,
#[serde(default)]
pub meta: Meta,
}
#[derive(Clone)]
pub struct QueryResult {
pub node: Option<Node>,
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<Node> {
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"
);
}
}

12
src/graph/edge.rs Normal file
View file

@ -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,
}

View file

@ -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<String, Node>,
pub root_node: String,
#[serde(skip_deserializing)]
pub incoming: HashMap<String, Vec<Edge>>,
#[serde(skip_deserializing)]
pub lowercase_keymap: HashMap<String, String>,
#[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<String>,
#[serde(default)]
pub redirect: String,
#[serde(default)]
pub hidden: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub connections: Option<Vec<Edge>>,
}
#[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<String>,
}
// 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<Node>,
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<Node> {
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<String>) -> 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() {

53
src/graph/node.rs Normal file
View file

@ -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<String>,
#[serde(default)]
pub redirect: String,
#[serde(default)]
pub hidden: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub connections: Option<Vec<Edge>>,
}
impl Node {
pub fn new(message: Option<String>) -> 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.");
}
}

View file

@ -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;

View file

@ -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::*;

View file

@ -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<String>) -> Response<Body> {
let graph = populate_graph();
@ -29,7 +27,6 @@ pub async fn node(Path(id): Path<String>) -> Response<Body> {
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);

View file

@ -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<Body> {

View file

@ -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

View file

@ -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<Token>) {
parser::rich_read(text, graph)
}

View file

@ -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<Token> {
let mut tokens: Vec<Token> = 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<Token> {
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<Token>) {
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},
};

View file

@ -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<Token>) {
#[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")]

View file

@ -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())

View file

@ -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 {

View file

@ -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(

View file

@ -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 {

View file

@ -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())

View file

@ -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)]

View file

@ -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<anchor::Anchor>),
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<Anchor>),
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}")

View file

@ -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<Node> {
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("<empty>")
} 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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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#"<h{} id="{}">"#, self.level, id)
} else if open {
format!("<h{}>", 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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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<String, Node> {
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<String, Node> {
});
}
// Create connections for each anchor
let parsed_anchors =
tokens.iter().filter(|t| matches!(t, Token::Anchor(_)));
let mut anchors: Vec<Anchor> = 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<String, Node> {
title: new_title,
summary: flatten(&summary, graph),
connections: Some(new_edges),
text,
..node.clone()
};