1
2
3
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
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
62 dirListTemplate *template.Template
63
64
65
66 contentTemplate map[string]*template.Template
67 )
68
69 func initTemplates(fsys fs.FS) error {
70
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
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
96
97 func renderDoc(w io.Writer, docFile string) error {
98
99 doc, err := parse(docFile, 0)
100 if err != nil {
101 return err
102 }
103
104
105 tmpl := contentTemplate[filepath.Ext(docFile)]
106
107
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
121
122
123
124
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
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
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
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 }
219
View as plain text