218 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2012 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 main
 | |
| 
 | |
| import (
 | |
| 	"html/template"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	"golang.org/x/tools/present"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	http.HandleFunc("/", dirHandler)
 | |
| }
 | |
| 
 | |
| // dirHandler serves a directory listing for the requested path, rooted at *contentPath.
 | |
| func dirHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	if r.URL.Path == "/favicon.ico" {
 | |
| 		http.NotFound(w, r)
 | |
| 		return
 | |
| 	}
 | |
| 	name := filepath.Join(*contentPath, r.URL.Path)
 | |
| 	if isDoc(name) {
 | |
| 		err := renderDoc(w, name)
 | |
| 		if err != nil {
 | |
| 			log.Println(err)
 | |
| 			http.Error(w, err.Error(), http.StatusInternalServerError)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	if isDir, err := dirList(w, name); 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
 | |
| 	} else if isDir {
 | |
| 		return
 | |
| 	}
 | |
| 	http.FileServer(http.Dir(*contentPath)).ServeHTTP(w, r)
 | |
| }
 | |
| 
 | |
| func isDoc(path string) bool {
 | |
| 	_, ok := contentTemplate[filepath.Ext(path)]
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	// dirListTemplate holds the front page template.
 | |
| 	dirListTemplate *template.Template
 | |
| 
 | |
| 	// contentTemplate maps the presentable file extensions to the
 | |
| 	// template to be executed.
 | |
| 	contentTemplate map[string]*template.Template
 | |
| )
 | |
| 
 | |
| func initTemplates(base string) error {
 | |
| 	// Locate the template file.
 | |
| 	actionTmpl := filepath.Join(base, "templates/action.tmpl")
 | |
| 
 | |
| 	contentTemplate = make(map[string]*template.Template)
 | |
| 
 | |
| 	for ext, contentTmpl := range map[string]string{
 | |
| 		".slide":   "slides.tmpl",
 | |
| 		".article": "article.tmpl",
 | |
| 	} {
 | |
| 		contentTmpl = filepath.Join(base, "templates", contentTmpl)
 | |
| 
 | |
| 		// Read and parse the input.
 | |
| 		tmpl := present.Template()
 | |
| 		tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
 | |
| 		if _, err := tmpl.ParseFiles(actionTmpl, contentTmpl); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		contentTemplate[ext] = tmpl
 | |
| 	}
 | |
| 
 | |
| 	var err error
 | |
| 	dirListTemplate, err = template.ParseFiles(filepath.Join(base, "templates/dir.tmpl"))
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // renderDoc reads the present file, gets its template representation,
 | |
| // and executes the template, sending output to w.
 | |
| func renderDoc(w io.Writer, docFile string) error {
 | |
| 	// Read the input and build the doc structure.
 | |
| 	doc, err := parse(docFile, 0)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Find which template should be executed.
 | |
| 	tmpl := contentTemplate[filepath.Ext(docFile)]
 | |
| 
 | |
| 	// Execute the template.
 | |
| 	return doc.Render(w, tmpl)
 | |
| }
 | |
| 
 | |
| func parse(name string, mode present.ParseMode) (*present.Doc, error) {
 | |
| 	f, err := os.Open(name)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 	return present.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 (isDir == false, err == nil)
 | |
| // and writes nothing to w.
 | |
| func dirList(w io.Writer, name string) (isDir bool, err error) {
 | |
| 	f, err := os.Open(name)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 	fi, err := f.Stat()
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	if isDir = fi.IsDir(); !isDir {
 | |
| 		return false, nil
 | |
| 	}
 | |
| 	fis, err := f.Readdir(0)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	strippedPath := strings.TrimPrefix(name, filepath.Clean(*contentPath))
 | |
| 	strippedPath = strings.TrimPrefix(strippedPath, "/")
 | |
| 	d := &dirListData{Path: strippedPath}
 | |
| 	for _, fi := range fis {
 | |
| 		// skip the golang.org directory
 | |
| 		if name == "." && fi.Name() == "golang.org" {
 | |
| 			continue
 | |
| 		}
 | |
| 		e := dirEntry{
 | |
| 			Name: fi.Name(),
 | |
| 			Path: filepath.ToSlash(filepath.Join(strippedPath, fi.Name())),
 | |
| 		}
 | |
| 		if fi.IsDir() && showDir(e.Name) {
 | |
| 			d.Dirs = append(d.Dirs, e)
 | |
| 			continue
 | |
| 		}
 | |
| 		if isDoc(e.Name) {
 | |
| 			fn := filepath.ToSlash(filepath.Join(name, fi.Name()))
 | |
| 			if p, err := parse(fn, present.TitlesOnly); err != nil {
 | |
| 				log.Printf("parse(%q, present.TitlesOnly): %v", fn, err)
 | |
| 			} else {
 | |
| 				e.Title = p.Title
 | |
| 			}
 | |
| 			switch filepath.Ext(e.Path) {
 | |
| 			case ".article":
 | |
| 				d.Articles = append(d.Articles, e)
 | |
| 			case ".slide":
 | |
| 				d.Slides = append(d.Slides, e)
 | |
| 			}
 | |
| 		} else if 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)
 | |
| 	return true, dirListTemplate.Execute(w, d)
 | |
| }
 | |
| 
 | |
| // showFile reports whether the given file should be displayed in the list.
 | |
| func showFile(n string) bool {
 | |
| 	switch filepath.Ext(n) {
 | |
| 	case ".pdf":
 | |
| 	case ".html":
 | |
| 	case ".go":
 | |
| 	default:
 | |
| 		return isDoc(n)
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // showDir reports whether the given directory should be displayed in the list.
 | |
| func 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 }
 |