diff options
Diffstat (limited to 'src/bin/ngit')
| -rw-r--r-- | src/bin/ngit/cli.rs | 3 | ||||
| -rw-r--r-- | src/bin/ngit/main.rs | 2 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/init.rs | 4 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/repo/mod.rs | 219 |
4 files changed, 203 insertions, 25 deletions
diff --git a/src/bin/ngit/cli.rs b/src/bin/ngit/cli.rs index 5ee9165..5feaf31 100644 --- a/src/bin/ngit/cli.rs +++ b/src/bin/ngit/cli.rs | |||
| @@ -158,6 +158,9 @@ pub struct RepoSubCommandArgs { | |||
| 158 | /// Use local cache only, skip network fetch | 158 | /// Use local cache only, skip network fetch |
| 159 | #[arg(long)] | 159 | #[arg(long)] |
| 160 | pub offline: bool, | 160 | pub offline: bool, |
| 161 | /// Output repository info as JSON; is_nostr_repo is false when not in a nostr repository | ||
| 162 | #[arg(long)] | ||
| 163 | pub json: bool, | ||
| 161 | } | 164 | } |
| 162 | 165 | ||
| 163 | // --------------------------------------------------------------------------- | 166 | // --------------------------------------------------------------------------- |
diff --git a/src/bin/ngit/main.rs b/src/bin/ngit/main.rs index 28bf1da..8f3b0da 100644 --- a/src/bin/ngit/main.rs +++ b/src/bin/ngit/main.rs | |||
| @@ -51,7 +51,7 @@ async fn main() { | |||
| 51 | }, | 51 | }, |
| 52 | Commands::Init(args) => sub_commands::init::launch(&cli, args).await, | 52 | Commands::Init(args) => sub_commands::init::launch(&cli, args).await, |
| 53 | Commands::Repo(args) => { | 53 | Commands::Repo(args) => { |
| 54 | sub_commands::repo::launch(&cli, args.repo_command.as_ref(), args.offline).await | 54 | sub_commands::repo::launch(&cli, args.repo_command.as_ref(), args.offline, args.json).await |
| 55 | } | 55 | } |
| 56 | Commands::Send(args) => sub_commands::send::launch(&cli, args, false).await, | 56 | Commands::Send(args) => sub_commands::send::launch(&cli, args, false).await, |
| 57 | Commands::Pr(args) => match &args.pr_command { | 57 | Commands::Pr(args) => match &args.pr_command { |
diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs index 0554f32..85496ea 100644 --- a/src/bin/ngit/sub_commands/init.rs +++ b/src/bin/ngit/sub_commands/init.rs | |||
| @@ -400,8 +400,8 @@ fn validate_fresh(cli: &Cli, args: &SubCommandArgs, user_has_grasp_list: bool) - | |||
| 400 | 400 | ||
| 401 | #[derive(Debug, clap::Args)] | 401 | #[derive(Debug, clap::Args)] |
| 402 | pub struct SubCommandArgs { | 402 | pub struct SubCommandArgs { |
| 403 | #[clap(long)] | 403 | #[clap(long, alias = "title")] |
| 404 | /// name of repository (preferred over --identifier) | 404 | /// name of repository (preferred over --identifier); --title is an alias |
| 405 | name: Option<String>, | 405 | name: Option<String>, |
| 406 | #[clap(long)] | 406 | #[clap(long)] |
| 407 | /// shortname with no spaces or special characters | 407 | /// shortname with no spaces or special characters |
diff --git a/src/bin/ngit/sub_commands/repo/mod.rs b/src/bin/ngit/sub_commands/repo/mod.rs index 97e2c2b..63d96bd 100644 --- a/src/bin/ngit/sub_commands/repo/mod.rs +++ b/src/bin/ngit/sub_commands/repo/mod.rs | |||
| @@ -14,6 +14,7 @@ use ngit::{ | |||
| 14 | utils::get_short_git_server_name, | 14 | utils::get_short_git_server_name, |
| 15 | }; | 15 | }; |
| 16 | use nostr::{FromBech32, PublicKey, TagStandard, ToBech32, nips::nip19::Nip19Coordinate}; | 16 | use nostr::{FromBech32, PublicKey, TagStandard, ToBech32, nips::nip19::Nip19Coordinate}; |
| 17 | use serde::Serialize; | ||
| 17 | 18 | ||
| 18 | use crate::{ | 19 | use crate::{ |
| 19 | cli::{Cli, RepoCommands, extract_signer_cli_arguments}, | 20 | cli::{Cli, RepoCommands, extract_signer_cli_arguments}, |
| @@ -27,21 +28,53 @@ pub async fn launch( | |||
| 27 | cli_args: &Cli, | 28 | cli_args: &Cli, |
| 28 | repo_command: Option<&RepoCommands>, | 29 | repo_command: Option<&RepoCommands>, |
| 29 | offline: bool, | 30 | offline: bool, |
| 31 | json: bool, | ||
| 30 | ) -> Result<()> { | 32 | ) -> Result<()> { |
| 31 | match repo_command { | 33 | match repo_command { |
| 32 | Some(RepoCommands::Init(args) | RepoCommands::Edit(args)) => { | 34 | Some(RepoCommands::Init(args) | RepoCommands::Edit(args)) => { |
| 33 | init::launch(cli_args, args).await | 35 | init::launch(cli_args, args).await |
| 34 | } | 36 | } |
| 35 | Some(RepoCommands::Accept(args)) => accept::launch(cli_args, args).await, | 37 | Some(RepoCommands::Accept(args)) => accept::launch(cli_args, args).await, |
| 36 | None => show_info(cli_args, offline).await, | 38 | None => show_info(cli_args, offline, json).await, |
| 37 | } | 39 | } |
| 38 | } | 40 | } |
| 39 | 41 | ||
| 40 | // --------------------------------------------------------------------------- | 42 | // --------------------------------------------------------------------------- |
| 43 | // JSON output types | ||
| 44 | // --------------------------------------------------------------------------- | ||
| 45 | |||
| 46 | #[derive(Serialize)] | ||
| 47 | struct RepoInfoJson { | ||
| 48 | is_nostr_repo: bool, | ||
| 49 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 50 | name: Option<String>, | ||
| 51 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 52 | identifier: Option<String>, | ||
| 53 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 54 | description: Option<String>, | ||
| 55 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 56 | nostr_url: Option<String>, | ||
| 57 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 58 | coordinate: Option<String>, | ||
| 59 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 60 | web: Option<Vec<String>>, | ||
| 61 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 62 | maintainers: Option<Vec<String>>, | ||
| 63 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 64 | grasp_servers: Option<Vec<String>>, | ||
| 65 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 66 | git_servers: Option<Vec<String>>, | ||
| 67 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 68 | relays: Option<Vec<String>>, | ||
| 69 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 70 | hashtags: Option<Vec<String>>, | ||
| 71 | } | ||
| 72 | |||
| 73 | // --------------------------------------------------------------------------- | ||
| 41 | // `ngit repo` (no subcommand) — show repository info | 74 | // `ngit repo` (no subcommand) — show repository info |
| 42 | // --------------------------------------------------------------------------- | 75 | // --------------------------------------------------------------------------- |
| 43 | 76 | ||
| 44 | async fn show_info(cli_args: &Cli, offline: bool) -> Result<()> { | 77 | async fn show_info(cli_args: &Cli, offline: bool, json: bool) -> Result<()> { |
| 45 | let git_repo = Repo::discover().context("failed to find a git repository")?; | 78 | let git_repo = Repo::discover().context("failed to find a git repository")?; |
| 46 | let git_repo_path = git_repo.get_path()?; | 79 | let git_repo_path = git_repo.get_path()?; |
| 47 | let client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo))); | 80 | let client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo))); |
| @@ -61,15 +94,31 @@ async fn show_info(cli_args: &Cli, offline: bool) -> Result<()> { | |||
| 61 | .ok() | 94 | .ok() |
| 62 | .map(|(_, user_ref, _)| user_ref.public_key); | 95 | .map(|(_, user_ref, _)| user_ref.public_key); |
| 63 | 96 | ||
| 64 | println!("subcommands: init, edit, accept (run `ngit repo --help` for details)"); | ||
| 65 | println!(); | ||
| 66 | |||
| 67 | let repo_coordinate = (try_and_get_repo_coordinates_when_remote_unknown(&git_repo).await).ok(); | 97 | let repo_coordinate = (try_and_get_repo_coordinates_when_remote_unknown(&git_repo).await).ok(); |
| 68 | 98 | ||
| 69 | let Some(repo_coordinate) = repo_coordinate else { | 99 | let Some(repo_coordinate) = repo_coordinate else { |
| 70 | println!("no nostr repository found"); | 100 | if json { |
| 71 | println!(); | 101 | println!("{}", serde_json::to_string_pretty(&RepoInfoJson { |
| 72 | println!("use `ngit repo init` to publish this repository to nostr"); | 102 | is_nostr_repo: false, |
| 103 | name: None, | ||
| 104 | identifier: None, | ||
| 105 | description: None, | ||
| 106 | nostr_url: None, | ||
| 107 | coordinate: None, | ||
| 108 | web: None, | ||
| 109 | maintainers: None, | ||
| 110 | grasp_servers: None, | ||
| 111 | git_servers: None, | ||
| 112 | relays: None, | ||
| 113 | hashtags: None, | ||
| 114 | })?); | ||
| 115 | } else { | ||
| 116 | println!("subcommands: init, edit, accept (run `ngit repo --help` for details)"); | ||
| 117 | println!(); | ||
| 118 | println!("no nostr repository found"); | ||
| 119 | println!(); | ||
| 120 | println!("use `ngit repo init` to publish this repository to nostr"); | ||
| 121 | } | ||
| 73 | return Ok(()); | 122 | return Ok(()); |
| 74 | }; | 123 | }; |
| 75 | 124 | ||
| @@ -83,23 +132,149 @@ async fn show_info(cli_args: &Cli, offline: bool) -> Result<()> { | |||
| 83 | let Some(repo_ref) = | 132 | let Some(repo_ref) = |
| 84 | (get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinate).await).ok() | 133 | (get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinate).await).ok() |
| 85 | else { | 134 | else { |
| 86 | println!( | 135 | if json { |
| 87 | "coordinate found ({}) but no announcement on relays", | 136 | // Coordinate found but no announcement yet — still a nostr repo |
| 88 | repo_coordinate.identifier | 137 | let nostr_url = git_repo |
| 89 | ); | 138 | .git_repo |
| 90 | println!(); | 139 | .find_remote("origin") |
| 91 | println!("if you created this repository, run `ngit repo init` to publish an announcement"); | 140 | .ok() |
| 92 | println!("if you are a co-maintainer, run `ngit repo accept` to publish your announcement"); | 141 | .and_then(|r| r.url().map(std::string::ToString::to_string)) |
| 142 | .filter(|u| u.starts_with("nostr://")); | ||
| 143 | println!("{}", serde_json::to_string_pretty(&RepoInfoJson { | ||
| 144 | is_nostr_repo: true, | ||
| 145 | name: None, | ||
| 146 | identifier: Some(repo_coordinate.identifier.clone()), | ||
| 147 | description: None, | ||
| 148 | nostr_url, | ||
| 149 | coordinate: repo_coordinate.to_bech32().ok(), | ||
| 150 | web: None, | ||
| 151 | maintainers: None, | ||
| 152 | grasp_servers: None, | ||
| 153 | git_servers: None, | ||
| 154 | relays: None, | ||
| 155 | hashtags: None, | ||
| 156 | })?); | ||
| 157 | } else { | ||
| 158 | println!("subcommands: init, edit, accept (run `ngit repo --help` for details)"); | ||
| 159 | println!(); | ||
| 160 | println!( | ||
| 161 | "coordinate found ({}) but no announcement on relays", | ||
| 162 | repo_coordinate.identifier | ||
| 163 | ); | ||
| 164 | println!(); | ||
| 165 | println!("if you created this repository, run `ngit repo init` to publish an announcement"); | ||
| 166 | println!("if you are a co-maintainer, run `ngit repo accept` to publish your announcement"); | ||
| 167 | } | ||
| 93 | return Ok(()); | 168 | return Ok(()); |
| 94 | }; | 169 | }; |
| 95 | 170 | ||
| 96 | print_repo_info( | 171 | if json { |
| 97 | &repo_ref, | 172 | print_repo_info_json(&repo_ref, &repo_coordinate, &git_repo)?; |
| 98 | my_pubkey.as_ref(), | 173 | } else { |
| 99 | &repo_coordinate, | 174 | println!("subcommands: init, edit, accept (run `ngit repo --help` for details)"); |
| 100 | git_repo_path, | 175 | println!(); |
| 101 | ) | 176 | print_repo_info( |
| 102 | .await; | 177 | &repo_ref, |
| 178 | my_pubkey.as_ref(), | ||
| 179 | &repo_coordinate, | ||
| 180 | git_repo_path, | ||
| 181 | ) | ||
| 182 | .await; | ||
| 183 | } | ||
| 184 | Ok(()) | ||
| 185 | } | ||
| 186 | |||
| 187 | fn print_repo_info_json( | ||
| 188 | repo_ref: &RepoRef, | ||
| 189 | coordinate: &Nip19Coordinate, | ||
| 190 | git_repo: &Repo, | ||
| 191 | ) -> Result<()> { | ||
| 192 | let nostr_url = git_repo | ||
| 193 | .git_repo | ||
| 194 | .find_remote("origin") | ||
| 195 | .ok() | ||
| 196 | .and_then(|r| r.url().map(std::string::ToString::to_string)) | ||
| 197 | .filter(|u| u.starts_with("nostr://")); | ||
| 198 | |||
| 199 | let grasp_servers: Vec<String> = repo_ref | ||
| 200 | .git_server | ||
| 201 | .iter() | ||
| 202 | .filter(|s| is_grasp_server_clone_url(s)) | ||
| 203 | .filter_map(|s| normalize_grasp_server_url(s).ok()) | ||
| 204 | .collect(); | ||
| 205 | |||
| 206 | let git_servers: Vec<String> = repo_ref | ||
| 207 | .git_server | ||
| 208 | .iter() | ||
| 209 | .filter(|s| !is_grasp_server_clone_url(s)) | ||
| 210 | .cloned() | ||
| 211 | .collect(); | ||
| 212 | |||
| 213 | let grasp_relay_urls: Vec<String> = repo_ref | ||
| 214 | .git_server | ||
| 215 | .iter() | ||
| 216 | .filter(|s| is_grasp_server_clone_url(s)) | ||
| 217 | .filter_map(|s| format_grasp_server_url_as_relay_url(s).ok()) | ||
| 218 | .collect(); | ||
| 219 | |||
| 220 | let relays: Vec<String> = repo_ref | ||
| 221 | .relays | ||
| 222 | .iter() | ||
| 223 | .filter(|r| { | ||
| 224 | let r_str = r.as_str().trim_end_matches('/'); | ||
| 225 | !grasp_relay_urls | ||
| 226 | .iter() | ||
| 227 | .any(|g| g.trim_end_matches('/') == r_str) | ||
| 228 | }) | ||
| 229 | .map(std::string::ToString::to_string) | ||
| 230 | .collect(); | ||
| 231 | |||
| 232 | let maintainers: Vec<String> = repo_ref | ||
| 233 | .maintainers | ||
| 234 | .iter() | ||
| 235 | .filter_map(|pk| pk.to_bech32().ok()) | ||
| 236 | .collect(); | ||
| 237 | |||
| 238 | let info = RepoInfoJson { | ||
| 239 | is_nostr_repo: true, | ||
| 240 | name: Some(repo_ref.name.clone()), | ||
| 241 | identifier: Some(repo_ref.identifier.clone()), | ||
| 242 | description: if repo_ref.description.is_empty() { | ||
| 243 | None | ||
| 244 | } else { | ||
| 245 | Some(repo_ref.description.clone()) | ||
| 246 | }, | ||
| 247 | nostr_url, | ||
| 248 | coordinate: coordinate.to_bech32().ok(), | ||
| 249 | web: if repo_ref.web.is_empty() { | ||
| 250 | None | ||
| 251 | } else { | ||
| 252 | Some(repo_ref.web.clone()) | ||
| 253 | }, | ||
| 254 | maintainers: Some(maintainers), | ||
| 255 | grasp_servers: if grasp_servers.is_empty() { | ||
| 256 | None | ||
| 257 | } else { | ||
| 258 | Some(grasp_servers) | ||
| 259 | }, | ||
| 260 | git_servers: if git_servers.is_empty() { | ||
| 261 | None | ||
| 262 | } else { | ||
| 263 | Some(git_servers) | ||
| 264 | }, | ||
| 265 | relays: if relays.is_empty() { | ||
| 266 | None | ||
| 267 | } else { | ||
| 268 | Some(relays) | ||
| 269 | }, | ||
| 270 | hashtags: if repo_ref.hashtags.is_empty() { | ||
| 271 | None | ||
| 272 | } else { | ||
| 273 | Some(repo_ref.hashtags.clone()) | ||
| 274 | }, | ||
| 275 | }; | ||
| 276 | |||
| 277 | println!("{}", serde_json::to_string_pretty(&info)?); | ||
| 103 | Ok(()) | 278 | Ok(()) |
| 104 | } | 279 | } |
| 105 | 280 | ||