use anyhow::{anyhow, Context}; use futures::future::join_all; use std::{ path::{Path, PathBuf}, process::Stdio, }; use tokio::{ fs::File, io::{AsyncBufReadExt, BufReader}, process::Command, }; pub struct ArticleMeta { pub title: String, pub canonical_name: String, pub date: iso8601::Date, } pub async fn article_metadata(path: PathBuf) -> anyhow::Result { let f = File::open(&path).await.context("article not found")?; let mut f = BufReader::new(f); let mut buf = String::new(); f.read_line(&mut buf).await.context("cant read the file")?; // assume the 1st line has the title Ok(ArticleMeta { title: String::from(buf[2..].trim()), canonical_name: path .file_stem() .ok_or(anyhow!("no file stem"))? .to_str() .ok_or(anyhow!("this file's name is broken"))? .to_string(), date: iso8601::date( &path .file_name() .ok_or(anyhow!("file has no name"))? .to_str() .ok_or(anyhow!("this file's name is broken"))?[0..10], ) .map_err(|e| anyhow!("file date wrong: {e}"))?, }) } pub async fn get_articles(blog_root: &Path) -> anyhow::Result> { let mut a = join_all( std::fs::read_dir(blog_root)? .map(|e| e.unwrap()) .map(|e| article_metadata(e.path())), ) .await .into_iter() .collect::>>()?; a.sort_by_cached_key(|e| -match e.date { iso8601::Date::YMD { year, month, day } => day as i32 + month as i32 * 100 + year * 10000, _ => unreachable!(), }); Ok(a) } // TODO use this somewhere pub async fn file_history(filename: &str) -> String { String::from_utf8( Command::new("/usr/bin/git") .args(&["log", "--follow", "--pretty=tformat:%as %h %s", filename]) .stdout(Stdio::piped()) .output() .await .unwrap() .stdout, ) .unwrap() }