implement custom timestamp formats for resources, resolves #15

This commit is contained in:
zyl 2024-11-13 14:52:36 -08:00
parent 0233bf0dad
commit 5b09813f24
Signed by: zyl
SSH key fingerprint: SHA256:uxxbSXbdroP/OnKBGnEDk5q7EKB2razvstC/KmzdXXs
6 changed files with 62 additions and 38 deletions

View file

@ -109,6 +109,10 @@ the name of this resource type if it is plural.
how many resources of this type to display per page when content is paginated.
### `timestamp_format`
format to display timestamps as, as defined by version 2 of [the rust time crate's format description](https://time-rs.github.io/book/api/format-description.html).
## defining a resource
resources are made up of markdown files with yaml front matter. for instance:
@ -154,3 +158,11 @@ whether the resource is a draft and should be excluded from normal builds. defau
### other properties
resources may add extra properties which will get passed to the various resource templates later. simply add the property like it was any other property.
## extra properties
in addition to the resource properties, resources may receive additional properties from webdog as follows:
### `readable_timestamp`
the resource's timestamp in the timestamp format provided in the resource type's config.

View file

@ -1,19 +1,19 @@
{% extends "base.tera" %}
{% block content %}
<div>
<h1>{{ title }}</h1>
<span>published {{ timestamp }}</span>
{% if draft %}
<h1>{{ data.title }}</h1>
<span>published {{ data.readable_timestamp }}</span>
{% if data.draft %}
<h2>DRAFT</h2>
{% endif %}
<p>{{ desc }}</p>
<p>{{ data.desc }}</p>
<div>
{{ content | safe }}
{{ data.content | safe }}
</div>
<hr />
<h3>tags</h3>
<div>
{% for tag in tags %}
{% for tag in data.tags %}
<a href="/!!RESOURCE_TYPE!!/tag/{{tag}}">{{ tag }}</a>{% if not loop.last %},{% endif %}
{% endfor %}
</div>

View file

@ -1,3 +1,4 @@
use itertools::Itertools;
use lol_html::{element, RewriteStrSettings};
use serde::{Deserialize, Serialize};
@ -91,22 +92,31 @@ fn resource_list_outside(
let data: ResourceListData = serde_yml::from_value(data.inner.clone())?;
let res_builder = builder
.resource_builders
.get(&data.resource)
.ok_or_else(|| eyre::eyre!("missing resource builder: {}", data.resource))?;
let resource_list = builder.tera.render(
&data.template,
&tera::Context::from_serialize(ResourceListTemplateData {
resources: builder
.resource_builders
.get(&data.resource)
.ok_or_else(|| eyre::eyre!("missing resource builder: {}", data.resource))?
resources: res_builder
.loaded_metadata
.iter()
.take(data.count)
.map(|(id, v)| ResourceTemplateData {
.map(|(id, v)| {
crate::util::format_timestamp(
v.data().timestamp,
&res_builder.config.timestamp_format,
)
.map(|ts| (id, v, ts))
})
.map_ok(|(id, v, ts)| ResourceTemplateData {
resource: v,
id: id.clone(),
timestamp: v.data().timestamp,
readable_timestamp: ts,
})
.collect(),
.collect::<eyre::Result<Vec<_>>>()?,
})?,
)?;

View file

@ -222,6 +222,7 @@ fn main() -> eyre::Result<()> {
tag_list_title: format!("{name} tags"),
resource_name_plural: plural,
resources_per_page: 3,
timestamp_format: "[weekday], [month repr:long] [day], [year]".to_string(),
};
config.resources.insert(id.clone(), resource_config);

View file

@ -7,11 +7,12 @@ use eyre::Context;
use itertools::Itertools;
use pulldown_cmark::{Options, Parser};
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
use serde::{Deserialize, Serialize, Serializer};
use serde::{Deserialize, Serialize};
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
use crate::{
builder::SiteBuilder, frontmatter::FrontMatterRequired, link_list::Link, PageMetadata,
builder::SiteBuilder, frontmatter::FrontMatterRequired, link_list::Link,
util::format_timestamp, PageMetadata,
};
/// Metadata for resources.
@ -43,24 +44,8 @@ pub struct ResourceTemplateData<'r> {
pub resource: &'r FrontMatterRequired<ResourceMetadata>,
/// The resource's ID.
pub id: String,
/// The resource's timestamp. Duplicated to change serialization method.
#[serde(serialize_with = "ResourceTemplateData::timestamp_formatter")]
pub timestamp: OffsetDateTime,
}
impl<'r> ResourceTemplateData<'r> {
fn timestamp_formatter<S>(timestamp: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let out = timestamp
.format(
&time::format_description::parse("[weekday], [month repr:long] [day], [year]")
.expect("Should never fail"),
)
.expect("Should never fail");
serializer.serialize_str(&out)
}
/// The resource's timestamp in a readable format.
pub readable_timestamp: String,
}
/// struct for adding custom meta content embeds
@ -121,7 +106,7 @@ struct ResourceListTemplateData<'r> {
}
/// Config for the resource builder.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceBuilderConfig {
/// Path to where the resources should be loaded from.
pub source_path: String,
@ -145,6 +130,8 @@ pub struct ResourceBuilderConfig {
pub resource_name_plural: String,
/// The number of resources to display on a single page.
pub resources_per_page: usize,
/// The format to use for the readable timestamp.
pub timestamp_format: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@ -158,7 +145,7 @@ pub struct ResourceRSSBuilderConfig {
}
/// Helper to genericize resource building.
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct ResourceBuilder {
/// The builder's config.
pub config: ResourceBuilderConfig,
@ -277,7 +264,10 @@ impl ResourceBuilder {
ResourceTemplateData {
resource,
id,
timestamp: resource.data.as_ref().expect("should never fail").timestamp,
readable_timestamp: format_timestamp(
resource.data().timestamp,
&self.config.timestamp_format,
)?,
},
)?;
std::fs::write(out_path, out)?;
@ -307,7 +297,10 @@ impl ResourceBuilder {
data.push(ResourceTemplateData {
resource,
id: id.clone(),
timestamp: resource.data().timestamp,
readable_timestamp: format_timestamp(
resource.data().timestamp,
&self.config.timestamp_format,
)?,
});
}
@ -435,7 +428,7 @@ impl ResourceBuilder {
.to_string(),
))
.description(resource.resource.data().desc.clone())
.pub_date(Some(resource.timestamp.format(&Rfc2822)?))
.pub_date(Some(resource.resource.data().timestamp.format(&Rfc2822)?))
.content(Some(builder.tera.render(
&rss.template,
&tera::Context::from_serialize(resource)?,

View file

@ -2,6 +2,8 @@
use std::path::Path;
use time::OffsetDateTime;
/// Simple helper to remove the contents of a directory without removing the directory itself.
pub fn remove_dir_contents(path: &Path) -> eyre::Result<()> {
for entry in path.read_dir()? {
@ -16,3 +18,9 @@ pub fn remove_dir_contents(path: &Path) -> eyre::Result<()> {
Ok(())
}
/// Helper to format a timestamp according to the given format.
pub fn format_timestamp(ts: OffsetDateTime, format: &str) -> eyre::Result<String> {
let fmt = time::format_description::parse_borrowed::<2>(format)?;
Ok(ts.format(&fmt)?)
}