1use comfy_table::{Cell, Color, Table, presets::UTF8_FULL_CONDENSED};
2use serde_json::{Map, Value, json};
3
4#[derive(Default)]
6pub struct ListOptions<'a> {
7 pub limit: Option<usize>,
8 pub offset: Option<usize>,
9 pub fields: Option<&'a str>,
10}
11
12pub fn apply_pagination(rows: &mut Vec<Vec<String>>, opts: &ListOptions<'_>) {
14 if let Some(off) = opts.offset {
15 if off >= rows.len() {
16 rows.clear();
17 return;
18 }
19 rows.drain(..off);
20 }
21 if let Some(lim) = opts.limit {
22 rows.truncate(lim);
23 }
24}
25
26pub enum OutputFormat {
27 Table,
28 Json,
29}
30
31impl OutputFormat {
32 pub fn from_flag(flag: Option<&str>, is_tty: bool) -> Self {
33 match flag {
34 Some("json") => Self::Json,
35 Some("text") | Some("table") => Self::Table,
37 Some("auto") | None => {
39 if is_tty {
40 Self::Table
41 } else {
42 Self::Json
43 }
44 }
45 Some(_) => {
47 if is_tty {
48 Self::Table
49 } else {
50 Self::Json
51 }
52 }
53 }
54 }
55}
56
57pub fn format_json(headers: &[String], rows: &[Vec<String>]) -> String {
59 let items: Vec<Value> = rows
60 .iter()
61 .map(|row| {
62 let mut map = Map::new();
63 for (i, header) in headers.iter().enumerate() {
64 let val = row.get(i).cloned().unwrap_or_default();
65 map.insert(header.clone(), Value::String(val));
66 }
67 Value::Object(map)
68 })
69 .collect();
70 let total = items.len();
71 serde_json::to_string_pretty(&json!({
72 "items": items,
73 "total": total
74 }))
75 .unwrap_or_else(|_| r#"{"items":[],"total":0}"#.into())
76}
77
78pub fn format_table(headers: &[String], rows: &[Vec<String>]) -> String {
79 let mut table = Table::new();
80 table.load_preset(UTF8_FULL_CONDENSED);
81 let header_cells: Vec<Cell> = headers
82 .iter()
83 .map(|h| {
84 Cell::new(h)
85 .fg(Color::White)
86 .add_attribute(comfy_table::Attribute::Bold)
87 })
88 .collect();
89 table.set_header(header_cells);
90 for row in rows {
91 table.add_row(row);
92 }
93 table.to_string()
94}
95
96pub fn format_error_json(message: &str, kind: &str) -> String {
101 serde_json::to_string(&json!({
102 "error": {
103 "kind": kind,
104 "message": message
105 }
106 }))
107 .unwrap_or_else(|_| format!(r#"{{"error":{{"kind":"{kind}","message":"{message}"}}}}"#))
108}
109
110pub fn is_tty() -> bool {
111 std::io::IsTerminal::is_terminal(&std::io::stdout())
112}