Skip to content

Commit

Permalink
Merge pull request #4 from golift/dn2_wildcards
Browse files Browse the repository at this point in the history
Add wildcard path support.
  • Loading branch information
davidnewhall committed Jul 18, 2019
2 parents a287829 + 30fd263 commit 1d2071d
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 31 deletions.
9 changes: 9 additions & 0 deletions config.yaml
Expand Up @@ -58,6 +58,15 @@ paths:
/securityspy:
repo: https://github.com/golift/securityspy
redir: https://github.com/golift/securityspy

# Unused Examples.
/david/:
repo: https://github.com/davidnewhall/
wildcard: true
/captain-:
repo: https://github.com/davidnewhall/
wildcard: true

# No repos.
/unifi-poller:
redir: https://github.com/davidnewhall/unifi-poller
Expand Down
84 changes: 58 additions & 26 deletions handler.go
Expand Up @@ -66,6 +66,7 @@ type PathConfig struct {
Redir string `yaml:"redir,omitempty"`
Display string `yaml:"display,omitempty"`
VCS string `yaml:"vcs,omitempty"`
Wildcard bool `yaml:"wildcard,omitempty"`
cacheControl string
}

Expand Down Expand Up @@ -97,7 +98,6 @@ func newHandler(configData []byte) (*Handler, error) {
h.Paths[p].RedirPaths = h.RedirPaths
}
h.Paths[p].setRepoCacheControl(cacheControl)
h.Paths[p].setRepoDisplay()
if err := h.Paths[p].setRepoVCS(); err != nil {
return nil, err
}
Expand All @@ -115,19 +115,6 @@ func (p *PathConfig) setRepoCacheControl(cc string) {
}
}

// Set display path.
func (p *PathConfig) setRepoDisplay() {
if p.Display != "" {
return
}
// github, gitlab, git, svn, hg, bzr - may need more tweaking for some of these.
p.Display = fmt.Sprintf("%v %v/tree/master{/dir} %v/blob/master{/dir}/{file}#L{line}", p.Repo, p.Repo, p.Repo)
if strings.HasPrefix(p.Repo, "https://bitbucket.org") {
// bitbucket is weird.
p.Display = fmt.Sprintf("%v %v/src/default{/dir} %v/src/default{/dir}/{file}#{file}-{line}", p.Repo, p.Repo, p.Repo)
}
}

