diff --git a/CHANGELOG.md b/CHANGELOG.md index b8630cfb4b5..e3aeead8766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## ✨ **0.18.0** *(2021-05-15)* + +#### Changelog + +- #### 🛠 Fixes + + - Fix missing redirects. [[@siku2](https://github.com/siku2), [#1640](https://github.com/yewstack/yew/pull/1640)] + - Remove Drop bound from Task trait. [[@siku2](https://github.com/siku2), [#1627](https://github.com/yewstack/yew/pull/1627)] + - Enable std feature for indexmap. [[@jstarry](https://github.com/jstarry), [#1709](https://github.com/yewstack/yew/pull/1709)] + +- #### ⚡️ Features + + - Implicit optional attributes. [[@siku2](https://github.com/siku2), [#1637](https://github.com/yewstack/yew/pull/1637)] + - Added callback_future_once in yewtil.(#1712). [[@fraillt](https://github.com/fraillt), [#1696](https://github.com/yewstack/yew/pull/1696)] + - Added relevant examples section to the docs. [[@oOBoomberOo](https://github.com/oOBoomberOo), [#1695](https://github.com/yewstack/yew/pull/1695)] + - Added missing KeyboardService re-export. [[@SOF3](https://github.com/SOF3), [#1694](https://github.com/yewstack/yew/pull/1694)] + - Rename internal Agent structs to match Component. [[@jstarry](https://github.com/jstarry), [#1688](https://github.com/yewstack/yew/pull/1688)] + - Add discussion link to issue selector. [[@jstarry](https://github.com/jstarry), [#1674](https://github.com/yewstack/yew/pull/1674)] + - Update link to Material Design Components. [[@TapioT](https://github.com/TapioT), [#1662](https://github.com/yewstack/yew/pull/1662)] + - Extract Classes to a separate macro. [[@cecton](https://github.com/cecton), [#1601](https://github.com/yewstack/yew/pull/1601)] + - Improve the "keyed_list" example. [[@titaneric](https://github.com/titaneric), [#1650](https://github.com/yewstack/yew/pull/1650)] + - Add documentation for component children. [[@K4rakara](https://github.com/K4rakara), [#1616](https://github.com/yewstack/yew/pull/1616)] + - Add a macro for building properties outside of html!. [[@siku2](https://github.com/siku2), [#1599](https://github.com/yewstack/yew/pull/1599)] + ## ✨ **0.17.4** *(2020-10-18)* #### Changelog @@ -301,7 +325,7 @@ Lastly, take note that API docs on https://docs.rs/yew will be using the `"web_s - Implemented `PartialEq` for `ChildrenRenderer` to allow `children` comparison. [[@jstarry], [#916](https://github.com/yewstack/yew/pull/916)] - Reduced restrictions on `ComponentLink` methods to improve `Future` support. [[@jplatte], [#931](https://github.com/yewstack/yew/pull/931)] - Added `referrer`, `referrer_policy` and `integrity` to `FetchOptions`. [[@leo-lb], [#931](https://github.com/yewstack/yew/pull/931)] - + - #### 🛠 Fixes - Fixed touch event listeners. [[@AlephAlpha], [#872](https://github.com/yewstack/yew/pull/872)] diff --git a/Cargo.toml b/Cargo.toml index 227bc064108..036c5fec83b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,4 +44,7 @@ members = [ "examples/todomvc", "examples/two_apps", "examples/webgl", + + # Release tools + "packages/changelog", ] diff --git a/README.md b/README.md index 52703ce06fe..11d6aef5c46 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ | Documentation (latest) | - Examples + Examples | Changelog | diff --git a/packages/changelog/Cargo.toml b/packages/changelog/Cargo.toml new file mode 100644 index 00000000000..103d92d9d51 --- /dev/null +++ b/packages/changelog/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "changelog" +version = "0.1.0" +authors = ["Cecile Tonglet "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +chrono = "0.4" +git2 = "0.13" +regex = "1" +reqwest = { version = "0.11", features = ["blocking", "json"] } +serde = { version = "1", features = ["derive"] } +structopt = "0.3" diff --git a/packages/changelog/src/main.rs b/packages/changelog/src/main.rs new file mode 100644 index 00000000000..52a33145dd6 --- /dev/null +++ b/packages/changelog/src/main.rs @@ -0,0 +1,213 @@ +use anyhow::{bail, Context, Result}; +use serde::Deserialize; +use std::collections::HashMap; +use std::fs; +use std::io; +use std::io::Write; +use structopt::StructOpt; + +fn main() -> Result<()> { + Cli::from_args().run() +} + +#[derive(StructOpt)] +pub struct Cli { + /// From commit. + from: String, + + /// To commit. + #[structopt(default_value = "HEAD")] + to: String, + + #[structopt(skip = Self::open_repository())] + repo: git2::Repository, + + #[structopt(skip)] + github_users: GitHubUsers, + + #[structopt(skip = regex::Regex::new(r"\s*\(#(\d+)\)").unwrap())] + re_issue: regex::Regex, +} + +impl Cli { + fn open_repository() -> git2::Repository { + match git2::Repository::open(".") { + Err(err) => { + eprintln!("Error: could not open repository: {}", err); + std::process::exit(1); + } + Ok(repo) => repo, + } + } + + fn run(&mut self) -> Result<()> { + let mut old_changelog = + fs::File::open("CHANGELOG.md").context("could not open CHANGELOG.md for reading")?; + let mut f = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open("CHANGELOG.md.new") + .context("could not open CHANGELOG.md.new for writing")?; + + let mut revwalk = self.repo.revwalk()?; + revwalk.set_sorting(git2::Sort::TOPOLOGICAL)?; + + let from_object = self + .repo + .revparse_single(&self.from) + .context("Could not find `from` revision")?; + let to_object = self + .repo + .revparse_single(&self.to) + .context("Could not find `to` revision")?; + revwalk.hide(from_object.id())?; + revwalk.push(to_object.id())?; + + let mut logs = Vec::new(); + for oid in revwalk { + let oid = oid?; + let commit = self.repo.find_commit(oid)?; + let first_line = commit + .message() + .context("Invalid UTF-8 in commit message")? + .lines() + .next() + .context("Missing commit message")?; + let author = commit.author(); + let email = author.email().context("Missing author's email")?; + + if email.contains("dependabot") { + continue; + } + + let (issue, first_line) = + if let Some(caps) = self.re_issue.captures_iter(first_line).last() { + let first_line_stripped = vec![ + &first_line[..caps.get(0).unwrap().start()], + &first_line[caps.get(0).unwrap().end()..], + ] + .join(""); + (caps[1].to_string(), first_line_stripped) + } else { + eprintln!("Missing issue for commit: {}", oid); + continue; + }; + + let user = self + .github_users + .find_user_by_commit_author(email, oid.to_string()) + .with_context(|| format!("Could not find GitHub user for commit: {}", oid))?; + + logs.push((first_line.to_string(), user.to_owned(), issue.to_owned())); + } + + let (features, fixes): (Vec<_>, Vec<_>) = logs + .into_iter() + .partition(|(msg, _, _)| msg.to_lowercase().contains("fix")); + + writeln!( + f, + "## ✨ **x.y.z** *({})*", + chrono::Utc::now().format("%Y-%m-%d") + )?; + writeln!(f)?; + writeln!(f, "#### Changelog")?; + writeln!(f)?; + + writeln!(f, "- #### 🛠 Fixes")?; + writeln!(f)?; + for (msg, user, issue) in fixes { + writeln!( + f, + " - {msg}. [[@{user}](https://github.com/{user}), [#{issue}](https://github.com/yewstack/yew/pull/{issue})]", + msg = msg, + user = user, + issue = issue + )?; + } + + writeln!(f, "- #### ⚡️ Features")?; + writeln!(f)?; + for (msg, user, issue) in features { + writeln!( + f, + " - {msg}. [[@{user}](https://github.com/{user}), [#{issue}](https://github.com/yewstack/yew/pull/{issue})]", + msg = msg, + user = user, + issue = issue + )?; + } + + writeln!(f)?; + io::copy(&mut old_changelog, &mut f)?; + + drop(old_changelog); + drop(f); + + fs::remove_file("CHANGELOG.md").context("Could not delete CHANGELOG.md")?; + fs::rename("CHANGELOG.md.new", "CHANGELOG.md") + .context("Could not replace CHANGELOG.md with CHANGELOG.md.new")?; + + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct GitHubUsers { + cache: HashMap>, +} + +impl GitHubUsers { + pub fn find_user_by_commit_author( + &mut self, + key: impl Into, + commit: impl AsRef, + ) -> Option<&str> { + self.cache + .entry(key.into()) + .or_insert_with(|| match Self::query_commit(commit) { + Ok(value) => value, + Err(err) => { + eprintln!("Error: {}", err); + None + } + }) + .as_deref() + } + + fn query_commit(q: impl AsRef) -> Result> { + std::thread::sleep(std::time::Duration::from_secs(1)); + let client = reqwest::blocking::Client::new(); + let resp = client + .get(format!( + "https://api.github.com/repos/yewstack/yew/commits/{}", + q.as_ref(), + )) + .header("user-agent", "reqwest") + .header("accept", "application/vnd.github.v3+json") + .send()?; + let status = resp.status(); + if !status.is_success() { + if let Some(remaining) = resp.headers().get("x-ratelimit-remaining") { + if remaining == "0" { + bail!("GitHub API limit reached."); + } + } + bail!("GitHub API request error: {}", status); + } + let body = resp.json::()?; + + Ok(Some(body.author.login)) + } +} + +#[derive(Deserialize, Debug)] +pub struct GitHubCommitApi { + author: GitHubCommitAuthorApi, +} + +#[derive(Deserialize, Debug)] +pub struct GitHubCommitAuthorApi { + login: String, +} diff --git a/packages/yew-macro/src/props/component.rs b/packages/yew-macro/src/props/component.rs index b735e95d0f0..e9b9085b335 100644 --- a/packages/yew-macro/src/props/component.rs +++ b/packages/yew-macro/src/props/component.rs @@ -146,13 +146,11 @@ impl ComponentProps { )} }); - let set_children = if let Some(children) = children_renderer { - Some(quote_spanned! {props_ty.span()=> + let set_children = children_renderer.map(|children| { + quote_spanned! {props_ty.span()=> .children(#children) - }) - } else { - None - }; + } + }); quote_spanned! {props_ty.span()=> <#props_ty as ::yew::html::Properties>::builder() @@ -163,13 +161,11 @@ impl ComponentProps { } Self::With(with_props) => { let ident = Ident::new("__yew_props", props_ty.span()); - let set_children = if let Some(children) = children_renderer { - Some(quote_spanned! {props_ty.span()=> + let set_children = children_renderer.map(|children| { + quote_spanned! {props_ty.span()=> #ident.children = #children; - }) - } else { - None - }; + } + }); let expr = &with_props.expr; quote! { diff --git a/packages/yew-router-macro/Cargo.toml b/packages/yew-router-macro/Cargo.toml index d28da0f2337..b8e486f1c11 100644 --- a/packages/yew-router-macro/Cargo.toml +++ b/packages/yew-router-macro/Cargo.toml @@ -17,4 +17,4 @@ yew-router-route-parser = { version = "0.14.0", path = "../yew-router-route-pars proc-macro2 = "1.0.1" [dev-dependencies] -yew-router = { version = "0.14.0", path = "../yew-router" } # This should probably be removed, it makes the deploy process much more annoying. +yew-router = { version = "0.15.0", path = "../yew-router" } # This should probably be removed, it makes the deploy process much more annoying. diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 560d12a0f18..8c7d097dbd5 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/yewstack/yew" homepage = "https://github.com/yewstack/yew" documentation = "https://docs.rs/yew/" license = "MIT OR Apache-2.0" -readme = "../README.md" +readme = "../../README.md" keywords = ["web", "webasm", "javascript"] categories = ["gui", "wasm", "web-programming"] description = "A framework for making client-side single-page apps" diff --git a/packages/yewtil/src/store.rs b/packages/yewtil/src/store.rs index 06a079b6360..05f4e813b25 100644 --- a/packages/yewtil/src/store.rs +++ b/packages/yewtil/src/store.rs @@ -90,8 +90,8 @@ impl Agent for StoreWrapper { StoreWrapper { handlers, - state, link, + state, self_dispatcher, } }