diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/sub_commands/list.rs | 194 | ||||
| -rw-r--r-- | src/sub_commands/push.rs | 13 |
2 files changed, 163 insertions, 44 deletions
diff --git a/src/sub_commands/list.rs b/src/sub_commands/list.rs index 24979fe..5dc868c 100644 --- a/src/sub_commands/list.rs +++ b/src/sub_commands/list.rs | |||
| @@ -46,39 +46,134 @@ pub async fn launch() -> Result<()> { | |||
| 46 | 46 | ||
| 47 | println!("finding proposals..."); | 47 | println!("finding proposals..."); |
| 48 | 48 | ||
| 49 | let proposal_events_and_revisions: Vec<nostr::Event> = | 49 | let proposal_events_and_revisions_and_statuses: Vec<nostr::Event> = |
| 50 | find_proposal_events(&client, &repo_ref, &root_commit.to_string()).await?; | 50 | find_proposal_and_status_events(&client, &repo_ref, &root_commit.to_string()).await?; |
| 51 | 51 | ||
| 52 | if proposal_events_and_revisions.is_empty() { | 52 | if proposal_events_and_revisions_and_statuses.is_empty() { |
| 53 | println!("no proposals found... create one? try `ngit send`"); | 53 | println!("no proposals found... create one? try `ngit send`"); |
| 54 | return Ok(()); | 54 | return Ok(()); |
| 55 | } | 55 | } |
| 56 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions | 56 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions_and_statuses |
| 57 | .iter() | 57 | .iter() |
| 58 | .filter(|e| !event_is_revision_root(e)) | 58 | .filter(|e| !event_is_revision_root(e) && e.kind().as_u16().eq(&PATCH_KIND)) |
| 59 | .collect::<Vec<&nostr::Event>>(); | 59 | .collect::<Vec<&nostr::Event>>(); |
| 60 | 60 | ||
| 61 | let mut open_proposals: Vec<&nostr::Event> = vec![]; | ||
| 62 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; | ||
| 63 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; | ||
| 64 | let mut applied_proposals: Vec<&nostr::Event> = vec![]; | ||
| 65 | |||
| 66 | for proposal in &proposal_events { | ||
| 67 | let status = if let Some(e) = proposal_events_and_revisions_and_statuses | ||
| 68 | .iter() | ||
| 69 | .filter(|e| { | ||
| 70 | status_kinds().contains(&e.kind()) | ||
| 71 | && e.iter_tags() | ||
| 72 | .any(|t| t.as_vec()[1].eq(&proposal.id.to_string())) | ||
| 73 | }) | ||
| 74 | .collect::<Vec<&nostr::Event>>() | ||
| 75 | .first() | ||
| 76 | { | ||
| 77 | e.kind().as_u16() | ||
| 78 | } else { | ||
| 79 | STATUS_KIND_OPEN | ||
| 80 | }; | ||
| 81 | if status.eq(&STATUS_KIND_OPEN) { | ||
| 82 | open_proposals.push(proposal); | ||
| 83 | } else if status.eq(&STATUS_KIND_CLOSED) { | ||
| 84 | closed_proposals.push(proposal); | ||
| 85 | } else if status.eq(&STATUS_KIND_DRAFT) { | ||
| 86 | draft_proposals.push(proposal); | ||
| 87 | } else if status.eq(&STATUS_KIND_APPLIED) { | ||
| 88 | applied_proposals.push(proposal); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | if proposal_events_and_revisions_and_statuses.is_empty() { | ||
| 93 | println!("no open proposals found... create one? try `ngit send`"); | ||
| 94 | return Ok(()); | ||
| 95 | } | ||
| 96 | |||
| 97 | let mut selected_status = STATUS_KIND_OPEN; | ||
| 98 | |||
| 61 | loop { | 99 | loop { |
| 100 | let proposals_for_status = if selected_status == STATUS_KIND_OPEN { | ||
| 101 | &open_proposals | ||
| 102 | } else if selected_status == STATUS_KIND_DRAFT { | ||
| 103 | &draft_proposals | ||
| 104 | } else if selected_status == STATUS_KIND_CLOSED { | ||
| 105 | &closed_proposals | ||
| 106 | } else if selected_status == STATUS_KIND_APPLIED { | ||
| 107 | &applied_proposals | ||
| 108 | } else { | ||
| 109 | &proposal_events | ||
| 110 | }; | ||
| 111 | |||
| 112 | let prompt = if proposal_events.len().eq(&open_proposals.len()) { | ||
| 113 | "all proposals" | ||
| 114 | } else if selected_status == STATUS_KIND_OPEN { | ||
| 115 | if open_proposals.is_empty() { | ||
| 116 | "proposals menu" | ||
| 117 | } else { | ||
| 118 | "open proposals" | ||
| 119 | } | ||
| 120 | } else if selected_status == STATUS_KIND_DRAFT { | ||
| 121 | "draft proposals" | ||
| 122 | } else if selected_status == STATUS_KIND_CLOSED { | ||
| 123 | "closed proposals" | ||
| 124 | } else { | ||
| 125 | "applied proposals" | ||
| 126 | }; | ||
| 127 | |||
| 128 | let mut choices: Vec<String> = proposals_for_status | ||
| 129 | .iter() | ||
| 130 | .map(|e| { | ||
| 131 | if let Ok(cl) = event_to_cover_letter(e) { | ||
| 132 | cl.title | ||
| 133 | } else if let Ok(msg) = tag_value(e, "description") { | ||
| 134 | msg.split('\n').collect::<Vec<&str>>()[0].to_string() | ||
| 135 | } else { | ||
| 136 | e.id.to_string() | ||
| 137 | } | ||
| 138 | }) | ||
| 139 | .collect(); | ||
| 140 | |||
| 141 | if !selected_status.eq(&STATUS_KIND_OPEN) && open_proposals.len().gt(&0) { | ||
| 142 | choices.push(format!("({}) Open proposals...", open_proposals.len())); | ||
| 143 | } | ||
| 144 | if !selected_status.eq(&STATUS_KIND_DRAFT) && draft_proposals.len().gt(&0) { | ||
| 145 | choices.push(format!("({}) Draft proposals...", draft_proposals.len())); | ||
| 146 | } | ||
| 147 | if !selected_status.eq(&STATUS_KIND_CLOSED) && closed_proposals.len().gt(&0) { | ||
| 148 | choices.push(format!("({}) Closed proposals...", closed_proposals.len())); | ||
| 149 | } | ||
| 150 | if !selected_status.eq(&STATUS_KIND_APPLIED) && applied_proposals.len().gt(&0) { | ||
| 151 | choices.push(format!( | ||
| 152 | "({}) Applied proposals...", | ||
| 153 | applied_proposals.len() | ||
| 154 | )); | ||
| 155 | } | ||
| 156 | |||
| 62 | let selected_index = Interactor::default().choice( | 157 | let selected_index = Interactor::default().choice( |
| 63 | PromptChoiceParms::default() | 158 | PromptChoiceParms::default() |
| 64 | .with_prompt("all proposals") | 159 | .with_prompt(prompt) |
| 65 | .with_choices( | 160 | .with_choices(choices.clone()), |
| 66 | proposal_events | ||
| 67 | .iter() | ||
| 68 | .map(|e| { | ||
| 69 | if let Ok(cl) = event_to_cover_letter(e) { | ||
| 70 | cl.title | ||
| 71 | } else if let Ok(msg) = tag_value(e, "description") { | ||
| 72 | msg.split('\n').collect::<Vec<&str>>()[0].to_string() | ||
| 73 | } else { | ||
| 74 | e.id.to_string() | ||
| 75 | } | ||
| 76 | }) | ||
| 77 | .collect(), | ||
| 78 | ), | ||
| 79 | )?; | 161 | )?; |
| 80 | 162 | ||
| 81 | let cover_letter = event_to_cover_letter(proposal_events[selected_index]) | 163 | if (selected_index + 1).gt(&proposals_for_status.len()) { |
| 164 | if choices[selected_index].contains("Open") { | ||
| 165 | selected_status = STATUS_KIND_OPEN; | ||
| 166 | } else if choices[selected_index].contains("Draft") { | ||
| 167 | selected_status = STATUS_KIND_DRAFT; | ||
| 168 | } else if choices[selected_index].contains("Closed") { | ||
| 169 | selected_status = STATUS_KIND_CLOSED; | ||
| 170 | } else if choices[selected_index].contains("Applied") { | ||
| 171 | selected_status = STATUS_KIND_APPLIED; | ||
| 172 | } | ||
| 173 | continue; | ||
| 174 | } | ||
| 175 | |||
| 176 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) | ||
| 82 | .context("cannot extract proposal details from proposal root event")?; | 177 | .context("cannot extract proposal details from proposal root event")?; |
| 83 | 178 | ||
| 84 | println!("finding commits..."); | 179 | println!("finding commits..."); |
| @@ -86,14 +181,16 @@ pub async fn launch() -> Result<()> { | |||
| 86 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( | 181 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( |
| 87 | &client, | 182 | &client, |
| 88 | &[ | 183 | &[ |
| 89 | vec![proposal_events[selected_index]], | 184 | vec![proposals_for_status[selected_index]], |
| 90 | proposal_events_and_revisions | 185 | proposal_events_and_revisions_and_statuses |
| 91 | .iter() | 186 | .iter() |
| 92 | .filter(|e| { | 187 | .filter(|e| { |
| 93 | e.tags.iter().any(|t| { | 188 | e.kind.as_u16().eq(&PATCH_KIND) |
| 94 | t.as_vec().len().gt(&1) | 189 | && e.tags.iter().any(|t| { |
| 95 | && t.as_vec()[1].eq(&proposal_events[selected_index].id.to_string()) | 190 | t.as_vec().len().gt(&1) |
| 96 | }) | 191 | && t.as_vec()[1] |
| 192 | .eq(&proposals_for_status[selected_index].id.to_string()) | ||
| 193 | }) | ||
| 97 | }) | 194 | }) |
| 98 | .collect::<Vec<&nostr::Event>>(), | 195 | .collect::<Vec<&nostr::Event>>(), |
| 99 | ] | 196 | ] |
| @@ -718,45 +815,64 @@ pub fn get_most_recent_patch_with_ancestors( | |||
| 718 | Ok(res) | 815 | Ok(res) |
| 719 | } | 816 | } |
| 720 | 817 | ||
| 721 | pub async fn find_proposal_events( | 818 | pub static STATUS_KIND_OPEN: u16 = 1630; |
| 819 | pub static STATUS_KIND_APPLIED: u16 = 1631; | ||
| 820 | pub static STATUS_KIND_CLOSED: u16 = 1632; | ||
| 821 | pub static STATUS_KIND_DRAFT: u16 = 1633; | ||
| 822 | |||
| 823 | fn status_kinds() -> Vec<nostr::Kind> { | ||
| 824 | vec![ | ||
| 825 | nostr::Kind::Custom(STATUS_KIND_OPEN), | ||
| 826 | nostr::Kind::Custom(STATUS_KIND_APPLIED), | ||
| 827 | nostr::Kind::Custom(STATUS_KIND_CLOSED), | ||
| 828 | nostr::Kind::Custom(STATUS_KIND_DRAFT), | ||
| 829 | ] | ||
| 830 | } | ||
| 831 | |||
| 832 | pub async fn find_proposal_and_status_events( | ||
| 722 | #[cfg(test)] client: &crate::client::MockConnect, | 833 | #[cfg(test)] client: &crate::client::MockConnect, |
| 723 | #[cfg(not(test))] client: &Client, | 834 | #[cfg(not(test))] client: &Client, |
| 724 | repo_ref: &RepoRef, | 835 | repo_ref: &RepoRef, |
| 725 | root_commit: &str, | 836 | root_commit: &str, |
| 726 | ) -> Result<Vec<nostr::Event>> { | 837 | ) -> Result<Vec<nostr::Event>> { |
| 838 | let repo_tags_filter = nostr::Filter::default().custom_tag( | ||
| 839 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::A), | ||
| 840 | repo_ref | ||
| 841 | .maintainers | ||
| 842 | .iter() | ||
| 843 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)), | ||
| 844 | ); | ||
| 727 | let mut proposals = client | 845 | let mut proposals = client |
| 728 | .get_events( | 846 | .get_events( |
| 729 | repo_ref.relays.clone(), | 847 | repo_ref.relays.clone(), |
| 730 | vec![ | 848 | vec![ |
| 731 | nostr::Filter::default() | 849 | repo_tags_filter |
| 850 | .clone() | ||
| 732 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 851 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 733 | .custom_tag( | 852 | .custom_tag( |
| 734 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | 853 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), |
| 735 | vec!["root"], | 854 | vec!["root"], |
| 736 | ) | ||
| 737 | .custom_tag( | ||
| 738 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::A), | ||
| 739 | repo_ref | ||
| 740 | .maintainers | ||
| 741 | .iter() | ||
| 742 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)), | ||
| 743 | ), | 855 | ), |
| 744 | // also pick up proposals from the same repo but no target at our maintainers repo | 856 | // also pick up proposals from the same repo but no target at our maintainers repo |
| 745 | // events | 857 | // events |
| 746 | nostr::Filter::default() | 858 | nostr::Filter::default() |
| 859 | .reference(root_commit) | ||
| 747 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 860 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 748 | .custom_tag( | 861 | .custom_tag( |
| 749 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | 862 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), |
| 750 | vec!["root"], | 863 | vec!["root"], |
| 751 | ) | 864 | ), |
| 752 | .reference(root_commit), | 865 | repo_tags_filter.clone().kinds(status_kinds().clone()), |
| 866 | nostr::Filter::default() | ||
| 867 | .reference(root_commit) | ||
| 868 | .kinds(status_kinds().clone()), | ||
| 753 | ], | 869 | ], |
| 754 | ) | 870 | ) |
| 755 | .await | 871 | .await |
| 756 | .context("cannot get proposal events")? | 872 | .context("cannot get proposal events")? |
| 757 | .iter() | 873 | .iter() |
| 758 | .filter(|e| { | 874 | .filter(|e| { |
| 759 | event_is_patch_set_root(e) | 875 | (event_is_patch_set_root(e) || status_kinds().contains(&e.kind())) |
| 760 | && (e | 876 | && (e |
| 761 | .tags | 877 | .tags |
| 762 | .iter() | 878 | .iter() |
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs index 3c471c0..9e3e041 100644 --- a/src/sub_commands/push.rs +++ b/src/sub_commands/push.rs | |||
| @@ -13,10 +13,13 @@ use crate::{ | |||
| 13 | sub_commands::{ | 13 | sub_commands::{ |
| 14 | self, | 14 | self, |
| 15 | list::{ | 15 | list::{ |
| 16 | find_commits_for_proposal_root_events, find_proposal_events, get_commit_id_from_patch, | 16 | find_commits_for_proposal_root_events, find_proposal_and_status_events, |
| 17 | get_most_recent_patch_with_ancestors, tag_value, | 17 | get_commit_id_from_patch, get_most_recent_patch_with_ancestors, tag_value, |
| 18 | }, | ||
| 19 | send::{ | ||
| 20 | event_is_revision_root, event_to_cover_letter, generate_patch_event, send_events, | ||
| 21 | PATCH_KIND, | ||
| 18 | }, | 22 | }, |
| 19 | send::{event_is_revision_root, event_to_cover_letter, generate_patch_event, send_events}, | ||
| 20 | }, | 23 | }, |
| 21 | Cli, | 24 | Cli, |
| 22 | }; | 25 | }; |
| @@ -207,13 +210,13 @@ pub async fn fetch_proposal_root_and_most_recent_patch_chain( | |||
| 207 | println!("finding proposal root event..."); | 210 | println!("finding proposal root event..."); |
| 208 | 211 | ||
| 209 | let proposal_events_and_revisions: Vec<nostr::Event> = | 212 | let proposal_events_and_revisions: Vec<nostr::Event> = |
| 210 | find_proposal_events(client, repo_ref, &root_commit.to_string()) | 213 | find_proposal_and_status_events(client, repo_ref, &root_commit.to_string()) |
| 211 | .await | 214 | .await |
| 212 | .context("cannot get proposal events for repo")?; | 215 | .context("cannot get proposal events for repo")?; |
| 213 | 216 | ||
| 214 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions | 217 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions |
| 215 | .iter() | 218 | .iter() |
| 216 | .filter(|e| !event_is_revision_root(e)) | 219 | .filter(|e| !event_is_revision_root(e) && e.kind().as_u16().eq(&PATCH_KIND)) |
| 217 | .collect::<Vec<&nostr::Event>>(); | 220 | .collect::<Vec<&nostr::Event>>(); |
| 218 | 221 | ||
| 219 | let proposal_root_event: &nostr::Event = proposal_events | 222 | let proposal_root_event: &nostr::Event = proposal_events |