aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2020-09-19 11:33:46 -0400
committerGitHub <noreply@github.com>2020-09-19 11:33:46 -0400
commita2fba89738caac83ce24d40b762d6205f2266361 (patch)
tree3a61afb3b1a9d42b0a61b67be527322b6740f179
parent1e61d2ad33f7ac7751063bdd373ab3d1305015e2 (diff)
downloadxesite-a2fba89738caac83ce24d40b762d6205f2266361.tar.xz
xesite-a2fba89738caac83ce24d40b762d6205f2266361.zip
TL;DR Rust (#210)
* start mara code * better alt text * more mara tests * cleanups * blog: start tl;dr rust post * more words * feature complete * little oopses * oops lol
-rw-r--r--Cargo.lock98
-rw-r--r--Cargo.toml3
-rw-r--r--blog/TLDR-rust-2020-09-19.markdown674
-rw-r--r--shell.nix3
-rw-r--r--src/app/markdown.rs79
-rw-r--r--src/app/mod.rs (renamed from src/app.rs)24
-rw-r--r--src/main.rs3
-rw-r--r--src/post/frontmatter.rs2
-rw-r--r--src/post/mod.rs15
-rw-r--r--src/signalboost.rs2
-rw-r--r--templates/blogpost.rs.html91
-rw-r--r--templates/mara.rs.html21
12 files changed, 933 insertions, 82 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0900e11..5979efa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -67,12 +67,6 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -264,6 +258,32 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -434,6 +454,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -759,6 +789,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1140,6 +1176,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1811,6 +1853,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2033,10 +2084,22 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2046,6 +2109,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2056,6 +2129,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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<i32> {
+ match y {
+ 0 => Err(eyre!("cannot divide by zero")),
+ _ => Ok(x / y),
+ }
+}
+```
+
+[Huh? I thought Rust had the <a
+href="https://doc.rust-lang.org/std/error/trait.Error.html">Error trait</a>,
+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<i32, DivideByZero> {
+ 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<i32, impl Error> {
+ // ...
+}
+```
+
+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 <a href="https://docs.rs/color-eyre">color-eyre</a>) also
+has support for adding <a href="https://docs.rs/color-eyre/0.5.4/color_eyre/#custom-sections-for-error-reports-via-help-trait">custom
+sections and context</a> to errors similar to Go's <a href="https://godoc.org/fmt#Errorf">`fmt.Errorf` `%w`
+format argument</a>, 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 <a href="https://docs.rs/thiserror">thiserror</a> 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 <a href="https://doc.rust-lang.org/std/option/index.html">Option</a> 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<i32> {
+ 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 <a
+href="https://doc.rust-lang.org/rust-by-example/scope/lifetime/explicit.html">explicit
+lifetime annotations</a>, 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 <a
+href="https://doc.rust-lang.org/stable/book/ch04-00-understanding-ownership.html">The
+Rust Book</a>.](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 <a
+href="http://www.sheshbabu.com/posts/rust-module-system/">module system</a>
+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<String> {
+ 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 <a
+href="https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU/edit">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<String>,
+}
+```
+
+## 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 <a href="https://learning-rust.github.io/docs/e4.unwrap_and_expect.html#expect">`.expect()`</a> 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<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;
+
+ 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
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<Option<patreon::Users>> {
use patreon::*;
let creds: Credentials = envy::prefixed("PATREON_").from_env().unwrap();
@@ -72,7 +58,7 @@ pub async fn init(cfg: PathBuf) -> Result<State> {
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<State> {
#[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/