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.
This commit is contained in:
Thomas Gideon 2024-10-06 10:28:06 -04:00
parent 709033f5cc
commit 927a0c3257
4 changed files with 95 additions and 24 deletions

11
Cargo.lock generated
View file

@ -1101,6 +1101,7 @@ dependencies = [
"log", "log",
"megalodon", "megalodon",
"regex", "regex",
"relativetime",
"rss", "rss",
"rustyline", "rustyline",
"tokio", "tokio",
@ -1560,6 +1561,16 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 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]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.27" version = "0.11.27"

View file

@ -23,6 +23,7 @@ html2md = "0.2.14"
log = "0.4.19" log = "0.4.19"
megalodon = "0.13.3" megalodon = "0.13.3"
regex = "1.9.1" regex = "1.9.1"
relativetime = { version = "0.1.4", features = ["chrono"] }
rss = "2.0.4" rss = "2.0.4"
rustyline = "14.0.0" rustyline = "14.0.0"
tokio = { version = "1.28.2", features = ["default", "full"] } tokio = { version = "1.28.2", features = ["default", "full"] }

View file

@ -113,7 +113,7 @@ fn apply_block_quote<S: AsRef<str>>(depth: usize, content: S) -> String {
// replace separately to avoid trailing spaces when replacing empty lines with the prefix // 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 = empty.replace_all(content.as_ref(), format!("\n{}\n", prefix.trim()));
let content = non_empty.replace_all(&content, |c: &Captures| { let content = non_empty.replace_all(&content, |c: &Captures| {
format!("\n{}{}", prefix, c["initial"].to_string()) format!("\n{}{}", prefix, &c["initial"])
}); });
content.to_string() content.to_string()
} }

View file

@ -9,10 +9,15 @@ use megalodon::{
response::Response, response::Response,
Megalodon, Megalodon,
}; };
use relativetime::RelativeTime;
use tokio::fs::try_exists; use tokio::fs::try_exists;
use tokio_stream::{iter, StreamExt}; use tokio_stream::{iter, StreamExt};
use std::{env, fs::File, io::prelude::*}; use std::{
env,
fs::{read_dir, File},
io::{prelude::*, BufReader},
};
use self::{ use self::{
format::format_status, format::format_status,
@ -127,7 +132,7 @@ async fn main() -> Result<()> {
Ok(exists) if exists => { Ok(exists) if exists => {
debug!("Appending {}", output); debug!("Appending {}", output);
let mut file = File::options().append(true).open(&output)?; let mut file = File::options().append(true).open(&output)?;
file.write("\n\n".as_bytes())?; file.write_all("\n".as_bytes())?;
file file
} }
_ => { _ => {
@ -137,21 +142,18 @@ async fn main() -> Result<()> {
.append(true) .append(true)
.open(&output) .open(&output)
.with_context(|| format!("Failed to create {}", 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 let back_links = create_back_links(&output_dir, &day.end).await?;
file.write(create_back_link(&day.end, "One week ago", 7).as_bytes())?; debug!("Created {back_links:?}");
file.write(create_back_link(&day.end, "One month ago", 30).as_bytes())?; file.write_all(back_links.join("\n").as_bytes())
file.write(create_back_link(&day.end, "Six months ago", 6 * 30).as_bytes())?; .with_context(|| "Failed to write back links!")?;
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())?;
file.write(b"\n")?; file.write_all(b"\n")?;
file 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))?; .with_context(|| format!("Failed to write all to {}", output))?;
println!("Appended matching posts to {}.", output); println!("Appended matching posts to {}.", output);
} else { } else {
@ -160,14 +162,71 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn create_back_link(day_end: &DateTime<Local>, anchor_text: &str, ago: i64) -> String { async fn create_back_links(output_dir: &str, this_day: &DateTime<Local>) -> Result<Vec<String>> {
let prior_date = *day_end - Duration::days(ago); //file.write_all(create_back_link_old(&day.end, "One week ago", 7).as_bytes())?;
// TODO check if the file exists //file.write_all(create_back_link_old(&day.end, "One month ago", 30).as_bytes())?;
format!( //file.write_all(
"[{}](diary:{})\n", // create_back_link_old(&day.end, "Six months ago", 6 * 30).as_bytes(),
anchor_text, //)?;
prior_date.format("%Y-%m-%d") 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<String> = 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<Local> = 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::<Result<Vec<String>>>()?;
Ok(years_past)
} }
enum NextIter { enum NextIter {
@ -215,14 +274,14 @@ async fn process_page(
} }
if let Some(id) = page.oldest_id { if let Some(id) = page.oldest_id {
return Ok((Some(id.clone()), None, formatted)); Ok((Some(id.clone()), None, formatted))
} else { } 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 // 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<Status>) -> Vec<&'a Status> { fn filter_statuses<'a>(account: &Account, day: &Range, json: &'a [Status]) -> Vec<&'a Status> {
json.iter() json.iter()
.filter(|status| { .filter(|status| {
status.account.id == account.id status.account.id == account.id