Skip to main content

hyperi_rustlib/cli/
output.rs

1// Project:   hyperi-rustlib
2// File:      src/cli/output.rs
3// Purpose:   CLI output formatting helpers
4// Language:  Rust
5//
6// License:   BUSL-1.1
7// Copyright: (c) 2026 HYPERI PTY LIMITED
8
9//! Output formatting helpers for CLI tools.
10//!
11//! Provides consistent terminal output across all DFE services.
12
13use std::fmt;
14
15/// Print a success message to stderr.
16pub fn print_success(msg: &str) {
17    eprintln!("[ok] {msg}");
18}
19
20/// Print an error message to stderr.
21pub fn print_error(msg: &str) {
22    eprintln!("[error] {msg}");
23}
24
25/// Print a warning message to stderr.
26pub fn print_warn(msg: &str) {
27    eprintln!("[warn] {msg}");
28}
29
30/// Print an info message to stderr.
31pub fn print_info(msg: &str) {
32    eprintln!("[info] {msg}");
33}
34
35/// Print a key-value pair to stderr with aligned formatting.
36pub fn print_kv(key: &str, value: &dyn fmt::Display) {
37    eprintln!("  {key:<16} {value}");
38}
39
40/// Print a simple table to stderr.
41///
42/// Headers and rows are aligned by column width.
43pub fn print_table(headers: &[&str], rows: &[Vec<String>]) {
44    if headers.is_empty() {
45        return;
46    }
47
48    // Calculate column widths
49    let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
50    for row in rows {
51        for (i, cell) in row.iter().enumerate() {
52            if i < widths.len() {
53                widths[i] = widths[i].max(cell.len());
54            }
55        }
56    }
57
58    // Print header
59    let header_line: Vec<String> = headers
60        .iter()
61        .zip(&widths)
62        .map(|(h, w)| format!("{h:<w$}"))
63        .collect();
64    eprintln!("  {}", header_line.join("  "));
65
66    // Print separator
67    let sep_line: Vec<String> = widths.iter().map(|w| "-".repeat(*w)).collect();
68    eprintln!("  {}", sep_line.join("  "));
69
70    // Print rows
71    for row in rows {
72        let cells: Vec<String> = row
73            .iter()
74            .zip(&widths)
75            .map(|(c, w)| format!("{c:<w$}"))
76            .collect();
77        eprintln!("  {}", cells.join("  "));
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_print_table_empty_headers() {
87        // Should not panic with empty headers
88        print_table(&[], &[]);
89    }
90
91    #[test]
92    fn test_print_table_formats() {
93        // Verify it doesn't panic with real data
94        let headers = &["Name", "Value"];
95        let rows = vec![
96            vec!["key1".to_string(), "val1".to_string()],
97            vec!["longer_key".to_string(), "v".to_string()],
98        ];
99        print_table(headers, &rows);
100    }
101}