diff --git a/tools/automator/src/args.rs b/tools/automator/src/args.rs index 8420ea122..31ee0123c 100644 --- a/tools/automator/src/args.rs +++ b/tools/automator/src/args.rs @@ -1,6 +1,7 @@ #[derive(clap::Parser)] pub enum Args { - Announcement, + #[command(subcommand)] + Blog(Blog), Sponsors(Sponsors), } @@ -10,6 +11,12 @@ impl Args { } } +#[derive(clap::Subcommand)] +pub enum Blog { + Release, + SponsorUpdate, +} + #[derive(clap::Parser)] pub struct Sponsors { #[clap(short, long)] diff --git a/tools/automator/src/blog/mod.rs b/tools/automator/src/blog/mod.rs new file mode 100644 index 000000000..5e8b60478 --- /dev/null +++ b/tools/automator/src/blog/mod.rs @@ -0,0 +1,7 @@ +mod release; +mod sponsors; +mod util; + +pub use self::{ + release::create_release_announcement, sponsors::create_sponsor_update, +}; diff --git a/tools/automator/src/announcement.rs b/tools/automator/src/blog/release.rs similarity index 78% rename from tools/automator/src/announcement.rs rename to tools/automator/src/blog/release.rs index fc314ad62..550c957fa 100644 --- a/tools/automator/src/announcement.rs +++ b/tools/automator/src/blog/release.rs @@ -1,26 +1,20 @@ -use std::{collections::HashSet, fmt::Write, path::PathBuf}; +use std::{collections::HashSet, fmt::Write}; -use anyhow::Context; -use chrono::{Datelike, Utc}; use map_macro::hash_set; use octocrab::Octocrab; -use tokio::{ - fs::{self, File}, - io::AsyncWriteExt, -}; +use tokio::{fs::File, io::AsyncWriteExt}; use crate::{ pull_requests::{Author, PullRequest, PullRequestsSinceLastRelease}, sponsors::Sponsors, }; +use super::util; + pub async fn create_release_announcement( octocrab: &Octocrab, ) -> anyhow::Result<()> { - let now = Utc::now(); - - let year = now.year(); - let date = format!("{year}-{:02}-{:02}", now.month(), now.day()); + let date = util::now_ymd(); let pull_requests_since_last_release = PullRequestsSinceLastRelease::fetch(octocrab).await?; @@ -42,31 +36,13 @@ pub async fn create_release_announcement( .await? .as_markdown(min_dollars, for_readme)?; - let mut file = create_file(&version).await?; + let mut file = util::create_blog_post_file("release", &version).await?; generate_announcement(date, version, sponsors, pull_requests, &mut file) .await?; Ok(()) } -async fn create_file(version: &str) -> anyhow::Result { - let dir = PathBuf::from(format!("content/blog/release/{version}")); - let file = dir.join("index.md"); - - // VS Code (and probably other editors/IDEs) renders the path in the output - // as a clickable link, so the user can open the file easily. - println!("Generating release announcement at {}", file.display()); - - fs::create_dir_all(&dir).await.with_context(|| { - format!("Failed to create directory `{}`", dir.display()) - })?; - let file = File::create(&file).await.with_context(|| { - format!("Failed to create file `{}`", file.display()) - })?; - - Ok(file) -} - async fn generate_announcement( date: String, version: String, diff --git a/tools/automator/src/blog/sponsors.rs b/tools/automator/src/blog/sponsors.rs new file mode 100644 index 000000000..39cb1f6c1 --- /dev/null +++ b/tools/automator/src/blog/sponsors.rs @@ -0,0 +1,58 @@ +use std::fmt::Write; + +use tokio::{fs::File, io::AsyncWriteExt}; + +use super::util; + +pub async fn create_sponsor_update() -> anyhow::Result<()> { + let month = util::now_ym(); + let date = util::now_ymd(); + + let mut file = util::create_blog_post_file("sponsors", &month).await?; + generate_update(date, month, &mut file).await?; + + Ok(()) +} + +async fn generate_update( + date: String, + month: String, + file: &mut File, +) -> anyhow::Result<()> { + let mut buf = String::new(); + write!( + buf, + "\ ++++ +title = \"Sponsor Update - {month}\" +date = {date} + +# Uncomment to generate the HTML for the email newsletter. +# template = \"newsletter/email.html\" ++++ + +Hey folks! + +I just sent out the new sponsor update! Topics this month include: + +- **TASK: Summarize sponsor update.** + +If you want to receive monthly behind-the-scenes updates too, why not support Fornjot by [becoming a sponsor](https://github.com/sponsors/hannobraun)? You can start with as little as $2 a month. More substantial contributions are also welcome, of course 😁 + +I dedicate a substantial chunk of my week to working on Fornjot. Your contribution can help make that more sustainable. + + +### Not receiving these updates? + +I've been sending out an update every month since February 2022. If you are a sponsor and haven't received those updates, maybe you are not opted in? Update your sponsorship over at GitHub, and make sure you check `Receive email updates from hannobraun`. Also make sure to check the spam folder in your email client. + +If you still haven't received an update, [please contact me](mailto:hanno@braun-odw.eu). I'm happy to send you a copy directly. + +I'm sorry for any inconvenience! Unfortunately, GitHub gives me no control over, or insight into, who is receiving those updates. +" + )?; + + file.write_all(buf.as_bytes()).await?; + + Ok(()) +} diff --git a/tools/automator/src/blog/util.rs b/tools/automator/src/blog/util.rs new file mode 100644 index 000000000..a9beee4a5 --- /dev/null +++ b/tools/automator/src/blog/util.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +use anyhow::Context; +use chrono::{Datelike, Utc}; +use tokio::fs::{self, File}; + +pub fn now_ym() -> String { + let now = Utc::now(); + format!("{}-{:02}", now.year(), now.month()) +} + +pub fn now_ymd() -> String { + let now = Utc::now(); + format!("{}-{:02}-{:02}", now.year(), now.month(), now.day()) +} + +pub async fn create_blog_post_file( + category: &str, + title: &str, +) -> anyhow::Result { + let dir = PathBuf::from(format!("content/blog/{category}/{title}")); + let file = dir.join("index.md"); + + // VS Code (and probably other editors/IDEs) renders the path in the output + // as a clickable link, so the user can open the file easily. + println!("Generating `{category}` blog post at {}", file.display()); + + fs::create_dir_all(&dir).await.with_context(|| { + format!("Failed to create directory `{}`", dir.display()) + })?; + let file = File::create(&file).await.with_context(|| { + format!("Failed to create file `{}`", file.display()) + })?; + + Ok(file) +} diff --git a/tools/automator/src/main.rs b/tools/automator/src/main.rs index b94f4c1a8..d45a43937 100644 --- a/tools/automator/src/main.rs +++ b/tools/automator/src/main.rs @@ -1,5 +1,5 @@ -mod announcement; mod args; +mod blog; mod pull_requests; mod run; mod sponsors; diff --git a/tools/automator/src/run.rs b/tools/automator/src/run.rs index a5fe2f271..6413ff6ef 100644 --- a/tools/automator/src/run.rs +++ b/tools/automator/src/run.rs @@ -4,7 +4,9 @@ use anyhow::Context; use octocrab::Octocrab; use crate::{ - announcement::create_release_announcement, args::Args, sponsors::Sponsors, + args::{Args, Blog}, + blog, + sponsors::Sponsors, }; pub async fn run() -> anyhow::Result<()> { @@ -13,16 +15,22 @@ pub async fn run() -> anyhow::Result<()> { let octocrab = Octocrab::builder().personal_token(token).build()?; match Args::parse() { - Args::Announcement => { - create_release_announcement(&octocrab) + Args::Blog(Blog::Release) => { + blog::create_release_announcement(&octocrab) .await .context("Failed to create release announcement")?; } + Args::Blog(Blog::SponsorUpdate) => { + blog::create_sponsor_update() + .await + .context("Failed to create sponsor update")?; + } Args::Sponsors(args) => { + let min_dollars = 8; let sponsors = Sponsors::query(&octocrab) .await .context("Failed to query sponsors")? - .as_markdown(8, args.for_readme) + .as_markdown(min_dollars, args.for_readme) .context("Failed to format sponsors")?; println!("{sponsors}");