diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-04 08:04:48 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-04 13:30:59 +0100 |
| commit | 949c6459aa7683453a7160423b689ceadb08954b (patch) | |
| tree | 230c26ecb11b99916e5570e548673eb09ecf0a36 /src/sub_commands/init.rs | |
| parent | a825311f2c55661aaab3a163bda9109295c96044 (diff) | |
refactor: organise into lib and bin structure
the make the code more readable
this commit just moves the files, the next commit should fix the imports
Diffstat (limited to 'src/sub_commands/init.rs')
| -rw-r--r-- | src/sub_commands/init.rs | 385 |
1 files changed, 0 insertions, 385 deletions
diff --git a/src/sub_commands/init.rs b/src/sub_commands/init.rs deleted file mode 100644 index 5b7e03d..0000000 --- a/src/sub_commands/init.rs +++ /dev/null | |||
| @@ -1,385 +0,0 @@ | |||
| 1 | use std::collections::HashMap; | ||
| 2 | |||
| 3 | use anyhow::{Context, Result}; | ||
| 4 | use nostr::{nips::nip01::Coordinate, FromBech32, PublicKey, ToBech32}; | ||
| 5 | use nostr_sdk::Kind; | ||
| 6 | |||
| 7 | use super::send::send_events; | ||
| 8 | #[cfg(not(test))] | ||
| 9 | use crate::client::Client; | ||
| 10 | #[cfg(test)] | ||
| 11 | use crate::client::MockConnect; | ||
| 12 | use crate::{ | ||
| 13 | cli::Cli, | ||
| 14 | cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, | ||
| 15 | client::{fetching_with_report, get_repo_ref_from_cache, Connect}, | ||
| 16 | git::{convert_clone_url_to_https, Repo, RepoActions}, | ||
| 17 | login, | ||
| 18 | repo_ref::{ | ||
| 19 | extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml, | ||
| 20 | try_and_get_repo_coordinates, RepoRef, | ||
| 21 | }, | ||
| 22 | }; | ||
| 23 | |||
| 24 | #[derive(Debug, clap::Args)] | ||
| 25 | pub struct SubCommandArgs { | ||
| 26 | #[clap(short, long)] | ||
| 27 | /// name of repository | ||
| 28 | title: Option<String>, | ||
| 29 | #[clap(short, long)] | ||
| 30 | /// optional description | ||
| 31 | description: Option<String>, | ||
| 32 | #[clap(long)] | ||
| 33 | /// git server url users can clone from | ||
| 34 | clone_url: Vec<String>, | ||
| 35 | #[clap(short, long, value_parser, num_args = 1..)] | ||
| 36 | /// homepage | ||
| 37 | web: Vec<String>, | ||
| 38 | #[clap(short, long, value_parser, num_args = 1..)] | ||
| 39 | /// relays contributors push patches and comments to | ||
| 40 | relays: Vec<String>, | ||
| 41 | #[clap(short, long, value_parser, num_args = 1..)] | ||
| 42 | /// npubs of other maintainers | ||
| 43 | other_maintainers: Vec<String>, | ||
| 44 | #[clap(long)] | ||
| 45 | /// usually root commit but will be more recent commit for forks | ||
| 46 | earliest_unique_commit: Option<String>, | ||
| 47 | #[clap(short, long)] | ||
| 48 | /// shortname with no spaces or special characters | ||
| 49 | identifier: Option<String>, | ||
| 50 | } | ||
| 51 | |||
| 52 | #[allow(clippy::too_many_lines)] | ||
| 53 | pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | ||
| 54 | let git_repo = Repo::discover().context("cannot find a git repository")?; | ||
| 55 | let git_repo_path = git_repo.get_path()?; | ||
| 56 | |||
| 57 | let root_commit = git_repo | ||
| 58 | .get_root_commit() | ||
| 59 | .context("failed to get root commit of the repository")?; | ||
| 60 | |||
| 61 | // TODO: check for empty repo | ||
| 62 | // TODO: check for existing maintaiers file | ||
| 63 | |||
| 64 | #[cfg(not(test))] | ||
| 65 | let mut client = Client::default(); | ||
| 66 | #[cfg(test)] | ||
| 67 | let mut client = <MockConnect as std::default::Default>::default(); | ||
| 68 | |||
| 69 | let repo_coordinates = if let Ok(repo_coordinates) = | ||
| 70 | try_and_get_repo_coordinates(&git_repo, &client, false).await | ||
| 71 | { | ||
| 72 | Some(repo_coordinates) | ||
| 73 | } else { | ||
| 74 | None | ||
| 75 | }; | ||
| 76 | |||
| 77 | let repo_ref = if let Some(repo_coordinates) = repo_coordinates { | ||
| 78 | fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; | ||
| 79 | Some(get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?) | ||
| 80 | } else { | ||
| 81 | None | ||
| 82 | }; | ||
| 83 | |||
| 84 | let (signer, user_ref) = login::launch( | ||
| 85 | &git_repo, | ||
| 86 | &cli_args.bunker_uri, | ||
| 87 | &cli_args.bunker_app_key, | ||
| 88 | &cli_args.nsec, | ||
| 89 | &cli_args.password, | ||
| 90 | Some(&client), | ||
| 91 | false, | ||
| 92 | false, | ||
| 93 | ) | ||
| 94 | .await?; | ||
| 95 | |||
| 96 | let repo_config_result = get_repo_config_from_yaml(&git_repo); | ||
| 97 | // TODO: check for other claims | ||
| 98 | |||
| 99 | let name = match &args.title { | ||
| 100 | Some(t) => t.clone(), | ||
| 101 | None => Interactor::default().input( | ||
| 102 | PromptInputParms::default() | ||
| 103 | .with_prompt("name") | ||
| 104 | .with_default(if let Some(repo_ref) = &repo_ref { | ||
| 105 | repo_ref.name.clone() | ||
| 106 | } else { | ||
| 107 | String::new() | ||
| 108 | }), | ||
| 109 | )?, | ||
| 110 | }; | ||
| 111 | |||
| 112 | let identifier = match &args.identifier { | ||
| 113 | Some(t) => t.clone(), | ||
| 114 | None => Interactor::default().input( | ||
| 115 | PromptInputParms::default() | ||
| 116 | .with_prompt("identifier") | ||
| 117 | .with_default(if let Some(repo_ref) = &repo_ref { | ||
| 118 | repo_ref.identifier.clone() | ||
| 119 | } else { | ||
| 120 | let fallback = name | ||
| 121 | .clone() | ||
| 122 | .replace(' ', "-") | ||
| 123 | .chars() | ||
| 124 | .map(|c| { | ||
| 125 | if c.is_ascii_alphanumeric() || c.eq(&'/') { | ||
| 126 | c | ||
| 127 | } else { | ||
| 128 | '-' | ||
| 129 | } | ||
| 130 | }) | ||
| 131 | .collect(); | ||
| 132 | if let Ok(config) = &repo_config_result { | ||
| 133 | if let Some(identifier) = &config.identifier { | ||
| 134 | identifier.to_string() | ||
| 135 | } else { | ||
| 136 | fallback | ||
| 137 | } | ||
| 138 | } else { | ||
| 139 | fallback | ||
| 140 | } | ||
| 141 | }), | ||
| 142 | )?, | ||
| 143 | }; | ||
| 144 | |||
| 145 | let description = match &args.description { | ||
| 146 | Some(t) => t.clone(), | ||
| 147 | None => Interactor::default().input( | ||
| 148 | PromptInputParms::default() | ||
| 149 | .with_prompt("description") | ||
| 150 | .with_default(if let Some(repo_ref) = &repo_ref { | ||
| 151 | repo_ref.description.clone() | ||
| 152 | } else { | ||
| 153 | String::new() | ||
| 154 | }), | ||
| 155 | )?, | ||
| 156 | }; | ||
| 157 | |||
| 158 | let git_server = if args.clone_url.is_empty() { | ||
| 159 | Interactor::default() | ||
| 160 | .input( | ||
| 161 | PromptInputParms::default() | ||
| 162 | .with_prompt("clone url (for fetch)") | ||
| 163 | .with_default(if let Some(repo_ref) = &repo_ref { | ||
| 164 | repo_ref.git_server.clone().join(" ") | ||
| 165 | } else if let Ok(url) = git_repo.get_origin_url() { | ||
| 166 | if let Ok(fetch_url) = convert_clone_url_to_https(&url) { | ||
| 167 | fetch_url | ||
| 168 | } else { | ||
| 169 | // local repo or custom protocol | ||
| 170 | url | ||
| 171 | } | ||
| 172 | } else { | ||
| 173 | String::new() | ||
| 174 | }), | ||
| 175 | )? | ||
| 176 | .split(' ') | ||
| 177 | .map(std::string::ToString::to_string) | ||
| 178 | .collect() | ||
| 179 | } else { | ||
| 180 | args.clone_url.clone() | ||
| 181 | }; | ||
| 182 | |||
| 183 | let web: Vec<String> = if args.web.is_empty() { | ||
| 184 | Interactor::default() | ||
| 185 | .input( | ||
| 186 | PromptInputParms::default() | ||
| 187 | .with_prompt("web") | ||
| 188 | .optional() | ||
| 189 | .with_default(if let Some(repo_ref) = &repo_ref { | ||
| 190 | repo_ref.web.clone().join(" ") | ||
| 191 | } else { | ||
| 192 | format!("https://gitworkshop.dev/repo/{}", &identifier) | ||
| 193 | }), | ||
| 194 | )? | ||
| 195 | .split(' ') | ||
| 196 | .map(std::string::ToString::to_string) | ||
| 197 | .collect() | ||
| 198 | } else { | ||
| 199 | args.web.clone() | ||
| 200 | }; | ||
| 201 | |||
| 202 | let maintainers: Vec<PublicKey> = { | ||
| 203 | let mut dont_ask = !args.other_maintainers.is_empty(); | ||
| 204 | let mut maintainers_string = if !args.other_maintainers.is_empty() { | ||
| 205 | [args.other_maintainers.clone()].concat().join(" ") | ||
| 206 | } else if repo_ref.is_none() && repo_config_result.is_err() { | ||
| 207 | signer.public_key().await?.to_bech32()? | ||
| 208 | } else { | ||
| 209 | let maintainers = if let Ok(config) = &repo_config_result { | ||
| 210 | config.maintainers.clone() | ||
| 211 | } else if let Some(repo_ref) = &repo_ref { | ||
| 212 | repo_ref | ||
| 213 | .maintainers | ||
| 214 | .clone() | ||
| 215 | .iter() | ||
| 216 | .map(|k| k.to_bech32().unwrap()) | ||
| 217 | .collect() | ||
| 218 | } else { | ||
| 219 | //unreachable | ||
| 220 | vec![signer.public_key().await?.to_bech32()?] | ||
| 221 | }; | ||
| 222 | // add current user if not present | ||
| 223 | if maintainers.iter().any(|m| { | ||
| 224 | if let Ok(m_pubkey) = PublicKey::from_bech32(m) { | ||
| 225 | user_ref.public_key.eq(&m_pubkey) | ||
| 226 | } else { | ||
| 227 | false | ||
| 228 | } | ||
| 229 | }) { | ||
| 230 | maintainers.join(" ") | ||
| 231 | } else { | ||
| 232 | [maintainers, vec![signer.public_key().await?.to_bech32()?]] | ||
| 233 | .concat() | ||
| 234 | .join(" ") | ||
| 235 | } | ||
| 236 | }; | ||
| 237 | 'outer: loop { | ||
| 238 | if !dont_ask { | ||
| 239 | println!("{}", &maintainers_string); | ||
| 240 | maintainers_string = Interactor::default().input( | ||
| 241 | PromptInputParms::default() | ||
| 242 | .with_prompt("maintainers") | ||
| 243 | .with_default(maintainers_string), | ||
| 244 | )?; | ||
| 245 | } | ||
| 246 | let mut maintainers: Vec<PublicKey> = vec![]; | ||
| 247 | for m in maintainers_string.split(' ') { | ||
| 248 | if let Ok(m_pubkey) = PublicKey::from_bech32(m) { | ||
| 249 | maintainers.push(m_pubkey); | ||
| 250 | } else { | ||
| 251 | println!("not a valid set of npubs seperated by a space"); | ||
| 252 | dont_ask = false; | ||
| 253 | continue 'outer; | ||
| 254 | } | ||
| 255 | } | ||
| 256 | // add current user incase removed | ||
| 257 | if !maintainers.iter().any(|m| user_ref.public_key.eq(m)) { | ||
| 258 | maintainers.push(signer.public_key().await?); | ||
| 259 | } | ||
| 260 | break maintainers; | ||
| 261 | } | ||
| 262 | }; | ||
| 263 | |||
| 264 | // TODO: check if relays are free to post to so contributors can submit patches | ||
| 265 | // TODO: recommend some reliable free ones | ||
| 266 | let relays: Vec<String> = if args.relays.is_empty() { | ||
| 267 | Interactor::default() | ||
| 268 | .input( | ||
| 269 | PromptInputParms::default() | ||
| 270 | .with_prompt("relays") | ||
| 271 | .with_default(if let Ok(config) = &repo_config_result { | ||
| 272 | config.relays.clone().join(" ") | ||
| 273 | } else if let Some(repo_ref) = &repo_ref { | ||
| 274 | repo_ref.relays.clone().join(" ") | ||
| 275 | } else { | ||
| 276 | user_ref.relays.write().join(" ") | ||
| 277 | }), | ||
| 278 | )? | ||
| 279 | .split(' ') | ||
| 280 | .map(std::string::ToString::to_string) | ||
| 281 | .collect() | ||
| 282 | } else { | ||
| 283 | args.relays.clone() | ||
| 284 | }; | ||
| 285 | |||
| 286 | let earliest_unique_commit = match &args.earliest_unique_commit { | ||
| 287 | Some(t) => t.clone(), | ||
| 288 | None => { | ||
| 289 | let mut earliest_unique_commit = if let Some(repo_ref) = &repo_ref { | ||
| 290 | repo_ref.root_commit.clone() | ||
| 291 | } else { | ||
| 292 | root_commit.to_string() | ||
| 293 | }; | ||
| 294 | loop { | ||
| 295 | earliest_unique_commit = Interactor::default().input( | ||
| 296 | PromptInputParms::default() | ||
| 297 | .with_prompt("earliest unique commit") | ||
| 298 | .with_default(earliest_unique_commit.clone()), | ||
| 299 | )?; | ||
| 300 | if let Ok(exists) = git_repo.does_commit_exist(&earliest_unique_commit) { | ||
| 301 | if exists { | ||
| 302 | break earliest_unique_commit; | ||
| 303 | } | ||
| 304 | println!("commit does not exist on current repository"); | ||
| 305 | } else { | ||
| 306 | println!("commit id not formatted correctly"); | ||
| 307 | } | ||
| 308 | if earliest_unique_commit.len().ne(&40) { | ||
| 309 | println!("commit id must be 40 characters long"); | ||
| 310 | } | ||
| 311 | } | ||
| 312 | } | ||
| 313 | }; | ||
| 314 | |||
| 315 | println!("publishing repostory reference..."); | ||
| 316 | |||
| 317 | let repo_ref = RepoRef { | ||
| 318 | identifier: identifier.clone(), | ||
| 319 | name, | ||
| 320 | description, | ||
| 321 | root_commit: earliest_unique_commit, | ||
| 322 | git_server, | ||
| 323 | web, | ||
| 324 | relays: relays.clone(), | ||
| 325 | maintainers: maintainers.clone(), | ||
| 326 | events: HashMap::new(), | ||
| 327 | }; | ||
| 328 | let repo_event = repo_ref.to_event(&signer).await?; | ||
| 329 | |||
| 330 | client.set_signer(signer).await; | ||
| 331 | |||
| 332 | send_events( | ||
| 333 | &client, | ||
| 334 | git_repo_path, | ||
| 335 | vec![repo_event], | ||
| 336 | user_ref.relays.write(), | ||
| 337 | relays.clone(), | ||
| 338 | !cli_args.disable_cli_spinners, | ||
| 339 | false, | ||
| 340 | ) | ||
| 341 | .await?; | ||
| 342 | |||
| 343 | git_repo.save_git_config_item( | ||
| 344 | "nostr.repo", | ||
| 345 | &Coordinate { | ||
| 346 | kind: Kind::GitRepoAnnouncement, | ||
| 347 | public_key: user_ref.public_key, | ||
| 348 | identifier: identifier.clone(), | ||
| 349 | relays: vec![], | ||
| 350 | } | ||
| 351 | .to_bech32()?, | ||
| 352 | false, | ||
| 353 | )?; | ||
| 354 | |||
| 355 | // if yaml file doesnt exist or needs updating | ||
| 356 | if match &repo_config_result { | ||
| 357 | Ok(config) => { | ||
| 358 | !<std::option::Option<std::string::String> as Clone>::clone(&config.identifier) | ||
| 359 | .unwrap_or_default() | ||
| 360 | .eq(&identifier) | ||
| 361 | || !extract_pks(config.maintainers.clone())?.eq(&maintainers) | ||
| 362 | || !config.relays.eq(&relays) | ||
| 363 | } | ||
| 364 | Err(_) => true, | ||
| 365 | } { | ||
| 366 | save_repo_config_to_yaml( | ||
| 367 | &git_repo, | ||
| 368 | identifier.clone(), | ||
| 369 | maintainers.clone(), | ||
| 370 | relays.clone(), | ||
| 371 | )?; | ||
| 372 | println!( | ||
| 373 | "maintainers.yaml {}. commit and push.", | ||
| 374 | if repo_config_result.is_err() { | ||
| 375 | "created" | ||
| 376 | } else { | ||
| 377 | "updated" | ||
| 378 | } | ||
| 379 | ); | ||
| 380 | println!( | ||
| 381 | "this optional file helps in identifying who the maintainers are over time through the commit history" | ||
| 382 | ); | ||
| 383 | } | ||
| 384 | Ok(()) | ||
| 385 | } | ||