upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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