.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / 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 type Metadata struct {
30         // These fields can be set in the JSON header at the top of a doc.
31         Title    string
32         Subtitle string
33         Template bool     // execute as template
34         Path     string   // canonical path for this page
35         AltPaths []string // redirect these other paths to this page
36
37         // These are internal to the implementation.
38         filePath string // filesystem path relative to goroot
39 }
40
41 func (m *Metadata) FilePath() string { return m.filePath }
42
43 // extractMetadata extracts the Metadata from a byte slice.
44 // It returns the Metadata value and the remaining data.
45 // If no metadata is present the original byte slice is returned.
46 //
47 func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) {
48         tail = b
49         if !bytes.HasPrefix(b, jsonStart) {
50                 return
51         }
52         end := bytes.Index(b, jsonEnd)
53         if end < 0 {
54                 return
55         }
56         b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
57         if err = json.Unmarshal(b, &meta); err != nil {
58                 return
59         }
60         tail = tail[end+len(jsonEnd):]
61         return
62 }
63
64 // UpdateMetadata scans $GOROOT/doc for HTML and Markdown files, reads their metadata,
65 // and updates the DocMetadata map.
66 func (c *Corpus) updateMetadata() {
67         metadata := make(map[string]*Metadata)
68         var scan func(string) // scan is recursive
69         scan = func(dir string) {
70                 fis, err := c.fs.ReadDir(dir)
71                 if err != nil {
72                         if dir == "/doc" && errors.Is(err, os.ErrNotExist) {
73                                 // Be quiet during tests that don't have a /doc tree.
74                                 return
75                         }
76                         log.Printf("updateMetadata %s: %v", dir, err)
77                         return
78                 }
79                 for _, fi := range fis {
80                         name := pathpkg.Join(dir, fi.Name())
81                         if fi.IsDir() {
82                                 scan(name) // recurse
83                                 continue
84                         }
85                         if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".md") {
86                                 continue
87                         }
88                         // Extract metadata from the file.
89                         b, err := vfs.ReadFile(c.fs, name)
90                         if err != nil {
91                                 log.Printf("updateMetadata %s: %v", name, err)
92                                 continue
93                         }
94                         meta, _, err := extractMetadata(b)
95                         if err != nil {
96                                 log.Printf("updateMetadata: %s: %v", name, err)
97                                 continue
98                         }
99                         // Present all .md as if they were .html,
100                         // so that it doesn't matter which one a page is written in.
101                         if strings.HasSuffix(name, ".md") {
102                                 name = strings.TrimSuffix(name, ".md") + ".html"
103                         }
104                         // Store relative filesystem path in Metadata.
105                         meta.filePath = name
106                         if meta.Path == "" {
107                                 // If no Path, canonical path is actual path with .html removed.
108                                 meta.Path = strings.TrimSuffix(name, ".html")
109                         }
110                         // Store under both paths.
111                         metadata[meta.Path] = &meta
112                         metadata[meta.filePath] = &meta
113                         for _, path := range meta.AltPaths {
114                                 metadata[path] = &meta
115                         }
116                 }
117         }
118         scan("/doc")
119         c.docMetadata.Set(metadata)
120 }
121
122 // MetadataFor returns the *Metadata for a given relative path or nil if none
123 // exists.
124 //
125 func (c *Corpus) MetadataFor(relpath string) *Metadata {
126         if m, _ := c.docMetadata.Get(); m != nil {
127                 meta := m.(map[string]*Metadata)
128                 // If metadata for this relpath exists, return it.
129                 if p := meta[relpath]; p != nil {
130                         return p
131                 }
132                 // Try with or without trailing slash.
133                 if strings.HasSuffix(relpath, "/") {
134                         relpath = relpath[:len(relpath)-1]
135                 } else {
136                         relpath = relpath + "/"
137                 }
138                 return meta[relpath]
139         }
140         return nil
141 }
142
143 // refreshMetadata sends a signal to update DocMetadata. If a refresh is in
144 // progress the metadata will be refreshed again afterward.
145 //
146 func (c *Corpus) refreshMetadata() {
147         select {
148         case c.refreshMetadataSignal <- true:
149         default:
150         }
151 }
152
153 // RefreshMetadataLoop runs forever, updating DocMetadata when the underlying
154 // file system changes. It should be launched in a goroutine.
155 func (c *Corpus) refreshMetadataLoop() {
156         for {
157                 <-c.refreshMetadataSignal
158                 c.updateMetadata()
159                 time.Sleep(10 * time.Second) // at most once every 10 seconds
160         }
161 }