use crate::core::backend::GeneratedFile;
use crate::e2e::config::E2eConfig;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use tracing::warn;
pub fn run_formatters(files: &[GeneratedFile], e2e_config: &E2eConfig) {
let output_prefix = Path::new(e2e_config.effective_output());
let languages: HashSet<String> = files
.iter()
.filter_map(|f| {
let remainder = f.path.strip_prefix(output_prefix).ok()?;
let first = remainder.components().next()?;
Some(first.as_os_str().to_string_lossy().into_owned())
})
.collect();
for lang in &languages {
let dir = format!("{}/{}", e2e_config.effective_output(), lang);
let dir_path = PathBuf::from(&dir);
if let Some(custom) = e2e_config.format.get(lang.as_str()) {
let cmd = custom.replace("{dir}", &dir);
eprintln!(" Formatting {lang}: {cmd}");
run_shell(&cmd, lang);
continue;
}
eprintln!(" Formatting {lang} with poly: {dir}");
crate::cli::pipeline::poly_format(std::slice::from_ref(&dir_path), &dir_path);
if lang == "go" {
run_go_mod_tidy(&dir);
}
}
}
fn run_shell(cmd: &str, lang: &str) {
match std::process::Command::new("sh").args(["-c", cmd]).status() {
Ok(s) if s.success() => {}
Ok(s) => warn!("Formatter for {lang} exited with {s}: {cmd}"),
Err(e) => warn!("Failed to run formatter for {lang}: {e}"),
}
}
fn run_go_mod_tidy(dir: &str) {
let cmd = format!("(cd {dir} && go mod tidy)");
run_shell(&cmd, "go");
}
#[cfg(test)]
mod tests {
use super::*;
fn e2e_config_for(out: &Path) -> E2eConfig {
E2eConfig {
output: out.to_string_lossy().into_owned(),
..E2eConfig::default()
}
}
#[test]
fn user_override_command_is_expanded_and_run() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let out = base.join("e2e-out");
std::fs::create_dir_all(out.join("python")).unwrap();
let sentinel = out.join("python/was_run.txt");
let sentinel_str = sentinel.to_string_lossy().replace('\\', "/");
let mut e2e_config = e2e_config_for(&out);
e2e_config
.format
.insert("python".to_owned(), format!("touch {sentinel_str}"));
let files = vec![GeneratedFile {
path: out.join("python/main.py"),
content: "x = 1\n".to_owned(),
generated_header: false,
}];
assert!(!sentinel.exists());
run_formatters(&files, &e2e_config);
assert!(
sentinel.exists(),
"user override command must run with {{dir}} expanded"
);
}
#[test]
fn default_path_formats_python_with_poly() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let out = base.join("e2e-out");
std::fs::create_dir_all(out.join("python")).unwrap();
let py = out.join("python/main.py");
std::fs::write(&py, "x=1").unwrap();
let e2e_config = e2e_config_for(&out);
let files = vec![GeneratedFile {
path: out.join("python/main.py"),
content: "x=1".to_owned(),
generated_header: false,
}];
run_formatters(&files, &e2e_config);
let formatted = std::fs::read_to_string(&py).unwrap();
if which::which("poly").is_ok() {
assert_eq!(
formatted, "x = 1\n",
"with poly installed, `poly fmt --fix` must reformat the e2e Python file"
);
} else {
assert_eq!(formatted, "x=1", "without poly the file must be left untouched");
}
}
#[test]
fn unknown_language_dir_is_best_effort() {
let dir = tempfile::tempdir().expect("tempdir");
let base = dir.path();
let out = base.join("e2e-out");
std::fs::create_dir_all(out.join("cobol")).unwrap();
let e2e_config = e2e_config_for(&out);
let files = vec![GeneratedFile {
path: out.join("cobol/main.cob"),
content: " IDENTIFICATION DIVISION.\n".to_owned(),
generated_header: false,
}];
run_formatters(&files, &e2e_config);
}
}