Browse Source

Add interface page.

Add VersionList ([]Version) implementing sort.Interface.
Changed hackedRefs to also return a VersionList holding all valid versions for a repo.
Change Repo struct to hold parts and added methods to construct useful strings from those parts.
Comment out the whole nameHasversion() thingie. It was noted as obsolte made it harder to create clean Repo struct.
master
GeertJohan 10 years ago
parent
commit
f2f6cbda4e
3 changed files with 259 additions and 73 deletions
  1. +144
    -0
      interface.go
  2. +101
    -73
      main.go
  3. +14
    -0
      version.go

+ 144
- 0
interface.go View File

@@ -0,0 +1,144 @@
package main

import (
"fmt"
"html/template"
"log"
"net/http"
"os"
"sort"
)

const tmplStrPackage = `<!DOCTYPE html>
<html >
<head>
<meta charset="utf-8">
<title>{{.Repo.PkgPath}}</title>
<link href='http://fonts.googleapis.com/css?family=Ubuntu+Mono|Ubuntu' rel='stylesheet' type='text/css'>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<style>
@media (min-width: 1200px) {
.container {
width: 970px;
}
}

body {
font-family: 'Ubuntu', sans-serif;
}

pre {
font-family: 'Ubuntu Mono', sans-serif;
}

.main {
padding-top: 20px;
}

.getting-started {
font-size: 1.3em;
}

.versions {
font-size: 1.3em;
}
.versions div {
padding-top: 5px;
}
.versions a {
font-weight: bold;
}
.versions a.current {
color: black;
font-decoration: none;
}
</style>
</head>
<body>

<div class="container" >
<div class="row" >
<div class="col-sm-12" >
<div class="page-header">
<h1>{{.Repo.PkgPath}}</h1>
</div>
</div>
</div>
<div class="row" >
<div class="col-sm-8" >
<a class="btn btn-lg btn-info" href="{{.Repo.HubRoot}}/tree/{{.FullVersion.String}}" ><i class="fa fa-github"></i> Source Code</a>
<a class="btn btn-lg btn-info" href="http://godoc.org/{{.Repo.PkgPath}}" ><i class="fa fa-info-circle"></i> API Documentation</a>
</div>
<div class="col-sm-4" >
</div>
</div>
<div class="row main" >
<div class="col-sm-8 info" >
<div class="getting-started" >
<h2>Getting started</h2>
To get the package, execute:
<pre>go get {{.Repo.PkgPath}}</pre>

To import this package, add the following line to your code:
<pre>import "{{.Repo.PkgPath}}"</pre>
</div>
</div>
<div class="col-sm-4 versions" >
<h2>Versions</h2>
{{ range .LatestVersions }}
<div>
<a href='//{{$.Repo.PkgBase}}.v{{.Major}}' {{if eq .Major $.Repo.Version.Major}}class="current"{{end}} >v{{.Major}}</a>
&rarr;
<span class="label label-default">{{.String}}</span>
</div>
{{ end }}
</div>
</div>
</div>

<!--<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>-->
<!--<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>-->
</body>
</html>`

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)
}
}

+ 101
- 73
main.go View File

@@ -56,7 +56,7 @@ func run() error {
return <-ch
}

var tmpl = template.Must(template.New("").Parse(`
var tmplProxy = template.Must(template.New("").Parse(`
<html>
<head>
<meta name="go-import" content="{{.PkgRoot}} git {{.GitRoot}}">
@@ -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
}

+ 14
- 0
version.go View File

@@ -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]
}

Loading…
Cancel
Save