stax 0.50.2

Fast stacked Git branches and PRs
Documentation
use crate::commands::github_list::{
    format_relative_time, print_table, split_flexible_width, terminal_width, CellTone, TableCell,
    TableColumn, TruncationMode,
};
use crate::config::Config;
use crate::forge::{ForgeClient, RepoIssueListItem};
use crate::git::GitRepo;
use crate::remote::RemoteInfo;
use anyhow::Result;

const TITLE_MIN_WIDTH: usize = 24;
const LABELS_MIN_WIDTH: usize = 12;
const LABELS_MAX_WIDTH: usize = 24;

pub fn run_list(limit: u8, json: bool) -> Result<()> {
    let repo = GitRepo::open()?;
    let config = Config::load()?;
    let remote_info = RemoteInfo::from_repo(&repo, &config)?;
    let repo_label = format!("{}/{}", remote_info.namespace, remote_info.repo);

    let rt = tokio::runtime::Runtime::new()?;
    let issues = rt.block_on(async {
        let client = ForgeClient::new(&remote_info)?;
        client.list_open_issues(limit).await
    })?;

    if json {
        println!("{}", serde_json::to_string_pretty(&issues)?);
        return Ok(());
    }

    print_issue_table(&repo_label, &issues);
    Ok(())
}

fn print_issue_table(repo_label: &str, issues: &[RepoIssueListItem]) {
    let updated_strings: Vec<String> = issues
        .iter()
        .map(|issue| format_relative_time(issue.updated_at))
        .collect();
    let label_strings: Vec<String> = issues
        .iter()
        .map(|issue| {
            if issue.labels.is_empty() {
                "".to_string()
            } else {
                issue.labels.join(", ")
            }
        })
        .collect();

    let id_width = issues
        .iter()
        .map(|issue| format!("#{}", issue.number).len())
        .max()
        .unwrap_or(2)
        .max("ID".len());
    let updated_width = updated_strings
        .iter()
        .map(|value| value.len())
        .max()
        .unwrap_or("UPDATED".len())
        .max("UPDATED".len());
    let label_pref = label_strings
        .iter()
        .map(|value| value.len())
        .max()
        .unwrap_or("LABELS".len())
        .clamp(LABELS_MIN_WIDTH, LABELS_MAX_WIDTH);

    let width = terminal_width().max(72);
    let fixed_width = id_width + updated_width + 6;
    let flex_width = width.saturating_sub(fixed_width);
    let (title_width, labels_width) = split_flexible_width(
        flex_width,
        TITLE_MIN_WIDTH,
        label_pref,
        LABELS_MIN_WIDTH,
        LABELS_MAX_WIDTH,
    );

    let columns = vec![
        TableColumn {
            header: "ID",
            width: id_width,
        },
        TableColumn {
            header: "TITLE",
            width: title_width,
        },
        TableColumn {
            header: "LABELS",
            width: labels_width,
        },
        TableColumn {
            header: "UPDATED",
            width: updated_width,
        },
    ];

    let rows = issues
        .iter()
        .zip(label_strings.iter())
        .zip(updated_strings.iter())
        .map(|((issue, labels), updated)| {
            vec![
                TableCell {
                    text: format!("#{}", issue.number),
                    tone: CellTone::Id,
                    truncation: TruncationMode::None,
                },
                TableCell {
                    text: issue.title.clone(),
                    tone: CellTone::Default,
                    truncation: TruncationMode::End,
                },
                TableCell {
                    text: labels.clone(),
                    tone: if issue.labels.is_empty() {
                        CellTone::Secondary
                    } else {
                        CellTone::Label
                    },
                    truncation: TruncationMode::End,
                },
                TableCell {
                    text: updated.clone(),
                    tone: CellTone::Secondary,
                    truncation: TruncationMode::None,
                },
            ]
        })
        .collect::<Vec<_>>();

    print_table(
        repo_label,
        &format!("{} open issues", issues.len()),
        "No open issues.",
        &columns,
        &rows,
    );
}