forked from slonk/bingus-blog
markdown_render.rs, post/mod.rs: remove unnecessary function and rename render_with_config
main.rs, config.rs, view_post.html: add markdown_access main.rs: change AppError to PostError cache.rs: replace development string config.rs: put syntect options in their own struct view_post.html: add markdown_access README.md: write the readme
This commit is contained in:
parent
c56a182d14
commit
2b3f935a98
9 changed files with 165 additions and 49 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -369,7 +369,6 @@ dependencies = [
|
||||||
"comrak",
|
"comrak",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
"fronma",
|
"fronma",
|
||||||
"futures-util",
|
|
||||||
"notify",
|
"notify",
|
||||||
"scc",
|
"scc",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -844,17 +843,6 @@ version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-macro"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.60",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
@ -874,11 +862,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-macro",
|
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2126,9 +2112,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.9"
|
version = "0.22.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.6",
|
"indexmap 2.2.6",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -32,7 +32,6 @@ color-eyre = "0.6.3"
|
||||||
comrak = { version = "0.22.0", features = ["syntect"] }
|
comrak = { version = "0.22.0", features = ["syntect"] }
|
||||||
console-subscriber = { version = "0.2.0", optional = true }
|
console-subscriber = { version = "0.2.0", optional = true }
|
||||||
fronma = "0.2.0"
|
fronma = "0.2.0"
|
||||||
futures-util = "0.3.30"
|
|
||||||
notify = "6.1.1"
|
notify = "6.1.1"
|
||||||
scc = "2.1.0"
|
scc = "2.1.0"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|
119
README.md
119
README.md
|
@ -2,7 +2,7 @@
|
||||||
title: "README"
|
title: "README"
|
||||||
description: "the README.md file of this project"
|
description: "the README.md file of this project"
|
||||||
author: "slonkazoid"
|
author: "slonkazoid"
|
||||||
created_at: 2024-04-18T01:15:26Z
|
created_at: 2024-04-18T04:15:26+03:00
|
||||||
---
|
---
|
||||||
|
|
||||||
# bingus-blog
|
# bingus-blog
|
||||||
|
@ -11,10 +11,123 @@ blazingly fast markdown blog software written in rust memory safe
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] finish writing this document
|
- [ ] RSS
|
||||||
- [ ] document config
|
- [x] finish writing this document
|
||||||
|
- [x] document config
|
||||||
- [ ] extend syntect options
|
- [ ] extend syntect options
|
||||||
- [ ] general cleanup of code
|
- [ ] general cleanup of code
|
||||||
|
- [ ] make `compress.rs` not suck
|
||||||
|
- [ ] better error reporting and pages
|
||||||
|
- [ ] better tracing
|
||||||
|
- [ ] cache cleanup task
|
||||||
|
- [ ] (de)compress cache with zstd on startup/shutdown
|
||||||
|
- [ ] make date parsing less strict
|
||||||
|
- [ ] make date formatting better
|
||||||
|
- [ ] clean up imports and require less features
|
||||||
- [x] be blazingly fast
|
- [x] be blazingly fast
|
||||||
- [x] 100+ MiB binary size
|
- [x] 100+ MiB binary size
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
the default configuration with comments looks like this
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# main settings
|
||||||
|
host = "0.0.0.0" # ip to listen on
|
||||||
|
port = 3000 # port to listen on
|
||||||
|
title = "bingus-blog" # title of the website
|
||||||
|
description = "blazingly fast markdown blog software written in rust memory safe" # description of the website
|
||||||
|
posts_dir = "posts" # where posts are stored
|
||||||
|
#cache_file = "..." # file to serialize the cache into on shutdown, and
|
||||||
|
# to deserialize from on startup. uncomment to enable
|
||||||
|
markdown_access = true # allow users to see the raw markdown of a post
|
||||||
|
|
||||||
|
[render] # rendering-specific settings
|
||||||
|
syntect.load_defaults = false # include default syntect themes
|
||||||
|
syntect.themes_dir = "themes" # directory to include themes from
|
||||||
|
syntect.theme = "Catppuccin Mocha" # theme file name (without `.tmTheme`)
|
||||||
|
|
||||||
|
[precompression] # precompression settings
|
||||||
|
enable = false # gzip every file in static/ on startup
|
||||||
|
watch = true # keep watching and gzip files as they change
|
||||||
|
```
|
||||||
|
|
||||||
|
you don't have to copy it from here, it's generated if it doesn't exist
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
build the application with `cargo`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
the executable will be located at `target/release/bingus-blog`.
|
||||||
|
|
||||||
|
### Building for another architecture
|
||||||
|
|
||||||
|
you can use the `--target` flag in `cargo build` for this purpose
|
||||||
|
|
||||||
|
building for `aarch64-unknown-linux-musl` (for example, a Redmi 5 Plus running postmarketOS):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# install the required packages to compile and link aarch64 binaries
|
||||||
|
sudo pacman -S aarch64-linux-gnu-gcc
|
||||||
|
export CC=aarch64-linux-gnu-gcc
|
||||||
|
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=$CC
|
||||||
|
cargo build --release --target=aarch64-unknown-linux-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
your executable will be located at `target/<target>/release/bingus-blog` this time.
|
||||||
|
|
||||||
|
## Writing Posts
|
||||||
|
|
||||||
|
posts are written in markdown. the requirements for a file to count as a post are:
|
||||||
|
|
||||||
|
1. the file must be in the root of the `posts` directory you configured
|
||||||
|
2. the file's name must end with the extension `.md`
|
||||||
|
3. the file's contents must begin with a valid [front matter](#front-matter)
|
||||||
|
|
||||||
|
this file counts as a valid post, and will show up if you just `git clone` and
|
||||||
|
`cargo r`. there is a symlink to this file from the default posts directory
|
||||||
|
|
||||||
|
## Front Matter
|
||||||
|
|
||||||
|
every post **must** begin with a **valid** front matter. else it wont be listed
|
||||||
|
in / & /posts, and when you navigate to it, you will be met with an error page.
|
||||||
|
the error page will tell you what the problem is.
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```md
|
||||||
|
---
|
||||||
|
title: "README"
|
||||||
|
description: "the README.md file of this project"
|
||||||
|
author: "slonkazoid"
|
||||||
|
created_at: 2024-04-18T04:15:26+03:00
|
||||||
|
#modified_at: ... # see above
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
only first 3 fields are required. if it can't find the other 2 fields, it will
|
||||||
|
get them from filesystem metadata. if you are on musl and you omit the
|
||||||
|
`created_at` field, it will just not show up
|
||||||
|
|
||||||
|
the dates must follow the [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339)
|
||||||
|
standard. examples of valid and invalid dates:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ 2024-04-18T01:15:26Z # valid
|
||||||
|
+ 2024-04-18T04:15:26+03:00 # valid (with timezone)
|
||||||
|
- 2024-04-18T04:15:26Z # invalid (missing Z)
|
||||||
|
- 2024-04-18T04:15Z # invalid (missing seconds)
|
||||||
|
- # everything else is also invalid
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routes
|
||||||
|
|
||||||
|
- `GET /`: index page, lists posts
|
||||||
|
- `GET /posts`: returns a list of all posts with metadata in JSON format
|
||||||
|
- `GET /posts/<name>`: view a post
|
||||||
|
- `GET /posts/<name>.md`: view the raw markdown of a post
|
||||||
|
- `GET /post/*`: redirects to `/posts/*`
|
||||||
|
|
|
@ -9,12 +9,17 @@ use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SyntectConfig {
|
||||||
|
pub load_defaults: bool,
|
||||||
|
pub themes_dir: Option<PathBuf>,
|
||||||
|
pub theme: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct RenderConfig {
|
pub struct RenderConfig {
|
||||||
pub syntect_load_defaults: bool,
|
pub syntect: SyntectConfig,
|
||||||
pub syntect_themes_dir: Option<PathBuf>,
|
|
||||||
pub syntect_theme: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "precompression")]
|
#[cfg(feature = "precompression")]
|
||||||
|
@ -37,6 +42,7 @@ pub struct Config {
|
||||||
#[cfg(feature = "precompression")]
|
#[cfg(feature = "precompression")]
|
||||||
pub precompression: PrecompressionConfig,
|
pub precompression: PrecompressionConfig,
|
||||||
pub cache_file: Option<PathBuf>,
|
pub cache_file: Option<PathBuf>,
|
||||||
|
pub markdown_access: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -51,6 +57,7 @@ impl Default for Config {
|
||||||
#[cfg(feature = "precompression")]
|
#[cfg(feature = "precompression")]
|
||||||
precompression: Default::default(),
|
precompression: Default::default(),
|
||||||
cache_file: None,
|
cache_file: None,
|
||||||
|
markdown_access: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,9 +65,11 @@ impl Default for Config {
|
||||||
impl Default for RenderConfig {
|
impl Default for RenderConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
syntect_load_defaults: false,
|
syntect: SyntectConfig {
|
||||||
syntect_themes_dir: Some("themes".into()),
|
load_defaults: false,
|
||||||
syntect_theme: Some("Catppuccin Mocha".into()),
|
themes_dir: Some("themes".into()),
|
||||||
|
theme: Some("Catppuccin Mocha".into()),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -63,9 +63,10 @@ struct ViewPostTemplate {
|
||||||
meta: PostMetadata,
|
meta: PostMetadata,
|
||||||
rendered: String,
|
rendered: String,
|
||||||
rendered_in: RenderStats,
|
rendered_in: RenderStats,
|
||||||
|
markdown_access: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppResult<T> = Result<T, AppError>;
|
type AppResult<T> = Result<T, PostError>;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
enum AppError {
|
enum AppError {
|
||||||
|
@ -107,16 +108,27 @@ async fn index(State(state): State<ArcState>) -> AppResult<IndexTemplate> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post(State(state): State<ArcState>, Path(name): Path<String>) -> AppResult<Response> {
|
async fn post(State(state): State<ArcState>, Path(name): Path<String>) -> AppResult<Response> {
|
||||||
let post = state.posts.get_post(&name).await?;
|
if name.ends_with(".md") && state.config.markdown_access {
|
||||||
|
let mut file = tokio::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(state.config.posts_dir.join(&name))
|
||||||
|
.await?;
|
||||||
|
|
||||||
let post = ViewPostTemplate {
|
let mut buf = Vec::new();
|
||||||
|
file.read_to_end(&mut buf).await?;
|
||||||
|
|
||||||
|
Ok(([("content-type", "text/plain")], buf).into_response())
|
||||||
|
} else {
|
||||||
|
let post = state.posts.get_post(&name).await?;
|
||||||
|
let page = ViewPostTemplate {
|
||||||
meta: post.0,
|
meta: post.0,
|
||||||
rendered: post.1,
|
rendered: post.1,
|
||||||
rendered_in: post.2,
|
rendered_in: post.2,
|
||||||
}
|
markdown_access: state.config.markdown_access,
|
||||||
.into_response();
|
};
|
||||||
|
|
||||||
Ok(post)
|
Ok(page.into_response())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn all_posts(State(state): State<ArcState>) -> AppResult<Json<Vec<PostMetadata>>> {
|
async fn all_posts(State(state): State<ArcState>) -> AppResult<Json<Vec<PostMetadata>>> {
|
||||||
|
|
|
@ -3,7 +3,6 @@ use std::sync::{Arc, OnceLock, RwLock};
|
||||||
use comrak::markdown_to_html_with_plugins;
|
use comrak::markdown_to_html_with_plugins;
|
||||||
use comrak::plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder};
|
use comrak::plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder};
|
||||||
use comrak::ComrakOptions;
|
use comrak::ComrakOptions;
|
||||||
use comrak::Plugins;
|
|
||||||
use comrak::RenderPlugins;
|
use comrak::RenderPlugins;
|
||||||
use syntect::highlighting::ThemeSet;
|
use syntect::highlighting::ThemeSet;
|
||||||
|
|
||||||
|
@ -18,22 +17,22 @@ fn syntect_adapter(config: &RenderConfig) -> Arc<SyntectAdapter> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_syntect(config: &RenderConfig) -> Arc<SyntectAdapter> {
|
fn build_syntect(config: &RenderConfig) -> Arc<SyntectAdapter> {
|
||||||
let mut theme_set = if config.syntect_load_defaults {
|
let mut theme_set = if config.syntect.load_defaults {
|
||||||
ThemeSet::load_defaults()
|
ThemeSet::load_defaults()
|
||||||
} else {
|
} else {
|
||||||
ThemeSet::new()
|
ThemeSet::new()
|
||||||
};
|
};
|
||||||
if let Some(path) = config.syntect_themes_dir.as_ref() {
|
if let Some(path) = config.syntect.themes_dir.as_ref() {
|
||||||
theme_set.add_from_folder(path).unwrap();
|
theme_set.add_from_folder(path).unwrap();
|
||||||
}
|
}
|
||||||
let mut builder = SyntectAdapterBuilder::new().theme_set(theme_set);
|
let mut builder = SyntectAdapterBuilder::new().theme_set(theme_set);
|
||||||
if let Some(theme) = config.syntect_theme.as_ref() {
|
if let Some(theme) = config.syntect.theme.as_ref() {
|
||||||
builder = builder.theme(theme);
|
builder = builder.theme(theme);
|
||||||
}
|
}
|
||||||
Arc::new(builder.build())
|
Arc::new(builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_with_config(markdown: &str, config: &RenderConfig, front_matter: bool) -> String {
|
pub fn render(markdown: &str, config: &RenderConfig, front_matter: bool) -> String {
|
||||||
let mut options = ComrakOptions::default();
|
let mut options = ComrakOptions::default();
|
||||||
options.extension.table = true;
|
options.extension.table = true;
|
||||||
options.extension.autolink = true;
|
options.extension.autolink = true;
|
||||||
|
@ -55,10 +54,5 @@ pub fn render_with_config(markdown: &str, config: &RenderConfig, front_matter: b
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
render(markdown, &options, &plugins)
|
markdown_to_html_with_plugins(markdown, &options, &plugins)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(markdown: &str, options: &ComrakOptions, plugins: &Plugins) -> String {
|
|
||||||
// TODO: post-processing
|
|
||||||
markdown_to_html_with_plugins(markdown, options, plugins)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ impl<'de> Deserialize<'de> for Cache {
|
||||||
type Value = Cache;
|
type Value = Cache;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
write!(formatter, "meow")
|
write!(formatter, "expected a map")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||||
|
|
|
@ -13,7 +13,7 @@ use tokio::io::AsyncReadExt;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::config::RenderConfig;
|
use crate::config::RenderConfig;
|
||||||
use crate::markdown_render;
|
use crate::markdown_render::render;
|
||||||
use crate::post::cache::Cache;
|
use crate::post::cache::Cache;
|
||||||
use crate::PostError;
|
use crate::PostError;
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ impl PostManager {
|
||||||
let parsing = parsing_start.elapsed();
|
let parsing = parsing_start.elapsed();
|
||||||
|
|
||||||
let before_render = Instant::now();
|
let before_render = Instant::now();
|
||||||
let rendered_markdown = markdown_render::render_with_config(body, &self.config, false);
|
let rendered_markdown = render(body, &self.config, false);
|
||||||
let post = Post {
|
let post = Post {
|
||||||
meta: &metadata,
|
meta: &metadata,
|
||||||
rendered_markdown,
|
rendered_markdown,
|
||||||
|
|
|
@ -30,6 +30,9 @@
|
||||||
{% when RenderStats::Cached(total) %}
|
{% when RenderStats::Cached(total) %}
|
||||||
retrieved from cache in {{ total|duration }}
|
retrieved from cache in {{ total|duration }}
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
|
{% if markdown_access %}
|
||||||
|
- <a href="/posts/{{ meta.name }}.md">view raw</a>
|
||||||
|
{% endif %}
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue