diff options
Diffstat (limited to 'src/bin/ngit/sub_commands/list.rs')
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 93 |
1 files changed, 83 insertions, 10 deletions
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index 60e129f..547c051 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs | |||
| @@ -97,6 +97,7 @@ fn run_git_fetch(remote_name: &str) -> Result<()> { | |||
| 97 | #[allow(clippy::too_many_lines)] | 97 | #[allow(clippy::too_many_lines)] |
| 98 | pub async fn launch( | 98 | pub async fn launch( |
| 99 | status: String, | 99 | status: String, |
| 100 | labels: Vec<String>, | ||
| 100 | json: bool, | 101 | json: bool, |
| 101 | show_comments: bool, | 102 | show_comments: bool, |
| 102 | id: Option<String>, | 103 | id: Option<String>, |
| @@ -187,6 +188,9 @@ pub async fn launch( | |||
| 187 | 188 | ||
| 188 | let status_filter: HashSet<&str> = status.split(',').map(str::trim).collect(); | 189 | let status_filter: HashSet<&str> = status.split(',').map(str::trim).collect(); |
| 189 | 190 | ||
| 191 | // OR filter: proposal must have at least one of the requested labels. | ||
| 192 | let label_filter: HashSet<String> = labels.iter().map(|l| l.trim().to_lowercase()).collect(); | ||
| 193 | |||
| 190 | let filtered_proposals: Vec<(&nostr::Event, Kind)> = proposals | 194 | let filtered_proposals: Vec<(&nostr::Event, Kind)> = proposals |
| 191 | .iter() | 195 | .iter() |
| 192 | .filter_map(|p| { | 196 | .filter_map(|p| { |
| @@ -198,11 +202,24 @@ pub async fn launch( | |||
| 198 | Kind::GitStatusApplied => "applied", | 202 | Kind::GitStatusApplied => "applied", |
| 199 | _ => "unknown", | 203 | _ => "unknown", |
| 200 | }; | 204 | }; |
| 201 | if status_filter.contains(status_str) || status_filter.contains("unknown") { | 205 | if !status_filter.contains(status_str) && !status_filter.contains("unknown") { |
| 202 | Some((p, status_kind)) | 206 | return None; |
| 203 | } else { | ||
| 204 | None | ||
| 205 | } | 207 | } |
| 208 | if !label_filter.is_empty() { | ||
| 209 | let proposal_labels: HashSet<String> = p | ||
| 210 | .tags | ||
| 211 | .iter() | ||
| 212 | .filter(|t| { | ||
| 213 | let s = t.as_slice(); | ||
| 214 | s.len() >= 2 && s[0].eq("t") | ||
| 215 | }) | ||
| 216 | .map(|t| t.as_slice()[1].to_lowercase()) | ||
| 217 | .collect(); | ||
| 218 | if !label_filter.iter().any(|l| proposal_labels.contains(l)) { | ||
| 219 | return None; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | Some((p, status_kind)) | ||
| 206 | }) | 223 | }) |
| 207 | .collect(); | 224 | .collect(); |
| 208 | 225 | ||
| @@ -236,7 +253,7 @@ pub async fn launch( | |||
| 236 | if json { | 253 | if json { |
| 237 | output_json(&filtered_proposals, &repo_ref)?; | 254 | output_json(&filtered_proposals, &repo_ref)?; |
| 238 | } else { | 255 | } else { |
| 239 | output_table(&filtered_proposals, &repo_ref, &status); | 256 | output_table(&filtered_proposals, &repo_ref, &status, &label_filter); |
| 240 | } | 257 | } |
| 241 | 258 | ||
| 242 | Ok(()) | 259 | Ok(()) |
| @@ -299,13 +316,18 @@ fn status_kind_to_str(kind: Kind) -> &'static str { | |||
| 299 | } | 316 | } |
| 300 | } | 317 | } |
| 301 | 318 | ||
| 302 | fn output_table(proposals: &[(&nostr::Event, Kind)], _repo_ref: &RepoRef, status_filter: &str) { | 319 | fn output_table( |
| 320 | proposals: &[(&nostr::Event, Kind)], | ||
| 321 | _repo_ref: &RepoRef, | ||
| 322 | status_filter: &str, | ||
| 323 | label_filter: &HashSet<String>, | ||
| 324 | ) { | ||
| 303 | if proposals.is_empty() { | 325 | if proposals.is_empty() { |
| 304 | println!("No proposals found matching status: {status_filter}"); | 326 | println!("No proposals found matching status: {status_filter}"); |
| 305 | return; | 327 | return; |
| 306 | } | 328 | } |
| 307 | 329 | ||
| 308 | println!("{:<66} {:<8} TITLE", "ID", "STATUS"); | 330 | println!("{:<66} {:<8} TITLE LABELS", "ID", "STATUS"); |
| 309 | for (proposal, status_kind) in proposals { | 331 | for (proposal, status_kind) in proposals { |
| 310 | let id = proposal.id.to_string(); | 332 | let id = proposal.id.to_string(); |
| 311 | let status = status_kind_to_str(*status_kind); | 333 | let status = status_kind_to_str(*status_kind); |
| @@ -316,11 +338,31 @@ fn output_table(proposals: &[(&nostr::Event, Kind)], _repo_ref: &RepoRef, status | |||
| 316 | } else { | 338 | } else { |
| 317 | proposal.id.to_string() | 339 | proposal.id.to_string() |
| 318 | }; | 340 | }; |
| 319 | println!("{id:<66} {status:<8} {title}"); | 341 | let labels_str: String = proposal |
| 342 | .tags | ||
| 343 | .iter() | ||
| 344 | .filter(|t| { | ||
| 345 | let s = t.as_slice(); | ||
| 346 | s.len() >= 2 && s[0].eq("t") | ||
| 347 | }) | ||
| 348 | .map(|t| format!("#{}", t.as_slice()[1])) | ||
| 349 | .collect::<Vec<_>>() | ||
| 350 | .join(" "); | ||
| 351 | if labels_str.is_empty() { | ||
| 352 | println!("{id:<66} {status:<8} {title}"); | ||
| 353 | } else { | ||
| 354 | println!("{id:<66} {status:<8} {title} {labels_str}"); | ||
| 355 | } | ||
| 320 | } | 356 | } |
| 321 | 357 | ||
| 322 | println!(); | 358 | println!(); |
| 323 | println!("--status {status_filter}"); | 359 | print!("--status {status_filter}"); |
| 360 | if !label_filter.is_empty() { | ||
| 361 | for l in label_filter { | ||
| 362 | print!(" --label {l}"); | ||
| 363 | } | ||
| 364 | } | ||
| 365 | println!(); | ||
| 324 | println!( | 366 | println!( |
| 325 | "{}", | 367 | "{}", |
| 326 | console::style("To view: ngit pr view <id>").yellow() | 368 | console::style("To view: ngit pr view <id>").yellow() |
| @@ -359,12 +401,22 @@ fn output_json(proposals: &[(&nostr::Event, Kind)], _repo_ref: &RepoRef) -> Resu | |||
| 359 | String::new(), | 401 | String::new(), |
| 360 | ) | 402 | ) |
| 361 | }; | 403 | }; |
| 404 | let labels: Vec<String> = proposal | ||
| 405 | .tags | ||
| 406 | .iter() | ||
| 407 | .filter(|t| { | ||
| 408 | let s = t.as_slice(); | ||
| 409 | s.len() >= 2 && s[0].eq("t") | ||
| 410 | }) | ||
| 411 | .map(|t| t.as_slice()[1].clone()) | ||
| 412 | .collect(); | ||
| 362 | serde_json::json!({ | 413 | serde_json::json!({ |
| 363 | "id": id, | 414 | "id": id, |
| 364 | "status": status, | 415 | "status": status, |
| 365 | "title": title, | 416 | "title": title, |
| 366 | "author": author, | 417 | "author": author, |
| 367 | "branch": branch | 418 | "branch": branch, |
| 419 | "labels": labels, | ||
| 368 | }) | 420 | }) |
| 369 | }) | 421 | }) |
| 370 | .collect(); | 422 | .collect(); |
| @@ -400,6 +452,7 @@ fn comment_reply_to(comment: &nostr::Event) -> Option<nostr::EventId> { | |||
| 400 | }) | 452 | }) |
| 401 | } | 453 | } |
| 402 | 454 | ||
| 455 | #[allow(clippy::too_many_lines)] | ||
| 403 | fn show_proposal_details( | 456 | fn show_proposal_details( |
| 404 | proposals: &[(&nostr::Event, Kind)], | 457 | proposals: &[(&nostr::Event, Kind)], |
| 405 | _repo_ref: &RepoRef, | 458 | _repo_ref: &RepoRef, |
| @@ -421,6 +474,16 @@ fn show_proposal_details( | |||
| 421 | let cover_letter = event_to_cover_letter(proposal) | 474 | let cover_letter = event_to_cover_letter(proposal) |
| 422 | .context("failed to extract proposal details from proposal root event")?; | 475 | .context("failed to extract proposal details from proposal root event")?; |
| 423 | 476 | ||
| 477 | let proposal_labels: Vec<String> = proposal | ||
| 478 | .tags | ||
| 479 | .iter() | ||
| 480 | .filter(|t| { | ||
| 481 | let s = t.as_slice(); | ||
| 482 | s.len() >= 2 && s[0].eq("t") | ||
| 483 | }) | ||
| 484 | .map(|t| t.as_slice()[1].clone()) | ||
| 485 | .collect(); | ||
| 486 | |||
| 424 | if json { | 487 | if json { |
| 425 | let json_output = if show_comments { | 488 | let json_output = if show_comments { |
| 426 | let comments_json: Vec<serde_json::Value> = comments | 489 | let comments_json: Vec<serde_json::Value> = comments |
| @@ -442,6 +505,7 @@ fn show_proposal_details( | |||
| 442 | "title": cover_letter.title, | 505 | "title": cover_letter.title, |
| 443 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | 506 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), |
| 444 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 507 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 508 | "labels": proposal_labels, | ||
| 445 | "comment_count": comment_count, | 509 | "comment_count": comment_count, |
| 446 | "comments": comments_json, | 510 | "comments": comments_json, |
| 447 | "description": cover_letter.description, | 511 | "description": cover_letter.description, |
| @@ -453,6 +517,7 @@ fn show_proposal_details( | |||
| 453 | "title": cover_letter.title, | 517 | "title": cover_letter.title, |
| 454 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | 518 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), |
| 455 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 519 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 520 | "labels": proposal_labels, | ||
| 456 | "comment_count": comment_count, | 521 | "comment_count": comment_count, |
| 457 | "description": cover_letter.description, | 522 | "description": cover_letter.description, |
| 458 | }) | 523 | }) |
| @@ -471,6 +536,14 @@ fn show_proposal_details( | |||
| 471 | "Branch: {}", | 536 | "Branch: {}", |
| 472 | cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()? | 537 | cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()? |
| 473 | ); | 538 | ); |
| 539 | if !proposal_labels.is_empty() { | ||
| 540 | let labels_str = proposal_labels | ||
| 541 | .iter() | ||
| 542 | .map(|l| format!("#{l}")) | ||
| 543 | .collect::<Vec<_>>() | ||
| 544 | .join(" "); | ||
| 545 | println!("Labels: {labels_str}"); | ||
| 546 | } | ||
| 474 | 547 | ||
| 475 | if !cover_letter.description.is_empty() { | 548 | if !cover_letter.description.is_empty() { |
| 476 | println!(); | 549 | println!(); |