Skip to main content

mz_deploy/cli/
progress.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Progress reporting utilities for user-facing output.
11//!
12//! This module provides helper functions for displaying progress and status
13//! during command execution. It uses colors and symbols to create clear,
14//! scannable output similar to tools like dbt.
15
16use crate::info;
17use owo_colors::{OwoColorize, Stream, Style};
18use std::time::Duration;
19
20/// Print a stage start message with yellow arrow.
21pub fn stage_start(name: &str) {
22    info!(
23        "{} {}...",
24        "→".if_supports_color(Stream::Stderr, |t| t.yellow()),
25        name
26    );
27}
28
29/// Print a stage completion message with green checkmark and duration.
30pub fn stage_success(message: &str, duration: Duration) {
31    let seconds = duration.as_secs_f64();
32    let suffix = format!("({}s)", format_duration(seconds));
33    info!(
34        "  {} {} {}",
35        "✓".if_supports_color(Stream::Stderr, |t| t.green()),
36        message,
37        suffix.if_supports_color(Stream::Stderr, |t| t.dimmed()),
38    );
39}
40
41/// Print a success message with green checkmark.
42pub fn success(message: &str) {
43    info!(
44        "  {} {}",
45        "✓".if_supports_color(Stream::Stderr, |t| t.green()),
46        message
47    );
48}
49
50/// Print a warning message with yellow exclamation symbol.
51pub fn warn(message: &str) {
52    info!(
53        "  {} {}",
54        "⚠".if_supports_color(Stream::Stderr, |t| t.yellow()),
55        message
56    );
57}
58
59/// Print an error message with red X symbol.
60pub fn error(message: &str) {
61    info!(
62        "  {} {}",
63        "✗".if_supports_color(Stream::Stderr, |t| t.red()),
64        message
65    );
66}
67
68/// Print a cargo-style action line: a 12-column right-aligned bold-green
69/// verb followed by `message`.
70pub fn action(verb: &str, message: &str) {
71    let label = format!("{:>12}", verb);
72    let style = Style::new().bright_green().bold();
73    info!(
74        "{} {}",
75        label.if_supports_color(Stream::Stderr, |t| style.style(t)),
76        message
77    );
78}
79
80/// Print a cargo-style "Finished" line for `action_name` after `duration`.
81///
82/// # Example
83/// ```ignore
84/// finished("compile", Duration::from_millis(80));
85/// // Output:     Finished compile in 0.08s
86/// ```
87pub fn finished(action_name: &str, duration: Duration) {
88    let seconds = duration.as_secs_f64();
89    action(
90        "Finished",
91        &format!("{} in {}s", action_name, format_duration(seconds)),
92    );
93}
94
95/// Format duration to show appropriate precision.
96/// - < 1s: show 2 decimal places
97/// - >= 1s: show 1 decimal place
98fn format_duration(seconds: f64) -> String {
99    if seconds < 1.0 {
100        format!("{:.2}", seconds)
101    } else {
102        format!("{:.1}", seconds)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[mz_ore::test]
111    fn test_format_duration() {
112        assert_eq!(format_duration(0.05), "0.05");
113        assert_eq!(format_duration(0.123), "0.12");
114        assert_eq!(format_duration(1.0), "1.0");
115        assert_eq!(format_duration(2.567), "2.6");
116        assert_eq!(format_duration(10.12), "10.1");
117    }
118}