Add parser for node text with support for headers

This commit is contained in:
Juno Takano 2025-12-15 19:37:35 -03:00
commit 2f247f477b
5 changed files with 69 additions and 9 deletions

View file

@ -12,10 +12,13 @@ pub async fn node(Path(id): Path<String>) -> Response<Body> {
context.insert("id", &id);
context.insert("title", &node.title);
context.insert("text", &node.text);
context.insert("connections", &node.connections.clone());
context.insert("incoming", &graph.incoming.get(&id));
let escaped_text = tera::escape_html(&node.text);
let out_text = crate::syntax::content::parse(&escaped_text);
context.insert("text", &out_text);
let not_found = node.clone() == empty_node;
let template_name = "node.html".to_string();

View file

@ -1 +1,2 @@
pub mod arguments;
pub mod content;

57
src/syntax/content.rs Normal file
View file

@ -0,0 +1,57 @@
use std::fmt::Write as _;
use crate::dev::log;
pub fn parse(text: &str) -> String {
let mut out_text: Vec<String> = Vec::new();
for line in text.lines() {
if line.is_empty() || line.replace(" ", "").is_empty() {
continue;
}
let mut out_line: String = line.to_owned();
let words: Vec<String> = line.split(" ").map(str::to_string).collect();
let first_word: &String =
words.first().unwrap_or_else(|| unreachable!());
if is_header(first_word) {
out_line = parse_header(&out_line, first_word);
}
// if not special, default to treating line as a paragraph
else {
out_line.insert_str(0, "<p>");
out_line.push_str("</p>");
}
out_text.push(out_line);
}
out_text.join("\n")
}
fn is_header(lexeme: &str) -> bool {
!lexeme.trim().is_empty()
&& lexeme.replace("#", "").is_empty()
&& lexeme.len() <= 6
}
fn parse_header(line: &str, first_word: &str) -> String {
log(&parse_header, &format!("Parsing: {line:?}"));
let header_level = first_word.len();
log(&parse, &format!("Header level is {header_level}"));
let header_text = line.to_owned().replace(first_word, "");
let mut w = String::with_capacity(header_text.len().strict_add(9));
let alloc = w.capacity();
match write!(w, "<h{header_level}>{header_text}</h{header_level}>") {
Ok(()) => (),
Err(e) => panic!("{e:?}"),
}
if alloc != w.capacity() {
log(
&parse_header,
&format!("w reallocated to {} despite prediction", w.capacity()),
);
}
w
}

View file

@ -3,7 +3,7 @@ root_node = "Documentation"
[nodes.Documentation]
text = """
Installation
## Installation
For now, if you want to try en, you must build it yourself.
@ -15,7 +15,7 @@ cargo build --release
The en binary will be in target/release/en.
Graph Syntax
## Graph Syntax
The graph is a TOML file. You can create nodes by adding text such as:
@ -31,7 +31,8 @@ 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:
To add a simple connection without any associated properties, you can simply
add links:
[nodes.Quark]
text = "A subatomic particle that forms hadrons."
@ -40,7 +41,7 @@ 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:
If you want to add properties to the connection, you can use the connection syntax:
[[nodes.Quark.connections]]
to = "Particle physics"
@ -48,7 +49,7 @@ 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.
CLI Options
## CLI Options
You can set the hostname, port and graph file path using CLI options:

View file

@ -6,9 +6,7 @@
<section>
<h1>{{ title }}</h1>
<code style="display: inline;">ID: {{ id }}</code>
{% for line in text | split(pat="\n") %}
{% if line %} <p>{{ line }}</p> {% endif %}
{% endfor %}
{{ text | safe }}
</section>
{% if connections or incoming %}
<aside>