| // 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 web |
| |
| import ( |
| "fmt" |
| "html/template" |
| "io/fs" |
| "path" |
| "reflect" |
| "sort" |
| "strings" |
| |
| "golang.org/x/tools/present" |
| "gopkg.in/yaml.v3" |
| ) |
| |
| // A siteDir is a site extended with a known directory for interpreting relative paths. |
| type siteDir struct { |
| *Site |
| dir string |
| } |
| |
| func toString(x interface{}) string { |
| switch x := x.(type) { |
| case string: |
| return x |
| case template.HTML: |
| return string(x) |
| case nil: |
| return "" |
| default: |
| panic(fmt.Sprintf("cannot toString %T", x)) |
| } |
| } |
| |
| // data parses the named yaml file (relative to dir) and returns its structured data. |
| func (site *siteDir) data(name string) (interface{}, error) { |
| data, err := site.readFile(site.dir, name) |
| if err != nil { |
| return nil, err |
| } |
| var d interface{} |
| if err := yaml.Unmarshal(data, &d); err != nil { |
| return nil, err |
| } |
| return d, nil |
| } |
| |
| func first(n int, list reflect.Value) reflect.Value { |
| if !list.IsValid() { |
| return list |
| } |
| if list.Kind() == reflect.Interface { |
| if list.IsNil() { |
| return list |
| } |
| list = list.Elem() |
| } |
| |
| if list.Len() < n { |
| return list |
| } |
| return list.Slice(0, n) |
| } |
| |
| // markdown is the function provided to templates. |
| func markdown(data interface{}) (template.HTML, error) { |
| h, err := markdownToHTML(toString(data)) |
| if err != nil { |
| return "", err |
| } |
| s := strings.TrimSpace(string(h)) |
| if strings.HasPrefix(s, "<p>") && strings.HasSuffix(s, "</p>") && strings.Count(s, "<p>") == 1 { |
| h = template.HTML(strings.TrimSpace(s[len("<p>") : len(s)-len("</p>")])) |
| } |
| return h, nil |
| } |
| |
| func (site *siteDir) readfile(name string) (string, error) { |
| data, err := site.readFile(site.dir, name) |
| return string(data), err |
| } |
| |
| // page returns the page params for the page with a given url u. |
| // The url may or may not have its leading slash. |
| func (site *siteDir) page(u string) (Page, error) { |
| if !path.IsAbs(u) { |
| u = path.Join(site.dir, u) |
| } |
| p, err := site.openPage(strings.Trim(u, "/")) |
| if err != nil { |
| return nil, err |
| } |
| return p.page, nil |
| } |
| |
| // Pages returns the pages found in files matching glob. |
| func (site *Site) Pages(glob string) ([]Page, error) { |
| return (&siteDir{site, "."}).pages(glob) |
| } |
| |
| // pages returns the page params for pages with urls matching glob. |
| func (site *siteDir) pages(glob string) ([]Page, error) { |
| if !path.IsAbs(glob) { |
| glob = path.Join(site.dir, glob) |
| } |
| // TODO(rsc): Add a cache? |
| _, err := path.Match(glob, "") |
| if err != nil { |
| return nil, err |
| } |
| glob = strings.Trim(glob, "/") |
| if glob == "" { |
| glob = "." |
| } |
| matches, err := fs.Glob(site.fs, glob) |
| if err != nil { |
| return nil, err |
| } |
| var out []Page |
| for _, file := range matches { |
| if !strings.HasSuffix(file, ".md") && !strings.HasSuffix(file, ".html") { |
| f := path.Join(file, "index.md") |
| if _, err := fs.Stat(site.fs, f); err != nil { |
| f = path.Join(file, "index.html") |
| if _, err = fs.Stat(site.fs, f); err != nil { |
| continue |
| } |
| } |
| file = f |
| } |
| p, err := site.openPage(file) |
| if err != nil { |
| return nil, fmt.Errorf("%s: %v", file, err) |
| } |
| out = append(out, p.page) |
| } |
| |
| sort.Slice(out, func(i, j int) bool { |
| return out[i]["URL"].(string) < out[j]["URL"].(string) |
| }) |
| return out, nil |
| } |
| |
| // file parses the named file (relative to dir) and returns its content as a string. |
| func (site *siteDir) file(name string) (string, error) { |
| data, err := site.readFile(site.dir, name) |
| if err != nil { |
| return "", err |
| } |
| return string(data), nil |
| } |
| |
| func raw(s interface{}) template.HTML { |
| return template.HTML(toString(s)) |
| } |
| |
| func yamlFn(s string) (interface{}, error) { |
| var d interface{} |
| if err := yaml.Unmarshal([]byte(s), &d); err != nil { |
| return nil, err |
| } |
| return d, nil |
| } |
| |
| func presentStyle(s string) template.HTML { |
| return template.HTML(present.Style(s)) |
| } |