Skip to content

Commit

Permalink
add task to mark crates for publishing
Browse files Browse the repository at this point in the history
  • Loading branch information
busticated committed Oct 14, 2023
1 parent 27302f3 commit 1a107b4
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 3 deletions.
153 changes: 153 additions & 0 deletions xtask/src/git.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use crate::options::Options;
use duct::{cmd, Expression};
use std::ffi::OsString;
use std::path::Path;

#[derive(Clone, Debug, PartialEq)]
pub struct Git<'a> {
opts: &'a Options,
}

impl<'a> Git<'a> {
pub fn new(opts: &'a Options) -> Git<'a> {
Git { opts }
}

pub fn cmd(&self, args: Vec<OsString>) -> Expression {
let mut args = args.clone();

if self.opts.has("dry-run"){
args.insert(0, "skipping:".into());
args.insert(1, "git".into());
// TODO (mirande): windows? see: https://stackoverflow.com/a/61857874/579167
return cmd("echo", args);
}

cmd("git", args)
}

fn build_args<U, UU>(&self, args1: U, args2: UU) -> Vec<OsString>
where
U: IntoIterator,
U::Item: Into<OsString>,
UU: IntoIterator,
UU::Item: Into<OsString>,
{
let mut args = args1.into_iter().map(Into::<OsString>::into).collect::<Vec<_>>();
args.extend(args2.into_iter().map(Into::<OsString>::into).collect::<Vec<_>>());
args.retain(|a| !a.is_empty());
args
}

pub fn add<P, U>(&self, path: P, arguments: U) -> Expression
where
P: AsRef<Path>,
U: IntoIterator,
U::Item: Into<OsString>,
{
let args = self.add_raw(path, arguments);
self.cmd(args)
}

fn add_raw<P, U>(&self, path: P, arguments: U) -> Vec<OsString>
where
P: AsRef<Path>,
U: IntoIterator,
U::Item: Into<OsString>,
{
self.build_args(
vec![OsString::from("add"), path.as_ref().to_owned().into()],
arguments
)
}

pub fn commit<M, U>(&self, message: M, arguments: U) -> Expression
where
M: AsRef<str>,
U: IntoIterator,
U::Item: Into<OsString>,
{
let args = self.commit_raw(message, arguments);
self.cmd(args)
}

fn commit_raw<M, U>(&self, message: M, arguments: U) -> Vec<OsString>
where
M: AsRef<str>,
U: IntoIterator,
U::Item: Into<OsString>,
{
self.build_args(
vec!["commit", "--message", message.as_ref()],
arguments
)
}

pub fn tag<T, U>(&self, tag: T, arguments: U) -> Expression
where
T: AsRef<str>,
U: IntoIterator,
U::Item: Into<OsString>,
{
let args = self.tag_raw(tag, arguments);
self.cmd(args)
}

fn tag_raw<T, U>(&self, tag: T, arguments: U) -> Vec<OsString>
where
T: AsRef<str>,
U: IntoIterator,
U::Item: Into<OsString>,
{
self.build_args(
vec!["tag", tag.as_ref(), "--message", tag.as_ref()],
arguments
)
}
}


#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
use crate::task_flags;

#[test]
fn it_initializes() {
let opts = Options::new(vec![], task_flags! {}).unwrap();
let _ = Git::new(&opts);
}

#[test]
fn it_builds_args() {
let opts = Options::new(vec![], task_flags! {}).unwrap();
let git = Git::new(&opts);
let args = git.build_args(["one"], vec!["two", "three"]);
assert_eq!(args, ["one", "two", "three"]);
}

#[test]
fn it_builds_args_for_the_add_subcommand() {
let opts = Options::new(vec![], task_flags! {}).unwrap();
let git = Git::new(&opts);
let args = git.add_raw(Path::new("path/to/file"), [""]);
assert_eq!(args, ["add", "path/to/file"]);
}

#[test]
fn it_builds_args_for_the_commit_subcommand() {
let opts = Options::new(vec![], task_flags! {}).unwrap();
let git = Git::new(&opts);
let args = git.commit_raw("my message", ["--one", "--two"]);
assert_eq!(args, ["commit", "--message", "my message", "--one", "--two"]);
}

#[test]
fn it_builds_args_for_the_tag_subcommand() {
let opts = Options::new(vec![], task_flags! {}).unwrap();
let git = Git::new(&opts);
let args = git.tag_raw("my tag", ["--one", "--two"]);
assert_eq!(args, ["tag", "my tag", "--message", "my tag", "--one", "--two"]);
}
}
26 changes: 26 additions & 0 deletions xtask/src/krate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ impl Krate {
Ok(krate)
}

