Skip to main content

wasm4pm_cli/
io.rs

1use anyhow::{Context, Result};
2use colored::*;
3use std::fmt::Display;
4use std::fs;
5use std::path::Path;
6
7/// IO Manager for wasm4pm (wpm) CLI
8pub struct Io {
9    verbose: bool,
10}
11
12impl Io {
13    /// Create a new IO manager
14    pub fn new(verbose: bool) -> Self {
15        Self { verbose }
16    }
17
18    /// Print a stylized header
19    pub fn header<D: Display>(&self, text: D) {
20        println!("\n{}", text.to_string().bold().bright_cyan());
21    }
22
23    /// Print a success message
24    pub fn success<D: Display>(&self, text: D) {
25        println!("{} {}", "✔".green(), text);
26    }
27
28    /// Print an error message to stderr
29    pub fn error<D: Display>(&self, text: D) {
30        eprintln!("{} {}", "✘".red(), text);
31    }
32
33    /// Print a warning message
34    pub fn warning<D: Display>(&self, text: D) {
35        println!("{} {}", "!".yellow(), text);
36    }
37
38    /// Print an info message only if verbose is enabled
39    pub fn info<D: Display>(&self, text: D) {
40        if self.verbose {
41            println!("{} {}", "ℹ".blue(), text);
42        }
43    }
44
45    /// Read a file to string with consistent error wrapping
46    pub fn read_file<P: AsRef<Path>>(&self, path: P) -> Result<String> {
47        let path = path.as_ref();
48        self.info(format!("Reading file: {}", path.display()));
49        fs::read_to_string(path).with_context(|| format!("Failed to read file: {}", path.display()))
50    }
51
52    /// Write content to a file with consistent error wrapping
53    pub fn write_file<P: AsRef<Path>>(&self, path: P, content: &str) -> Result<()> {
54        let path = path.as_ref();
55        self.info(format!("Writing file: {}", path.display()));
56        if let Some(parent) = path.parent() {
57            if !parent.exists() {
58                fs::create_dir_all(parent)
59                    .with_context(|| format!("Failed to create directory: {}", parent.display()))?;
60            }
61        }
62        fs::write(path, content)
63            .with_context(|| format!("Failed to write file: {}", path.display()))
64    }
65}
66
67/// A simple table formatter for CLI output
68pub struct Table {
69    headers: Vec<String>,
70    rows: Vec<Vec<String>>,
71}
72
73impl Table {
74    /// Create a new table with headers
75    pub fn new(headers: Vec<&str>) -> Self {
76        Self {
77            headers: headers.into_iter().map(|s| s.to_string()).collect(),
78            rows: Vec::new(),
79        }
80    }
81
82    /// Add a row to the table
83    pub fn add_row(&mut self, row: Vec<String>) {
84        self.rows.push(row);
85    }
86
87    /// Print the table to stdout
88    pub fn print(&self) {
89        if self.headers.is_empty() && self.rows.is_empty() {
90            return;
91        }
92
93        let mut col_widths = self.headers.iter().map(|h| h.len()).collect::<Vec<_>>();
94        for row in &self.rows {
95            for (i, cell) in row.iter().enumerate() {
96                if i < col_widths.len() {
97                    col_widths[i] = col_widths[i].max(cell.len());
98                }
99            }
100        }
101
102        // Print header
103        println!();
104        for (i, header) in self.headers.iter().enumerate() {
105            print!(
106                "{:<width$}  ",
107                header.bold().underline(),
108                width = col_widths[i]
109            );
110        }
111        println!();
112
113        // Print rows
114        for row in &self.rows {
115            for (i, cell) in row.iter().enumerate() {
116                if i < col_widths.len() {
117                    print!("{:<width$}  ", cell, width = col_widths[i]);
118                }
119            }
120            println!();
121        }
122        println!();
123    }
124}