| // 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" | |
| "os" | |
| pathutil "path" | |
| "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). | |
| // | |
| type Mapping struct { | |
| list []mapping | |
| prefixes []string | |
| } | |
| type mapping struct { | |
| prefix, path string | |
| } | |
| // Init initializes the Mapping from a list of ':'-separated | |
| // 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, 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) { | |
| cwd, _ := os.Getwd() // ignore errors | |
| pathlist := strings.Split(paths, ":", 0) | |
| list := make([]mapping, len(pathlist)) | |
| n := 0 // number of mappings | |
| for _, path := range pathlist { | |
| if len(path) == 0 { | |
| // ignore empty paths (don't assume ".") | |
| continue | |
| } | |
| // len(path) > 0: normalize path | |
| if path[0] != '/' { | |
| path = pathutil.Join(cwd, path) | |
| } else { | |
| path = pathutil.Clean(path) | |
| } | |
| // check if mapping exists already | |
| var i int | |
| for i = 0; i < n; i++ { | |
| if path == list[i].path { | |
| break | |
| } | |
| } | |
| // add mapping if it is new | |
| if i >= n { | |
| _, prefix := pathutil.Split(path) | |
| list[n] = mapping{prefix, path} | |
| n++ | |
| } | |
| } | |
| m.list = list[0:n] | |
| } | |
| // 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)) | |
| n := 0 // nuber of prefixes | |
| for _, e := range m.list { | |
| // check if prefix exists already | |
| var i int | |
| for i = 0; i < n; i++ { | |
| if e.prefix == list[i] { | |
| break | |
| } | |
| } | |
| // add prefix if it is new | |
| if i >= n { | |
| list[n] = e.prefix | |
| n++ | |
| } | |
| } | |
| m.prefixes = list[0:n] | |
| } | |
| 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 split(path string) (head, tail string) { | |
| i := strings.Index(path, "/") | |
| if i > 0 { | |
| // 0 < i < len(path) | |
| return path[0:i], path[i+1:] | |
| } | |
| return "", path | |
| } | |
| // ToAbsolute maps a relative path to an absolute path using the Mapping | |
| // specified by the receiver. If the path cannot be mapped, the empty | |
| // string is returned. | |
| // | |
| func (m *Mapping) ToAbsolute(path string) string { | |
| prefix, tail := split(path) | |
| for _, e := range m.list { | |
| switch { | |
| case e.prefix == prefix: | |
| // use tail | |
| case e.prefix == "": | |
| tail = path | |
| default: | |
| continue // no match | |
| } | |
| abspath := pathutil.Join(e.path, tail) | |
| if _, err := os.Stat(abspath); err == nil { | |
| return abspath | |
| } | |
| } | |
| return "" // no match | |
| } | |
| // ToRelative maps an absolute path to a relative path using the Mapping | |
| // specified by the receiver. If the path cannot be mapped, the empty | |
| // string is returned. | |
| // | |
| func (m *Mapping) ToRelative(path string) string { | |
| for _, e := range m.list { | |
| if strings.HasPrefix(path, e.path) { | |
| // /absolute/prefix/foo -> prefix/foo | |
| return pathutil.Join(e.prefix, path[len(e.path):]) // Join will remove a trailing '/' | |
| } | |
| } | |
| return "" // no match | |
| } |