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 / godoc / meta.go
1 // Copyright 2009 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 godoc
6
7 import (
8         "bytes"
9         "encoding/json"
10         "errors"
11         "log"
12         "os"
13         pathpkg "path"
14         "strings"
15         "time"
16
17         "golang.org/x/tools/godoc/vfs"
18 )
19
20 var (
21         doctype   = []byte("<!DOCTYPE ")
22         jsonStart = []byte("<!--{")
23         jsonEnd   = []byte("}-->")
24 )
25
26 // ----------------------------------------------------------------------------
27 // Documentation Metadata
28
29 // TODO(adg): why are some exported and some aren't? -brad
30 type Metadata struct {
31         Title    string
32         Subtitle string
33         Template bool   // execute as template
34         Path     string // canonical path for this page
35         filePath string // filesystem path relative to goroot
36 }
37
38 func (m *Metadata) FilePath() string { return m.filePath }
39
40 // extractMetadata extracts the Metadata from a byte slice.
41 // It returns the Metadata value and the remaining data.
42 // If no metadata is present the original byte slice is returned.
43 //
44 func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) {
45         tail = b
46         if !bytes.HasPrefix(b, jsonStart) {
47                 return
48         }
49         end := bytes.Index(b, jsonEnd)
50         if end < 0 {
51                 return
52         }
53         b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
54         if err = json.Unmarshal(b, &meta); err != nil {
55                 return
56         }
57         tail = tail[end+len(jsonEnd):]
58         return
59 }
60
61 // UpdateMetadata scans $GOROOT/doc for HTML files, reads their metadata,
62 // and updates the DocMetadata map.
63 func (c *Corpus) updateMetadata() {
64         metadata := make(map[string]*Metadata)
65         var scan func(string) // scan is recursive
66         scan = func(dir string) {
67                 fis, err := c.fs.ReadDir(dir)
68                 if err != nil {
69                         if dir == "/doc" && errors.Is(err, os.ErrNotExist) {
70                                 // Be quiet during tests that don't have a /doc tree.
71                                 return
72                         }
73                         log.Printf("updateMetadata %s: %v", dir, err)
74                         return
75                 }
76                 for _, fi := range fis {
77                         name := pathpkg.Join(dir, fi.Name())
78                         if fi.IsDir() {
79                                 scan(name) // recurse
80                                 continue
81                         }
82                         if !strings.HasSuffix(name, ".html") {
83                                 continue
84                         }
85                         // Extract metadata from the file.
86                         b, err := vfs.ReadFile(c.fs, name)
87                         if err != nil {
88                                 log.Printf("updateMetadata %s: %v", name, err)
89                                 continue
90                         }
91                         meta, _, err := extractMetadata(b)
92                         if err != nil {
93                                 log.Printf("updateMetadata: %s: %v", name, err)
94                                 continue
95                         }
96                         // Store relative filesystem path in Metadata.
97                         meta.filePath = name
98                         if meta.Path == "" {
99                                 // If no Path, canonical path is actual path.
100                                 meta.Path = meta.filePath
101                         }
102                         // Store under both paths.
103                         metadata[meta.Path] = &meta
104                         metadata[meta.filePath] = &meta
105                 }
106         }
107         scan("/doc")
108         c.docMetadata.Set(metadata)
109 }
110
111 // MetadataFor returns the *Metadata for a given relative path or nil if none
112 // exists.
113 //
114 func (c *Corpus) MetadataFor(relpath string) *Metadata {
115         if m, _ := c.docMetadata.Get(); m != nil {
116                 meta := m.(map[string]*Metadata)
117                 // If metadata for this relpath exists, return it.
118                 if p := meta[relpath]; p != nil {
119                         return p
120                 }
121                 // Try with or without trailing slash.
122                 if strings.HasSuffix(relpath, "/") {
123                         relpath = relpath[:len(relpath)-1]
124                 } else {
125                         relpath = relpath + "/"
126                 }
127                 return meta[relpath]
128         }
129         return nil
130 }
131
132 // refreshMetadata sends a signal to update DocMetadata. If a refresh is in
133 // progress the metadata will be refreshed again afterward.
134 //
135 func (c *Corpus) refreshMetadata() {
136         select {
137         case c.refreshMetadataSignal <- true:
138         default:
139         }
140 }
141
142 // RefreshMetadataLoop runs forever, updating DocMetadata when the underlying
143 // file system changes. It should be launched in a goroutine.
144 func (c *Corpus) refreshMetadataLoop() {
145         for {
146                 <-c.refreshMetadataSignal
147                 c.updateMetadata()
148                 time.Sleep(10 * time.Second) // at most once every 10 seconds
149         }
150 }