diff --git a/CHANGELOG.md b/CHANGELOG.md index b8630cfb4..a308286f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## ✨ **0.18.0** *(2021-05-15)* + +#### Changelog + +- #### 🛠 Fixes + + - Fix missing redirects (#1640). [[@siku2] [#1640]](https://github.com/yewstack/yew/pull/1640) + +- #### ⚡️ Features + + - Implicit optional attributes (#1637). [[@siku2] [#1637]](https://github.com/yewstack/yew/pull/1637) + - Added callback_future_once in yewtil (#1696) (#1712). [[@fraillt] [#1696]](https://github.com/yewstack/yew/pull/1696) + - Added relevant examples section to the docs (#1695). [[@oOBoomberOo] [#1695]](https://github.com/yewstack/yew/pull/1695) + - Enable std feature for indexmap (#1709). [[@jstarry] [#1709]](https://github.com/yewstack/yew/pull/1709) + - Added missing KeyboardService re-export (#1694). [[@SOF3] [#1694]](https://github.com/yewstack/yew/pull/1694) + - Clean up component lifecycle state (#1700). [[@jstarry] [#1700]](https://github.com/yewstack/yew/pull/1700) + - Move top-level crates to packages/ (#1680). [[@philip-peterson] [#1680]](https://github.com/yewstack/yew/pull/1680) + - Refactor component lifecycle event handling (#1692). [[@jstarry] [#1692]](https://github.com/yewstack/yew/pull/1692) + - Prune stdweb examples to reduce maintenance burden (#1690). [[@jstarry] [#1690]](https://github.com/yewstack/yew/pull/1690) + - Refactor html module into new component submodule (#1689). [[@jstarry] [#1689]](https://github.com/yewstack/yew/pull/1689) + - Rename internal Agent structs to match Component (#1688). [[@jstarry] [#1688]](https://github.com/yewstack/yew/pull/1688) + - Revert "Update rand requirement from 0.7 to 0.8 (#1682)" (#1684). [[@siku2] [#1682]](https://github.com/yewstack/yew/pull/1682) + - Add discussion link to issue selector (#1674). [[@jstarry] [#1674]](https://github.com/yewstack/yew/pull/1674) + - Update link to Material Design Components (#1662). [[@TapioT] [#1662]](https://github.com/yewstack/yew/pull/1662) + - Extract Classes to a separate macro (#1601). [[@cecton] [#1601]](https://github.com/yewstack/yew/pull/1601) + - Improve the "keyed_list" example (#1650). [[@titaneric] [#1650]](https://github.com/yewstack/yew/pull/1650) + - Apply new Clippy lints, examples deployment, and stdweb integration tests (#1651). [[@siku2] [#1651]](https://github.com/yewstack/yew/pull/1651) + - Add documentation for component children (#1616). [[@K4rakara] [#1616]](https://github.com/yewstack/yew/pull/1616) + - More ergonomic use state 1505 (#1630). [[@mattferrin] [#1630]](https://github.com/yewstack/yew/pull/1630) + - Remove Drop bound from Task trait (#1627). [[@siku2] [#1627]](https://github.com/yewstack/yew/pull/1627) + - Document dynamic tag names (#1628). [[@siku2] [#1628]](https://github.com/yewstack/yew/pull/1628) + - Add a macro for building properties outside of html! (#1599). [[@siku2] [#1599]](https://github.com/yewstack/yew/pull/1599) + ## ✨ **0.17.4** *(2020-10-18)* #### Changelog @@ -301,7 +334,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 017eadf89..d2b9e663a 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/packages/changelog/Cargo.toml b/packages/changelog/Cargo.toml new file mode 100644 index 000000000..103d92d9d --- /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 000000000..a3c5a2281 --- /dev/null +++ b/packages/changelog/src/main.rs @@ -0,0 +1,208 @@ +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"\(#(\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 = + if let Some(issue) = self.re_issue.captures(first_line).map(|x| x[1].to_string()) { + issue + } 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_owned(), 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}] [#{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}] [#{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 40891356c..b777e943c 100644 --- a/packages/yew-macro/src/props/component.rs +++ b/packages/yew-macro/src/props/component.rs @@ -144,9 +144,9 @@ impl ComponentProps { }); let set_children = children_renderer.map(|children| { - Some(quote_spanned! {props_ty.span()=> + quote_spanned! {props_ty.span()=> .children(#children) - }) + } }); quote_spanned! {props_ty.span()=> @@ -159,9 +159,9 @@ impl ComponentProps { Self::With(with_props) => { let ident = Ident::new("__yew_props", props_ty.span()); let set_children = children_renderer.map(|children| { - Some(quote_spanned! {props_ty.span()=> + quote_spanned! {props_ty.span()=> #ident.children = #children; - }) + } }); let expr = &with_props.expr; diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index f29a22564..8856c97f8 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -234,6 +234,7 @@ mod tests { #[derive(Clone, Properties, Default)] struct Props { lifecycle: Rc>>, + #[allow(dead_code)] #[cfg(feature = "wasm_test")] create_message: Option, update_message: RefCell>, diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 61694f8c3..f7fc96b77 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -213,10 +213,7 @@ impl VTag { &mut self, listeners: impl IntoIterator>>, ) { - self.listeners = listeners - .into_iter() - .filter_map(std::convert::identity) - .collect(); + self.listeners = listeners.into_iter().flatten().collect(); } /// Every render it removes all listeners and attach it back later