github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/godoc/meta.go (about)

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