///|
/// Stable, machine-readable exports for reports and engineering results.
pub(all) struct KeyValue {
key : String
value : String
} derive(Debug, Eq, ToJson)
///|
pub(all) struct ExportManifest {
format : String
title : String
record_count : Int
generated_by : String
fields : Array[String]
} derive(Debug, Eq, ToJson)
///|
pub fn escape_json_text(value : String) -> String {
value
.replace(old="\\", new="\\\\")
.replace(old="\"", new="\\\"")
.replace(old="\n", new="\\n")
.replace(old="\r", new="\\r")
}
///|
pub fn key_value_json(values : ArrayView[KeyValue]) -> String {
let fields = []
for value in values {
fields.push(
"\"" +
escape_json_text(value.key) +
"\":\"" +
escape_json_text(value.value) +
"\"",
)
}
"{" + fields.join(",") + "}"
}
///|
pub fn manifest_json(manifest : ExportManifest) -> String {
let fields = manifest.fields.map(field => {
"\"" + escape_json_text(field) + "\""
})
"{\"format\":\"" +
escape_json_text(manifest.format) +
"\",\"title\":\"" +
escape_json_text(manifest.title) +
"\",\"record_count\":" +
manifest.record_count.to_string() +
",\"generated_by\":\"" +
escape_json_text(manifest.generated_by) +
"\",\"fields\":[" +
fields.join(",") +
"]}"
}
///|
pub fn document_manifest(
document : ReportDocument,
format : String,
) -> ExportManifest {
let fields = ["title", "subtitle", "blocks", "generated_by"]
{
format,
title: document.title,
record_count: document.blocks.length(),
generated_by: document.generated_by,
fields,
}
}
///|
pub fn document_outline(document : ReportDocument) -> Array[String] {
let outline = []
for block in document.blocks {
match block {
Heading(level~, text~) => outline.push("#".repeat(level) + " " + text)
Paragraph(text~) => outline.push(text)
Bullet(items~) => outline.push(items.join("; "))
Table(table~) => outline.push(table.title)
Finding(finding~) => outline.push(finding.title)
Metric(name~, value~, unit~) =>
outline.push(name + "=" + value.to_string() + unit)
Divider => outline.push("---")
}
}
outline
}
///|
pub fn document_plain_text(document : ReportDocument) -> String {
let lines = [document.title]
if document.subtitle.length() > 0 {
lines.push(document.subtitle)
}
for line in document_outline(document) {
lines.push(line)
}
lines.join("\n")
}
///|
pub fn document_csv(document : ReportDocument) -> String {
let lines = ["kind,name,value,unit,details"]
for block in document.blocks {
match block {
Heading(level~, text~) =>
lines.push("heading," + level.to_string() + ",," + csv_text(text))
Paragraph(text~) => lines.push("paragraph,," + csv_text(text) + ",,")
Bullet(items~) =>
lines.push("bullet,," + csv_text(items.join("; ")) + ",,")
Table(table~) => {
lines.push(
"table," +
csv_text(table.title) +
"," +
table.row_count().to_string() +
",,",
)
for row in table.rows() {
lines.push("table-row,," + csv_text(row.join(" | ")) + ",,")
}
}
Finding(finding~) =>
lines.push(
"finding," +
csv_text(finding.title) +
"," +
csv_text(finding.detail) +
",,",
)
Metric(name~, value~, unit~) =>
lines.push(
"metric," +
csv_text(name) +
"," +
value.to_string() +
"," +
csv_text(unit) +
",",
)
Divider => lines.push("divider,,,,")
}
}
lines.join("\n")
}
///|
fn csv_text(value : String) -> String {
"\"" + value.replace(old="\"", new="\"\"") + "\""
}
///|
fn severity_text(severity : Severity) -> String {
match severity {
Pass => "pass"
Notice => "notice"
Fail => "fail"
}
}
///|
pub fn document_json_lines(document : ReportDocument) -> String {
let lines = []
for block in document.blocks {
let record = match block {
Heading(level~, text~) =>
"{\"kind\":\"heading\",\"level\":" +
level.to_string() +
",\"text\":\"" +
escape_json_text(text) +
"\"}"
Paragraph(text~) =>
"{\"kind\":\"paragraph\",\"text\":\"" + escape_json_text(text) + "\"}"
Bullet(items~) =>
"{\"kind\":\"bullet\",\"items\":[\"" +
items.map(escape_json_text).join("\",\"") +
"\"]}"
Table(table~) =>
"{\"kind\":\"table\",\"title\":\"" +
escape_json_text(table.title) +
"\",\"rows\":" +
table.row_count().to_string() +
"}"
Finding(finding~) =>
"{\"kind\":\"finding\",\"title\":\"" +
escape_json_text(finding.title) +
"\",\"severity\":\"" +
severity_text(finding.severity) +
"\"}"
Metric(name~, value~, unit~) =>
"{\"kind\":\"metric\",\"name\":\"" +
escape_json_text(name) +
"\",\"value\":" +
value.to_string() +
",\"unit\":\"" +
escape_json_text(unit) +
"\"}"
Divider => "{\"kind\":\"divider\"}"
}
lines.push(record)
}
lines.join("\n")
}
///|
pub fn table_to_tsv(table : ReportTable) -> String {
let lines = [table.columns.join("\t")]
for row in table.rows() {
lines.push(row.map(value => value.replace(old="\t", new=" ")).join("\t"))
}
lines.join("\n")
}
///|
pub fn table_to_html(table : ReportTable) -> String {
let lines = [
"",
"" + table.title + " ",
"",
]
for column in table.columns {
lines.push("" + column + " ")
}
lines.push(" ")
for row in table.rows() {
let cells = row.map(value => "" + value + " ")
lines.push("" + cells.join("") + " ")
}
lines.push("
")
lines.join("\n")
}
///|
pub fn document_html(document : ReportDocument) -> String {
let lines = [
"",
"" +
document.title +
" ",
]
lines.push("" + document.title + "
")
if document.subtitle.length() > 0 {
lines.push("" + document.subtitle + "
")
}
for block in document.blocks {
match block {
Heading(level~, text~) =>
lines.push(
"" +
text +
" ",
)
Paragraph(text~) => lines.push("" + text + "
")
Bullet(items~) =>
lines.push("- " + items.join("
- ") + "
")
Table(table~) => lines.push(table_to_html(table))
Finding(finding~) =>
lines.push(
"" +
finding.title +
"" +
finding.detail +
"
" +
severity_text(finding.severity) +
"",
)
Metric(name~, value~, unit~) =>
lines.push(
"" +
name +
"" +
value.to_string() +
"" +
unit +
"",
)
Divider => lines.push("
")
}
}
lines.push("")
lines.join("\n")
}
///|
pub fn export_checksum(text : String) -> String {
let mut first = 216613626
for index in 0.. String {
manifest.format +
": " +
manifest.title +
", " +
manifest.record_count.to_string() +
" records"
}
///|
pub fn report_export_formats() -> Array[String] {
["markdown", "csv", "tsv", "jsonl", "html"]
}
///|
pub fn report_has_format(format : String) -> Bool {
for available in report_export_formats() {
if available == format {
return true
}
}
false
}
///|
pub fn report_export(
document : ReportDocument,
format : String,
) -> String raise @domain.ExtractionError {
match format {
"markdown" => document.to_markdown()
"csv" => document_csv(document)
"jsonl" => document_json_lines(document)
"html" => document_html(document)
_ =>
raise @domain.ExtractionError::InvalidArgument(
message="unsupported report format",
)
}
}