diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/branch_refs.rs | 274 | ||||
| -rw-r--r-- | src/cli_helpers.rs | 160 | ||||
| -rw-r--r-- | src/config.rs | 32 | ||||
| -rw-r--r-- | src/kind.rs | 137 | ||||
| -rw-r--r-- | src/ngit_tag.rs | 180 | ||||
| -rw-r--r-- | src/utils.rs | 212 |
6 files changed, 995 insertions, 0 deletions
diff --git a/src/branch_refs.rs b/src/branch_refs.rs new file mode 100644 index 0000000..d6f9613 --- /dev/null +++ b/src/branch_refs.rs | |||
| @@ -0,0 +1,274 @@ | |||
| 1 | use std::{path::PathBuf, fs, str::FromStr}; | ||
| 2 | |||
| 3 | use nostr::{Event, Filter, Timestamp, secp256k1::XOnlyPublicKey, EventId}; | ||
| 4 | use nostr_sdk::blocking::Client; | ||
| 5 | |||
| 6 | use crate::{utils::{load_event, save_event}, kind::Kind, repos::repo::Repo, groups::group::Group, repo_config::RepoConfig}; | ||
| 7 | |||
| 8 | |||
| 9 | pub struct BranchRefs { | ||
| 10 | pub branches: Vec<Event>, | ||
| 11 | pub pull_requests: Vec<Event>, | ||
| 12 | pub merges: Vec<Event>, | ||
| 13 | pub groups: Vec<Event>, | ||
| 14 | repo_dir_path: PathBuf, | ||
| 15 | pub most_recent_timestamp: Timestamp, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl BranchRefs { | ||
| 19 | pub fn new (branch_events: Vec<Event>, repo_dir_path: PathBuf) -> Self { | ||
| 20 | let mut refs = Self { | ||
| 21 | branches: vec![], | ||
| 22 | pull_requests: vec![], | ||
| 23 | merges: vec![], | ||
| 24 | groups: vec![], | ||
| 25 | repo_dir_path, | ||
| 26 | most_recent_timestamp: Timestamp::from(0), | ||
| 27 | }; | ||
| 28 | |||
| 29 | // add repo first branch in branches vector | ||
| 30 | refs.update( | ||
| 31 | load_event(refs.repo_dir_path.join(".ngit/repo.json")) | ||
| 32 | .expect("repo.json to be present and load as event") | ||
| 33 | ); | ||
| 34 | |||
| 35 | //load locally | ||
| 36 | for dir_name in [ | ||
| 37 | "groups", | ||
| 38 | "branches", | ||
| 39 | "merges", | ||
| 40 | "prs", | ||
| 41 | ] { | ||
| 42 | let dir_path = refs.repo_dir_path.join(".ngit").join(&dir_name); | ||
| 43 | if dir_path.exists() { | ||
| 44 | let dir = fs::read_dir(&dir_path) | ||
| 45 | .expect("read_dir to produce ReadDir from a path that exists"); | ||
| 46 | for entry in dir { | ||
| 47 | let path = entry | ||
| 48 | .expect("DirEntry to return from ReadDir") | ||
| 49 | .path(); | ||
| 50 | // load each BranchRef event in .ngit and call update | ||
| 51 | refs.update( | ||
| 52 | load_event(path) | ||
| 53 | .expect("every file in .ngit paths is a valid json event") | ||
| 54 | ); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | else { | ||
| 58 | panic!("expected dir to exist in branch_refs"); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | refs.updates(branch_events); | ||
| 62 | refs | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn updates (&mut self, branch_events: Vec<Event>) { | ||
| 66 | for event in branch_events.clone().into_iter() { | ||
| 67 | self.update(event); | ||
| 68 | } | ||
| 69 | let mut repo_config = RepoConfig::open(&self.repo_dir_path); | ||
| 70 | repo_config.set_last_branch_ref_update_time(self.most_recent_timestamp.clone()); | ||
| 71 | } | ||
| 72 | |||
| 73 | pub fn update (&mut self, event: Event) { | ||
| 74 | let event_to_store = event.clone(); | ||
| 75 | // /// check event is for repo | ||
| 76 | // fn event_is_for_repo(event: &Event,branch_refs: &BranchRefs) -> bool { | ||
| 77 | // match event.tags.iter().find(|tag| tag_is_repo(tag)) { | ||
| 78 | // None => false, | ||
| 79 | // Some(tag) => { | ||
| 80 | // match branch_refs.branches.get(0) { | ||
| 81 | // None => true, // current repo unknown | ||
| 82 | // Some(b) => tag_extract_value(tag) == b.id.to_string(), | ||
| 83 | // } | ||
| 84 | // }, | ||
| 85 | // } | ||
| 86 | // } | ||
| 87 | |||
| 88 | // update most_recent_timestamp | ||
| 89 | if event.created_at > self.most_recent_timestamp { | ||
| 90 | self.most_recent_timestamp = event.created_at.clone(); | ||
| 91 | } | ||
| 92 | |||
| 93 | // add events to vectors | ||
| 94 | let dir_name = match Kind::from(event.clone().kind.as_u64()) { | ||
| 95 | Kind::InitializeRepo => { | ||
| 96 | // if !self.branches.iter().any(|e| e.id == event.id) | ||
| 97 | // && event_is_for_repo(&event, &self) { | ||
| 98 | if !self.branches.iter().any(|e| e.id == event.id) { | ||
| 99 | self.branches.push(event); | ||
| 100 | Some("branches") | ||
| 101 | } | ||
| 102 | else { None } | ||
| 103 | }, | ||
| 104 | Kind::InitializeBranch => { | ||
| 105 | // if !self.branches.iter().any(|e| e.id == event.id) | ||
| 106 | // && event_is_for_repo(&event, &self) { | ||
| 107 | if !self.branches.iter().any(|e| e.id == event.id) { | ||
| 108 | self.branches.push(event); | ||
| 109 | Some("branches") | ||
| 110 | } | ||
| 111 | else { None } | ||
| 112 | }, | ||
| 113 | Kind::PullRequest => { | ||
| 114 | // if !self.pull_requests.iter().any(|e| e.id == event.id) | ||
| 115 | // && event_is_for_repo(&event, &self) { | ||
| 116 | if !self.pull_requests.iter().any(|e| e.id == event.id) { | ||
| 117 | self.pull_requests.push(event); | ||
| 118 | Some("prs") | ||
| 119 | } | ||
| 120 | else { None } | ||
| 121 | } | ||
| 122 | Kind::Merge => { | ||
| 123 | // if !self.merges.iter().any(|e| e.id == event.id) | ||
| 124 | // && event_is_for_repo(&event, &self) { | ||
| 125 | if !self.merges.iter().any(|e| e.id == event.id) { | ||
| 126 | self.merges.push(event); | ||
| 127 | Some("merges") | ||
| 128 | } | ||
| 129 | else { None } | ||
| 130 | }, | ||
| 131 | Kind::InitializeGroup => { | ||
| 132 | if !self.groups.iter().any(|e| e.id == event.id) { | ||
| 133 | self.groups.push(event); | ||
| 134 | Some("groups") | ||
| 135 | } | ||
| 136 | else { None } | ||
| 137 | }, | ||
| 138 | _ => None, | ||
| 139 | }; | ||
| 140 | |||
| 141 | // store events in .ngit directory | ||
| 142 | match dir_name { | ||
| 143 | Some(dir_name) => { | ||
| 144 | let path = self.repo_dir_path.join(".ngit").join(format!("{}/{}.json",dir_name, event_to_store.id)); | ||
| 145 | if !path.exists() { | ||
| 146 | save_event(&path, &event_to_store) | ||
| 147 | .expect(format!("save_event will store BranchRefs event in {}",&path.to_string_lossy()).as_str()); | ||
| 148 | } | ||
| 149 | }, | ||
| 150 | None => (), | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | fn branch_event(&self, branch_id: Option<&String>) -> Event { | ||
| 155 | match branch_id { | ||
| 156 | None => self.branches[0].clone(), | ||
| 157 | Some(branch_id) => self.branches.iter().find(|b| b.id.to_string() == *branch_id) | ||
| 158 | .expect("BranchRefs.branch_event() will always be called with a branch_id from a branch in its cache") | ||
| 159 | .clone(), | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | pub fn branch_as_repo(&self, branch_id: Option<&String>) -> Repo { | ||
| 164 | Repo::new_from_event(self.branch_event(branch_id)) | ||
| 165 | .expect("event in BranchRefs.branches to produce Repo") | ||
| 166 | } | ||
| 167 | |||
| 168 | /// assumes the branch_id is in cachse | ||
| 169 | pub fn maintainers_group_id(&self, branch_id: Option<&String>) -> EventId { | ||
| 170 | self.branch_as_repo(branch_id) | ||
| 171 | .maintainers_group.get_first_active_group() | ||
| 172 | .expect("a repo will always have an active maintainers group") | ||
| 173 | .clone() | ||
| 174 | } | ||
| 175 | |||
| 176 | /// assumes the branch_id is in cache. returns None if maintainers group event cannot be found. | ||
| 177 | pub fn maintainers_group(&self, branch_or_group_id: Option<&String>) -> Option<Group> { | ||
| 178 | match self.groups.iter().find(|g| | ||
| 179 | // for branch id | ||
| 180 | g.id == self.maintainers_group_id(branch_or_group_id) | ||
| 181 | // for group id | ||
| 182 | || match branch_or_group_id { | ||
| 183 | None => false, | ||
| 184 | Some(id) => g.id == EventId::from_str(id).expect("id to be valid event id"), | ||
| 185 | }, | ||
| 186 | ) { | ||
| 187 | None => None, | ||
| 188 | Some(event) => Some( | ||
| 189 | Group::new_from_event(event.clone()) | ||
| 190 | .expect("group stored in BranchRefs.groups will always produce Group") | ||
| 191 | ), | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | /// returns None if maintainers group event cannot be found | ||
| 196 | pub fn is_authorized(&self, branch_id: Option<&String>, pubkey: &XOnlyPublicKey) -> Option<bool> { | ||
| 197 | match self.maintainers_group(branch_id) { | ||
| 198 | None => None, | ||
| 199 | Some(group) => Some( | ||
| 200 | group.is_member(pubkey) | ||
| 201 | // TODO - add support for nested groups so 'is_member' checks for indirect membership | ||
| 202 | // for it will just be members of the branch group or maintainers group | ||
| 203 | || | ||
| 204 | match self.maintainers_group(None) { | ||
| 205 | None => false, | ||
| 206 | Some(group) => group.is_member(pubkey), | ||
| 207 | } | ||
| 208 | ), | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | pub fn group_ids_for_branches_without_cached_groups(&self) -> Vec<EventId> { | ||
| 213 | self.branches.iter() | ||
| 214 | .map(|b| | ||
| 215 | self.maintainers_group_id(Some(&b.id.to_string())) | ||
| 216 | .clone() | ||
| 217 | ) | ||
| 218 | .filter(|id|!self.groups.iter().any(|e|e.id == *id)) | ||
| 219 | .collect() | ||
| 220 | |||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | pub fn get_branch_refs (repo: &Repo, client: &Client, repo_dir_path: &PathBuf) -> BranchRefs { | ||
| 225 | |||
| 226 | let mut refs = BranchRefs::new(vec![],repo_dir_path.clone()); | ||
| 227 | |||
| 228 | let repo_config = RepoConfig::open(repo_dir_path); | ||
| 229 | |||
| 230 | // filter for branches, PRs and Merges | ||
| 231 | let mut tag_filter = Filter::new() | ||
| 232 | .event(repo.id) | ||
| 233 | .kinds(vec![ | ||
| 234 | Kind::InitializeBranch.into_sdk_custom_kind(), | ||
| 235 | Kind::PullRequest.into_sdk_custom_kind(), | ||
| 236 | Kind::Merge.into_sdk_custom_kind(), | ||
| 237 | ]); | ||
| 238 | match repo_config.last_branch_ref_update_time() { | ||
| 239 | None => (), | ||
| 240 | Some(timestamp) => { | ||
| 241 | tag_filter = tag_filter.since(timestamp.clone()); | ||
| 242 | } | ||
| 243 | }; | ||
| 244 | |||
| 245 | let branch_events: Vec<Event> = client.get_events_of( | ||
| 246 | vec![ | ||
| 247 | // branch maintainer groups | ||
| 248 | Filter::new().ids(refs.group_ids_for_branches_without_cached_groups()), | ||
| 249 | tag_filter, | ||
| 250 | ], | ||
| 251 | None, | ||
| 252 | ) | ||
| 253 | .expect("get_events_of to not return an error"); | ||
| 254 | |||
| 255 | refs.updates(branch_events); | ||
| 256 | // refs.merged_branches_ids.push(repo.id.to_string()); | ||
| 257 | |||
| 258 | // for event in refs.merges.iter() { | ||
| 259 | // match &event.tags.iter().find(|t|tag_is_branch(t)) { | ||
| 260 | // None => {}, | ||
| 261 | // Some(t) => { | ||
| 262 | // match &refs.maintainers_group { | ||
| 263 | // None => (), | ||
| 264 | // Some(g) => { | ||
| 265 | // if g.is_member(&event.pubkey) { | ||
| 266 | // refs.merged_branches_ids.push(tag_extract_value(t)); | ||
| 267 | // } | ||
| 268 | // } | ||
| 269 | // } | ||
| 270 | // } | ||
| 271 | // } | ||
| 272 | // } | ||
| 273 | refs | ||
| 274 | } | ||
diff --git a/src/cli_helpers.rs b/src/cli_helpers.rs new file mode 100644 index 0000000..91f6dde --- /dev/null +++ b/src/cli_helpers.rs | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | use confy::ConfyError; | ||
| 2 | use dialoguer::{theme::ColorfulTheme, Input, MultiSelect, Confirm}; | ||
| 3 | use nostr::{EventId, prelude::{Nip19Event, FromBech32}}; | ||
| 4 | |||
| 5 | use crate::config::{MyConfig, save_conifg}; | ||
| 6 | /// Renders a dialoguer multi select prompt with a free-form option | ||
| 7 | pub fn multi_select_with_add( | ||
| 8 | proposed:Vec<String>, | ||
| 9 | selected:Vec<bool>, | ||
| 10 | prompt: &str, | ||
| 11 | add_prompt: &str, | ||
| 12 | ) -> Vec<String> { | ||
| 13 | // add option with add_prompt | ||
| 14 | let mut options:Vec<String> = proposed.clone(); | ||
| 15 | options.push(add_prompt.to_string()); | ||
| 16 | let mut options_selected = selected.clone(); | ||
| 17 | options_selected.push(false); | ||
| 18 | // present options | ||
| 19 | let chosen : Vec<usize> = MultiSelect::new() | ||
| 20 | .with_prompt(prompt) | ||
| 21 | .items(&options) | ||
| 22 | .defaults(&options_selected) | ||
| 23 | .report(false) | ||
| 24 | .interact() | ||
| 25 | .unwrap(); | ||
| 26 | // reduce options list | ||
| 27 | let mut new_proposed: Vec<String> = [].to_vec(); | ||
| 28 | for (i, _el) in proposed.iter().enumerate() { | ||
| 29 | if chosen.contains(&i) { | ||
| 30 | new_proposed.push(proposed[i].clone()) | ||
| 31 | } | ||
| 32 | } | ||
| 33 | let mut new_selected: Vec<bool> = vec![true;new_proposed.len()]; | ||
| 34 | // if add_prompt selected | ||
| 35 | let last = chosen.last(); | ||
| 36 | if last == None || *last.unwrap() == options.len() - 1 { | ||
| 37 | // get user to input new item | ||
| 38 | let new_relay: String = Input::with_theme(&ColorfulTheme::default()) | ||
| 39 | .with_prompt(add_prompt) | ||
| 40 | .report(false) | ||
| 41 | .interact_text() | ||
| 42 | .unwrap(); | ||
| 43 | // prepare new proposed options | ||
| 44 | // if new item is not blank add it as a selected option | ||
| 45 | if new_relay.len() > 0 { | ||
| 46 | new_proposed.push(new_relay); | ||
| 47 | new_selected.push(true); | ||
| 48 | } | ||
| 49 | // re run selection | ||
| 50 | return multi_select_with_add( | ||
| 51 | new_proposed, | ||
| 52 | new_selected, | ||
| 53 | prompt, | ||
| 54 | add_prompt, | ||
| 55 | ) | ||
| 56 | } | ||
| 57 | else { | ||
| 58 | let mut items: Vec<String> = [].to_vec(); | ||
| 59 | for i in chosen { | ||
| 60 | items.push(options[i].clone()); | ||
| 61 | } | ||
| 62 | println!("{}: {:?}",prompt,items); | ||
| 63 | return items; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | pub fn select_relays(cfg:&mut MyConfig, selected_defaults:&Vec<String>) -> Result<Vec<String>,ConfyError> { | ||
| 68 | // set default relays (selected by default) | ||
| 69 | let default_relays = | ||
| 70 | if selected_defaults.is_empty() { cfg.default_relays.clone() } | ||
| 71 | else { selected_defaults.clone() }; | ||
| 72 | // set full proposed list | ||
| 73 | let mut proposed_relays = default_relays.clone(); | ||
| 74 | // add config defaults to proposed unless duplicate | ||
| 75 | for s in &cfg.default_relays { | ||
| 76 | if !(proposed_relays.iter().any(|df| s.eq(df))) { | ||
| 77 | proposed_relays.push(s.clone()); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | // add example options to proposed list unless duplicate | ||
| 81 | for s in vec![ | ||
| 82 | String::from("wss://relay.damus.io"), | ||
| 83 | String::from("wss://nostr.wine"), | ||
| 84 | String::from("wss://nos.lol"), | ||
| 85 | ] { | ||
| 86 | if !(proposed_relays.iter().any(|df| s.eq(df))) { | ||
| 87 | proposed_relays.push(s.clone()); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | // select only cli attribute relays or thie first in the proposed list | ||
| 91 | // this does the same thing but which is more idiumatic? | ||
| 92 | // let mut selected: Vec<bool> = vec![]; | ||
| 93 | // for i in 0..proposed_relays.len() { | ||
| 94 | // selected.push(i < relays.len()); | ||
| 95 | // } | ||
| 96 | let selected: Vec<bool> = proposed_relays | ||
| 97 | .iter() | ||
| 98 | .enumerate() | ||
| 99 | .map(|r| r.0 < default_relays.len() ).collect(); | ||
| 100 | |||
| 101 | // get user relay selection | ||
| 102 | let relay_selection: Vec<String> = crate::cli_helpers::multi_select_with_add( | ||
| 103 | proposed_relays, | ||
| 104 | selected, | ||
| 105 | "Relays", | ||
| 106 | "Other Relay" | ||
| 107 | ); | ||
| 108 | |||
| 109 | // offer to save as default config | ||
| 110 | if relay_selection.ne(&cfg.default_relays) { | ||
| 111 | if Confirm::with_theme(&ColorfulTheme::default()) | ||
| 112 | .with_prompt("Save relays as ngit default?") | ||
| 113 | .default(true) | ||
| 114 | .interact() | ||
| 115 | .unwrap() { | ||
| 116 | cfg.default_relays = relay_selection.clone(); | ||
| 117 | save_conifg(&cfg); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | Ok(relay_selection) | ||
| 121 | } | ||
| 122 | |||
| 123 | pub fn valid_event_id_from_input( | ||
| 124 | proposed_event_id: Option<String>, | ||
| 125 | prompt:&String, | ||
| 126 | ) -> EventId { | ||
| 127 | let mut string_param = proposed_event_id.clone(); | ||
| 128 | loop { | ||
| 129 | string_param = match string_param { | ||
| 130 | None => { | ||
| 131 | let response: String = Input::with_theme(&ColorfulTheme::default()) | ||
| 132 | .with_prompt(prompt.clone()) | ||
| 133 | .report(true) | ||
| 134 | .interact_text() | ||
| 135 | .unwrap(); | ||
| 136 | Some(response) | ||
| 137 | } | ||
| 138 | Some(ref s) => { Some(s.clone()) }, | ||
| 139 | }; | ||
| 140 | |||
| 141 | let _valid_id = match Nip19Event::from_bech32(&string_param.clone().unwrap()) { | ||
| 142 | Ok(n19) => { break n19.event_id } | ||
| 143 | Err(_) => { | ||
| 144 | match EventId::from_bech32(&string_param.clone().unwrap()) { | ||
| 145 | Ok(id) => { break id } | ||
| 146 | Err(_) => { | ||
| 147 | match EventId::from_hex(&string_param.clone().unwrap()) { | ||
| 148 | Ok(id) => { break id } | ||
| 149 | Err(_) => { | ||
| 150 | println!("not a valid nevent, note or hex string. try again."); | ||
| 151 | string_param = None; | ||
| 152 | continue; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | }; | ||
| 159 | } | ||
| 160 | } \ No newline at end of file | ||
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..f3b7f7b --- /dev/null +++ b/src/config.rs | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | use nostr::{secp256k1::SecretKey}; | ||
| 2 | use serde::{Deserialize, Serialize}; | ||
| 3 | |||
| 4 | #[derive(Serialize, Deserialize)] | ||
| 5 | pub struct MyConfig { | ||
| 6 | version: u8, | ||
| 7 | pub default_admin_group_event_serialized: Option<String>, | ||
| 8 | pub default_relays:Vec<String>, | ||
| 9 | pub private_key:Option<SecretKey>, | ||
| 10 | } | ||
| 11 | |||
| 12 | /// `MyConfig` implements `Default` | ||
| 13 | impl ::std::default::Default for MyConfig { | ||
| 14 | fn default() -> Self { Self { | ||
| 15 | version: 0, | ||
| 16 | default_admin_group_event_serialized: None, | ||
| 17 | default_relays:vec![], | ||
| 18 | private_key: None, | ||
| 19 | } } | ||
| 20 | } | ||
| 21 | |||
| 22 | pub fn load_config() -> MyConfig { | ||
| 23 | confy::load("ngit-cli", None) | ||
| 24 | .expect("load_config always to load confy custom config or defaults for ngit-cli") | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn save_conifg(cfg:&MyConfig) -> &MyConfig { | ||
| 28 | confy::store("ngit-cli",None, &cfg) | ||
| 29 | .expect("save_conifg always to save confy custom config or defaults for ngit-cli and return it"); | ||
| 30 | cfg | ||
| 31 | } | ||
| 32 | |||
diff --git a/src/kind.rs b/src/kind.rs new file mode 100644 index 0000000..90771b5 --- /dev/null +++ b/src/kind.rs | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | // Copyright (c) 2022-2023 Yuki Kishimoto | ||
| 2 | // Distributed under the MIT software license | ||
| 3 | |||
| 4 | //! Kind | ||
| 5 | |||
| 6 | use std::fmt; | ||
| 7 | use std::num::ParseIntError; | ||
| 8 | use std::str::FromStr; | ||
| 9 | |||
| 10 | use serde::de::{Deserialize, Deserializer, Error, Visitor}; | ||
| 11 | use serde::ser::{Serialize, Serializer}; | ||
| 12 | |||
| 13 | /// Event [`Kind`] | ||
| 14 | #[derive(Debug, Copy, Clone, Eq, Ord, PartialOrd)] | ||
| 15 | pub enum Kind { | ||
| 16 | /// Initialize Group | ||
| 17 | InitializeGroup, | ||
| 18 | /// Update Group | ||
| 19 | UpdateGroup, | ||
| 20 | /// Initialize Repository | ||
| 21 | InitializeRepo, | ||
| 22 | /// Update Repository | ||
| 23 | UpdateRepo, | ||
| 24 | /// Initialize Branch | ||
| 25 | InitializeBranch, | ||
| 26 | /// Update Branch | ||
| 27 | UpdateBranch, | ||
| 28 | /// Patch | ||
| 29 | Patch, | ||
| 30 | /// Pull Request | ||
| 31 | PullRequest, | ||
| 32 | /// Merge | ||
| 33 | Merge, | ||
| 34 | /// Custom | ||
| 35 | Custom(u64), | ||
| 36 | } | ||
| 37 | |||
| 38 | impl Kind { | ||
| 39 | /// Get [`Kind`] as `u32` | ||
| 40 | pub fn as_u32(&self) -> u32 { | ||
| 41 | self.as_u64() as u32 | ||
| 42 | } | ||
| 43 | |||
| 44 | /// Get [`Kind`] as `u64` | ||
| 45 | pub fn as_u64(&self) -> u64 { | ||
| 46 | (*self).into() | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Convert to nostr::event::Kind::Custom() | ||
| 50 | pub fn into_sdk_custom_kind(&self) -> nostr::event::Kind { | ||
| 51 | nostr::event::Kind::Custom((*self).into()) | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | impl From<u64> for Kind { | ||
| 56 | fn from(u: u64) -> Self { | ||
| 57 | match u { | ||
| 58 | 40000 => Self::InitializeGroup, | ||
| 59 | 40001 => Self::UpdateGroup, | ||
| 60 | 40010 => Self::InitializeRepo, | ||
| 61 | 40011 => Self::UpdateRepo, | ||
| 62 | 40020 => Self::InitializeBranch, | ||
| 63 | 40021 => Self::UpdateBranch, | ||
| 64 | 410 => Self::Patch, | ||
| 65 | 1 => Self::PullRequest, | ||
| 66 | 421 => Self::Merge, | ||
| 67 | x => Self::Custom(x), | ||
| 68 | |||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | impl From<Kind> for u64 { | ||
| 74 | fn from(e: Kind) -> u64 { | ||
| 75 | match e { | ||
| 76 | Kind::InitializeGroup => 40000, | ||
| 77 | Kind::UpdateGroup => 40001, | ||
| 78 | Kind::InitializeRepo => 40010, | ||
| 79 | Kind::UpdateRepo => 40011, | ||
| 80 | Kind::InitializeBranch => 40020, | ||
| 81 | Kind::UpdateBranch => 40021, | ||
| 82 | Kind::Patch => 410, | ||
| 83 | Kind::PullRequest => 1, | ||
| 84 | Kind::Merge => 421, | ||
| 85 | Kind::Custom(u) => u, | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | impl FromStr for Kind { | ||
| 91 | type Err = ParseIntError; | ||
| 92 | fn from_str(kind: &str) -> Result<Self, Self::Err> { | ||
| 93 | let kind: u64 = kind.parse()?; | ||
| 94 | Ok(Self::from(kind)) | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | impl PartialEq<Kind> for Kind { | ||
| 99 | fn eq(&self, other: &Kind) -> bool { | ||
| 100 | self.as_u64() == other.as_u64() | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | impl Serialize for Kind { | ||
| 105 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
| 106 | where | ||
| 107 | S: Serializer, | ||
| 108 | { | ||
| 109 | serializer.serialize_u64(From::from(*self)) | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | impl<'de> Deserialize<'de> for Kind { | ||
| 114 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
| 115 | where | ||
| 116 | D: Deserializer<'de>, | ||
| 117 | { | ||
| 118 | deserializer.deserialize_u64(KindVisitor) | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | struct KindVisitor; | ||
| 123 | |||
| 124 | impl Visitor<'_> for KindVisitor { | ||
| 125 | type Value = Kind; | ||
| 126 | |||
| 127 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| 128 | write!(f, "an unsigned number") | ||
| 129 | } | ||
| 130 | |||
| 131 | fn visit_u64<E>(self, v: u64) -> Result<Kind, E> | ||
| 132 | where | ||
| 133 | E: Error, | ||
| 134 | { | ||
| 135 | Ok(From::<u64>::from(v)) | ||
| 136 | } | ||
| 137 | } | ||
diff --git a/src/ngit_tag.rs b/src/ngit_tag.rs new file mode 100644 index 0000000..9f02da3 --- /dev/null +++ b/src/ngit_tag.rs | |||
| @@ -0,0 +1,180 @@ | |||
| 1 | use core::fmt; | ||
| 2 | use std::str::FromStr; | ||
| 3 | |||
| 4 | use nostr::{Tag, prelude::{self, UncheckedUrl}, EventId}; | ||
| 5 | |||
| 6 | /// Tag kind | ||
| 7 | #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] | ||
| 8 | pub enum TagKind { | ||
| 9 | /// Group | ||
| 10 | Group, | ||
| 11 | /// Admin group | ||
| 12 | AdminGroup, | ||
| 13 | /// Repository | ||
| 14 | Repo, | ||
| 15 | /// Branch | ||
| 16 | Branch, | ||
| 17 | /// Branch merged from | ||
| 18 | BranchMergeFrom, | ||
| 19 | /// Patch | ||
| 20 | Patch, | ||
| 21 | /// Patch Parent | ||
| 22 | PatchParent, | ||
| 23 | /// Commit | ||
| 24 | Commit, | ||
| 25 | /// Commit Parent | ||
| 26 | CommitParent, | ||
| 27 | /// Commit Message | ||
| 28 | CommitMessage, | ||
| 29 | /// Initial Commit | ||
| 30 | InitialCommit, | ||
| 31 | /// Relays | ||
| 32 | Relays, | ||
| 33 | /// Custom tag kind | ||
| 34 | Custom(String), | ||
| 35 | } | ||
| 36 | |||
| 37 | impl fmt::Display for TagKind { | ||
| 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| 39 | match self { | ||
| 40 | Self::Group => write!(f, "group"), | ||
| 41 | Self::AdminGroup => write!(f, "admin-group"), | ||
| 42 | Self::Repo => write!(f, "repo"), | ||
| 43 | Self::Branch => write!(f, "branch"), | ||
| 44 | Self::BranchMergeFrom => write!(f, "from-branch"), | ||
| 45 | Self::Patch => write!(f, "patch"), | ||
| 46 | Self::PatchParent => write!(f, "parent-patch"), | ||
| 47 | Self::Commit => write!(f, "commit"), | ||
| 48 | Self::CommitParent => write!(f, "parent-commit"), | ||
| 49 | Self::CommitMessage => write!(f, "commit-message"), | ||
| 50 | Self::InitialCommit => write!(f, "initial-commit"), | ||
| 51 | Self::Relays => write!(f, "relays"), | ||
| 52 | Self::Custom(tag) => write!(f, "{tag}"), | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | impl<S> From<S> for TagKind | ||
| 58 | where | ||
| 59 | S: Into<String>, | ||
| 60 | { | ||
| 61 | fn from(s: S) -> Self { | ||
| 62 | let s: String = s.into(); | ||
| 63 | match s.as_str() { | ||
| 64 | "group" => Self::Group, | ||
| 65 | "admin-group" => Self::AdminGroup, | ||
| 66 | "repo" => Self::Repo, // single letter tags are searchable under NIP-12 | ||
| 67 | "branch" => Self::Branch, // single letter tags are searchable under NIP-12 | ||
| 68 | "from-branch" => Self::BranchMergeFrom, | ||
| 69 | "patch" => Self::Patch, | ||
| 70 | "parent-patch" => Self::PatchParent, | ||
| 71 | "commit" => Self::Commit, | ||
| 72 | "parent-commit" => Self::CommitParent, | ||
| 73 | "commit-message" => Self::CommitMessage, | ||
| 74 | "initial-commit" => Self::InitialCommit, | ||
| 75 | "relays" => Self::Relays, | ||
| 76 | tag => Self::Custom(tag.to_string()), | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | fn tag(label:TagKind,value:&String) -> Tag { | ||
| 82 | Tag::Generic( | ||
| 83 | prelude::TagKind::Custom(label.to_string()), | ||
| 84 | vec![value.clone()], | ||
| 85 | ) | ||
| 86 | } | ||
| 87 | fn tag_multi_value(label:TagKind,value:&Vec<String>) -> Tag { | ||
| 88 | Tag::Generic( | ||
| 89 | prelude::TagKind::Custom(label.to_string()), | ||
| 90 | value.clone(), | ||
| 91 | ) | ||
| 92 | } | ||
| 93 | |||
| 94 | pub fn tag_group(event_id: &String) -> Tag { tag(TagKind::Group, event_id) } | ||
| 95 | pub fn tag_group_with_relays(group_id: &String, vec_relays: &Vec<String>) -> Tag { | ||
| 96 | let mut combined = vec![group_id.clone()]; | ||
| 97 | for r in vec_relays { | ||
| 98 | combined.push(r.to_string()); | ||
| 99 | } | ||
| 100 | tag_multi_value( | ||
| 101 | TagKind::Group, | ||
| 102 | &combined, | ||
| 103 | ) | ||
| 104 | } | ||
| 105 | pub fn tag_admin_group(event_id: &String) -> Tag { tag(TagKind::AdminGroup, event_id) } | ||
| 106 | pub fn tag_admin_group_with_relays(group_id: &String, vec_relays: &Vec<UncheckedUrl>) -> Tag { | ||
| 107 | let mut combined = vec![group_id.clone()]; | ||
| 108 | for r in vec_relays { | ||
| 109 | combined.push(r.to_string()); | ||
| 110 | } | ||
| 111 | tag_multi_value( | ||
| 112 | TagKind::AdminGroup, | ||
| 113 | &combined, | ||
| 114 | ) | ||
| 115 | } | ||
| 116 | // takes a tag referencing an event with optional relays and turns it into an e tag which is fiterable. perfect for tag_repo, tag_branch, etc. | ||
| 117 | pub fn tag_into_event(tag:Tag) -> Tag { | ||
| 118 | Tag::Event( | ||
| 119 | tag_extract_value_as_event_id(&tag), | ||
| 120 | tag_extract_relays(&tag).get(0).cloned(), | ||
| 121 | None, | ||
| 122 | ) | ||
| 123 | } | ||
| 124 | pub fn tag_repo(event_id: &String) -> Tag { tag(TagKind::Repo, event_id) } | ||
| 125 | pub fn tag_branch(event_id: &String) -> Tag { tag(TagKind::Branch, event_id) } | ||
| 126 | pub fn tag_branch_merge_from(event_id: &String) -> Tag { tag(TagKind::BranchMergeFrom, event_id) } | ||
| 127 | pub fn tag_patch(event_id: &String) -> Tag { tag(TagKind::Patch, event_id) } | ||
| 128 | pub fn tag_patch_parent(event_id: &String) -> Tag { tag(TagKind::PatchParent, event_id) } | ||
| 129 | pub fn tag_commit(commit_id: &String) -> Tag { tag(TagKind::Commit, commit_id) } | ||
| 130 | pub fn tag_commit_parent(commit_id: &String) -> Tag { tag(TagKind::CommitParent, commit_id) } | ||
| 131 | pub fn tag_commit_message(message: &String) -> Tag { tag(TagKind::CommitMessage, message) } | ||
| 132 | pub fn tag_initial_commit() -> Tag { Tag::Hashtag(TagKind::InitialCommit.to_string()) } | ||
| 133 | pub fn tag_relays(relays:&Vec<String>) -> Tag { | ||
| 134 | let mut relays_unchecked_url = vec![]; | ||
| 135 | for r in relays { | ||
| 136 | relays_unchecked_url.push( | ||
| 137 | UncheckedUrl::from_str(r) | ||
| 138 | .expect("relay in string to not produce error on uncheckedUrl"), | ||
| 139 | ) | ||
| 140 | } | ||
| 141 | Tag::Relays(relays_unchecked_url) | ||
| 142 | } | ||
| 143 | pub fn tag_hashtag(hashtag:&str) -> Tag { Tag::Hashtag(hashtag.to_string()) } | ||
| 144 | pub fn tag_extract_value(tag:&Tag) -> String { tag.as_vec()[1].clone() } | ||
| 145 | pub fn tag_extract_value_as_event_id(tag:&Tag) -> EventId { | ||
| 146 | EventId::from_str(tag.as_vec()[1].clone().as_str()) | ||
| 147 | .expect("first tag value is a event id") | ||
| 148 | } | ||
| 149 | pub fn tag_extract_relays(tag:&Tag) -> Vec<UncheckedUrl> { | ||
| 150 | let mut relays = vec![]; | ||
| 151 | let tag_vec = tag.as_vec(); | ||
| 152 | for (i, s) in tag_vec.iter().enumerate() { | ||
| 153 | if i > 1 || ( | ||
| 154 | i == 1 && tag_vec[0] == TagKind::Relays.to_string() | ||
| 155 | ) | ||
| 156 | { | ||
| 157 | relays.push( | ||
| 158 | UncheckedUrl::from_str(s) | ||
| 159 | .expect("relay strings to not produce error on uncheckedUrl"), | ||
| 160 | ); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | relays | ||
| 164 | } | ||
| 165 | |||
| 166 | pub fn tag_is_group(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::Group.to_string() } | ||
| 167 | pub fn tag_is_admin_group(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::AdminGroup.to_string() } | ||
| 168 | pub fn tag_is_repo(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::Repo.to_string() } | ||
| 169 | pub fn tag_is_branch(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::Branch.to_string() } | ||
| 170 | pub fn tag_is_branch_merged_from(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::BranchMergeFrom.to_string() } | ||
| 171 | pub fn tag_is_patch(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::Patch.to_string() } | ||
| 172 | pub fn tag_is_patch_parent(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::PatchParent.to_string() } | ||
| 173 | pub fn tag_is_commit(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::Commit.to_string() } | ||
| 174 | pub fn tag_is_commit_parent(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::CommitParent.to_string() } | ||
| 175 | pub fn tag_is_commit_message(tag:&Tag) -> bool { tag.kind().to_string() == TagKind::CommitMessage.to_string() } | ||
| 176 | pub fn tag_is_initial_commit(tag:&Tag) -> bool { | ||
| 177 | tag.kind().to_string() == prelude::TagKind::T.to_string() | ||
| 178 | && tag.as_vec()[1] == TagKind::InitialCommit.to_string() | ||
| 179 | } | ||
| 180 | pub fn tag_is_relays(tag:Tag) -> bool { tag.kind().to_string() == TagKind::Relays.to_string() } | ||
diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..48aafa9 --- /dev/null +++ b/src/utils.rs | |||
| @@ -0,0 +1,212 @@ | |||
| 1 | use std::fs::File; | ||
| 2 | use std::io::{Read, Write}; | ||
| 3 | use std::path::{Path}; | ||
| 4 | use std::time::Duration; | ||
| 5 | |||
| 6 | use dialoguer::{Select, Input}; | ||
| 7 | use dialoguer::theme::ColorfulTheme; | ||
| 8 | use nostr_sdk::blocking::Client; | ||
| 9 | use nostr_sdk::prelude::*; | ||
| 10 | |||
| 11 | use crate::config::{MyConfig, save_conifg}; | ||
| 12 | |||
| 13 | pub fn handle_keys(private_key: Option<String>, hex: bool) -> Result<Keys> { | ||
| 14 | // Parse and validate private key | ||
| 15 | let keys = match private_key { | ||
| 16 | Some(pk) => { | ||
| 17 | // create a new identity using the provided private key | ||
| 18 | Keys::from_sk_str(pk.as_str())? | ||
| 19 | } | ||
| 20 | None => { | ||
| 21 | // create a new identity with a new keypair | ||
| 22 | println!("No private key provided, creating new identity"); | ||
| 23 | Keys::generate() | ||
| 24 | } | ||
| 25 | }; | ||
| 26 | |||
| 27 | if !hex { | ||
| 28 | println!("Private key: {}", keys.secret_key()?.to_bech32()?); | ||
| 29 | println!("Public key: {}", keys.public_key().to_bech32()?); | ||
| 30 | } else { | ||
| 31 | println!("Private key: {}", keys.secret_key()?.display_secret()); | ||
| 32 | println!("Public key: {}", keys.public_key()); | ||
| 33 | } | ||
| 34 | Ok(keys) | ||
| 35 | } | ||
| 36 | |||
| 37 | // Creates the websocket client that is used for communicating with relays | ||
| 38 | pub fn create_client(keys: &Keys, relays: Vec<String>) -> Result<Client> { | ||
| 39 | let opts = Options::new() | ||
| 40 | .wait_for_send(true) | ||
| 41 | .timeout(Some(Duration::from_secs(7))); | ||
| 42 | let client = Client::with_opts(keys, opts); | ||
| 43 | let relays = relays.iter().map(|url| (url, None)).collect(); | ||
| 44 | client.add_relays(relays)?; | ||
| 45 | client.connect(); | ||
| 46 | Ok(client) | ||
| 47 | } | ||
| 48 | |||
| 49 | // Accepts both hex and bech32 keys and returns the hex encoded key | ||
| 50 | pub fn parse_key(key: String) -> Result<String> { | ||
| 51 | // Check if the key is a bech32 encoded key | ||
| 52 | let parsed_key = if key.starts_with("npub") { | ||
| 53 | XOnlyPublicKey::from_bech32(key)?.to_string() | ||
| 54 | } else if key.starts_with("nsec") { | ||
| 55 | SecretKey::from_bech32(key)?.display_secret().to_string() | ||
| 56 | } else if key.starts_with("note") { | ||
| 57 | EventId::from_bech32(key)?.to_hex() | ||
| 58 | } else if key.starts_with("nchannel") { | ||
| 59 | ChannelId::from_bech32(key)?.to_hex() | ||
| 60 | } else { | ||
| 61 | // If the key is not bech32 encoded, return it as is | ||
| 62 | key | ||
| 63 | }; | ||
| 64 | Ok(parsed_key) | ||
| 65 | } | ||
| 66 | |||
| 67 | pub fn get_stored_keys(cfg:&mut MyConfig) -> Option<Keys> { | ||
| 68 | match &cfg.private_key { | ||
| 69 | None => None, | ||
| 70 | Some(k) => Some(Keys::new(*k)), | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn get_or_generate_keys(cfg:&mut MyConfig) -> Keys { | ||
| 75 | match cfg.private_key { | ||
| 76 | None => { | ||
| 77 | let selection = Select::with_theme(&ColorfulTheme::default()) | ||
| 78 | .items(&vec!["enter existing private key", "generate new keys"]) | ||
| 79 | .default(0) | ||
| 80 | .with_prompt("no keys are stored") | ||
| 81 | .interact().unwrap(); | ||
| 82 | let key = match selection { | ||
| 83 | 0 => { | ||
| 84 | let mut prompt = "secret key (nsec, hex, etc)"; | ||
| 85 | loop { | ||
| 86 | let pk: String = Input::with_theme(&ColorfulTheme::default()) | ||
| 87 | .with_prompt(prompt) | ||
| 88 | .interact_text() | ||
| 89 | .unwrap(); | ||
| 90 | match Keys::from_sk_str(&pk) { | ||
| 91 | Ok(key) => { break key; }, | ||
| 92 | Err(_e) => { prompt = "error interpeting secret key. try again with nsec, hex, etc"; }, | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | } | ||
| 97 | _ => Keys::generate(), | ||
| 98 | }; | ||
| 99 | cfg.private_key = Some(key.secret_key().unwrap()); | ||
| 100 | save_conifg(&cfg); | ||
| 101 | key | ||
| 102 | } | ||
| 103 | Some(k) => Keys::new(k), | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | #[derive(clap::ValueEnum, Clone, Debug)] | ||
| 108 | pub enum Prefix { | ||
| 109 | Npub, | ||
| 110 | Nsec, | ||
| 111 | Note, | ||
| 112 | Nchannel, | ||
| 113 | } | ||
| 114 | |||
| 115 | |||
| 116 | /// [`LoadFile`] error | ||
| 117 | #[derive(Debug, thiserror::Error)] | ||
| 118 | pub enum Error { | ||
| 119 | /// Error loading event file | ||
| 120 | #[error("cannot load event file.")] | ||
| 121 | // LoadFile(#[from] init::Error), | ||
| 122 | LoadFile(), | ||
| 123 | } | ||
| 124 | |||
| 125 | pub fn load_file<P: AsRef<Path>>(path: P) -> Result<String,Error> { | ||
| 126 | let mut buf = vec![]; | ||
| 127 | match File::open(path) { | ||
| 128 | Ok(mut f) => { | ||
| 129 | f.read_to_end(&mut buf) | ||
| 130 | .expect("read_to_end not to error on file"); | ||
| 131 | Ok( | ||
| 132 | std::str::from_utf8(&buf[..]) | ||
| 133 | .expect("file contents u8 to convert to str") | ||
| 134 | .to_string(), | ||
| 135 | ) | ||
| 136 | }, | ||
| 137 | Err(_e) => { Err(Error::LoadFile()) }, | ||
| 138 | } | ||
| 139 | |||
| 140 | } | ||
| 141 | |||
| 142 | pub fn load_event<P: AsRef<Path>>(path: P) -> Result<Event,Error> { | ||
| 143 | if let Ok(mut file) = File::open(path) { | ||
| 144 | let mut buf = vec![]; | ||
| 145 | if file.read_to_end(&mut buf).is_ok() { | ||
| 146 | if let Ok(event) = Event::from_json(std::str::from_utf8(&buf[..]).unwrap()) { | ||
| 147 | return Ok(event) | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | Err(Error::LoadFile()) | ||
| 152 | } | ||
| 153 | |||
| 154 | pub fn save_event<P: AsRef<Path>>(path: P, event: &Event) -> Result<()> { | ||
| 155 | let mut f = File::create(path)?; | ||
| 156 | f.write_all(&event.as_json().as_bytes())?; | ||
| 157 | Ok(()) | ||
| 158 | } | ||
| 159 | |||
| 160 | #[cfg(test)] | ||
| 161 | mod tests { | ||
| 162 | use super::*; | ||
| 163 | |||
| 164 | #[test] | ||
| 165 | fn test_parse_key_hex_input() { | ||
| 166 | let hex_key = | ||
| 167 | String::from("f4deaad98b61fa24d86ef315f1d5d57c1a6a533e1e87e777e5d0b48dcd332cdb"); | ||
| 168 | let result = parse_key(hex_key.clone()); | ||
| 169 | |||
| 170 | assert!(result.is_ok()); | ||
| 171 | assert_eq!(result.unwrap(), hex_key); | ||
| 172 | } | ||
| 173 | |||
| 174 | #[test] | ||
| 175 | fn test_parse_key_bech32_note_input() { | ||
| 176 | let bech32_note_id = | ||
| 177 | String::from("note1h445ule4je70k7kvddate8kpsh2fd6n77esevww5hmgda2qwssjsw957wk"); | ||
| 178 | let result = parse_key(bech32_note_id); | ||
| 179 | |||
| 180 | assert!(result.is_ok()); | ||
| 181 | assert_eq!( | ||
| 182 | result.unwrap(), | ||
| 183 | String::from("bd6b4e7f35967cfb7acc6b7abc9ec185d496ea7ef6619639d4bed0dea80e8425") | ||
| 184 | ); | ||
| 185 | } | ||
| 186 | |||
| 187 | #[test] | ||
| 188 | fn test_parse_bech32_public_key_input() { | ||
| 189 | let bech32_encoded_key = | ||
| 190 | String::from("npub1ktt8phjnkfmfrsxrgqpztdjuxk3x6psf80xyray0l3c7pyrln49qhkyhz0"); | ||
| 191 | let result = parse_key(bech32_encoded_key); | ||
| 192 | |||
| 193 | assert!(result.is_ok()); | ||
| 194 | assert_eq!( | ||
| 195 | result.unwrap(), | ||
| 196 | String::from("b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a") | ||
| 197 | ); | ||
| 198 | } | ||
| 199 | |||
| 200 | #[test] | ||
| 201 | fn test_parse_bech32_private_key() { | ||
| 202 | let bech32_encoded_key = | ||
| 203 | String::from("nsec1hdeqm0y8vgzuucqv4840h7rlpy4qfu928ulxh3dzj6s2nqupdtzqagtew3"); | ||
| 204 | let result = parse_key(bech32_encoded_key); | ||
| 205 | |||
| 206 | assert!(result.is_ok()); | ||
| 207 | assert_eq!( | ||
| 208 | result.unwrap(), | ||
| 209 | String::from("bb720dbc876205ce600ca9eafbf87f092a04f0aa3f3e6bc5a296a0a983816ac4") | ||
| 210 | ); | ||
| 211 | } | ||
| 212 | } | ||