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 crate::post::PostMetadata;
|
||||
|
||||
pub fn date<T: TimeZone>(date: &DateTime<T>) -> Result<String, askama::Error> {
|
||||
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> {
|
||||
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::error::{AppResult, PostError};
|
||||
use crate::post::cache::{Cache, CACHE_VERSION};
|
||||
use crate::post::{PostManager, PostMetadata, RenderStats};
|
||||
|
||||
type ArcState = Arc<AppState>;
|
||||
|
@ -179,8 +180,13 @@ async fn main() -> eyre::Result<()> {
|
|||
.context("failed to read cache file")?;
|
||||
buf
|
||||
};
|
||||
let cache =
|
||||
let mut cache: 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(
|
||||
config.dirs.posts.clone(),
|
||||
config.render.clone(),
|
||||
|
|
|
@ -7,6 +7,9 @@ use tracing::{debug, instrument};
|
|||
use crate::config::RenderConfig;
|
||||
use crate::post::PostMetadata;
|
||||
|
||||
/// do not persist cache if this version number changed
|
||||
pub const CACHE_VERSION: u16 = 1;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct CacheValue {
|
||||
pub metadata: PostMetadata,
|
||||
|
@ -15,8 +18,14 @@ pub struct CacheValue {
|
|||
config_hash: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
pub struct Cache(HashMap<String, CacheValue>);
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct Cache(HashMap<String, CacheValue>, u16);
|
||||
|
||||
impl Default for Cache {
|
||||
fn default() -> Self {
|
||||
Self(Default::default(), CACHE_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub async fn lookup(
|
||||
|
@ -117,4 +126,9 @@ impl Cache {
|
|||
let new_size = self.0.len();
|
||||
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::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
@ -27,7 +28,7 @@ struct FrontMatter {
|
|||
pub created_at: Option<DateTime<Utc>>,
|
||||
pub modified_at: Option<DateTime<Utc>>,
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
pub tags: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl FrontMatter {
|
||||
|
@ -45,7 +46,7 @@ impl FrontMatter {
|
|||
icon: self.icon,
|
||||
created_at: self.created_at.or_else(|| created.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,
|
||||
code {
|
||||
/* please have one at least one good monospace font */
|
||||
font-family: "Hack", "Hack Nerd Font", "JetBrains Mono",
|
||||
"JetBrainsMono Nerd Font", "Ubuntu Mono", monospace, sans-serif;
|
||||
font-family: "Hack", "Hack Nerd Font", "JetBrains Mono", "JetBrainsMono Nerd Font", "Ubuntu Mono", monospace, sans-serif;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
@ -81,6 +80,10 @@ footer {
|
|||
opacity: 0.65;
|
||||
}
|
||||
|
||||
div.post {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* BEGIN cool effect everyone liked */
|
||||
|
||||
body {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{%- import "macros.askama" as macros -%}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -17,20 +18,23 @@
|
|||
<!-- prettier-ignore -->
|
||||
<div>
|
||||
{% for post in posts %}
|
||||
<p>
|
||||
<div class="post">
|
||||
<a href="/posts/{{ post.name }}"><b>{{ post.title }}</b></a>
|
||||
<span class="post-author">- by {{ post.author }}</span>
|
||||
<br />
|
||||
{{ post.description }}<br />
|
||||
{% 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 }}
|
||||
{% when None %} {% endmatch %}
|
||||
</p>
|
||||
{% call macros::table(post) %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</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>
|
||||
</body>
|
||||
</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">
|
||||
{{ meta.title }}
|
||||
<span class="post-author">- by {{ meta.author }}</span>
|
||||
|
@ -6,12 +7,7 @@
|
|||
<p>
|
||||
<!-- prettier-ignore -->
|
||||
<div>
|
||||
{% match meta.created_at %} {% when Some(created_at) %}
|
||||
written: {{ created_at|date }}<br />
|
||||
{% when None %} {% endmatch %}
|
||||
{% match meta.modified_at %} {% when Some(modified_at) %}
|
||||
last modified: {{ modified_at|date }}
|
||||
{% when None %} {% endmatch %}
|
||||
{% call macros::table(meta) %}
|
||||
</div>
|
||||
<a href="/posts/{{ meta.name }}">link</a><br />
|
||||
<a href="/">back to home</a>
|
||||
|
|
Loading…
Reference in a new issue