use gotcha_gitea::models::{ ChangedFile, Comment, Commit, Issue, Milestone, PullRequest, PullReview, }; use gotcha_gitea::pull_state; use crate::print_table; pub(super) fn print_issues(issues: &[Issue]) { print_table( &[ ("INDEX", 8), ("STATE", 10), ("TITLE", 60), ("MILESTONE", 30), ("LABELS", 30), ], issues .iter() .map(|issue| { vec![ issue.number.unwrap_or_default().to_string(), issue.state.as_deref().unwrap_or("unknown").into(), one_line(issue.title.as_deref().unwrap_or("")).into(), issue .milestone .as_deref() .and_then(|milestone| milestone.title.as_deref()) .unwrap_or("-") .into(), issue_labels(issue), ] }) .collect(), ); } pub(super) fn print_issue(issue: &Issue) { println!( "#{} [{}] {}", issue.number.unwrap_or_default(), issue.state.as_deref().unwrap_or("unknown"), issue.title.as_deref().unwrap_or("") ); field("url", issue.html_url.as_deref()); field( "author", issue.user.as_deref().and_then(|user| user.login.as_deref()), ); let milestone = issue .milestone .as_deref() .and_then(|milestone| milestone.title.as_deref()); field("milestone", milestone); let labels = issue_labels(issue); field("labels", (!labels.is_empty()).then_some(labels.as_str())); let assignees = issue .assignees .as_deref() .unwrap_or_default() .iter() .filter_map(|user| user.login.as_deref()) .collect::>() .join(","); field( "assignees", (!assignees.is_empty()).then_some(assignees.as_str()), ); field("due", issue.due_date.as_deref()); field("created", issue.created_at.as_deref()); field("updated", issue.updated_at.as_deref()); println!("comments: {}", issue.comments.unwrap_or_default()); body(issue.body.as_deref()); } fn issue_labels(issue: &Issue) -> String { issue .labels .as_deref() .unwrap_or_default() .iter() .filter_map(|label| label.name.as_deref()) .collect::>() .join(",") } pub(super) fn print_comments(comments: &[Comment]) { for comment in comments { println!( "{} ยท {}", comment .user .as_deref() .and_then(|user| user.login.as_deref()) .unwrap_or("unknown"), comment.created_at.as_deref().unwrap_or("-") ); body(comment.body.as_deref()); } } pub(super) fn print_milestones(milestones: &[Milestone]) { print_table( &[ ("ID", 8), ("STATE", 10), ("TITLE", 60), ("DUE", 25), ("OPEN/CLOSED", 12), ], milestones .iter() .map(|milestone| { vec![ milestone.id.unwrap_or_default().to_string(), milestone.state.as_deref().unwrap_or("unknown").into(), one_line(milestone.title.as_deref().unwrap_or("")).into(), milestone.due_on.as_deref().unwrap_or("-").into(), format!( "{}/{}", milestone.open_issues.unwrap_or_default(), milestone.closed_issues.unwrap_or_default() ), ] }) .collect(), ); } pub(super) fn print_milestone(milestone: &Milestone) { println!( "{} [{}] {}", milestone.id.unwrap_or_default(), milestone.state.as_deref().unwrap_or("unknown"), milestone.title.as_deref().unwrap_or("") ); field("due", milestone.due_on.as_deref()); println!( "issues: {} open, {} closed", milestone.open_issues.unwrap_or_default(), milestone.closed_issues.unwrap_or_default() ); body(milestone.description.as_deref()); } pub(super) fn print_pulls(pulls: &[PullRequest]) { print_table( &[("INDEX", 8), ("STATE", 10), ("TITLE", 70), ("UPDATED", 25)], pulls .iter() .map(|pull| { vec![ pull.number.unwrap_or_default().to_string(), pull.state.as_deref().unwrap_or("unknown").into(), one_line(pull.title.as_deref().unwrap_or("")).into(), pull.updated_at.as_deref().unwrap_or("-").into(), ] }) .collect(), ); } pub(super) fn print_pull(pull: &PullRequest) { println!( "#{} [{}] {}", pull.number.unwrap_or_default(), pull_state(pull), pull.title.as_deref().unwrap_or("") ); field("url", pull.html_url.as_deref()); field( "author", pull.user.as_deref().and_then(|user| user.login.as_deref()), ); field("updated", pull.updated_at.as_deref()); println!("mergeable: {}", pull.mergeable.unwrap_or(false)); body(pull.body.as_deref()); } pub(super) fn print_commits(commits: &[Commit]) { print_table( &[("SHA", 40), ("MESSAGE", 80)], commits .iter() .map(|commit| { vec![ commit.sha.as_deref().unwrap_or("unknown").into(), one_line( commit .commit .as_deref() .and_then(|commit| commit.message.as_deref()) .unwrap_or(""), ) .into(), ] }) .collect(), ); } pub(super) fn print_files(files: &[ChangedFile]) { print_table( &[("STATUS", 12), ("CHANGES", 10), ("FILE", 90)], files .iter() .map(|file| { vec![ file.status.as_deref().unwrap_or("unknown").into(), file.changes.unwrap_or_default().to_string(), file.filename.as_deref().unwrap_or("").into(), ] }) .collect(), ); } pub(super) fn print_reviews(reviews: &[PullReview]) { print_table( &[ ("ID", 12), ("STATE", 20), ("REVIEWER", 25), ("SUBMITTED", 25), ], reviews .iter() .map(|review| { vec![ review.id.unwrap_or_default().to_string(), review.state.as_deref().unwrap_or("unknown").into(), review .user .as_deref() .and_then(|user| user.login.as_deref()) .unwrap_or("unknown") .into(), review.submitted_at.as_deref().unwrap_or("-").into(), ] }) .collect(), ); } fn field(name: &str, value: Option<&str>) { if let Some(value) = value.filter(|value| !value.is_empty()) { println!("{name}: {value}"); } } fn body(value: Option<&str>) { if let Some(value) = value.filter(|value| !value.is_empty()) { println!("\n{value}"); } } fn one_line(value: &str) -> &str { value.lines().next().unwrap_or_default() }