// setRepoVCS makes sure the provides VCS type is supported,
// or sets it automatically based on the repo's prefix.
func (p *PathConfig) setRepoVCS() error {
Expand Down Expand Up @@ -188,8 +175,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Create a vanity redirect page.
w.Header().Set("Cache-Control", pc.cacheControl)
pc.Host = h.Hostname(r)
pc.Path = strings.TrimSuffix(pc.Path, "/")
if err := vanityTmpl.Execute(w, pc); err != nil {
if err := vanityTmpl.Execute(w, &pc); err != nil {
http.Error(w, "cannot render the page", http.StatusInternalServerError)
}
}
Expand Down Expand Up @@ -221,17 +207,58 @@ func (p *PathReq) RedirectablePath() bool {
return false
}

// Len is a sort.Search interface method.
// ImportPath is used in the template to generate the import path.
func (p *PathReq) ImportPath() string {
path := p.Path
repo := p.Repo
if p.Wildcard {
sub := strings.Split(p.Subpath, "/")[0]
path += sub
repo += sub
}
return p.Host + strings.TrimSuffix(path, "/") + " " + p.VCS + " " + repo
}

// SourcePath is used in the template to generate the source path.
func (p *PathReq) SourcePath() string {
if p.Display != "" {
return p.Host + strings.TrimSuffix(p.Path, "/") + " " + p.Display
}
// TODO: add branch control.
template := "%v%v %v %v/tree/master{/dir} %v/blob/master{/dir}/{file}#L{line}"
if strings.HasPrefix(p.Repo, "https://bitbucket.org") {
template = "%v%v %v %v/src/default{/dir} %v/src/default{/dir}/{file}#{file}-{line}"
}
path := p.Path
repo := p.Repo
if p.Wildcard {
sub := strings.Split(p.Subpath, "/")[0]
path += sub
repo += sub
}
// github, gitlab, git, svn, hg, bzr - may need more tweaking for some of these.
return fmt.Sprintf(template, p.Host, strings.TrimSuffix(path, "/"), repo, repo, repo)
}

// GoDocPath is used in the template to generate the GoDoc path.
func (p *PathReq) GoDocPath() string {
if p.Wildcard {
return p.Host + "/" + p.Subpath
}
return p.Host + p.Path + "/" + p.Subpath
}

// Len is a sort.Sort interface method.
func (pset PathConfigs) Len() int {
return len(pset)
}

// Less is a sort.Search interface method.
// Less is a sort.Sort interface method.
func (pset PathConfigs) Less(i, j int) bool {
return pset[i].Path < pset[j].Path
}

// Swap is a sort.Search interface method.
// Swap is a sort.Sort interface method.
func (pset PathConfigs) Swap(i, j int) {
pset[i], pset[j] = pset[j], pset[i]
}
Expand All @@ -243,18 +270,21 @@ func (pset PathConfigs) find(path string) PathReq {
return pset[i].Path >= path
})
if i < len(pset) && pset[i].Path == path {
// We have an exact match to a configured path.
return PathReq{PathConfig: pset[i]}
}
// This attempts to match /some/path/here but not /some/pathhere
if i > 0 && strings.HasPrefix(path, pset[i-1].Path+"/") {
// We have a partial match with a subpath!
return PathReq{
PathConfig: pset[i-1],
Subpath: path[len(pset[i-1].Path)+1:],
}
}

