bzr 0.3.0

A CLI for Bugzilla, inspired by gh
Documentation
use std::io::{self, Write as _};

use colored::Colorize;
use tabled::{Table, Tabled};

use super::formatting::{
    colorize_status, print_field, print_formatted, print_id_list_field, print_list_field,
    print_optional_field, shorten_email, truncate,
};
use crate::types::{Bug, HistoryEntry, OutputFormat};

#[derive(Tabled)]
struct BugRow {
    #[tabled(rename = "ID")]
    id: u64,
    #[tabled(rename = "STATUS")]
    status: String,
    #[tabled(rename = "PRIORITY")]
    priority: String,
    #[tabled(rename = "ASSIGNEE")]
    assignee: String,
    #[tabled(rename = "SUMMARY")]
    summary: String,
}

impl From<&Bug> for BugRow {
    fn from(b: &Bug) -> Self {
        let summary = truncate(&b.summary, 72);
        BugRow {
            id: b.id,
            status: b.status.clone(),
            priority: b.priority.clone().unwrap_or_default(),
            assignee: shorten_email(b.assigned_to.as_deref().unwrap_or("")),
            summary,
        }
    }
}

pub fn print_bugs(bugs: &[Bug], format: OutputFormat) {
    print_formatted(bugs, format, |bugs| {
        if bugs.is_empty() {
            let _ = writeln!(io::stdout(), "No bugs found.");
            return;
        }
        let rows: Vec<BugRow> = bugs.iter().map(BugRow::from).collect();
        let table = Table::new(rows).to_string();
        let _ = writeln!(io::stdout(), "{table}");
    });
}

pub fn print_bug_detail(bug: &Bug, format: OutputFormat) {
    print_formatted(bug, format, |bug| {
        let _ = writeln!(
            io::stdout(),
            "{} #{}\n{}\n",
            "Bug".bold(),
            bug.id.to_string().bold(),
            bug.summary.bold()
        );
        print_field("Status", &colorize_status(&bug.status));
        print_optional_field("Resolution", bug.resolution.as_deref());
        print_optional_field("Product", bug.product.as_deref());
        print_optional_field("Component", bug.component.as_deref());
        print_optional_field("Assignee", bug.assigned_to.as_deref());
        print_optional_field("Priority", bug.priority.as_deref());
        print_optional_field("Severity", bug.severity.as_deref());
        print_optional_field("Creator", bug.creator.as_deref());
        print_optional_field("Created", bug.creation_time.as_deref());
        print_optional_field("Updated", bug.last_change_time.as_deref());
        print_list_field("Keywords", &bug.keywords);
        print_id_list_field("Blocks", &bug.blocks);
        print_id_list_field("Depends on", &bug.depends_on);
    });
}

pub fn print_history(history: &[HistoryEntry], format: OutputFormat) {
    print_formatted(history, format, |history| {
        for entry in history {
            let _ = writeln!(
                io::stdout(),
                "{} by {} ({})",
                "Change".bold(),
                entry.who.cyan(),
                entry.when,
            );
            for change in &entry.changes {
                let attachment_suffix = change
                    .attachment_id
                    .map(|id| format!(" [attachment #{id}]"))
                    .unwrap_or_default();
                let _ = writeln!(
                    io::stdout(),
                    "  {}{attachment_suffix}:",
                    change.field_name.bold()
                );
                if !change.removed.is_empty() {
                    let _ = writeln!(io::stdout(), "    - {}", change.removed.red());
                }
                if !change.added.is_empty() {
                    let _ = writeln!(io::stdout(), "    + {}", change.added.green());
                }
            }
            let _ = writeln!(io::stdout(), "{}", "".repeat(60));
        }
    });
}

#[cfg(test)]
#[path = "bug_tests.rs"]
mod tests;