use crate::remedy::Remedy;
use std::io::{BufRead, Write};
use std::path::Path;
#[derive(Debug, PartialEq, Eq)]
pub enum Outcome {
Fixed,
Stopped,
Failed,
}
pub fn walk(
project: &Path,
mut remedy: Remedy,
input: &mut impl BufRead,
output: &mut impl Write,
) -> std::io::Result<Outcome> {
writeln!(output, "{}", remedy.problem)?;
while !remedy.done() && !remedy.failed {
let Some(step) = remedy.steps.get(remedy.at) else {
break;
};
writeln!(output, "\n{}. {}", remedy.at + 1, step.said)?;
if let Some(warns) = &step.warns {
writeln!(output, " {warns}")?;
}
if step.commands.is_empty() {
writeln!(output, "\n This one is yours to do.")?;
return Ok(Outcome::Stopped);
}
for command in &step.commands {
writeln!(output, " $ {}", command.join(" "))?;
}
write!(output, "\nRun it? [y] yes, anything else stops: ")?;
output.flush()?;
let mut answer = String::new();
if input.read_line(&mut answer)? == 0 || !answer.trim().eq_ignore_ascii_case("y") {
return Ok(Outcome::Stopped);
}
remedy.take_step(project);
if remedy.failed {
if let Some(said) = &remedy.said {
writeln!(output, "\n{said}")?;
}
return Ok(Outcome::Failed);
}
}
match Remedy::diagnose(project) {
Some(still) => {
writeln!(output, "\nstill: {}", still.problem)?;
Ok(Outcome::Stopped)
}
None => {
writeln!(output, "\nnothing is in the way now")?;
Ok(Outcome::Fixed)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn scratch(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("ostraka-fix-{}-{name}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("scratch");
dir
}
#[test]
fn a_walk_nobody_says_yes_to_changes_nothing() {
let dir = scratch("declined");
std::fs::write(dir.join("seed.txt"), "seed\n").expect("write");
let remedy = Remedy::diagnose(&dir).expect("a problem");
let mut input = std::io::Cursor::new(b"n\n".to_vec());
let mut out: Vec<u8> = Vec::new();
let ended = walk(&dir, remedy, &mut input, &mut out).expect("walks");
assert_eq!(ended, Outcome::Stopped);
assert!(!dir.join(".git").exists(), "a step ran without a yes");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_stream_that_ends_is_not_a_yes() {
let dir = scratch("eof");
std::fs::write(dir.join("seed.txt"), "seed\n").expect("write");
let remedy = Remedy::diagnose(&dir).expect("a problem");
let mut input = std::io::Cursor::new(Vec::new());
let mut out: Vec<u8> = Vec::new();
assert_eq!(
walk(&dir, remedy, &mut input, &mut out).expect("walks"),
Outcome::Stopped
);
assert!(!dir.join(".git").exists());
let _ = std::fs::remove_dir_all(&dir);
}
fn git(dir: &Path, args: &[&str]) {
let out = std::process::Command::new("git")
.args(args)
.current_dir(dir)
.output()
.expect("git runs");
assert!(out.status.success(), "git {args:?}");
}
#[test]
fn yes_to_every_step_leaves_a_directory_with_nothing_wrong_with_it() {
let dir = scratch("fixed");
git(&dir, &["init", "-q", "-b", "main"]);
git(&dir, &["config", "user.email", "t@example.invalid"]);
git(&dir, &["config", "user.name", "t"]);
std::fs::write(dir.join("seed.txt"), "seed\n").expect("write");
let remedy = Remedy::diagnose(&dir).expect("a problem");
let mut input = std::io::Cursor::new(b"y\ny\ny\n".to_vec());
let mut out: Vec<u8> = Vec::new();
let ended = walk(&dir, remedy, &mut input, &mut out).expect("walks");
assert_eq!(ended, Outcome::Fixed, "{}", String::from_utf8_lossy(&out));
assert!(Remedy::diagnose(&dir).is_none());
let _ = std::fs::remove_dir_all(&dir);
}
}