bzr 0.4.1

A CLI for Bugzilla, inspired by gh
Documentation
use std::collections::HashMap;
use std::io::Write;

use crate::types::{OutputFormat, QueryKind, SavedQuery};

use crate::output::formatting::{
    write_field, write_formatted, write_json, write_list_field, write_optional_field,
};

fn kind_label(kind: &QueryKind) -> &'static str {
    match kind {
        QueryKind::List => "list",
        QueryKind::Search => "search",
        QueryKind::Url => "url",
    }
}

fn query_saved_message(name: &str, verb: &str) -> String {
    format!("{verb} query '{name}'")
}

fn query_summary_line(name: &str, q: &SavedQuery) -> String {
    let mut parts = vec![format!("kind={}", kind_label(&q.kind))];
    if !q.product.is_empty() {
        parts.push(format!("product={}", q.product.join(",")));
    }
    if !q.status.is_empty() {
        parts.push(format!("status={}", q.status.join(",")));
    }
    if let Some(qs) = &q.quicksearch {
        parts.push(format!("search=\"{qs}\""));
    }
    if let Some(ct) = &q.creation_time {
        parts.push(format!("created>={ct}"));
    }
    if let Some(lct) = &q.last_change_time {
        parts.push(format!("changed>={lct}"));
    }
    if let Some(limit) = q.limit {
        parts.push(format!("limit={limit}"));
    }
    if !q.raw_params.is_empty() {
        parts.push(format!("{} raw params", q.raw_params.len()));
    }
    format!("{name} ({})", parts.join(", "))
}

pub fn write_query_saved<W: Write + ?Sized>(
    name: &str,
    verb: &str,
    format: OutputFormat,
    out: &mut W,
) {
    match format {
        OutputFormat::Json => {
            write_json(
                &serde_json::json!({"name": name, "action": verb.to_lowercase()}),
                out,
            );
        }
        OutputFormat::Table => {
            let _ = writeln!(out, "{}", query_saved_message(name, verb));
        }
    }
}

pub fn write_query_list<W: Write + ?Sized, S: ::std::hash::BuildHasher>(
    queries: &HashMap<String, SavedQuery, S>,
    format: OutputFormat,
    out: &mut W,
) {
    write_formatted(queries, format, out, |queries, out| {
        if queries.is_empty() {
            let _ = writeln!(out, "No saved queries configured.");
            return;
        }
        let mut names: Vec<&str> = queries.keys().map(String::as_str).collect();
        names.sort_unstable();
        for name in names {
            let _ = writeln!(out, "{}", query_summary_line(name, &queries[name]));
        }
    });
}

pub fn write_query_detail<W: Write + ?Sized>(
    name: &str,
    query: &SavedQuery,
    format: OutputFormat,
    out: &mut W,
) {
    #[derive(serde::Serialize)]
    struct QueryView<'a> {
        name: &'a str,
        #[serde(flatten)]
        query: &'a SavedQuery,
    }

    let view = QueryView { name, query };
    write_formatted(&view, format, out, |view, out| {
        write_field(out, "Name", view.name);
        write_field(out, "Kind", kind_label(&view.query.kind));
        write_optional_field(out, "Source URL", view.query.source_url.as_deref());
        write_optional_field(out, "Server", view.query.server.as_deref());
        write_list_field(out, "Product", &view.query.product);
        write_list_field(out, "Component", &view.query.component);
        write_list_field(out, "Status", &view.query.status);
        write_list_field(out, "Assignee", &view.query.assignee);
        write_list_field(out, "Creator", &view.query.creator);
        write_list_field(out, "Priority", &view.query.priority);
        write_list_field(out, "Severity", &view.query.severity);
        write_optional_field(out, "Search", view.query.quicksearch.as_deref());
        if let Some(limit) = view.query.limit {
            write_field(out, "Limit", &limit.to_string());
        }
        write_optional_field(out, "Fields", view.query.fields.as_deref());
        write_optional_field(out, "Exclude", view.query.exclude_fields.as_deref());
        write_optional_field(out, "Created since", view.query.creation_time.as_deref());
        write_optional_field(out, "Changed since", view.query.last_change_time.as_deref());
        write_list_field(out, "Whiteboard", &view.query.whiteboard);
        write_list_field(out, "Target Milestone", &view.query.target_milestone);
        write_list_field(out, "Version", &view.query.version);
        write_list_field(out, "OS", &view.query.op_sys);
        write_list_field(out, "Platform", &view.query.platform);
        write_list_field(out, "Resolution", &view.query.resolution);
        write_list_field(out, "QA Contact", &view.query.qa_contact);
        write_list_field(out, "URL", &view.query.url);
        if !view.query.raw_params.is_empty() {
            write_field(out, "Raw params", &view.query.raw_params.len().to_string());
        }
    });
}

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