use crate::seed;
const COMMAND_PLACEHOLDER: &str = "{command}";
const MESSAGE_PLACEHOLDER: &str = "{message}";
const PREFLIGHT: &str =
"command -v {command} >/dev/null 2>&1 || { printf '%s\\n' {message} >&2; exit 1; }";
const STRICT_MODE: &str = "set -eu";
const SCRATCH_VARIABLE: &str = "report_dir";
const SCRATCH_SETUP: &str = "report_dir=$(mktemp -d \"${TMPDIR:-/tmp}/formal-ai-report.XXXXXX\")\n\
trap 'rm -rf \"$report_dir\"' EXIT";
pub(super) struct ReportScript {
programs: Vec<String>,
steps: Vec<String>,
scratch: bool,
}
impl ReportScript {
pub(super) const fn new() -> Self {
Self {
programs: Vec::new(),
steps: Vec::new(),
scratch: false,
}
}
pub(super) fn step(&mut self, program: &str, command: String) {
if !self.programs.iter().any(|known| known == program) {
self.programs.push(program.to_owned());
}
self.steps.push(command);
}
pub(super) fn scratch(&mut self, name: &str) -> String {
self.scratch = true;
format!("\"${SCRATCH_VARIABLE}/{name}\"")
}
pub(super) fn render(&self) -> String {
let mut lines = vec![String::from(STRICT_MODE)];
lines.extend(self.programs.iter().map(|program| preflight(program)));
if self.scratch {
lines.push(String::from(SCRATCH_SETUP));
}
lines.extend(self.steps.iter().cloned());
lines.join("\n")
}
}
fn preflight(program: &str) -> String {
let message = seed::agent_info()
.remove("issue_report_command_missing")
.unwrap_or_default()
.replace(COMMAND_PLACEHOLDER, program);
PREFLIGHT
.replace(COMMAND_PLACEHOLDER, program)
.replace(MESSAGE_PLACEHOLDER, &shell_quote(&message))
}
pub(super) fn shell_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\\''"))
}