mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-07-18 10:08:34 +00:00
add filesys.InsertPathPart function
This commit is contained in:
@@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
package filesys
|
package filesys
|
||||||
|
|
||||||
import "path/filepath"
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// RootedPath returns a rooted path, e.g. "/foo/bar" as
|
// RootedPath returns a rooted path, e.g. "/foo/bar" as
|
||||||
// opposed to "foo/bar".
|
// opposed to "foo/bar".
|
||||||
@@ -28,3 +32,94 @@ func StripLeadingSeps(s string) string {
|
|||||||
}
|
}
|
||||||
return s[k:]
|
return s[k:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PathSplit converts a file path to a slice of string.
|
||||||
|
// If the path is absolute (if the path has a leading slash),
|
||||||
|
// then the first entry in the result is an empty string.
|
||||||
|
// Desired: path == PathJoin(PathSplit(path))
|
||||||
|
func PathSplit(incoming string) []string {
|
||||||
|
if incoming == "" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
dir, path := filepath.Split(incoming)
|
||||||
|
if dir == string(os.PathSeparator) {
|
||||||
|
if path == "" {
|
||||||
|
return []string{""}
|
||||||
|
}
|
||||||
|
return []string{"", path}
|
||||||
|
}
|
||||||
|
dir = strings.TrimSuffix(dir, string(os.PathSeparator))
|
||||||
|
if dir == "" {
|
||||||
|
return []string{path}
|
||||||
|
}
|
||||||
|
return append(PathSplit(dir), path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathJoin converts a slice of string to a file path.
|
||||||
|
// If the first entry is an empty string, then the returned
|
||||||
|
// path is absolute (it has a leading slash).
|
||||||
|
// Desired: path == PathJoin(PathSplit(path))
|
||||||
|
func PathJoin(incoming []string) string {
|
||||||
|
if len(incoming) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if incoming[0] == "" {
|
||||||
|
return string(os.PathSeparator) + filepath.Join(incoming[1:]...)
|
||||||
|
}
|
||||||
|
return filepath.Join(incoming...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertPathPart inserts 'part' at position 'pos' in the given filepath.
|
||||||
|
// The first position is 0.
|
||||||
|
//
|
||||||
|
// E.g. if part == 'PEACH'
|
||||||
|
//
|
||||||
|
// OLD : NEW : POS
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// {empty} : PEACH : irrelevant
|
||||||
|
// / : /PEACH : irrelevant
|
||||||
|
// pie : PEACH/pie : 0 (or negative)
|
||||||
|
// /pie : /PEACH/pie : 0 (or negative)
|
||||||
|
// raw : raw/PEACH : 1 (or larger)
|
||||||
|
// /raw : /raw/PEACH : 1 (or larger)
|
||||||
|
// a/nice/warm/pie : a/nice/warm/PEACH/pie : 3
|
||||||
|
// /a/nice/warm/pie : /a/nice/warm/PEACH/pie : 3
|
||||||
|
//
|
||||||
|
// * An empty part results in no change.
|
||||||
|
//
|
||||||
|
// * Absolute paths get their leading '/' stripped, treated like
|
||||||
|
// relative paths, and the leading '/' is re-added on output.
|
||||||
|
// The meaning of pos is intentionally the same in either absolute or
|
||||||
|
// relative paths; if it weren't, this function could convert absolute
|
||||||
|
// paths to relative paths, which is not desirable.
|
||||||
|
//
|
||||||
|
// * For robustness (liberal input, conservative output) Pos values that
|
||||||
|
// that are too small (large) to index the split filepath result in a
|
||||||
|
// prefix (postfix) rather than an error. Use extreme position values
|
||||||
|
// to assure a prefix or postfix (e.g. 0 will always prefix, and
|
||||||
|
// 9999 will presumably always postfix).
|
||||||
|
func InsertPathPart(path string, pos int, part string) string {
|
||||||
|
if part == "" {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
parts := PathSplit(path)
|
||||||
|
if pos < 0 {
|
||||||
|
pos = 0
|
||||||
|
} else if pos > len(parts) {
|
||||||
|
pos = len(parts)
|
||||||
|
}
|
||||||
|
if len(parts) > 0 && parts[0] == "" && pos < len(parts) {
|
||||||
|
// An empty string at 0 indicates an absolute path, and means
|
||||||
|
// we must increment pos. This change means that a position
|
||||||
|
// specification has the same meaning in relative and absolute paths.
|
||||||
|
// E.g. in either the path 'a/b/c' or the path '/a/b/c',
|
||||||
|
// 'a' is at 0, 'b' is at 1 and 'c' is at 2, and inserting at
|
||||||
|
// zero means a new first field _without_ changing an absolute
|
||||||
|
// path to a relative path.
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
result := make([]string, len(parts)+1)
|
||||||
|
copy(result, parts[0:pos])
|
||||||
|
result[pos] = part
|
||||||
|
return PathJoin(append(result, parts[pos:]...))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package filesys_test
|
package filesys_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -93,6 +94,11 @@ func TestFilePathSplit(t *testing.T) {
|
|||||||
dir: "",
|
dir: "",
|
||||||
file: "rabbit.jpg",
|
file: "rabbit.jpg",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
full: "/",
|
||||||
|
dir: "/",
|
||||||
|
file: "",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
full: "/beans",
|
full: "/beans",
|
||||||
dir: "/",
|
dir: "/",
|
||||||
@@ -124,6 +130,169 @@ func TestFilePathSplit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathSplitAndJoin(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
original string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
"Empty": {
|
||||||
|
original: "",
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
"One": {
|
||||||
|
original: "hello",
|
||||||
|
expected: []string{"hello"},
|
||||||
|
},
|
||||||
|
"Two": {
|
||||||
|
original: "hello/there",
|
||||||
|
expected: []string{"hello", "there"},
|
||||||
|
},
|
||||||
|
"Three": {
|
||||||
|
original: "hello/my/friend",
|
||||||
|
expected: []string{"hello", "my", "friend"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for n, c := range cases {
|
||||||
|
f := func(t *testing.T, original string, expected []string) {
|
||||||
|
actual := PathSplit(original)
|
||||||
|
if len(actual) != len(expected) {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected len %d, got len %d",
|
||||||
|
len(expected), len(actual))
|
||||||
|
}
|
||||||
|
for i := range expected {
|
||||||
|
if expected[i] != actual[i] {
|
||||||
|
t.Fatalf(
|
||||||
|
"at i=%d, expected '%s', got '%s'",
|
||||||
|
i, expected[i], actual[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
joined := PathJoin(actual)
|
||||||
|
if joined != original {
|
||||||
|
t.Fatalf(
|
||||||
|
"when rejoining, expected '%s', got '%s'",
|
||||||
|
original, joined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Run("relative"+n, func(t *testing.T) {
|
||||||
|
f(t, c.original, c.expected)
|
||||||
|
})
|
||||||
|
t.Run("absolute"+n, func(t *testing.T) {
|
||||||
|
f(t,
|
||||||
|
string(os.PathSeparator)+c.original,
|
||||||
|
append([]string{""}, c.expected...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertPathPart(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
original string
|
||||||
|
pos int
|
||||||
|
part string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
"rootOne": {
|
||||||
|
original: "/",
|
||||||
|
pos: 0,
|
||||||
|
part: "___",
|
||||||
|
expected: "/___",
|
||||||
|
},
|
||||||
|
"rootTwo": {
|
||||||
|
original: "/",
|
||||||
|
pos: 444,
|
||||||
|
part: "___",
|
||||||
|
expected: "/___",
|
||||||
|
},
|
||||||
|
"rootedFirst": {
|
||||||
|
original: "/apple",
|
||||||
|
pos: 0,
|
||||||
|
part: "___",
|
||||||
|
expected: "/___/apple",
|
||||||
|
},
|
||||||
|
"rootedSecond": {
|
||||||
|
original: "/apple",
|
||||||
|
pos: 444,
|
||||||
|
part: "___",
|
||||||
|
expected: "/apple/___",
|
||||||
|
},
|
||||||
|
"rootedThird": {
|
||||||
|
original: "/apple/banana",
|
||||||
|
pos: 444,
|
||||||
|
part: "___",
|
||||||
|
expected: "/apple/banana/___",
|
||||||
|
},
|
||||||
|
"emptyLow": {
|
||||||
|
original: "",
|
||||||
|
pos: -3,
|
||||||
|
part: "___",
|
||||||
|
expected: "___",
|
||||||
|
},
|
||||||
|
"emptyHigh": {
|
||||||
|
original: "",
|
||||||
|
pos: 444,
|
||||||
|
part: "___",
|
||||||
|
expected: "___",
|
||||||
|
},
|
||||||
|
"peachPie": {
|
||||||
|
original: "a/nice/warm/pie",
|
||||||
|
pos: 3,
|
||||||
|
part: "PEACH",
|
||||||
|
expected: "a/nice/warm/PEACH/pie",
|
||||||
|
},
|
||||||
|
"rootedPeachPie": {
|
||||||
|
original: "/a/nice/warm/pie",
|
||||||
|
pos: 3,
|
||||||
|
part: "PEACH",
|
||||||
|
expected: "/a/nice/warm/PEACH/pie",
|
||||||
|
},
|
||||||
|
"longStart": {
|
||||||
|
original: "a/b/c/d/e/f",
|
||||||
|
pos: 0,
|
||||||
|
part: "___",
|
||||||
|
expected: "___/a/b/c/d/e/f",
|
||||||
|
},
|
||||||
|
"rootedLongStart": {
|
||||||
|
original: "/a/b/c/d/e/f",
|
||||||
|
pos: 0,
|
||||||
|
part: "___",
|
||||||
|
expected: "/___/a/b/c/d/e/f",
|
||||||
|
},
|
||||||
|
"longMiddle": {
|
||||||
|
original: "a/b/c/d/e/f",
|
||||||
|
pos: 3,
|
||||||
|
part: "___",
|
||||||
|
expected: "a/b/c/___/d/e/f",
|
||||||
|
},
|
||||||
|
"rootedLongMiddle": {
|
||||||
|
original: "/a/b/c/d/e/f",
|
||||||
|
pos: 3,
|
||||||
|
part: "___",
|
||||||
|
expected: "/a/b/c/___/d/e/f",
|
||||||
|
},
|
||||||
|
"longEnd": {
|
||||||
|
original: "a/b/c/d/e/f",
|
||||||
|
pos: 444,
|
||||||
|
part: "___",
|
||||||
|
expected: "a/b/c/d/e/f/___",
|
||||||
|
},
|
||||||
|
"rootedLongEnd": {
|
||||||
|
original: "/a/b/c/d/e/f",
|
||||||
|
pos: 444,
|
||||||
|
part: "___",
|
||||||
|
expected: "/a/b/c/d/e/f/___",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for n, c := range cases {
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
actual := InsertPathPart(c.original, c.pos, c.part)
|
||||||
|
if actual != c.expected {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", c.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStripTrailingSeps(t *testing.T) {
|
func TestStripTrailingSeps(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
full string
|
full string
|
||||||
|
|||||||
Reference in New Issue
Block a user