///|
fn json_hex_digit(n : Int) -> Char {
  let digits = "0123456789abcdef".to_array()
  digits[n]
}

///|
fn json_escaped(value : String) -> String {
  let out = StringBuilder()
  out.write_char('"')
  for c in value {
    match c {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      _ => {
        let n = c.to_int()
        if n < 32 {
          out.write_string("\\u00")
          out.write_char(json_hex_digit(n / 16))
          out.write_char(json_hex_digit(n % 16))
        } else {
          out.write_char(c)
        }
      }
    }
  }
  out.write_char('"')
  out.to_string()
}

///|
fn write_json_field(
  out : StringBuilder,
  name : String,
  value : String,
  comma : Bool,
) -> Unit {
  if comma {
    out.write_char(',')
  }
  out.write_string(json_escaped(name))
  out.write_char(':')
  out.write_string(json_escaped(value))
}

///|
fn write_json_number(
  out : StringBuilder,
  name : String,
  value : Int,
  comma : Bool,
) -> Unit {
  if comma {
    out.write_char(',')
  }
  out.write_string(json_escaped(name))
  out.write_char(':')
  out.write_string(value.to_string())
}

///|
fn write_diagnostic_json(out : StringBuilder, item : Diagnostic) -> Unit {
  out.write_char('{')
  write_json_field(out, "code", item.code, false)
  write_json_field(out, "severity", item.severity, true)
  write_json_field(out, "message", item.message, true)
  write_json_number(out, "line", item.line, true)
  write_json_number(out, "column", item.column, true)
  write_json_field(out, "owner", item.owner, true)
  out.write_char('}')
}

///|
/// Render a deterministic, parseable JSON diagnostics object.
pub fn render_diagnostics_json(items : Array[Diagnostic]) -> String {
  let out = StringBuilder()
  out.write_string("{\"errors\":")
  out.write_string(error_count(items).to_string())
  out.write_string(",\"warnings\":")
  out.write_string(warning_count(items).to_string())
  out.write_string(",\"diagnostics\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    write_diagnostic_json(out, items[i])
  }
  out.write_string("]}")
  out.to_string()
}

///|
fn diagnostic_line(item : Diagnostic) -> String {
  "\{item.line}:\{item.column}: \{item.severity} \{item.code}: \{item.message}"
}

///|
/// Render diagnostics for terminal display.
pub fn render_diagnostics_text(items : Array[Diagnostic]) -> String {
  let out = StringBuilder()
  for item in items {
    out.write_string(diagnostic_line(item))
    if item.owner != "" {
      out.write_string(" [")
      out.write_string(item.owner)
      out.write_char(']')
    }
    out.write_char('\n')
  }
  out.write_string(
    "\{error_count(items)} error(s), \{warning_count(items)} warning(s)\n",
  )
  out.to_string()
}

///|
fn markdown_escape(text : String) -> String {
  let out = StringBuilder()
  for c in text {
    if c == '|' {
      out.write_string("\\|")
    } else if c == '\n' {
      out.write_string("
") } else { out.write_char(c) } } out.to_string() } ///| /// Render a compact review report suitable for a pull request description. pub fn render_diagnostics_markdown(items : Array[Diagnostic]) -> String { let out = StringBuilder() out.write_string("# MoonZoneGuard report\n\n") out.write_string( "Errors: \{error_count(items)} ยท Warnings: \{warning_count(items)}\n\n", ) out.write_string( "| Location | Severity | Rule | Finding |\n| --- | --- | --- | --- |\n", ) for item in items { out.write_string( "| \{item.line}:\{item.column} | \{item.severity} | \{item.code} | \{markdown_escape(item.message)} |\n", ) } if items.length() == 0 { out.write_string("| โ€” | โ€” | โ€” | No findings |\n") } out.to_string() } ///| fn write_rdata_text(out : StringBuilder, rdata : Array[String]) -> Unit { for i in 0.. 0 { out.write_char(' ') } out.write_string(rdata[i]) } } ///| fn write_change_json(out : StringBuilder, change : ZoneChange) -> Unit { out.write_char('{') write_json_field(out, "kind", change.kind, false) write_json_field(out, "owner", change.owner, true) write_json_field(out, "type", change.record_type, true) out.write_string(",\"rdata\":[") for i in 0.. 0 { out.write_char(',') } out.write_string(json_escaped(change.rdata[i])) } out.write_char(']') out.write_string(",\"before_ttl\":") match change.before_ttl { Some(n) => out.write_string(n.to_string()) None => out.write_string("null") } out.write_string(",\"after_ttl\":") match change.after_ttl { Some(n) => out.write_string(n.to_string()) None => out.write_string("null") } out.write_char('}') } ///| pub fn render_diff_json(diff : ZoneDiff) -> String { let out = StringBuilder() out.write_string("{\"changes\":[") for i in 0.. 0 { out.write_char(',') } write_change_json(out, diff.changes[i]) } out.write_string("],\"diagnostics\":[") for i in 0.. 0 { out.write_char(',') } write_diagnostic_json(out, diff.diagnostics[i]) } out.write_string("]}") out.to_string() } ///| pub fn render_diff_text(diff : ZoneDiff) -> String { let out = StringBuilder() for change in diff.changes { out.write_string(change.kind) out.write_char(' ') out.write_string(change.owner) out.write_char(' ') out.write_string(change.record_type) out.write_char(' ') write_rdata_text(out, change.rdata) if change.kind == "ttl-changed" { out.write_string(" ttl ") match change.before_ttl { Some(n) => out.write_string(n.to_string()) None => out.write_string("?") } out.write_string(" -> ") match change.after_ttl { Some(n) => out.write_string(n.to_string()) None => out.write_string("?") } } out.write_char('\n') } for item in diff.diagnostics { out.write_string(diagnostic_line(item)) out.write_char('\n') } out.write_string( "\{diff.changes.length()} change(s), \{error_count(diff.diagnostics)} error(s)\n", ) out.to_string() }