diff --git a/src/handlers/error.rs b/src/handlers/error.rs
index 94860f2..9b24d54 100644
--- a/src/handlers/error.rs
+++ b/src/handlers/error.rs
@@ -3,7 +3,7 @@ use axum::{
http::{Response, StatusCode, header},
};
-use crate::handlers;
+use crate::{formats::populate_graph, handlers};
pub(in crate::handlers) fn by_code(
code: Option,
@@ -26,6 +26,7 @@ fn make_body(code: Option, message: Option<&str>) -> String {
let out_code = code.unwrap_or(500);
let out_message = &message.unwrap_or("Unknown error");
+ let config = populate_graph().meta.config;
context.insert(
"title",
@@ -36,6 +37,7 @@ fn make_body(code: Option, message: Option<&str>) -> String {
context.insert("message", out_message);
context.insert("status_code", &out_code.to_string());
+ context.insert("config", &config);
handlers::template::render(
"error.html",
diff --git a/src/handlers/graph.rs b/src/handlers/graph.rs
index e69adb3..d919afb 100644
--- a/src/handlers/graph.rs
+++ b/src/handlers/graph.rs
@@ -1,25 +1,25 @@
use axum::{body::Body, extract::Path, http::Response};
+use crate::syntax::content::elements::paragraph::Paragraph;
use crate::syntax::content::parser;
use crate::{formats::populate_graph, handlers, types::Node};
pub async fn node(Path(id): Path) -> Response {
- let mut context = tera::Context::new();
-
let graph = populate_graph();
let empty_node = Node::new(Some(format!("Could not find node ID {id}.")));
-
let node: &Node = graph.nodes.get(&id).unwrap_or(&empty_node);
+ let mut context = tera::Context::new();
context.insert("id", &id);
context.insert("title", &node.title);
context.insert("connections", &node.connections.clone());
context.insert("incoming", &graph.incoming.get(&id));
+ context.insert("config", &graph.meta.config);
let out_text = parser::read::(&node.text);
context.insert("text", &out_text);
- let not_found = node.clone() == empty_node;
+ let not_found = *node == empty_node;
let template_name = "node.html".to_string();
handlers::template::by_filename(
diff --git a/src/handlers/navigation.rs b/src/handlers/navigation.rs
index cdfdec7..bd61f31 100644
--- a/src/handlers/navigation.rs
+++ b/src/handlers/navigation.rs
@@ -5,10 +5,16 @@ use axum::{
Form,
};
-use crate::{formats::populate_graph, types::Node, handlers};
+use crate::{
+ formats::populate_graph,
+ handlers,
+ syntax::content::parser,
+ types::{Config, Node},
+ syntax::content::elements::{span::Span, paragraph::Paragraph},
+};
#[expect(clippy::unused_async)]
-pub async fn nexus(template: &str) -> Response {
+pub async fn page(template: &str) -> Response {
let mut context = tera::Context::new();
let graph = populate_graph();
let root_node = graph.get_root().unwrap_or_default();
@@ -17,6 +23,14 @@ pub async fn nexus(template: &str) -> Response {
context.insert("nodes", &nodes);
context.insert("root_node", &root_node);
+ let text_parsed_config = Config {
+ footer_text: parser::read::(&graph.meta.config.footer_text),
+ about_text: parser::read::(&graph.meta.config.about_text),
+ ..graph.meta.config
+ };
+
+ context.insert("config", &text_parsed_config);
+
handlers::template::by_filename(template, &context, 500, None, false)
}
diff --git a/src/main.rs b/src/main.rs
index d690bf2..2060000 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,6 @@
use std::{backtrace, io, panic};
-use en::{ONSET, syntax, dev};
+use en::{ONSET, dev, formats::populate_graph, syntax};
#[tokio::main]
async fn main() -> io::Result<()> {
@@ -25,7 +25,8 @@ async fn main() -> io::Result<()> {
}
}));
- let app = en::router::new();
+ let graph = populate_graph();
+ let app = en::router::new(&graph);
let listener =
tokio::net::TcpListener::bind(&address).await.map_err(|e| {
diff --git a/src/router.rs b/src/router.rs
index 4357b1d..00ab756 100644
--- a/src/router.rs
+++ b/src/router.rs
@@ -1,22 +1,14 @@
use axum::{routing::get, Router};
-use crate::{handlers, formats::Format};
+use crate::{formats::Format, handlers, types::Graph};
-pub fn new() -> Router {
- Router::new()
+pub fn new(graph: &Graph) -> Router {
+ let mut router = Router::new()
.route(
"/",
- get(|| handlers::navigation::nexus("index.html"))
+ get(|| handlers::navigation::page("index.html"))
.post(handlers::navigation::search),
)
- .route(
- "/graph/toml",
- get(|| handlers::fixed::serial(&Format::Toml)),
- )
- .route(
- "/graph/json",
- get(|| handlers::fixed::serial(&Format::Json)),
- )
.route(
"/static/style.css",
get(|| handlers::fixed::file("./static/style.css", "text/css")),
@@ -31,11 +23,32 @@ pub fn new() -> Router {
"/node/{node_id}",
get(handlers::graph::node).post(handlers::graph::node),
)
- .route("/tree", get(|| handlers::navigation::nexus("tree.html")))
- .route("/about", get(|| handlers::template::fixed("about.html")))
- .route(
- "/acknowledgments",
- get(|| handlers::template::fixed("acknowledgments.html")),
- )
- .fallback(handlers::error::not_found)
+ .fallback(handlers::error::not_found);
+
+ if graph.meta.config.about {
+ router = router
+ .route("/about", get(|| handlers::navigation::page("about.html")));
+ }
+
+ if graph.meta.config.tree {
+ router = router
+ .route("/tree", get(|| handlers::navigation::page("tree.html")));
+ }
+
+ if graph.meta.config.raw {
+ if graph.meta.config.raw_json {
+ router = router.route(
+ "/graph/json",
+ get(|| handlers::fixed::serial(&Format::Json)),
+ );
+ }
+ if graph.meta.config.raw_toml {
+ router = router.route(
+ "/graph/toml",
+ get(|| handlers::fixed::serial(&Format::Toml)),
+ );
+ }
+ }
+
+ router
}
diff --git a/src/syntax/content/elements/header.rs b/src/syntax/content/elements/header.rs
index 9dda6ba..fe01163 100644
--- a/src/syntax/content/elements/header.rs
+++ b/src/syntax/content/elements/header.rs
@@ -26,7 +26,7 @@ impl Display for Level {
}
}
-pub(in crate::syntax::content) struct Header {
+pub struct Header {
level: Level,
text: String,
}
@@ -70,6 +70,7 @@ impl Parseable for Header {
format!("{} ", &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)
diff --git a/src/syntax/content/elements/span.rs b/src/syntax/content/elements/span.rs
new file mode 100644
index 0000000..c375f8d
--- /dev/null
+++ b/src/syntax/content/elements/span.rs
@@ -0,0 +1,28 @@
+use std::fmt::Display;
+use crate::syntax::content::{Parseable, Lexeme};
+
+pub struct Span {
+ text: String,
+}
+
+impl Parseable for Span {
+ fn probe(lexeme: &Lexeme) -> bool {
+ !lexeme.raw.trim().is_empty()
+ }
+
+ fn lex(lexeme: &Lexeme) -> Self {
+ Self {
+ text: lexeme.raw.trim().to_owned(),
+ }
+ }
+
+ fn render(&self) -> String {
+ format!("{} ", &self.text)
+ }
+}
+
+impl Display for Span {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Span: <{}>", &self.text)
+ }
+}
diff --git a/src/types.rs b/src/types.rs
index 11b871f..35f1aeb 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -6,10 +6,10 @@ use serde::{Serialize, Deserialize};
pub struct Graph {
pub nodes: HashMap,
pub root_node: String,
- #[serde(default)]
- pub messages: Vec,
#[serde(skip_deserializing)]
pub incoming: HashMap>,
+ #[serde(default)]
+ pub meta: Meta,
}
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
@@ -37,16 +37,87 @@ pub struct Edge {
pub detached: bool,
}
+#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
+pub struct Meta {
+ pub config: Config,
+ #[serde(default = "mkversion")]
+ pub version: (u8, u8, u8),
+ #[serde(default)]
+ pub messages: Vec,
+}
+
+// See: https://github.com/serde-rs/serde/issues/368
+fn mkversion() -> (u8, u8, u8) {
+ (0, 0, 0)
+}
+
+#[expect(clippy::struct_excessive_bools)]
+#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
+pub struct Config {
+ #[serde(default)]
+ pub site_title: String,
+ #[serde(default)]
+ pub site_description: String,
+ #[serde(default = "mktrue")]
+ pub footer: bool,
+ #[serde(default = "mktrue")]
+ pub footer_credits: bool,
+ #[serde(default = "mktrue")]
+ pub footer_date: bool,
+ #[serde(default)]
+ pub footer_text: String,
+ #[serde(default = "mktrue")]
+ pub about: bool,
+ #[serde(default)]
+ pub about_text: String,
+ #[serde(default = "mktrue")]
+ pub tree: bool,
+ #[serde(default = "mktrue")]
+ pub raw: bool,
+ #[serde(default = "mktrue")]
+ pub raw_toml: bool,
+ #[serde(default = "mktrue")]
+ pub raw_json: bool,
+ #[serde(default = "mktrue")]
+ pub index_search: bool,
+ #[serde(default = "mktrue")]
+ pub index_node_list: bool,
+ #[serde(default = "mktrue")]
+ pub tree_node_text: bool,
+}
+
+// See: https://github.com/serde-rs/serde/issues/368
+fn mktrue() -> bool {
+ true
+}
+
impl Graph {
pub fn new(message: Option) -> Graph {
Self {
nodes: HashMap::new(),
root_node: "VoidNode".to_string(),
incoming: HashMap::new(),
- messages: vec![
- message
- .unwrap_or("This graph is empty or in error".to_string()),
- ],
+ meta: Meta {
+ config: Config {
+ site_title: String::new(),
+ site_description: String::new(),
+ footer: true,
+ footer_credits: true,
+ footer_date: true,
+ footer_text: String::new(),
+ about: true,
+ about_text: String::new(),
+ tree: true,
+ raw: true,
+ raw_toml: true,
+ raw_json: true,
+ index_search: true,
+ index_node_list: true,
+ tree_node_text: true,
+ },
+ version: (0, 1, 0),
+ messages: message.map_or(vec![], |m| vec![m]),
+ },
}
}
diff --git a/static/graph.toml b/static/graph.toml
index f0d6c30..aeea612 100644
--- a/static/graph.toml
+++ b/static/graph.toml
@@ -122,3 +122,23 @@ To learn more about TOML, you can visit its website at .
To see the TOML declaration that translates into the rendered graph you are reading right now, visit the "TOML Graph" link on the top navigation bar.
"""
+
+[nodes.Acknowledgments]
+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/)
+"""
+
+[meta.config]
+footer_credits = false
+footer_text = """
+made by jutty • acknowledgements • source code
+"""
+
+
diff --git a/templates/about.html b/templates/about.html
index e65702f..25a1397 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -5,6 +5,9 @@
{%- block body %}
About
+{% if config.about_text %}
+{{ config.about_text | safe }}
+{% else %}
en is a program to create a connected collection of texts.
You define your graph using a plain-text configuration file, en reads this file and generates a website like the one you are browsing right now.
@@ -17,5 +20,6 @@
Documentation
Source code repository
+{% endif %}
{%- endblock body %}
diff --git a/templates/acknowledgments.html b/templates/acknowledgments.html
deleted file mode 100644
index 1a1e218..0000000
--- a/templates/acknowledgments.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Acknowledgments{% endblock title %}
-
-{%- block body %}
-Acknowledgments
-
-en is only possible thanks to a number of projects and people:
-
-
-
-{%- endblock body %}
diff --git a/templates/base.html b/templates/base.html
index b7b42d8..0f3b4b7 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,7 +1,11 @@
+ {% if config.site_title %}
+ {% block title %}{% endblock title %} • {{config.site_title}}
+ {% else %}
{% block title %}{% endblock title %} • en
+ {% endif %}
@@ -13,10 +17,20 @@
Index
+ {% if config.about %}
About
+ {% endif %}
+ {% if config.tree %}
Tree
+ {% endif %}
+ {% if config.raw %}
+ {% if config.raw_toml %}
TOML Graph
+ {% endif %}
+ {% if config.raw_json %}
JSON Graph
+ {% endif %}
+ {% endif %}
@@ -24,22 +38,30 @@
{% block body %}
{% endblock body %}
+ {% if config.footer %}
-
made by jutty
- •
-
acknowledgments
- •
-
source code
+ {% if config.footer_text %}{{ config.footer_text | safe }}{% endif %}
+ {% if config.footer_text and (config.footer_credits or config.footer_date) %}
+ {% endif %}
+ {% if config.footer_credits %}
+
made with en , a non-linear writing instrument
+ {% endif %}
+ {% if config.footer_credits and config.footer_date %}
+
+ {% endif %}
+ {% if config.footer_date %}
built
{{ now(utc=true) | date(format="%Y-%m-%d %H:%M") }} UTC
•
{{ now(timestamp=true) }} Unix Epoch
+ {% endif %}
+ {% endif %}
diff --git a/templates/empty.html b/templates/empty.html
index 3bcea75..9dd335d 100644
--- a/templates/empty.html
+++ b/templates/empty.html
@@ -1,2 +1,14 @@
There are no nodes. The graph is either empty or failed to parse.
-Check the raw endpoints for possible parsing errors.
+{% if config.raw %}
+Check the
+ {% if config.raw_toml %}
+
+ {% elif config.raw_json %}
+
+ {% endif %}
+ raw endpoints
+ {% if config.raw_toml or config.raw_json %}
+
+ {% endif %}
+ for possible parsing errors.
+{% endif %}
diff --git a/templates/error.html b/templates/error.html
index 971cbfe..4bf2617 100644
--- a/templates/error.html
+++ b/templates/error.html
@@ -11,7 +11,7 @@
fallen
out of the circle
you are welcome to climb
- back onto the tree
+ back onto the {% if config.tree %}tree {% else %}tree{% endif %}
diff --git a/templates/index.html b/templates/index.html
index 76b32c3..0a5e945 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -3,15 +3,24 @@
{% block title %}Index{% endblock title %}
{%- block body %}
- en
+ {%if config.site_title %}{{ config.site_title }}{% else %}en{%endif%}
- A non-linear writing instrument.
+
+ {% if config.site_description %}
+ {{config.site_description}}
+ {% else %}
+ A non-linear writing instrument.
+ {% endif %}
+
{% if nodes %}
+ {% if config.index_search %}
+ {% endif %}
+ {% if config.index_node_list %}
Nodes
@@ -31,6 +40,7 @@
{% endif %}
+ {% endif %}
{% else %}
{% include "empty.html" %}
diff --git a/templates/tree.html b/templates/tree.html
index 6c5e135..1f4f26e 100644
--- a/templates/tree.html
+++ b/templates/tree.html
@@ -2,6 +2,9 @@
{% block title %}Tree{% endblock title %}
+
+
+
{%- block body %}
{% if nodes or root_node %}
Tree
@@ -12,7 +15,9 @@
{{root_node.title}}
+ {% if root_node.connections or config.tree_node_text %}
+ {% if config.tree_node_text %}
Text:
- {% if root_node.connections %}
- Connections
-
-
- {% endif %}
+ {% endif %}
+ {% if root_node.connections %}
+ {% if config.tree_node_text %}Connections
+ {% endif %}
+ {% for connection in root_node.connections %}
+ {{connection.to}}
+ {% endfor %}
+ {% if config.tree_node_text %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
{% endif %}
@@ -44,7 +51,9 @@
{% for node in nodes %}
{{node.title}}
+ {% if node.connections or config.tree_node_text %}
+ {% if config.tree_node_text %}
Text:
- {% if node.connections %}
- Connections
-
- {% for connection in node.connections %}
- {% if not connection.detached %}
- {{connection.to}}
{% endif %}
- {% endfor %}
-
-
- {% endif %}
+ {% if node.connections %}
+ {% if config.tree_node_text %}Connections
+ {% endif %}
+ {% for connection in node.connections %}
+ {% if not connection.detached %}
+ {{connection.to}}
+ {% endif %}
+ {% endfor %}
+ {% if config.tree_node_text %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
{% endfor %}
{% endif %}