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 }