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.
17 "golang.org/x/tools/godoc/vfs"
21 doctype = []byte("<!DOCTYPE ")
22 jsonStart = []byte("<!--{")
23 jsonEnd = []byte("}-->")
26 // ----------------------------------------------------------------------------
27 // Documentation Metadata
29 // TODO(adg): why are some exported and some aren't? -brad
30 type Metadata struct {
33 Template bool // execute as template
34 Path string // canonical path for this page
35 filePath string // filesystem path relative to goroot
38 func (m *Metadata) FilePath() string { return m.filePath }
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.
44 func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) {
46 if !bytes.HasPrefix(b, jsonStart) {
49 end := bytes.Index(b, jsonEnd)
53 b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
54 if err = json.Unmarshal(b, &meta); err != nil {
57 tail = tail[end+len(jsonEnd):]
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)
69 if dir == "/doc" && errors.Is(err, os.ErrNotExist) {
70 // Be quiet during tests that don't have a /doc tree.
73 log.Printf("updateMetadata %s: %v", dir, err)
76 for _, fi := range fis {
77 name := pathpkg.Join(dir, fi.Name())
82 if !strings.HasSuffix(name, ".html") {
85 // Extract metadata from the file.
86 b, err := vfs.ReadFile(c.fs, name)
88 log.Printf("updateMetadata %s: %v", name, err)
91 meta, _, err := extractMetadata(b)
93 log.Printf("updateMetadata: %s: %v", name, err)
96 // Store relative filesystem path in Metadata.
99 // If no Path, canonical path is actual path.
100 meta.Path = meta.filePath
102 // Store under both paths.
103 metadata[meta.Path] = &meta
104 metadata[meta.filePath] = &meta
108 c.docMetadata.Set(metadata)
111 // MetadataFor returns the *Metadata for a given relative path or nil if none
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 {
121 // Try with or without trailing slash.
122 if strings.HasSuffix(relpath, "/") {
123 relpath = relpath[:len(relpath)-1]
125 relpath = relpath + "/"
132 // refreshMetadata sends a signal to update DocMetadata. If a refresh is in
133 // progress the metadata will be refreshed again afterward.
135 func (c *Corpus) refreshMetadata() {
137 case c.refreshMetadataSignal <- true:
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() {
146 <-c.refreshMetadataSignal
148 time.Sleep(10 * time.Second) // at most once every 10 seconds