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

    + {% 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:
        • @@ -24,16 +29,18 @@
        - {% if root_node.connections %} -
      • Connections -
          - {% for connection in root_node.connections %} -
        • {{connection.to}}
        • - {% endfor %} -
        -
      • - {% 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:
      • @@ -56,18 +65,20 @@
      - {% 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 %}