use crate::config::{CompiledConfig, PostProcessStep};
use anyhow::bail;
use rust_llm_tidy_lint::check;
use std::collections::HashSet;
use std::path::PathBuf;
use super::Cli;
use crate::paths;
pub(crate) fn run_pipeline(
cli: &Cli,
config: Option<&CompiledConfig>,
cli_include: Option<&HashSet<String>>,
cli_disabled: &HashSet<String>,
) -> anyhow::Result<()> {
let paths = paths::resolve_inputs(cli, &["rs", "md"])?;
if paths.is_empty() {
return Ok(());
}
let multiple_files = paths.len() > 1;
let mut error_count = 0usize;
let mut failed = Vec::new();
let mut processed: Vec<PathBuf> = Vec::new();
let vis_may_run = cli_include.as_ref().is_none_or(|s| s.contains("vis"));
let ctx = if vis_may_run {
super::resolve_vis_context(&paths)
} else {
None
};
for path in &paths {
let mut policy = config.map(|c| c.policy_for(path)).unwrap_or_default();
if policy.skip {
continue;
}
if let Some(include) = cli_include {
policy.enabled = Some(include.clone());
policy.disabled.clear();
}
if !cli_disabled.is_empty() {
policy.disabled.extend(cli_disabled.iter().cloned());
if let Some(set) = &mut policy.enabled {
set.retain(|r| !cli_disabled.contains(r));
}
}
let enabled = &policy.enabled;
let disabled = &policy.disabled;
let should_post_process = ["tables", "fences", "links", "reorder", "vis"]
.iter()
.any(|op| op_enabled(op, enabled, disabled));
if (op_enabled("tables", enabled, disabled)
|| op_enabled("fences", enabled, disabled)
|| op_enabled("links", enabled, disabled))
&& let Err(e) = super::fix_file(path, cli.dry_run, multiple_files, enabled, disabled)
{
eprintln!("error processing {}: {e:?}", path.display());
failed.push(path);
continue;
}
let is_rust = path
.extension()
.and_then(|e| e.to_str())
.is_some_and(|e| e == "rs");
if !is_rust {
if should_post_process {
processed.push(path.clone());
}
continue;
}
if op_enabled("reorder", enabled, disabled)
&& let Err(e) = super::reorder_file(path, cli.dry_run, multiple_files, disabled)
{
eprintln!("error processing {}: {e:?}", path.display());
failed.push(path);
continue;
}
if op_enabled("vis", enabled, disabled)
&& let Err(e) =
super::vis_file(path, cli.dry_run, multiple_files, ctx.as_ref(), disabled)
{
eprintln!("error processing {}: {e:?}", path.display());
failed.push(path);
continue;
}
let lints_on = !disabled.contains("lints")
&& match enabled {
Some(set) => {
set.contains("lints") || check::LINT_CODES.iter().any(|c| set.contains(*c))
}
None => true,
};
if lints_on {
let lint_disabled: HashSet<String> = match enabled {
Some(set) if !set.contains("lints") => check::LINT_CODES
.iter()
.filter(|c| !set.contains(**c))
.map(|c| c.to_string())
.chain(disabled.iter().cloned())
.collect(),
_ => disabled.clone(),
};
match super::check_file(path, &lint_disabled) {
Ok(errs) => error_count += errs,
Err(e) => {
eprintln!("error processing {}: {e:?}", path.display());
failed.push(path);
continue;
}
}
}
if should_post_process {
processed.push(path.clone());
}
}
if let Some(c) = config
&& !cli.dry_run
{
let pp_failed = run_post_process(c.post_process_steps(), &processed);
if !pp_failed.is_empty() {
bail!("post_process failed on {} file(s)", pp_failed.len());
}
}
if !failed.is_empty() {
bail!("failed to process {} file(s)", failed.len());
}
if error_count > 0 {
bail!("found {} error(s)", error_count);
}
Ok(())
}
pub(crate) fn op_enabled(
name: &str,
enabled: &Option<HashSet<String>>,
disabled: &HashSet<String>,
) -> bool {
match enabled {
Some(set) => set.contains(name),
None => !disabled.contains(name),
}
}
pub(crate) fn run_post_process(steps: &[PostProcessStep], files: &[PathBuf]) -> Vec<PathBuf> {
let mut failed = Vec::new();
for step in steps {
for file in files {
if !step.extensions.is_empty() {
let ext_ok = file
.extension()
.and_then(|e| e.to_str())
.is_some_and(|e| step.extensions.iter().any(|x| x == e));
if !ext_ok {
continue;
}
}
let output = std::process::Command::new(&step.command)
.args(&step.args)
.arg(file)
.output();
match output {
Ok(out) if out.status.success() => {}
Ok(out) => {
eprintln!(
"post_process `{}` failed on {}: {}",
step.command,
file.display(),
String::from_utf8_lossy(&out.stderr).trim()
);
failed.push(file.clone());
}
Err(e) => {
eprintln!(
"post_process `{}` failed to spawn on {}: {e}",
step.command,
file.display()
);
failed.push(file.clone());
}
}
}
}
failed
}