package main import ( "encoding/json" "fmt" "html/template" "io/ioutil" "log" "net/http" "net/url" "os" "regexp" "sort" "sync" "time" ) const packageTemplateString = ` {{.Repo.Name}}.{{.Repo.MajorVersion}}{{.Repo.SubPath}} - {{.Repo.GopkgPath}}
{{ if .Repo.MajorVersion.Unstable }}
This is an unstable package and should not be used in released code.
{{ end }}

Getting started

To get the package, execute:

go get {{.Repo.GopkgPath}}

To import this package, add the following line to your code:

import "{{.Repo.GopkgPath}}"
{{if .PackageName}}

Refer to it as {{.PackageName}}.{{end}}

For more details, see the API documentation.

Versions

{{ if .LatestVersions }} {{ range .LatestVersions }}
v{{.Major}}{{if .Unstable}}-unstable{{end}}{{.}}
{{ end }} {{ else }}
v0master
{{ end }}
` var packageTemplate *template.Template func gopkgVersionRoot(repo *Repo, version Version) string { return repo.GopkgVersionRoot(version) } var packageFuncs = template.FuncMap{ "gopkgVersionRoot": gopkgVersionRoot, } func init() { var err error packageTemplate, err = template.New("page").Funcs(packageFuncs).Parse(packageTemplateString) if err != nil { fmt.Fprintf(os.Stderr, "fatal: parsing package template failed: %s\n", err) os.Exit(1) } } type packageData struct { Repo *Repo LatestVersions VersionList // Contains only the latest version for each major PackageName string // Actual package identifier as specified in http://golang.org/ref/spec#PackageClause Synopsis string GitTreeName string } // SearchResults is used with the godoc.org search API type SearchResults struct { Results []struct { Path string `json:"path"` Synopsis string `json:"synopsis"` } `json:"results"` } var regexpPackageName = regexp.MustCompile(`

package ([\p{L}_][\p{L}\p{Nd}_]*)

`) func renderPackagePage(resp http.ResponseWriter, req *http.Request, repo *Repo) { data := &packageData{ Repo: repo, } // Calculate the latest version for each major version, both stable and unstable. latestVersions := make(map[int]Version) for _, v := range repo.AllVersions { if v.Unstable { continue } v2, exists := latestVersions[v.Major] if !exists || v2.Less(v) { latestVersions[v.Major] = v } } data.LatestVersions = make(VersionList, 0, len(latestVersions)) for _, v := range latestVersions { data.LatestVersions = append(data.LatestVersions, v) } sort.Sort(sort.Reverse(data.LatestVersions)) if repo.FullVersion.Unstable { // Prepend post-sorting so it shows first. data.LatestVersions = append([]Version{repo.FullVersion}, data.LatestVersions...) } var dataMutex sync.Mutex wantResps := 2 gotResp := make(chan bool, wantResps) go func() { // Retrieve package name from godoc.org. This should be on a proper API. godocResp, err := http.Get("http://godoc.org/" + repo.GopkgPath()) if err == nil { godocRespBytes, err := ioutil.ReadAll(godocResp.Body) godocResp.Body.Close() if err == nil { matches := regexpPackageName.FindSubmatch(godocRespBytes) if matches != nil { dataMutex.Lock() data.PackageName = string(matches[1]) dataMutex.Unlock() } } } gotResp <- true }() go func() { // Retrieve synopsis from godoc.org. This should be on a package path API // rather than a search. searchResp, err := http.Get("http://api.godoc.org/search?q=" + url.QueryEscape(repo.GopkgPath())) if err == nil { searchResults := &SearchResults{} err = json.NewDecoder(searchResp.Body).Decode(&searchResults) searchResp.Body.Close() if err == nil { gopkgPath := repo.GopkgPath() for _, result := range searchResults.Results { if result.Path == gopkgPath { dataMutex.Lock() data.Synopsis = result.Synopsis dataMutex.Unlock() break } } } } gotResp <- true }() r := 0 for r < wantResps { select { case <-gotResp: r++ case <-time.After(3 * time.Second): r = wantResps } } dataMutex.Lock() defer dataMutex.Unlock() err := packageTemplate.Execute(resp, data) if err != nil { log.Printf("error executing package page template: %v", err) } }