use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::io::Write;
use std::path::Path;
use color_eyre::eyre::{self, WrapErr};
use termcolor::Color;
use tytanic_core::project::Paths;
use tytanic_core::test::Id;
use crate::cli::Context;
use crate::{cwrite, ui};
#[derive(clap::Args, Debug, Clone)]
#[group(id = "util-migrate-args")]
pub struct Args {
#[arg(long)]
pub confirm: bool,
#[arg(long, default_value = "self")]
pub name: String,
}
pub fn run(ctx: &mut Context, args: &Args) -> eyre::Result<()> {
let project = ctx.project()?;
let paths = project.paths();
let mut w = ctx.ui.stderr();
let mappings = collect_old_structure(paths, &args.name)?;
if mappings.is_empty() {
writeln!(w, "No tests need to be moved")?;
return Ok(());
}
if args.confirm {
writeln!(w, "Moving tests:")?;
} else {
writeln!(w, "These tests would be moved:")?;
}
for (old, (new, collision)) in &mappings {
if *collision {
cwrite!(bold_colored(w, Color::Red), "*")?;
write!(w, " ")?;
} else {
write!(w, " ")?;
}
ui::write_test_id(&mut w, old)?;
write!(w, " -> ")?;
ui::write_test_id(&mut w, new)?;
writeln!(w)?;
}
writeln!(w)?;
let mut has_colission = false;
for (old, (new, collision)) in &mappings {
if !*collision {
let old = paths.test_dir(old);
let new = paths.test_dir(new);
std::fs::rename(old, new)?;
} else {
has_colission = true;
}
}
if has_colission {
let mut w = ctx.ui.hint()?;
cwrite!(bold_colored(w, Color::Red), "*")?;
writeln!(
w,
" denotes paths which were excluded because of another test with the same id."
)?;
write!(w, "Try another name using ")?;
cwrite!(colored(w, Color::Cyan), "--name")?;
writeln!(w)?;
}
if !args.confirm {
writeln!(ctx.ui.warn()?, "Make sure to back up your code!")?;
{
let mut w = ctx.ui.hint()?;
write!(w, "Use ")?;
cwrite!(colored(w, Color::Cyan), "--confirm")?;
writeln!(w, " to move the tests automatically")?;
}
{
let mut w = ctx.ui.hint()?;
write!(w, "Use ")?;
cwrite!(colored(w, Color::Cyan), "--name")?;
writeln!(w, " to configure the sub directory name")?;
}
if project.vcs().is_some() {
let mut w = ctx.ui.hint()?;
write!(w, "VCS detected, consider also running ")?;
cwrite!(colored(w, Color::Cyan), "tt util vcs ignore")?;
writeln!(w, " after you've migrated")?;
}
}
Ok(())
}
pub fn collect_old_structure(
paths: &Paths,
migration_name: &str,
) -> eyre::Result<BTreeMap<Id, (Id, bool)>> {
let mut entries = BTreeSet::new();
for entry in paths.test_root().read_dir()? {
let entry = entry?;
if entry.metadata()?.is_dir() {
collect_old_structure_inner(paths, &entry.path(), &mut entries)?;
}
}
let mut mappings = BTreeMap::new();
'outer: for id in &entries {
'inner: for internal in id.ancestors().skip(1) {
if !entries.contains(internal) {
continue 'inner;
}
let old = Id::new(internal)?;
let new = Id::new(format!("{internal}/{migration_name}"))?;
let colission = entries.contains(&new);
if mappings.insert(old, (new, colission)).is_some() {
continue 'outer;
}
}
}
Ok(mappings)
}
fn collect_old_structure_inner(
paths: &Paths,
path: &Path,
entries: &mut BTreeSet<Id>,
) -> eyre::Result<()> {
if path.join("test.typ").try_exists()? {
entries.insert(Id::new_from_path(path.strip_prefix(paths.test_root())?)?);
}
for entry in fs::read_dir(path).wrap_err_with(|| format!("{path:?}"))? {
let entry = entry?;
let path = entry.path();
let name = entry.file_name();
if name == "ref" || name == "out" || name == "diff" {
continue;
}
if entry.metadata()?.is_dir() {
collect_old_structure_inner(paths, &path, entries)?;
}
}
Ok(())
}