// Slow path, now looking for the longest prefix/shortest subpath i.e.
// e.g. given pset ["/", "/abc/", "/abc/def/", "/xyz"/]
// * query "/abc/foo" returns "/abc/" with a subpath of "foo"
// e.g. given pset ["/", "/abc", "/abc/def", "/xyz"]
// * query "/abc/foo" returns "/abc" with a subpath of "foo"
// * query "/x" returns "/" with a subpath of "x"
lenShortestSubpath := len(path)
var p PathReq
Expand All @@ -263,14 +293,16 @@ func (pset PathConfigs) find(path string) PathReq {
// nothing greater than i will be a prefix of path.
max := i
for i := 0; i < max; i++ {
ps := pset[i]
if len(ps.Path) >= len(path) {
// We previously didn't find the path by search, so any
// route with equal or greater length is NOT a match.
if len(pset[i].Path) >= len(path) {
// We previously didn't find the request path by search, so any
// configured path with equal or greater length is NOT a match.
continue
}
sSubpath := strings.TrimPrefix(path, ps.Path)
sSubpath := strings.TrimPrefix(path, pset[i].Path)
if len(sSubpath) < lenShortestSubpath {
// We get into this if statement only if TrimPrefix trimmed something.
// Then we try the next path, and check to see if what's left after we
// trimmed the configured path off is shorter than this one. /x is better than /xyz.
p.Subpath = sSubpath
lenShortestSubpath = len(sSubpath)
p.PathConfig = pset[i]
Expand Down
45 changes: 45 additions & 0 deletions handler_test.go
Expand Up @@ -108,6 +108,51 @@ func TestHandler(t *testing.T) {
goImport: "example.com git https://github.com/rakyll/portmidi",
goSource: "example.com https://github.com/rakyll/portmidi _ _",
},
{
name: "wildcard with sub path",
config: "host: example.com\n" +
"paths:\n" +
" /rakyll/:\n" +
" repo: https://github.com/rakyll/\n" +
" wildcard: true\n",
path: "/rakyll/repo/foo",
goImport: "example.com/rakyll/repo git https://github.com/rakyll/repo",
goSource: "example.com/rakyll/repo https://github.com/rakyll/repo https://github.com/rakyll/repo/tree/master{/dir} https://github.com/rakyll/repo/blob/master{/dir}/{file}#L{line}",
},
{
name: "wildcard with no slashes",
config: "host: example.com\n" +
"paths:\n" +
" /rakyll/:\n" +
" repo: https://github.com/rakyll/\n" +
" wildcard: true\n",
path: "/rakyll/repo",
goImport: "example.com/rakyll/repo git https://github.com/rakyll/repo",
goSource: "example.com/rakyll/repo https://github.com/rakyll/repo https://github.com/rakyll/repo/tree/master{/dir} https://github.com/rakyll/repo/blob/master{/dir}/{file}#L{line}",
},
{
name: "wildcard with dashes",
config: "host: example.com\n" +
"paths:\n" +
" /rakyll-:\n" +
" repo: https://github.com/rakyll/\n" +
" wildcard: true\n",
path: "/rakyll-repo",
goImport: "example.com/rakyll-repo git https://github.com/rakyll/repo",
goSource: "example.com/rakyll-repo https://github.com/rakyll/repo https://github.com/rakyll/repo/tree/master{/dir} https://github.com/rakyll/repo/blob/master{/dir}/{file}#L{line}",
},
{
name: "wildcard bare word",
config: "host: example.com\n" +
"paths:\n" +
" /rakyll:\n" +
" repo: https://github.com/rakyll/\n" +
" cache_max_age: 99\n" +
" wildcard: true\n",
path: "/rakyllrepo",
goImport: "example.com/rakyllrepo git https://github.com/rakyll/repo",
goSource: "example.com/rakyllrepo https://github.com/rakyll/repo https://github.com/rakyll/repo/tree/master{/dir} https://github.com/rakyll/repo/blob/master{/dir}/{file}#L{line}",
},
{
name: "display Gitlab inference",
config: "host: example.com\n" +
Expand Down
10 changes: 5 additions & 5 deletions templates.go
Expand Up @@ -28,7 +28,7 @@ var indexTmpl = template.Must(template.New("index").Parse(`<!DOCTYPE html>
<h3><a href="{{.URL}}">{{.Title}}</a></h3>
{{end -}}
<ul>
{{- range .Paths}} {{if ne .Repo ""}}
{{- range .Paths}} {{if and (ne .Repo "") (ne .Wildcard true)}}
<li><a href="{{.Path}}">{{.Path}}</a></li>
<li><a href="https://godoc.org/{{$.Host}}{{.Path}}">GoDoc</a></li>
<li><a href="{{.Repo}}">Code</a></li>
Expand All @@ -43,12 +43,12 @@ var vanityTmpl = template.Must(template.New("vanity").Parse(`<!DOCTYPE html>
<head>
<link rel="icon" href="/favicon.ico" type="image/x-icon"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="{{.Host}}{{.Path}} {{.VCS}} {{.Repo}}"/>
<meta name="go-source" content="{{.Host}}{{.Path}} {{.Display}}"/>
<meta http-equiv="refresh" content="0; url=https://godoc.org/{{.Host}}{{.Path}}/{{.Subpath}}"/>
<meta name="go-import" content="{{.ImportPath}}"/>
<meta name="go-source" content="{{.SourcePath}}"/>
<!-- meta http-equiv="refresh" content="0; url=https://godoc.org/{{.GoDocPath}}"/-->
</head>
<body>
Nothing to see here; <a href="https://godoc.org/{{.Host}}{{.Path}}/{{.Subpath}}">See the package on godoc</a>.
Nothing to see here; <a href="https://godoc.org/{{.GoDocPath}}">See the package on godoc</a>.
</body>
</html>
`))

0 comments on commit 1d2071d

Please sign in to comment.