github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/present/dir.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"html/template"
     9  	"io"
    10  	"io/fs"
    11  	"log"
    12  	"net"
    13  	"net/http"
    14  	"os"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  
    19  	"golang.org/x/tools/present"
    20  )
    21  
    22  func init() {
    23  	http.HandleFunc("/", dirHandler)
    24  }
    25  
    26  // dirHandler serves a directory listing for the requested path, rooted at *contentPath.
    27  func dirHandler(w http.ResponseWriter, r *http.Request) {
    28  	if r.URL.Path == "/favicon.ico" {
    29  		http.NotFound(w, r)
    30  		return
    31  	}
    32  	name := filepath.Join(*contentPath, r.URL.Path)
    33  	if isDoc(name) {
    34  		err := renderDoc(w, name)
    35  		if err != nil {
    36  			log.Println(err)
    37  			http.Error(w, err.Error(), http.StatusInternalServerError)
    38  		}
    39  		return
    40  	}
    41  	if isDir, err := dirList(w, name); err != nil {
    42  		addr, _, e := net.SplitHostPort(r.RemoteAddr)
    43  		if e != nil {
    44  			addr = r.RemoteAddr
    45  		}
    46  		log.Printf("request from %s: %s", addr, err)
    47  		http.Error(w, err.Error(), http.StatusInternalServerError)
    48  		return
    49  	} else if isDir {
    50  		return
    51  	}
    52  	http.FileServer(http.Dir(*contentPath)).ServeHTTP(w, r)
    53  }
    54  
    55  func isDoc(path string) bool {
    56  	_, ok := contentTemplate[filepath.Ext(path)]
    57  	return ok
    58  }
    59  
    60  var (
    61  	// dirListTemplate holds the front page template.
    62  	dirListTemplate *template.Template
    63  
    64  	// contentTemplate maps the presentable file extensions to the
    65  	// template to be executed.
    66  	contentTemplate map[string]*template.Template
    67  )
    68  
    69  func initTemplates(fsys fs.FS) error {
    70  	// Locate the template file.
    71  	actionTmpl := "templates/action.tmpl"
    72  
    73  	contentTemplate = make(map[string]*template.Template)
    74  
    75  	for ext, contentTmpl := range map[string]string{
    76  		".slide":   "slides.tmpl",
    77  		".article": "article.tmpl",
    78  	} {
    79  		contentTmpl = "templates/" + contentTmpl
    80  
    81  		// Read and parse the input.
    82  		tmpl := present.Template()
    83  		tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
    84  		if _, err := tmpl.ParseFS(fsys, actionTmpl, contentTmpl); err != nil {
    85  			return err
    86  		}
    87  		contentTemplate[ext] = tmpl
    88  	}
    89  
    90  	var err error
    91  	dirListTemplate, err = template.ParseFS(fsys, "templates/dir.tmpl")
    92  	return err
    93  }
    94  
    95  // renderDoc reads the present file, gets its template representation,
    96  // and executes the template, sending output to w.
    97  func renderDoc(w io.Writer, docFile string) error {
    98  	// Read the input and build the doc structure.
    99  	doc, err := parse(docFile, 0)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	// Find which template should be executed.
   105  	tmpl := contentTemplate[filepath.Ext(docFile)]
   106  
   107  	// Execute the template.
   108  	return doc.Render(w, tmpl)
   109  }
   110  
   111  func parse(name string, mode present.ParseMode) (*present.Doc, error) {
   112  	f, err := os.Open(name)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	defer f.Close()
   117  	return present.Parse(f, name, mode)
   118  }
   119  
   120  // dirList scans the given path and writes a directory listing to w.
   121  // It parses the first part of each .slide file it encounters to display the
   122  // presentation title in the listing.
   123  // If the given path is not a directory, it returns (isDir == false, err == nil)
   124  // and writes nothing to w.
   125  func dirList(w io.Writer, name string) (isDir bool, err error) {
   126  	f, err := os.Open(name)
   127  	if err != nil {
   128  		return false, err
   129  	}
   130  	defer f.Close()
   131  	fi, err := f.Stat()
   132  	if err != nil {
   133  		return false, err
   134  	}
   135  	if isDir = fi.IsDir(); !isDir {
   136  		return false, nil
   137  	}
   138  	fis, err := f.Readdir(0)
   139  	if err != nil {
   140  		return false, err
   141  	}
   142  	strippedPath := strings.TrimPrefix(name, filepath.Clean(*contentPath))
   143  	strippedPath = strings.TrimPrefix(strippedPath, "/")
   144  	d := &dirListData{Path: strippedPath}
   145  	for _, fi := range fis {
   146  		// skip the golang.org directory
   147  		if name == "." && fi.Name() == "golang.org" {
   148  			continue
   149  		}
   150  		e := dirEntry{
   151  			Name: fi.Name(),
   152  			Path: filepath.ToSlash(filepath.Join(strippedPath, fi.Name())),
   153  		}
   154  		if fi.IsDir() && showDir(e.Name) {
   155  			d.Dirs = append(d.Dirs, e)
   156  			continue
   157  		}
   158  		if isDoc(e.Name) {
   159  			fn := filepath.ToSlash(filepath.Join(name, fi.Name()))
   160  			if p, err := parse(fn, present.TitlesOnly); err != nil {
   161  				log.Printf("parse(%q, present.TitlesOnly): %v", fn, err)
   162  			} else {
   163  				e.Title = p.Title
   164  			}
   165  			switch filepath.Ext(e.Path) {
   166  			case ".article":
   167  				d.Articles = append(d.Articles, e)
   168  			case ".slide":
   169  				d.Slides = append(d.Slides, e)
   170  			}
   171  		} else if showFile(e.Name) {
   172  			d.Other = append(d.Other, e)
   173  		}
   174  	}
   175  	if d.Path == "." {
   176  		d.Path = ""
   177  	}
   178  	sort.Sort(d.Dirs)
   179  	sort.Sort(d.Slides)
   180  	sort.Sort(d.Articles)
   181  	sort.Sort(d.Other)
   182  	return true, dirListTemplate.Execute(w, d)
   183  }
   184  
   185  // showFile reports whether the given file should be displayed in the list.
   186  func showFile(n string) bool {
   187  	switch filepath.Ext(n) {
   188  	case ".pdf":
   189  	case ".html":
   190  	case ".go":
   191  	default:
   192  		return isDoc(n)
   193  	}
   194  	return true
   195  }
   196  
   197  // showDir reports whether the given directory should be displayed in the list.
   198  func showDir(n string) bool {
   199  	if len(n) > 0 && (n[0] == '.' || n[0] == '_') || n == "present" {
   200  		return false
   201  	}
   202  	return true
   203  }
   204  
   205  type dirListData struct {
   206  	Path                          string
   207  	Dirs, Slides, Articles, Other dirEntrySlice
   208  }
   209  
   210  type dirEntry struct {
   211  	Name, Path, Title string
   212  }
   213  
   214  type dirEntrySlice []dirEntry
   215  
   216  func (s dirEntrySlice) Len() int           { return len(s) }
   217  func (s dirEntrySlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   218  func (s dirEntrySlice) Less(i, j int) bool { return s[i].Name < s[j].Name }