use anyhow::Result;
use clap::{Parser, Subcommand};
use log::{info, warn};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use sphinx_ultra::{analyze_project, BuildConfig, SphinxBuilder};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
config: Option<PathBuf>,
}
#[derive(Subcommand)]
enum Commands {
Build {
#[arg(short, long, default_value = ".")]
source: PathBuf,
#[arg(short, long, default_value = "_build")]
output: PathBuf,
#[arg(short, long)]
jobs: Option<usize>,
#[arg(long)]
clean: bool,
#[arg(long)]
incremental: bool,
#[arg(short = 'W', long)]
fail_on_warning: bool,
#[arg(short = 'w', long)]
warning_file: Option<PathBuf>,
},
Clean {
#[arg(short, long, default_value = "_build")]
output: PathBuf,
},
Stats {
#[arg(short, long, default_value = ".")]
source: PathBuf,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let log_level = if cli.verbose { "debug" } else { "info" };
std::env::set_var("RUST_LOG", log_level);
env_logger::init();
info!("Sphinx Ultra Builder v{}", env!("CARGO_PKG_VERSION"));
match cli.command {
Commands::Build {
source,
output,
jobs,
clean,
incremental,
fail_on_warning,
warning_file,
} => {
let mut config = if let Some(ref config_path) = cli.config {
BuildConfig::from_file(config_path)?
} else {
BuildConfig::auto_detect(&source)?
};
if fail_on_warning {
config.fail_on_warning = true;
}
let should_fail_on_warning = config.fail_on_warning;
let mut builder = SphinxBuilder::new(config, source, output)?;
if let Some(jobs) = jobs {
builder.set_parallel_jobs(jobs);
}
if clean {
builder.clean().await?;
}
if incremental {
builder.enable_incremental();
}
let stats = builder.build().await?;
let mut warning_file_handle = if let Some(ref warning_file_path) = warning_file {
if let Some(parent) = warning_file_path.parent() {
std::fs::create_dir_all(parent)?;
}
Some(
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(warning_file_path)?,
)
} else {
None
};
for warning in &stats.warning_details {
let file_path = warning.file.display();
let line_info = if let Some(line) = warning.line {
format!(":{}", line)
} else {
String::new()
};
let warning_msg =
format!("{}{}: WARNING: {}", file_path, line_info, warning.message);
if let Some(ref mut file) = warning_file_handle {
writeln!(file, "{}", warning_msg)?;
}
warn!("{}", warning_msg);
}
for error in &stats.error_details {
let file_path = error.file.display();
let line_info = if let Some(line) = error.line {
format!(":{}", line)
} else {
String::new()
};
let error_msg = format!("{}{}: ERROR: {}", file_path, line_info, error.message);
if let Some(ref mut file) = warning_file_handle {
writeln!(file, "{}", error_msg)?;
}
eprintln!("{}", error_msg);
}
if let Some(mut file) = warning_file_handle {
file.flush()?;
}
if should_fail_on_warning && stats.warnings > 0 {
eprintln!("Build failed due to warnings (caused by --fail-on-warning)");
std::process::exit(1);
}
if stats.warnings > 0 || stats.errors > 0 {
let status_msg = if stats.errors > 0 {
"build succeeded with problems"
} else {
"build succeeded"
};
if stats.warnings > 0 && stats.errors > 0 {
warn!(
"{}, {} warnings, {} errors.",
status_msg, stats.warnings, stats.errors
);
} else if stats.warnings > 0 {
warn!("{}, {} warnings.", status_msg, stats.warnings);
} else if stats.errors > 0 {
warn!("{}, {} errors.", status_msg, stats.errors);
}
}
info!("Build completed successfully!");
info!("Files processed: {}", stats.files_processed);
info!("Files skipped: {}", stats.files_skipped);
info!("Cache hits: {}", stats.cache_hits);
info!("Build time: {:?}", stats.build_time);
info!("Output size: {} MB", stats.output_size_mb);
}
Commands::Clean { output } => {
info!("Cleaning output directory: {}", output.display());
if output.exists() {
std::fs::remove_dir_all(&output)?;
info!("Clean completed");
} else {
warn!("Output directory does not exist");
}
}
Commands::Stats { source } => {
let stats = analyze_project(&source).await?;
println!("Project Statistics:");
println!(" Source files: {}", stats.source_files);
println!(" Total lines: {}", stats.total_lines);
println!(" Average file size: {} KB", stats.avg_file_size_kb);
println!(" Largest file: {} KB", stats.largest_file_kb);
println!(" Directory depth: {}", stats.max_depth);
println!(" Cross-references: {}", stats.cross_references);
}
}
Ok(())
}