use crate::color::{ColorConfig, ColorScheme};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Output {
quiet: bool,
color: ColorConfig,
}
impl Output {
pub fn new(quiet: bool, color: ColorConfig) -> Self {
Self { quiet, color }
}
pub fn mkdir(&self, path: &std::path::Path, description: Option<&str>) {
if self.quiet {
return;
}
if self.color.is_enabled() {
match description {
Some(desc) => println!(
"{}: {} ({})",
ColorScheme::operation("Creating"),
ColorScheme::path(&path.display().to_string()),
desc
),
None => println!(
"{}: {}",
ColorScheme::operation("Creating"),
ColorScheme::path(&path.display().to_string())
),
}
} else {
match description {
Some(desc) => println!("Creating: {} ({desc})", path.display()),
None => println!("Creating: {}", path.display()),
}
}
}
fn print_file_op(
&self,
op: &str,
source: &std::path::Path,
target: &std::path::Path,
description: Option<&str>,
) {
if self.quiet {
return;
}
let source_str = source.file_name().unwrap_or_default().to_string_lossy();
let target_str = target.file_name().unwrap_or_default().to_string_lossy();
if self.color.is_enabled() {
if source_str == target_str {
match description {
Some(desc) => println!(
"{}: {} ({})",
ColorScheme::operation(op),
ColorScheme::path(&source_str),
desc
),
None => println!(
"{}: {}",
ColorScheme::operation(op),
ColorScheme::path(&source_str)
),
}
} else {
match description {
Some(desc) => println!(
"{}: {} → {} ({})",
ColorScheme::operation(op),
ColorScheme::path(&source_str),
ColorScheme::path(&target_str),
desc
),
None => println!(
"{}: {} → {}",
ColorScheme::operation(op),
ColorScheme::path(&source_str),
ColorScheme::path(&target_str)
),
}
}
} else {
let base = if source_str == target_str {
format!("{op}: {source_str}")
} else {
format!("{op}: {source_str} → {target_str}")
};
match description {
Some(desc) => println!("{base} ({desc})"),
None => println!("{base}"),
}
}
}
pub fn link(
&self,
source: &std::path::Path,
target: &std::path::Path,
description: Option<&str>,
) {
self.print_file_op("Linking", source, target, description);
}
pub fn copy(
&self,
source: &std::path::Path,
target: &std::path::Path,
description: Option<&str>,
) {
self.print_file_op("Copying", source, target, description);
}
pub fn skip(&self, path: &std::path::Path) {
if !self.quiet {
if self.color.is_enabled() {
println!(
"{}: {} (conflict)",
ColorScheme::skip("Skipped"),
ColorScheme::path(&path.display().to_string())
);
} else {
println!("Skipped: {} (conflict)", path.display());
}
}
}
pub fn dry_run(&self, message: &str) {
if !self.quiet {
if self.color.is_enabled() {
println!("{}", ColorScheme::dimmed(&format!("[dry-run] {message}")));
} else {
println!("[dry-run] {message}");
}
}
}
pub fn remove(&self, path: &std::path::Path) {
if !self.quiet {
if self.color.is_enabled() {
println!(
"{}: {}",
ColorScheme::operation("Removed"),
ColorScheme::path(&path.display().to_string())
);
} else {
println!("Removed: {}", path.display());
}
}
}
pub fn safety_warning(&self, path: &std::path::Path, message: &str) {
if !self.quiet {
if self.color.is_enabled() {
println!(
"{}: {} - {}",
ColorScheme::warning("Warning"),
ColorScheme::path(&path.display().to_string()),
message
);
} else {
println!("Warning: {} - {}", path.display(), message);
}
}
}
pub fn list(&self, line: &str) {
if !self.quiet {
println!("{line}");
}
}
pub fn hook_running(
&self,
hook_type: &str,
index: usize,
total: usize,
command: &str,
description: Option<&str>,
) {
if !self.quiet {
let display_text = description.unwrap_or(command);
if self.color.is_enabled() {
println!(
"{} {} hook {}: {}",
ColorScheme::hook_running("Running"),
ColorScheme::hook_type(hook_type),
ColorScheme::dimmed(&format!("[{}/{}]", index, total)),
ColorScheme::dimmed(display_text)
);
} else {
println!(
"Running {} hook [{}/{}]: {}",
hook_type, index, total, display_text
);
}
}
}
pub fn hook_separator(&self) {
if !self.quiet {
println!();
}
}
pub fn hook_warning(&self, hook_type: &str, error: &str, exit_code: Option<i32>) {
if !self.quiet {
eprintln!(); if self.color.is_enabled() {
eprintln!(
"{}: {} hook failed: {}",
ColorScheme::warning("Warning"),
hook_type,
error
);
if let Some(code) = exit_code {
eprintln!(" {}: {}", ColorScheme::exit_code("Exit code"), code);
}
} else {
eprintln!("Warning: {} hook failed: {}", hook_type, error);
if let Some(code) = exit_code {
eprintln!(" Exit code: {}", code);
}
}
}
}
pub fn hook_note(&self, message: &str) {
if !self.quiet {
if self.color.is_enabled() {
println!("{}", ColorScheme::dimmed(message));
} else {
println!("{}", message);
}
}
}
pub fn results_success(&self, message: &str) {
if !self.quiet {
if self.color.is_enabled() {
println!("{} {}", ColorScheme::success_label("[OK]"), message);
} else {
println!("[OK] {}", message);
}
}
}
pub fn results_header(&self) {
if !self.quiet {
println!("Results:");
}
}
pub fn results_item_success(&self, message: &str) {
if !self.quiet {
if self.color.is_enabled() {
println!(" {} {}", ColorScheme::success_label("[OK]"), message);
} else {
println!(" [OK] {}", message);
}
}
}
pub fn results_item_failed(&self, message: &str) {
if !self.quiet {
if self.color.is_enabled() {
println!(" {} {}", ColorScheme::failure_label("[FAIL]"), message);
} else {
println!(" [FAIL] {}", message);
}
}
}
pub fn results_failed_detail(
&self,
description: Option<&str>,
command: &str,
exit_code: Option<i32>,
) {
if !self.quiet {
let display = description.unwrap_or(command);
println!(" - {}", display);
if self.color.is_enabled() {
println!(" {}: {}", ColorScheme::dimmed("Command"), command);
if let Some(code) = exit_code {
println!(" {}: {}", ColorScheme::exit_code("Exit code"), code);
}
} else {
println!(" Command: {}", command);
if let Some(code) = exit_code {
println!(" Exit code: {}", code);
}
}
}
}
}