Skip to content

Commit

Permalink
Implement changelog update functionality. (#646)
Browse files Browse the repository at this point in the history
2 notes:
* This PR correctly writes back to Cargo.toml file however the original
format is not preserved. Any easy way to do that?
* Not yet handling updates to crates that depend on originated change.
That is next.
  • Loading branch information
exzachlyvv authored Nov 7, 2024
1 parent 64ef8cb commit ba8f253
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 6 deletions.
154 changes: 149 additions & 5 deletions crates/cli-tools/src/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use std::io::BufRead;

use anyhow::{anyhow, bail, ensure, Context, Result};
use clap::ValueEnum;
use semver::Version;
use semver::{Prerelease, Version};
use tokio::process::Command;

use crate::cargo::metadata;
Expand Down Expand Up @@ -58,6 +58,38 @@ impl Changelog {
Self::parse(&String::from_utf8(fs::read(path).await?)?)
}

async fn write_file(&self, path: &str) -> Result<()> {
fs::write(path, self.to_string().as_bytes()).await
}

fn push_description(&mut self, severity: Severity, content: &str) -> Result<()> {
let content = if content.starts_with("- ") { content } else { &format!("- {content}") };
let current_release = self.get_or_create_release_mut();
current_release.push_content(severity, content)
}

// Gets newest (first) release. Creates a new one if newest release is not prerelease.
fn get_or_create_release_mut(&mut self) -> &mut Release {
let current_release = self.releases.first().unwrap();

// Current version is released, insert a new one.
if current_release.version.pre.is_empty() {
let mut next_version = Version::new(
current_release.version.major,
current_release.version.minor,
current_release.version.patch + 1,
);

next_version.pre = Prerelease::new("git").unwrap();

let new_release = Release::from(next_version);

self.releases.insert(0, new_release);
}

self.releases.first_mut().unwrap()
}

/// Parses and validates a changelog.
fn parse(input: &str) -> Result<Changelog> {
let mut releases: Vec<Release> = Vec::new();
Expand Down Expand Up @@ -147,6 +179,24 @@ impl Changelog {
);
Ok(())
}

async fn sync_cargo_toml(&self, path: &str) -> Result<()> {
let expected_version = &self.releases.first().unwrap().version;

let mut sed = Command::new("sed");
sed.arg("-i");
sed.arg(format!("s#^version = .*#version = \"{expected_version}\"#"));
sed.arg("Cargo.toml");
cmd::execute(sed.current_dir(path)).await?;

Ok(())
}

async fn sync_dependencies(&self, _path: &str) -> Result<()> {
// TODO

Ok(())
}
}

impl Display for Changelog {
Expand Down Expand Up @@ -209,6 +259,19 @@ struct Release {
contents: BTreeMap<Severity, Vec<String>>,
}

impl Release {
fn push_content(&mut self, severity: Severity, content: &str) -> Result<()> {
self.contents.entry(severity).or_default().push(content.to_string());
Ok(())
}
}

impl From<Version> for Release {
fn from(version: Version) -> Self {
Release { version, contents: BTreeMap::new() }
}
}

impl Display for Release {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "## {}\n", self.version)?;
Expand Down Expand Up @@ -260,12 +323,17 @@ pub async fn execute_ci() -> Result<()> {
}

/// Updates a changelog file and changelog files of dependencies.
pub async fn execute_change(path: &str, _severity: &Severity, _description: &str) -> Result<()> {
ensure!(fs::exists(path).await, "Crate does not exist: {path}");
pub async fn execute_change(path: &str, severity: Severity, description: &str) -> Result<()> {
let changelog_file_path = format!("{path}/CHANGELOG.md");

let mut changelog = Changelog::read_file(&changelog_file_path).await?;

let _changelog = Changelog::read_file(&format!("{path}/CHANGELOG.md")).await?;
changelog.push_description(severity, description)?;
changelog.write_file(&changelog_file_path).await?;
changelog.sync_cargo_toml(path).await?;
changelog.sync_dependencies(path).await?;

todo!("Implement changelog updates");
Ok(())
}

#[cfg(test)]
Expand Down Expand Up @@ -717,4 +785,80 @@ mod tests {
"Unexpected prerelease line 9"
);
}

#[test]
fn push_description_prepends_dash_only_when_needed() {
let changelog_str = r"# Changelog
## 0.2.0-git
### Major
- A change
## 0.1.0
<!-- Increment to skip CHANGELOG.md test: 0 -->
";

let mut changelog = Changelog::parse(changelog_str).expect("Failed to parse changelog.");

changelog.push_description(Severity::Major, "testing no dash").unwrap();
changelog.push_description(Severity::Major, "- testing with dash").unwrap();

assert_eq!(
changelog.get_or_create_release_mut().contents.get(&Severity::Major).unwrap(),
&vec![
"- A change".to_string(),
"- testing no dash".to_string(),
"- testing with dash".to_string(),
]
);
}

#[test]
fn get_or_create_release_mut_uses_first_when_prerelease() {
let changelog_str = r"# Changelog
## 0.2.0-git
### Major
- A change
## 0.1.0
<!-- Increment to skip CHANGELOG.md test: 0 -->
";

let mut changelog = Changelog::parse(changelog_str).expect("Failed to parse changelog.");

let current_release = changelog.get_or_create_release_mut();

assert_eq!(current_release.version, Version::parse("0.2.0-git").unwrap());
}

#[test]
fn get_or_create_release_mut_creates_new_when_current_is_released() {
let changelog_str = r"# Changelog
## 0.2.0
### Major
- A change
## 0.1.0
<!-- Increment to skip CHANGELOG.md test: 0 -->
";

let mut changelog = Changelog::parse(changelog_str).expect("Failed to parse changelog.");

let current_release = changelog.get_or_create_release_mut();

assert_eq!(current_release.version, Version::parse("0.2.1-git").unwrap());
// Assert the new release already inserted
assert_eq!(changelog.releases.len(), 3);
}
}
2 changes: 1 addition & 1 deletion crates/xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ impl Flags {
MainCommand::Changelog(subcommand) => match subcommand.command {
ChangelogCommand::Ci => changelog::execute_ci().await,
ChangelogCommand::Change { path, severity, description } => {
changelog::execute_change(&path, &severity, &description).await
changelog::execute_change(&path, severity, &description).await
}
},
}
Expand Down

0 comments on commit ba8f253

Please sign in to comment.