a73x

e81d4628

Implement Display for IssueStatus and PatchStatus enums

a73x   2026-03-22 08:23

Add as_str() and Display impls so the status-to-string mapping lives in
one place. Replace 14 scattered match blocks across 5 files.

Closes issue cd1549f2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

diff --git a/src/issue.rs b/src/issue.rs
index b732f7f..05958ab 100644
--- a/src/issue.rs
+++ b/src/issue.rs
@@ -87,10 +87,7 @@ pub fn list_to_writer(
    }
    for e in &entries {
        let i = &e.issue;
        let status = match i.status {
            IssueStatus::Open => "open",
            IssueStatus::Closed => "closed",
        };
        let status = i.status.as_str();
        let labels = if i.labels.is_empty() {
            String::new()
        } else {
diff --git a/src/lib.rs b/src/lib.rs
index 2d11ea4..21c5b6b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,7 +20,7 @@ use base64::Engine;
use cli::{Commands, IdentityCmd, IssueCmd, KeyCmd, PatchCmd};
use event::ReviewVerdict;
use git2::Repository;
use state::{IssueStatus, PatchStatus};


/// Check if the reviewer's base ref has moved ahead of the patch's latest revision.
pub fn staleness_warning(repo: &Repository, patch: &state::PatchState) -> Option<String> {
@@ -64,10 +64,7 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> {
                } else {
                    for e in &entries {
                        let i = &e.issue;
                        let status = match i.status {
                            IssueStatus::Open => "open",
                            IssueStatus::Closed => "closed",
                        };
                        let status = i.status.as_str();
                        let labels = if i.labels.is_empty() {
                            String::new()
                        } else {
@@ -92,11 +89,7 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> {
                    return Ok(());
                }
                let i = issue::show(repo, &id)?;
                let status = match i.status {
                    IssueStatus::Open => "open",
                    IssueStatus::Closed => "closed",
                };
                println!("Issue {} [{}]", &i.id[..8], status);
                println!("Issue {} [{}]", &i.id[..8], i.status);
                println!("Title: {}", i.title);
                println!("Author: {} <{}>", i.author.name, i.author.email);
                println!("Created: {}", i.created_at);
@@ -217,14 +210,9 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> {
                    println!("No patches found.");
                } else {
                    for p in &patches {
                        let status = match p.status {
                            PatchStatus::Open => "open",
                            PatchStatus::Closed => "closed",
                            PatchStatus::Merged => "merged",
                        };
                        println!(
                            "{:.8}  {:6}  {}  (by {})",
                            p.id, status, p.title, p.author.name
                            p.id, p.status, p.title, p.author.name
                        );
                    }
                }
@@ -237,13 +225,8 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> {
                    return Ok(());
                }
                let p = patch::show(repo, &id)?;
                let status = match p.status {
                    PatchStatus::Open => "open",
                    PatchStatus::Closed => "closed",
                    PatchStatus::Merged => "merged",
                };
                let rev_count = p.revisions.len();
                println!("Patch {} [{}] (r{})", &p.id[..8], status, rev_count);
                println!("Patch {} [{}] (r{})", &p.id[..8], p.status, rev_count);
                println!("Title: {}", p.title);
                println!("Author: {} <{}>", p.author.name, p.author.email);
                match p.resolve_head(repo) {
diff --git a/src/patch.rs b/src/patch.rs
index 1b545cc..a97ad33 100644
--- a/src/patch.rs
+++ b/src/patch.rs
@@ -173,15 +173,10 @@ pub fn list_to_writer(
        return Ok(());
    }
    for p in &patches {
        let status = match p.status {
            PatchStatus::Open => "open",
            PatchStatus::Closed => "closed",
            PatchStatus::Merged => "merged",
        };
        writeln!(
            writer,
            "{:.8}  {:6}  {}  (by {})",
            p.id, status, p.title, p.author.name
            p.id, p.status, p.title, p.author.name
        )
        .ok();
    }
@@ -385,7 +380,7 @@ pub fn merge(repo: &Repository, id_prefix: &str) -> Result<PatchState, crate::er

    if p.status != PatchStatus::Open {
        return Err(git2::Error::from_str(&format!(
            "patch is {:?}, can only merge open patches",
            "patch is {}, can only merge open patches",
            p.status
        ))
        .into());
diff --git a/src/state.rs b/src/state.rs
index 205e99f..f59f179 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -1,3 +1,5 @@
use std::fmt;

use git2::{Oid, Repository};
use serde::{Deserialize, Serialize};

@@ -60,6 +62,21 @@ pub enum IssueStatus {
    Closed,
}

impl IssueStatus {
    pub fn as_str(&self) -> &'static str {
        match self {
            IssueStatus::Open => "open",
            IssueStatus::Closed => "closed",
        }
    }
}

impl fmt::Display for IssueStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct Comment {
@@ -113,6 +130,22 @@ pub enum PatchStatus {
    Merged,
}

impl PatchStatus {
    pub fn as_str(&self) -> &'static str {
        match self {
            PatchStatus::Open => "open",
            PatchStatus::Closed => "closed",
            PatchStatus::Merged => "merged",
        }
    }
}

impl fmt::Display for PatchStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Revision {
    pub number: u32,
diff --git a/src/status.rs b/src/status.rs
index 022226d..14b0962 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -80,10 +80,7 @@ pub fn compute(repo: &Repository) -> Result<ProjectStatus, Error> {
            kind: "issue",
            id: issue.id[..8.min(issue.id.len())].to_string(),
            title: issue.title.clone(),
            status: match issue.status {
                IssueStatus::Open => "open".to_string(),
                IssueStatus::Closed => "closed".to_string(),
            },
            status: issue.status.to_string(),
            created_at: issue.created_at.clone(),
        });
    }
@@ -92,11 +89,7 @@ pub fn compute(repo: &Repository) -> Result<ProjectStatus, Error> {
            kind: "patch",
            id: patch.id[..8.min(patch.id.len())].to_string(),
            title: patch.title.clone(),
            status: match patch.status {
                PatchStatus::Open => "open".to_string(),
                PatchStatus::Closed => "closed".to_string(),
                PatchStatus::Merged => "merged".to_string(),
            },
            status: patch.status.to_string(),
            created_at: patch.created_at.clone(),
        });
    }
diff --git a/src/tui/widgets.rs b/src/tui/widgets.rs
index 096c80c..4bf517f 100644
--- a/src/tui/widgets.rs
+++ b/src/tui/widgets.rs
@@ -152,10 +152,7 @@ fn render_list(frame: &mut Frame, app: &mut App, area: Rect) {
            let items: Vec<ListItem> = visible
                .iter()
                .map(|i| {
                    let status = match i.status {
                        IssueStatus::Open => "open",
                        IssueStatus::Closed => "closed",
                    };
                    let status = i.status.as_str();
                    let style = match i.status {
                        IssueStatus::Open => Style::default().fg(Color::Green),
                        IssueStatus::Closed => Style::default().fg(Color::Red),
@@ -187,11 +184,7 @@ fn render_list(frame: &mut Frame, app: &mut App, area: Rect) {
            let items: Vec<ListItem> = visible
                .iter()
                .map(|p| {
                    let status = match p.status {
                        PatchStatus::Open => "open",
                        PatchStatus::Closed => "closed",
                        PatchStatus::Merged => "merged",
                    };
                    let status = p.status.as_str();
                    let style = match p.status {
                        PatchStatus::Open => Style::default().fg(Color::Green),
                        PatchStatus::Closed => Style::default().fg(Color::Red),
@@ -346,10 +339,7 @@ fn render_detail(frame: &mut Frame, app: &mut App, area: Rect) {
}

fn build_issue_detail(issue: &IssueState, patches: &[PatchState]) -> Text<'static> {
    let status = match issue.status {
        IssueStatus::Open => "open",
        IssueStatus::Closed => "closed",
    };
    let status = issue.status.as_str();

    let mut lines: Vec<Line> = vec![
        Line::from(vec![
@@ -475,11 +465,7 @@ fn build_issue_detail(issue: &IssueState, patches: &[PatchState]) -> Text<'stati
}

fn build_patch_summary(patch: &PatchState) -> Text<'static> {
    let status_str = match patch.status {
        PatchStatus::Open => "open",
        PatchStatus::Closed => "closed",
        PatchStatus::Merged => "merged",
    };
    let status_str = patch.status.as_str();
    let status_color = match patch.status {
        PatchStatus::Open => Color::Green,
        PatchStatus::Closed => Color::Red,
@@ -549,11 +535,7 @@ fn build_patch_detail_text(app: &App) -> Text<'static> {
        None => return Text::raw("No patch loaded."),
    };

    let status_str = match patch.status {
        PatchStatus::Open => "open",
        PatchStatus::Closed => "closed",
        PatchStatus::Merged => "merged",
    };
    let status_str = patch.status.as_str();
    let status_color = match patch.status {
        PatchStatus::Open => Color::Green,
        PatchStatus::Closed => Color::Red,