blob: 95aed8075afdaf9d2124885994853477dc3d94a2 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package talks
import (
"io/fs"
"log"
"net"
"net/http"
"path"
"path/filepath"
"sort"
"strings"
"golang.org/x/tools/present"
"golang.org/x/website/internal/web"
)
func RegisterHandlers(mux *http.ServeMux, site *web.Site, content fs.FS) error {
h := &handler{content: content, site: site}
mux.Handle("/talks/", h)
return nil
}
type handler struct {
content fs.FS
site *web.Site
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path
if h.isDoc(name) {
err := h.renderDoc(w, r, strings.Trim(name, "/"))
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
handled, err := h.dirList(w, r)
if err != nil {
addr, _, e := net.SplitHostPort(r.RemoteAddr)
if e != nil {
addr = r.RemoteAddr
}
log.Printf("request from %s: %s", addr, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if !handled {
http.FileServer(http.FS(h.content)).ServeHTTP(w, r)
}
}
func (h *handler) isDoc(name string) bool {
switch path.Ext(name) {
case ".slide", ".article":
return true
}
return false
}
func playable(c present.Code) bool {
// Restrict playable files to only Go source files when using play.golang.org,
// since there is no method to execute shell scripts there.
return c.Ext == ".go"
}
// renderDoc reads the present file, gets its template representation,
// and executes the template, sending output to w.
func (h *handler) renderDoc(w http.ResponseWriter, r *http.Request, docFile string) error {
// Read the input and build the doc structure.
doc, err := h.parse(docFile, 0)
if err != nil {
println("PARSE", err.Error())
return err
}
ext := strings.TrimPrefix(path.Ext(r.URL.Path), ".")
h.site.ServePage(w, r, web.Page{
"layout": "/talks/" + ext,
"doc": doc,
"title": doc.Title,
})
return nil
}
func (h *handler) readFile(name string) ([]byte, error) {
return fs.ReadFile(h.content, filepath.ToSlash(name))
}
func (h *handler) parse(name string, mode present.ParseMode) (*present.Doc, error) {
ctx := &present.Context{ReadFile: h.readFile}
f, err := h.content.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return ctx.Parse(f, name, mode)
}
// dirList scans the given path and writes a directory listing to w.
// It parses the first part of each .slide file it encounters to display the
// presentation title in the listing.
// If the given path is not a directory, it returns (handled == false, err == nil)
// and writes nothing to w.
func (h *handler) dirList(w http.ResponseWriter, r *http.Request) (handled bool, err error) {
name := strings.Trim(r.URL.Path, "/")
info, err := fs.Stat(h.content, name)
if err != nil || !info.IsDir() {
return false, err
}
if !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, r.URL.Path+"/", http.StatusFound)
return
}
files, err := fs.ReadDir(h.content, name)
if err != nil {
return false, err
}
d := &dirListData{Path: name}
for _, fi := range files {
// skip the golang.org directory
if name == "." && fi.Name() == "golang.org" {
continue
}
e := dirEntry{
Name: fi.Name(),
Path: path.Join(name, fi.Name()),
}
if fi.IsDir() && h.showDir(e.Name) {
d.Dirs = append(d.Dirs, e)
continue
}
if h.isDoc(e.Name) {
fn := path.Join(name, fi.Name())
if p, err := h.parse(fn, present.TitlesOnly); err != nil {
log.Printf("parse(%q, present.TitlesOnly): %v", fn, err)
} else {
e.Title = p.Title
}
switch path.Ext(e.Path) {
case ".article":
d.Articles = append(d.Articles, e)
case ".slide":
d.Slides = append(d.Slides, e)
}
} else if h.showFile(e.Name) {
d.Other = append(d.Other, e)
}
}
if d.Path == "." {
d.Path = ""
}
sort.Sort(d.Dirs)
sort.Sort(d.Slides)
sort.Sort(d.Articles)
sort.Sort(d.Other)
h.site.ServePage(w, r, web.Page{
"layout": "/talks/dir",
"title": d.Path,
"dir": d,
})
return true, nil
}
// showFile reports whether the given file should be displayed in the list.
func (h *handler) showFile(n string) bool {
switch path.Ext(n) {
case ".pdf":
case ".html":
case ".go":
default:
return h.isDoc(n)
}
return true
}
// showDir reports whether the given directory should be displayed in the list.
func (h *handler) showDir(n string) bool {
if len(n) > 0 && (n[0] == '.' || n[0] == '_') || n == "present" {
return false
}
return true
}
type dirListData struct {
Path string
Dirs, Slides, Articles, Other dirEntrySlice
}
type dirEntry struct {
Name, Path, Title string
}
type dirEntrySlice []dirEntry
func (s dirEntrySlice) Len() int { return len(s) }
func (s dirEntrySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s dirEntrySlice) Less(i, j int) bool { return s[i].Name < s[j].Name }