forked from slonk/bingus-blog
add tags and cache versioning
This commit is contained in:
parent
37c344b53c
commit
d466f531eb
8 changed files with 91 additions and 25 deletions
|
@ -1,7 +1,9 @@
|
||||||
use std::time::Duration;
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
|
||||||
use chrono::{DateTime, TimeZone};
|
use chrono::{DateTime, TimeZone};
|
||||||
|
|
||||||
|
use crate::post::PostMetadata;
|
||||||
|
|
||||||
pub fn date<T: TimeZone>(date: &DateTime<T>) -> Result<String, askama::Error> {
|
pub fn date<T: TimeZone>(date: &DateTime<T>) -> Result<String, askama::Error> {
|
||||||
Ok(date.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
|
Ok(date.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
|
||||||
}
|
}
|
||||||
|
@ -9,3 +11,24 @@ pub fn date<T: TimeZone>(date: &DateTime<T>) -> Result<String, askama::Error> {
|
||||||
pub fn duration(duration: &&Duration) -> Result<String, askama::Error> {
|
pub fn duration(duration: &&Duration) -> Result<String, askama::Error> {
|
||||||
Ok(format!("{:?}", duration))
|
Ok(format!("{:?}", duration))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn collect_tags(posts: &Vec<PostMetadata>) -> Result<Vec<(String, u64)>, askama::Error> {
|
||||||
|
let mut tags = HashMap::new();
|
||||||
|
|
||||||
|
for post in posts {
|
||||||
|
for tag in &post.tags {
|
||||||
|
if let Some((existing_tag, count)) = tags.remove_entry(tag) {
|
||||||
|
tags.insert(existing_tag, count + 1);
|
||||||
|
} else {
|
||||||
|
tags.insert(tag.clone(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tags: Vec<(String, u64)> = tags.into_iter().collect();
|
||||||
|
|
||||||
|
tags.sort_unstable_by_key(|(v, _)| v.clone());
|
||||||
|
tags.sort_by_key(|(_, v)| -(*v as i64));
|
||||||
|
|
||||||
|
Ok(tags)
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::error::{AppResult, PostError};
|
use crate::error::{AppResult, PostError};
|
||||||
|
use crate::post::cache::{Cache, CACHE_VERSION};
|
||||||
use crate::post::{PostManager, PostMetadata, RenderStats};
|
use crate::post::{PostManager, PostMetadata, RenderStats};
|
||||||
|
|
||||||
type ArcState = Arc<AppState>;
|
type ArcState = Arc<AppState>;
|
||||||
|
@ -179,8 +180,13 @@ async fn main() -> eyre::Result<()> {
|
||||||
.context("failed to read cache file")?;
|
.context("failed to read cache file")?;
|
||||||
buf
|
buf
|
||||||
};
|
};
|
||||||
let cache =
|
let mut cache: Cache =
|
||||||
bitcode::deserialize(serialized.as_slice()).context("failed to parse cache")?;
|
bitcode::deserialize(serialized.as_slice()).context("failed to parse cache")?;
|
||||||
|
if cache.version() < CACHE_VERSION {
|
||||||
|
warn!("cache version changed, clearing cache");
|
||||||
|
cache = Cache::default();
|
||||||
|
};
|
||||||
|
|
||||||
Ok::<PostManager, color_eyre::Report>(PostManager::new_with_cache(
|
Ok::<PostManager, color_eyre::Report>(PostManager::new_with_cache(
|
||||||
config.dirs.posts.clone(),
|
config.dirs.posts.clone(),
|
||||||
config.render.clone(),
|
config.render.clone(),
|
||||||
|
|
|
@ -7,6 +7,9 @@ use tracing::{debug, instrument};
|
||||||
use crate::config::RenderConfig;
|
use crate::config::RenderConfig;
|
||||||
use crate::post::PostMetadata;
|
use crate::post::PostMetadata;
|
||||||
|
|
||||||
|
/// do not persist cache if this version number changed
|
||||||
|
pub const CACHE_VERSION: u16 = 1;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct CacheValue {
|
pub struct CacheValue {
|
||||||
pub metadata: PostMetadata,
|
pub metadata: PostMetadata,
|
||||||
|
@ -15,8 +18,14 @@ pub struct CacheValue {
|
||||||
config_hash: u64,
|
config_hash: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Cache(HashMap<String, CacheValue>);
|
pub struct Cache(HashMap<String, CacheValue>, u16);
|
||||||
|
|
||||||
|
impl Default for Cache {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Default::default(), CACHE_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
pub async fn lookup(
|
pub async fn lookup(
|
||||||
|
@ -117,4 +126,9 @@ impl Cache {
|
||||||
let new_size = self.0.len();
|
let new_size = self.0.len();
|
||||||
debug!("removed {i} entries ({old_size} -> {new_size} entries)");
|
debug!("removed {i} entries ({old_size} -> {new_size} entries)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn version(&self) -> u16 {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
mod cache;
|
pub mod cache;
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::{Duration, Instant, SystemTime};
|
use std::time::{Duration, Instant, SystemTime};
|
||||||
|
@ -27,7 +28,7 @@ struct FrontMatter {
|
||||||
pub created_at: Option<DateTime<Utc>>,
|
pub created_at: Option<DateTime<Utc>>,
|
||||||
pub modified_at: Option<DateTime<Utc>>,
|
pub modified_at: Option<DateTime<Utc>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tags: Vec<String>,
|
pub tags: BTreeSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrontMatter {
|
impl FrontMatter {
|
||||||
|
@ -45,7 +46,7 @@ impl FrontMatter {
|
||||||
icon: self.icon,
|
icon: self.icon,
|
||||||
created_at: self.created_at.or_else(|| created.map(|t| t.into())),
|
created_at: self.created_at.or_else(|| created.map(|t| t.into())),
|
||||||
modified_at: self.modified_at.or_else(|| modified.map(|t| t.into())),
|
modified_at: self.modified_at.or_else(|| modified.map(|t| t.into())),
|
||||||
tags: self.tags,
|
tags: self.tags.into_iter().collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,7 @@
|
||||||
:root,
|
:root,
|
||||||
code {
|
code {
|
||||||
/* please have one at least one good monospace font */
|
/* please have one at least one good monospace font */
|
||||||
font-family: "Hack", "Hack Nerd Font", "JetBrains Mono",
|
font-family: "Hack", "Hack Nerd Font", "JetBrains Mono", "JetBrainsMono Nerd Font", "Ubuntu Mono", monospace, sans-serif;
|
||||||
"JetBrainsMono Nerd Font", "Ubuntu Mono", monospace, sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
@ -81,6 +80,10 @@ footer {
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.post {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
/* BEGIN cool effect everyone liked */
|
/* BEGIN cool effect everyone liked */
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{%- import "macros.askama" as macros -%}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
@ -17,20 +18,23 @@
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div>
|
<div>
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
<p>
|
<div class="post">
|
||||||
<a href="/posts/{{ post.name }}"><b>{{ post.title }}</b></a>
|
<a href="/posts/{{ post.name }}"><b>{{ post.title }}</b></a>
|
||||||
<span class="post-author">- by {{ post.author }}</span>
|
<span class="post-author">- by {{ post.author }}</span>
|
||||||
<br />
|
<br />
|
||||||
{{ post.description }}<br />
|
{{ post.description }}<br />
|
||||||
{% match post.created_at %} {% when Some(created_at) %}
|
{% call macros::table(post) %}
|
||||||
written: {{ created_at|date }}<br />
|
</div>
|
||||||
{% when None %} {% endmatch %}
|
|
||||||
{% match post.modified_at %} {% when Some(modified_at) %}
|
|
||||||
last modified: {{ modified_at|date }}
|
|
||||||
{% when None %} {% endmatch %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% let tags = posts|collect_tags %}<!-- prettier-br -->
|
||||||
|
{% if !tags.is_empty() %}
|
||||||
|
<h2>tags</h2>
|
||||||
|
{% endif %}<!-- prettier-br -->
|
||||||
|
{% for tag in tags %}
|
||||||
|
<a href="/?tag={{ tag.0 }}" title="view all posts with this tag">{{ tag.0 }}</a>
|
||||||
|
<span class="post-author">- {{ tag.1 }} post{% if tag.1 != 1 %}s{%endif %}</span><br />
|
||||||
|
{% endfor %}
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
19
templates/macros.askama
Normal file
19
templates/macros.askama
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% macro table(post) %}
|
||||||
|
{% match post.created_at %}
|
||||||
|
{% when Some(created_at) %}
|
||||||
|
written: {{ created_at|date }}<br />
|
||||||
|
{% when None %}
|
||||||
|
{% endmatch %}
|
||||||
|
{% match post.modified_at %}
|
||||||
|
{% when Some(modified_at) %}
|
||||||
|
last modified: {{ modified_at|date }}<br />
|
||||||
|
{% when None %}
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
|
{% if !post.tags.is_empty() %}
|
||||||
|
tags:
|
||||||
|
{% for tag in post.tags %}
|
||||||
|
<a href="/?tag={{ tag }}" title="view all posts with this tag">{{ tag }}</a>
|
||||||
|
{% endfor %}<br />
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
|
@ -1,3 +1,4 @@
|
||||||
|
{%- import "macros.askama" as macros -%}
|
||||||
<h1 class="post-title">
|
<h1 class="post-title">
|
||||||
{{ meta.title }}
|
{{ meta.title }}
|
||||||
<span class="post-author">- by {{ meta.author }}</span>
|
<span class="post-author">- by {{ meta.author }}</span>
|
||||||
|
@ -6,12 +7,7 @@
|
||||||
<p>
|
<p>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<div>
|
<div>
|
||||||
{% match meta.created_at %} {% when Some(created_at) %}
|
{% call macros::table(meta) %}
|
||||||
written: {{ created_at|date }}<br />
|
|
||||||
{% when None %} {% endmatch %}
|
|
||||||
{% match meta.modified_at %} {% when Some(modified_at) %}
|
|
||||||
last modified: {{ modified_at|date }}
|
|
||||||
{% when None %} {% endmatch %}
|
|
||||||
</div>
|
</div>
|
||||||
<a href="/posts/{{ meta.name }}">link</a><br />
|
<a href="/posts/{{ meta.name }}">link</a><br />
|
||||||
<a href="/">back to home</a>
|
<a href="/">back to home</a>
|
||||||
|
|
Loading…
Reference in a new issue