use std::io::Write;
use clap::CommandFactory;
use semver::Version;
use crate::Result;
use crate::alias::{self, Outcome};
use crate::cli::{Alias, Cli, Completions, Set, Tag, Verb};
use crate::version;
use crate::workspace::Workspace;
const REPORTED: &str = "";
pub fn run(verb: Option<Verb>) -> Result<()> {
match verb {
None => list(),
Some(Verb::Patch(args)) => bump_workspace(version::Level::Patch, args.dry_run),
Some(Verb::Minor(args)) => bump_workspace(version::Level::Minor, args.dry_run),
Some(Verb::Major(args)) => bump_workspace(version::Level::Major, args.dry_run),
Some(Verb::Tag(args)) => tag_workspace(args),
Some(Verb::Set(args)) => set_workspace(args),
Some(Verb::Version) => tool_version(),
Some(Verb::Alias(args)) => install_alias(args),
Some(Verb::Completions(args)) => completions(args),
}
}
fn list() -> Result<()> {
let workspace = Workspace::discover()?;
for member in workspace.members() {
out(&format!(" {:<12} {}", member.name, member.version));
}
Ok(())
}
fn tool_version() -> Result<()> {
out(&format!("semver-sync {}", env!("CARGO_PKG_VERSION")));
Ok(())
}
fn bump_workspace(level: version::Level, dry_run: bool) -> Result<()> {
let workspace = Workspace::discover()?;
let current = workspace.shared_version()?;
let next = version::bump(¤t, level)?;
write_workspace(workspace, &next, dry_run)
}
fn tag_workspace(args: Tag) -> Result<()> {
let workspace = Workspace::discover()?;
let current = workspace.shared_version()?;
let next = version::tag(¤t, &args.tag)?;
write_workspace(workspace, &next, args.dry_run)
}
fn set_workspace(args: Set) -> Result<()> {
let workspace = Workspace::discover()?;
let next = version::parse(&args.version)?;
write_workspace(workspace, &next, args.dry_run)
}
fn write_workspace(mut workspace: Workspace, next: &Version, dry_run: bool) -> Result<()> {
version::check_image_tag(&next.to_string())?;
let before = workspace.snapshot()?;
let crates = workspace.member_names();
let mut failed = false;
for index in 0..workspace.target_count() {
let target = workspace.target_mut(index);
match target.version() {
Ok(from) => {
target.rewrite(next, &crates);
if dry_run {
out(&line(&target.name(), &from, next, true));
} else if let Err(err) = target.save() {
note(&format!("semver-sync: {err}"));
failed = true;
} else {
out(&line(&target.name(), &from, next, false));
}
}
Err(err) => {
note(&format!("semver-sync: {err}"));
failed = true;
}
}
}
if dry_run {
workspace.unchanged_since(&before)?;
}
if failed {
return Err(String::from(REPORTED));
}
if !dry_run {
workspace.refresh_lock()?;
next_steps(&workspace, next);
}
Ok(())
}
fn line(name: &str, from: &Version, to: &Version, dry_run: bool) -> String {
let marker = if dry_run { " (dry-run)" } else { "" };
format!(" {name:<12} {from} -> {to}{marker}")
}
fn next_steps(workspace: &Workspace, next: &Version) {
if !workspace.root().join("bin/build.sh").is_file() {
return;
}
out("");
out("next steps:");
if workspace.root().join("bin/unit_test.sh").is_file() {
out(" bin/unit_test.sh");
}
out(&format!(" bin/build.sh # pushes images tagged {next}"));
}
fn install_alias(args: Alias) -> Result<()> {
match alias::install(args)? {
Outcome::Written { path, line } => out(&format!(
"wrote `{line}` to {} — reload with: source {}",
path.display(),
path.display()
)),
Outcome::Already { path } => out(&format!("the alias is already in {}", path.display())),
}
Ok(())
}
fn completions(args: Completions) -> Result<()> {
let mut command = Cli::command();
clap_complete::generate(
args.shell,
&mut command,
"semver-sync",
&mut std::io::stdout(),
);
Ok(())
}
fn out(text: &str) {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
if writeln!(handle, "{text}").is_err() {
std::process::exit(0);
}
}
fn note(text: &str) {
let stderr = std::io::stderr();
let mut handle = stderr.lock();
if writeln!(handle, "{text}").is_err() {
std::process::exit(1);
}
}