From 927a0c3257e2b484cef3b400bc563c5037aa02cd Mon Sep 17 00:00:00 2001 From: Thomas Gideon Date: Sun, 6 Oct 2024 10:28:06 -0400 Subject: [PATCH] Enhance back links - Use directory listing to ensure linked entries exist. - Use a filter array for within the past year. - Read the first line summary of each entry to include in link text. --- Cargo.lock | 11 ++++++ Cargo.toml | 1 + src/format.rs | 2 +- src/main.rs | 105 +++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fde107a..74e0885 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1101,6 +1101,7 @@ dependencies = [ "log", "megalodon", "regex", + "relativetime", "rss", "rustyline", "tokio", @@ -1560,6 +1561,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "relativetime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b093e81f7b2ee8db5253e1343a98c8334bd5e7214e507e188d2450084e1c8d" +dependencies = [ + "chrono", + "humantime", +] + [[package]] name = "reqwest" version = "0.11.27" diff --git a/Cargo.toml b/Cargo.toml index ee89d60..288d34f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ html2md = "0.2.14" log = "0.4.19" megalodon = "0.13.3" regex = "1.9.1" +relativetime = { version = "0.1.4", features = ["chrono"] } rss = "2.0.4" rustyline = "14.0.0" tokio = { version = "1.28.2", features = ["default", "full"] } diff --git a/src/format.rs b/src/format.rs index b0c3d2b..bd15c45 100644 --- a/src/format.rs +++ b/src/format.rs @@ -113,7 +113,7 @@ fn apply_block_quote>(depth: usize, content: S) -> String { // replace separately to avoid trailing spaces when replacing empty lines with the prefix let content = empty.replace_all(content.as_ref(), format!("\n{}\n", prefix.trim())); let content = non_empty.replace_all(&content, |c: &Captures| { - format!("\n{}{}", prefix, c["initial"].to_string()) + format!("\n{}{}", prefix, &c["initial"]) }); content.to_string() } diff --git a/src/main.rs b/src/main.rs index 6067706..f103bcf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,10 +9,15 @@ use megalodon::{ response::Response, Megalodon, }; +use relativetime::RelativeTime; use tokio::fs::try_exists; use tokio_stream::{iter, StreamExt}; -use std::{env, fs::File, io::prelude::*}; +use std::{ + env, + fs::{read_dir, File}, + io::{prelude::*, BufReader}, +}; use self::{ format::format_status, @@ -127,7 +132,7 @@ async fn main() -> Result<()> { Ok(exists) if exists => { debug!("Appending {}", output); let mut file = File::options().append(true).open(&output)?; - file.write("\n\n".as_bytes())?; + file.write_all("\n".as_bytes())?; file } _ => { @@ -137,21 +142,18 @@ async fn main() -> Result<()> { .append(true) .open(&output) .with_context(|| format!("Failed to create {}", output))?; - file.write(format!("# {}\n\n", day.end.format("%Y-%m-%d")).as_bytes())?; + file.write_all(format!("# {}\n\n", day.end.format("%Y-%m-%d")).as_bytes())?; - // TODO move to separate function - file.write(create_back_link(&day.end, "One week ago", 7).as_bytes())?; - file.write(create_back_link(&day.end, "One month ago", 30).as_bytes())?; - file.write(create_back_link(&day.end, "Six months ago", 6 * 30).as_bytes())?; - file.write(create_back_link(&day.end, "One year ago", 365).as_bytes())?; - file.write(create_back_link(&day.end, "Two years ago", 365 * 2).as_bytes())?; - file.write(create_back_link(&day.end, "Three years ago", 365 * 3).as_bytes())?; + let back_links = create_back_links(&output_dir, &day.end).await?; + debug!("Created {back_links:?}"); + file.write_all(back_links.join("\n").as_bytes()) + .with_context(|| "Failed to write back links!")?; - file.write(b"\n")?; + file.write_all(b"\n")?; file } }; - f.write_all(&reversed.join("\n\n").as_bytes()) + f.write_all(reversed.join("\n\n").as_bytes()) .with_context(|| format!("Failed to write all to {}", output))?; println!("Appended matching posts to {}.", output); } else { @@ -160,14 +162,71 @@ async fn main() -> Result<()> { Ok(()) } -fn create_back_link(day_end: &DateTime, anchor_text: &str, ago: i64) -> String { - let prior_date = *day_end - Duration::days(ago); - // TODO check if the file exists - format!( - "[{}](diary:{})\n", - anchor_text, - prior_date.format("%Y-%m-%d") - ) +async fn create_back_links(output_dir: &str, this_day: &DateTime) -> Result> { + //file.write_all(create_back_link_old(&day.end, "One week ago", 7).as_bytes())?; + //file.write_all(create_back_link_old(&day.end, "One month ago", 30).as_bytes())?; + //file.write_all( + // create_back_link_old(&day.end, "Six months ago", 6 * 30).as_bytes(), + //)?; + let within_year = [ + (*this_day - Duration::days(7)) + .format("%Y-%m-%d.md") + .to_string(), + (*this_day - Duration::days(30)) + .format("%Y-%m-%d.md") + .to_string(), + (*this_day - Duration::days(6 * 30)) + .format("%Y-%m-%d.md") + .to_string(), + ]; + let mut years_past: Vec = read_dir(output_dir)? + .filter_map(|d| { + d.ok().and_then(|d| { + let d = d.file_name().to_owned(); + let d = d.to_string_lossy().to_string(); + if within_year.contains(&d) + || (!d.starts_with(&this_day.format("%Y-").to_string()) + && d.ends_with(&this_day.format("-%m-%d.md").to_string())) + { + Some(d) + } else { + None + } + }) + }) + .collect(); + years_past.sort(); + years_past.reverse(); + debug!("Found {years_past:?}"); + + let years_past = years_past + .into_iter() + .map(|b| { + let f = format!("{}/{}", output_dir.trim_end_matches("/"), b); + trace!("Building link for {f}"); + let mut f = + BufReader::new(File::open(&f).with_context(|| format!("Could not open {f}"))?); + let mut first = String::default(); + f.read_line(&mut first) + .with_context(|| format!("Failed to read first line of {b}"))?; + trace!("Read {first}"); + let day = b.to_string(); + let day = day.trim_end_matches(".md"); + let day: DateTime = format!("{day}T00:00:00-04:00") + .parse() + .with_context(|| format!("Could not parse {day} as date!"))?; + let first = first.trim_start_matches(&format!("# {} - ", day.format("%Y-%m-%d"))); + let link = format!( + "[{} - {}](diary:{})", + (day - *this_day).to_relative(), + first.trim(), + b + ); + debug!("Link {link}"); + Ok(link) + }) + .collect::>>()?; + Ok(years_past) } enum NextIter { @@ -215,14 +274,14 @@ async fn process_page( } if let Some(id) = page.oldest_id { - return Ok((Some(id.clone()), None, formatted)); + Ok((Some(id.clone()), None, formatted)) } else { - return Ok((None, None, formatted)); + Ok((None, None, formatted)) } } // Only ones authored by the user, on the date requested, that aren't a reply to any other status -fn filter_statuses<'a>(account: &Account, day: &Range, json: &'a Vec) -> Vec<&'a Status> { +fn filter_statuses<'a>(account: &Account, day: &Range, json: &'a [Status]) -> Vec<&'a Status> { json.iter() .filter(|status| { status.account.id == account.id