Skip to content

Commit

Permalink
Helper to build changelog (#1845)
Browse files Browse the repository at this point in the history
  • Loading branch information
cecton authored May 15, 2021
1 parent aaf1ec5 commit 93b6f3b
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 19 deletions.
35 changes: 34 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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/<crate> (#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
Expand Down Expand Up @@ -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)]
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ members = [
"examples/todomvc",
"examples/two_apps",
"examples/webgl",

# Release tools
"packages/changelog",
]
16 changes: 16 additions & 0 deletions packages/changelog/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "changelog"
version = "0.1.0"
authors = ["Cecile Tonglet <cecile.tonglet@cecton.com>"]
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"
208 changes: 208 additions & 0 deletions packages/changelog/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<String, Option<String>>,
}

impl GitHubUsers {
pub fn find_user_by_commit_author(
&mut self,
key: impl Into<String>,
commit: impl AsRef<str>,
) -> 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<str>) -> Result<Option<String>> {
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::<GitHubCommitApi>()?;

Ok(Some(body.author.login))
}
}

#[derive(Deserialize, Debug)]
pub struct GitHubCommitApi {
author: GitHubCommitAuthorApi,
}

#[derive(Deserialize, Debug)]
pub struct GitHubCommitAuthorApi {
login: String,
}
2 changes: 1 addition & 1 deletion packages/yew-macro/src/html_tree/html_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl Parse for HtmlBlock {
BlockContent::Node(Box::new(content.parse()?))
};

Ok(HtmlBlock { brace, content })
Ok(HtmlBlock { content, brace })
}
}

Expand Down
20 changes: 8 additions & 12 deletions packages/yew-macro/src/props/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,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()
Expand All @@ -154,13 +152,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! {
Expand Down
1 change: 1 addition & 0 deletions packages/yew/src/html/component/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ mod tests {
#[derive(Clone, Properties, Default)]
struct Props {
lifecycle: Rc<RefCell<Vec<String>>>,
#[allow(dead_code)]
#[cfg(feature = "wasm_test")]
create_message: Option<bool>,
update_message: RefCell<Option<bool>>,
Expand Down
5 changes: 1 addition & 4 deletions packages/yew/src/virtual_dom/vtag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,7 @@ impl VTag {
&mut self,
listeners: impl IntoIterator<Item = Option<Rc<dyn Listener>>>,
) {
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
Expand Down
2 changes: 1 addition & 1 deletion packages/yewtil/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ impl<S: Store> Agent for StoreWrapper<S> {

StoreWrapper {
handlers,
state,
link,
state,
self_dispatcher,
}
}
Expand Down

0 comments on commit 93b6f3b

Please sign in to comment.