summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCarlos A Becker <caarlos0@users.noreply.github.com>2023-01-26 14:47:20 -0300
committerCarlos A Becker <caarlos0@users.noreply.github.com>2023-01-26 14:47:20 -0300
commit6fc03ff86e9c6888c9c36f6a3f24e07974236d14 (patch)
tree9b4c948976b3ee66e56ae4d3aa1297dca2cec432
parentbe60a0fb727fc65c778fde3c6e37d13065389a7e (diff)
feat: improve gitlab/github readme urlurls
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
-rw-r--r--github.go30
-rw-r--r--gitlab.go38
-rw-r--r--main.go16
-rw-r--r--url.go80
-rw-r--r--url_test.go29
5 files changed, 134 insertions, 59 deletions
diff --git a/github.go b/github.go
index d3d2084..6b169fa 100644
--- a/github.go
+++ b/github.go
@@ -3,40 +3,29 @@ package main
import (
"encoding/json"
"errors"
+ "fmt"
"io"
"net/http"
"net/url"
"strings"
)
-// isGitHubURL tests a string to determine if it is a well-structured GitHub URL.
-func isGitHubURL(s string) (string, bool) {
- if strings.HasPrefix(s, "github.com/") {
- s = "https://" + s
- }
-
- u, err := url.ParseRequestURI(s)
- if err != nil {
- return "", false
- }
-
- return u.String(), strings.ToLower(u.Host) == "github.com"
-}
-
// findGitHubREADME tries to find the correct README filename in a repository using GitHub API.
-func findGitHubREADME(s string) (*source, error) {
- sSplit := strings.Split(s, "/")
- owner, repo := sSplit[3], sSplit[4]
+func findGitHubREADME(u *url.URL) (*source, error) {
+ owner, repo, ok := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
+ if !ok {
+ return nil, fmt.Errorf("invalid url: %s", u.String())
+ }
type readme struct {
DownloadURL string `json:"download_url"`
}
- apiURL := "https://api.github.com/repos/" + owner + "/" + repo + "/readme"
+ apiURL := fmt.Sprintf("https://api.%s/repos/%s/%s/readme", u.Hostname(), owner, repo)
// nolint:bodyclose
// it is closed on the caller
- res, err := http.Get(apiURL)
+ res, err := http.Get(apiURL) // nolint: gosec
if err != nil {
return nil, err
}
@@ -47,8 +36,7 @@ func findGitHubREADME(s string) (*source, error) {
}
var result readme
- jsonErr := json.Unmarshal(body, &result)
- if jsonErr != nil {
+ if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
diff --git a/gitlab.go b/gitlab.go
index 81fcf84..05e1239 100644
--- a/gitlab.go
+++ b/gitlab.go
@@ -3,42 +3,31 @@ package main
import (
"encoding/json"
"errors"
+ "fmt"
"io"
"net/http"
"net/url"
"strings"
)
-// isGitLabURL tests a string to determine if it is a well-structured GitLab URL.
-func isGitLabURL(s string) (string, bool) {
- if strings.HasPrefix(s, "gitlab.com/") {
- s = "https://" + s
- }
-
- u, err := url.ParseRequestURI(s)
- if err != nil {
- return "", false
- }
-
- return u.String(), strings.ToLower(u.Host) == "gitlab.com"
-}
-
// findGitLabREADME tries to find the correct README filename in a repository using GitLab API.
-func findGitLabREADME(s string) (*source, error) {
- sSplit := strings.Split(s, "/")
- owner, repo := sSplit[3], sSplit[4]
+func findGitLabREADME(u *url.URL) (*source, error) {
+ owner, repo, ok := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
+ if !ok {
+ return nil, fmt.Errorf("invalid url: %s", u.String())
+ }
projectPath := url.QueryEscape(owner + "/" + repo)
type readme struct {
- ReadmeUrl string `json:"readme_url"`
+ ReadmeURL string `json:"readme_url"`
}
- apiURL := "https://gitlab.com/api/v4/projects/" + projectPath
+ apiURL := fmt.Sprintf("https://%s/api/v4/projects/%s", u.Hostname(), projectPath)
// nolint:bodyclose
// it is closed on the caller
- res, err := http.Get(apiURL)
+ res, err := http.Get(apiURL) // nolint: gosec
if err != nil {
return nil, err
}
@@ -49,23 +38,22 @@ func findGitLabREADME(s string) (*source, error) {
}
var result readme
- jsonErr := json.Unmarshal(body, &result)
- if jsonErr != nil {
+ if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
- readmeRawUrl := strings.Replace(result.ReadmeUrl, "blob", "raw", -1)
+ readmeRawURL := strings.Replace(result.ReadmeURL, "blob", "raw", -1)
if res.StatusCode == http.StatusOK {
// nolint:bodyclose
// it is closed on the caller
- resp, err := http.Get(readmeRawUrl)
+ resp, err := http.Get(readmeRawURL) // nolint: gosec
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusOK {
- return &source{resp.Body, readmeRawUrl}, nil
+ return &source{resp.Body, readmeRawURL}, nil
}
}
diff --git a/main.go b/main.go
index 17d66e7..5a42f9b 100644
--- a/main.go
+++ b/main.go
@@ -62,19 +62,9 @@ func sourceFromArg(arg string) (*source, error) {
}
// a GitHub or GitLab URL (even without the protocol):
- if u, ok := isGitHubURL(arg); ok {
- src, err := findGitHubREADME(u)
- if err != nil {
- return nil, err
- }
- return src, nil
- }
- if u, ok := isGitLabURL(arg); ok {
- src, err := findGitLabREADME(u)
- if err != nil {
- return nil, err
- }
- return src, nil
+ src, err := readmeURL(arg)
+ if src != nil || err != nil {
+ return src, err
}
// HTTP(S) URLs:
diff --git a/url.go b/url.go
new file mode 100644
index 0000000..43ca167
--- /dev/null
+++ b/url.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "net/url"
+ "strings"
+ "sync"
+)
+
+const (
+ protoGithub = "github://"
+ protoGitlab = "gitlab://"
+ protoHTTPS = "https://"
+)
+
+var (
+ githubURL *url.URL
+ gitlabURL *url.URL
+ urlsOnce sync.Once
+)
+
+func init() {
+ urlsOnce.Do(func() {
+ githubURL, _ = url.Parse("https://github.com")
+ gitlabURL, _ = url.Parse("https://gitlab.com")
+ })
+}
+
+func readmeURL(path string) (*source, error) {
+ switch {
+ case strings.HasPrefix(path, protoGithub):
+ if u := githubReadmeURL(path); u != nil {
+ return readmeURL(u.String())
+ }
+ return nil, nil
+ case strings.HasPrefix(path, protoGitlab):
+ if u := gitlabReadmeURL(path); u != nil {
+ return readmeURL(u.String())
+ }
+ return nil, nil
+ }
+
+ if !strings.HasPrefix(path, protoHTTPS) {
+ path = protoHTTPS + path
+ }
+ u, err := url.Parse(path)
+ if err != nil {
+ return nil, err
+ }
+
+ switch {
+ case u.Hostname() == githubURL.Hostname():
+ return findGitHubREADME(u)
+ case u.Hostname() == gitlabURL.Hostname():
+ return findGitLabREADME(u)
+ }
+
+ return nil, nil
+}
+
+func githubReadmeURL(path string) *url.URL {
+ path = strings.TrimPrefix(path, protoGithub)
+ parts := strings.Split(path, "/")
+ if len(parts) != 2 {
+ // custom hostnames are not supported yet
+ return nil
+ }
+ u, _ := url.Parse(githubURL.String())
+ return u.JoinPath(path)
+}
+
+func gitlabReadmeURL(path string) *url.URL {
+ path = strings.TrimPrefix(path, protoGitlab)
+ parts := strings.Split(path, "/")
+ if len(parts) != 2 {
+ // custom hostnames are not supported yet
+ return nil
+ }
+ u, _ := url.Parse(gitlabURL.String())
+ return u.JoinPath(path)
+}
diff --git a/url_test.go b/url_test.go
new file mode 100644
index 0000000..02d1547
--- /dev/null
+++ b/url_test.go
@@ -0,0 +1,29 @@
+package main
+
+import "testing"
+
+func TestURLParser(t *testing.T) {
+ for path, url := range map[string]string{
+ "github.com/charmbracelet/glow": "https://raw.githubusercontent.com/charmbracelet/glow/master/README.md",
+ "github://charmbracelet/glow": "https://raw.githubusercontent.com/charmbracelet/glow/master/README.md",
+ "github://caarlos0/dotfiles.fish": "https://raw.githubusercontent.com/caarlos0/dotfiles.fish/main/README.md",
+ "github://tj/git-extras": "https://raw.githubusercontent.com/tj/git-extras/master/Readme.md",
+ "https://github.com/goreleaser/nfpm": "https://raw.githubusercontent.com/goreleaser/nfpm/main/README.md",
+ "gitlab.com/caarlos0/test": "https://gitlab.com/caarlos0/test/-/raw/master/README.md",
+ "gitlab://caarlos0/test": "https://gitlab.com/caarlos0/test/-/raw/master/README.md",
+ "https://gitlab.com/terrakok/gitlab-client": "https://gitlab.com/terrakok/gitlab-client/-/raw/develop/Readme.md",
+ } {
+ t.Run(path, func(t *testing.T) {
+ got, err := readmeURL(path)
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+ if got == nil {
+ t.Fatalf("should not be nil")
+ }
+ if url != got.URL {
+ t.Errorf("expected url for %s to be %s, was %s", path, url, got.URL)
+ }
+ })
+ }
+}