mirror of
https://github.com/zyllian/webdog.git
synced 2025-05-10 02:26:42 -07:00
implement custom timestamp formats for resources, resolves #15
This commit is contained in:
parent
0233bf0dad
commit
5b09813f24
6 changed files with 62 additions and 38 deletions
|
@ -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.
|
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
|
## defining a resource
|
||||||
|
|
||||||
resources are made up of markdown files with yaml front matter. for instance:
|
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
|
### 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.
|
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.
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
{% extends "base.tera" %}
|
{% extends "base.tera" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<div>
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ data.title }}</h1>
|
||||||
<span>published {{ timestamp }}</span>
|
<span>published {{ data.readable_timestamp }}</span>
|
||||||
{% if draft %}
|
{% if data.draft %}
|
||||||
<h2>DRAFT</h2>
|
<h2>DRAFT</h2>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>{{ desc }}</p>
|
<p>{{ data.desc }}</p>
|
||||||
<div>
|
<div>
|
||||||
{{ content | safe }}
|
{{ data.content | safe }}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<h3>tags</h3>
|
<h3>tags</h3>
|
||||||
<div>
|
<div>
|
||||||
{% for tag in tags %}
|
{% for tag in data.tags %}
|
||||||
<a href="/!!RESOURCE_TYPE!!/tag/{{tag}}">{{ tag }}</a>{% if not loop.last %},{% endif %}
|
<a href="/!!RESOURCE_TYPE!!/tag/{{tag}}">{{ tag }}</a>{% if not loop.last %},{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use itertools::Itertools;
|
||||||
use lol_html::{element, RewriteStrSettings};
|
use lol_html::{element, RewriteStrSettings};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -91,22 +92,31 @@ fn resource_list_outside(
|
||||||
|
|
||||||
let data: ResourceListData = serde_yml::from_value(data.inner.clone())?;
|
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(
|
let resource_list = builder.tera.render(
|
||||||
&data.template,
|
&data.template,
|
||||||
&tera::Context::from_serialize(ResourceListTemplateData {
|
&tera::Context::from_serialize(ResourceListTemplateData {
|
||||||
resources: builder
|
resources: res_builder
|
||||||
.resource_builders
|
|
||||||
.get(&data.resource)
|
|
||||||
.ok_or_else(|| eyre::eyre!("missing resource builder: {}", data.resource))?
|
|
||||||
.loaded_metadata
|
.loaded_metadata
|
||||||
.iter()
|
.iter()
|
||||||
.take(data.count)
|
.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,
|
resource: v,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
timestamp: v.data().timestamp,
|
readable_timestamp: ts,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect::<eyre::Result<Vec<_>>>()?,
|
||||||
})?,
|
})?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -222,6 +222,7 @@ fn main() -> eyre::Result<()> {
|
||||||
tag_list_title: format!("{name} tags"),
|
tag_list_title: format!("{name} tags"),
|
||||||
resource_name_plural: plural,
|
resource_name_plural: plural,
|
||||||
resources_per_page: 3,
|
resources_per_page: 3,
|
||||||
|
timestamp_format: "[weekday], [month repr:long] [day], [year]".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
config.resources.insert(id.clone(), resource_config);
|
config.resources.insert(id.clone(), resource_config);
|
||||||
|
|
|
@ -7,11 +7,12 @@ use eyre::Context;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pulldown_cmark::{Options, Parser};
|
use pulldown_cmark::{Options, Parser};
|
||||||
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
|
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
|
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builder::SiteBuilder, frontmatter::FrontMatterRequired, link_list::Link, PageMetadata,
|
builder::SiteBuilder, frontmatter::FrontMatterRequired, link_list::Link,
|
||||||
|
util::format_timestamp, PageMetadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Metadata for resources.
|
/// Metadata for resources.
|
||||||
|
@ -43,24 +44,8 @@ pub struct ResourceTemplateData<'r> {
|
||||||
pub resource: &'r FrontMatterRequired<ResourceMetadata>,
|
pub resource: &'r FrontMatterRequired<ResourceMetadata>,
|
||||||
/// The resource's ID.
|
/// The resource's ID.
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// The resource's timestamp. Duplicated to change serialization method.
|
/// The resource's timestamp in a readable format.
|
||||||
#[serde(serialize_with = "ResourceTemplateData::timestamp_formatter")]
|
pub readable_timestamp: String,
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// struct for adding custom meta content embeds
|
/// struct for adding custom meta content embeds
|
||||||
|
@ -121,7 +106,7 @@ struct ResourceListTemplateData<'r> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config for the resource builder.
|
/// Config for the resource builder.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ResourceBuilderConfig {
|
pub struct ResourceBuilderConfig {
|
||||||
/// Path to where the resources should be loaded from.
|
/// Path to where the resources should be loaded from.
|
||||||
pub source_path: String,
|
pub source_path: String,
|
||||||
|
@ -145,6 +130,8 @@ pub struct ResourceBuilderConfig {
|
||||||
pub resource_name_plural: String,
|
pub resource_name_plural: String,
|
||||||
/// The number of resources to display on a single page.
|
/// The number of resources to display on a single page.
|
||||||
pub resources_per_page: usize,
|
pub resources_per_page: usize,
|
||||||
|
/// The format to use for the readable timestamp.
|
||||||
|
pub timestamp_format: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
@ -158,7 +145,7 @@ pub struct ResourceRSSBuilderConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to genericize resource building.
|
/// Helper to genericize resource building.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct ResourceBuilder {
|
pub struct ResourceBuilder {
|
||||||
/// The builder's config.
|
/// The builder's config.
|
||||||
pub config: ResourceBuilderConfig,
|
pub config: ResourceBuilderConfig,
|
||||||
|
@ -277,7 +264,10 @@ impl ResourceBuilder {
|
||||||
ResourceTemplateData {
|
ResourceTemplateData {
|
||||||
resource,
|
resource,
|
||||||
id,
|
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)?;
|
std::fs::write(out_path, out)?;
|
||||||
|
@ -307,7 +297,10 @@ impl ResourceBuilder {
|
||||||
data.push(ResourceTemplateData {
|
data.push(ResourceTemplateData {
|
||||||
resource,
|
resource,
|
||||||
id: id.clone(),
|
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(),
|
.to_string(),
|
||||||
))
|
))
|
||||||
.description(resource.resource.data().desc.clone())
|
.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(
|
.content(Some(builder.tera.render(
|
||||||
&rss.template,
|
&rss.template,
|
||||||
&tera::Context::from_serialize(resource)?,
|
&tera::Context::from_serialize(resource)?,
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
/// Simple helper to remove the contents of a directory without removing the directory itself.
|
/// Simple helper to remove the contents of a directory without removing the directory itself.
|
||||||
pub fn remove_dir_contents(path: &Path) -> eyre::Result<()> {
|
pub fn remove_dir_contents(path: &Path) -> eyre::Result<()> {
|
||||||
for entry in path.read_dir()? {
|
for entry in path.read_dir()? {
|
||||||
|
@ -16,3 +18,9 @@ pub fn remove_dir_contents(path: &Path) -> eyre::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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)?)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue