use std::path::PathBuf;
use std::process::ExitCode;
use anyhow::Result;
use clap::Parser;
use split_modules::model::{Config, FileOutcome};
use split_modules::{split_file, split_project};
#[derive(Parser, Debug)]
#[command(name = "cargo-split-modules", version, about, long_about = None)]
struct Cli {
path: PathBuf,
#[arg(short, long)]
recursive: bool,
#[arg(short = 'n', long)]
dry_run: bool,
#[arg(long)]
no_verify: bool,
#[arg(long)]
no_fmt: bool,
#[arg(long, default_value_t = 2)]
min_groups: usize,
}
fn main() -> ExitCode {
let mut args: Vec<String> = std::env::args().collect();
if args.get(1).map(|s| s.as_str()) == Some("split-modules") {
args.remove(1);
}
let cli = Cli::parse_from(args);
match run(cli) {
Ok(code) => code,
Err(e) => {
eprintln!("error: {e:#}");
ExitCode::FAILURE
}
}
}
fn run(cli: Cli) -> Result<ExitCode> {
let config = Config {
verify: !cli.no_verify,
rustfmt: !cli.no_fmt,
dry_run: cli.dry_run,
min_groups: cli.min_groups,
};
if config.dry_run {
println!("(dry run — no files will be written)");
}
if config.verify && !cli.dry_run {
println!("running baseline `cargo check` …");
}
let recursive = cli.recursive || cli.path.is_dir();
let mut had_problem = false;
if recursive {
let results = split_project(&cli.path, &config)?;
if results.is_empty() {
println!("no files needed splitting under {}", cli.path.display());
}
for (path, outcome) in &results {
had_problem |= report(&path.display().to_string(), outcome);
}
let split = results
.iter()
.filter(|(_, o)| matches!(o, FileOutcome::Split { .. }))
.count();
println!("\n{} file(s) split.", split);
} else {
let outcome = split_file(&cli.path, &config)?;
had_problem |= report(&cli.path.display().to_string(), &outcome);
}
Ok(if had_problem { ExitCode::FAILURE } else { ExitCode::SUCCESS })
}
fn report(path: &str, outcome: &FileOutcome) -> bool {
match outcome {
FileOutcome::Split { files } => {
let children = files.len().saturating_sub(1);
println!("✓ {path} → {children} module file(s)");
false
}
FileOutcome::Skipped(why) => {
println!("• {path} skipped: {why}");
false
}
FileOutcome::RolledBack(reason) => {
eprintln!("✗ {path} rolled back (would not compile):");
for line in reason.lines() {
eprintln!(" {line}");
}
true
}
}
}