diff options
Diffstat (limited to 'src/cmd/godoc/utils.go')
-rw-r--r-- | src/cmd/godoc/utils.go | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/cmd/godoc/utils.go b/src/cmd/godoc/utils.go new file mode 100644 index 000000000..11e46aee5 --- /dev/null +++ b/src/cmd/godoc/utils.go @@ -0,0 +1,168 @@ +// Copyright 2010 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. + +// This file contains support functionality for godoc. + +package main + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + "utf8" +) + +// An RWValue wraps a value and permits mutually exclusive +// access to it and records the time the value was last set. +// +type RWValue struct { + mutex sync.RWMutex + value interface{} + timestamp int64 // time of last set(), in seconds since epoch +} + +func (v *RWValue) set(value interface{}) { + v.mutex.Lock() + v.value = value + v.timestamp = time.Seconds() + v.mutex.Unlock() +} + +func (v *RWValue) get() (interface{}, int64) { + v.mutex.RLock() + defer v.mutex.RUnlock() + return v.value, v.timestamp +} + +// TODO(gri) For now, using os.Getwd() is ok here since the functionality +// based on this code is not invoked for the appengine version, +// but this is fragile. Determine what the right thing to do is, +// here (possibly have some Getwd-equivalent in FileSystem). +var cwd, _ = os.Getwd() // ignore errors + +// canonicalizePaths takes a list of (directory/file) paths and returns +// the list of corresponding absolute paths in sorted (increasing) order. +// Relative paths are assumed to be relative to the current directory, +// empty and duplicate paths as well as paths for which filter(path) is +// false are discarded. filter may be nil in which case it is not used. +// +func canonicalizePaths(list []string, filter func(path string) bool) []string { + i := 0 + for _, path := range list { + path = strings.TrimSpace(path) + if len(path) == 0 { + continue // ignore empty paths (don't assume ".") + } + // len(path) > 0: normalize path + if filepath.IsAbs(path) { + path = filepath.Clean(path) + } else { + path = filepath.Join(cwd, path) + } + // we have a non-empty absolute path + if filter != nil && !filter(path) { + continue + } + // keep the path + list[i] = path + i++ + } + list = list[0:i] + + // sort the list and remove duplicate entries + sort.Strings(list) + i = 0 + prev := "" + for _, path := range list { + if path != prev { + list[i] = path + i++ + prev = path + } + } + + return list[0:i] +} + +// writeFileAtomically writes data to a temporary file and then +// atomically renames that file to the file named by filename. +// +func writeFileAtomically(filename string, data []byte) os.Error { + // TODO(gri) this won't work on appengine + f, err := ioutil.TempFile(filepath.Split(filename)) + if err != nil { + return err + } + n, err := f.Write(data) + f.Close() + if err != nil { + return err + } + if n < len(data) { + return io.ErrShortWrite + } + return os.Rename(f.Name(), filename) +} + +// isText returns true if a significant prefix of s looks like correct UTF-8; +// that is, if it is likely that s is human-readable text. +// +func isText(s []byte) bool { + const max = 1024 // at least utf8.UTFMax + if len(s) > max { + s = s[0:max] + } + for i, c := range string(s) { + if i+utf8.UTFMax > len(s) { + // last char may be incomplete - ignore + break + } + if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' { + // decoding error or control character - not a text file + return false + } + } + return true +} + +// TODO(gri): Should have a mapping from extension to handler, eventually. + +// textExt[x] is true if the extension x indicates a text file, and false otherwise. +var textExt = map[string]bool{ + ".css": false, // must be served raw + ".js": false, // must be served raw +} + +// isTextFile returns true if the file has a known extension indicating +// a text file, or if a significant chunk of the specified file looks like +// correct UTF-8; that is, if it is likely that the file contains human- +// readable text. +// +func isTextFile(filename string) bool { + // if the extension is known, use it for decision making + if isText, found := textExt[filepath.Ext(filename)]; found { + return isText + } + + // the extension is not known; read an initial chunk + // of the file and check if it looks like text + f, err := fs.Open(filename) + if err != nil { + return false + } + defer f.Close() + + var buf [1024]byte + n, err := f.Read(buf[0:]) + if err != nil { + return false + } + + return isText(buf[0:n]) +} |