From dfddbba4ef1f6d894014f841835dcd850086d158 Mon Sep 17 00:00:00 2001 From: jutty Date: Sun, 22 Mar 2026 18:50:15 -0300 Subject: [PATCH] Embed a 'welcome' graph to fallback on --- Cargo.lock | 18 +++--- Cargo.toml | 2 +- src/graph.rs | 103 ++++++++++++++++++++++++++------ src/router/handlers/template.rs | 18 +++--- static/welcome.toml | 17 ++++++ 5 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 static/welcome.toml diff --git a/Cargo.lock b/Cargo.lock index d5037d8..edb0fee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,7 +259,7 @@ dependencies = [ [[package]] name = "en" -version = "0.3.1-alpha" +version = "0.4.0-alpha" dependencies = [ "axum", "serde", @@ -1209,9 +1209,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ "base64", "flate2", @@ -1220,15 +1220,15 @@ dependencies = [ "rustls", "rustls-pki-types", "ureq-proto", - "utf-8", + "utf8-zero", "webpki-roots", ] [[package]] name = "ureq-proto" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" dependencies = [ "base64", "http", @@ -1237,10 +1237,10 @@ dependencies = [ ] [[package]] -name = "utf-8" -version = "0.7.6" +name = "utf8-zero" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" [[package]] name = "version_check" diff --git a/Cargo.toml b/Cargo.toml index d42bdc2..bac9f33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "en" -version = "0.3.1-alpha" +version = "0.4.0-alpha" description = "A non-linear writing instrument." license = "AGPL-3.0-only" diff --git a/src/graph.rs b/src/graph.rs index 7e24eb0..67a32b5 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, io, path::PathBuf}; pub use edge::Edge; pub use meta::{Config, Meta}; @@ -43,6 +43,18 @@ pub struct Stats { } impl Graph { + fn welcome() -> Graph { + let toml = include_str!("../static/welcome.toml"); + let mut welcome_graph = match Graph::from_serial(toml, &Format::TOML) { + Ok(graph) => graph, + Err(error) => { + panic!("Welcome graph parsing must be infallible: {error:?}") + }, + }; + welcome_graph.modulate(); + welcome_graph + } + pub fn with_message(message: &str) -> Graph { let graph = Graph::default(); let mut messages = graph.meta.messages; @@ -69,12 +81,23 @@ impl Graph { /// Loads a Graph TOML file from CLI arguments or their defaults and /// returns a modulated Graph. /// + /// Loads a default graph with basic usage instructions if no file is found. + /// /// Returns a graph with an error message if any errors are propagated. pub fn load() -> Graph { let result = Graph::load_file(None); match result { Ok(graph) => graph, - Err(error) => Graph::malformed(Some(&error)), + Err(error) => { + if error.not_found { + return Graph::welcome() + } + if let Some(message) = error.message { + Graph::malformed(Some(&message)) + } else { + Graph::malformed(None) + } + }, } } @@ -84,7 +107,7 @@ impl Graph { /// /// # Errors /// Propagates errors from `Graph::read_file`. - pub fn load_file(path: Option<&str>) -> Result { + pub fn load_file(path: Option<&str>) -> Result { let mut graph = Graph::from_file(path)?; graph.modulate(); Ok(graph) @@ -95,21 +118,24 @@ impl Graph { /// # Errors /// Returns Err if it can't read the contents of `in_path`. /// Propagates errors from `Graph::from_serial`. - pub fn from_file(in_path: Option<&str>) -> Result { + pub fn from_file(in_path: Option<&str>) -> Result { let cli_path = Arguments::default().parse().graph_path; let path = in_path.map_or(cli_path, PathBuf::from); let toml_source = match std::fs::read_to_string(&path) { Ok(s) => s, - Err(e) => { + Err(error) => { log!( ERROR, - "Error reading path {}: {e}", + "Error reading path {}: {error}", path.as_path().display(), ); - return Err(format!( - "Failed reading file at {}", - path.as_path().display(), + return Err(LoadError::from_io_with_message( + &format!( + "Failed reading file at {}", + path.as_path().display(), + ), + error, )); }, }; @@ -489,12 +515,60 @@ impl Graph { } } +#[derive(Debug)] pub enum Format { TOML, JSON, Unsupported, } +#[derive(Debug)] +pub struct LoadError { + pub message: Option, + pub not_found: bool, + pub io_error: Option, + pub serial_error: Option, +} + +impl LoadError { + fn from_io_with_message(message: &str, io_error: io::Error) -> LoadError { + LoadError { + message: Some(String::from(message)), + not_found: io_error.kind() == io::ErrorKind::NotFound, + io_error: Some(io_error), + serial_error: None, + } + } +} + +impl From for LoadError { + fn from(error: SerialError) -> LoadError { + LoadError { + message: Some(error.message.clone()), + not_found: false, + serial_error: Some(error), + io_error: None, + } + } +} + +impl From for LoadError { + fn from(error: io::Error) -> LoadError { + LoadError { + message: Some(error.to_string()), + not_found: error.kind() == io::ErrorKind::NotFound, + io_error: Some(error), + serial_error: None, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SerialError { + pub cause: SerialErrorCause, + pub message: String, +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub enum SerialErrorCause { UnsupportedFormat, @@ -511,12 +585,6 @@ impl std::fmt::Display for SerialErrorCause { } } -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct SerialError { - pub cause: SerialErrorCause, - pub message: String, -} - impl From for String { fn from(error: SerialError) -> String { format!("{}: {}", error.cause, error.message) @@ -1314,12 +1382,11 @@ mod serial_tests { use crate::dev::test::{Directories, Error}; #[test] - fn bad_graph_path() -> Result<(), Error> { + fn no_graph_fallback() -> Result<(), Error> { let _dirs = Directories::setup("bad_graph_path")?; let graph = Graph::load(); - let message = graph.meta.messages.first().unwrap(); - assert!(message.contains("Failed reading file at")); + assert_eq!(graph.nodes["GettingStarted"].title, "Getting Started"); Ok(()) } diff --git a/src/router/handlers/template.rs b/src/router/handlers/template.rs index 2fd7a83..eab2675 100644 --- a/src/router/handlers/template.rs +++ b/src/router/handlers/template.rs @@ -137,15 +137,14 @@ fn load_templates() -> Result { let root = PathBuf::from("templates"); let default_names: Vec<&str> = DEFAULTS.iter().map(|(n, _)| *n).collect(); - log!( - DEBUG, - "Reading templates from {}, canonical form {:?}", - root.display(), - root.canonicalize() - ); - match fs::read_dir(&root) { Ok(dir) => { + log!( + DEBUG, + "Reading templates from root directory '{}', canonically {:?}", + root.display(), + root.canonicalize() + ); for file_opt in dir { let file = file_opt?; let path = file.path(); @@ -168,6 +167,11 @@ fn load_templates() -> Result { } }, Err(error) => { + log!( + VERBOSE, + "A 'templates' directory was not found or is not accessible: \ + only built-in templates will be available" + ); if error.kind() != ErrorKind::NotFound { return Err(tera::Error::msg(error.to_string())) } diff --git a/static/welcome.toml b/static/welcome.toml new file mode 100644 index 0000000..7ff2d33 --- /dev/null +++ b/static/welcome.toml @@ -0,0 +1,17 @@ +[nodes.GettingStarted] +title = "Getting Started" +text = """ +## Welcome to en! +# +If you are seeing this, it's working! + +Now that you know how to run it, tell en how to find your graph file by adding a `--graph` option: + +` +en --graph my_graph.toml +` + +Alternatively, you can also add a `static` directory next to the en binary with a `graph.toml` file in it. + +To learn how to write your first graph and everything else about en, check out the |documentation|https://en.jutty.dev|. +"""