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.
This commit is contained in:
92
src/output.rs
Normal file
92
src/output.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
/// 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 "));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user