Merge pull request #315 from gwilymk/release-script-in-rust

Release script in rust
This commit is contained in:
Gwilym Kuiper 2022-10-02 18:38:08 +01:00 committed by GitHub
commit 416f238062
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 579 additions and 127 deletions

View file

@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y).
- Fixed formatting of fixed point numbers in the range (-1, 0), which previously appeared positive.
## Changed
### Changed
- `testing` is now a default feature, so you no longer need to add a separate `dev-dependencies` line for `agb` in order to enable unit tests for your project.
## [0.11.1] - 2022/08/02

View file

@ -68,8 +68,11 @@ update-linker-scripts:
publish: (_run-tool "publish")
release +args: (_run-tool "release" args)
_run-tool +tool:
cargo run --manifest-path "{{justfile_directory() + "/tools/Cargo.toml"}}" -- {{tool}}
(cd tools && cargo build)
"$CARGO_TARGET_DIR/debug/tools" {{tool}}
_build-rom folder name:
#!/usr/bin/env bash

View file

@ -1,96 +0,0 @@
#!/usr/bin/env bash
# Fail if any command fails
set -e
set -x
VERSION=$1
NO_COMMIT=$2
# Sanity check that we actually have a version
if [ "$VERSION" = "" ]; then
echo "Usage $0 <version> [--no-commit]"
exit 1
fi
# Check the format of version
if echo "$VERSION" | grep -q -Ev "^[0-9]+\.[0-9]+\.[0-9]+$"; then
echo "Version must be of the form x.y.z, got $VERSION"
exit 1
fi
# Check if no commit option is valid
if [ ! "$NO_COMMIT" = "" ] && [ ! "$NO_COMMIT" = "--no-commit" ]; then
echo "Must pass either no last argument or --no-commit"
exit 1
fi
function maybe_git() {
if [ "$NO_COMMIT" = "--no-commit" ]; then
echo "Would run: git $*"
else
git "$@"
fi
}
# Check that no out-standing changes in git
if [ "$NO_COMMIT" = "" ] && [ -n "$(git status --porcelain)" ]; then
echo "Uncommitted changes, please commit first"
exit 1
fi
# Check that we are in the master branch, but only if actually committing
if [ ! "$NO_COMMIT" = "--no-commit" ] && [ "$(git symbolic-ref --short HEAD)" != "master" ]; then
echo "You must be in the master branch before releasing"
exit 1
fi
TAGNAME="v$VERSION"
for PROJECT_TOML_FILE in agb/Cargo.toml agb-*/Cargo.toml; do
DIRECTORY=$(dirname "$PROJECT_TOML_FILE")
# Update the version in Cargo.toml
sed -i -e "s/^version = \".*\"/version = \"$VERSION\"/" "$DIRECTORY/Cargo.toml"
# Also update the lock file
(cd "$DIRECTORY" && cargo update)
if [ "$DIRECTORY" = "agb" ]; then
# also update the agb version in the template and the examples
sed -i -e "s/^agb = \".*\"/agb = \"$VERSION\"/" template/Cargo.toml
for EXAMPLE_TOML_FILE in examples/*/Cargo.toml book/games/*/Cargo.toml template/Cargo.toml; do
EXAMPLE_DIR=$(dirname "$EXAMPLE_TOML_FILE")
sed -E -i -e "/agb =/ s/version = \"[^\"]+\"/version = \"$VERSION\"/" "$EXAMPLE_DIR/Cargo.toml"
done
for EXAMPLE_TOML_FILE in examples/*/Cargo.toml book/games/*/Cargo.toml; do
EXAMPLE_DIR=$(dirname "$EXAMPLE_TOML_FILE")
(cd "$EXAMPLE_DIR" && cargo update)
done
else
PROJECT_NAME_WITH_UNDERSCORES=$(echo -n "$DIRECTORY" | tr - _)
for CARGO_TOML_FILE in agb-*/Cargo.toml agb/Cargo.toml examples/*/Cargo.toml book/games/*/Cargo.toml; do
sed -i -E -e "s/($PROJECT_NAME_WITH_UNDERSCORES = .*version = \")[^\"]+(\".*)/\1$VERSION\2/" "$CARGO_TOML_FILE"
(cd "$(dirname "$CARGO_TOML_FILE")" && cargo generate-lockfile)
done
fi
done
# Sanity check to make sure the build works
just ci
for EXAMPLE_TOML_FILE in examples/*/Cargo.toml book/games/*/Cargo.toml; do
EXAMPLE_DIR=$(dirname "$EXAMPLE_TOML_FILE")
(cd "$EXAMPLE_DIR" && cargo check --release)
done
# Commit the Cargo.toml changes
maybe_git commit -am "Release v$VERSION"
# Tag the version
maybe_git tag -a "$TAGNAME" -m "v$VERSION"
echo "Done! Push with"
echo "git push --atomic origin master $TAGNAME"

