///|
/// Render a human-readable Markdown audit report.
pub fn Audit::to_markdown(self : Audit) -> String {
let buf = StringBuilder()
buf.write_string("# Constraint Lens Report\n\n")
buf.write_string("- Target: \{self.title}\n")
buf.write_string("- Score: \{self.score}/100 (\{self.grade})\n")
buf.write_string("- Earned: \{self.earned_score}/\{self.max_score}\n")
buf.write_string("- Lines: \{self.stats.line_count}\n")
buf.write_string("- Headings: \{self.stats.heading_count}\n")
buf.write_string("- Bullets: \{self.stats.bullet_count}\n")
buf.write_string("- Failed rules: \{self.stats.failed_rules}\n")
buf.write_string("- Blocking errors: \{self.stats.error_count}\n\n")
buf.write_string("## Findings\n\n")
for finding in self.findings {
let status = if finding.passed { "PASS" } else { "FAIL" }
buf.write_string("### \{status} \{finding.rule_id}: \{finding.title}\n\n")
buf.write_string("- Severity: \{finding.severity.label()}\n")
buf.write_string("- Category: \{finding.category}\n")
buf.write_string("- Weight: \{finding.earned}/\{finding.weight}\n")
buf.write_string("- Message: \{finding.message}\n")
if !finding.hint.is_empty() {
buf.write_string("- Hint: \{finding.hint}\n")
}
if !finding.evidence.is_empty() {
buf.write_string("- Evidence:\n")
for item in finding.evidence {
buf.write_string(
" - line \{item.line}, matched `\{item.matched}`: \{item.snippet}\n",
)
}
}
buf.write_string("\n")
}
buf.to_string()
}
///|
/// Render a compact JSON-like string without depending on a JSON package.
pub fn Audit::to_jsonish(self : Audit) -> String {
let buf = StringBuilder()
buf.write_string("{")
buf.write_string("\"title\":\"\{json_escape(self.title)}\",")
buf.write_string("\"score\":\{self.score},")
buf.write_string("\"grade\":\"\{self.grade}\",")
buf.write_string(
"\"success\":\{if self.is_success() { "true" } else { "false" }},",
)
buf.write_string("\"findings\":[")
for i in 0.. 0 {
buf.write_string(",")
}
let finding = self.findings[i]
buf.write_string("{")
buf.write_string("\"id\":\"\{json_escape(finding.rule_id)}\",")
buf.write_string(
"\"passed\":\{if finding.passed { "true" } else { "false" }},",
)
buf.write_string("\"severity\":\"\{finding.severity.label()}\",")
buf.write_string("\"earned\":\{finding.earned},")
buf.write_string("\"weight\":\{finding.weight},")
buf.write_string("\"message\":\"\{json_escape(finding.message)}\"")
buf.write_string("}")
}
buf.write_string("]}")
buf.to_string()
}
///|
fn json_escape(input : String) -> String {
let buf = StringBuilder()
for ch in input {
if ch == '"' {
buf.write_string("\\\"")
} else if ch == '\\' {
buf.write_string("\\\\")
} else if ch == '\n' {
buf.write_string("\\n")
} else if ch == '\r' {
buf.write_string("\\r")
} else if ch == '\t' {
buf.write_string("\\t")
} else {
buf.write_char(ch)
}
}
buf.to_string()
}