Skip to main content

nexql_tools/
format.rs

1// SPDX-License-Identifier: GPL-3.0-only
2// Copyright (C) 2026 NexQL-OSS Team
3
4//! Output format helpers for `run_select`.
5
6use serde_json::Value;
7
8pub fn escape_markdown_cell(value: &str) -> String {
9    value.replace('|', "\\|").replace('\n', " ")
10}
11
12pub fn value_to_display_string(value: &Value) -> String {
13    match value {
14        Value::Null => String::new(),
15        Value::String(s) => s.clone(),
16        Value::Number(n) => n.to_string(),
17        Value::Bool(b) => b.to_string(),
18        other => other.to_string(),
19    }
20}
21
22/// Render columnar query output as a GitHub-flavored markdown pipe table.
23pub fn rows_to_markdown(columns: &[String], rows: &[Vec<Value>]) -> String {
24    if columns.is_empty() {
25        return String::new();
26    }
27    let mut out = String::new();
28    out.push('|');
29    for col in columns {
30        out.push(' ');
31        out.push_str(&escape_markdown_cell(col));
32        out.push_str(" |");
33    }
34    out.push('\n');
35    out.push('|');
36    for _ in columns {
37        out.push_str(" --- |");
38    }
39    out.push('\n');
40    for row in rows {
41        out.push('|');
42        for (idx, _) in columns.iter().enumerate() {
43            let cell = row.get(idx).map(value_to_display_string).unwrap_or_default();
44            out.push(' ');
45            out.push_str(&escape_markdown_cell(&cell));
46            out.push_str(" |");
47        }
48        out.push('\n');
49    }
50    out
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use serde_json::json;
57
58    #[test]
59    fn markdown_table_renders_headers_and_rows() {
60        let md = rows_to_markdown(
61            &["id".into(), "name".into()],
62            &[vec![json!(1), json!("alice")], vec![json!(2), json!("bob")]],
63        );
64        assert!(md.contains("| id | name |"));
65        assert!(md.contains("| 1 | alice |"));
66        assert!(md.contains("| 2 | bob |"));
67    }
68
69    #[test]
70    fn markdown_escapes_pipes() {
71        let md = rows_to_markdown(&["x".into()], &[vec![json!("a|b")]]);
72        assert!(md.contains(r"a\|b"));
73    }
74}