diff --git a/.justfile b/.justfile index f574647..66e420b 100644 --- a/.justfile +++ b/.justfile @@ -22,6 +22,14 @@ run-watch: alias w := run-watch +# Apply basic assessments, build and run on changes +[group: 'develop'] +assess-run-watch extra='false': + {{ watch_cmd }} {{ just_cmd }} lint test \ + {{ if extra == "cover" { "cover-assess" } else { "" } }} run + +alias aw := assess-run-watch + # Format all files [group: 'develop'] format: @@ -207,8 +215,8 @@ alias fb := full-build ## META -[default] -_default: +[default, private] +default: @just --list --unsorted --justfile {{justfile()}} export RUSTFLAGS := "-Dwarnings" diff --git a/Cargo.lock b/Cargo.lock index 4ce3186..8423a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,9 +73,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -529,9 +529,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" @@ -740,9 +740,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -872,9 +872,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -917,9 +917,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", @@ -1571,6 +1571,6 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zmij" -version = "0.1.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a" +checksum = "e6d6085d62852e35540689d1f97ad663e3971fc19cf5eceab364d62c646ea167" diff --git a/Cargo.toml b/Cargo.toml index 4164629..cea89ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ keyword-idents = "warn" [lints.clippy] # levels: allow, warn, deny, forbid +manual_non_exhaustive = "allow" + # pedantic assigning_clones = "warn" borrow_as_ptr = "warn" @@ -63,7 +65,6 @@ explicit_iter_loop = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp = "warn" -fn_params_excessive_bools = "warn" format_push_string = "warn" from_iter_instead_of_collect = "warn" if_not_else = "warn" @@ -126,7 +127,6 @@ semicolon_if_nothing_returned = "warn" should_panic_without_expect = "warn" similar_names = "warn" str_split_at_newline = "warn" -struct_excessive_bools = "warn" struct_field_names = "warn" trivially_copy_pass_by_ref = "warn" unchecked_time_subtraction = "warn" diff --git a/src/router.rs b/src/router.rs index e7aa60c..d3da447 100644 --- a/src/router.rs +++ b/src/router.rs @@ -75,7 +75,7 @@ mod tests { let graph = Graph { meta: Meta { config: config - .map(|c| c.to_owned()) + .map(std::borrow::ToOwned::to_owned) .unwrap_or(default_graph.meta.config), ..default_graph.meta }, @@ -121,10 +121,8 @@ mod tests { #[tokio::test] async fn no_about_page() { - let config = Config { - about: false, - ..populate_graph().meta.config - }; + let mut config = Config::new(); + config.about = false; let response = request("/about", Some(&config)).await; assert_eq!(response.status(), StatusCode::NOT_FOUND); @@ -132,10 +130,8 @@ mod tests { #[tokio::test] async fn no_tree_page() { - let config = Config { - tree: false, - ..populate_graph().meta.config - }; + let mut config = Config::new(); + config.tree = false; let response = request("/tree", Some(&config)).await; assert_eq!(response.status(), StatusCode::NOT_FOUND); @@ -143,10 +139,8 @@ mod tests { #[tokio::test] async fn no_toml_raw_graph() { - let config = Config { - raw_toml: false, - ..populate_graph().meta.config - }; + let mut config = Config::new(); + config.raw_toml = false; let response = request("/graph/toml", Some(&config)).await; assert_eq!(response.status(), StatusCode::NOT_FOUND); @@ -154,10 +148,8 @@ mod tests { #[tokio::test] async fn no_json_raw_graph() { - let config = Config { - raw_json: false, - ..populate_graph().meta.config - }; + let mut config = Config::new(); + config.raw_json = false; let response = request("/graph/json", Some(&config)).await; assert_eq!(response.status(), StatusCode::NOT_FOUND); @@ -165,10 +157,8 @@ mod tests { #[tokio::test] async fn no_raw_graph() { - let config = Config { - raw: false, - ..populate_graph().meta.config - }; + let mut config = Config::new(); + config.raw = false; let toml_response = request("/graph/toml", Some(&config)).await; assert_eq!(toml_response.status(), StatusCode::NOT_FOUND); diff --git a/src/router/handlers/graph.rs b/src/router/handlers/graph.rs index 7ce2858..2fb9a58 100644 --- a/src/router/handlers/graph.rs +++ b/src/router/handlers/graph.rs @@ -19,9 +19,9 @@ pub async fn node(Path(id): Path) -> Response { let mut context = tera::Context::new(); context.insert("node", &node); - context.insert("text", &content::parse(&node.text)); + context.insert("text", &content::parse(&node.text, &graph.meta.config)); context.insert("incoming", &graph.incoming.get(&id)); - context.insert("config", &graph.meta.config.parse_text()); + context.insert("config", &graph.meta.config); let not_found = node == empty_node; diff --git a/src/router/handlers/navigation.rs b/src/router/handlers/navigation.rs index f3a2602..4f1bb7c 100644 --- a/src/router/handlers/navigation.rs +++ b/src/router/handlers/navigation.rs @@ -16,7 +16,7 @@ pub async fn page(template: &str) -> Response { context.insert("nodes", &nodes); context.insert("root_node", &root_node); - context.insert("config", &graph.meta.config.parse_text()); + context.insert("config", &graph.meta.config); handlers::template::by_filename(template, &context, 500, None, false) } diff --git a/src/router/handlers/template.rs b/src/router/handlers/template.rs index 3b221ef..f5d2a0c 100644 --- a/src/router/handlers/template.rs +++ b/src/router/handlers/template.rs @@ -145,9 +145,12 @@ mod tests { let node = crate::types::Node::new(Some(payload.to_string())); let graph = crate::syntax::serial::populate_graph(); context.insert("node", &node); - context.insert("text", &crate::syntax::content::parse(&node.text)); + context.insert( + "text", + &crate::syntax::content::parse(&node.text, &graph.meta.config), + ); context.insert("incoming", &graph.incoming.get(&node.id)); - context.insert("config", &graph.meta.config.parse_text()); + context.insert("config", &graph.meta.config); let (body, status) = render("node.html", &context, None); assert_eq!(status, 200); assert!(body.matches(payload).count() == 1); diff --git a/src/syntax/content.rs b/src/syntax/content.rs index 353af63..1f912a6 100644 --- a/src/syntax/content.rs +++ b/src/syntax/content.rs @@ -1,5 +1,7 @@ use parser::{token::Token, lexeme::Lexeme}; +use crate::types::Config; + pub mod parser; pub trait Parseable { @@ -12,6 +14,9 @@ type Probe = fn(&Lexeme) -> bool; type Lexer = fn(&Lexeme) -> Token; type LexMap<'lm> = &'lm [(Probe, Lexer)]; -pub fn parse(text: &str) -> String { - parser::read(text) +pub fn parse(text: &str, config: &Config) -> String { + if text.is_empty() { + return String::new(); + } + parser::read(text, config) } diff --git a/src/syntax/content/parser.rs b/src/syntax/content/parser.rs index a57b9ce..37a8abc 100644 --- a/src/syntax/content/parser.rs +++ b/src/syntax/content/parser.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap}; -use crate::{syntax::serial::populate_graph, types::Config}; +use crate::types::Config; use super::{Parseable as _, Token, LexMap}; use token::{ anchor::Anchor, linebreak::LineBreak, paragraph::Paragraph, header::Header, @@ -19,10 +19,9 @@ const LEXMAP: LexMap = &[ (Literal::probe, |word| Token::Literal(Literal::lex(word))), ]; -fn lex(text: &str, map: LexMap) -> Vec { +fn lex(text: &str, map: LexMap, config: &Config) -> Vec { let mut tokens: Vec = Vec::new(); let mut state = State::new(); - let config: Config = populate_graph().meta.config; let segments = segment::segment(text); let lexemes = Lexeme::collect(&segments); @@ -38,7 +37,7 @@ fn lex(text: &str, map: LexMap) -> Vec { } else if Header::probe(lexeme) { let mut header = Header::lex(lexeme); header.dom_id = Some(Header::make_id( - &config, + config, iterator.peek().map_or(&Lexeme::new("", ""), |l| l), &mut state.dom_ids, )); @@ -247,19 +246,23 @@ fn parse(tokens: &[Token]) -> String { tokens.iter().map(Token::render).collect::() } -pub(super) fn read(text: &str) -> String { - parse(&lex(text, LEXMAP)) +pub(super) fn read(text: &str, config: &Config) -> String { + parse(&lex(text, LEXMAP, config)) } #[cfg(test)] mod tests { - use crate::syntax::content::parser::token::header::Level; + use crate::{types::Graph, syntax::content::parser::token::header::Level}; use super::*; + fn read_noconfig(input: &str) -> String { + read(input, &Graph::new(None).meta.config) + } + #[test] fn empty_render_is_empty() { - assert_eq!(read(""), ""); + assert_eq!(read_noconfig(""), ""); } #[test] @@ -267,18 +270,21 @@ mod tests { let en = "`this |test|` tries ## to |brea|k|: things"; let html = r#"

