diff --git a/Cargo.lock b/Cargo.lock index 0900e11..5979efa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,12 +66,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "anyhow" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" - [[package]] name = "arrayvec" version = "0.5.1" @@ -263,6 +257,32 @@ dependencies = [ "bitflags", ] +[[package]] +name = "color-eyre" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ba9b5e817f1bc1f2219b5a3474b69f838321b2a2ba8860d6a71c3bfe3d0fc1" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a99aa4aa18448eef4c7d3f86d2720d2d8cad5c860fe9ff9b279293efdc8f5be" +dependencies = [ + "ansi_term", + "tracing-core", + "tracing-error", +] + [[package]] name = "comrak" version = "0.8.2" @@ -433,6 +453,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "eyre" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f9683839e579a53258d377fcc0073ca0bf2042ac5e6c60a598069e64403a6d" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -758,6 +788,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0bd112d44d9d870a6819eb505d04dd92b5e4d94bb8c304924a0872ae7016fb5" + [[package]] name = "indexmap" version = "1.5.0" @@ -1139,6 +1175,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "owo-colors" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1250cdd103eef6bd542b5ae82989f931fc00a41a27f60377338241594410f3" + [[package]] name = "parking_lot" version = "0.11.0" @@ -1810,6 +1852,15 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sharded-slab" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.0.0" @@ -2033,9 +2084,21 @@ checksum = "dbdf4ccd1652592b01286a5dbe1e2a77d78afaa34beadd9872a5f7396f92aaa9" dependencies = [ "cfg-if", "log", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.11" @@ -2045,6 +2108,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tracing-error" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-futures" version = "0.2.4" @@ -2055,6 +2128,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-subscriber" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f5dd7095c2481b7b3cbed71c8de53085fb3542bc3c2b4c73cba43e8f11c7ba" +dependencies = [ + "sharded-slab", + "tracing-core", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -2414,8 +2497,8 @@ checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" name = "xesite" version = "2.0.1" dependencies = [ - "anyhow", "chrono", + "color-eyre", "comrak", "envy", "glob", @@ -2437,6 +2520,7 @@ dependencies = [ "sitemap", "thiserror", "tokio", + "url", "warp", "xml-rs", ] diff --git a/Cargo.toml b/Cargo.toml index a9c178b..f43230d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/Xe/site" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1" +color-eyre = "0.5" chrono = "0.4" comrak = "0.8" envy = "0.4" @@ -30,6 +30,7 @@ thiserror = "1" tokio = { version = "0.2", features = ["macros"] } warp = "0.2" xml-rs = "0.8" +url = "2" # workspace dependencies go_vanity = { path = "./lib/go_vanity" } diff --git a/blog/TLDR-rust-2020-09-19.markdown b/blog/TLDR-rust-2020-09-19.markdown new file mode 100644 index 0000000..a440dd3 --- /dev/null +++ b/blog/TLDR-rust-2020-09-19.markdown @@ -0,0 +1,674 @@ +--- +title: "TL;DR Rust" +date: 2020-09-19 +tags: + - rust + - go + - golang +--- + +# TL;DR Rust + +Recently I've been starting to use Rust more and more for larger and larger +projects. As things have come up, I realized that I am missing a good reference +for common things in Rust as compared to Go. This post contains a quick +high-level overview of patterns in Rust and how they compare to patterns +in Go. This will focus on code samples. This is no replacement for the [Rust +book](https://doc.rust-lang.org/book/), but should help you get spun up on the +various patterns used in Rust code. + +Also I'm happy to introduce Mara to the blog! + +[Hey, happy to be here! I'm Mara, I'll interject with side information, +challenge assertions and more! Thanks for inviting +me!](conversation://Mara/hacker) + +Let's start somewhere simple: functions. + +## Making Functions + +Functions are defined using `fn` instead of `func`: + +```go +// go + +func foo() {} +``` + +```rust +// rust + +fn foo() {} +``` + +### Arguments + +Arguments can be passed by separating the name from the type with a colon: + +```go +// go + +func foo(bar int) {} +``` + +```rust +// rust + +fn foo(bar: i32) {} +``` + +### Returns + +Values can be returned by adding `-> Type` to the function declaration: + +```go +// go + +func foo() int { + return 2 +} +``` + +```rust +// rust + +fn foo() -> i32 { + return 2; +} +``` + +In Rust values can also be returned on the last statement without the `return` +keyword or a terminating semicolon: + +```rust +// rust + +fn foo() -> i32 { + 2 +} +``` + +[Hmm, what if I try to do something like this. Will this +work?](conversation://Mara/hmm) + +```rust +// rust + +fn foo() -> i32 { + if some_cond { + 2 + } + + 4 +} +``` + +Let's find out! The compiler spits back an error: + +``` +error[E0308]: mismatched types + --> src/lib.rs:3:9 + | +2 | / if some_cond { +3 | | 2 + | | ^ expected `()`, found integer +4 | | } + | | -- help: consider using a semicolon here + | |_____| + | expected this to be `()` +``` + +This happens because most basic statements in Rust can return values. The best +way to fix this would be to move the `4` return into an `else` block: + +```rust +// rust + +fn foo() -> i32 { + if some_cond { + 2 + } else { + 4 + } +} +``` + +Otherwise, the compiler will think you are trying to use that `if` as a +statement, such as like this: + +```rust +// rust + +let val = if some_cond { 2 } else { 4 }; +``` + +### Functions that can fail + +The [Result](https://doc.rust-lang.org/std/result/) type represents things that +can fail with specific errors. The [eyre Result +type](https://docs.rs/eyre) represents things that can fail +with any error. For readability, this post will use the eyre Result type. + +[The angle brackets in the `Result` type are arguments to the type, this allows +the Result type to work across any type you could imagine.](conversation://Mara/hacker) + +```go +// go + +import "errors" + +func divide(x, y int) (int, err) { + if y == 0 { + return 0, errors.New("cannot divide by zero") + } + + return x / y, nil +} +``` + +```rust +// rust + +use eyre::{eyre, Result}; + +fn divide(x: i32, y: i32) -> Result { + match y { + 0 => Err(eyre!("cannot divide by zero")), + _ => Ok(x / y), + } +} +``` + +[Huh? I thought Rust had the Error trait, +shouldn't you be able to use that instead of a third party package like +eyre?](conversation://Mara/wat) + +Let's try that, however we will need to make our own error type because the +[`eyre!`](https://docs.rs/eyre/0.6.0/eyre/macro.eyre.html) macro creates its own +transient error type on the fly. + +First we need to make our own simple error type for a DivideByZero error: + +```rust +// rust + +use std::error::Error; +use std::fmt; + +#[derive(Debug)] +struct DivideByZero; + +impl fmt::Display for DivideByZero { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "cannot divide by zero") + } +} + +impl Error for DivideByZero {} +``` + +So now let's use it: + +```rust +// rust + +fn divide(x: i32, y: i32) -> Result { + match y { + 0 => Err(DivideByZero{}), + _ => Ok(x / y), + } +} +``` + +However there is still one thing left: the function returns a DivideByZero +error, not _any_ error like the [error interface in +Go](https://godoc.org/builtin#error). In order to represent that we need to +return something that implements the Error trait: + +```rust +// rust + +fn divide(x: i32, y: i32) -> Result { + // ... +} +``` + +And for the simple case, this will work. However as things get more complicated +this simple facade will not work due to reality and its complexities. This is +why I am shipping as much as I can out to other packages like eyre or +[anyhow](https://docs.rs/anyhow). Check out this code in the [Rust +Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=946057d8eb02f388cb3f03bae226d10d) +to mess with this code interactively. + +[Pro tip: eyre (via color-eyre) also +has support for adding custom +sections and context to errors similar to Go's `fmt.Errorf` `%w` +format argument, which will help in real world +applications. When you do need to actually make your own errors, you may want to look into +crates like thiserror to help with +automatically generating your error implementation.](conversation://Mara/hacker) + +### The `?` Operator + +In Rust, the `?` operator checks for an error in a function call and if there is +one, it automatically returns the error and gives you the result of the function +if there was no error. This only works in functions that return either an Option +or a Result. + +[The Option type +isn't shown in very much detail here, but it acts like a "this thing might not exist and it's your +responsibility to check" container for any value. The closest analogue in Go is +making a pointer to a value or possibly putting a value in an `interface{}` +(which can be annoying to deal with in practice).](conversation://Mara/hacker) + +```go +// go + +func doThing() (int, error) { + result, err := divide(3, 4) + if err != nil { + return 0, err + } + + return result, nil +} +``` + +```rust +// rust + +use eyre::Result; + +fn do_thing() -> Result { + let result = divide(3, 4)?; + Ok(result) +} +``` + +If the second argument of divide is changed to `0`, then `do_thing` will return +an error. + +[And how does that work with eyre?](conversation://Mara/hmm) + +It works with eyre because eyre has its own error wrapper type called +[`Report`](https://docs.rs/eyre/0.6.0/eyre/struct.Report.html), which can +represent anything that implements the Error trait. + +## Macros + +Rust macros are function calls with `!` after their name: + +```rust +// rust + +println!("hello, world"); +``` + +## Variables + +Variables are created using `let`: + +```go +// go + +var foo int +var foo = 3 +foo := 3 +``` + +```rust +// rust + +let foo: i32; +let foo = 3; +``` + +### Mutability + +In Rust, every variable is immutable (unchangeable) by default. If we try to +change those variables above we get a compiler error: + +```rust +// rust + +fn main() { + let foo: i32; + let foo = 3; + foo = 4; +} +``` + +This makes the compiler return this error: + +``` +error[E0384]: cannot assign twice to immutable variable `foo` + --> src/main.rs:4:5 + | +3 | let foo = 3; + | --- + | | + | first assignment to `foo` + | help: make this binding mutable: `mut foo` +4 | foo = 4; + | ^^^^^^^ cannot assign twice to immutable variable +``` + +As the compiler suggests, you can create a mutable variable by adding the `mut` +keyword after the `let` keyword. There is no analog to this in Go. + +```rust +// rust + +let mut foo: i32 = 0; +foo = 4; +``` + +[This is slightly a lie. There's more advanced cases involving interior +mutability and other fun stuff like that, however this is a more advanced topic +that isn't covered here.](conversation://Mara/hacker) + +### Lifetimes + +Rust does garbage collection at compile time. It also passes ownership of memory +to functions as soon as possible. For example: + +```rust +// rust + +let quo = divide(4, 8)?; +let other_quo = divide(quo, 5)?; + +// Fails compile because ownership of quo was given to divide to create other_quo +let yet_another_quo = divide(quo, 4)?; +``` + +To work around this you can pass a reference to the divide function: + +```rust +// rust + +let other_quo = divide(&quo, 5); +let yet_another_quo = divide(&quo, 4)?; +``` + +Or even create a clone of it: + +```rust +// rust + +let other_quo = divide(quo.clone(), 5); +let yet_another_quo = divide(quo, 4)?; +``` + +[You can also get more fancy with explicit +lifetime annotations, however as of Rust's 2018 edition they aren't usually +required unless you are doing something weird. This is something that is also +covered in more detail in The +Rust Book.](conversation://Mara/hacker) + +### Passing Mutability + +Sometimes functions need mutable variables. To pass a mutable reference, add +`&mut` before the name of the variable: + +```rust +let something = do_something_to_quo(&mut quo)?; +``` + +## Project Setup + +### Imports + +External dependencies are declared using the [Cargo.toml +file](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html): + +```toml +# Cargo.toml + +[dependencies] +eyre = "0.6" +``` + +This depends on the crate [anyhow](https://crates.io/anyhow) at version 1.0.x. + +Dependencies can also have optional features: + +```toml +# Cargo.toml + +[dependencies] +reqwest = { version = "0.10", features = ["json"] } +``` + +This depends on the crate [reqwest](https://crates.io/reqwest) at version 0.10.x +with the `json` feature enabled (in this case it enables reqwest being able to +automagically convert things to/from json using Serde). + +External dependencies can be used with the `use` statement: + +```go +// go + +import "github.com/foo/bar" +``` + +```rust +// rust + +use foo; // -> foo now has the members of crate foo behind the :: operator +use foo::Bar; // -> Bar is now exposed as a type in this file + +use eyre::{eyre, Result}; // exposes the eyre! and Result members of eyre +``` + +[This doesn't cover how the module system +works, however the post I linked there covers this better than I +can.](conversation://Mara/hacker) + +## Async/Await + +Async functions may be interrupted to let other things execute as needed. This +program uses [tokio](https://tokio.rs/) to handle async tasks. To run an async +task and wait for its result, do this: + +``` +// rust + +let printer_fact = reqwest::get("https://printerfacts.cetacean.club/fact") + .await? + .text() + .await?; +println!("your printer fact is: {}", printer_fact); +``` + +This will populate `response` with an amusing fact about everyone's favorite +household pet, the [printer](https://printerfacts.cetacean.club). + +To make an async function, add the `async` keyword before the `fn` keyword: + +```rust +// rust + +async fn get_text(url: String) -> Result { + reqwest::get(&url) + .await? + .text() + .await? +} +``` + +This can then be called like this: + +```rust +// rust + +let printer_fact = get_text("https://printerfacts.cetacean.club/fact").await?; +``` + +## Public/Private Types and Functions + +Rust has three privacy levels for functions: + +- Only visible to the current file (no keyword, lowercase in Go) +- Visible to anything in the current crate (`pub(crate)`, internal packages in + go) +- Visible to everyone (`pub`, upper case in Go) + +[You can't get a perfect analog to `pub(crate)` in Go, but internal +packages can get close to this behavior.](conversation://Mara/hacker) + +## Structures + +Rust structures are created using the `struct` keyword: + +```go +// go + +type Client struct { + Token string +} +``` + +```rust +// rust + +pub struct Client { + pub token: String, +} +``` + +If the `pub` keyword is not specified before a member name, it will not be +usable outside the Rust source code file it is defined in: + +```go +type Client struct { + token string +} +``` + +```rust +pub(crate) struct Client { + token: String, +} +``` + +### Encoding structs to JSON + +[serde](https://serde.rs) is used to convert structures to json. The Rust +compiler's +[derive](https://doc.rust-lang.org/stable/rust-by-example/trait/derive.html) +feature is used to automatically implement the conversion logic. + +```go +type Response struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` +} +``` + +```rust +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) struct Response { + pub name: String, + pub description: Option, +} +``` + +## Strings + +Rust has a few string types that do different things. You can read more about +this [here](https://fasterthanli.me/blog/2020/working-with-strings-in-rust/), +but at a high level most projects only uses a few of them: + +- `&str`, a slice reference to a String owned by someone else +- String, an owned UTF-8 string +- PathBuf, a filepath string (encoded in whatever encoding the OS running this + code uses for filesystems) + +The strings are different types for safety reasons. See the linked blogpost for +more detail about this. + +## Enumerations / Tagged Unions + +Enumerations, also known as tagged unions, are a way to specify a superposition +of one of a few different kinds of values in one type. The main place they are +used in this project is for command line parsing with +[structopt](https://docs.rs/structopt/0.3.14/structopt/). There is no easy +analog for this in Go. + +```rust +#[derive(StructOpt, Debug)] +#[structopt(about = "A simple release management tool")] +pub(crate) enum Cmd { + /// Creates a new release for a git repo + Cut { + #[structopt(flatten)] + common: Common, + /// Changelog location + #[structopt(long, short, default_value="./CHANGELOG.md")] + changelog: PathBuf, + }, + + /// Runs releases as triggered by GitHub Actions + GitHubAction { + #[structopt(flatten)] + gha: GitHubAction, + }, +} +``` + +Enum variants can be matched using the `match` keyword: + +```rust +match cmd { + Cmd::Cut { common, changelog } => { + cmd::cut::run(common, changelog).await + } + Cmd::GitHubAction { gha } => { + cmd::github_action::run(gha).await + } +} +``` + +All variants of an enum must be matched in order for the code to compile. + +## Testing + +Test functions need to be marked with the `#[test]` annotation, then they will +be run alongside `cargo test`: + +```rust +mod tests { // not required but it is good practice + #[test] + fn math_works() { + assert_eq!(2 + 2, 4); + } + + #[tokio::test] // needs tokio as a dependency + async fn http_works() { + let _ = get_html("https://within.website").await.unwrap(); + } +} +``` + +Avoid the use of `unwrap()` outside of tests. In the wrong cases, using +`unwrap()` in production code can cause the server to crash and can incur data +loss. + +[Alternatively, you can also use the `.expect()` method instead +of `.unwrap()`. This lets you attach a message that will be shown when the +result isn't Ok.](conversation://Mara/hacker) + +--- + +This is by no means comprehensive, see the rust book or [Learn X in Y Minutes +Where X = Rust](https://learnxinyminutes.com/docs/rust/) for more information. +This code is written to be as boring and obvious as possible. If things don't +make sense, please reach out and don't be afraid to ask questions. diff --git a/shell.nix b/shell.nix index b9c873a..fbd55ce 100644 --- a/shell.nix +++ b/shell.nix @@ -33,6 +33,7 @@ mkShell { SITE_PREFIX = "devel."; CLACK_SET = "Ashlynn,Terry Davis,Dennis Ritchie"; - RUST_LOG = "info"; + RUST_LOG = "debug"; + RUST_BACKTRACE = "1"; GITHUB_SHA = "devel"; } diff --git a/src/app/markdown.rs b/src/app/markdown.rs new file mode 100644 index 0000000..fe33a21 --- /dev/null +++ b/src/app/markdown.rs @@ -0,0 +1,79 @@ +use color_eyre::eyre::{Result, WrapErr}; +use comrak::nodes::{Ast, AstNode, NodeValue}; +use comrak::{format_html, parse_document, markdown_to_html, Arena, ComrakOptions}; +use std::cell::RefCell; +use crate::templates::Html; +use url::Url; + +pub fn render(inp: &str) -> Result { + let mut options = ComrakOptions::default(); + + options.extension.autolink = true; + options.extension.table = true; + options.extension.description_lists = true; + options.extension.superscript = true; + options.extension.strikethrough = true; + options.extension.footnotes = true; + + options.render.unsafe_ = true; + + let arena = Arena::new(); + let root = parse_document(&arena, inp, &options); + + iter_nodes(root, &|node| { + let mut data = node.data.borrow_mut(); + match &mut data.value { + &mut NodeValue::Link(ref mut link) => { + let base = Url::parse("https://christine.website/")?; + let u = base.join(std::str::from_utf8(&link.url.clone())?)?; + if u.scheme() != "conversation" { + return Ok(()); + } + let parent = node.parent().unwrap(); + node.detach(); + let mut message = vec![]; + for child in node.children() { + format_html(child, &options, &mut message)?; + } + let message = std::str::from_utf8(&message)?; + let message = markdown_to_html(message, &options); + let mood = without_first(u.path()); + let name = u.host_str().unwrap_or("Mara"); + + let mut html = vec![]; + crate::templates::mara(&mut html, mood, name, Html(message))?; + + let new_node = + arena.alloc(AstNode::new(RefCell::new(Ast::new(NodeValue::HtmlInline(html))))); + parent.append(new_node); + + Ok(()) + } + _ => Ok(()), + } + })?; + + let mut html = vec![]; + format_html(root, &options, &mut html).unwrap(); + + String::from_utf8(html).wrap_err("post is somehow invalid UTF-8") +} + +fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F) -> Result<()> +where + F: Fn(&'a AstNode<'a>) -> Result<()>, +{ + f(node)?; + for c in node.children() { + iter_nodes(c, f)?; + } + Ok(()) +} + +fn without_first(string: &str) -> &str { + string + .char_indices() + .nth(1) + .and_then(|(i, _)| string.get(i..)) + .unwrap_or("") +} diff --git a/src/app.rs b/src/app/mod.rs similarity index 87% rename from src/app.rs rename to src/app/mod.rs index 5ffca7c..44f05e7 100644 --- a/src/app.rs +++ b/src/app/mod.rs @@ -1,9 +1,10 @@ use crate::{post::Post, signalboost::Person}; -use anyhow::Result; -use comrak::{markdown_to_html, ComrakOptions}; +use color_eyre::eyre::Result; use serde::Deserialize; use std::{fs, path::PathBuf}; +pub mod markdown; + #[derive(Clone, Deserialize)] pub struct Config { #[serde(rename = "clackSet")] @@ -14,21 +15,6 @@ pub struct Config { resume_fname: PathBuf, } -pub fn markdown(inp: &str) -> String { - let mut options = ComrakOptions::default(); - - options.extension.autolink = true; - options.extension.table = true; - options.extension.description_lists = true; - options.extension.superscript = true; - options.extension.strikethrough = true; - options.extension.footnotes = true; - - options.render.unsafe_ = true; - - markdown_to_html(inp, &options) -} - async fn patrons() -> Result> { use patreon::*; let creds: Credentials = envy::prefixed("PATREON_").from_env().unwrap(); @@ -72,7 +58,7 @@ pub async fn init(cfg: PathBuf) -> Result { let cfg: Config = serde_dhall::from_file(cfg).parse()?; let sb = cfg.signalboost.clone(); let resume = fs::read_to_string(cfg.resume_fname.clone())?; - let resume: String = markdown(&resume); + let resume: String = markdown::render(&resume)?; let blog = crate::post::load("blog")?; let gallery = crate::post::load("gallery")?; let talks = crate::post::load("talks")?; @@ -145,7 +131,7 @@ pub async fn init(cfg: PathBuf) -> Result { #[cfg(test)] mod tests { - use anyhow::Result; + use color_eyre::eyre::Result; #[tokio::test] async fn init() -> Result<()> { let _ = pretty_env_logger::try_init(); diff --git a/src/main.rs b/src/main.rs index aa5400e..c1e9e1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use color_eyre::eyre::Result; use hyper::{header::CONTENT_TYPE, Body, Response}; use prometheus::{Encoder, TextEncoder}; use std::sync::Arc; @@ -21,6 +21,7 @@ fn with_state( #[tokio::main] async fn main() -> Result<()> { + color_eyre::install()?; let _ = kankyo::init(); pretty_env_logger::init(); log::info!("starting up commit {}", env!("GITHUB_SHA")); diff --git a/src/post/frontmatter.rs b/src/post/frontmatter.rs index 1cc8032..615f2c5 100644 --- a/src/post/frontmatter.rs +++ b/src/post/frontmatter.rs @@ -1,6 +1,6 @@ /// This code was borrowed from @fasterthanlime. -use anyhow::{Result}; +use color_eyre::eyre::{Result}; use serde::{Serialize, Deserialize}; #[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)] diff --git a/src/post/mod.rs b/src/post/mod.rs index a948017..c0062a4 100644 --- a/src/post/mod.rs +++ b/src/post/mod.rs @@ -1,5 +1,5 @@ -use anyhow::{anyhow, Result}; use chrono::prelude::*; +use color_eyre::eyre::{eyre, Result, WrapErr}; use glob::glob; use std::{cmp::Ordering, fs}; @@ -75,8 +75,10 @@ pub fn load(dir: &str) -> Result> { for path in glob(&format!("{}/*.markdown", dir))?.filter_map(Result::ok) { log::debug!("loading {:?}", path); - let body = fs::read_to_string(path.clone()).expect("things to work"); - let (fm, content_offset) = frontmatter::Data::parse(body.clone().as_str()).expect("stuff to work"); + let body = + fs::read_to_string(path.clone()).wrap_err_with(|| format!("can't read {:?}", path))?; + let (fm, content_offset) = frontmatter::Data::parse(body.clone().as_str()) + .wrap_err_with(|| format!("can't parse frontmatter of {:?}", path))?; let markup = &body[content_offset..]; let date = NaiveDate::parse_from_str(&fm.clone().date, "%Y-%m-%d")?; @@ -84,7 +86,8 @@ pub fn load(dir: &str) -> Result> { front_matter: fm, link: format!("{}/{}", dir, path.file_stem().unwrap().to_str().unwrap()), body: markup.to_string(), - body_html: crate::app::markdown(&markup), + body_html: crate::app::markdown::render(&markup) + .wrap_err_with(|| format!("can't parse markdown for {:?}", path))?, date: { DateTime::::from_utc( NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0)), @@ -97,7 +100,7 @@ pub fn load(dir: &str) -> Result> { } if result.len() == 0 { - Err(anyhow!("no posts loaded")) + Err(eyre!("no posts loaded")) } else { result.sort(); result.reverse(); @@ -108,7 +111,7 @@ pub fn load(dir: &str) -> Result> { #[cfg(test)] mod tests { use super::*; - use anyhow::Result; + use color_eyre::eyre::Result; #[test] fn blog() { diff --git a/src/signalboost.rs b/src/signalboost.rs index 079990b..f580d7c 100644 --- a/src/signalboost.rs +++ b/src/signalboost.rs @@ -13,7 +13,7 @@ pub struct Person { #[cfg(test)] mod tests { - use anyhow::Result; + use color_eyre::eyre::Result; #[test] fn load() -> Result<()> { let _people: Vec = serde_dhall::from_file("./signalboost.dhall").parse()?; diff --git a/templates/blogpost.rs.html b/templates/blogpost.rs.html index 6b55850..d90a8e3 100644 --- a/templates/blogpost.rs.html +++ b/templates/blogpost.rs.html @@ -62,61 +62,62 @@

Tags: @for tag in post.front_matter.tags.as_ref().unwrap() { @tag }

} - @:footer_html() diff --git a/templates/mara.rs.html b/templates/mara.rs.html new file mode 100644 index 0000000..1786d6f --- /dev/null +++ b/templates/mara.rs.html @@ -0,0 +1,21 @@ +@(mood: &str, character: &str, message: Html) +
+
+
+ + + @character is @mood + +
+
+
+
+

+ @character +

+
+ @message +
+
+
+