aboutsummaryrefslogtreecommitdiff
path: root/handler.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--handler.go206
1 files changed, 132 insertions, 74 deletions
diff --git a/handler.go b/handler.go
index 18dd1f4..f636359 100644
--- a/handler.go
+++ b/handler.go
@@ -1,10 +1,12 @@
package main
import (
+ "cmp"
"fmt"
"html/template"
"net/http"
- "sort"
+ "path"
+ "slices"
"strings"
"go.uber.org/sally/templates"
@@ -17,112 +19,168 @@ var (
template.New("package.html").Parse(templates.Package))
)
-// CreateHandler creates a Sally http.Handler
+// CreateHandler builds a new handler
+// with the provided package configuration.
+// The returned handler provides the following endpoints:
+//
+// GET /
+// Index page listing all packages.
+// GET /<name>
+// Package page for the given package.
+// GET /<dir>
+// Page listing packages under the given directory,
+// assuming that there's no package with the given name.
+// GET /<name>/<subpkg>
+// Package page for the given subpackage.
func CreateHandler(config *Config) http.Handler {
mux := http.NewServeMux()
-
- pkgs := make([]packageInfo, 0, len(config.Packages))
+ pkgs := make([]*sallyPackage, 0, len(config.Packages))
for name, pkg := range config.Packages {
- handler := newPackageHandler(config, name, pkg)
+ baseURL := config.URL
+ if pkg.URL != "" {
+ // Package-specific override for the base URL.
+ baseURL = pkg.URL
+ }
+ modulePath := path.Join(baseURL, name)
+ docURL := "https://" + path.Join(config.Godoc.Host, modulePath)
+
+ pkg := &sallyPackage{
+ Name: name,
+ Desc: pkg.Desc,
+ ModulePath: modulePath,
+ DocURL: docURL,
+ GitURL: pkg.Repo,
+ }
+ pkgs = append(pkgs, pkg)
+
// Double-register so that "/foo"
// does not redirect to "/foo/" with a 300.
+ handler := &packageHandler{Pkg: pkg}
mux.Handle("/"+name, handler)
mux.Handle("/"+name+"/", handler)
-
- pkgs = append(pkgs, packageInfo{
- Desc: pkg.Desc,
- ImportPath: handler.canonicalURL,
- GitURL: handler.gitURL,
- GodocHome: handler.godocHost + "/" + handler.canonicalURL,
- })
}
- sort.Slice(pkgs, func(i, j int) bool {
- return pkgs[i].ImportPath < pkgs[j].ImportPath
+
+ mux.Handle("/", newIndexHandler(pkgs))
+ return requireMethod(http.MethodGet, mux)
+}
+
+func requireMethod(method string, handler http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != method {
+ http.NotFound(w, r)
+ return
+ }
+
+ handler.ServeHTTP(w, r)
})
- mux.Handle("/", &indexHandler{pkgs: pkgs})
+}
- return mux
+type sallyPackage struct {
+ // Name of the package.
+ //
+ // This is the part after the base URL.
+ Name string
+
+ // Canonical import path for the package.
+ ModulePath string
+
+ // Description of the package, if any.
+ Desc string
+
+ // URL at which documentation for the package can be found.
+ DocURL string
+
+ // URL at which the Git repository is hosted.
+ GitURL string
}
type indexHandler struct {
- pkgs []packageInfo
+ pkgs []*sallyPackage // sorted by name
}
-type packageInfo struct {
- Desc string // package description
- ImportPath string // canonical import path
- GitURL string // URL of the Git repository
- GodocHome string // documentation home URL
-}
+var _ http.Handler = (*indexHandler)(nil)
-func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- // Index handler only supports '/'.
- // ServeMux will call us for any '/foo' that is not a known package.
- if r.Method != http.MethodGet || r.URL.Path != "/" {
- http.NotFound(w, r)
- return
- }
+func newIndexHandler(pkgs []*sallyPackage) *indexHandler {
+ slices.SortFunc(pkgs, func(a, b *sallyPackage) int {
+ return cmp.Compare(a.Name, b.Name)
+ })
- data := struct{ Packages []packageInfo }{
- Packages: h.pkgs,
- }
- if err := indexTemplate.Execute(w, data); err != nil {
- http.Error(w, err.Error(), 500)
+ return &indexHandler{
+ pkgs: pkgs,
}
}
-type packageHandler struct {
- // Hostname of the godoc server, e.g. "godoc.org".
- godocHost string
+func (h *indexHandler) rangeOf(path string) (start, end int) {
+ if len(path) == 0 {
+ return 0, len(h.pkgs)
+ }
- // Name of the package relative to the vanity base URL.
- // For example, "zap" for "go.uber.org/zap".
- name string
+ // If the packages are sorted by name,
+ // we can scan adjacent packages to find the range of packages
+ // whose name descends from path.
+ start, _ = slices.BinarySearchFunc(h.pkgs, path, func(pkg *sallyPackage, path string) int {
+ return cmp.Compare(pkg.Name, path)
+ })
- // Path at which the Git repository is hosted.
- // For example, "github.com/uber-go/zap".
- gitURL string
+ for idx := start; idx < len(h.pkgs); idx++ {
+ if !descends(path, h.pkgs[idx].Name) {
+ // End of matching sequences.
+ // The next path is not a descendant of path.
+ return start, idx
+ }
+ }
- // Canonical import path for the package.
- canonicalURL string
+ // All packages following start are descendants of path.
+ // Return the rest of the packages.
+ return start, len(h.pkgs)
}
-func newPackageHandler(cfg *Config, name string, pkg PackageConfig) *packageHandler {
- baseURL := cfg.URL
- if pkg.URL != "" {
- baseURL = pkg.URL
+func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ path := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/")
+ start, end := h.rangeOf(path)
+
+ // If start == end, then there are no packages
+ if start == end {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprintf(w, "no packages found under path: %v\n", path)
+ return
}
- canonicalURL := fmt.Sprintf("%s/%s", baseURL, name)
- return &packageHandler{
- godocHost: cfg.Godoc.Host,
- name: name,
- canonicalURL: canonicalURL,
- gitURL: pkg.Repo,
+ err := indexTemplate.Execute(w,
+ struct{ Packages []*sallyPackage }{
+ Packages: h.pkgs[start:end],
+ })
+ if err != nil {
+ http.Error(w, err.Error(), 500)
}
}
-func (h *packageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodGet {
- http.NotFound(w, r)
- return
- }
+type packageHandler struct {
+ Pkg *sallyPackage
+}
+
+var _ http.Handler = (*packageHandler)(nil)
+func (h *packageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Extract the relative path to subpackages, if any.
- // "/foo/bar" => "/bar"
- // "/foo" => ""
- relPath := strings.TrimPrefix(r.URL.Path, "/"+h.name)
-
- data := struct {
- Repo string
- CanonicalURL string
- GodocURL string
+ // "/foo/bar" => "/bar"
+ // "/foo" => ""
+ relPath := strings.TrimPrefix(r.URL.Path, "/"+h.Pkg.Name)
+
+ err := packageTemplate.Execute(w, struct {
+ ModulePath string
+ GitURL string
+ DocURL string
}{
- Repo: h.gitURL,
- CanonicalURL: h.canonicalURL,
- GodocURL: fmt.Sprintf("https://%s/%s%s", h.godocHost, h.canonicalURL, relPath),
- }
- if err := packageTemplate.Execute(w, data); err != nil {
+ ModulePath: h.Pkg.ModulePath,
+ GitURL: h.Pkg.GitURL,
+ DocURL: h.Pkg.DocURL + relPath,
+ })
+ if err != nil {
http.Error(w, err.Error(), 500)
}
}
+
+func descends(from, to string) bool {
+ return to == from || (strings.HasPrefix(to, from) && to[len(from)] == '/')
+}