use std::io::{Read, Write};
use anyhow::{Context, Result};
use super::checkpoint::extract_checkpoint;
use super::compress_output;
use super::delta::delta;
use super::waste::{detect_waste, parse_calls};
#[derive(clap::Args, Debug)]
pub struct CompressOutputArgs {
#[arg(long)]
pub family: Option<String>,
}
pub fn run(args: &CompressOutputArgs) -> Result<()> {
let mut raw = Vec::new();
std::io::stdin()
.read_to_end(&mut raw)
.context("read stdin")?;
let text = String::from_utf8_lossy(&raw);
let outcome = compress_output(text.as_ref(), args.family.as_deref());
let mut stdout = std::io::stdout().lock();
stdout
.write_all(outcome.output.as_bytes())
.context("write compressed output")?;
stdout.flush().context("flush stdout")?;
let saved = outcome
.original_bytes
.saturating_sub(outcome.compressed_bytes);
let pct = if outcome.original_bytes > 0 {
(saved as f64 / outcome.original_bytes as f64) * 100.0
} else {
0.0
};
eprintln!(
"compress-output: family={} compressed={} {} -> {} bytes ({:.0}% saved)",
outcome.family_detected,
outcome.compressed,
outcome.original_bytes,
outcome.compressed_bytes,
pct,
);
Ok(())
}
#[derive(clap::Args, Debug)]
pub struct DeltaArgs {
#[arg(long)]
pub old: std::path::PathBuf,
}
pub fn run_delta(args: &DeltaArgs) -> Result<()> {
let old_raw = std::fs::read(&args.old)
.with_context(|| format!("read old content from {}", args.old.display()))?;
let old = String::from_utf8_lossy(&old_raw);
let mut new_raw = Vec::new();
std::io::stdin()
.read_to_end(&mut new_raw)
.context("read new content from stdin")?;
let new = String::from_utf8_lossy(&new_raw);
let outcome = delta(old.as_ref(), new.as_ref());
let mut stdout = std::io::stdout().lock();
stdout
.write_all(outcome.output.as_bytes())
.context("write delta output")?;
stdout.write_all(b"\n").context("write trailing newline")?;
stdout.flush().context("flush stdout")?;
eprintln!(
"delta: changed={} bailed={} old_lines={} new_lines={} +{}/-{}",
outcome.changed,
outcome.bailed,
outcome.old_lines,
outcome.new_lines,
outcome.added,
outcome.removed,
);
Ok(())
}
#[derive(clap::Args, Debug)]
pub struct CheckpointArgs {}
pub fn run_checkpoint(root: &std::path::Path, _args: &CheckpointArgs) -> Result<()> {
let mut raw = Vec::new();
std::io::stdin()
.read_to_end(&mut raw)
.context("read stdin")?;
let text = String::from_utf8_lossy(&raw);
let files_changed = changed_files(root);
let checkpoint = extract_checkpoint(text.as_ref(), files_changed);
let json = serde_json::to_string_pretty(&checkpoint).context("serialize checkpoint")?;
let mut stdout = std::io::stdout().lock();
stdout
.write_all(json.as_bytes())
.context("write checkpoint json")?;
stdout.write_all(b"\n").context("write trailing newline")?;
stdout.flush().context("flush stdout")?;
eprintln!(
"checkpoint: decisions={} errors={} files={} decisions_truncated={} errors_truncated={}",
checkpoint.decisions.len(),
checkpoint.errors.len(),
checkpoint.files_changed.len(),
checkpoint.decisions_truncated,
checkpoint.errors_truncated,
);
Ok(())
}
fn changed_files(root: &std::path::Path) -> Vec<String> {
let Ok(repo) = crate::git::Repo::discover(root) else {
return Vec::new();
};
let Ok(status) = repo.status_porcelain() else {
return Vec::new();
};
status
.staged_added
.iter()
.chain(&status.staged_modified)
.chain(&status.staged_deleted)
.chain(&status.modified)
.chain(&status.untracked)
.map(|p| p.to_string())
.collect()
}
#[derive(clap::Args, Debug)]
pub struct DetectWasteArgs {}
pub fn run_detect_waste(_args: &DetectWasteArgs) -> Result<()> {
let mut raw = Vec::new();
std::io::stdin()
.read_to_end(&mut raw)
.context("read stdin")?;
let text = String::from_utf8_lossy(&raw);
let calls = parse_calls(text.as_ref());
let report = detect_waste(&calls);
let json = serde_json::to_string_pretty(&report).context("serialize waste report")?;
let mut stdout = std::io::stdout().lock();
stdout
.write_all(json.as_bytes())
.context("write waste report json")?;
stdout.write_all(b"\n").context("write trailing newline")?;
stdout.flush().context("flush stdout")?;
eprintln!(
"detect-waste: findings={} waste_bytes={} truncated={}",
report.findings.len(),
report.total_estimated_waste_bytes,
report.truncated,
);
Ok(())
}