Files
cli/src/output.rs
Sienna Meridian Satterwhite 80c67d34cb feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:

- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)

23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00

93 lines
2.4 KiB
Rust

/// Print a step header.
pub fn step(msg: &str) {
println!("\n==> {msg}");
}
/// Print a success/info line.
pub fn ok(msg: &str) {
println!(" {msg}");
}
/// Print a warning to stderr.
pub fn warn(msg: &str) {
eprintln!(" WARN: {msg}");
}
/// Return an aligned text table. Columns padded to max width.
pub fn table(rows: &[Vec<String>], headers: &[&str]) -> String {
if headers.is_empty() {
return String::new();
}
let mut col_widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
for row in rows {
for (i, cell) in row.iter().enumerate() {
if i < col_widths.len() {
col_widths[i] = col_widths[i].max(cell.len());
}
}
}
let header_line: String = headers
.iter()
.enumerate()
.map(|(i, h)| format!("{:<width$}", h, width = col_widths[i]))
.collect::<Vec<_>>()
.join(" ");
let separator: String = col_widths
.iter()
.map(|&w| "-".repeat(w))
.collect::<Vec<_>>()
.join(" ");
let mut lines = vec![header_line, separator];
for row in rows {
let cells: Vec<String> = (0..headers.len())
.map(|i| {
let val = row.get(i).map(|s| s.as_str()).unwrap_or("");
format!("{:<width$}", val, width = col_widths[i])
})
.collect();
lines.push(cells.join(" "));
}
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_table_basic() {
let rows = vec![
vec!["abc".to_string(), "def".to_string()],
vec!["x".to_string(), "longer".to_string()],
];
let result = table(&rows, &["Col1", "Col2"]);
assert!(result.contains("Col1"));
assert!(result.contains("Col2"));
assert!(result.contains("abc"));
assert!(result.contains("longer"));
}
#[test]
fn test_table_empty_headers() {
let result = table(&[], &[]);
assert!(result.is_empty());
}
#[test]
fn test_table_column_widths() {
let rows = vec![vec!["short".to_string(), "x".to_string()]];
let result = table(&rows, &["LongHeader", "H2"]);
// Header should set minimum width
for line in result.lines().skip(2) {
// Data row: "short" should be padded to "LongHeader" width
assert!(line.starts_with("short "));
}
}
}