pub fn id(&self) -> String {
format!("{}@{}", &self.name, self.version)
}

pub fn set_version(&mut self, version: Version) -> Result<(), DynError> {
self.version = version;
self.toml.set_version(&self.version)?;
Ok(())
}

pub fn clean(&self) -> Result<(), DynError> {
Ok(fs::remove_dir_all(self.tmp_path())?)
}
Expand Down Expand Up @@ -263,6 +273,22 @@ mod tests {
assert_eq!(krate.toml.path, PathBuf::from(""));
}

#[test]
fn it_sets_krate_version() {
let mut krate = Krate::new(
"lib",
"0.1.0",
"my-crate",
"my-crate's description",
PathBuf::from("fake-crate"),
);

krate.set_version(Version::new(1, 0, 0)).unwrap();

assert_eq!(krate.version.to_string(), "1.0.0");
assert_eq!(krate.toml.get_version().unwrap().to_string(), "1.0.0");
}

#[test]
fn it_gets_path_to_krate() {
let krate = Krate::new(
Expand Down
65 changes: 63 additions & 2 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
mod git;
mod krate;
mod options;
mod readme;
mod semver;
mod tasks;
mod toml;
mod workspace;

use crate::krate::KratePaths;
use crate::git::Git;
use crate::krate::{Krate, KratePaths};
use crate::tasks::{Task, Tasks};
use crate::semver::VersionChoice;
use crate::workspace::Workspace;
use duct::cmd;
use inquire::required;
use inquire::list_option::ListOption as InquireListOption;
use inquire::validator::Validation as InquireValidation;
use inquire::{Select as InquireSelect, Text as InquireText};
use inquire::{MultiSelect as InquireMultiSelect, Select as InquireSelect, Text as InquireText};
use regex::RegexBuilder;
use std::env;
use std::error::Error;
Expand Down Expand Up @@ -261,6 +266,62 @@ fn init_tasks() -> Tasks {
Ok(())
},
},
Task {
name: "crate:release".into(),
description: "prepate crates for publishing".into(),
flags: task_flags! {
"dry-run" => "run thru steps but do not save changes"
},
run: |opts, workspace, _tasks| {
println!("::::::::::::::::::::::::::");
println!(":::: Releasing Crates ::::");
println!("::::::::::::::::::::::::::");
println!();

let git = Git::new(&opts);
let mut krates = workspace.krates()?;
let question = InquireMultiSelect::new("Which crates should be published?", krates.keys().cloned().collect());
let to_publish = question
.with_validator(|selections: &[InquireListOption<&String>]| {
if selections.is_empty() {
return Ok(InquireValidation::Invalid("Please select at least one crate!".into()));
}

Ok(InquireValidation::Valid)
})
.prompt()?;

krates.retain(|_, v| to_publish.contains(&v.name));
let mut krates = krates.values().cloned().collect::<Vec<Krate>>();

for krate in krates.iter_mut() {
let version = krate.toml.get_version()?;
let options = VersionChoice::options(&version);
let message = format!("Version for `{}` [current: {}]", krate.name, version);
let question = InquireSelect::new(&message, options);
let choice = question.prompt()?;
krate.set_version(choice.get_version())?;
if opts.has("dry-run") {
println!("Skipping: Version bump for {}", krate.toml.path.display());
} else {
krate.toml.save()?;
}
git.add(&krate.toml.path, [""]).run()?;
}

let tags: Vec<String> = krates.iter().map(|k| k.id()).collect();
let message = format!("Release:\n{}", tags.join("\n"));
git.commit(message, [""]).run()?;

for tag in tags {
git.tag(tag, [""]).run()?;
}

println!(":::: Done!");
println!();
Ok(())
},
},
Task {
name: "dist".into(),
description: "create release artifacts".into(),
Expand Down
111 changes: 111 additions & 0 deletions xtask/src/semver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::fmt::{Display, Formatter};
use semver::{BuildMetadata, Prerelease, Version};

#[derive(Clone, Debug, PartialEq)]
pub enum VersionChoice {
Major(Version),
Minor(Version),
Patch(Version),
}

impl Display for VersionChoice {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let msg = match self {
VersionChoice::Major(v) => format!("Major: {}", v),
VersionChoice::Minor(v) => format!("Minor: {}", v),
VersionChoice::Patch(v) => format!("Patch: {}", v),
};

write!(f, "{}", msg)
}
}

impl VersionChoice {
pub fn options(version: &Version) -> Vec<VersionChoice> {
vec![
VersionChoice::Major(increment_major(version)),
VersionChoice::Minor(increment_minor(version)),
VersionChoice::Patch(increment_patch(version)),
]
}

pub fn get_version(&self) -> Version {
match self {
VersionChoice::Major(v) => v.clone(),
VersionChoice::Minor(v) => v.clone(),
VersionChoice::Patch(v) => v.clone(),
}
}
}

pub fn increment_major(version: &Version) -> Version {
let mut v = version.clone();
v.major += 1;
v.minor = 0;
v.patch = 0;
v.pre = Prerelease::EMPTY;
v.build = BuildMetadata::EMPTY;
v
}

pub fn increment_minor(version: &Version) -> Version {
let mut v = version.clone();
v.minor += 1;
v.patch = 0;
v.pre = Prerelease::EMPTY;
v.build = BuildMetadata::EMPTY;
v
}

pub fn increment_patch(version: &Version) -> Version {
let mut v = version.clone();
v.patch += 1;
v.pre = Prerelease::EMPTY;
v.build = BuildMetadata::EMPTY;
v
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_initializes_version_choice_options() {
let version = Version::new(1, 0, 0);
let options = VersionChoice::options(&version);
assert_eq!(options.len(), 3);
assert_eq!(options[0], VersionChoice::Major(Version::new(2, 0, 0)));
assert_eq!(options[1], VersionChoice::Minor(Version::new(1, 1, 0)));
assert_eq!(options[2], VersionChoice::Patch(Version::new(1, 0, 1)));
}

#[test]
fn it_gets_version() {
let choice = VersionChoice::Major(Version::new(1, 0, 0));
assert_eq!(choice.get_version(), Version::new(1, 0, 0));
}

#[test]
fn it_displays_version_choice_text() {
let choice = VersionChoice::Major(Version::new(1, 0, 0));
assert_eq!(format!("{}", choice), "Major: 1.0.0");
}

#[test]
fn it_increments_major_version() {
let version = Version::new(1, 0, 0);
assert_eq!(increment_major(&version), Version::new(2, 0, 0));
}

#[test]
fn it_increments_minor_version() {
let version = Version::new(1, 0, 0);
assert_eq!(increment_minor(&version), Version::new(1, 1, 0));
}

#[test]
fn it_increments_patch_version() {
let version = Version::new(1, 0, 0);
assert_eq!(increment_patch(&version), Version::new(1, 0, 1));
}
}
Loading

0 comments on commit 1a107b4

Please sign in to comment.