use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static SYSTEM_CHECK_OK_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^System check identified no issues[^\n]*\n?").unwrap());
static SYSTEM_CHECK_PERFORMING_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^Performing system checks\.\.\.\s*\n?").unwrap());
static HTTP_LOG_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(?m)^"[A-Z]+ [^\n]+" \d{3} \d+\s*$"#).unwrap());
pub fn compress_migrate(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = SYSTEM_CHECK_OK_RE.replace_all(&cleaned, "");
let s = SYSTEM_CHECK_PERFORMING_RE.replace_all(&s, "");
let useful: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.starts_with("Running migrations:")
|| t.starts_with("Applying")
|| t.starts_with("Unapplying")
|| t.starts_with("No migrations")
|| t.contains("OK")
|| t.contains("FAKED")
|| t.contains("error")
|| t.contains("Error")
|| t.contains("CommandError"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&s);
}
useful.join("\n")
}
pub fn compress_makemigrations(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = SYSTEM_CHECK_OK_RE.replace_all(&cleaned, "");
let s = SYSTEM_CHECK_PERFORMING_RE.replace_all(&s, "");
let useful: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.starts_with("Migrations for")
|| t.starts_with("No changes")
|| t.starts_with("Create model")
|| t.starts_with("Add field")
|| t.starts_with("Alter field")
|| t.starts_with("Delete model")
|| t.starts_with("Rename")
|| t.starts_with('-')
|| t.contains("migration")
|| t.contains("Migration")
|| t.contains("error")
|| t.contains("Error"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&s);
}
useful.join("\n")
}
pub fn compress_runserver(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = HTTP_LOG_RE.replace_all(&cleaned, "");
let s = SYSTEM_CHECK_OK_RE.replace_all(&s, "");
let s = SYSTEM_CHECK_PERFORMING_RE.replace_all(&s, "");
let useful: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.contains("Starting development server")
|| t.contains("Quit the server")
|| t.contains("WARNINGS")
|| t.contains("Error")
|| t.contains("error")
|| t.contains("Django version")
|| t.contains("Using settings")
|| t.contains("127.0.0.1")
|| t.contains("0.0.0.0"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&s);
}
useful.join("\n")
}
pub fn compress_test(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = SYSTEM_CHECK_OK_RE.replace_all(&cleaned, "");
let s = SYSTEM_CHECK_PERFORMING_RE.replace_all(&s, "");
let s_no_dots = s.to_string();
let useful: Vec<&str> = s_no_dots
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& !t.chars().all(|c| c == '.')
&& (t.starts_with("FAIL")
|| t.starts_with("ERROR")
|| t.starts_with("Ran ")
|| t.starts_with("OK")
|| t.starts_with("FAILED")
|| t.contains("AssertionError")
|| t.contains("Error:")
|| t.starts_with("=====")
|| t.starts_with("-----"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&s_no_dots);
}
useful.join("\n")
}
pub fn compress_collectstatic(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = SYSTEM_CHECK_OK_RE.replace_all(&cleaned, "");
let useful: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.contains("static files")
|| t.contains("copied")
|| t.contains("post-processed")
|| t.contains("unmodified")
|| t.contains("error")
|| t.contains("Error"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&s);
}
useful.join("\n")
}
pub fn compress_django(subcmd: &str, raw: &str) -> String {
let sub = subcmd.trim();
if sub.starts_with("migrate") {
return compress_migrate(raw);
}
if sub.starts_with("makemigrations") {
return compress_makemigrations(raw);
}
if sub.starts_with("runserver") {
return compress_runserver(raw);
}
if sub.starts_with("test") {
return compress_test(raw);
}
if sub.starts_with("collectstatic") {
return compress_collectstatic(raw);
}
let cleaned = compactor::normalise(raw);
let s = SYSTEM_CHECK_OK_RE.replace_all(&cleaned, "");
let s = SYSTEM_CHECK_PERFORMING_RE.replace_all(&s, "");
compactor::collapse_blanks(&s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn migrate_strips_system_check_boilerplate() {
let raw = "Performing system checks...\n\nSystem check identified no issues (0 silenced).\n\nOperations to perform:\n Apply all migrations: auth, contenttypes, myapp\nRunning migrations:\n Applying myapp.0001_initial... OK\n Applying myapp.0002_add_email... OK\n";
let out = compress_migrate(raw);
assert!(!out.contains("System check identified"), "{out}");
assert!(!out.contains("Performing system checks"), "{out}");
assert!(out.contains("0001_initial"), "{out}");
assert!(out.contains("OK"), "{out}");
}
#[test]
fn makemigrations_keeps_migration_details() {
let raw = "Performing system checks...\n\nSystem check identified no issues (0 silenced).\n\nMigrations for 'myapp':\n myapp/migrations/0003_add_phone.py\n - Add field phone to userprofile\n";
let out = compress_makemigrations(raw);
assert!(out.contains("Migrations for"), "{out}");
assert!(out.contains("0003_add_phone"), "{out}");
assert!(out.contains("Add field phone"), "{out}");
assert!(!out.contains("System check identified"), "{out}");
}
#[test]
fn runserver_strips_http_request_logs() {
let raw = "Django version 5.0, using settings 'myproject.settings'\nStarting development server at http://127.0.0.1:8000/\nQuit the server with CONTROL-C.\n\"GET / HTTP/1.1\" 200 1234\n\"GET /admin/ HTTP/1.1\" 302 0\n\"POST /api/login/ HTTP/1.1\" 200 456\n";
let out = compress_runserver(raw);
assert!(!out.contains("\"GET /"), "{out}");
assert!(!out.contains("\"POST /"), "{out}");
assert!(out.contains("127.0.0.1:8000"), "{out}");
assert!(out.contains("Django version"), "{out}");
}
#[test]
fn test_keeps_failure_output() {
let raw = "Performing system checks...\n\nSystem check identified no issues (0 silenced).\n\n..F\n======================================================================\nFAIL: test_add (myapp.tests.MathTests)\n----------------------------------------------------------------------\nAssertionError: 3 != 4\n----------------------------------------------------------------------\nRan 3 tests in 0.001s\n\nFAILED (failures=1)\n";
let out = compress_test(raw);
assert!(out.contains("FAIL"), "{out}");
assert!(out.contains("AssertionError"), "{out}");
assert!(out.contains("Ran 3 tests"), "{out}");
assert!(!out.contains("System check identified"), "{out}");
}
#[test]
fn collectstatic_keeps_summary() {
let raw = "Performing system checks...\n\nSystem check identified no issues (0 silenced).\n\n125 static files copied to '/app/staticfiles', 42 unmodified.\n";
let out = compress_collectstatic(raw);
assert!(out.contains("static files"), "{out}");
assert!(!out.contains("System check identified"), "{out}");
}
}