diff options
Diffstat (limited to 'src/cmd/godoc/mapping.go')
-rw-r--r-- | src/cmd/godoc/mapping.go | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/src/cmd/godoc/mapping.go b/src/cmd/godoc/mapping.go new file mode 100644 index 000000000..51f23ab98 --- /dev/null +++ b/src/cmd/godoc/mapping.go @@ -0,0 +1,200 @@ +// Copyright 2009 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 implements the Mapping data structure. + +package main + +import ( + "fmt" + "io" + "path" + "path/filepath" + "sort" + "strings" +) + +// A Mapping object maps relative paths (e.g. from URLs) +// to absolute paths (of the file system) and vice versa. +// +// A Mapping object consists of a list of individual mappings +// of the form: prefix -> path which are interpreted as follows: +// A relative path of the form prefix/tail is to be mapped to +// the absolute path/tail, if that absolute path exists in the file +// system. Given a Mapping object, a relative path is mapped to an +// absolute path by trying each of the individual mappings in order, +// until a valid mapping is found. For instance, for the mapping: +// +// user -> /home/user +// public -> /home/user/public +// public -> /home/build/public +// +// the relative paths below are mapped to absolute paths as follows: +// +// user/foo -> /home/user/foo +// public/net/rpc/file1.go -> /home/user/public/net/rpc/file1.go +// +// If there is no /home/user/public/net/rpc/file2.go, the next public +// mapping entry is used to map the relative path to: +// +// public/net/rpc/file2.go -> /home/build/public/net/rpc/file2.go +// +// (assuming that file exists). +// +// Each individual mapping also has a RWValue associated with it that +// may be used to store mapping-specific information. See the Iterate +// method. +// +type Mapping struct { + list []mapping + prefixes []string // lazily computed from list +} + +type mapping struct { + prefix, path string + value *RWValue +} + +// Init initializes the Mapping from a list of paths. +// Empty paths are ignored; relative paths are assumed to be relative to +// the current working directory and converted to absolute paths. +// For each path of the form: +// +// dirname/localname +// +// a mapping +// +// localname -> path +// +// is added to the Mapping object, in the order of occurrence. +// For instance, under Unix, the argument: +// +// /home/user:/home/build/public +// +// leads to the following mapping: +// +// user -> /home/user +// public -> /home/build/public +// +func (m *Mapping) Init(paths []string) { + pathlist := canonicalizePaths(paths, nil) + list := make([]mapping, len(pathlist)) + + // create mapping list + for i, path := range pathlist { + _, prefix := filepath.Split(path) + list[i] = mapping{prefix, path, new(RWValue)} + } + + m.list = list +} + +// IsEmpty returns true if there are no mappings specified. +func (m *Mapping) IsEmpty() bool { return len(m.list) == 0 } + +// PrefixList returns a list of all prefixes, with duplicates removed. +// For instance, for the mapping: +// +// user -> /home/user +// public -> /home/user/public +// public -> /home/build/public +// +// the prefix list is: +// +// user, public +// +func (m *Mapping) PrefixList() []string { + // compute the list lazily + if m.prefixes == nil { + list := make([]string, len(m.list)) + + // populate list + for i, e := range m.list { + list[i] = e.prefix + } + + // 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 + } + } + + m.prefixes = list[0:i] + } + + return m.prefixes +} + +// Fprint prints the mapping. +func (m *Mapping) Fprint(w io.Writer) { + for _, e := range m.list { + fmt.Fprintf(w, "\t%s -> %s\n", e.prefix, e.path) + } +} + +func splitFirst(path string) (head, tail string) { + i := strings.Index(path, string(filepath.Separator)) + if i > 0 { + // 0 < i < len(path) + return path[0:i], path[i+1:] + } + return "", path +} + +// ToAbsolute maps a slash-separated relative path to an absolute filesystem +// path using the Mapping specified by the receiver. If the path cannot +// be mapped, the empty string is returned. +// +func (m *Mapping) ToAbsolute(spath string) string { + fpath := filepath.FromSlash(spath) + prefix, tail := splitFirst(fpath) + for _, e := range m.list { + switch { + case e.prefix == prefix: + // use tail + case e.prefix == "": + tail = fpath + default: + continue // no match + } + abspath := filepath.Join(e.path, tail) + if _, err := fs.Stat(abspath); err == nil { + return abspath + } + } + + return "" // no match +} + +// ToRelative maps an absolute filesystem path to a relative slash-separated +// path using the Mapping specified by the receiver. If the path cannot +// be mapped, the empty string is returned. +// +func (m *Mapping) ToRelative(fpath string) string { + for _, e := range m.list { + if strings.HasPrefix(fpath, e.path) { + spath := filepath.ToSlash(fpath) + // /absolute/prefix/foo -> prefix/foo + return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/' + } + } + return "" // no match +} + +// Iterate calls f for each path and RWValue in the mapping (in uspecified order) +// until f returns false. +// +func (m *Mapping) Iterate(f func(path string, value *RWValue) bool) { + for _, e := range m.list { + if !f(e.path, e.value) { + return + } + } +} |