| /* Copyright 2018 The Bazel Authors. All rights reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| // Package pathtools provides utilities for manipulating paths. Most paths |
| // within Gazelle are slash-separated paths, relative to the repository root |
| // directory. The repository root directory is represented by the empty |
| // string. Paths in this format may be used directly as package names in labels. |
| package pathtools |
| |
| import ( |
| "path" |
| "path/filepath" |
| "strings" |
| ) |
| |
| // HasPrefix returns whether the slash-separated path p has the given |
| // prefix. Unlike strings.HasPrefix, this function respects component |
| // boundaries, so "/home/foo" is not a prefix is "/home/foobar/baz". If the |
| // prefix is empty, this function always returns true. |
| func HasPrefix(p, prefix string) bool { |
| p = trimTrailingSlash(p) |
| prefix = trimTrailingSlash(prefix) |
| return prefix == "" || p == prefix || strings.HasPrefix(p, prefix+"/") |
| } |
| |
| // TrimPrefix returns p without the provided prefix. If p doesn't start |
| // with prefix, it returns p unchanged. Unlike strings.HasPrefix, this function |
| // respects component boundaries (assuming slash-separated paths), so |
| // TrimPrefix("foo/bar", "foo") returns "baz". |
| func TrimPrefix(p, prefix string) string { |
| origPath := p |
| p = trimTrailingSlash(p) |
| prefix = trimTrailingSlash(prefix) |
| if prefix == "" { |
| return origPath |
| } |
| if prefix == p { |
| return "" |
| } |
| return strings.TrimPrefix(p, prefix+"/") |
| } |
| |
| // RelBaseName returns the base name for rel, a slash-separated path relative |
| // to the repository root. If rel is empty, RelBaseName returns the base name |
| // of prefix. If prefix is empty, RelBaseName returns the base name of root, |
| // the absolute file path of the repository root directory. If that's empty |
| // to, then RelBaseName returns "root". |
| func RelBaseName(rel, prefix, root string) string { |
| base := path.Base(rel) |
| if base == "." || base == "/" { |
| base = path.Base(prefix) |
| } |
| if base == "." || base == "/" { |
| base = filepath.Base(root) |
| } |
| if base == "." || base == "/" { |
| base = "root" |
| } |
| return base |
| } |
| |
| // Index returns the starting index of the string sub within the non-absolute |
| // slash-separated path p. sub must start and end at component boundaries |
| // within p. |
| func Index(p, sub string) int { |
| if sub == "" { |
| return 0 |
| } |
| p = path.Clean(p) |
| sub = path.Clean(sub) |
| if path.IsAbs(sub) { |
| if HasPrefix(p, sub) { |
| return 0 |
| } else { |
| return -1 |
| } |
| } |
| if p == "" || p == "/" { |
| return -1 |
| } |
| |
| i := 0 // i is the index of the first byte of a path element |
| if len(p) > 0 && p[0] == '/' { |
| i++ |
| } |
| for { |
| suffix := p[i:] |
| if len(suffix) < len(sub) { |
| return -1 |
| } |
| if suffix[:len(sub)] == sub && (len(suffix) == len(sub) || suffix[len(sub)] == '/') { |
| return i |
| } |
| j := strings.IndexByte(suffix, '/') |
| if j < 0 { |
| return -1 |
| } |
| i += j + 1 |
| if i >= len(p) { |
| return -1 |
| } |
| } |
| } |
| |
| // Prefixes returns an iterator (iter.Seq) over all the prefixes of p. |
| // For example, if p is "a/b/c", the iterator yields "", "a", "a/b", "a/b/c". |
| // |
| // p must be a slash-separated path. It may be relative or absolute. p |
| // does not need to be a clean path, but if it is not clean, Prefixes ignores |
| // redundant slashes while keeping redundant path elements. For example, |
| // if p is "a/../b//c/", the iterator yields "a", "..", "b", "c". |
| func Prefixes(p string) func(yield func(string) bool) { |
| return func(yield func(string) bool) { |
| var slash int |
| if strings.HasPrefix(p, "/") { |
| slash = 0 |
| } else { |
| slash = -1 |
| } |
| if ok := yield(p[:slash+1]); !ok { |
| return |
| } |
| for { |
| i := strings.Index(p[slash+1:], "/") |
| if i < 0 { |
| break |
| } |
| if ok := yield(p[:slash+1+i]); !ok { |
| return |
| } |
| slash += 1 + i |
| for slash+1 < len(p) && p[slash+1] == '/' { |
| slash++ // skip over multiple slashes |
| } |
| } |
| if p != "" && !strings.HasSuffix(p, "/") { |
| yield(p) |
| } |
| } |
| } |
| |
| func trimTrailingSlash(p string) string { |
| for len(p) > 1 && p[len(p)-1] == '/' { |
| p = p[:len(p)-1] |
| } |
| return p |
| } |