Skip to content

Instantly share code, notes, and snippets.

@imjasonh
Created May 2, 2024 02:37
Show Gist options
  • Save imjasonh/9cb50c573dd3cf2ca26c4a4b931e2282 to your computer and use it in GitHub Desktop.
Save imjasonh/9cb50c573dd3cf2ca26c4a4b931e2282 to your computer and use it in GitHub Desktop.
translate vanity import paths to source URLs
package main
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"strings"
)
func main() {
fmt.Println(Repo("chainguard.dev/apko"))
fmt.Println(Repo("k8s.io/client-go"))
fmt.Println(Repo("knative.dev/pkg"))
}
func Repo(importpath string) (string, error) {
if strings.HasPrefix(importpath, "github.com/") {
return "https://" + importpath, nil
}
resp, err := http.Get("https://" + importpath + "?go-get=1")
if err != nil {
return "", err
}
defer resp.Body.Close()
metas, err := parseMetaGoImports(resp.Body)
if err != nil {
return "", err
}
return metas[0].RepoRoot, nil
}
// parseMetaGoImports returns meta imports from the HTML in r.
// Parsing ends at the end of the <head> section or the beginning of the <body>.
func parseMetaGoImports(r io.Reader) ([]metaImport, error) {
d := xml.NewDecoder(r)
d.CharsetReader = charsetReader
d.Strict = false
var imports []metaImport
for {
t, err := d.RawToken()
if err != nil {
if err != io.EOF && len(imports) == 0 {
return nil, err
}
break
}
if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
break
}
if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
break
}
e, ok := t.(xml.StartElement)
if !ok || !strings.EqualFold(e.Name.Local, "meta") {
continue
}
if attrValue(e.Attr, "name") != "go-import" {
continue
}
if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
imports = append(imports, metaImport{
Prefix: f[0],
VCS: f[1],
RepoRoot: f[2],
})
}
}
// Extract mod entries if we are paying attention to them.
var list []metaImport
// Append non-mod entries, ignoring those superseded by a mod entry.
for _, m := range imports {
if m.VCS != "mod" {
list = append(list, m)
}
}
return list, nil
}
// metaImport represents the parsed <meta name="go-import"
// content="prefix vcs reporoot" /> tags from HTML files.
type metaImport struct {
Prefix, VCS, RepoRoot string
}
// charsetReader returns a reader that converts from the given charset to UTF-8.
// Currently it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
// error which is printed by go get, so the user can find why the package
// wasn't downloaded if the encoding is not supported. Note that, in
// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
// greater than 0x7f are not rejected).
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
switch strings.ToLower(charset) {
case "utf-8", "ascii":
return input, nil
default:
return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
}
}
// attrValue returns the attribute value for the case-insensitive key
// `name', or the empty string if nothing is found.
func attrValue(attrs []xml.Attr, name string) string {
for _, a := range attrs {
if strings.EqualFold(a.Name.Local, name) {
return a.Value
}
}
return ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment