diff options
author | 2024-09-18 00:21:56 +0200 | |
---|---|---|
committer | 2024-09-18 00:21:56 +0200 | |
commit | 1be278e60cca7433225bd2502afbd6e1200fb976 (patch) | |
tree | 1a8767abbc7173e0fc854ac108a3374f22d41a18 /examples/changelog | |
parent | 547e509683007b9e0c149d847ac685f3aa770de8 (diff) | |
download | iced-1be278e60cca7433225bd2502afbd6e1200fb976.tar.gz iced-1be278e60cca7433225bd2502afbd6e1200fb976.tar.bz2 iced-1be278e60cca7433225bd2502afbd6e1200fb976.zip |
Save `CHANGELOG.md` after each review in `changelog` tool
Diffstat (limited to 'examples/changelog')
-rw-r--r-- | examples/changelog/fonts/changelog-icons.ttf | bin | 5764 -> 0 bytes | |||
-rw-r--r-- | examples/changelog/src/changelog.rs | 69 | ||||
-rw-r--r-- | examples/changelog/src/main.rs | 96 |
3 files changed, 99 insertions, 66 deletions
diff --git a/examples/changelog/fonts/changelog-icons.ttf b/examples/changelog/fonts/changelog-icons.ttf Binary files differdeleted file mode 100644 index a0f32553..00000000 --- a/examples/changelog/fonts/changelog-icons.ttf +++ /dev/null diff --git a/examples/changelog/src/changelog.rs b/examples/changelog/src/changelog.rs index 39cbb42c..9421f91c 100644 --- a/examples/changelog/src/changelog.rs +++ b/examples/changelog/src/changelog.rs @@ -29,7 +29,7 @@ impl Changelog { } } - pub async fn list() -> Result<(Self, Vec<Candidate>), Error> { + pub async fn list() -> Result<(Self, Vec<Contribution>), 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<String>, - pub author: String, -} - -impl Candidate { - pub async fn list() -> Result<Vec<Candidate>, Error> { +impl Contribution { + pub async fn list() -> Result<Vec<Contribution>, 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<PullRequest, Error> { +#[derive(Debug, Clone)] +pub struct PullRequest { + pub id: u64, + pub title: String, + pub description: String, + pub labels: Vec<String>, + pub author: String, +} + +impl PullRequest { + pub async fn fetch(contribution: Contribution) -> Result<Self, Error> { 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<io::Error> 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<changelog::Candidate>, + pending: Vec<changelog::Contribution>, state: State, preview: Vec<markdown::Item>, }, + Done, } enum State { - Loading(changelog::Candidate), + Loading(changelog::Contribution), Loaded { pull_request: changelog::PullRequest, description: Vec<markdown::Item>, @@ -42,7 +39,7 @@ enum State { #[derive(Debug, Clone)] enum Message { ChangelogListed( - Result<(Changelog, Vec<changelog::Candidate>), changelog::Error>, + Result<(Changelog, Vec<changelog::Contribution>), changelog::Error>, ), PullRequestFetched(Result<changelog::PullRequest, changelog::Error>), 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<Message> { 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(), + + Message::ChangelogListed(Err(error)) + | Message::PullRequestFetched(Err(error)) + | Message::ChangelogSaved(Err(error)) => { + log::error!("{error}"); - clipboard::write(changelog.to_string()) + Task::none() } + Message::Quit => iced::exit(), } } fn view(&self) -> Element<Message> { 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] |