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 return <-ch
} }


var tmpl = template.Must(template.New("").Parse(`
var tmplProxy = template.Must(template.New("").Parse(`
<html> <html>
<head> <head>
<meta name="go-import" content="{{.PkgRoot}} git {{.GitRoot}}"> <meta name="go-import" content="{{.PkgRoot}} git {{.GitRoot}}">
@@ -70,11 +70,40 @@ window.location = "http://godoc.org/{{.PkgPath}}" + window.location.hash;
`)) `))


type Repo struct { 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]*)*)$`) 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 return
} }


goget := req.FormValue("go-get") == "1"

m := patternNew.FindStringSubmatch(req.URL.Path) m := patternNew.FindStringSubmatch(req.URL.Path)
compat := false compat := false
if m == nil { if m == nil {
@@ -112,62 +143,48 @@ func handler(resp http.ResponseWriter, req *http.Request) {
return 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 var ok bool
repo.Version, ok = parseVersion(m[3])
repo.Version, ok = parseVersion(repo.VersionStr)
if !ok { if !ok {
sendNotFound(resp, "Version %q improperly considered invalid; please warn the service maintainers.", m[3]) sendNotFound(resp, "Version %q improperly considered invalid; please warn the service maintainers.", m[3])
return 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 { switch err {
case nil: case nil:
repo.GitRoot = "https://" + repo.PkgRoot
// repo.GitRoot = "https://" + repo.PkgRoot
// all ok
case ErrNoRepo: 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 return
case ErrNoVersion: case ErrNoVersion:
v := repo.Version.String() v := repo.Version.String()
if repo.Version.Minor == -1 { 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 { } 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 { } 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 return
default: default:
@@ -177,7 +194,7 @@ func handler(resp http.ResponseWriter, req *http.Request) {
} }


if m[4] == "/git-upload-pack" { 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) resp.WriteHeader(http.StatusMovedPermanently)
return return
} }
@@ -189,7 +206,16 @@ func handler(resp http.ResponseWriter, req *http.Request) {
} }


resp.Header().Set("Content-Type", "text/html") 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{}) { 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" 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 ErrNoRepo = errors.New("repository not found in github")
var ErrNoVersion = errors.New("version reference 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 { 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 { switch resp.StatusCode {
case 200: case 200:
defer resp.Body.Close() defer resp.Body.Close()
case 401, 404: case 401, 404:
return nil, ErrNoRepo
return nil, nil, ErrNoRepo
default: 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) data, err = ioutil.ReadAll(resp.Body)
if err != nil { 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 var mrefi, mrefj int
@@ -248,18 +274,19 @@ func hackedRefs(repo *Repo) (data []byte, err error) {
var vrefv = InvalidVersion var vrefv = InvalidVersion
var unversioned = true var unversioned = true


versions = make([]Version, 0)
sdata := string(data) sdata := string(data)
for i, j := 0, 0; i < len(data); i = j { for i, j := 0, 0; i < len(data); i = j {
size, err := strconv.ParseInt(sdata[i:i+4], 16, 32) size, err := strconv.ParseInt(sdata[i:i+4], 16, 32)
if err != nil { 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 { if size == 0 {
size = 4 size = 4
} }
j = i + int(size) j = i + int(size)
if j > len(sdata) { 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] == '#' { if sdata[0] == '#' {
continue continue
@@ -296,19 +323,20 @@ func hackedRefs(repo *Repo) (data []byte, err error) {
} }
if ok { if ok {
unversioned = false unversioned = false
versions = append(versions, v)
} }
} }
} }


// If there were absolutely no versions, and v0 was requested, accept the master as-is. // If there were absolutely no versions, and v0 was requested, accept the master as-is.
if unversioned && repo.Version == (Version{0, -1, -1}) { if unversioned && repo.Version == (Version{0, -1, -1}) {
return data, nil
return data, nil, nil
} }


if mrefi == 0 || vrefi == 0 { if mrefi == 0 || vrefi == 0 {
return nil, ErrNoVersion
return nil, nil, ErrNoVersion
} }


copy(data[mrefi:mrefj], data[vrefi:vrefj]) 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 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