use crate::package_repair_command::{ToolEnd, ToolRun};
use std::path::Path;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Tool {
Pnpm,
Cargo,
Toolchain,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Outcome {
Repaired {
detail: String,
millis: u128,
},
Failed {
reason: String,
run_instead: String,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Finding {
pub tool: Tool,
pub outcome: Outcome,
}
pub fn finding_from(tool: Tool, run: &ToolRun, detail: &str, run_instead: &str) -> Finding {
let outcome: Outcome = match &run.end {
ToolEnd::Succeeded => Outcome::Repaired { detail: detail.to_owned(), millis: run.millis },
ToolEnd::Failed(reason) => Outcome::Failed { reason: reason.clone(), run_instead: run_instead.to_owned() },
ToolEnd::TimedOut => Outcome::Failed { reason: String::from("it showed no sign of work for the quiet window and was stopped"), run_instead: run_instead.to_owned() },
ToolEnd::CeilingReached(ceiling) => Outcome::Failed { reason: ceiling_reason(tool, *ceiling), run_instead: run_instead.to_owned() },
};
return Finding { tool, outcome };
}
pub fn ceiling_reason(tool: Tool, ceiling: std::time::Duration) -> String {
let doing: &str = match tool {
Tool::Pnpm => "pnpm, relinking node_modules",
Tool::Cargo => "cargo, cloning and cleaning build state",
Tool::Toolchain => "mpm-shadow, rebuilding the toolchain links",
};
return format!(
"{doing}, ran for {} seconds, the longest the wrapper lets one repair tool run, and was stopped with its whole process group; if the repository is very large, run it by hand",
ceiling.as_secs()
);
}
pub fn missing_tool(tool: Tool, program: &str, run_instead: &str) -> Finding {
return Finding { tool, outcome: Outcome::Failed { reason: format!("{program} was not found on the search path"), run_instead: run_instead.to_owned() } };
}
fn seconds(millis: u128) -> String {
let tenths: u128 = millis / 100;
return format!("{}.{} s", tenths / 10, tenths % 10);
}
fn single_line(text: &str) -> String {
return text.split_whitespace().collect::<Vec<&str>>().join(" ");
}
pub fn render(destination: &Path, findings: &[Finding], quiet: bool) -> String {
let shown: String = destination.display().to_string();
let mut text: String = String::new();
let worked: Vec<String> = findings
.iter()
.filter_map(|finding: &Finding| {
return match &finding.outcome {
Outcome::Repaired { detail, millis } => Some(format!("{detail} in {}", seconds(*millis))),
Outcome::Failed { .. } => None,
};
})
.collect();
let partial: bool = findings.iter().any(|finding: &Finding| { return matches!(finding.outcome, Outcome::Failed { .. }); });
if !worked.is_empty() && !quiet {
let scope: &str = if partial { "part of the package-manager state" } else { "the package-manager state" };
text.push_str(format!("cli-git: made {scope} of {shown} work at its new location: {}.\n", worked.join("; ")).as_str());
}
for finding in findings {
if let Outcome::Failed { reason, run_instead } = &finding.outcome {
text.push_str(format!("cli-git: the worktree {shown} still needs one step ({}); run {}.\n", single_line(reason), single_line(run_instead)).as_str());
}
}
return text;
}
#[cfg(test)]
#[path = "package_repair_report_tests.rs"]
mod tests;