Add configuration options

This commit is contained in:
Juno Takano 2025-12-16 19:02:22 -03:00
commit 270fed54f0
16 changed files with 272 additions and 83 deletions

View file

@ -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<u16>,
@ -26,6 +26,7 @@ fn make_body(code: Option<u16>, 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<u16>, 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",

View file

@ -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<String>) -> Response<Body> {
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::<Paragraph>(&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(

View file

@ -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<Body> {
pub async fn page(template: &str) -> Response<Body> {
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<Body> {
context.insert("nodes", &nodes);
context.insert("root_node", &root_node);
let text_parsed_config = Config {
footer_text: parser::read::<Span>(&graph.meta.config.footer_text),
about_text: parser::read::<Paragraph>(&graph.meta.config.about_text),
..graph.meta.config
};
context.insert("config", &text_parsed_config);
handlers::template::by_filename(template, &context, 500, None, false)
}

View file

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

View file

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

View file

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

View file

@ -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!("<span>{}</span>", &self.text)
}
}
impl Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Span: <{}>", &self.text)
}
}

View file

@ -6,10 +6,10 @@ use serde::{Serialize, Deserialize};
pub struct Graph {
pub nodes: HashMap<String, Node>,
pub root_node: String,
#[serde(default)]
pub messages: Vec<String>,
#[serde(skip_deserializing)]
pub incoming: HashMap<String, Vec<Edge>>,
#[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<String>,
}
// 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<String>) -> 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]),
},
}
}