this |test| tries ## to brea: things

"#; - assert_eq!(read(en), html); + assert_eq!(read_noconfig(en), html); } #[test] fn force_flanking() { - assert_eq!(read("|Node||"), r#"

Node

"#); + assert_eq!( + read_noconfig("|Node||"), + r#"

Node

"# + ); } #[test] fn flanking_with_trailing_pipe() { assert_eq!( - read("|Node|Destination|"), + read_noconfig("|Node|Destination|"), r#"

Node

"# ); } @@ -286,7 +292,7 @@ mod tests { #[test] fn nonleading_second_pipe() { assert_eq!( - read("Go to Node|Destination|, here"), + read_noconfig("Go to Node|Destination|, here"), r#"

Go to Node, here

"#, ); } @@ -294,7 +300,7 @@ mod tests { #[test] fn clear_anchor_buffer() { assert_eq!( - read("|SomeAnchor|\n|SomeOtherAnchor|"), + read_noconfig("|SomeAnchor|\n|SomeOtherAnchor|"), concat!( r#"

SomeAnchor

"#, "\n", diff --git a/src/syntax/content/parser/token/header.rs b/src/syntax/content/parser/token/header.rs index 72be7e6..5c067c0 100644 --- a/src/syntax/content/parser/token/header.rs +++ b/src/syntax/content/parser/token/header.rs @@ -182,10 +182,8 @@ mod tests { #[test] fn ascii_ids_set() { - let config = Config { - ascii_dom_ids: true, - ..Config::default() - }; + let mut config = Config::new(); + config.ascii_dom_ids = true; let id = Header::make_id( &config, @@ -197,10 +195,8 @@ mod tests { #[test] fn ascii_ids_unset() { - let config = Config { - ascii_dom_ids: false, - ..Config::default() - }; + let mut config = Config::new(); + config.ascii_dom_ids = false; let id = Header::make_id( &config, diff --git a/src/syntax/serial.rs b/src/syntax/serial.rs index 571dacb..c22dddc 100644 --- a/src/syntax/serial.rs +++ b/src/syntax/serial.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{ syntax::command::Arguments, - types::{Edge, Graph, Node}, + types::{Edge, Graph, Meta, Node}, }; pub fn populate_graph() -> Graph { @@ -11,29 +11,26 @@ pub fn populate_graph() -> Graph { Ok(s) => s, Err(e) => format!("Error: {e}"), }; - let graph = deserialize_graph(&Format::TOML, &toml_source); + let graph = deserialize_graph(&Format::TOML, &toml_source); + modulate_graph(&graph) +} + +fn modulate_graph(graph: &Graph) -> Graph { let nodes = modulate_nodes(&graph.nodes); Graph { - nodes: nodes.clone(), incoming: make_incoming(&nodes), lowercase_keymap: map_lowercase_keys(&nodes), - ..graph + nodes, + meta: Meta { + config: graph.meta.config.clone().parse_text(), + ..graph.meta.clone() + }, + ..graph.to_owned() } } -fn map_lowercase_keys( - source_map: &HashMap, -) -> HashMap { - let mut out_map: HashMap = HashMap::new(); - let keys = source_map.keys(); - for key in keys { - out_map.insert(key.clone().to_lowercase(), key.clone()); - } - out_map -} - fn modulate_nodes(old_nodes: &HashMap) -> HashMap { let mut nodes: HashMap = HashMap::new(); @@ -89,23 +86,6 @@ fn modulate_nodes(old_nodes: &HashMap) -> HashMap { nodes } -// Construct a HashMap with incoming connections (reversed edges) -fn make_incoming(nodes: &HashMap) -> HashMap> { - let mut incoming: HashMap> = HashMap::new(); - - for node in nodes.clone().into_values() { - let empty_vec: Vec = vec![]; - for edge in &node.connections.clone().unwrap_or_default() { - let mut edges = - incoming.get(&edge.to.clone()).unwrap_or(&empty_vec).clone(); - edges.extend_from_slice(std::slice::from_ref(edge)); - incoming.insert(edge.to.clone(), edges.clone()); - } - } - - incoming -} - pub enum Format { TOML, JSON, @@ -137,6 +117,34 @@ pub fn deserialize_graph(in_format: &Format, serial: &str) -> Graph { } } +// Construct a HashMap with incoming connections (reversed edges) +fn make_incoming(nodes: &HashMap) -> HashMap> { + let mut incoming: HashMap> = HashMap::new(); + + for node in nodes.clone().into_values() { + let empty_vec: Vec = vec![]; + for edge in &node.connections.clone().unwrap_or_default() { + let mut edges = + incoming.get(&edge.to.clone()).unwrap_or(&empty_vec).clone(); + edges.extend_from_slice(std::slice::from_ref(edge)); + incoming.insert(edge.to.clone(), edges.clone()); + } + } + + incoming +} + +fn map_lowercase_keys( + source_map: &HashMap, +) -> HashMap { + let mut out_map: HashMap = HashMap::new(); + let keys = source_map.keys(); + for key in keys { + out_map.insert(key.clone().to_lowercase(), key.clone()); + } + out_map +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/types.rs b/src/types.rs index 00d78e6..b760643 100644 --- a/src/types.rs +++ b/src/types.rs @@ -57,9 +57,10 @@ 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)] + _private: bool, #[serde(default)] pub site_title: String, #[serde(default)] @@ -119,27 +120,7 @@ impl Graph { incoming: HashMap::new(), lowercase_keymap: HashMap::new(), 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, - index_node_count: 8, - index_root_node: true, - tree_node_text: false, - ascii_dom_ids: false, - content_language: String::new(), - }, + config: Config::new(), version: (0, 1, 0), messages: message.map_or(vec![], |m| vec![m.to_string()]), }, @@ -177,23 +158,36 @@ impl Node { } impl Config { + pub fn new() -> Config { + Config { + _private: true, + 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, + index_node_count: 8, + index_root_node: true, + tree_node_text: false, + ascii_dom_ids: false, + content_language: String::new(), + } + } + #[must_use] pub fn parse_text(self) -> Config { - let footer_text = if self.footer_text.is_empty() { - self.footer_text - } else { - content::parse(&self.footer_text) - }; - - let about_text = if self.about_text.is_empty() { - self.about_text - } else { - content::parse(&self.about_text) - }; - Config { - footer_text, - about_text, + footer_text: content::parse(&self.footer_text, &self), + about_text: content::parse(&self.about_text, &self), ..self } }