diff options
Diffstat (limited to 'src/bin/ngit')
| -rw-r--r-- | src/bin/ngit/sub_commands/issue_list.rs | 27 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 83 |
2 files changed, 45 insertions, 65 deletions
diff --git a/src/bin/ngit/sub_commands/issue_list.rs b/src/bin/ngit/sub_commands/issue_list.rs index d7c8ac9..22b1b8a 100644 --- a/src/bin/ngit/sub_commands/issue_list.rs +++ b/src/bin/ngit/sub_commands/issue_list.rs | |||
| @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; | |||
| 3 | use anyhow::{Context, Result, bail}; | 3 | use anyhow::{Context, Result, bail}; |
| 4 | use ngit::{ | 4 | use ngit::{ |
| 5 | client::{Params, get_events_from_local_cache, get_issues_from_cache}, | 5 | client::{Params, get_events_from_local_cache, get_issues_from_cache}, |
| 6 | git_events::{KIND_COMMENT, get_status, status_kinds, tag_value}, | 6 | git_events::{KIND_COMMENT, KIND_LABEL, get_labels, get_status, status_kinds, tag_value}, |
| 7 | }; | 7 | }; |
| 8 | use nostr::{ | 8 | use nostr::{ |
| 9 | FromBech32, ToBech32, | 9 | FromBech32, ToBech32, |
| @@ -38,17 +38,7 @@ fn get_issue_title(event: &nostr::Event) -> String { | |||
| 38 | }) | 38 | }) |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | fn get_issue_labels(event: &nostr::Event) -> Vec<String> { | 41 | |
| 42 | event | ||
| 43 | .tags | ||
| 44 | .iter() | ||
| 45 | .filter(|t| { | ||
| 46 | let s = t.as_slice(); | ||
| 47 | s.len() >= 2 && s[0].eq("t") | ||
| 48 | }) | ||
| 49 | .map(|t| t.as_slice()[1].clone()) | ||
| 50 | .collect() | ||
| 51 | } | ||
| 52 | 42 | ||
| 53 | fn status_kind_to_str(kind: Kind) -> &'static str { | 43 | fn status_kind_to_str(kind: Kind) -> &'static str { |
| 54 | match kind { | 44 | match kind { |
| @@ -184,6 +174,17 @@ pub async fn launch( | |||
| 184 | statuses | 174 | statuses |
| 185 | }; | 175 | }; |
| 186 | 176 | ||
| 177 | // Fetch NIP-32 kind-1985 label events for all issues. | ||
| 178 | let label_events: Vec<nostr::Event> = get_events_from_local_cache( | ||
| 179 | git_repo_path, | ||
| 180 | vec![ | ||
| 181 | nostr::Filter::default() | ||
| 182 | .events(issues.iter().map(|e| e.id)) | ||
| 183 | .kind(KIND_LABEL), | ||
| 184 | ], | ||
| 185 | ) | ||
| 186 | .await?; | ||
| 187 | |||
| 187 | let comment_counts = get_comment_counts(git_repo_path, &issues).await?; | 188 | let comment_counts = get_comment_counts(git_repo_path, &issues).await?; |
| 188 | 189 | ||
| 189 | let status_filter: HashSet<&str> = status.split(',').map(str::trim).collect(); | 190 | let status_filter: HashSet<&str> = status.split(',').map(str::trim).collect(); |
| @@ -203,7 +204,7 @@ pub async fn launch( | |||
| 203 | if !status_filter.contains(status_str) && !status_filter.contains("unknown") { | 204 | if !status_filter.contains(status_str) && !status_filter.contains("unknown") { |
| 204 | return None; | 205 | return None; |
| 205 | } | 206 | } |
| 206 | let issue_labels = get_issue_labels(issue); | 207 | let issue_labels = get_labels(issue, &repo_ref, &label_events); |
| 207 | if !label_filter.is_empty() { | 208 | if !label_filter.is_empty() { |
| 208 | let issue_labels_lower: HashSet<String> = | 209 | let issue_labels_lower: HashSet<String> = |
| 209 | issue_labels.iter().map(|t| t.to_lowercase()).collect(); | 210 | issue_labels.iter().map(|t| t.to_lowercase()).collect(); |
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index 547c051..404b25e 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs | |||
| @@ -15,7 +15,8 @@ use ngit::{ | |||
| 15 | }, | 15 | }, |
| 16 | fetch::fetch_from_git_server, | 16 | fetch::fetch_from_git_server, |
| 17 | git_events::{ | 17 | git_events::{ |
| 18 | KIND_COMMENT, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, get_commit_id_from_patch, | 18 | KIND_COMMENT, KIND_LABEL, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, |
| 19 | get_commit_id_from_patch, get_labels, | ||
| 19 | get_pr_tip_event_or_most_recent_patch_with_ancestors, get_status, status_kinds, tag_value, | 20 | get_pr_tip_event_or_most_recent_patch_with_ancestors, get_status, status_kinds, tag_value, |
| 20 | }, | 21 | }, |
| 21 | repo_ref::{RepoRef, is_grasp_server_in_list}, | 22 | repo_ref::{RepoRef, is_grasp_server_in_list}, |
| @@ -162,6 +163,17 @@ pub async fn launch( | |||
| 162 | statuses | 163 | statuses |
| 163 | }; | 164 | }; |
| 164 | 165 | ||
| 166 | // Fetch NIP-32 kind-1985 label events for all proposals. | ||
| 167 | let label_events: Vec<nostr::Event> = get_events_from_local_cache( | ||
| 168 | git_repo_path, | ||
| 169 | vec![ | ||
| 170 | nostr::Filter::default() | ||
| 171 | .events(proposals_and_revisions.iter().map(|e| e.id)) | ||
| 172 | .kind(KIND_LABEL), | ||
| 173 | ], | ||
| 174 | ) | ||
| 175 | .await?; | ||
| 176 | |||
| 165 | let mut open_proposals: Vec<&nostr::Event> = vec![]; | 177 | let mut open_proposals: Vec<&nostr::Event> = vec![]; |
| 166 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; | 178 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; |
| 167 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; | 179 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; |
| @@ -191,7 +203,7 @@ pub async fn launch( | |||
| 191 | // OR filter: proposal must have at least one of the requested labels. | 203 | // 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(); | 204 | let label_filter: HashSet<String> = labels.iter().map(|l| l.trim().to_lowercase()).collect(); |
| 193 | 205 | ||
| 194 | let filtered_proposals: Vec<(&nostr::Event, Kind)> = proposals | 206 | let filtered_proposals: Vec<(&nostr::Event, Kind, Vec<String>)> = proposals |
| 195 | .iter() | 207 | .iter() |
| 196 | .filter_map(|p| { | 208 | .filter_map(|p| { |
| 197 | let status_kind = get_status(p, &repo_ref, &statuses, &proposals); | 209 | let status_kind = get_status(p, &repo_ref, &statuses, &proposals); |
| @@ -205,21 +217,15 @@ pub async fn launch( | |||
| 205 | if !status_filter.contains(status_str) && !status_filter.contains("unknown") { | 217 | if !status_filter.contains(status_str) && !status_filter.contains("unknown") { |
| 206 | return None; | 218 | return None; |
| 207 | } | 219 | } |
| 220 | let proposal_labels = get_labels(p, &repo_ref, &label_events); | ||
| 208 | if !label_filter.is_empty() { | 221 | if !label_filter.is_empty() { |
| 209 | let proposal_labels: HashSet<String> = p | 222 | let proposal_labels_lower: HashSet<String> = |
| 210 | .tags | 223 | proposal_labels.iter().map(|l| l.to_lowercase()).collect(); |
| 211 | .iter() | 224 | if !label_filter.iter().any(|l| proposal_labels_lower.contains(l)) { |
| 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; | 225 | return None; |
| 220 | } | 226 | } |
| 221 | } | 227 | } |
| 222 | Some((p, status_kind)) | 228 | Some((p, status_kind, proposal_labels)) |
| 223 | }) | 229 | }) |
| 224 | .collect(); | 230 | .collect(); |
| 225 | 231 | ||
| @@ -241,7 +247,6 @@ pub async fn launch( | |||
| 241 | }; | 247 | }; |
| 242 | return show_proposal_details( | 248 | return show_proposal_details( |
| 243 | &filtered_proposals, | 249 | &filtered_proposals, |
| 244 | &repo_ref, | ||
| 245 | event_id_or_nevent, | 250 | event_id_or_nevent, |
| 246 | json, | 251 | json, |
| 247 | show_comments, | 252 | show_comments, |
| @@ -251,9 +256,9 @@ pub async fn launch( | |||
| 251 | } | 256 | } |
| 252 | 257 | ||
| 253 | if json { | 258 | if json { |
| 254 | output_json(&filtered_proposals, &repo_ref)?; | 259 | output_json(&filtered_proposals)?; |
| 255 | } else { | 260 | } else { |
| 256 | output_table(&filtered_proposals, &repo_ref, &status, &label_filter); | 261 | output_table(&filtered_proposals, &status, &label_filter); |
| 257 | } | 262 | } |
| 258 | 263 | ||
| 259 | Ok(()) | 264 | Ok(()) |
| @@ -317,8 +322,7 @@ fn status_kind_to_str(kind: Kind) -> &'static str { | |||
| 317 | } | 322 | } |
| 318 | 323 | ||
| 319 | fn output_table( | 324 | fn output_table( |
| 320 | proposals: &[(&nostr::Event, Kind)], | 325 | proposals: &[(&nostr::Event, Kind, Vec<String>)], |
| 321 | _repo_ref: &RepoRef, | ||
| 322 | status_filter: &str, | 326 | status_filter: &str, |
| 323 | label_filter: &HashSet<String>, | 327 | label_filter: &HashSet<String>, |
| 324 | ) { | 328 | ) { |
| @@ -328,7 +332,7 @@ fn output_table( | |||
| 328 | } | 332 | } |
| 329 | 333 | ||
| 330 | println!("{:<66} {:<8} TITLE LABELS", "ID", "STATUS"); | 334 | println!("{:<66} {:<8} TITLE LABELS", "ID", "STATUS"); |
| 331 | for (proposal, status_kind) in proposals { | 335 | for (proposal, status_kind, proposal_labels) in proposals { |
| 332 | let id = proposal.id.to_string(); | 336 | let id = proposal.id.to_string(); |
| 333 | let status = status_kind_to_str(*status_kind); | 337 | let status = status_kind_to_str(*status_kind); |
| 334 | let title = if let Ok(cl) = event_to_cover_letter(proposal) { | 338 | let title = if let Ok(cl) = event_to_cover_letter(proposal) { |
| @@ -338,14 +342,9 @@ fn output_table( | |||
| 338 | } else { | 342 | } else { |
| 339 | proposal.id.to_string() | 343 | proposal.id.to_string() |
| 340 | }; | 344 | }; |
| 341 | let labels_str: String = proposal | 345 | let labels_str: String = proposal_labels |
| 342 | .tags | ||
| 343 | .iter() | 346 | .iter() |
| 344 | .filter(|t| { | 347 | .map(|l| format!("#{l}")) |
| 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<_>>() | 348 | .collect::<Vec<_>>() |
| 350 | .join(" "); | 349 | .join(" "); |
| 351 | if labels_str.is_empty() { | 350 | if labels_str.is_empty() { |
| @@ -377,10 +376,10 @@ fn output_table( | |||
| 377 | ); | 376 | ); |
| 378 | } | 377 | } |
| 379 | 378 | ||
| 380 | fn output_json(proposals: &[(&nostr::Event, Kind)], _repo_ref: &RepoRef) -> Result<()> { | 379 | fn output_json(proposals: &[(&nostr::Event, Kind, Vec<String>)]) -> Result<()> { |
| 381 | let json_output: Vec<serde_json::Value> = proposals | 380 | let json_output: Vec<serde_json::Value> = proposals |
| 382 | .iter() | 381 | .iter() |
| 383 | .map(|(proposal, status_kind)| { | 382 | .map(|(proposal, status_kind, proposal_labels)| { |
| 384 | let id = proposal.id.to_string(); | 383 | let id = proposal.id.to_string(); |
| 385 | let status = status_kind_to_str(*status_kind).to_string(); | 384 | let status = status_kind_to_str(*status_kind).to_string(); |
| 386 | let (title, author, branch) = if let Ok(cl) = event_to_cover_letter(proposal) { | 385 | let (title, author, branch) = if let Ok(cl) = event_to_cover_letter(proposal) { |
| @@ -401,22 +400,13 @@ fn output_json(proposals: &[(&nostr::Event, Kind)], _repo_ref: &RepoRef) -> Resu | |||
| 401 | String::new(), | 400 | String::new(), |
| 402 | ) | 401 | ) |
| 403 | }; | 402 | }; |
| 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(); | ||
| 413 | serde_json::json!({ | 403 | serde_json::json!({ |
| 414 | "id": id, | 404 | "id": id, |
| 415 | "status": status, | 405 | "status": status, |
| 416 | "title": title, | 406 | "title": title, |
| 417 | "author": author, | 407 | "author": author, |
| 418 | "branch": branch, | 408 | "branch": branch, |
| 419 | "labels": labels, | 409 | "labels": proposal_labels, |
| 420 | }) | 410 | }) |
| 421 | }) | 411 | }) |
| 422 | .collect(); | 412 | .collect(); |
| @@ -454,8 +444,7 @@ fn comment_reply_to(comment: &nostr::Event) -> Option<nostr::EventId> { | |||
| 454 | 444 | ||
| 455 | #[allow(clippy::too_many_lines)] | 445 | #[allow(clippy::too_many_lines)] |
| 456 | fn show_proposal_details( | 446 | fn show_proposal_details( |
| 457 | proposals: &[(&nostr::Event, Kind)], | 447 | proposals: &[(&nostr::Event, Kind, Vec<String>)], |
| 458 | _repo_ref: &RepoRef, | ||
| 459 | event_id_or_nevent: &str, | 448 | event_id_or_nevent: &str, |
| 460 | json: bool, | 449 | json: bool, |
| 461 | show_comments: bool, | 450 | show_comments: bool, |
| @@ -466,24 +455,14 @@ fn show_proposal_details( | |||
| 466 | 455 | ||
| 467 | let target_id = resolve_event_id(event_id_or_nevent)?; | 456 | let target_id = resolve_event_id(event_id_or_nevent)?; |
| 468 | 457 | ||
| 469 | let (proposal, status_kind) = proposals | 458 | let (proposal, status_kind, proposal_labels) = proposals |
| 470 | .iter() | 459 | .iter() |
| 471 | .find(|(p, _)| p.id == target_id) | 460 | .find(|(p, _, _)| p.id == target_id) |
| 472 | .context("proposal not found")?; | 461 | .context("proposal not found")?; |
| 473 | 462 | ||
| 474 | let cover_letter = event_to_cover_letter(proposal) | 463 | let cover_letter = event_to_cover_letter(proposal) |
| 475 | .context("failed to extract proposal details from proposal root event")?; | 464 | .context("failed to extract proposal details from proposal root event")?; |
| 476 | 465 | ||
| 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 | |||
| 487 | if json { | 466 | if json { |
| 488 | let json_output = if show_comments { | 467 | let json_output = if show_comments { |
| 489 | let comments_json: Vec<serde_json::Value> = comments | 468 | let comments_json: Vec<serde_json::Value> = comments |