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",
|
||||
"console-subscriber",
|
||||
"fronma",
|
||||
"futures-util",
|
||||
"notify",
|
||||
"scc",
|
||||
"serde",
|
||||
|
@ -844,17 +843,6 @@ version = "0.3.30"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.30"
|
||||
|
@ -874,11 +862,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2126,9 +2112,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.9"
|
||||
version = "0.22.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
||||
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
|
|
|
@ -32,7 +32,6 @@ color-eyre = "0.6.3"
|
|||
comrak = { version = "0.22.0", features = ["syntect"] }
|
||||
console-subscriber = { version = "0.2.0", optional = true }
|
||||
fronma = "0.2.0"
|
||||
futures-util = "0.3.30"
|
||||
notify = "6.1.1"
|
||||
scc = "2.1.0"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
|
119
README.md
119
README.md
|
@ -2,7 +2,7 @@
|
|||
title: "README"
|
||||
description: "the README.md file of this project"
|
||||
author: "slonkazoid"
|
||||
created_at: 2024-04-18T01:15:26Z
|
||||
created_at: 2024-04-18T04:15:26+03:00
|
||||
---
|
||||
|
||||
# bingus-blog
|
||||
|
@ -11,10 +11,123 @@ blazingly fast markdown blog software written in rust memory safe
|
|||
|
||||
## TODO
|
||||
|
||||
- [ ] finish writing this document
|
||||
- [ ] document config
|
||||
- [ ] RSS
|
||||
- [x] finish writing this document
|
||||
- [x] document config
|
||||
- [ ] extend syntect options
|
||||
- [ ] 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] 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 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)]
|
||||
#[serde(default)]
|
||||
pub struct RenderConfig {
|
||||
pub syntect_load_defaults: bool,
|
||||
pub syntect_themes_dir: Option<PathBuf>,
|
||||
pub syntect_theme: Option<String>,
|
||||
pub syntect: SyntectConfig,
|
||||
}
|
||||
|
||||
#[cfg(feature = "precompression")]
|
||||
|
@ -37,6 +42,7 @@ pub struct Config {
|
|||
#[cfg(feature = "precompression")]
|
||||
pub precompression: PrecompressionConfig,
|
||||
pub cache_file: Option<PathBuf>,
|
||||
pub markdown_access: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -51,6 +57,7 @@ impl Default for Config {
|
|||
#[cfg(feature = "precompression")]
|
||||
precompression: Default::default(),
|
||||
cache_file: None,
|
||||
markdown_access: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,9 +65,11 @@ impl Default for Config {
|
|||
impl Default for RenderConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
syntect_load_defaults: false,
|
||||
syntect_themes_dir: Some("themes".into()),
|
||||
syntect_theme: Some("Catppuccin Mocha".into()),
|
||||
syntect: SyntectConfig {
|
||||
load_defaults: false,
|
||||
themes_dir: Some("themes".into()),
|
||||
theme: Some("Catppuccin Mocha".into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
src/main.rs
30
src/main.rs
|
@ -63,9 +63,10 @@ struct ViewPostTemplate {
|
|||
meta: PostMetadata,
|
||||
rendered: String,
|
||||
rendered_in: RenderStats,
|
||||
markdown_access: bool,
|
||||
}
|
||||
|
||||
type AppResult<T> = Result<T, AppError>;
|
||||
type AppResult<T> = Result<T, PostError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
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> {
|
||||
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 {
|
||||
meta: post.0,
|
||||
rendered: post.1,
|
||||
rendered_in: post.2,
|
||||
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,
|
||||
rendered: post.1,
|
||||
rendered_in: post.2,
|
||||
markdown_access: state.config.markdown_access,
|
||||
};
|
||||
|
||||
Ok(page.into_response())
|
||||
}
|
||||
.into_response();
|
||||
|
||||
Ok(post)
|
||||
}
|
||||
|
||||
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::plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder};
|
||||
use comrak::ComrakOptions;
|
||||
use comrak::Plugins;
|
||||
use comrak::RenderPlugins;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
|
||||
|
@ -18,22 +17,22 @@ fn syntect_adapter(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()
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
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();
|
||||
options.extension.table = true;
|
||||
options.extension.autolink = true;
|
||||
|
@ -55,10 +54,5 @@ pub fn render_with_config(markdown: &str, config: &RenderConfig, front_matter: b
|
|||
.build()
|
||||
.unwrap();
|
||||
|
||||
render(markdown, &options, &plugins)
|
||||
}
|
||||
|
||||
pub fn render(markdown: &str, options: &ComrakOptions, plugins: &Plugins) -> String {
|
||||
// TODO: post-processing
|
||||
markdown_to_html_with_plugins(markdown, options, plugins)
|
||||
markdown_to_html_with_plugins(markdown, &options, &plugins)
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ impl<'de> Deserialize<'de> for Cache {
|
|||
type Value = Cache;
|
||||
|
||||
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>
|
||||
|
|
|
@ -13,7 +13,7 @@ use tokio::io::AsyncReadExt;
|
|||
use tracing::warn;
|
||||
|
||||
use crate::config::RenderConfig;
|
||||
use crate::markdown_render;
|
||||
use crate::markdown_render::render;
|
||||
use crate::post::cache::Cache;
|
||||
use crate::PostError;
|
||||
|
||||
|
@ -117,7 +117,7 @@ impl PostManager {
|
|||
let parsing = parsing_start.elapsed();
|
||||
|
||||
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 {
|
||||
meta: &metadata,
|
||||
rendered_markdown,
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
{% when RenderStats::Cached(total) %}
|
||||
retrieved from cache in {{ total|duration }}
|
||||
{% endmatch %}
|
||||
{% if markdown_access %}
|
||||
- <a href="/posts/{{ meta.name }}.md">view raw</a>
|
||||
{% endif %}
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue