From 054155158cc82232b0acbb0e156a3773e1554d9d Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:58:43 -0800 Subject: [PATCH] Add URL Parsing --- .github/workflows/release.yaml | 2 +- README.md | 35 ++++++--- Taskfile.yml | 4 ++ cmd/info.go | 6 +- cmd/install.go | 128 +++++++++++++++++++++------------ docs/index.md | 39 ++++++---- go.mod | 2 +- go.sum | 4 +- 8 files changed, 147 insertions(+), 73 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f74c440..cc2d7b8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -59,7 +59,7 @@ jobs: - name: "GoReleaser" id: go - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 env: #GITHUB_TOKEN: ${{ steps.app.outputs.token }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index f32b8d6..0ff6f9b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GitHub Release Version](https://img.shields.io/github/v/release/smashedr/install-release?logo=github)](https://github.com/smashedr/install-release/releases) [![GitHub Downloads](https://img.shields.io/github/downloads/smashedr/install-release/total?logo=rolldown&logoColor=white)](https://github.com/smashedr/install-release/releases/latest) -[![Asset Size](https://badges.cssnr.com/gh/release/smashedr/install-release/latest/asset/ir_Windows_x86_64.zip/size?label=asset&lucide=file-archive)](https://github.com/smashedr/install-release/releases/latest) -[![Image Size](https://badges.cssnr.com/ghcr/size/smashedr/install-release?label=docker)](https://github.com/smashedr/install-release/pkgs/container/install-release) +[![Asset Size](https://badges.cssnr.com/gh/release/smashedr/install-release/latest/asset/ir_Windows_x86_64.zip/size?label=asset&lucide=file-archive&color=darkgreen)](https://github.com/smashedr/install-release/releases/latest) +[![Image Size](https://badges.cssnr.com/ghcr/size/smashedr/install-release?label=docker&color=darkgreen)](https://github.com/smashedr/install-release/pkgs/container/install-release) [![Go Version](https://img.shields.io/github/go-mod/go-version/smashedr/install-release?logo=go&logoColor=white&label=go)](https://github.com/smashedr/install-release/blob/master/go.mod) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=smashedr_install-release&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=smashedr_install-release) [![Deployment Docs](https://img.shields.io/github/deployments/smashedr/install-release/docs?logo=materialformkdocs&logoColor=white&label=docs)](https://github.com/smashedr/install-release/deployments/docs) @@ -125,18 +125,33 @@ Install a specific version/tag. ir owner/repo v1.0.0 ``` -Install using many formats: +Specify repo in any format: ```shell -ir owner repo -ir owner/repo -ir owner repo tag -ir owner/repo tag -ir owner/repo/tag -ir owner/repo:tag -ir owner/repo@tag +ir owner[/ ]repo +ir owner[/ ]repo[@:/ ]tag +ir github.com/owner[/ ]repo[@:/ ]tag +ir https://github.com/owner[/ ]repo[@:/ ]tag +``` + +
View Examples + +```shell +ir smashedr bup +ir smashedr/bup +ir smashedr bup latest +ir smashedr/bup latest +ir smashedr/bup/latest +ir smashedr/bup:latest +ir smashedr/bup@latest +ir github.com/smashedr/bup +ir github.com/smashedr/bup/latest +ir https://github.com/smashedr/bup +ir https://github.com/smashedr/bup@latest ``` +
+ Skip the asset and name prompts. ```shell diff --git a/Taskfile.yml b/Taskfile.yml index b606a91..77593cc 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -16,6 +16,10 @@ tasks: - go get -u - task: tidy + actions: + desc: Actions Up + cmd: actions-up --exclude "cssnr/.*,actions/.*,docker/.*" --yes + lint: desc: Lint cmds: diff --git a/cmd/info.go b/cmd/info.go index 5e5ea9c..53697b6 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -24,14 +24,14 @@ var infoCmd = &cobra.Command{ log.Debug("infoCmd", "args", args, "binPath", binPath, "preRelease", preRelease) if len(args) >= 1 && strings.Contains(args[0], "/") { - owner, repo, tag, err := parseRepository(args) + repo, err := parseRepository(args) if err != nil { _ = cmd.Help() log.Fatal(err) } - log.Info("Repository", "owner", owner, "repo", repo, "tag", tag) + log.Info("Repository", "repo", repo) client := getClient() - release, err := getRelease(client, owner, repo, tag, preRelease, true) + release, err := getRelease(client, repo, preRelease, true) if err != nil { log.Fatalf("Error getting release: %v", err) } diff --git a/cmd/install.go b/cmd/install.go index 6cd48ba..4250012 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -17,6 +17,7 @@ import ( "io" "io/fs" "net/http" + "net/url" "os" "path/filepath" "runtime" @@ -30,6 +31,12 @@ var archAliases = map[string][]string{ "arm64": {"arm64", "aarch64"}, } +type Repository struct { + Owner string + Name string + Tag string +} + func runInstall(cmd *cobra.Command, args []string) error { // NOSONAR cmd.SilenceUsage = true // set here so subcommands do not silence usage binPath := viper.GetString("bin") @@ -45,28 +52,29 @@ func runInstall(cmd *cobra.Command, args []string) error { // NOSONAR return fmt.Errorf("repository must be in format: owner/repo") } - owner, repo, tag, err := parseRepository(args) + repo, err := parseRepository(args) if err != nil { + log.Debugf("parseRepository err: %v", err) _ = cmd.Help() return err } - log.Info("Repository", "owner", owner, "repo", repo, "tag", tag) + log.Info("Repository", "repo", repo) log.Info("runtime", "GOOS", runtime.GOOS, "GOARCH", runtime.GOARCH) - tagDisplay := tag - if tag == "" { + tagDisplay := repo.Tag + if repo.Tag == "" { if preRelease { tagDisplay = "pre-release" } else { tagDisplay = "latest" } } - styles.PrintKV("Repository", fmt.Sprintf("%s/%s:%s", owner, repo, tagDisplay)) + styles.PrintKV("Repository", fmt.Sprintf("%s/%s:%s", repo.Owner, repo.Name, tagDisplay)) client := getClient() - release, err := getRelease(client, owner, repo, tag, preRelease, skipPrompts) + release, err := getRelease(client, repo, preRelease, skipPrompts) if err != nil { return fmt.Errorf("get release error: %w", err) } @@ -120,7 +128,7 @@ func runInstall(cmd *cobra.Command, args []string) error { // NOSONAR styles.PrintKV("Asset Name", asset.GetName()) rc, _, err := client.Repositories.DownloadReleaseAsset( - context.Background(), owner, repo, asset.GetID(), http.DefaultClient, + context.Background(), repo.Owner, repo.Name, asset.GetID(), http.DefaultClient, ) if err != nil { return err @@ -395,22 +403,22 @@ func getClient() *github.Client { return github.NewClient(httpClient) } -func getRelease(client *github.Client, owner, repo, tag string, pre, skip bool) (*github.RepositoryRelease, error) { +func getRelease(client *github.Client, repo Repository, pre, skip bool) (*github.RepositoryRelease, error) { ctx := context.Background() var release *github.RepositoryRelease var err error - if tag != "" { - log.Debugf("client.Repositories.GetReleaseByTag: %v", tag) - release, _, err = client.Repositories.GetReleaseByTag(ctx, owner, repo, tag) + if repo.Tag != "" { + log.Debugf("client.Repositories.GetReleaseByTag: %v", repo.Tag) + release, _, err = client.Repositories.GetReleaseByTag(ctx, repo.Owner, repo.Name, repo.Tag) } else if pre { log.Debugf("GetLatestRelease") - release, err = getLatestRelease(client, owner, repo) + release, err = getLatestRelease(client, repo) } else if skip { log.Debugf("client.Repositories.GetLatestRelease") - release, _, err = client.Repositories.GetLatestRelease(ctx, owner, repo) + release, _, err = client.Repositories.GetLatestRelease(ctx, repo.Owner, repo.Name) } else { log.Debugf("chooseRelease") - release, err = chooseRelease(client, owner, repo, 30) + release, err = chooseRelease(client, repo, 30) } if err != nil { return nil, fmt.Errorf("get release error: %w", err) @@ -418,8 +426,8 @@ func getRelease(client *github.Client, owner, repo, tag string, pre, skip bool) return release, nil } -func getLatestRelease(client *github.Client, owner, repo string) (*github.RepositoryRelease, error) { - releases, err := getReleases(client, owner, repo, 1) +func getLatestRelease(client *github.Client, repo Repository) (*github.RepositoryRelease, error) { + releases, err := getReleases(client, repo, 1) if err != nil { return nil, err } @@ -429,17 +437,17 @@ func getLatestRelease(client *github.Client, owner, repo string) (*github.Reposi return releases[0], nil } -func getReleases(client *github.Client, owner, repo string, number int) ([]*github.RepositoryRelease, error) { +func getReleases(client *github.Client, repo Repository, number int) ([]*github.RepositoryRelease, error) { ctx := context.Background() - releases, _, err := client.Repositories.ListReleases(ctx, owner, repo, &github.ListOptions{PerPage: number}) + releases, _, err := client.Repositories.ListReleases(ctx, repo.Owner, repo.Name, &github.ListOptions{PerPage: number}) if err != nil { return nil, err } return releases, nil } -func chooseRelease(client *github.Client, owner, repo string, number int) (*github.RepositoryRelease, error) { - releases, err := getReleases(client, owner, repo, number) +func chooseRelease(client *github.Client, repo Repository, number int) (*github.RepositoryRelease, error) { + releases, err := getReleases(client, repo, number) if err != nil { return nil, fmt.Errorf("error getting releases: %w", err) } @@ -477,52 +485,84 @@ func ensureWinExt(destName string) string { return destName } -func parseRepository(args []string) (owner, repo, tag string, err error) { +func parseRepository(args []string) (repo Repository, err error) { helpErr := errors.New("repository format: owner/repo[:tag]") log.Debugf("parseRepository %v: %v", len(args), args) + switch len(args) { case 0: - return "", "", "", helpErr + return repo, helpErr case 1: - repository := args[0] + // Parse URL + parsed := parseURL(args[0]) + log.Debug("URL", "args[0]", args[0], "parsed", parsed) + fullName := parsed // Check for :tag @tag /tag - if idx := strings.IndexAny(args[0], ":@"); idx != -1 { + if idx := strings.IndexAny(parsed, ":@"); idx != -1 { log.Debugf("idx: %v", idx) - repository = args[0][:idx] - tag = args[0][idx+1:] - } else if strings.Count(args[0], "/") == 2 { - split := strings.Split(args[0], "/") + fullName = parsed[:idx] + repo.Tag = parsed[idx+1:] + } else if strings.Count(parsed, "/") == 2 { + split := strings.Split(parsed, "/") if split[2] != "" { - repository = split[0] + "/" + split[1] - tag = split[2] + fullName = split[0] + "/" + split[1] + repo.Tag = split[2] } } // Set owner/repo - split := strings.Split(repository, "/") + split := strings.Split(fullName, "/") if len(split) != 2 { - return "", "", "", helpErr + return repo, helpErr } - owner = split[0] - repo = split[1] + repo.Owner = split[0] + repo.Name = split[1] case 2: if strings.Contains(args[0], "/") { split := strings.Split(args[0], "/") - owner = split[0] - repo = split[1] - tag = args[1] + repo.Owner = split[0] + repo.Name = split[1] + repo.Tag = args[1] } else { - owner = args[0] - repo = args[1] + repo.Owner = args[0] + repo.Name = args[1] } default: - owner = args[0] - repo = args[1] - tag = args[2] + repo.Owner = args[0] + repo.Name = args[1] + repo.Tag = args[2] } - if owner == "" || repo == "" { + if repo.Owner == "" || repo.Name == "" { log.Infof("owner/repo are blank") - return "", "", "", helpErr + return repo, helpErr } return } + +func parseURL(original string) string { + log.Debugf("parseURL: %v", original) + u, err := url.Parse(original) + if err != nil { + log.Debug(err) + return original + } + u.Path = strings.TrimLeft(u.Path, "/") + log.Debug("Original", "Host", u.Host, "Path", u.Path) + + if u.Host == "" && strings.HasPrefix(strings.ToLower(u.Path), "github.com/") { + u.Scheme = "https" + u.Host = "github.com" + u.Path = u.Path[11:] + } + + log.Debug("Updated", "Host", u.Host, "Path", u.Path) + + split := strings.Split(strings.TrimRight(u.Path, "/"), "/") + log.Debugf("split: %v", split) + count := len(split) + log.Debugf("count: %v", count) + if count < 2 { + return original + } + return u.Path +} diff --git a/docs/index.md b/docs/index.md index 83293d0..4eacbc8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,8 +10,8 @@ icon: lucide/rocket [![GitHub Release Version](https://img.shields.io/github/v/release/smashedr/install-release?logo=github)](https://github.com/smashedr/install-release/releases) [![GitHub Downloads](https://img.shields.io/github/downloads/smashedr/install-release/total?logo=rolldown&logoColor=white)](https://github.com/smashedr/install-release/releases/latest) -[![APK Size](https://badges.cssnr.com/gh/release/smashedr/install-release/latest/asset/ir_Windows_x86_64.zip/size?label=asset&lucide=file-archive)](https://github.com/smashedr/install-release/releases/latest) -[![Image Size](https://badges.cssnr.com/ghcr/size/smashedr/install-release?label=docker)](https://github.com/smashedr/install-release/pkgs/container/install-release) +[![Asset Size](https://badges.cssnr.com/gh/release/smashedr/install-release/latest/asset/ir_Windows_x86_64.zip/size?label=asset&lucide=file-archive&color=darkgreen)](https://github.com/smashedr/install-release/releases/latest) +[![Image Size](https://badges.cssnr.com/ghcr/size/smashedr/install-release?label=docker&color=darkgreen)](https://github.com/smashedr/install-release/pkgs/container/install-release) [![Go Version](https://img.shields.io/github/go-mod/go-version/smashedr/install-release?logo=go&logoColor=white&label=go)](https://github.com/smashedr/install-release/blob/master/go.mod) [![GitHub Last Commit](https://img.shields.io/github/last-commit/smashedr/install-release?logo=listenhub&label=updated)](https://github.com/smashedr/install-release/pulse) [![GitHub Repo Size](https://img.shields.io/github/repo-size/smashedr/install-release?logo=googlecloudstorage&logoColor=white&label=repo%20size)](https://github.com/smashedr/install-release?tab=readme-ov-file#readme) @@ -56,8 +56,8 @@ If you run into any issues or have any questions, [support](support.md) is avail --8<-- "docs/snippets/install.md" -[![Latest Release](https://img.shields.io/github/v/release/smashedr/install-release?style=for-the-badge&logo=github&label=latest%20version)](https://github.com/smashedr/install-release/releases/latest) -[![Latest Pre-Release](https://img.shields.io/github/v/release/smashedr/install-release?style=for-the-badge&logo=github&include_prereleases&label=pre-release)](https://github.com/smashedr/install-release/releases) +[![Latest Release](https://img.shields.io/github/v/release/smashedr/install-release?style=for-the-badge&logo=github&label=latest%20version&color=blue)](https://github.com/smashedr/install-release/releases/latest) +[![Latest Pre-Release](https://img.shields.io/github/v/release/smashedr/install-release?style=for-the-badge&logo=github&include_prereleases&label=pre-release&color=orange)](https://github.com/smashedr/install-release/releases) ## :lucide-terminal-square: Usage @@ -79,18 +79,33 @@ Install a specific version/tag. ir owner/repo v1.0.0 ``` -Install using many formats: +Specify repo in any format: ```shell -ir owner repo -ir owner/repo -ir owner repo tag -ir owner/repo tag -ir owner/repo/tag -ir owner/repo:tag -ir owner/repo@tag +ir owner[/ ]repo +ir owner[/ ]repo[@:/ ]tag +ir github.com/owner[/ ]repo[@:/ ]tag +ir https://github.com/owner[/ ]repo[@:/ ]tag +``` + +
View Examples + +```shell +ir smashedr bup +ir smashedr/bup +ir smashedr bup latest +ir smashedr/bup latest +ir smashedr/bup/latest +ir smashedr/bup:latest +ir smashedr/bup@latest +ir github.com/smashedr/bup +ir github.com/smashedr/bup/latest +ir https://github.com/smashedr/bup +ir https://github.com/smashedr/bup@latest ``` +
+ Skip the asset and name prompts. ```shell diff --git a/go.mod b/go.mod index a0addc0..56adb92 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/exp/strings v0.1.0 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect - github.com/clipperhouse/displaywidth v0.10.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect diff --git a/go.sum b/go.sum index 9a85dd8..15e6061 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= -github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= -github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/confluentinc/go-editor v0.11.0 h1:fcEALYHj7xV/fRSp54/IHi2DS4GlZMJWVgrYvi/llvU=