2022-06-14 19:04:17 +00:00
|
|
|
use crate::app::Config;
|
2020-07-16 19:32:30 +00:00
|
|
|
use chrono::prelude::*;
|
2020-09-19 15:33:46 +00:00
|
|
|
use color_eyre::eyre::{eyre, Result, WrapErr};
|
2020-07-16 19:32:30 +00:00
|
|
|
use glob::glob;
|
2022-03-22 00:14:14 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2022-06-14 19:04:17 +00:00
|
|
|
use std::{borrow::Borrow, cmp::Ordering, path::PathBuf, sync::Arc};
|
2021-02-15 20:09:25 +00:00
|
|
|
use tokio::fs;
|
2020-07-16 19:32:30 +00:00
|
|
|
|
|
|
|
pub mod frontmatter;
|
|
|
|
|
2022-03-22 00:14:14 +00:00
|
|
|
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
|
2020-07-16 19:32:30 +00:00
|
|
|
pub struct Post {
|
|
|
|
pub front_matter: frontmatter::Data,
|
|
|
|
pub link: String,
|
|
|
|
pub body_html: String,
|
|
|
|
pub date: DateTime<FixedOffset>,
|
2020-12-02 21:16:58 +00:00
|
|
|
pub mentions: Vec<mi::WebMention>,
|
2021-07-06 00:12:42 +00:00
|
|
|
pub new_post: NewPost,
|
2021-07-08 01:17:01 +00:00
|
|
|
pub read_time_estimate_minutes: u64,
|
2021-07-06 00:12:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Used with the Android app to show information in a widget.
|
2022-03-22 00:14:14 +00:00
|
|
|
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
|
2021-07-06 00:12:42 +00:00
|
|
|
pub struct NewPost {
|
|
|
|
pub title: String,
|
|
|
|
pub summary: String,
|
|
|
|
pub link: String,
|
2020-07-16 19:32:30 +00:00
|
|
|
}
|
|
|
|
|
2022-07-04 14:44:00 +00:00
|
|
|
impl Into<xe_jsonfeed::Item> for Post {
|
|
|
|
fn into(self) -> xe_jsonfeed::Item {
|
|
|
|
let mut result = xe_jsonfeed::Item::builder()
|
2020-07-16 19:32:30 +00:00
|
|
|
.title(self.front_matter.title)
|
|
|
|
.content_html(self.body_html)
|
2022-05-28 13:17:01 +00:00
|
|
|
.id(format!("https://xeiaso.net/{}", self.link))
|
|
|
|
.url(format!("https://xeiaso.net/{}", self.link))
|
2020-07-16 19:32:30 +00:00
|
|
|
.date_published(self.date.to_rfc3339())
|
|
|
|
.author(
|
2022-07-04 14:44:00 +00:00
|
|
|
xe_jsonfeed::Author::new()
|
2022-04-02 16:15:10 +00:00
|
|
|
.name("Xe Iaso")
|
2022-05-28 13:17:01 +00:00
|
|
|
.url("https://xeiaso.net")
|
|
|
|
.avatar("https://xeiaso.net/static/img/avatar.png"),
|
2020-07-16 19:32:30 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let mut tags: Vec<String> = vec![];
|
|
|
|
|
|
|
|
if let Some(series) = self.front_matter.series {
|
|
|
|
tags.push(series);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(mut meta_tags) = self.front_matter.tags {
|
|
|
|
tags.append(&mut meta_tags);
|
|
|
|
}
|
|
|
|
|
|
|
|
if tags.len() != 0 {
|
|
|
|
result = result.tags(tags);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(image_url) = self.front_matter.image {
|
|
|
|
result = result.image(image_url);
|
|
|
|
}
|
|
|
|
|
|
|
|
result.build().unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Ord for Post {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
self.partial_cmp(&other).unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialOrd for Post {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
Some(self.date.cmp(&other.date))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Post {
|
|
|
|
pub fn detri(&self) -> String {
|
|
|
|
self.date.format("M%m %d %Y").to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-14 19:04:17 +00:00
|
|
|
async fn read_post(
|
|
|
|
cfg: Arc<Config>,
|
|
|
|
dir: &str,
|
|
|
|
fname: PathBuf,
|
|
|
|
cli: &Option<mi::Client>,
|
|
|
|
) -> Result<Post> {
|
2022-01-02 22:17:51 +00:00
|
|
|
debug!(
|
2022-04-02 16:15:10 +00:00
|
|
|
"loading {}",
|
2022-01-02 22:17:51 +00:00
|
|
|
fname.clone().into_os_string().into_string().unwrap()
|
|
|
|
);
|
|
|
|
|
2021-02-15 20:09:25 +00:00
|
|
|
let body = fs::read_to_string(fname.clone())
|
|
|
|
.await
|
|
|
|
.wrap_err_with(|| format!("can't read {:?}", fname))?;
|
|
|
|
let (front_matter, content_offset) = frontmatter::Data::parse(body.clone().as_str())
|
|
|
|
.wrap_err_with(|| format!("can't parse frontmatter of {:?}", fname))?;
|
|
|
|
let body = &body[content_offset..];
|
|
|
|
let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d")
|
|
|
|
.map_err(|why| eyre!("error parsing date in {:?}: {}", fname, why))?;
|
2021-04-18 14:40:03 +00:00
|
|
|
let link = format!("{}/{}", dir, fname.file_stem().unwrap().to_str().unwrap());
|
2022-06-14 19:04:17 +00:00
|
|
|
let body_html = crate::app::markdown::render(cfg.clone(), &body)
|
2021-02-15 20:09:25 +00:00
|
|
|
.wrap_err_with(|| format!("can't parse markdown for {:?}", fname))?;
|
|
|
|
let date: DateTime<FixedOffset> =
|
|
|
|
DateTime::<Utc>::from_utc(NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0)), Utc)
|
|
|
|
.with_timezone(&Utc)
|
|
|
|
.into();
|
|
|
|
|
2021-09-29 12:36:49 +00:00
|
|
|
let mentions: Vec<mi::WebMention> = match cli {
|
|
|
|
Some(cli) => cli
|
2022-05-28 13:17:01 +00:00
|
|
|
.mentioners(format!("https://xeiaso.net/{}", link))
|
2021-02-15 20:09:25 +00:00
|
|
|
.await
|
|
|
|
.map_err(|why| tracing::error!("error: can't load mentions for {}: {}", link, why))
|
2021-07-08 12:14:09 +00:00
|
|
|
.unwrap_or(vec![])
|
|
|
|
.into_iter()
|
|
|
|
.filter(|wm| {
|
|
|
|
wm.title.as_ref().unwrap_or(&"".to_string()) != &"Bridgy Response".to_string()
|
|
|
|
})
|
|
|
|
.collect(),
|
2021-09-29 12:36:49 +00:00
|
|
|
None => vec![],
|
2021-02-15 20:09:25 +00:00
|
|
|
};
|
|
|
|
|
2021-07-08 01:17:01 +00:00
|
|
|
let time_taken = estimated_read_time::text(
|
|
|
|
&body,
|
|
|
|
&estimated_read_time::Options::new()
|
2021-07-08 12:14:09 +00:00
|
|
|
.technical_document(true)
|
|
|
|
.technical_difficulty(1)
|
2021-07-08 01:17:01 +00:00
|
|
|
.build()
|
|
|
|
.unwrap_or_default(),
|
|
|
|
);
|
|
|
|
let read_time_estimate_minutes = time_taken.seconds() / 60;
|
|
|
|
|
2021-07-06 00:12:42 +00:00
|
|
|
let new_post = NewPost {
|
|
|
|
title: front_matter.title.clone(),
|
2021-07-08 01:17:01 +00:00
|
|
|
summary: format!("{} minute read", read_time_estimate_minutes),
|
2022-05-28 13:17:01 +00:00
|
|
|
link: format!("https://xeiaso.net/{}", link),
|
2021-07-06 00:12:42 +00:00
|
|
|
};
|
|
|
|
|
2021-02-15 20:09:25 +00:00
|
|
|
Ok(Post {
|
|
|
|
front_matter,
|
|
|
|
link,
|
|
|
|
body_html,
|
|
|
|
date,
|
|
|
|
mentions,
|
2021-07-06 00:12:42 +00:00
|
|
|
new_post,
|
2021-07-08 01:17:01 +00:00
|
|
|
read_time_estimate_minutes,
|
2021-02-15 20:09:25 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-14 19:04:17 +00:00
|
|
|
pub async fn load(cfg: Arc<Config>, dir: &str) -> Result<Vec<Post>> {
|
2021-09-29 12:36:49 +00:00
|
|
|
let cli = match std::env::var("MI_TOKEN") {
|
|
|
|
Ok(token) => mi::Client::new(token.to_string(), crate::APPLICATION_NAME.to_string()).ok(),
|
|
|
|
Err(_) => None,
|
|
|
|
};
|
|
|
|
|
2021-02-15 20:09:25 +00:00
|
|
|
let futs = glob(&format!("{}/*.markdown", dir))?
|
|
|
|
.filter_map(Result::ok)
|
2022-06-14 19:04:17 +00:00
|
|
|
.map(|fname| read_post(cfg.clone(), dir, fname, cli.borrow()));
|
2021-02-15 20:09:25 +00:00
|
|
|
|
|
|
|
let mut result: Vec<Post> = futures::future::join_all(futs)
|
|
|
|
.await
|
|
|
|
.into_iter()
|
|
|
|
.map(Result::unwrap)
|
|
|
|
.collect();
|
2020-07-16 19:32:30 +00:00
|
|
|
|
|
|
|
if result.len() == 0 {
|
2020-09-19 15:33:46 +00:00
|
|
|
Err(eyre!("no posts loaded"))
|
2020-07-16 19:32:30 +00:00
|
|
|
} else {
|
|
|
|
result.sort();
|
|
|
|
result.reverse();
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2022-06-14 19:04:17 +00:00
|
|
|
use crate::app::Config;
|
2020-09-19 15:33:46 +00:00
|
|
|
use color_eyre::eyre::Result;
|
2022-06-14 19:04:17 +00:00
|
|
|
use std::sync::Arc;
|
2020-07-16 19:32:30 +00:00
|
|
|
|
2020-12-02 21:16:58 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn blog() {
|
2020-07-27 03:12:01 +00:00
|
|
|
let _ = pretty_env_logger::try_init();
|
2022-06-14 19:04:17 +00:00
|
|
|
let cfg = Arc::new(Config::default());
|
|
|
|
load(cfg, "blog").await.expect("posts to load");
|
2020-07-16 19:32:30 +00:00
|
|
|
}
|
|
|
|
|
2020-12-02 21:16:58 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn gallery() -> Result<()> {
|
2020-07-27 03:12:01 +00:00
|
|
|
let _ = pretty_env_logger::try_init();
|
2022-06-14 19:04:17 +00:00
|
|
|
let cfg = Arc::new(Config::default());
|
|
|
|
load(cfg, "gallery").await?;
|
2020-07-16 19:32:30 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-12-02 21:16:58 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn talks() -> Result<()> {
|
2020-07-27 03:12:01 +00:00
|
|
|
let _ = pretty_env_logger::try_init();
|
2022-06-14 19:04:17 +00:00
|
|
|
let cfg = Arc::new(Config::default());
|
|
|
|
load(cfg, "talks").await?;
|
2020-07-16 19:32:30 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|