util: Add some path manipulation algorithms
These could use some optimization by an algorithmist! Not urgent right now since they're not currently in any fast paths in the code.
This commit is contained in:
78
util/util.go
78
util/util.go
@@ -293,6 +293,84 @@ loop:
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SafePathClean does path.Clean, but it preserves any trailing slash if it was
|
||||||
|
// present in the initial path.
|
||||||
|
func SafePathClean(s string) string {
|
||||||
|
hasSlash := strings.HasSuffix(s, "/")
|
||||||
|
clean := path.Clean(s) // removes trailing slashes
|
||||||
|
if hasSlash { // add it back if it was taken off
|
||||||
|
clean = clean + "/"
|
||||||
|
}
|
||||||
|
return clean
|
||||||
|
}
|
||||||
|
|
||||||
|
// SegmentedPathSplit splits an absolute path into chunks including their slash.
|
||||||
|
// This is similar to the PathSplit function, but the slashes are not lost here!
|
||||||
|
// TODO: There is likely a more efficient implementation of this function.
|
||||||
|
func SegmentedPathSplit(p string) []string {
|
||||||
|
out := []string{}
|
||||||
|
sponge := ""
|
||||||
|
for i := 0; i < len(p); i++ {
|
||||||
|
sponge = sponge + string(p[i])
|
||||||
|
if string(p[i]) == "/" {
|
||||||
|
out = append(out, sponge) // found one!
|
||||||
|
sponge = "" // reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sponge != "" {
|
||||||
|
out = append(out, sponge) // the last piece
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonPathPrefix returns the longest common prefix directory out of all the
|
||||||
|
// input paths! This is always a directory and thus ends with a slash, unless
|
||||||
|
// all of the paths are identical and are files, or if only one path is given in
|
||||||
|
// which case that is returned, or unless no paths are given in which case the
|
||||||
|
// empty string is returned. If any of your input paths are not absolute, and as
|
||||||
|
// such do not begin with a slash, then the behaviour is undefined.
|
||||||
|
func CommonPathPrefix(paths ...string) string {
|
||||||
|
// XXX: I am not a good algorithmist, there is probably a more efficient
|
||||||
|
// way to write this algorithm. Patches are welcome!
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(paths) == 1 {
|
||||||
|
return paths[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
sps := make([][]string, 0)
|
||||||
|
for _, x := range paths {
|
||||||
|
if !strings.HasPrefix(x, "/") {
|
||||||
|
// TODO: panic? error?
|
||||||
|
return "" // undefined behaviour!
|
||||||
|
}
|
||||||
|
//x = SafePathClean(x) // TODO: Should we "safe clean" each path?
|
||||||
|
sp := SegmentedPathSplit(x) // compute once in advance
|
||||||
|
sps = append(sps, sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
z := sps[0] // SegmentedPathSplit(paths[0]) // arbitrarily choose the first one
|
||||||
|
ix := 0
|
||||||
|
ret := ""
|
||||||
|
for {
|
||||||
|
for i := range paths {
|
||||||
|
sp := sps[i] // SegmentedPathSplit(paths[i])
|
||||||
|
if len(sp) <= ix { // one path is longer
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp[ix] != z[ix] { // the chunk differs!
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ret + z[ix] // append what we've got so far!
|
||||||
|
ix++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PathPrefixDelta returns the delta of the path prefix, which tells you how
|
// PathPrefixDelta returns the delta of the path prefix, which tells you how
|
||||||
// many path tokens different the prefix is.
|
// many path tokens different the prefix is.
|
||||||
func PathPrefixDelta(p, prefix string) int {
|
func PathPrefixDelta(p, prefix string) int {
|
||||||
|
|||||||
@@ -795,6 +795,117 @@ func TestUtilT11(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSegmentedPathSplit(t *testing.T) {
|
||||||
|
if ex, out := []string{}, SegmentedPathSplit(
|
||||||
|
"",
|
||||||
|
); !reflect.DeepEqual(out, ex) {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ex, out := []string{"/"}, SegmentedPathSplit(
|
||||||
|
"/",
|
||||||
|
); !reflect.DeepEqual(out, ex) {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ex, out := []string{"/", "foo/", "bar/"}, SegmentedPathSplit(
|
||||||
|
"/foo/bar/",
|
||||||
|
); !reflect.DeepEqual(out, ex) {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ex, out := []string{"/", "foo/", "bar"}, SegmentedPathSplit(
|
||||||
|
"/foo/bar",
|
||||||
|
); !reflect.DeepEqual(out, ex) {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommonPathPrefix1(t *testing.T) {
|
||||||
|
if ex, out := "/foo/whatever2/", CommonPathPrefix(
|
||||||
|
"/foo/whatever2/",
|
||||||
|
"/foo/whatever2/",
|
||||||
|
"/foo/whatever2/",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommonPathPrefix2(t *testing.T) {
|
||||||
|
if ex, out := "/whatever1", CommonPathPrefix(
|
||||||
|
"/whatever1",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
if ex, out := "/whatever2", CommonPathPrefix(
|
||||||
|
"/whatever2",
|
||||||
|
"/whatever2",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
if ex, out := "/foo/whatever1", CommonPathPrefix(
|
||||||
|
"/foo/whatever1",
|
||||||
|
"/foo/whatever1",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
if ex, out := "/foo/whatever2", CommonPathPrefix(
|
||||||
|
"/foo/whatever2",
|
||||||
|
"/foo/whatever2",
|
||||||
|
"/foo/whatever2",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ex, out := "/whatever3/", CommonPathPrefix(
|
||||||
|
"/whatever3/",
|
||||||
|
"/whatever3/",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
if ex, out := "/foo/whatever3/", CommonPathPrefix(
|
||||||
|
"/foo/whatever3/",
|
||||||
|
"/foo/whatever3/",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
if ex, out := "/foo/whatever4/", CommonPathPrefix(
|
||||||
|
"/foo/whatever4/",
|
||||||
|
"/foo/whatever4/",
|
||||||
|
"/foo/whatever4/",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ex, out := "/", CommonPathPrefix(
|
||||||
|
"/foo/bar",
|
||||||
|
"/bar/baz/",
|
||||||
|
"/baz/bing/wow",
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ex, out := "/foo/", CommonPathPrefix(
|
||||||
|
"/foo/bar/",
|
||||||
|
"/foo/bar/dude",
|
||||||
|
"/foo/bar", // this is not the same as /foo/bar/ !
|
||||||
|
); out != ex {
|
||||||
|
t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we want to "safe clean" each path, then this test should be added.
|
||||||
|
//if ex, out := "/home/james/tmp/", CommonPathPrefix(
|
||||||
|
// "/home/james/tmp/coverage/test",
|
||||||
|
// "/home/james/tmp/covert/operator",
|
||||||
|
// "/home/james/tmp/coven/members",
|
||||||
|
// "/home//james/tmp/coventry",
|
||||||
|
// "/home/james/././tmp/covertly/foo",
|
||||||
|
// "/home/luser/../james/tmp/coved/bar",
|
||||||
|
//); out != ex {
|
||||||
|
// t.Errorf("expected: %v got: %v", ex, out)
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUtilFlattenListWithSplit1(t *testing.T) {
|
func TestUtilFlattenListWithSplit1(t *testing.T) {
|
||||||
{
|
{
|
||||||
in := []string{} // input
|
in := []string{} // input
|
||||||
|
|||||||
Reference in New Issue
Block a user