#![allow(dead_code)]
use console::{Style, Term};
use serde::Serialize;
use std::io::IsTerminal;
use std::sync::atomic::{AtomicBool, Ordering};
static JSON_MODE: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Serialize)]
pub struct JsonResponse<T: Serialize> {
pub success: bool,
pub command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl<T: Serialize> JsonResponse<T> {
pub fn success(command: impl Into<String>, data: T) -> Self {
Self {
success: true,
command: command.into(),
data: Some(data),
error: None,
}
}
pub fn error(command: impl Into<String>, error: impl Into<String>) -> JsonResponse<()> {
JsonResponse {
success: false,
command: command.into(),
data: None,
error: Some(error.into()),
}
}
pub fn print(&self) -> Result<(), serde_json::Error> {
println!("{}", serde_json::to_string_pretty(self)?);
Ok(())
}
}
pub fn is_json_mode() -> bool {
JSON_MODE.load(Ordering::Relaxed)
}
pub struct Output {
term: Term,
colors_enabled: bool,
success_style: Style,
error_style: Style,
warn_style: Style,
info_style: Style,
bold_style: Style,
dim_style: Style,
}
impl Default for Output {
fn default() -> Self {
Self::new()
}
}
impl Output {
pub fn new() -> Self {
let term = Term::stderr();
let colors_enabled = Self::should_use_colors(&term);
Self {
term,
colors_enabled,
success_style: Style::new().green(),
error_style: Style::new().red(),
warn_style: Style::new().yellow(),
info_style: Style::new().cyan(),
bold_style: Style::new().bold(),
dim_style: Style::new().dim(),
}
}
pub fn stdout() -> Self {
let term = Term::stdout();
let colors_enabled = Self::should_use_colors(&term);
Self {
term,
colors_enabled,
success_style: Style::new().green(),
error_style: Style::new().red(),
warn_style: Style::new().yellow(),
info_style: Style::new().cyan(),
bold_style: Style::new().bold(),
dim_style: Style::new().dim(),
}
}
fn should_use_colors(term: &Term) -> bool {
if JSON_MODE.load(Ordering::Relaxed) {
return false;
}
if std::env::var("NO_COLOR").is_ok() {
return false;
}
if !term.is_term() {
return false;
}
if !std::io::stderr().is_terminal() {
return false;
}
true
}
pub fn success(&self, text: &str) -> String {
if self.colors_enabled {
self.success_style.apply_to(text).to_string()
} else {
text.to_string()
}
}
pub fn error(&self, text: &str) -> String {
if self.colors_enabled {
self.error_style.apply_to(text).to_string()
} else {
text.to_string()
}
}
pub fn warn(&self, text: &str) -> String {
if self.colors_enabled {
self.warn_style.apply_to(text).to_string()
} else {
text.to_string()
}
}
pub fn info(&self, text: &str) -> String {
if self.colors_enabled {
self.info_style.apply_to(text).to_string()
} else {
text.to_string()
}
}
pub fn bold(&self, text: &str) -> String {
if self.colors_enabled {
self.bold_style.apply_to(text).to_string()
} else {
text.to_string()
}
}
pub fn dim(&self, text: &str) -> String {
if self.colors_enabled {
self.dim_style.apply_to(text).to_string()
} else {
text.to_string()
}
}
pub fn print_success(&self, message: &str) {
let icon = if self.colors_enabled {
self.success_style.apply_to("\u{2713}").to_string()
} else {
"[OK]".to_string()
};
eprintln!("{} {}", icon, message);
}
pub fn print_error(&self, message: &str) {
let icon = if self.colors_enabled {
self.error_style.apply_to("\u{2717}").to_string()
} else {
"[ERROR]".to_string()
};
eprintln!("{} {}", icon, message);
}
pub fn print_warn(&self, message: &str) {
let icon = if self.colors_enabled {
self.warn_style.apply_to("!").to_string()
} else {
"[WARN]".to_string()
};
eprintln!("{} {}", icon, message);
}
pub fn print_info(&self, message: &str) {
let icon = if self.colors_enabled {
self.info_style.apply_to("i").to_string()
} else {
"[INFO]".to_string()
};
eprintln!("{} {}", icon, message);
}
pub fn print_heading(&self, text: &str) {
let styled = if self.colors_enabled {
self.bold_style.apply_to(text).to_string()
} else {
text.to_string()
};
eprintln!("{}", styled);
}
pub fn println(&self, text: &str) {
eprintln!("{}", text);
}
pub fn newline(&self) {
eprintln!();
}
pub fn key_value(&self, key: &str, value: &str) -> String {
if self.colors_enabled {
format!(
"{}: {}",
self.dim_style.apply_to(key),
self.info_style.apply_to(value)
)
} else {
format!("{}: {}", key, value)
}
}
pub fn status(&self, passed: bool) -> &'static str {
if passed {
if self.colors_enabled {
"\u{2713}"
} else {
"[PASS]"
}
} else if self.colors_enabled {
"\u{2717}"
} else {
"[FAIL]"
}
}
}
pub fn set_json_mode(enabled: bool) {
JSON_MODE.store(enabled, Ordering::Relaxed);
}
#[cfg(test)]
mod tests {
use super::*;
impl Output {
fn new_without_colors() -> Self {
let term = Term::stderr();
Self {
term,
colors_enabled: false,
success_style: Style::new().green(),
error_style: Style::new().red(),
warn_style: Style::new().yellow(),
info_style: Style::new().cyan(),
bold_style: Style::new().bold(),
dim_style: Style::new().dim(),
}
}
}
#[test]
fn test_output_no_colors_in_test() {
let output = Output::new();
let success = output.success("test");
assert!(success.contains("test"));
}
#[test]
fn test_json_mode() {
let output = Output::new_without_colors();
let styled = output.success("test");
assert_eq!(styled, "test");
}
#[test]
fn test_key_value_format() {
let output = Output::new_without_colors();
let kv = output.key_value("name", "value");
assert_eq!(kv, "name: value");
}
}