212
tools/Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "atty"
version = "0.2.14"
@ -25,12 +34,39 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "bytes"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clap"
version = "4.0.7"
@ -63,12 +99,24 @@ dependencies = [
"memchr",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -84,6 +132,19 @@ dependencies = [
"libc",
]
[[package]]
name = "iana-time-zone"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "indexmap"
version = "1.9.1"
@ -103,30 +164,102 @@ dependencies = [
"either",
]
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]]
name = "proc-macro2"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@ -136,6 +269,17 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "toml_edit"
version = "0.14.4"
@ -151,10 +295,78 @@ dependencies = [
name = "tools"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"glob",
"toml_edit",
]
[[package]]
name = "unicode-ident"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -8,3 +8,5 @@ edition = "2021"
[dependencies]
clap = "4"
toml_edit = "0.14"
glob = "0.3"
chrono = "0.4"

View file

@ -2,19 +2,35 @@
use clap::Command;
mod publish;
mod release;
mod utils;
#[derive(Debug)]
pub enum Error {
PublishError(publish::Error),
ReleaseError(release::Error),
}
fn cli() -> Command {
Command::new("Agb tools")
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(publish::command())
.subcommand(release::command())
}
fn main() {
let matches = cli().get_matches();
let result = match matches.subcommand() {
Some(("publish", arg_matches)) => publish::publish(arg_matches),
Some(("publish", arg_matches)) => {
publish::publish(arg_matches).map_err(Error::PublishError)
}
Some(("release", arg_matches)) => {
release::release(arg_matches).map_err(Error::ReleaseError)
}
_ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"),
};

View file

@ -1,12 +1,14 @@
use clap::{Arg, ArgAction, ArgMatches};
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::process::Command;
use std::thread;
use std::time::Duration;
use std::{env, thread};
use toml_edit::Document;
use crate::utils::*;
#[derive(Debug)]
pub enum Error {
FindRootDirectory,
@ -31,7 +33,7 @@ pub fn command() -> clap::Command {
pub fn publish(matches: &ArgMatches) -> Result<(), Error> {
let dry_run = matches.get_one::<bool>("Dry run").expect("defined by clap");
let root_directory = find_agb_root_directory()?;
let root_directory = find_agb_root_directory().map_err(|_| Error::FindRootDirectory)?;
let mut fully_published_crates: HashSet<String> = HashSet::new();
let mut published_crates: HashSet<String> = HashSet::new();
@ -60,11 +62,12 @@ pub fn publish(matches: &ArgMatches) -> Result<(), Error> {
if *dry_run {
println!("Would execute cargo publish for {publishable_crate}");
} else {
Command::new("cargo")
assert!(Command::new("cargo")
.arg("publish")
.current_dir(&root_directory.join(publishable_crate))
.spawn()
.map_err(|_| Error::PublishCrate)?;
.status()
.map_err(|_| Error::PublishCrate)?
.success());
}
published_crates.insert(publishable_crate.to_string());
@ -86,19 +89,6 @@ pub fn publish(matches: &ArgMatches) -> Result<(), Error> {
Ok(())
}
fn find_agb_root_directory() -> Result<PathBuf, Error> {
let mut current_path = env::current_dir().map_err(|_| Error::FindRootDirectory)?;
while !current_path.clone().join("justfile").exists() {
current_path = current_path
.parent()
.ok_or(Error::FindRootDirectory)?
.to_owned();
}
Ok(current_path)
}
fn check_if_released(crate_to_publish: &str, expected_version: &str) -> Result<bool, Error> {
let url_to_poll = &get_url_to_poll(crate_to_publish);
@ -220,16 +210,9 @@ mod test {
}
}
#[test]
fn should_find_root_directory() -> Result<(), Error> {
assert_ne!(find_agb_root_directory()?.to_string_lossy(), "");
Ok(())
}
#[test]
fn should_read_version() -> Result<(), Error> {
let root_directory = find_agb_root_directory()?;
let root_directory = crate::utils::find_agb_root_directory().unwrap();
let my_version = read_cargo_toml_version(&root_directory.join("tools"))?;
assert_eq!(my_version, "0.1.0");
@ -238,7 +221,7 @@ mod test {
#[test]
fn should_detect_dependencies() -> Result<(), Error> {
let root_directory = find_agb_root_directory()?;
let root_directory = crate::utils::find_agb_root_directory().unwrap();
let deps = get_agb_dependencies(&root_directory.join("agb"))?;
assert_eq!(

305
tools/src/release.rs Normal file
View file

@ -0,0 +1,305 @@
use std::{path::Path, process::Command};
use crate::utils::find_agb_root_directory;
pub fn command() -> clap::Command {
clap::Command::new("release")
.about("Prepares and commits the changes required to release agb")
.arg(
clap::Arg::new("version")
.required(true)
.help("New version to release")
.value_parser(version_parser),
)
.arg(
clap::Arg::new("Dry run")
.long("dry-run")
.help("Don't do anything with git (but does everything else)")
.action(clap::ArgAction::SetTrue),
)
}
pub fn release(matches: &clap::ArgMatches) -> Result<(), Error> {
let dry_run = matches.get_one::<bool>("Dry run").expect("defined by clap");
let version = matches
.get_one::<Version>("version")
.expect("defined by clap");
let root_directory = find_agb_root_directory().map_err(|_| Error::FindRootDirectory)?;
// if not dry run, check that there are no out-standing changes in git
if !dry_run && !execute_git_command(&root_directory, &["status", "--porcelain"])?.is_empty() {
println!("Uncommitted changes, please commit first");
return Ok(());
}
// Check that we are in the master branch
if !dry_run
&& execute_git_command(&root_directory, &["symbolic-ref", "--short", "HEAD"])? != "master"
{
println!("You must be on the master branch before releasing");
return Ok(());
}
let project_toml_files = glob_many(&root_directory, &["agb-*/Cargo.toml"])?;
let agb_cargo_toml = root_directory.join("agb/Cargo.toml");
update_to_version(&root_directory, &agb_cargo_toml, version)?;
for toml_file in &project_toml_files {
update_to_version(&root_directory, toml_file, version)?;
}
assert!(Command::new("just")
.arg("ci")
.current_dir(&root_directory)
.status()
.map_err(|_| Error::JustCiFailed)?
.success());
let changelog_text = update_changelog(&root_directory, version)?;
println!("Content of changelog:\n{changelog_text}");
if !dry_run {
execute_git_command(
&root_directory,
&["commit", "-am", &format!("Release v{version}")],
)?;
execute_git_command(
&root_directory,
&[
"tag",
"-a",
&version.to_string(),
"-m",
&format!("#v{version}\n{changelog_text}"),
],
)?;
}
println!("Done! Push with");
println!("git push --atomic origin master v{version}");
Ok(())
}
fn update_to_version(
root_directory: &Path,
toml_file: &Path,
new_version: &Version,
) -> Result<(), Error> {
let directory_name = toml_file.parent().unwrap().file_name().unwrap();
let project_name = directory_name.to_string_lossy().replace('-', "_");
let toml_file_content = std::fs::read_to_string(toml_file).map_err(|_| Error::ReadTomlFile)?;
let mut cargo_toml = toml_file_content
.parse::<toml_edit::Document>()
.map_err(|_| Error::InvalidToml(toml_file.to_string_lossy().into_owned()))?;
let new_version = format!("{new_version}");
cargo_toml["package"]["version"] = toml_edit::value(&new_version);
std::fs::write(toml_file, cargo_toml.to_string()).map_err(|_| Error::WriteTomlFile)?;
for cargo_toml_file in glob_many(
root_directory,
&[
"agb-*/Cargo.toml",
"agb/Cargo.toml",
"examples/*/Cargo.toml",
"book/games/*/Cargo.toml",
"template/Cargo.toml",
],
)? {
let toml_file_content =
std::fs::read_to_string(&cargo_toml_file).map_err(|_| Error::ReadTomlFile)?;
let mut cargo_toml = toml_file_content
.parse::<toml_edit::Document>()
.map_err(|_| Error::InvalidToml(cargo_toml_file.to_string_lossy().into_owned()))?;
if let Some(this_dep) = cargo_toml["dependencies"].get_mut(&project_name) {
match this_dep {
toml_edit::Item::Value(s @ toml_edit::Value::String(_)) => {
*s = new_version.clone().into()
}
toml_edit::Item::Value(toml_edit::Value::InlineTable(t)) => {
t["version"] = new_version.clone().into()
}
toml_edit::Item::None => continue,
_ => {
return Err(Error::InvalidToml(format!(
"{:?} while seaching dependencies in {}",
this_dep,
cargo_toml_file.to_string_lossy()
)))
}
}
}
std::fs::write(cargo_toml_file, cargo_toml.to_string())
.map_err(|_| Error::WriteTomlFile)?;
}
Ok(())
}
fn update_changelog(root_directory: &Path, new_version: &Version) -> Result<String, Error> {
use chrono::Datelike;
let changelog_file = root_directory.join("CHANGELOG.md");
let changelog_content =
std::fs::read_to_string(&changelog_file).map_err(|_| Error::FailedToReadChangelog)?;
let today = chrono::Local::today();
let formatted_date = format!(
"{:04}/{:02}/{:02}",
today.year(),
today.month(),
today.day()
);
const UNRELEASED_HEADER: &str = "## [Unreleased]";
let unreleased_bit_start = changelog_content
.find(UNRELEASED_HEADER)
.ok_or(Error::FailedToParseChangelog)?
+ UNRELEASED_HEADER.len();
let unreleased_bit_end = changelog_content[unreleased_bit_start..]
.find("\n## [") // the start of the next entry
.ok_or(Error::FailedToParseChangelog)?
+ unreleased_bit_start;
let change_content = changelog_content[unreleased_bit_start..unreleased_bit_end].to_owned();
let changelog_content = changelog_content.replacen(
UNRELEASED_HEADER,
&format!("{UNRELEASED_HEADER}\n\n## [{new_version}] - {formatted_date}"),
1,
);
std::fs::write(&changelog_file, &changelog_content)
.map_err(|_| Error::FailedToWriteChangelog)?;
Ok(change_content)
}
fn execute_git_command(root_directory: &Path, args: &[&str]) -> Result<String, Error> {
let git_cmd = Command::new("git")
.args(args)
.current_dir(root_directory)
.output()
.map_err(|_| Error::Git("Failed to run command"))?;
assert!(git_cmd.status.success());
String::from_utf8(git_cmd.stdout).map_err(|_| Error::Git("Output not utf-8"))
}
fn glob_many(root_directory: &Path, globs: &[&str]) -> Result<Vec<std::path::PathBuf>, Error> {
let mut result = vec![];
for g in globs.iter() {
for path in glob::glob(&root_directory.join(g).to_string_lossy()).expect("Invalid glob") {
result.push(path.map_err(|_| Error::Glob)?);
}
}
Ok(result)
}
#[derive(Debug)]
pub enum Error {
FindRootDirectory,
Git(&'static str),
Glob,
ReadTomlFile,
InvalidToml(String),
WriteTomlFile,
JustCiFailed,
CargoUpdateFailed,
FailedToReadChangelog,
FailedToWriteChangelog,
FailedToParseChangelog,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Version {
major: u32,
minor: u32,
patch: u32,
}
impl Version {
#[cfg(test)]
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Debug, PartialEq, Eq)]
struct ParseVersionError;
impl std::str::FromStr for Version {
type Err = ParseVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let version_array: Vec<_> = s
.split('.')
.map(|v| v.parse())
.collect::<Result<Vec<_>, _>>()
.map_err(|_| ParseVersionError)?;
if version_array.len() > 3 || version_array.is_empty() {
return Err(ParseVersionError);
}
Ok(Version {
major: version_array[0],
minor: *version_array.get(1).unwrap_or(&0),
patch: *version_array.get(2).unwrap_or(&0),
})
}
}
fn version_parser(maybe_version: &str) -> Result<Version, &'static str> {
maybe_version
.parse()
.map_err(|_| "Failed to parse version, must be of the format x.y.z")
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn verify_cli() {
command().debug_assert();
}
#[test]
fn can_parse_versions() {
assert_eq!(Version::from_str("0.1.2").unwrap(), Version::new(0, 1, 2));
assert_eq!(Version::from_str("0.1").unwrap(), Version::new(0, 1, 0));
assert_eq!(
Version::from_str("33.23.4000").unwrap(),
Version::new(33, 23, 4000)
);
assert_eq!(Version::from_str("abc").unwrap_err(), ParseVersionError);
assert_eq!(Version::from_str("").unwrap_err(), ParseVersionError);
assert_eq!(Version::from_str("0.2.4.5").unwrap_err(), ParseVersionError);
assert_eq!(Version::from_str("0.2.4a").unwrap_err(), ParseVersionError);
}
}

27
tools/src/utils.rs Normal file
View file

@ -0,0 +1,27 @@
use std::{env, path::PathBuf};
#[derive(Debug)]
pub struct FindRootDirectoryError;
pub fn find_agb_root_directory() -> Result<PathBuf, FindRootDirectoryError> {
let mut current_path = env::current_dir().map_err(|_| FindRootDirectoryError)?;
while !current_path.clone().join("justfile").exists() {
current_path = current_path
.parent()
.ok_or(FindRootDirectoryError)?
.to_owned();
}
Ok(current_path)
}
#[cfg(test)]
mod tests {
use super::find_agb_root_directory;
#[test]
fn find_agb_root_directory_works() {
find_agb_root_directory().unwrap();
}
}