use crate::diagnostic::DOC_URL;
use crate::diagnostic::output::Snippet;
use crate::diagnostic::output::diagnostic::{render_error, theme};
use crate::{
diagnostic::{EnkryptitOutput, output::kind::EnkryptitOutputKind},
errors::EnkryptitError,
};
use colored::Colorize;
use miette::GraphicalReportHandler;
use miette::MietteDiagnostic;
use terminal_size::{Width as TermWidth, terminal_size};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
impl EnkryptitOutput {
pub fn display(self) {
match self.kind {
EnkryptitOutputKind::Info => display_info(&self.msg),
EnkryptitOutputKind::Phantom => (),
EnkryptitOutputKind::Error {
error,
location,
help,
snippet,
} => display_error(&self.msg, &error, &location, &help, &snippet),
EnkryptitOutputKind::Success => display_success(&self.msg),
EnkryptitOutputKind::Warning => display_warning(&self.msg),
};
}
}
pub fn display_error(
msg: &str,
error: &EnkryptitError,
location: &Option<String>,
help: &Option<String>,
snippet: &Option<Snippet>,
) {
tracing::error!("{}", error);
println!("{}", render_error(msg, error, location, help, snippet));
}
pub fn render_warning(msg: &str) -> String {
let report = MietteDiagnostic::new(msg)
.with_url(DOC_URL)
.with_severity(miette::Severity::Warning);
let mut output = String::new();
GraphicalReportHandler::new_themed(theme())
.render_report(&mut output, &report)
.expect("rendering a miette report into String shouldn't fail");
output
}
fn display_warning(msg: &str) {
tracing::warn!("{}", msg);
println!("{}", render_warning(msg));
}
const SUCCESS_BOX_MIN_WIDTH: usize = 60;
#[doc(hidden)]
pub const SUCCESS_BOX_PADDING: usize = 2;
#[doc(hidden)]
pub const SUCCESS_BADGE_WIDTH: usize = 3;
const DEFAULT_TERMINAL_WIDTH: usize = 80;
fn display_success(msg: &str) {
tracing::info!("Success : {}", msg);
let box_width = choose_box_width(msg);
let message_width = success_message_width(box_width);
let mut lines = wrap_text(msg, message_width);
if lines.is_empty() {
lines.push(String::new());
}
let horizontal = "─".repeat(box_width - 2);
println!();
println!("{}", format!("╭{horizontal}╮").green());
for (i, line) in lines.iter().enumerate() {
let badge = if i == 0 {
format!("{} ", "✔".green().bold())
} else {
" ".repeat(SUCCESS_BADGE_WIDTH)
};
let text = pad_to(line, message_width);
println!(
"{}{}{}{}{}",
"│".green(),
" ".repeat(SUCCESS_BOX_PADDING),
badge,
text,
"│".green(),
);
}
println!("{}", format!("╰{horizontal}╯").green());
}
#[doc(hidden)]
pub fn success_message_width(box_width: usize) -> usize {
box_width - SUCCESS_BOX_PADDING - SUCCESS_BADGE_WIDTH - 2
}
fn choose_box_width(msg: &str) -> usize {
let min = SUCCESS_BOX_MIN_WIDTH;
let content = msg.width() + SUCCESS_BOX_PADDING * 2 + SUCCESS_BADGE_WIDTH + 2;
let term = terminal_width().saturating_sub(1);
content.max(min).min(term.max(min))
}
fn terminal_width() -> usize {
terminal_size()
.map(|(TermWidth(width), _)| usize::from(width))
.unwrap_or(DEFAULT_TERMINAL_WIDTH)
}
fn display_info(msg: &str) {
tracing::info!("{}", msg);
println!("\n{} {}", "[INFO]".cyan().bold(), msg);
}
#[doc(hidden)]
pub fn wrap_text(msg: &str, width: usize) -> Vec<String> {
let mut wrapped = Vec::new();
let mut line = String::new();
for paragraph in msg.split('\n') {
for word in paragraph.split_whitespace() {
if word.width() > width {
if !line.is_empty() {
wrapped.push(std::mem::take(&mut line));
}
wrapped.extend(split_word(word, width));
continue;
}
if line.is_empty() {
line.push_str(word);
} else if line.width() + 1 + word.width() <= width {
line.push(' ');
line.push_str(word);
} else {
wrapped.push(std::mem::take(&mut line));
line.push_str(word);
}
}
if !line.is_empty() {
wrapped.push(std::mem::take(&mut line));
}
}
wrapped
}
fn split_word(word: &str, width: usize) -> Vec<String> {
let mut chunks = Vec::new();
let mut chunk = String::new();
for grapheme in word.graphemes(true) {
let grapheme_width = grapheme.width().max(1);
if !chunk.is_empty() && chunk.width() + grapheme_width > width {
chunks.push(std::mem::take(&mut chunk));
}
chunk.push_str(grapheme);
}
if !chunk.is_empty() {
chunks.push(chunk);
}
chunks
}
#[doc(hidden)]
pub fn pad_to(text: &str, width: usize) -> String {
let extra = width.saturating_sub(text.width());
format!("{text}{}", " ".repeat(extra))
}