blob: e4349423375850adda496237c7e595d7d4f06dff [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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.
// Matching the unix-like build tags in the Golang standard library based on the dependency
// on "path/filepath", i.e. https://cs.opensource.google/go/go/+/refs/tags/go1.17:src/path/filepath/path_unix.go;l=5-6
//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris
package normalpath
import (
"path/filepath"
"strings"
)
// NormalizeAndValidate normalizes and validates the given path.
//
// This calls Normalize on the path.
// Returns Error if the path is not relative or jumps context.
// This can be used to validate that paths are valid to use with Buckets.
// The error message is safe to pass to users.
func NormalizeAndValidate(path string) (string, error) {
normalizedPath := Normalize(path)
if filepath.IsAbs(normalizedPath) {
return "", NewError(path, errNotRelative)
}
// https://github.com/ProtobufMan/bufman-cli/issues/51
if strings.HasPrefix(normalizedPath, normalizedRelPathJumpContextPrefix) {
return "", NewError(path, errOutsideContextDir)
}
return normalizedPath, nil
}
// EqualsOrContainsPath returns true if the value is equal to or contains the path.
//
// The path and value are expected to be normalized and validated if Relative is used.
// The path and value are expected to be normalized and absolute if Absolute is used.
func EqualsOrContainsPath(value string, path string, pathType PathType) bool {
pathRoot := stringOSPathSeparator
if pathType == Relative {
pathRoot = "."
}
if value == pathRoot {
return true
}
// Walk up the path and compare at each directory level until there is a
// match or the path reaches its root (either `/` or `.`).
for curPath := path; curPath != pathRoot; curPath = Dir(curPath) {
if value == curPath {
return true
}
}
return false
}
// MapHasEqualOrContainingPath returns true if the path matches any file or directory in the map.
//
// The path and keys in m are expected to be normalized and validated if Relative is used.
// The path and keys in m are expected to be normalized and absolute if Absolute is used.
//
// If the map is empty, returns false.
func MapHasEqualOrContainingPath(m map[string]struct{}, path string, pathType PathType) bool {
if len(m) == 0 {
return false
}
pathRoot := stringOSPathSeparator
if pathType == Relative {
pathRoot = "."
}
if _, ok := m[pathRoot]; ok {
return true
}
for curPath := path; curPath != pathRoot; curPath = Dir(curPath) {
if _, ok := m[curPath]; ok {
return true
}
}
return false
}
// MapAllEqualOrContainingPathMap returns the paths in m that are equal to, or contain
// path, in a new map.
//
// The path and keys in m are expected to be normalized and validated if Relative is used.
// The path and keys in m are expected to be normalized and absolute if Absolute is used.
//
// If the map is empty, returns nil.
func MapAllEqualOrContainingPathMap(m map[string]struct{}, path string, pathType PathType) map[string]struct{} {
if len(m) == 0 {
return nil
}
pathRoot := stringOSPathSeparator
if pathType == Relative {
pathRoot = "."
}
n := make(map[string]struct{})
if _, ok := m[pathRoot]; ok {
// also covers if path == separator.
n[pathRoot] = struct{}{}
}
for potentialMatch := range m {
for curPath := path; curPath != pathRoot; curPath = Dir(curPath) {
if potentialMatch == curPath {
n[potentialMatch] = struct{}{}
break
}
}
}
return n
}
// Components splits the path into it's components.
//
// This calls filepath.Split repeatedly.
//
// The path is expected to be normalized.
func Components(path string) []string {
var components []string
dir := Unnormalize(path)
for {
var file string
dir, file = filepath.Split(dir)
// puts in reverse
components = append(components, file)
if dir == stringOSPathSeparator {
components = append(components, dir)
break
}
dir = strings.TrimSuffix(dir, stringOSPathSeparator)
if dir == "" {
break
}
}
// https://github.com/golang/go/wiki/SliceTricks#reversing
for i := len(components)/2 - 1; i >= 0; i-- {
opp := len(components) - 1 - i
components[i], components[opp] = components[opp], components[i]
}
for i, component := range components {
components[i] = Normalize(component)
}
return components
}