Skip to main content

bel7_cli/
output.rs

1// Copyright (C) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Colored console output utilities.
16//!
17//! Provides consistent, colored output for CLI applications.
18//! Respects the `NO_COLOR` environment variable and detects non-TTY output.
19
20use std::env;
21use std::fmt::Display;
22use std::io::IsTerminal;
23
24use owo_colors::OwoColorize;
25
26/// Returns whether colored output should be used.
27///
28/// Returns `false` if the `NO_COLOR` environment variable is set (any value)
29/// or `stdout` is not a terminal (that is, piped or redirected).
30///
31/// This follows the [NO_COLOR standard](https://no-color.org/).
32#[must_use]
33pub fn should_colorize() -> bool {
34    env::var("NO_COLOR").is_err() && std::io::stdout().is_terminal()
35}
36
37/// Returns whether colored output should be used for stderr.
38///
39/// Returns `false` if the `NO_COLOR` environment variable is set (any value)
40/// or `stdout` is not a terminal (that is, piped or redirected).
41#[must_use]
42pub fn should_colorize_stderr() -> bool {
43    env::var("NO_COLOR").is_err() && std::io::stderr().is_terminal()
44}
45
46/// Prints a success message with a green checkmark prefix.
47///
48/// Respects `NO_COLOR` and terminal detection.
49pub fn print_success(message: impl Display) {
50    if should_colorize() {
51        println!("{} {}", "✓".green().bold(), message);
52    } else {
53        println!("✓ {}", message);
54    }
55}
56
57/// Prints an error message to stderr with a red X prefix.
58///
59/// Respects `NO_COLOR` and terminal detection.
60pub fn print_error(message: impl Display) {
61    if should_colorize_stderr() {
62        eprintln!("{} {}", "✗".red().bold(), message);
63    } else {
64        eprintln!("✗ {}", message);
65    }
66}
67
68/// Prints a warning message with a yellow exclamation prefix.
69///
70/// Respects `NO_COLOR` and terminal detection.
71pub fn print_warning(message: impl Display) {
72    if should_colorize() {
73        println!("{} {}", "!".yellow().bold(), message);
74    } else {
75        println!("! {}", message);
76    }
77}
78
79/// Prints an info message with a blue arrow prefix.
80///
81/// Respects `NO_COLOR` and terminal detection.
82pub fn print_info(message: impl Display) {
83    if should_colorize() {
84        println!("{} {}", "→".blue().bold(), message);
85    } else {
86        println!("→ {}", message);
87    }
88}
89
90/// Prints a dimmed/muted message.
91///
92/// Respects `NO_COLOR` and terminal detection.
93pub fn print_dimmed(message: impl Display) {
94    if should_colorize() {
95        println!("{}", message.to_string().dimmed());
96    } else {
97        println!("{}", message);
98    }
99}
100
101/// Formats a value as success (green) if colors are enabled.
102#[must_use]
103pub fn format_success<T: Display>(value: T) -> String {
104    if should_colorize() {
105        format!("{}", value.green())
106    } else {
107        value.to_string()
108    }
109}
110
111/// Formats a value as error (red) if colors are enabled.
112#[must_use]
113pub fn format_error<T: Display>(value: T) -> String {
114    if should_colorize() {
115        format!("{}", value.red())
116    } else {
117        value.to_string()
118    }
119}
120
121/// Formats a value as warning (yellow) if colors are enabled.
122#[must_use]
123pub fn format_warning<T: Display>(value: T) -> String {
124    if should_colorize() {
125        format!("{}", value.yellow())
126    } else {
127        value.to_string()
128    }
129}
130
131/// Formats a value as info (blue) if colors are enabled.
132#[must_use]
133pub fn format_info<T: Display>(value: T) -> String {
134    if should_colorize() {
135        format!("{}", value.blue())
136    } else {
137        value.to_string()
138    }
139}
140
141/// Formats a value as dimmed/muted if colors are enabled.
142#[must_use]
143pub fn format_dimmed<T: Display>(value: T) -> String {
144    if should_colorize() {
145        format!("{}", value.dimmed())
146    } else {
147        value.to_string()
148    }
149}
150
151/// Formats a value as bold if colors are enabled.
152#[must_use]
153pub fn format_bold<T: Display>(value: T) -> String {
154    if should_colorize() {
155        format!("{}", value.bold())
156    } else {
157        value.to_string()
158    }
159}