Compare commits
3 commits
20eea2b6dd
...
3189b57a46
Author | SHA1 | Date | |
---|---|---|---|
Adrian Hedqvist | 3189b57a46 | ||
Adrian Hedqvist | 73f88fd78c | ||
Adrian Hedqvist | 5b6dd2330d |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
.git
|
||||
.vscode
|
||||
target
|
3
.markdownlint.json
Normal file
3
.markdownlint.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"MD025": false
|
||||
}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -3,7 +3,6 @@
|
|||
"sv"
|
||||
],
|
||||
"spellright.documentTypes": [
|
||||
"latex",
|
||||
"plaintext"
|
||||
"latex"
|
||||
]
|
||||
}
|
122
Cargo.lock
generated
122
Cargo.lock
generated
|
@ -50,6 +50,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.3.15"
|
||||
|
@ -68,13 +74,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.67"
|
||||
version = "0.1.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4"
|
||||
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.6",
|
||||
"syn 2.0.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -283,8 +289,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -355,9 +365,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -405,7 +415,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 2.0.6",
|
||||
"syn 2.0.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -422,7 +432,7 @@ checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.6",
|
||||
"syn 2.0.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -600,7 +610,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -817,9 +827,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.2"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
|
@ -959,7 +969,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
|
@ -1265,9 +1275,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.2"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -1282,9 +1292,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
|||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
|
@ -1336,7 +1346,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.6",
|
||||
"syn 2.0.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1359,6 +1369,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
@ -1459,9 +1478,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.6"
|
||||
version = "2.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece519cfaf36269ea69d16c363fa1d59ceba8296bbfbfc003c3176d01f2816ee"
|
||||
checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1523,7 +1542,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.6",
|
||||
"syn 2.0.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1535,6 +1554,17 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.26.0"
|
||||
|
@ -1580,6 +1610,40 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
|
@ -1851,6 +1915,12 @@ dependencies = [
|
|||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -1915,17 +1985,22 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
|||
name = "website"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"cached",
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"glob",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
"pulldown-cmark",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tera",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
|
@ -2038,6 +2113,15 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
|
|
|
@ -6,17 +6,23 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.70"
|
||||
axum = { version = "0.6.12", features = ["http2"] }
|
||||
cached = "0.42.0"
|
||||
chrono = { version = "0.4.24", features = ["serde"] }
|
||||
color-eyre = "0.6.1"
|
||||
glob = "0.3.0"
|
||||
#grass = { version = "0.12.3", features = ["random"] } # not really needed yet
|
||||
hyper = { version = "0.14.19", features = ["full"] }
|
||||
lazy_static = "1.4.0"
|
||||
pulldown-cmark = "0.9.2"
|
||||
regex = "1.7.2"
|
||||
serde = "1.0.144"
|
||||
serde_derive = "1.0.144"
|
||||
serde_json = "1.0.85"
|
||||
tera = "1.17.0"
|
||||
tera = { version = "1.17.0", features = ["builtins"] }
|
||||
tokio = { version = "1.19.2", features = ["full"] }
|
||||
toml = "0.7.3"
|
||||
tower = { version = "0.4.12", features = ["full"] }
|
||||
tower-http = { version = "0.4.0", features = ["full"] }
|
||||
tracing = "0.1.35"
|
||||
|
|
65
Dockerfile
Normal file
65
Dockerfile
Normal file
|
@ -0,0 +1,65 @@
|
|||
FROM rust:slim AS chef
|
||||
RUN cargo install cargo-chef
|
||||
WORKDIR app
|
||||
|
||||
####################################################################################################
|
||||
## Planner
|
||||
####################################################################################################
|
||||
FROM chef AS planner
|
||||
COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
####################################################################################################
|
||||
## Builder
|
||||
####################################################################################################
|
||||
FROM chef AS builder
|
||||
|
||||
RUN rustup target add x86_64-unknown-linux-musl
|
||||
RUN apt update && apt install -y musl-tools musl-dev
|
||||
RUN update-ca-certificates
|
||||
|
||||
# Create appuser
|
||||
ENV USER=website
|
||||
ENV UID=10001
|
||||
|
||||
RUN adduser \
|
||||
--disabled-password \
|
||||
--gecos "" \
|
||||
--home "/nonexistent" \
|
||||
--shell "/sbin/nologin" \
|
||||
--no-create-home \
|
||||
--uid "${UID}" \
|
||||
"${USER}"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=planner /app/recipe.json .
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cargo build --target x86_64-unknown-linux-musl --release
|
||||
|
||||
####################################################################################################
|
||||
## Final image
|
||||
####################################################################################################
|
||||
FROM scratch
|
||||
|
||||
# Import from builder.
|
||||
COPY --from=builder /etc/passwd /etc/passwd
|
||||
COPY --from=builder /etc/group /etc/group
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy our build
|
||||
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/website ./
|
||||
COPY --from=builder /app/posts ./posts
|
||||
COPY --from=builder /app/static ./static
|
||||
COPY --from=builder /app/templates ./templates
|
||||
|
||||
EXPOSE 8180
|
||||
|
||||
# Use an unprivileged user.
|
||||
USER website:website
|
||||
|
||||
CMD ["./website"]
|
49
posts/dungeon/index.md
Normal file
49
posts/dungeon/index.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
+++
|
||||
date = 2018-01-10T17:50:00+01:00
|
||||
draft = false
|
||||
title = "Yet another (traditional) roguelike written in c++"
|
||||
aliases = ["/blog/dungeon/", "/blog/dungeon.html"]
|
||||
|
||||
[taxonomies]
|
||||
tags = ["cpp", "roguelike"]
|
||||
+++
|
||||
|
||||
![work-in-progress screenshot](screenshot.png)
|
||||
|
||||
My current go-to pet project is [dungeon](https://github.com/tollyx/dungeon), a
|
||||
roguelike which is currently pretty incomplete and basic, but I think I've
|
||||
gotten far enough to start showing it off a bit more.
|
||||
|
||||
It's a roguelike with your usual fantasy-setting (for now, anyway)
|
||||
and mechanically it's going to be pretty typical as well. Eventually though,
|
||||
I'm planning on implementing some kind of bodypart system where potions, food,
|
||||
traps, enemy attacks and so on can transform your character, giving you both
|
||||
positive and negative effects.
|
||||
|
||||
Also, Lua scripting. I want as much as possible outside the main game engine be
|
||||
scriptable in Lua. Which means items, status effects, skills, traps, actors and
|
||||
whatever else that might be fun to have easily modifiable. I'm also using Lua
|
||||
for basic data loading but that's just because I'm lazy and why the hell not
|
||||
when Lua is already implemented.
|
||||
|
||||
The main things that are currently missing are:
|
||||
|
||||
- Menus
|
||||
- In-game UI (including a log)
|
||||
- Items (with lua-scripted effects)
|
||||
- Loading enemies from data
|
||||
|
||||
After those and a bit more content I'll be implementing whatever cool ideas that
|
||||
I come up with.
|
||||
|
||||
I'm mainly making it for learning purposes (and for fun), trying out new things
|
||||
as I go. That lack of planning combined with not actually spending too much
|
||||
time on it means that progress is slow. The fact that I find myself rewriting
|
||||
things quite often due to the mentioned lack of planning doesn't help much,
|
||||
either.
|
||||
|
||||
I'll try to write blog posts about whatever in the game that might be
|
||||
interesting for others, but it will probably take a while before they pop up as
|
||||
the game is currently pretty basic and will stay so for a while longer.
|
||||
|
||||
[You can find the source on github](https://github.com/tollyx/dungeon).
|
BIN
posts/dungeon/screenshot.png
Normal file
BIN
posts/dungeon/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -1,4 +1,7 @@
|
|||
# Testing post as index within folder
|
||||
+++
|
||||
title="TOML metadata test"
|
||||
date=2023-03-25T14:50:25+01:00
|
||||
+++
|
||||
|
||||
hope it works yay
|
||||
|
||||
|
|
11
posts/hello-world.md
Normal file
11
posts/hello-world.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
+++
|
||||
date = 2017-06-25T22:40:00+01:00
|
||||
draft = false
|
||||
title = "Hello world, again"
|
||||
aliases = ["/blog/hello-world/", "/blog/hello-world.html"]
|
||||
tags = ["hugo", "jekyll"]
|
||||
+++
|
||||
|
||||
So I've yet again remade this website. This time I've moved from [jekyll](https://jekyllrb.com/) to [hugo](https://gohugo.io/), because I've had to reinstall ruby far too many times and there's always some issue with it preventing me from just getting it up and running so I can update this site. Hugo is just a binary, no need to install anything to get it to run. It's far less of a hassle.
|
||||
|
||||
If you for whatever reason want to see the old posts (which were basically just dev logs required for some of my school courses), you can find them [here.](@/old/_index.md)
|
24
posts/status-update-2020-05-16/index.md
Normal file
24
posts/status-update-2020-05-16/index.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
+++
|
||||
date = 2020-05-16T13:43:00+02:00
|
||||
draft = false
|
||||
title = 'Status update: 2020-05-16'
|
||||
tags = ["hugo", "zola", "roguelike", "spork"]
|
||||
+++
|
||||
|
||||
Oh boy, it's been a while since I wrote anything on this website, over two years ago!
|
||||
|
||||
But I've been thinking of starting to write posts more often, try this proggramming-blogging thing out that seems to have had some sort of resurgance. I don't know if I have anything others will find interesting to read, but I've got a few drafts lying around that I'm gonna try finishing up. We'll see how it goes. Just don't expect me to post things too often - maybe once or twice a month, at most.
|
||||
|
||||
And with that, I've moved the website to yet another static website generator, this time I'm using [zola]. Why? It's written in rust. That's pretty much all I got. It's actually very similar to [hugo], which I used previously, but I don't know - zola kinda feels nicer to work with when it comes to templates/themes?
|
||||
|
||||
My latest free-time project I've been working on is yet another roguelike - this time, I'm following a tutorial so that I won't get stuck for too long figuring out architecture stuff. It's being written in rust and the tutorial I'm following is [this one][roguelike-tutorial]. It's pretty great - it's using [specs] which I've tried out multiple times but I never really got a good hang of figuring out how to do stuff with an ECS, so learning more about that has been nice. But I did the stupid mistake of not writing down some small errors in the tutorial, so I'll probably go back and try to find them again and open a few PR's for them. I'll try to write a post or two about the roguelike here as well. But for now, [you can find the source code for it over here][roguelike-src].
|
||||
|
||||
In other news, I bought myself an apartment. I'm moving at the start of June, so that's pretty exciting.
|
||||
|
||||
Also, in case someone actually got my rss feed in their reader - sorry, it broke again when I switched to zola, and from what I've read it'll break again in version 0.11 due to a change I agree with. You'll have to fix the feed link, twice. Again, sorry about that.
|
||||
|
||||
[zola]: https://getzola.org
|
||||
[hugo]: https://gohugo.io
|
||||
[roguelike-tutorial]: https://bfnightly.bracketproductions.com/rustbook/chapter_0.html
|
||||
[specs]: https://crates.io/crates/specs
|
||||
[roguelike-src]: https://gitlab.com/tollyx/roguelike-tutorial-rs
|
|
@ -1,3 +0,0 @@
|
|||
# Test page please ignore
|
||||
|
||||
Hello world!
|
|
@ -1,42 +1,35 @@
|
|||
use serde_derive::Serialize;
|
||||
use hyper::StatusCode;
|
||||
use std::sync::Arc;
|
||||
use tracing::log::*;
|
||||
use tracing::{instrument, log::*};
|
||||
|
||||
use axum::{
|
||||
response::{Html, IntoResponse, Response},
|
||||
Extension,
|
||||
};
|
||||
|
||||
use crate::State;
|
||||
use crate::{State, WebsiteError};
|
||||
|
||||
pub mod posts;
|
||||
|
||||
pub enum Error {
|
||||
NotFound,
|
||||
}
|
||||
|
||||
pub type Result<T = Html<Vec<u8>>> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PageContext {
|
||||
content: String,
|
||||
}
|
||||
|
||||
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
||||
#[instrument(skip(state))]
|
||||
pub async fn index(Extension(state): Extension<Arc<State>>) -> std::result::Result<Html<Vec<u8>>, WebsiteError> {
|
||||
let ctx = tera::Context::new();
|
||||
let res = state.tera.render("index.html", &ctx).map_err(|e| {
|
||||
error!("Failed rendering index: {}", e);
|
||||
Error::NotFound
|
||||
WebsiteError::NotFound
|
||||
})?;
|
||||
Ok(Html(res.into()))
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
impl IntoResponse for WebsiteError {
|
||||
fn into_response(self) -> Response {
|
||||
let result: Vec<u8> = "not found".into();
|
||||
let body = axum::body::boxed(axum::body::Full::from(result));
|
||||
match self {
|
||||
Error::NotFound => Response::builder().status(404).body(body).unwrap(),
|
||||
WebsiteError::NotFound => {
|
||||
info!("not found");
|
||||
(StatusCode::NOT_FOUND, ()).into_response()
|
||||
}
|
||||
WebsiteError::InternalError(e) => {
|
||||
error!("internal error: {e}");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, ()).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::Path, response::Html, Extension};
|
||||
use cached::proc_macro::cached;
|
||||
use pulldown_cmark::{html, Options, Parser};
|
||||
use tracing::log::*;
|
||||
|
||||
use super::{Error, Result};
|
||||
use crate::{handlers::PageContext, State};
|
||||
|
||||
pub async fn view(Path(path): Path<String>, Extension(state): Extension<Arc<State>>) -> Result {
|
||||
let post = path.trim_end_matches('/');
|
||||
info!("Requested post: {}", post);
|
||||
let res = render_post(&state, post).await.ok_or(Error::NotFound)?;
|
||||
Ok(Html(res.into()))
|
||||
}
|
||||
|
||||
#[cached(time = 60, key = "String", convert = r"{ path.to_owned() }")]
|
||||
async fn render_post(state: &State, path: &str) -> Option<String> {
|
||||
info!("Rendering post...");
|
||||
let post = state.posts.iter().find(|p| p.slug == path)?;
|
||||
|
||||
let options = Options::all();
|
||||
let parser = Parser::new_ext(&post.content, options);
|
||||
|
||||
let mut out = String::new();
|
||||
html::push_html(&mut out, parser);
|
||||
|
||||
let ctx = tera::Context::from_serialize(PageContext { content: out }).ok()?;
|
||||
|
||||
let res = match state.tera.render("post.html", &ctx) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("Failed rendering post: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
|
||||
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("posts", &state.posts);
|
||||
let res = state.tera.render("postsindex.html", &ctx).map_err(|e| {
|
||||
error!("Failed rendering posts index: {:?}", e);
|
||||
Error::NotFound
|
||||
})?;
|
||||
Ok(Html(res.into()))
|
||||
}
|
111
src/main.rs
111
src/main.rs
|
@ -1,78 +1,27 @@
|
|||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
routing::get,
|
||||
Extension, Router,
|
||||
};
|
||||
use axum::{routing::get, Extension, Router};
|
||||
use color_eyre::eyre::Result;
|
||||
use glob::glob;
|
||||
use serde_derive::Serialize;
|
||||
use post::Post;
|
||||
use tera::Tera;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::log::*;
|
||||
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
|
||||
use tracing::{instrument, log::*};
|
||||
|
||||
mod handlers;
|
||||
mod post;
|
||||
|
||||
pub struct State {
|
||||
posts: Vec<Post>,
|
||||
posts: HashMap<String, Post>,
|
||||
tera: Tera,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Post {
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
tracing_subscriber::fmt::init();
|
||||
info!("Starting server...");
|
||||
|
||||
let tera = Tera::new("templates/**/*")?;
|
||||
let posts = glob("posts/**/*.md")?
|
||||
.map(|p| {
|
||||
let path = p.unwrap();
|
||||
info!("found page: {}", path.display());
|
||||
|
||||
let filename = path.file_name().unwrap().to_string_lossy();
|
||||
|
||||
let (filename, _) = filename.rsplit_once('.').unwrap();
|
||||
|
||||
let slug = if filename.eq_ignore_ascii_case("index") {
|
||||
path.parent()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
} else {
|
||||
filename.to_owned()
|
||||
};
|
||||
Post {
|
||||
name: slug.clone(),
|
||||
slug,
|
||||
content: std::fs::read_to_string(&path).unwrap(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let state = Arc::new(State { tera, posts });
|
||||
|
||||
let middleware = tower::ServiceBuilder::new()
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(Extension(state.clone()));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handlers::index))
|
||||
.nest("/posts", Router::new()
|
||||
.route("/", get(handlers::posts::index))
|
||||
.route("/:route/", get(handlers::posts::view))
|
||||
.fallback_service(tower_http::services::ServeDir::new("./posts")))
|
||||
.nest_service("/static", tower_http::services::ServeDir::new("./static"))
|
||||
.layer(middleware);
|
||||
let app = init_app().await?;
|
||||
|
||||
info!("Now listening at http://localhost:8180");
|
||||
|
||||
|
@ -82,3 +31,47 @@ async fn main() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn init_app() -> Result<Router> {
|
||||
let tera = Tera::new("templates/**/*")?;
|
||||
let posts = post::load_all().await?;
|
||||
|
||||
let state = Arc::new(State { tera, posts });
|
||||
|
||||
let middleware = tower::ServiceBuilder::new()
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(Extension(state))
|
||||
.layer(CompressionLayer::new());
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handlers::index))
|
||||
.nest(
|
||||
"/posts",
|
||||
post::router(),
|
||||
)
|
||||
.nest_service("/static", tower_http::services::ServeDir::new("./static"))
|
||||
.route("/.healthcheck", get(healthcheck))
|
||||
.layer(middleware);
|
||||
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
async fn healthcheck() -> &'static str {
|
||||
"OK"
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WebsiteError {
|
||||
NotFound,
|
||||
InternalError(anyhow::Error),
|
||||
}
|
||||
|
||||
impl<E> From<E> for WebsiteError
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(value: E) -> Self {
|
||||
WebsiteError::InternalError(value.into())
|
||||
}
|
||||
}
|
||||
|
|
193
src/post.rs
Normal file
193
src/post.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||
|
||||
use axum::{response::Html, routing::get, Extension, Router, extract};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use glob::glob;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use pulldown_cmark::{html, Options, Parser};
|
||||
use regex::Regex;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use tracing::{instrument, log::*};
|
||||
|
||||
use crate::{State, WebsiteError};
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct TomlFrontMatter {
|
||||
pub title: String,
|
||||
pub date: Option<toml::value::Datetime>,
|
||||
pub draft: Option<bool>,
|
||||
pub aliases: Option<Vec<String>>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
pub struct Post {
|
||||
pub title: String,
|
||||
pub date: Option<DateTime<FixedOffset>>,
|
||||
pub aliases: Vec<String>,
|
||||
pub tags: Vec<String>,
|
||||
pub content: String,
|
||||
pub slug: String,
|
||||
pub absolute_path: String,
|
||||
}
|
||||
|
||||
impl Post {
|
||||
pub fn new(slug: String, content: String, fm: TomlFrontMatter) -> Post {
|
||||
Post {
|
||||
absolute_path: format!("/posts/{}/", slug),
|
||||
slug,
|
||||
content,
|
||||
title: fm.title,
|
||||
date: fm.date.map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")),
|
||||
aliases: fm.aliases.unwrap_or_default(),
|
||||
tags: fm.tags.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn load_all() -> color_eyre::eyre::Result<HashMap<String, Post>> {
|
||||
let mut res = HashMap::<String, Post>::new();
|
||||
for path in glob("posts/**/*.md")? {
|
||||
let path = path.unwrap();
|
||||
debug!("found page: {}", path.display());
|
||||
|
||||
let path = path.to_string_lossy().replace('\\', "/");
|
||||
let slug = path
|
||||
.trim_start_matches("posts")
|
||||
.trim_start_matches('/')
|
||||
.trim_start_matches('\\')
|
||||
.trim_end_matches(".html")
|
||||
.trim_end_matches(".md")
|
||||
.trim_end_matches("index")
|
||||
.trim_end_matches('\\')
|
||||
.trim_end_matches('/');
|
||||
|
||||
let post = load_post(slug).await?;
|
||||
|
||||
res.insert(slug.to_string(), post);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn load_post(slug: &str) -> color_eyre::eyre::Result<Post> {
|
||||
debug!("loading post: {slug}");
|
||||
|
||||
let file_path = Path::new("posts").join(slug);
|
||||
|
||||
let content = if let Ok(content) = fs::read_to_string(file_path.with_extension("md")).await {
|
||||
content
|
||||
} else {
|
||||
fs::read_to_string(file_path.join("index.md")).await?
|
||||
};
|
||||
|
||||
let (tomlfm, content) = parse_frontmatter(content)?;
|
||||
let tomlfm = tomlfm.expect("Missing frontmatter");
|
||||
|
||||
let content = content.map(|c| {
|
||||
let options = Options::all();
|
||||
let mut content_html = String::new();
|
||||
let parser = Parser::new_ext(&c, options);
|
||||
html::push_html(&mut content_html, parser);
|
||||
content_html
|
||||
});
|
||||
|
||||
Ok(Post::new(slug.to_string(), content.unwrap_or_default(), tomlfm))
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn parse_frontmatter(
|
||||
src: String,
|
||||
) -> color_eyre::eyre::Result<(Option<TomlFrontMatter>, Option<String>)> {
|
||||
lazy_static! {
|
||||
static ref FRONTMATTER_REGEX: Regex = regex::Regex::new(
|
||||
r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))"
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
Ok(if let Some(captures) = FRONTMATTER_REGEX.captures(&src) {
|
||||
(
|
||||
Some(toml::from_str(captures.get(1).unwrap().as_str())?),
|
||||
captures.get(2).map(|m| m.as_str().to_owned()),
|
||||
)
|
||||
} else {
|
||||
(None, Some(src))
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(tera, post))]
|
||||
async fn render_post(tera: &tera::Tera, post: &Post) -> Result<String, WebsiteError> {
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("page", &post);
|
||||
|
||||
tera.render("post.html", &ctx).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn router() -> Router
|
||||
{
|
||||
Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/:slug/", get(view))
|
||||
.fallback_service(tower_http::services::ServeDir::new("./posts"))
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn view(
|
||||
extract::Path(slug): extract::Path<String>,
|
||||
Extension(state): Extension<Arc<State>>,
|
||||
) -> Result<Html<String>, WebsiteError> {
|
||||
debug!("viewing post: {slug}");
|
||||
let post = state
|
||||
.posts
|
||||
.get(&slug)
|
||||
.ok_or(WebsiteError::NotFound)?;
|
||||
|
||||
let res = render_post(&state.tera, post).await?;
|
||||
|
||||
Ok(Html(res))
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn index(
|
||||
Extension(state): Extension<Arc<State>>,
|
||||
) -> Result<Html<String>, WebsiteError> {
|
||||
let mut ctx = tera::Context::new();
|
||||
|
||||
let mut posts = state.posts.values().collect::<Vec<&Post>>();
|
||||
|
||||
posts.sort_by_key(|p| &p.date);
|
||||
posts.reverse();
|
||||
|
||||
ctx.insert("page.title", "Posts");
|
||||
ctx.insert("posts", &posts);
|
||||
|
||||
let res = match state.tera.render("posts_index.html", &ctx) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
error!("failed to render posts index: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Html(res))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tera::Tera;
|
||||
|
||||
#[tokio::test]
|
||||
async fn render_all() {
|
||||
let tera = Tera::new("templates/**/*").unwrap();
|
||||
let posts = super::load_all().await.unwrap();
|
||||
|
||||
for (_slug, post) in posts {
|
||||
super::render_post(&tera, &post).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "partials/head.html" %}
|
||||
{% include "partials/head.html" -%}
|
||||
<body>
|
||||
<header>
|
||||
{% include "partials/header.html" %}
|
||||
{% include "partials/header.html" -%}
|
||||
</header>
|
||||
<hr>
|
||||
<main>
|
||||
{% block main %}{% endblock main %}
|
||||
{% block main %}{% endblock main -%}
|
||||
</main>
|
||||
<hr>
|
||||
<footer>
|
||||
{% include "partials/footer.html" %}
|
||||
{% include "partials/footer.html" -%}
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -7,9 +7,9 @@
|
|||
<ul>
|
||||
<li>static content ✅</li>
|
||||
<li>sass compilation</li>
|
||||
<li>post metadata (frontmatter)</li>
|
||||
<li>post metadata (frontmatter) ✅</li>
|
||||
<li>rss/atom/jsonfeed</li>
|
||||
<li>proper error handling</li>
|
||||
<li>proper error handling ✅ (i guess??)</li>
|
||||
<li>other pages???</li>
|
||||
</ul>
|
||||
{% endblock main %}
|
|
@ -4,5 +4,9 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/static/site.css">
|
||||
<link rel="icon" href="/static/avatar.png" />
|
||||
<title>Document</title>
|
||||
{% if page.title -%}
|
||||
<title>{{ page.title }} | tollyx.net</title>
|
||||
{% else -%}
|
||||
<title>tollyx.net</title>
|
||||
{% endif -%}
|
||||
</head>
|
|
@ -1,5 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block main %}
|
||||
{{ content | safe }}
|
||||
{% endblock main %}
|
||||
{% block main -%}
|
||||
<article>
|
||||
{% if page.title -%}
|
||||
<h1>{{ page.title }}</h1>
|
||||
{% endif -%}
|
||||
{% if page.date -%}
|
||||
<small>Posted on <time datetime="{{ page.date }}">{{ page.date | date(format="%Y-%m-%d %H:%M") }}</time></small>
|
||||
{% endif -%}
|
||||
{{ page.content | safe -}}
|
||||
</article>
|
||||
{% endblock main -%}
|
15
templates/posts_index.html
Normal file
15
templates/posts_index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
{% block main -%}
|
||||
<h1>Posts</h1>
|
||||
<p>I occasionally write some stuff, it's quite rare but it does happen believe it or not.</p>
|
||||
<ul>
|
||||
{% for post in posts -%}
|
||||
<li><a href="{{post.absolute_path | safe}}">{% if post.date -%}
|
||||
<time datetime="{{ post.date }}">{{ post.date | date(format="%Y-%m-%d") }}</time> - {{ post.title -}}
|
||||
{% else -%}
|
||||
{{ post.title -}}
|
||||
{% endif -%}
|
||||
</a></li>
|
||||
{% endfor -%}
|
||||
</ul>
|
||||
{% endblock main -%}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block main %}
|
||||
<h1>posts</h1>
|
||||
<p>i occasionally write some stuff i guess</p>
|
||||
<ul>
|
||||
{% for post in posts %}
|
||||
<li><a href="/posts/{{post.name}}/">{{post.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock main %}
|
Loading…
Reference in a new issue