From 1be278e60cca7433225bd2502afbd6e1200fb976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 18 Sep 2024 00:21:56 +0200 Subject: [PATCH] Save `CHANGELOG.md` after each review in `changelog` tool --- CHANGELOG.md | 1 - examples/changelog/fonts/changelog-icons.ttf | Bin 5764 -> 0 bytes examples/changelog/src/changelog.rs | 69 +++++++++---- examples/changelog/src/main.rs | 96 ++++++++++--------- 4 files changed, 99 insertions(+), 67 deletions(-) delete mode 100644 examples/changelog/fonts/changelog-icons.ttf diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ac8d68..2a381e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix `block_on` in `iced_wgpu` hanging Wasm builds. [#2313](https://github.com/iced-rs/iced/pull/2313) Many thanks to... -- @hecrj - @n1ght-hunter ## [0.12.1] - 2024-02-22 diff --git a/examples/changelog/fonts/changelog-icons.ttf b/examples/changelog/fonts/changelog-icons.ttf deleted file mode 100644 index a0f32553f25efedf1acc7a9cf56e65b21a170310..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5764 zcmZQzWME+6XJ}wxW+-qE4s}xKR;^-SVEDtpz!2getZ!te?75eLfiZxAfgvF|H?iQQ z#_m-N42&BX7#ItZ%Ssd&z<~V>0|NtJT0wellHSiM1_t&!3=Dic>50V!3=9kc3=Hf? z7#J8h(sL@)v^f6fF)%QL^zF+?O-#|$2rpz{V1B^Bz+jer?V3mvv91M&MY*0}a<{-ui46F=1_qG*j3HomGBEftFoRh-49pB%3=E7R3@i*vU^Oft@fQsL|1&T^NCuGG!Tw_e zxf@~r$Yk+=y)6SK!Jz_59w5xX$iT$F@IQ-b4|5QMAcG`BhW7^EfCvW-P;|2uFfg+) zvM@6?FfcN*62c4&LJard z=9t1$HzNZ-gDF&;iGiKL1x{m<>!^8=H%q-CFkcRXC&sOr{?6R>t-hB=M@K~rkCa<7NLoval=xJiZk=` z6b$tY&}7_G^HPfvOHxxnHW!zr8 zgEK=uLjgl2LlHwJLpnnSLkWWdg9d{$gC>IlgAs!PgAs!Xg91YmLnVU(Lq1ehCPN-W zB0~;?0z)E0DMJZE23V$;L4m=6L4hHSA)g_Sp@booA%`J{A)i5yA(oE zF&Gdq(VZccArGuC5$y65xSxv|${5laj2KE7N*L0>zAFOzMuEW%Y(B!D3Je7dAh|4t zREA`R5(YhRXqGS(Fjz6@Gw94eD0Na+nssvCHmlQ%E~MQmX31}RqDz~e0Ky+ID7O3piRhX6w|NW~6ThNQ^Il*Gsl zjM|ZrP#;7pq-+p#R(6Wi-N34&;Ht2JMKv)+VFSB!K*R=iWv2}s%1+XXijf-_6LdGQ zfrEPkv$jIYM)o8p1&}1jyV4NngUAgG39iW-ShW-tHgGsAbSZRgVAS4dz^b}|MJ+HQ zAwW7YC^AAhQZZ6tgF|q{21adZP`E&yqPu}zX9K6R_9g};^bp+c6HKH zgeM$s9n^%x12KhBTX_S6^9B~x1O?YFa{ME|Dr5cvF=tbTwIfq7U z6cTdYz?kT?K~Ni%I&I_!5duuAP8%4-lod8Gs|G}D1m(jGEUHdjT?!y;1+)~E zH!vo`*wPSrkQOG@#0>&kijf=mo!vJGIJ+loU_y;7g$-Dva3ceQu(HbrM&}I-cAFR( z8Nu0JREJ?B1Cwh=#0DW{Ck59He9BH6c$GIWBseJ~Y?qK=WDsN!W^i(H0)>ExveO2} z#El{x+8esHrIjLeH;CzMWDwQX-5{>Bkr6~o=xk&H(ULkFnL)Ia&PEmxEv>VW6-3ME zY-9t`vN{{tLA0FCMh*}yud|U8L~H9P!@_?9TY`5;gtCI(2F6%WZqePKgCr>imn?)x z>LN+1!6l0zk_tKu8~C+x`f~$g;#P=PLH14N5v289}tN&PFB>t)jD$ z8APknJ2_kVsH) zR^Gsv;0!99L77kwlx7WdHt1__(AU!4V5p;@;I4opHkA`CltBeMUKP@ck-8gb&W8H1jnK=Qt;qXW1^#A3U;}v4g*w;D`qjVfl(W4+5?F*h;0Ga zTP}9W78^|%ML~Ww(^0TdaL3`p4GhkS7P=eEb@aq-bT?QKQo4cBIoU#YgQdNPZiyDs${V}60gR)d6m;szdNCrC;KC1+&?8wEXO3#fZ_?8OWhf z-@t>wQ)dH%C?wl&VgTiEVZ9AzTDlv&aEN>BY-C^*5#C^~rMtmLXM?4d?gn2_D7q>u z=qb2$DJQ~0%TEWCpf~tx>u&JZ*~Gx$rmednKxY#JBZv{GvxyNb5~Q<<5iAl6Qsb_z zyCDRm2E+&jsR1#cFg6kUB6c4x|puiU-;0s;#>r0c0nLkqELA#7F|!31TFJ)Ocv?Zb$*C0WnfR zYCw!MkQxvp9b_Mai?;5D43K?bRwl?kFe?jWADER5QpX5VmjhA{_wLzF590QCcxF(n)&*N9qPC0Afa6x4GfGd4jsuMV3iCmksz> Result<(Self, Vec), Error> { + pub async fn list() -> Result<(Self, Vec), Error> { let mut changelog = Self::new(); { @@ -97,7 +97,7 @@ impl Changelog { } } - let mut candidates = Candidate::list().await?; + let mut candidates = Contribution::list().await?; for reviewed_entry in changelog.entries() { candidates.retain(|candidate| candidate.id != reviewed_entry); @@ -106,6 +106,30 @@ impl Changelog { Ok((changelog, candidates)) } + pub async fn save(self) -> Result<(), Error> { + let markdown = fs::read_to_string("CHANGELOG.md").await?; + + let Some((header, rest)) = markdown.split_once("\n## ") else { + return Err(Error::InvalidFormat); + }; + + let Some((_unreleased, rest)) = rest.split_once("\n## ") else { + return Err(Error::InvalidFormat); + }; + + let unreleased = format!( + "\n## [Unreleased]\n{changelog}", + changelog = self.to_string() + ); + + let rest = format!("\n## {rest}"); + + let changelog = [header, &unreleased, &rest].concat(); + fs::write("CHANGELOG.md", changelog).await?; + + Ok(()) + } + pub fn len(&self) -> usize { self.ids.len() } @@ -132,7 +156,7 @@ impl Changelog { target.push(item); - if !self.authors.contains(&entry.author) { + if entry.author != "hecrj" && !self.authors.contains(&entry.author) { self.authors.push(entry.author); self.authors.sort_by_key(|author| author.to_lowercase()); } @@ -238,21 +262,12 @@ impl fmt::Display for Category { } #[derive(Debug, Clone)] -pub struct Candidate { +pub struct Contribution { pub id: u64, } -#[derive(Debug, Clone)] -pub struct PullRequest { - pub id: u64, - pub title: String, - pub description: String, - pub labels: Vec, - pub author: String, -} - -impl Candidate { - pub async fn list() -> Result, Error> { +impl Contribution { + pub async fn list() -> Result, Error> { let output = process::Command::new("git") .args([ "log", @@ -273,20 +288,31 @@ impl Candidate { let (_, pull_request) = title.split_once("#")?; let (pull_request, _) = pull_request.split_once([')', ' '])?; - Some(Candidate { + Some(Contribution { id: pull_request.parse().ok()?, }) }) .collect()) } +} - pub async fn fetch(self) -> Result { +#[derive(Debug, Clone)] +pub struct PullRequest { + pub id: u64, + pub title: String, + pub description: String, + pub labels: Vec, + pub author: String, +} + +impl PullRequest { + pub async fn fetch(contribution: Contribution) -> Result { let request = reqwest::Client::new() .request( reqwest::Method::GET, format!( "https://api.github.com/repos/iced-rs/iced/pulls/{}", - self.id + contribution.id ), ) .header("User-Agent", "iced changelog generator") @@ -319,8 +345,8 @@ impl Candidate { let schema: Schema = request.send().await?.json().await?; - Ok(PullRequest { - id: self.id, + Ok(Self { + id: contribution.id, title: schema.title, description: schema.body, labels: schema.labels.into_iter().map(|label| label.name).collect(), @@ -339,6 +365,9 @@ pub enum Error { #[error("no GITHUB_TOKEN variable was set")] GitHubTokenNotFound, + + #[error("the changelog format is not valid")] + InvalidFormat, } impl From for Error { diff --git a/examples/changelog/src/main.rs b/examples/changelog/src/main.rs index 73da0f2c..79fa37b5 100644 --- a/examples/changelog/src/main.rs +++ b/examples/changelog/src/main.rs @@ -1,36 +1,33 @@ mod changelog; -mod icon; use crate::changelog::Changelog; -use iced::clipboard; use iced::font; use iced::widget::{ button, center, column, container, markdown, pick_list, progress_bar, rich_text, row, scrollable, span, stack, text, text_input, }; -use iced::{Element, Fill, FillPortion, Font, Task, Theme}; +use iced::{Center, Element, Fill, FillPortion, Font, Task, Theme}; pub fn main() -> iced::Result { iced::application("Changelog Generator", Generator::update, Generator::view) - .font(icon::FONT_BYTES) .theme(Generator::theme) .run_with(Generator::new) } enum Generator { Loading, - Empty, Reviewing { changelog: Changelog, - pending: Vec, + pending: Vec, state: State, preview: Vec, }, + Done, } enum State { - Loading(changelog::Candidate), + Loading(changelog::Contribution), Loaded { pull_request: changelog::PullRequest, description: Vec, @@ -42,7 +39,7 @@ enum State { #[derive(Debug, Clone)] enum Message { ChangelogListed( - Result<(Changelog, Vec), changelog::Error>, + Result<(Changelog, Vec), changelog::Error>, ), PullRequestFetched(Result), UrlClicked(markdown::Url), @@ -50,7 +47,8 @@ enum Message { CategorySelected(changelog::Category), Next, OpenPullRequest(u64), - CopyPreview, + ChangelogSaved(Result<(), changelog::Error>), + Quit, } impl Generator { @@ -64,23 +62,23 @@ impl Generator { fn update(&mut self, message: Message) -> Task { match message { Message::ChangelogListed(Ok((changelog, mut pending))) => { - if let Some(candidate) = pending.pop() { + if let Some(contribution) = pending.pop() { let preview = markdown::parse(&changelog.to_string()).collect(); *self = Self::Reviewing { changelog, pending, - state: State::Loading(candidate.clone()), + state: State::Loading(contribution.clone()), preview, }; Task::perform( - candidate.fetch(), + changelog::PullRequest::fetch(contribution), Message::PullRequestFetched, ) } else { - *self = Self::Empty; + *self = Self::Done; Task::none() } @@ -108,12 +106,6 @@ impl Generator { Task::none() } - Message::ChangelogListed(Err(error)) - | Message::PullRequestFetched(Err(error)) => { - log::error!("{error}"); - - Task::none() - } Message::UrlClicked(url) => { let _ = webbrowser::open(url.as_str()); @@ -172,19 +164,27 @@ impl Generator { { changelog.push(entry); + let save = Task::perform( + changelog.clone().save(), + Message::ChangelogSaved, + ); + *preview = markdown::parse(&changelog.to_string()).collect(); - if let Some(candidate) = pending.pop() { - *state = State::Loading(candidate.clone()); + if let Some(contribution) = pending.pop() { + *state = State::Loading(contribution.clone()); - Task::perform( - candidate.fetch(), - Message::PullRequestFetched, - ) + Task::batch([ + save, + Task::perform( + changelog::PullRequest::fetch(contribution), + Message::PullRequestFetched, + ), + ]) } else { - // TODO: We are done! - Task::none() + *self = Self::Done; + save } } else { Task::none() @@ -197,20 +197,32 @@ impl Generator { Task::none() } - Message::CopyPreview => { - let Self::Reviewing { changelog, .. } = self else { - return Task::none(); - }; + Message::ChangelogSaved(Ok(())) => Task::none(), - clipboard::write(changelog.to_string()) + Message::ChangelogListed(Err(error)) + | Message::PullRequestFetched(Err(error)) + | Message::ChangelogSaved(Err(error)) => { + log::error!("{error}"); + + Task::none() } + Message::Quit => iced::exit(), } } fn view(&self) -> Element { match self { Self::Loading => center("Loading...").into(), - Self::Empty => center("No changes found!").into(), + Self::Done => center( + column![ + text("Changelog is up-to-date! 🎉") + .shaping(text::Shaping::Advanced), + button("Quit").on_press(Message::Quit), + ] + .spacing(10) + .align_x(Center), + ) + .into(), Self::Reviewing { changelog, pending, @@ -237,8 +249,8 @@ impl Generator { }; let form: Element<_> = match state { - State::Loading(candidate) => { - text!("Loading #{}...", candidate.id).into() + State::Loading(contribution) => { + text!("Loading #{}...", contribution.id).into() } State::Loaded { pull_request, @@ -318,7 +330,7 @@ impl Generator { } }; - let preview: Element<_> = if preview.is_empty() { + let preview = if preview.is_empty() { center( container( text("The changelog is empty... so far!").size(12), @@ -326,9 +338,8 @@ impl Generator { .padding(10) .style(container::rounded_box), ) - .into() } else { - let content = container( + container( scrollable( markdown::view( preview, @@ -343,14 +354,7 @@ impl Generator { ) .width(Fill) .padding(10) - .style(container::rounded_box); - - let copy = button(icon::copy().size(12)) - .on_press(Message::CopyPreview) - .style(button::text); - - center(stack![content, container(copy).align_right(Fill)]) - .into() + .style(container::rounded_box) }; let review = column![container(form).height(Fill), progress]