| 1 | //! `si browse [target]` — open something on sigit.si in the default browser. |
| 2 | //! |
| 3 | //! `target` accepts a bare `OWNER/NAME`, `OWNER/NAME/pull/N`, or |
| 4 | //! `OWNER/NAME/issue/N`; omitted, it falls back to the repository in the |
| 5 | //! current directory. |
| 6 | |
| 7 | use { |
| 8 | crate::{cli::BrowseArgs, context::Ctx, ui}, |
| 9 | anyhow::{anyhow, Result}, |
| 10 | }; |
| 11 | |
| 12 | pub async fn run(ctx: &Ctx, args: BrowseArgs) -> Result<()> { |
| 13 | let url = match args.target { |
| 14 | Some(target) => resolve_target(ctx, &target)?, |
| 15 | None => { |
| 16 | let slug = ctx.resolve_repo(None)?; |
| 17 | format!("{}/{slug}", ctx.client.base_url()) |
| 18 | } |
| 19 | }; |
| 20 | |
| 21 | open::that(&url)?; |
| 22 | ui::note(format!("Opened {url}")); |
| 23 | Ok(()) |
| 24 | } |
| 25 | |
| 26 | /// `OWNER/NAME`, `OWNER/NAME/pull/N`, or `OWNER/NAME/issue/N`. |
| 27 | fn resolve_target(ctx: &Ctx, target: &str) -> Result<String> { |
| 28 | let trimmed = target.trim().trim_matches('/'); |
| 29 | let parts: Vec<&str> = trimmed.split('/').collect(); |
| 30 | |
| 31 | match parts.as_slice() { |
| 32 | [owner, name] => Ok(format!("{}/{owner}/{name}", ctx.client.base_url())), |
| 33 | [owner, name, kind @ ("pull" | "issue"), number] => { |
| 34 | number |
| 35 | .parse::<u64>() |
| 36 | .map_err(|_| anyhow!("Expected a number after {kind}/, got {number:?}."))?; |
| 37 | Ok(format!( |
| 38 | "{}/{owner}/{name}/{kind}/{number}", |
| 39 | ctx.client.base_url() |
| 40 | )) |
| 41 | } |
| 42 | _ => Err(anyhow!( |
| 43 | "Could not parse {target:?}. Expected OWNER/NAME, OWNER/NAME/pull/N, or \ |
| 44 | OWNER/NAME/issue/N." |
| 45 | )), |
| 46 | } |
| 47 | } |