upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/sub_commands
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-07-05 11:06:04 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-07-05 11:06:04 +0100
commitba82a894fad645757c49242c11573b6c5dd8d1e6 (patch)
tree4026fe28e40260578a817154c9ed614a16dfcb7f /src/sub_commands
parentd70a6ef029b90d00024da82e945316ce58b9507b (diff)
feat(init): group by statusv1.3-beta1
show open proposal by defult and include options to filter by other statuses there are not tests for this currently as the intention is to transform 'list' further by adding a 'fetch'
Diffstat (limited to 'src/sub_commands')
-rw-r--r--src/sub_commands/list.rs194
-rw-r--r--src/sub_commands/push.rs13
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
721pub async fn find_proposal_events( 818pub static STATUS_KIND_OPEN: u16 = 1630;
819pub static STATUS_KIND_APPLIED: u16 = 1631;
820pub static STATUS_KIND_CLOSED: u16 = 1632;
821pub static STATUS_KIND_DRAFT: u16 = 1633;
822
823fn 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
832pub 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