use std::path::PathBuf;
use anyhow::{Context, Result, bail};
use fast_yaml_core::emitter::EmitterConfig;
use fast_yaml_parallel::{BatchResult as ParallelBatchResult, FileProcessor};
use crate::config::CommonConfig;
use crate::discovery::{DiscoveryConfig, FileDiscovery};
use crate::error::ExitCode;
use crate::reporter::{ReportEvent, Reporter};
#[derive(Debug, Clone)]
pub struct BatchConfig {
pub common: CommonConfig,
pub discovery: DiscoveryConfig,
pub dry_run: bool,
pub in_place: bool,
}
impl BatchConfig {
pub fn new(common: CommonConfig) -> Self {
Self {
common,
discovery: DiscoveryConfig::new(),
dry_run: false,
in_place: false,
}
}
#[must_use]
pub fn with_discovery(mut self, discovery: DiscoveryConfig) -> Self {
self.discovery = discovery;
self
}
#[must_use]
pub const fn with_dry_run(mut self, dry_run: bool) -> Self {
self.dry_run = dry_run;
self
}
#[must_use]
pub const fn with_in_place(mut self, in_place: bool) -> Self {
self.in_place = in_place;
self
}
}
pub fn execute_batch(
config: &BatchConfig,
paths: &[PathBuf],
stdin_files: bool,
) -> Result<ExitCode> {
let discovery = FileDiscovery::new(config.discovery.clone())
.context("Failed to initialize file discovery")?;
let files = if stdin_files {
discovery
.discover_from_stdin()
.context("Failed to read file list from stdin")?
} else {
discovery
.discover(paths)
.context("Failed to discover files")?
};
if files.is_empty() {
if !config.common.output.is_quiet() {
eprintln!("No YAML files found");
}
return Ok(ExitCode::Success);
}
let reporter = Reporter::new(config.common.output.clone());
let file_paths: Vec<PathBuf> = files.iter().map(|f| f.path.clone()).collect();
let emitter_config = EmitterConfig::new()
.with_indent(config.common.formatter.indent() as usize)
.with_width(config.common.formatter.width());
let processor = FileProcessor::with_config(config.common.parallel.clone());
let result = if config.dry_run {
let formatted = processor.format_files(&file_paths, &emitter_config);
convert_format_results_to_batch_result(formatted)
} else if config.in_place {
processor.format_in_place(&file_paths, &emitter_config)
} else {
bail!("use -i to format files in-place or --dry-run to preview changes");
};
let would_change = if config.dry_run { result.changed } else { 0 };
let formatted = if config.dry_run { 0 } else { result.changed };
reporter.report(ReportEvent::BatchSummary {
total: result.total,
formatted,
unchanged: result.success - result.changed,
would_change,
failed: result.failed,
duration: result.duration,
})?;
for (path, error) in &result.errors {
reporter.report(ReportEvent::Error {
path: Some(path),
message: &error.to_string(),
})?;
}
if result.failed > 0 {
Ok(ExitCode::ParseError)
} else {
Ok(ExitCode::Success)
}
}
fn convert_format_results_to_batch_result(
results: Vec<(PathBuf, Result<String, fast_yaml_parallel::Error>)>,
) -> ParallelBatchResult {
use fast_yaml_parallel::{FileOutcome, FileResult};
use std::time::{Duration, Instant};
let start = Instant::now();
let mut file_results = Vec::with_capacity(results.len());
for (path, result) in results {
let outcome = match result {
Ok(_formatted) => {
FileOutcome::Changed {
duration: Duration::ZERO,
}
}
Err(error) => FileOutcome::Error {
error,
duration: Duration::ZERO,
},
};
file_results.push(FileResult::new(path, outcome));
}
let mut batch = ParallelBatchResult::from_results(file_results);
batch.duration = start.elapsed();
batch
}