Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / blog / blog.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201028153306-37f0764111ff/blog/blog.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201028153306-37f0764111ff/blog/blog.go
new file mode 100644 (file)
index 0000000..a87401c
--- /dev/null
@@ -0,0 +1,502 @@
+// Copyright 2013 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 blog implements a web server for articles written in present format.
+package blog // import "golang.org/x/tools/blog"
+
+import (
+       "bytes"
+       "encoding/json"
+       "encoding/xml"
+       "fmt"
+       "html/template"
+       "log"
+       "net/http"
+       "os"
+       "path/filepath"
+       "regexp"
+       "sort"
+       "strings"
+       "time"
+
+       "golang.org/x/tools/blog/atom"
+       "golang.org/x/tools/present"
+)
+
+var (
+       validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
+       // used to serve relative paths when ServeLocalLinks is enabled.
+       golangOrgAbsLinkReplacer = strings.NewReplacer(
+               `href="https://golang.org/pkg`, `href="/pkg`,
+               `href="https://golang.org/cmd`, `href="/cmd`,
+       )
+)
+
+// Config specifies Server configuration values.
+type Config struct {
+       ContentPath  string // Relative or absolute location of article files and related content.
+       TemplatePath string // Relative or absolute location of template files.
+
+       BaseURL       string        // Absolute base URL (for permalinks; no trailing slash).
+       BasePath      string        // Base URL path relative to server root (no trailing slash).
+       GodocURL      string        // The base URL of godoc (for menu bar; no trailing slash).
+       Hostname      string        // Server host name, used for rendering ATOM feeds.
+       AnalyticsHTML template.HTML // Optional analytics HTML to insert at the beginning of <head>.
+
+       HomeArticles int    // Articles to display on the home page.
+       FeedArticles int    // Articles to include in Atom and JSON feeds.
+       FeedTitle    string // The title of the Atom XML feed
+
+       PlayEnabled     bool
+       ServeLocalLinks bool // rewrite golang.org/{pkg,cmd} links to host-less, relative paths.
+}
+
+// Doc represents an article adorned with presentation data.
+type Doc struct {
+       *present.Doc
+       Permalink string        // Canonical URL for this document.
+       Path      string        // Path relative to server root (including base).
+       HTML      template.HTML // rendered article
+
+       Related      []*Doc
+       Newer, Older *Doc
+}
+
+// Server implements an http.Handler that serves blog articles.
+type Server struct {
+       cfg       Config
+       docs      []*Doc
+       redirects map[string]string
+       tags      []string
+       docPaths  map[string]*Doc // key is path without BasePath.
+       docTags   map[string][]*Doc
+       template  struct {
+               home, index, article, doc *template.Template
+       }
+       atomFeed []byte // pre-rendered Atom feed
+       jsonFeed []byte // pre-rendered JSON feed
+       content  http.Handler
+}
+
+// NewServer constructs a new Server using the specified config.
+func NewServer(cfg Config) (*Server, error) {
+       present.PlayEnabled = cfg.PlayEnabled
+
+       if notExist(cfg.TemplatePath) {
+               return nil, fmt.Errorf("template directory not found: %s", cfg.TemplatePath)
+       }
+       root := filepath.Join(cfg.TemplatePath, "root.tmpl")
+       parse := func(name string) (*template.Template, error) {
+               path := filepath.Join(cfg.TemplatePath, name)
+               if notExist(path) {
+                       return nil, fmt.Errorf("template %s was not found in %s", name, cfg.TemplatePath)
+               }
+               t := template.New("").Funcs(funcMap)
+               return t.ParseFiles(root, path)
+       }
+
+       s := &Server{cfg: cfg}
+
+       // Parse templates.
+       var err error
+       s.template.home, err = parse("home.tmpl")
+       if err != nil {
+               return nil, err
+       }
+       s.template.index, err = parse("index.tmpl")
+       if err != nil {
+               return nil, err
+       }
+       s.template.article, err = parse("article.tmpl")
+       if err != nil {
+               return nil, err
+       }
+       p := present.Template().Funcs(funcMap)
+       s.template.doc, err = p.ParseFiles(filepath.Join(cfg.TemplatePath, "doc.tmpl"))
+       if err != nil {
+               return nil, err
+       }
+
+       // Load content.
+       content := filepath.Clean(cfg.ContentPath)
+       err = s.loadDocs(content)
+       if err != nil {
+               return nil, err
+       }
+
+       err = s.renderAtomFeed()
+       if err != nil {
+               return nil, err
+       }
+
+       err = s.renderJSONFeed()
+       if err != nil {
+               return nil, err
+       }
+
+       // Set up content file server.
+       s.content = http.StripPrefix(s.cfg.BasePath, http.FileServer(http.Dir(cfg.ContentPath)))
+
+       return s, nil
+}
+
+var funcMap = template.FuncMap{
+       "sectioned": sectioned,
+       "authors":   authors,
+}
+
+// sectioned returns true if the provided Doc contains more than one section.
+// This is used to control whether to display the table of contents and headings.
+func sectioned(d *present.Doc) bool {
+       return len(d.Sections) > 1
+}
+
+// authors returns a comma-separated list of author names.
+func authors(authors []present.Author) string {
+       var b bytes.Buffer
+       last := len(authors) - 1
+       for i, a := range authors {
+               if i > 0 {
+                       if i == last {
+                               if len(authors) > 2 {
+                                       b.WriteString(",")
+                               }
+                               b.WriteString(" and ")
+                       } else {
+                               b.WriteString(", ")
+                       }
+               }
+               b.WriteString(authorName(a))
+       }
+       return b.String()
+}
+
+// authorName returns the first line of the Author text: the author's name.
+func authorName(a present.Author) string {
+       el := a.TextElem()
+       if len(el) == 0 {
+               return ""
+       }
+       text, ok := el[0].(present.Text)
+       if !ok || len(text.Lines) == 0 {
+               return ""
+       }
+       return text.Lines[0]
+}
+
+// loadDocs reads all content from the provided file system root, renders all
+// the articles it finds, adds them to the Server's docs field, computes the
+// denormalized docPaths, docTags, and tags fields, and populates the various
+// helper fields (Next, Previous, Related) for each Doc.
+func (s *Server) loadDocs(root string) error {
+       // Read content into docs field.
+       const ext = ".article"
+       fn := func(p string, info os.FileInfo, err error) error {
+               if err != nil {
+                       return err
+               }
+
+               if filepath.Ext(p) != ext {
+                       return nil
+               }
+               f, err := os.Open(p)
+               if err != nil {
+                       return err
+               }
+               defer f.Close()
+               d, err := present.Parse(f, p, 0)
+               if err != nil {
+                       return err
+               }
+               var html bytes.Buffer
+               err = d.Render(&html, s.template.doc)
+               if err != nil {
+                       return err
+               }
+               p = p[len(root) : len(p)-len(ext)] // trim root and extension
+               p = filepath.ToSlash(p)
+               s.docs = append(s.docs, &Doc{
+                       Doc:       d,
+                       Path:      s.cfg.BasePath + p,
+                       Permalink: s.cfg.BaseURL + p,
+                       HTML:      template.HTML(html.String()),
+               })
+               return nil
+       }
+       err := filepath.Walk(root, fn)
+       if err != nil {
+               return err
+       }
+       sort.Sort(docsByTime(s.docs))
+
+       // Pull out doc paths and tags and put in reverse-associating maps.
+       s.docPaths = make(map[string]*Doc)
+       s.docTags = make(map[string][]*Doc)
+       s.redirects = make(map[string]string)
+       for _, d := range s.docs {
+               s.docPaths[strings.TrimPrefix(d.Path, s.cfg.BasePath)] = d
+               for _, t := range d.Tags {
+                       s.docTags[t] = append(s.docTags[t], d)
+               }
+       }
+       for _, d := range s.docs {
+               for _, old := range d.OldURL {
+                       if !strings.HasPrefix(old, "/") {
+                               old = "/" + old
+                       }
+                       if _, ok := s.docPaths[old]; ok {
+                               return fmt.Errorf("redirect %s -> %s conflicts with document %s", old, d.Path, old)
+                       }
+                       if new, ok := s.redirects[old]; ok {
+                               return fmt.Errorf("redirect %s -> %s conflicts with redirect %s -> %s", old, d.Path, old, new)
+                       }
+                       s.redirects[old] = d.Path
+               }
+       }
+
+       // Pull out unique sorted list of tags.
+       for t := range s.docTags {
+               s.tags = append(s.tags, t)
+       }
+       sort.Strings(s.tags)
+
+       // Set up presentation-related fields, Newer, Older, and Related.
+       for _, doc := range s.docs {
+               // Newer, Older: docs adjacent to doc
+               for i := range s.docs {
+                       if s.docs[i] != doc {
+                               continue
+                       }
+                       if i > 0 {
+                               doc.Newer = s.docs[i-1]
+                       }
+                       if i+1 < len(s.docs) {
+                               doc.Older = s.docs[i+1]
+                       }
+                       break
+               }
+
+               // Related: all docs that share tags with doc.
+               related := make(map[*Doc]bool)
+               for _, t := range doc.Tags {
+                       for _, d := range s.docTags[t] {
+                               if d != doc {
+                                       related[d] = true
+                               }
+                       }
+               }
+               for d := range related {
+                       doc.Related = append(doc.Related, d)
+               }
+               sort.Sort(docsByTime(doc.Related))
+       }
+
+       return nil
+}
+
+// renderAtomFeed generates an XML Atom feed and stores it in the Server's
+// atomFeed field.
+func (s *Server) renderAtomFeed() error {
+       var updated time.Time
+       if len(s.docs) > 0 {
+               updated = s.docs[0].Time
+       }
+       feed := atom.Feed{
+               Title:   s.cfg.FeedTitle,
+               ID:      "tag:" + s.cfg.Hostname + ",2013:" + s.cfg.Hostname,
+               Updated: atom.Time(updated),
+               Link: []atom.Link{{
+                       Rel:  "self",
+                       Href: s.cfg.BaseURL + "/feed.atom",
+               }},
+       }
+       for i, doc := range s.docs {
+               if i >= s.cfg.FeedArticles {
+                       break
+               }
+
+               // Use original article path as ID in atom feed
+               // to avoid articles being treated as new when renamed.
+               idPath := doc.Path
+               if len(doc.OldURL) > 0 {
+                       old := doc.OldURL[0]
+                       if !strings.HasPrefix(old, "/") {
+                               old = "/" + old
+                       }
+                       idPath = old
+               }
+
+               e := &atom.Entry{
+                       Title: doc.Title,
+                       ID:    feed.ID + idPath,
+                       Link: []atom.Link{{
+                               Rel:  "alternate",
+                               Href: doc.Permalink,
+                       }},
+                       Published: atom.Time(doc.Time),
+                       Updated:   atom.Time(doc.Time),
+                       Summary: &atom.Text{
+                               Type: "html",
+                               Body: summary(doc),
+                       },
+                       Content: &atom.Text{
+                               Type: "html",
+                               Body: string(doc.HTML),
+                       },
+                       Author: &atom.Person{
+                               Name: authors(doc.Authors),
+                       },
+               }
+               feed.Entry = append(feed.Entry, e)
+       }
+       data, err := xml.Marshal(&feed)
+       if err != nil {
+               return err
+       }
+       s.atomFeed = data
+       return nil
+}
+
+type jsonItem struct {
+       Title   string
+       Link    string
+       Time    time.Time
+       Summary string
+       Content string
+       Author  string
+}
+
+// renderJSONFeed generates a JSON feed and stores it in the Server's jsonFeed
+// field.
+func (s *Server) renderJSONFeed() error {
+       var feed []jsonItem
+       for i, doc := range s.docs {
+               if i >= s.cfg.FeedArticles {
+                       break
+               }
+               item := jsonItem{
+                       Title:   doc.Title,
+                       Link:    doc.Permalink,
+                       Time:    doc.Time,
+                       Summary: summary(doc),
+                       Content: string(doc.HTML),
+                       Author:  authors(doc.Authors),
+               }
+               feed = append(feed, item)
+       }
+       data, err := json.Marshal(feed)
+       if err != nil {
+               return err
+       }
+       s.jsonFeed = data
+       return nil
+}
+
+// summary returns the first paragraph of text from the provided Doc.
+func summary(d *Doc) string {
+       if len(d.Sections) == 0 {
+               return ""
+       }
+       for _, elem := range d.Sections[0].Elem {
+               text, ok := elem.(present.Text)
+               if !ok || text.Pre {
+                       // skip everything but non-text elements
+                       continue
+               }
+               var buf bytes.Buffer
+               for _, s := range text.Lines {
+                       buf.WriteString(string(present.Style(s)))
+                       buf.WriteByte('\n')
+               }
+               return buf.String()
+       }
+       return ""
+}
+
+// rootData encapsulates data destined for the root template.
+type rootData struct {
+       Doc           *Doc
+       BasePath      string
+       GodocURL      string
+       AnalyticsHTML template.HTML
+       Data          interface{}
+}
+
+// ServeHTTP serves the front, index, and article pages
+// as well as the ATOM and JSON feeds.
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+       var (
+               d = rootData{
+                       BasePath:      s.cfg.BasePath,
+                       GodocURL:      s.cfg.GodocURL,
+                       AnalyticsHTML: s.cfg.AnalyticsHTML,
+               }
+               t *template.Template
+       )
+       switch p := strings.TrimPrefix(r.URL.Path, s.cfg.BasePath); p {
+       case "/":
+               d.Data = s.docs
+               if len(s.docs) > s.cfg.HomeArticles {
+                       d.Data = s.docs[:s.cfg.HomeArticles]
+               }
+               t = s.template.home
+       case "/index":
+               d.Data = s.docs
+               t = s.template.index
+       case "/feed.atom", "/feeds/posts/default":
+               w.Header().Set("Content-type", "application/atom+xml; charset=utf-8")
+               w.Write(s.atomFeed)
+               return
+       case "/.json":
+               if p := r.FormValue("jsonp"); validJSONPFunc.MatchString(p) {
+                       w.Header().Set("Content-type", "application/javascript; charset=utf-8")
+                       fmt.Fprintf(w, "%v(%s)", p, s.jsonFeed)
+                       return
+               }
+               w.Header().Set("Content-type", "application/json; charset=utf-8")
+               w.Write(s.jsonFeed)
+               return
+       default:
+               if redir, ok := s.redirects[p]; ok {
+                       http.Redirect(w, r, redir, http.StatusMovedPermanently)
+                       return
+               }
+               doc, ok := s.docPaths[p]
+               if !ok {
+                       // Not a doc; try to just serve static content.
+                       s.content.ServeHTTP(w, r)
+                       return
+               }
+               d.Doc = doc
+               t = s.template.article
+       }
+       var err error
+       if s.cfg.ServeLocalLinks {
+               var buf bytes.Buffer
+               err = t.ExecuteTemplate(&buf, "root", d)
+               if err != nil {
+                       log.Println(err)
+                       return
+               }
+               _, err = golangOrgAbsLinkReplacer.WriteString(w, buf.String())
+       } else {
+               err = t.ExecuteTemplate(w, "root", d)
+       }
+       if err != nil {
+               log.Println(err)
+       }
+}
+
+// docsByTime implements sort.Interface, sorting Docs by their Time field.
+type docsByTime []*Doc
+
+func (s docsByTime) Len() int           { return len(s) }
+func (s docsByTime) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) }
+
+// notExist reports whether the path exists or not.
+func notExist(path string) bool {
+       _, err := os.Stat(path)
+       return os.IsNotExist(err)
+}