diff --git a/vanity.go b/vanity.go index 6b5bfba..3538c38 100644 --- a/vanity.go +++ b/vanity.go @@ -7,21 +7,26 @@ import ( "strings" ) -type Tag interface { - get() string +type config struct { + importTag *string + sourceTag *string + redir Redirector } -type strTag string - -func (t strTag) get() string { - return string(t) -} +// Configures the Handler. The only required option is WithImport. +type Option func(*config) // Instructs the go tool where to fetch the repo at vcsRoot and the importPath // that tree should be rooted at. -func ImportTag(importPath, vcs, vcsRoot string) Tag { - return strTag("") +func WithImport(importPath, vcs, vcsRoot string) Option { + importTag := "meta name=\"go-import\" content=\"" + importPath + " " + + vcs + " " + vcsRoot + "\">" + return func(cfg *config) { + if cfg.importTag != nil { + panic(fmt.Sprintf("vanity: existing import tag: %s", *cfg.importTag)) + } + cfg.importTag = &importTag + } } // Instructs gddo (godoc.org) how to direct browsers to browsable source code @@ -33,22 +38,54 @@ func ImportTag(importPath, vcs, vcsRoot string) Tag { // // More information can be found at https://github.com/golang/gddo/wiki/Source-Code-Links. // -func SourceTag(prefix, home, directory, file string) Tag { - return strTag("") +func WithSource(prefix, home, directory, file string) Option { + sourceTag := "meta name=\"go-source\" content=\"" + prefix + " " + + home + " " + directory + " " + file + "\">" + return func(cfg *config) { + if cfg.sourceTag != nil { + panic(fmt.Sprintf("vanity: existing source tag: %s", *cfg.importTag)) + } + cfg.sourceTag = &sourceTag + } +} + +// When a browser navigates to the vanity URL of pkg, this function rewrites +// pkg to a browsable URL. +type Redirector func(pkg string) (url string) + +func WithRedirector(redir Redirector) Option { + return func(cfg *config) { + if cfg.redir != nil { + panic("vanity: existing Redirector") + } + cfg.redir = redir + } } // Returns an http.Handler that serves the vanity URL information for a single -// repository. Each tag gives additional information to agents about the -// repository and the packages it contains. An ImportTag is basically mandatory -// since the go tool requires it to fetch the repository. -func Handler(tags ...Tag) http.Handler { +// repository. Each Option gives additional information to agents about the +// repository or provides help to browsers that may have navigated to the vanity// URL. The WithImport Option is mandatory since the go tool requires it to +// fetch the repository. +func Handler(opts ...Option) http.Handler { + var redir Redirector + tpl := func() *template.Template { - s := make([]string, len(tags)) - for i, t := range tags { - s[i] = t.get() + // Process options. + var cfg config + for _, opt := range opts { + opt(&cfg) } - tagBlk := strings.Join(s, "\n") + + // A WithImport is required. + if cfg.importTag == nil { + panic("vanity: WithImport is required") + } + + tags := []string{*cfg.importTag} + if cfg.sourceTag != nil { + tags = append(tags, *cfg.sourceTag) + } + tagBlk := strings.Join(tags, "\n") h := fmt.Sprintf(` @@ -63,9 +100,16 @@ Nothing to see here; move along. `, tagBlk) + redir = cfg.redir return template.Must(template.New("").Parse(h)) }() + if redir == nil { + redir = func(pkg string) string { + return "https://godoc.org/" + pkg + } + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Redirect to https. if r.URL.Scheme == "http" { @@ -82,43 +126,45 @@ Nothing to see here; move along. } pkg := r.Host + r.URL.Path + redirURL := redir(pkg) - // Redirect browsers to gddo. + // Issue an HTTP redirect if this is definitely a browser. if r.FormValue("go-get") != "1" { - url := "https://godoc.org/" + r.Host + r.URL.Path - http.Redirect(w, r, url, http.StatusTemporaryRedirect) + http.Redirect(w, r, redirURL, http.StatusTemporaryRedirect) return } w.Header().Set("Cache-Control", "public, max-age=300") - if err := tpl.ExecuteTemplate(w, "", pkg); err != nil { + if err := tpl.ExecuteTemplate(w, "", redirURL); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) } +// Helpers for common VCSs. + // Redirects gddo to browsable source files for GitHub hosted repositories. -func GitHubStyleSourceTag(importPath, repoPath, ref string) Tag { +func WithGitHubStyleSource(importPath, repoPath, ref string) Option { directory := repoPath + "/tree/" + ref + "{/dir}" file := repoPath + "/blob/" + ref + "{/dir}/{file}#L{line}" - return SourceTag(importPath, repoPath, directory, file) + return WithSource(importPath, repoPath, directory, file) } // Redirects gddo to browsable source files for Gogs hosted repositories. -func GogsStyleSourceTag(importPath, repoPath, ref string) Tag { +func WithGogsStyleSource(importPath, repoPath, ref string) Option { directory := repoPath + "/src/" + ref + "{/dir}" file := repoPath + "/src/" + ref + "{/dir}/{file}#L{line}" - return SourceTag(importPath, repoPath, directory, file) + return WithSource(importPath, repoPath, directory, file) } // Creates a Handler that serves a GitHub repository at a specific importPath. func GitHubHandler(importPath, user, repo, vcsScheme string) http.Handler { ghImportPath := "github.com/" + user + "/" + repo return Handler( - ImportTag(importPath, "git", vcsScheme+"://"+ghImportPath), - GitHubStyleSourceTag(importPath, "https://"+ghImportPath, "master"), + WithImport(importPath, "git", vcsScheme+"://"+ghImportPath), + WithGitHubStyleSource(importPath, "https://"+ghImportPath, "master"), ) } @@ -127,7 +173,7 @@ func GitHubHandler(importPath, user, repo, vcsScheme string) http.Handler { func GogsHandler(importPath, host, user, repo, vcsScheme string) http.Handler { gogsImportPath := host + "/" + user + "/" + repo return Handler( - ImportTag(importPath, "git", vcsScheme+"://"+gogsImportPath), - GogsStyleSourceTag(importPath, "https://"+gogsImportPath, "master"), + WithImport(importPath, "git", vcsScheme+"://"+gogsImportPath), + WithGogsStyleSource(importPath, "https://"+gogsImportPath, "master"), ) }