diff options
| author | Xe Iaso <me@christine.website> | 2023-09-30 10:36:37 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-30 10:36:37 -0400 |
| commit | ac6a3df0d18cc73524c0096d954a57d24cad5669 (patch) | |
| tree | 81474177d730440657f490ae29892d62392251ea /src/post | |
| parent | cbdea8ba3fca9a663778af71f8df5965aeb6c090 (diff) | |
| download | xesite-ac6a3df0d18cc73524c0096d954a57d24cad5669.tar.xz xesite-ac6a3df0d18cc73524c0096d954a57d24cad5669.zip | |
Xesite V4 (#723)
* scripts/ditherify: fix quoting
Signed-off-by: Xe Iaso <me@xeiaso.net>
* clean up some old files
Signed-off-by: Xe Iaso <me@xeiaso.net>
* import site into lume
Signed-off-by: Xe Iaso <me@xeiaso.net>
* initial go code
Signed-off-by: Xe Iaso <me@xeiaso.net>
* move vods index to top level
Signed-off-by: Xe Iaso <me@xeiaso.net>
* remove the ads
Signed-off-by: Xe Iaso <me@xeiaso.net>
* internal/lume: metrics
Signed-off-by: Xe Iaso <me@xeiaso.net>
* delete old code
Signed-off-by: Xe Iaso <me@xeiaso.net>
* load config into memory
Signed-off-by: Xe Iaso <me@xeiaso.net>
* autogenerate data from dhall config
Signed-off-by: Xe Iaso <me@xeiaso.net>
* various cleanups, import clackset logic
Signed-off-by: Xe Iaso <me@xeiaso.net>
* Update signalboost.dhall (#722)
Added myself, and also fixed someone’s typo
* Add Connor Edwards to signal boost (#721)
* add cache headers
Signed-off-by: Xe Iaso <me@xeiaso.net>
* move command to xesite folder
Signed-off-by: Xe Iaso <me@xeiaso.net>
* xesite: listen for GitHub webhook push events
Signed-off-by: Xe Iaso <me@xeiaso.net>
* xesite: 5 minute timeout for rebuilding the site
Signed-off-by: Xe Iaso <me@xeiaso.net>
* xesite: add rebuild metrics
Signed-off-by: Xe Iaso <me@xeiaso.net>
* xesite: update default variables
Signed-off-by: Xe Iaso <me@xeiaso.net>
* don't commit binaries oops lol
Signed-off-by: Xe Iaso <me@xeiaso.net>
* lume: make search have a light background
Signed-off-by: Xe Iaso <me@xeiaso.net>
* add a notfound page
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fetch info from patreon API
Signed-off-by: Xe Iaso <me@xeiaso.net>
* create contact page
Signed-off-by: Xe Iaso <me@xeiaso.net>
* Toot embedding
Signed-off-by: Xe Iaso <me@xeiaso.net>
* attempt a docker image
Signed-off-by: Xe Iaso <me@xeiaso.net>
* lume: fix deno lock
Signed-off-by: Xe Iaso <me@xeiaso.net>
* add gokrazy post
Signed-off-by: Xe Iaso <me@xeiaso.net>
* cmd/xesite: go up before trying to connect to the saas proxy
Signed-off-by: Xe Iaso <me@xeiaso.net>
* blog: add Sine post/demo
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: bri <284789+b-@users.noreply.github.com>
Co-authored-by: Connor Edwards <38229097+cedws@users.noreply.github.com>
Diffstat (limited to 'src/post')
| -rw-r--r-- | src/post/frontmatter.rs | 99 | ||||
| -rw-r--r-- | src/post/mod.rs | 226 | ||||
| -rw-r--r-- | src/post/schemaorg.rs | 14 |
3 files changed, 0 insertions, 339 deletions
diff --git a/src/post/frontmatter.rs b/src/post/frontmatter.rs deleted file mode 100644 index 67b96ab..0000000 --- a/src/post/frontmatter.rs +++ /dev/null @@ -1,99 +0,0 @@ -/// This code was borrowed from @fasterthanlime. -use color_eyre::eyre::Result; -pub use xesite_types::Frontmatter as Data; - -enum State { - SearchForStart, - ReadingMarker { count: usize, end: bool }, - ReadingFrontMatter { buf: String, line_start: bool }, - SkipNewline { end: bool }, -} - -#[derive(Debug, thiserror::Error)] -enum Error { - #[error("EOF while parsing frontmatter")] - EOF, - #[error("Error parsing yaml: {0:?}")] - Yaml(#[from] serde_yaml::Error), -} - -pub fn parse(input: &str) -> Result<(Data, usize)> { - let mut state = State::SearchForStart; - - let mut payload = None; - let offset; - - let mut chars = input.char_indices(); - 'parse: loop { - let (idx, ch) = match chars.next() { - Some(x) => x, - None => return Err(Error::EOF)?, - }; - match &mut state { - State::SearchForStart => match ch { - '-' => { - state = State::ReadingMarker { - count: 1, - end: false, - }; - } - '\n' | '\t' | ' ' => { - // ignore whitespace - } - _ => { - panic!("Start of frontmatter not found"); - } - }, - State::ReadingMarker { count, end } => match ch { - '-' => { - *count += 1; - if *count == 3 { - state = State::SkipNewline { end: *end }; - } - } - _ => { - panic!("Malformed frontmatter marker"); - } - }, - State::SkipNewline { end } => match ch { - '\n' => { - if *end { - offset = idx + 1; - break 'parse; - } else { - state = State::ReadingFrontMatter { - buf: String::new(), - line_start: true, - }; - } - } - _ => panic!("Expected newline, got {:?}", ch), - }, - State::ReadingFrontMatter { buf, line_start } => match ch { - '-' if *line_start => { - let mut state_temp = State::ReadingMarker { - count: 1, - end: true, - }; - std::mem::swap(&mut state, &mut state_temp); - if let State::ReadingFrontMatter { buf, .. } = state_temp { - payload = Some(buf); - } else { - unreachable!(); - } - } - ch => { - buf.push(ch); - *line_start = ch == '\n'; - } - }, - } - } - - // unwrap justification: option set in state machine, Rust can't statically analyze it - let payload = payload.unwrap(); - - let fm: Data = serde_yaml::from_str(&payload)?; - - Ok((fm, offset)) -} diff --git a/src/post/mod.rs b/src/post/mod.rs deleted file mode 100644 index fb7d3bd..0000000 --- a/src/post/mod.rs +++ /dev/null @@ -1,226 +0,0 @@ -use chrono::prelude::*; -use color_eyre::eyre::{eyre, Result, WrapErr}; -use glob::glob; -use serde::{Deserialize, Serialize}; -use std::{borrow::Borrow, cmp::Ordering, path::PathBuf}; -use tokio::fs; - -pub mod frontmatter; -pub mod schemaorg; - -#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)] -pub struct Post { - pub front_matter: frontmatter::Data, - pub link: String, - pub body_html: String, - pub date: DateTime<FixedOffset>, - pub mentions: Vec<mi::WebMention>, - pub new_post: NewPost, - pub read_time_estimate_minutes: u64, -} - -/// Used with the Android app to show information in a widget. -#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)] -pub struct NewPost { - pub title: String, - pub summary: String, - pub link: String, -} - -impl Into<schemaorg::Article> for &Post { - fn into(self) -> schemaorg::Article { - schemaorg::Article { - context: "https://schema.org".to_string(), - r#type: "Article".to_string(), - headline: self.front_matter.title.clone(), - image: "https://xeiaso.net/static/img/avatar.png".to_string(), - url: format!("https://xeiaso.net/{}", self.link), - date_published: self.date.format("%Y-%m-%d").to_string(), - } - } -} - -impl Into<xe_jsonfeed::Item> for Post { - fn into(self) -> xe_jsonfeed::Item { - let mut result = xe_jsonfeed::Item::builder() - .title(self.front_matter.title.clone()) - .content_html(self.body_html) - .id(format!("https://xeiaso.net/{}", self.link)) - .url(if let Some(url) = self.front_matter.redirect_to.as_ref() { - url.clone() - } else { - format!("https://xeiaso.net/{}", self.link) - }) - .date_published(self.date.to_rfc3339()) - .author( - xe_jsonfeed::Author::new() - .name("Xe Iaso") - .url("https://xeiaso.net") - .avatar("https://xeiaso.net/static/img/avatar.png"), - ) - .xesite_frontmatter(self.front_matter.clone()); - - 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.is_empty() { - 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() - } -} - -async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Result<Post> { - debug!( - "loading {}", - fname.clone().into_os_string().into_string().unwrap() - ); - - let body = fs::read_to_string(fname.clone()) - .await - .wrap_err_with(|| format!("can't read {:?}", fname))?; - let (front_matter, content_offset) = frontmatter::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))?; - let link = format!("{}/{}", dir, fname.file_stem().unwrap().to_str().unwrap()); - let body_html = xesite_markdown::render(body) - .wrap_err_with(|| format!("can't parse markdown for {:?}", fname))?; - let date: DateTime<FixedOffset> = DateTime::<Utc>::from_utc( - NaiveDateTime::new(date, NaiveTime::from_hms_opt(0, 0, 0).unwrap()), - Utc, - ) - .with_timezone(&Utc) - .into(); - - let mentions: Vec<mi::WebMention> = match cli { - Some(cli) => cli - .mentioners(format!("https://xeiaso.net/{}", link)) - .await - .map_err(|why| tracing::error!("error: can't load mentions for {}: {}", link, why)) - .unwrap_or(vec![]) - .into_iter() - .filter(|wm| { - wm.title.as_ref().unwrap_or(&"".to_string()) != &"Bridgy Response".to_string() - }) - .map(|mut wm| { - wm.title = Some( - mastodon2text::convert( - wm.title.as_ref().unwrap_or(&"".to_string()).to_string(), - ) - .unwrap(), - ); - wm - }) - .collect(), - None => vec![], - }; - - let time_taken = estimated_read_time::text( - body, - &estimated_read_time::Options::new() - .technical_document(true) - .technical_difficulty(1) - .build() - .unwrap_or_default(), - ); - let read_time_estimate_minutes = time_taken.seconds() / 60; - - let new_post = NewPost { - title: front_matter.title.clone(), - summary: format!("{} minute read", read_time_estimate_minutes), - link: format!("https://xeiaso.net/{}", link), - }; - - Ok(Post { - front_matter, - link, - body_html, - date, - mentions, - new_post, - read_time_estimate_minutes, - }) -} - -pub async fn load(dir: &str) -> Result<Vec<Post>> { - let cli = match std::env::var("MI_TOKEN") { - Ok(token) => mi::Client::new(token, crate::APPLICATION_NAME.to_string()).ok(), - Err(_) => None, - }; - - let futs = glob(&format!("{}/*.markdown", dir))? - .filter_map(Result::ok) - .map(|fname| read_post(dir, fname, cli.borrow())); - - let mut result: Vec<Post> = futures::future::join_all(futs) - .await - .into_iter() - .map(Result::unwrap) - .collect(); - - if result.is_empty() { - Err(eyre!("no posts loaded")) - } else { - result.sort(); - result.reverse(); - Ok(result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use color_eyre::eyre::Result; - - #[tokio::test] - async fn blog() { - let _ = pretty_env_logger::try_init(); - load("blog").await.expect("posts to load"); - } - - #[tokio::test] - async fn gallery() -> Result<()> { - let _ = pretty_env_logger::try_init(); - load("gallery").await?; - Ok(()) - } - - #[tokio::test] - async fn talks() -> Result<()> { - let _ = pretty_env_logger::try_init(); - load("talks").await?; - Ok(()) - } -} diff --git a/src/post/schemaorg.rs b/src/post/schemaorg.rs deleted file mode 100644 index 18c238a..0000000 --- a/src/post/schemaorg.rs +++ /dev/null @@ -1,14 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)] -pub struct Article { - #[serde(rename = "@context")] - pub context: String, - #[serde(rename = "@type")] - pub r#type: String, - pub headline: String, - pub image: String, - pub url: String, - #[serde(rename = "datePublished")] - pub date_published: String, -} |
