use crate::core::compressor;
use crate::core::tokens::count_tokens;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Pipeline {
Read,
Shell,
}
impl Pipeline {
pub(crate) fn label(self) -> &'static str {
match self {
Pipeline::Read => "read/aggressive",
Pipeline::Shell => "shell",
}
}
}
pub(crate) struct Preview {
pub pipeline: Pipeline,
pub original: String,
pub compressed: String,
pub original_tokens: usize,
pub compressed_tokens: usize,
}
impl Preview {
pub(crate) fn original_bytes(&self) -> usize {
self.original.len()
}
pub(crate) fn compressed_bytes(&self) -> usize {
self.compressed.len()
}
pub(crate) fn saved_tokens(&self) -> usize {
self.original_tokens.saturating_sub(self.compressed_tokens)
}
pub(crate) fn token_ratio(&self) -> f64 {
if self.original_tokens == 0 {
return 1.0;
}
self.compressed_tokens as f64 / self.original_tokens as f64
}
pub(crate) fn saved_pct(&self) -> f64 {
((1.0 - self.token_ratio()) * 1000.0).round() / 10.0
}
pub(crate) fn diff(&self) -> String {
compressor::diff_content(&self.original, &self.compressed)
}
pub(crate) fn render(&self) -> String {
let mut out = String::new();
out.push_str("compress preview — pipeline: ");
out.push_str(self.pipeline.label());
out.push('\n');
out.push_str(&format!(
"tokens: {} -> {} (-{}, {:.1}% saved)\n",
self.original_tokens,
self.compressed_tokens,
self.saved_tokens(),
self.saved_pct(),
));
out.push_str(&format!(
"bytes: {} -> {}\n",
self.original_bytes(),
self.compressed_bytes(),
));
out.push_str("-- diff (original -> compressed) --\n");
out.push_str(&self.diff());
if !out.ends_with('\n') {
out.push('\n');
}
out
}
}
pub(crate) fn preview_read(content: &str, ext: Option<&str>) -> Preview {
let compressed = compressor::aggressive_compress(content, ext);
finish(Pipeline::Read, content, compressed)
}
pub(crate) fn preview_shell(command: &str, output: &str) -> Preview {
let compressed = crate::shell::compress::engine::compress_if_beneficial_pub(command, output);
finish(Pipeline::Shell, output, compressed)
}
fn finish(pipeline: Pipeline, original: &str, compressed: String) -> Preview {
Preview {
pipeline,
original_tokens: count_tokens(original),
compressed_tokens: count_tokens(&compressed),
original: original.to_string(),
compressed,
}
}
pub(crate) fn ext_of(path: &str) -> Option<String> {
std::path::Path::new(path)
.extension()
.and_then(|e| e.to_str())
.map(str::to_ascii_lowercase)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_preview_strips_comments_and_saves_tokens() {
let src = "// a leading comment\nfn main() {\n // inner note\n let x = 1;\n}\n";
let p = preview_read(src, Some("rs"));
assert_eq!(p.pipeline, Pipeline::Read);
assert!(
!p.compressed.contains("leading comment") && !p.compressed.contains("inner note"),
"comments must be stripped: {}",
p.compressed
);
assert!(p.compressed_tokens < p.original_tokens, "tokens must drop");
assert!(p.saved_tokens() > 0);
assert!(p.token_ratio() < 1.0);
assert!(p.saved_pct() > 0.0);
}
#[test]
fn read_preview_compacts_pretty_json() {
let json =
"{\n \"name\": \"lean-ctx\",\n \"nested\": {\n \"a\": 1,\n \"b\": 2\n }\n}\n";
let p = preview_read(json, Some("json"));
assert!(!p.compressed.contains("\n "), "json must be compacted");
assert!(p.compressed_tokens < p.original_tokens);
}
#[test]
fn no_change_input_reports_no_savings_and_clean_diff() {
let src = "let x = 1;";
let p = preview_read(src, Some("rs"));
assert_eq!(
p.compressed, p.original,
"already-minimal input is unchanged"
);
assert_eq!(p.saved_tokens(), 0);
assert!((p.token_ratio() - 1.0).abs() < f64::EPSILON);
assert_eq!(p.saved_pct(), 0.0);
assert_eq!(p.diff(), "(no changes)");
}
#[test]
fn shell_preview_compresses_repetitive_output() {
let output = "Compiling foo\n".repeat(40);
let p = preview_shell("cargo build", &output);
assert_eq!(p.pipeline, Pipeline::Shell);
assert!(
p.compressed_tokens <= p.original_tokens,
"shell compression never inflates"
);
}
#[test]
fn render_is_deterministic_and_self_describing() {
let src = "// c\nfn a() {}\n";
let p = preview_read(src, Some("rs"));
let a = p.render();
let b = p.render();
assert_eq!(a, b, "render must be deterministic (#498)");
assert!(a.contains("pipeline: read/aggressive"));
assert!(a.contains("tokens:"));
assert!(a.contains("bytes:"));
}
#[test]
fn ext_of_extracts_lowercased_extension() {
assert_eq!(ext_of("/a/b/File.RS").as_deref(), Some("rs"));
assert_eq!(ext_of("data.JSON").as_deref(), Some("json"));
assert_eq!(ext_of("Makefile"), None);
assert_eq!(ext_of("-"), None);
}
}