allow requests for repository sub-paths (#6)
This commit is contained in:
parent
b514f2bb0d
commit
148d52de30
76
handler.go
76
handler.go
@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
@ -26,26 +27,40 @@ import (
|
|||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
host string
|
host string
|
||||||
m map[string]*struct {
|
paths pathConfigSet
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathConfig struct {
|
||||||
|
path string
|
||||||
|
repo string
|
||||||
|
display string
|
||||||
|
vcs string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHandler(config []byte) (*handler, error) {
|
||||||
|
var m map[string]struct {
|
||||||
Repo string `yaml:"repo,omitempty"`
|
Repo string `yaml:"repo,omitempty"`
|
||||||
Display string `yaml:"display,omitempty"`
|
Display string `yaml:"display,omitempty"`
|
||||||
VCS string `yaml:"vcs,omitempty"`
|
VCS string `yaml:"vcs,omitempty"`
|
||||||
}
|
}
|
||||||
}
|
if err := yaml.Unmarshal(config, &m); err != nil {
|
||||||
|
|
||||||
func newHandler(config []byte) (*handler, error) {
|
|
||||||
h := new(handler)
|
|
||||||
if err := yaml.Unmarshal(config, &h.m); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for path, e := range h.m {
|
h := new(handler)
|
||||||
|
for path, e := range m {
|
||||||
|
pc := pathConfig{
|
||||||
|
path: strings.TrimSuffix(path, "/"),
|
||||||
|
repo: e.Repo,
|
||||||
|
display: e.Display,
|
||||||
|
vcs: e.VCS,
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case e.Display != "":
|
case e.Display != "":
|
||||||
// Already filled in.
|
// Already filled in.
|
||||||
case strings.HasPrefix(e.Repo, "https://github.com/"):
|
case strings.HasPrefix(e.Repo, "https://github.com/"):
|
||||||
e.Display = fmt.Sprintf("%v %v/tree/master{/dir} %v/blob/master{/dir}/{file}#L{line}", e.Repo, e.Repo, e.Repo)
|
pc.display = fmt.Sprintf("%v %v/tree/master{/dir} %v/blob/master{/dir}/{file}#L{line}", e.Repo, e.Repo, e.Repo)
|
||||||
case strings.HasPrefix(e.Repo, "https://bitbucket.org"):
|
case strings.HasPrefix(e.Repo, "https://bitbucket.org"):
|
||||||
e.Display = fmt.Sprintf("%v %v/src/default{/dir} %v/src/default{/dir}/{file}#{file}-{line}", e.Repo, e.Repo, e.Repo)
|
pc.display = fmt.Sprintf("%v %v/src/default{/dir} %v/src/default{/dir}/{file}#{file}-{line}", e.Repo, e.Repo, e.Repo)
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case e.VCS != "":
|
case e.VCS != "":
|
||||||
@ -54,18 +69,20 @@ func newHandler(config []byte) (*handler, error) {
|
|||||||
return nil, fmt.Errorf("configuration for %v: unknown VCS %s", path, e.VCS)
|
return nil, fmt.Errorf("configuration for %v: unknown VCS %s", path, e.VCS)
|
||||||
}
|
}
|
||||||
case strings.HasPrefix(e.Repo, "https://github.com/"):
|
case strings.HasPrefix(e.Repo, "https://github.com/"):
|
||||||
e.VCS = "git"
|
pc.vcs = "git"
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("configuration for %v: cannot infer VCS from %s", path, e.Repo)
|
return nil, fmt.Errorf("configuration for %v: cannot infer VCS from %s", path, e.Repo)
|
||||||
}
|
}
|
||||||
|
h.paths = append(h.paths, pc)
|
||||||
}
|
}
|
||||||
|
sort.Sort(h.paths)
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
current := r.URL.Path
|
current := r.URL.Path
|
||||||
p, ok := h.m[current]
|
pc, _ := h.paths.find(current)
|
||||||
if !ok {
|
if pc == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -80,10 +97,10 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
Display string
|
Display string
|
||||||
VCS string
|
VCS string
|
||||||
}{
|
}{
|
||||||
Import: host + current,
|
Import: host + pc.path,
|
||||||
Repo: p.Repo,
|
Repo: pc.repo,
|
||||||
Display: p.Display,
|
Display: pc.display,
|
||||||
VCS: p.VCS,
|
VCS: pc.vcs,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
http.Error(w, "cannot render the page", http.StatusInternalServerError)
|
http.Error(w, "cannot render the page", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@ -101,3 +118,30 @@ var vanityTmpl = template.Must(template.New("vanity").Parse(`<!DOCTYPE html>
|
|||||||
Nothing to see here; <a href="https://godoc.org/{{.Import}}">see the package on godoc</a>.
|
Nothing to see here; <a href="https://godoc.org/{{.Import}}">see the package on godoc</a>.
|
||||||
</body>
|
</body>
|
||||||
</html>`))
|
</html>`))
|
||||||
|
|
||||||
|
type pathConfigSet []pathConfig
|
||||||
|
|
||||||
|
func (pset pathConfigSet) Len() int {
|
||||||
|
return len(pset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pset pathConfigSet) Less(i, j int) bool {
|
||||||
|
return pset[i].path < pset[j].path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pset pathConfigSet) Swap(i, j int) {
|
||||||
|
pset[i], pset[j] = pset[j], pset[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pset pathConfigSet) find(path string) (pc *pathConfig, subpath string) {
|
||||||
|
i := sort.Search(len(pset), func(i int) bool {
|
||||||
|
return pset[i].path >= path
|
||||||
|
})
|
||||||
|
if i < len(pset) && pset[i].path == path {
|
||||||
|
return &pset[i], ""
|
||||||
|
}
|
||||||
|
if i > 0 && strings.HasPrefix(path, pset[i-1].path+"/") {
|
||||||
|
return &pset[i-1], path[len(pset[i-1].path)+1:]
|
||||||
|
}
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,6 +67,24 @@ func TestHandler(t *testing.T) {
|
|||||||
goImport: "example.com/mygit git https://bitbucket.org/zombiezen/mygit",
|
goImport: "example.com/mygit git https://bitbucket.org/zombiezen/mygit",
|
||||||
goSource: "example.com/mygit https://bitbucket.org/zombiezen/mygit https://bitbucket.org/zombiezen/mygit/src/default{/dir} https://bitbucket.org/zombiezen/mygit/src/default{/dir}/{file}#{file}-{line}",
|
goSource: "example.com/mygit https://bitbucket.org/zombiezen/mygit https://bitbucket.org/zombiezen/mygit/src/default{/dir} https://bitbucket.org/zombiezen/mygit/src/default{/dir}/{file}#{file}-{line}",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "subpath",
|
||||||
|
config: "/portmidi:\n" +
|
||||||
|
" repo: https://github.com/rakyll/portmidi\n" +
|
||||||
|
" display: https://github.com/rakyll/portmidi _ _\n",
|
||||||
|
path: "/portmidi/foo",
|
||||||
|
goImport: "example.com/portmidi git https://github.com/rakyll/portmidi",
|
||||||
|
goSource: "example.com/portmidi https://github.com/rakyll/portmidi _ _",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subpath with trailing config slash",
|
||||||
|
config: "/portmidi/:\n" +
|
||||||
|
" repo: https://github.com/rakyll/portmidi\n" +
|
||||||
|
" display: https://github.com/rakyll/portmidi _ _\n",
|
||||||
|
path: "/portmidi/foo",
|
||||||
|
goImport: "example.com/portmidi git https://github.com/rakyll/portmidi",
|
||||||
|
goSource: "example.com/portmidi https://github.com/rakyll/portmidi _ _",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
h, err := newHandler([]byte(test.config))
|
h, err := newHandler([]byte(test.config))
|
||||||
@ -132,3 +151,66 @@ func findMeta(data []byte, name string) string {
|
|||||||
}
|
}
|
||||||
return string(content[:j])
|
return string(content[:j])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathConfigSetFind(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
paths []string
|
||||||
|
query string
|
||||||
|
want string
|
||||||
|
subpath string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
paths: []string{"/portmidi"},
|
||||||
|
query: "/portmidi",
|
||||||
|
want: "/portmidi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
paths: []string{"/portmidi"},
|
||||||
|
query: "/portmidi/",
|
||||||
|
want: "/portmidi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
paths: []string{"/portmidi"},
|
||||||
|
query: "/foo",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
paths: []string{"/portmidi"},
|
||||||
|
query: "/zzz",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
paths: []string{"/abc", "/portmidi", "/xyz"},
|
||||||
|
query: "/portmidi",
|
||||||
|
want: "/portmidi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
paths: []string{"/abc", "/portmidi", "/xyz"},
|
||||||
|
query: "/portmidi/foo",
|
||||||
|
want: "/portmidi",
|
||||||
|
subpath: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
emptyToNil := func(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
pset := make(pathConfigSet, len(test.paths))
|
||||||
|
for i := range test.paths {
|
||||||
|
pset[i].path = test.paths[i]
|
||||||
|
}
|
||||||
|
sort.Sort(pset)
|
||||||
|
pc, subpath := pset.find(test.query)
|
||||||
|
var got string
|
||||||
|
if pc != nil {
|
||||||
|
got = pc.path
|
||||||
|
}
|
||||||
|
if got != test.want || subpath != test.subpath {
|
||||||
|
t.Errorf("pathConfigSet(%v).find(%q) = %v, %v; want %v, %v",
|
||||||
|
test.paths, test.query, emptyToNil(got), subpath, emptyToNil(test.want), test.subpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user