Skip to main content

cargo_hammerwork/utils/
display.rs

1//! Display formatting utilities for the Hammerwork CLI.
2//!
3//! This module provides table formatting and display utilities for presenting
4//! job information, statistics, and other data in a user-friendly format.
5//!
6//! # Examples
7//!
8//! ## Creating a Job Table
9//!
10//! ```rust
11//! use cargo_hammerwork::utils::display::JobTable;
12//!
13//! let mut table = JobTable::new();
14//! table.add_job_row(
15//!     "550e8400-e29b-41d4-a716-446655440000",
16//!     "email",
17//!     "pending",
18//!     "high",
19//!     0,
20//!     "2024-01-01 10:00:00",
21//!     "2024-01-01 10:05:00"
22//! );
23//!
24//! // Display the table
25//! println!("{}", table);
26//! ```
27//!
28//! ## Creating a Statistics Table
29//!
30//! ```rust
31//! use cargo_hammerwork::utils::display::StatsTable;
32//!
33//! let mut stats = StatsTable::new();
34//! stats.add_stats_row("pending", "normal", 42);
35//! stats.add_stats_row("running", "high", 5);
36//! stats.add_stats_row("completed", "normal", 1337);
37//!
38//! println!("{}", stats);
39//! ```
40//!
41//! ## Formatting Helpers
42//!
43//! ```rust
44//! use cargo_hammerwork::utils::display::{format_duration, format_size};
45//!
46//! // Format durations
47//! assert_eq!(format_duration(Some(45)), "45s");
48//! assert_eq!(format_duration(Some(125)), "2m 5s");
49//! assert_eq!(format_duration(Some(3661)), "1h 1m");
50//! assert_eq!(format_duration(None), "N/A");
51//!
52//! // Format sizes
53//! assert_eq!(format_size(Some(512)), "512B");
54//! assert_eq!(format_size(Some(2048)), "2.0KB");
55//! assert_eq!(format_size(Some(1_048_576)), "1.0MB");
56//! assert_eq!(format_size(None), "N/A");
57//! ```
58
59use comfy_table::Table;
60use std::fmt;
61
62/// Create a new basic table with default styling.
63///
64/// This is a convenience function for creating simple tables in CLI commands.
65pub fn create_table() -> Table {
66    Table::new()
67}
68
69/// Table formatter for displaying job information.
70///
71/// This struct creates formatted tables with job details including
72/// status icons, priority indicators, and truncated IDs for readability.
73///
74/// # Examples
75///
76/// ```rust
77/// use cargo_hammerwork::utils::display::JobTable;
78///
79/// let mut table = JobTable::new();
80///
81/// // Add multiple jobs
82/// table.add_job_row(
83///     "job-id-1", "email", "pending", "normal", 0,
84///     "2024-01-01 10:00:00", "2024-01-01 10:00:00"
85/// );
86/// table.add_job_row(
87///     "job-id-2", "data-processing", "running", "high", 1,
88///     "2024-01-01 09:55:00", "2024-01-01 10:00:00"
89/// );
90///
91/// // The table will display with color-coded status and priority
92/// ```
93pub struct JobTable {
94    table: Table,
95}
96
97impl Default for JobTable {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl JobTable {
104    /// Create a new job table with predefined headers.
105    ///
106    /// Headers include: ID, Queue, Status, Priority, Attempts, Created At, Scheduled At
107    pub fn new() -> Self {
108        let mut table = Table::new();
109        table.set_header(vec![
110            "ID",
111            "Queue",
112            "Status",
113            "Priority",
114            "Attempts",
115            "Created At",
116            "Scheduled At",
117        ]);
118        Self { table }
119    }
120
121    /// Add a job row to the table.
122    ///
123    /// The method automatically:
124    /// - Truncates job IDs to 8 characters for readability
125    /// - Adds status icons and colors (🟡 pending, 🔵 running, 🟢 completed, etc.)
126    /// - Adds priority icons (🚨 critical, ⚡ high, 📝 normal, etc.)
127    ///
128    /// # Arguments
129    ///
130    /// * `id` - Job UUID
131    /// * `queue_name` - Name of the queue
132    /// * `status` - Job status (pending, running, completed, failed, dead, retrying)
133    /// * `priority` - Job priority (critical, high, normal, low, background)
134    /// * `attempts` - Number of execution attempts
135    /// * `created_at` - Creation timestamp
136    /// * `scheduled_at` - Scheduled execution timestamp
137    #[allow(clippy::too_many_arguments)]
138    pub fn add_job_row(
139        &mut self,
140        id: &str,
141        queue_name: &str,
142        status: &str,
143        priority: &str,
144        attempts: i32,
145        created_at: &str,
146        scheduled_at: &str,
147    ) {
148        let status_colored = match status {
149            "pending" => format!("🟡 {}", status),
150            "running" => format!("🔵 {}", status),
151            "completed" => format!("🟢 {}", status),
152            "failed" => format!("🔴 {}", status),
153            "dead" => format!("💀 {}", status),
154            "retrying" => format!("🟠 {}", status),
155            _ => status.to_string(),
156        };
157
158        let priority_colored = match priority {
159            "critical" => format!("🚨 {}", priority),
160            "high" => format!("⚡ {}", priority),
161            "normal" => format!("📝 {}", priority),
162            "low" => format!("🐌 {}", priority),
163            "background" => format!("💤 {}", priority),
164            _ => priority.to_string(),
165        };
166
167        self.table.add_row(vec![
168            &id[..8.min(id.len())],
169            queue_name,
170            &status_colored,
171            &priority_colored,
172            &attempts.to_string(),
173            created_at,
174            scheduled_at,
175        ]);
176    }
177}
178
179impl fmt::Display for JobTable {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(f, "{}", self.table)
182    }
183}
184
185/// Table formatter for displaying job statistics.
186///
187/// This struct creates formatted tables showing job counts by status and priority,
188/// with visual indicators for easy scanning.
189///
190/// # Examples
191///
192/// ```rust
193/// use cargo_hammerwork::utils::display::StatsTable;
194///
195/// let mut stats = StatsTable::new();
196/// stats.add_stats_row("pending", "normal", 100);
197/// stats.add_stats_row("running", "high", 10);
198/// stats.add_stats_row("failed", "critical", 2);
199///
200/// // Display shows icons: 🟡 pending, 🔵 running, 🔴 failed
201/// ```
202pub struct StatsTable {
203    table: Table,
204}
205
206impl Default for StatsTable {
207    fn default() -> Self {
208        Self::new()
209    }
210}
211
212impl StatsTable {
213    pub fn new() -> Self {
214        let mut table = Table::new();
215        table.set_header(vec!["Status", "Priority", "Count"]);
216        Self { table }
217    }
218
219    pub fn add_stats_row(&mut self, status: &str, priority: &str, count: i64) {
220        let status_icon = match status {
221            "pending" => "🟡",
222            "running" => "🔵",
223            "completed" => "🟢",
224            "failed" => "🔴",
225            "dead" => "💀",
226            "retrying" => "🟠",
227            _ => "❓",
228        };
229
230        let priority_icon = match priority {
231            "critical" => "🚨",
232            "high" => "⚡",
233            "normal" => "📝",
234            "low" => "🐌",
235            "background" => "💤",
236            _ => "❓",
237        };
238
239        self.table.add_row(vec![
240            &format!("{} {}", status_icon, status),
241            &format!("{} {}", priority_icon, priority),
242            &count.to_string(),
243        ]);
244    }
245}
246
247impl fmt::Display for StatsTable {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        write!(f, "{}", self.table)
250    }
251}
252
253/// Format a duration in seconds to a human-readable string.
254///
255/// # Examples
256///
257/// ```rust
258/// use cargo_hammerwork::utils::display::format_duration;
259///
260/// assert_eq!(format_duration(Some(30)), "30s");
261/// assert_eq!(format_duration(Some(90)), "1m 30s");
262/// assert_eq!(format_duration(Some(3665)), "1h 1m");
263/// assert_eq!(format_duration(Some(7200)), "2h 0m");
264/// assert_eq!(format_duration(None), "N/A");
265/// ```
266pub fn format_duration(seconds: Option<i64>) -> String {
267    match seconds {
268        Some(secs) if secs < 60 => format!("{}s", secs),
269        Some(secs) if secs < 3600 => format!("{}m {}s", secs / 60, secs % 60),
270        Some(secs) => format!("{}h {}m", secs / 3600, (secs % 3600) / 60),
271        None => "N/A".to_string(),
272    }
273}
274
275/// Format a byte size to a human-readable string.
276///
277/// # Examples
278///
279/// ```rust
280/// use cargo_hammerwork::utils::display::format_size;
281///
282/// assert_eq!(format_size(Some(100)), "100B");
283/// assert_eq!(format_size(Some(1024)), "1.0KB");
284/// assert_eq!(format_size(Some(1536)), "1.5KB");
285/// assert_eq!(format_size(Some(1048576)), "1.0MB");
286/// assert_eq!(format_size(Some(1073741824)), "1.0GB");
287/// assert_eq!(format_size(None), "N/A");
288/// ```
289pub fn format_size(bytes: Option<i64>) -> String {
290    match bytes {
291        Some(b) if b < 1024 => format!("{}B", b),
292        Some(b) if b < 1024 * 1024 => format!("{:.1}KB", b as f64 / 1024.0),
293        Some(b) if b < 1024 * 1024 * 1024 => format!("{:.1}MB", b as f64 / (1024.0 * 1024.0)),
294        Some(b) => format!("{:.1}GB", b as f64 / (1024.0 * 1024.0 * 1024.0)),
295        None => "N/A".to_string(),
296    }
297}