diff --git a/interface.go b/interface.go new file mode 100644 index 0000000..c0a3113 --- /dev/null +++ b/interface.go @@ -0,0 +1,144 @@ +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + "os" + "sort" +) + +const tmplStrPackage = ` + + + + {{.Repo.PkgPath}} + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+
+
+

Getting started

+ To get the package, execute: +
go get {{.Repo.PkgPath}}
+ + To import this package, add the following line to your code: +
import "{{.Repo.PkgPath}}"
+
+
+
+

Versions

+ {{ range .LatestVersions }} +
+ v{{.Major}} + → + {{.String}} +
+ {{ end }} +
+
+
+ + + + +` + +var tmplPackage *template.Template + +func init() { + var err error + tmplPackage, err = template.New("page").Parse(tmplStrPackage) + if err != nil { + fmt.Printf("fatal: parsing template failed: %s\n", err) + os.Exit(1) + } +} + +type dataPackage struct { + Repo *Repo // repo object + LatestVersions VersionList // versionlist containing only the latest version for each major + FullVersion Version // full version that the requeste version resolves to +} + +func renderInterface(resp http.ResponseWriter, req *http.Request, repo *Repo) { + data := &dataPackage{ + Repo: repo, + } + latestVersionsMap := make(map[int]Version) + for _, v := range repo.Versions { + v2, exists := latestVersionsMap[v.Major] + if !exists || v2.Less(v) { + latestVersionsMap[v.Major] = v + } + } + data.FullVersion = latestVersionsMap[repo.Version.Major] + data.LatestVersions = make(VersionList, 0, len(latestVersionsMap)) + for _, v := range latestVersionsMap { + data.LatestVersions = append(data.LatestVersions, v) + } + sort.Sort(sort.Reverse(data.LatestVersions)) + + err := tmplPackage.Execute(resp, data) + if err != nil { + log.Printf("error executing tmplPackage: %s\n", err) + } +} diff --git a/main.go b/main.go index e084694..16d3e03 100644 --- a/main.go +++ b/main.go @@ -56,7 +56,7 @@ func run() error { return <-ch } -var tmpl = template.Must(template.New("").Parse(` +var tmplProxy = template.Must(template.New("").Parse(` @@ -70,11 +70,40 @@ window.location = "http://godoc.org/{{.PkgPath}}" + window.location.hash; `)) type Repo struct { - GitRoot string - HubRoot string - PkgRoot string - PkgPath string - Version Version + User string // username or organization, might include forward slash + Pkg string // repository name + VersionStr string // version string ("v1") + Path string // path inside repository + Compat bool // when true, use old format + + Version Version // requested version (major only) + Versions VersionList // available versions +} + +func (repo *Repo) GitRoot() string { + return "https://" + repo.PkgRoot() +} + +func (repo *Repo) HubRoot() string { + if len(repo.User) == 0 { + return "https://github.com/go-" + repo.Pkg + "/" + repo.Pkg + } + return "https://github.com/" + repo.User + repo.Pkg +} + +func (repo *Repo) PkgBase() string { + return "gopkg.in/" + repo.User + repo.Pkg +} + +func (repo *Repo) PkgRoot() string { + if repo.Compat { + return "gopkg.in/" + repo.User + repo.VersionStr + "/" + repo.Pkg + } + return repo.PkgBase() + "." + repo.VersionStr +} + +func (repo *Repo) PkgPath() string { + return repo.PkgRoot() + repo.Path } var patternOld = regexp.MustCompile(`^/([a-z0-9][-a-z0-9]+/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2})/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`) @@ -94,6 +123,8 @@ func handler(resp http.ResponseWriter, req *http.Request) { return } + goget := req.FormValue("go-get") == "1" + m := patternNew.FindStringSubmatch(req.URL.Path) compat := false if m == nil { @@ -112,62 +143,48 @@ func handler(resp http.ResponseWriter, req *http.Request) { return } - var repo *Repo - if m[1] == "" { - repo = &Repo{ - GitRoot: "https://github.com/go-" + m[2] + "/" + m[2], - PkgRoot: "gopkg.in/" + m[2] + "." + m[3], - PkgPath: "gopkg.in/" + m[2] + "." + m[3] + m[4], - } - if compat { - repo.PkgRoot = "gopkg.in/" + m[3] + "/" + m[2] - repo.PkgPath = "gopkg.in/" + m[3] + "/" + m[2] + m[4] - } - } else { - repo = &Repo{ - GitRoot: "https://github.com/" + m[1] + m[2], - PkgRoot: "gopkg.in/" + m[1] + m[2] + "." + m[3], - PkgPath: "gopkg.in/" + m[1] + m[2] + "." + m[3] + m[4], - } - if compat { - repo.PkgRoot = "gopkg.in/" + m[1] + m[3] + "/" + m[2] - repo.PkgPath = "gopkg.in/" + m[1] + m[3] + "/" + m[2] + m[4] - } + repo := &Repo{ + User: m[1], + Pkg: m[2], + VersionStr: m[3], + Path: m[4], + Compat: compat, } var ok bool - repo.Version, ok = parseVersion(m[3]) + repo.Version, ok = parseVersion(repo.VersionStr) if !ok { sendNotFound(resp, "Version %q improperly considered invalid; please warn the service maintainers.", m[3]) return } - repo.HubRoot = repo.GitRoot + // // Run this concurrently to avoid waiting later. + // nameVersioned := nameHasVersion(repo) - // Run this concurrently to avoid waiting later. - nameVersioned := nameHasVersion(repo) - - refs, err := hackedRefs(repo) + var err error + var refs []byte + refs, repo.Versions, err = hackedRefs(repo) switch err { case nil: - repo.GitRoot = "https://" + repo.PkgRoot + // repo.GitRoot = "https://" + repo.PkgRoot + // all ok case ErrNoRepo: - if <-nameVersioned { - v := repo.Version.String() - repo.GitRoot += "-" + v - repo.HubRoot += "-" + v - break - } - sendNotFound(resp, "GitHub repository not found at %s", repo.HubRoot) + // if <-nameVersioned { + // v := repo.Version.String() + // repo.GitRoot += "-" + v + // repo.HubRoot += "-" + v + // break + // } + sendNotFound(resp, "GitHub repository not found at %s", repo.HubRoot()) return case ErrNoVersion: v := repo.Version.String() if repo.Version.Minor == -1 { - sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.HubRoot, v, v, v) + sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.HubRoot(), v, v, v) } else if repo.Version.Patch == -1 { - sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s" or "%s.N"`, repo.HubRoot, v, v) + sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s" or "%s.N"`, repo.HubRoot(), v, v) } else { - sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s"`, repo.HubRoot, v) + sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s"`, repo.HubRoot(), v) } return default: @@ -177,7 +194,7 @@ func handler(resp http.ResponseWriter, req *http.Request) { } if m[4] == "/git-upload-pack" { - resp.Header().Set("Location", repo.HubRoot+"/git-upload-pack") + resp.Header().Set("Location", repo.HubRoot()+"/git-upload-pack") resp.WriteHeader(http.StatusMovedPermanently) return } @@ -189,7 +206,16 @@ func handler(resp http.ResponseWriter, req *http.Request) { } resp.Header().Set("Content-Type", "text/html") - tmpl.Execute(resp, repo) + if goget { + // execute simple template when this is a go-get request + err = tmplProxy.Execute(resp, repo) + if err != nil { + log.Printf("error executing tmplProxy: %s\n", err) + } + return + } + + renderInterface(resp, req, repo) } func sendNotFound(resp http.ResponseWriter, msg string, args ...interface{}) { @@ -204,43 +230,43 @@ func sendNotFound(resp http.ResponseWriter, msg string, args ...interface{}) { const refsSuffix = ".git/info/refs?service=git-upload-pack" -// Obsolete. Drop this. -func nameHasVersion(repo *Repo) chan bool { - ch := make(chan bool, 1) - go func() { - resp, err := http.Head(repo.HubRoot + "-" + repo.Version.String() + refsSuffix) - if err != nil { - ch <- false - return - } - if resp.Body != nil { - resp.Body.Close() - } - ch <- resp.StatusCode == 200 - }() - return ch -} +// // Obsolete. Drop this. +// func nameHasVersion(repo *Repo) chan bool { +// ch := make(chan bool, 1) +// go func() { +// resp, err := http.Head(repo.HubRoot + "-" + repo.Version.String() + refsSuffix) +// if err != nil { +// ch <- false +// return +// } +// if resp.Body != nil { +// resp.Body.Close() +// } +// ch <- resp.StatusCode == 200 +// }() +// return ch +// } var ErrNoRepo = errors.New("repository not found in github") var ErrNoVersion = errors.New("version reference not found in github") -func hackedRefs(repo *Repo) (data []byte, err error) { - resp, err := http.Get(repo.HubRoot + refsSuffix) +func hackedRefs(repo *Repo) (data []byte, versions []Version, err error) { + resp, err := http.Get(repo.HubRoot() + refsSuffix) if err != nil { - return nil, fmt.Errorf("cannot talk to GitHub: %v", err) + return nil, nil, fmt.Errorf("cannot talk to GitHub: %v", err) } switch resp.StatusCode { case 200: defer resp.Body.Close() case 401, 404: - return nil, ErrNoRepo + return nil, nil, ErrNoRepo default: - return nil, fmt.Errorf("error from GitHub: %v", resp.Status) + return nil, nil, fmt.Errorf("error from GitHub: %v", resp.Status) } data, err = ioutil.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("error reading from GitHub: %v", err) + return nil, nil, fmt.Errorf("error reading from GitHub: %v", err) } var mrefi, mrefj int @@ -248,18 +274,19 @@ func hackedRefs(repo *Repo) (data []byte, err error) { var vrefv = InvalidVersion var unversioned = true + versions = make([]Version, 0) sdata := string(data) for i, j := 0, 0; i < len(data); i = j { size, err := strconv.ParseInt(sdata[i:i+4], 16, 32) if err != nil { - return nil, fmt.Errorf("cannot parse refs line size: %s", string(data[i:i+4])) + return nil, nil, fmt.Errorf("cannot parse refs line size: %s", string(data[i:i+4])) } if size == 0 { size = 4 } j = i + int(size) if j > len(sdata) { - return nil, fmt.Errorf("incomplete refs data received from GitHub") + return nil, nil, fmt.Errorf("incomplete refs data received from GitHub") } if sdata[0] == '#' { continue @@ -296,19 +323,20 @@ func hackedRefs(repo *Repo) (data []byte, err error) { } if ok { unversioned = false + versions = append(versions, v) } } } // If there were absolutely no versions, and v0 was requested, accept the master as-is. if unversioned && repo.Version == (Version{0, -1, -1}) { - return data, nil + return data, nil, nil } if mrefi == 0 || vrefi == 0 { - return nil, ErrNoVersion + return nil, nil, ErrNoVersion } copy(data[mrefi:mrefj], data[vrefi:vrefj]) - return data, nil + return data, versions, nil } diff --git a/version.go b/version.go index f01c6da..13b9468 100644 --- a/version.go +++ b/version.go @@ -101,3 +101,17 @@ func parseVersionPart(s string, i int) (part int, newi int) { } return part, i } + +// VersionList implements sort.Interface +type VersionList []Version + +func (vl VersionList) Len() int { + return len(vl) +} +func (vl VersionList) Less(i, j int) bool { + return vl[i].Less(vl[j]) +} + +func (vl VersionList) Swap(i, j int) { + vl[i], vl[j] = vl[j], vl[i] +}