util: Add some path helper functions
In the end, I'm not sure how useful these will be, but we'll keep them in for now.
This commit is contained in:
54
util/util.go
54
util/util.go
@@ -421,6 +421,60 @@ func RemovePathSuffix(s string) (string, error) {
|
|||||||
return strings.Join(x, "/") + "/", nil
|
return strings.Join(x, "/") + "/", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DirParents returns a list of the parent directories in a given path. If you
|
||||||
|
// pass it an empty string, or a single slash, then you will get an empty list.
|
||||||
|
// If you pass it a malformed path, then you might get unexpected results.
|
||||||
|
func DirParents(p string) []string {
|
||||||
|
if p == "" {
|
||||||
|
return nil // TODO: should we error?
|
||||||
|
}
|
||||||
|
if p == "/" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
d := Dirname(p)
|
||||||
|
x := DirParents(d)
|
||||||
|
x = append(x, d)
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// MissingMkdirs takes a list of paths, and returns a list of any missing paths
|
||||||
|
// that would be needed to avoid having to `mkdir -p` to prevent missing parent
|
||||||
|
// directory errors from happening. This adds paths all the way up to the root,
|
||||||
|
// but without including it, because it's implied.
|
||||||
|
// TODO: do we want to include the root?
|
||||||
|
// TODO: this could probably be implemented in a more efficient way...
|
||||||
|
func MissingMkdirs(input []string) ([]string, error) {
|
||||||
|
dirs := []string{}
|
||||||
|
for _, p := range input {
|
||||||
|
if p == "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d := Dirname(p)
|
||||||
|
dirs = append(dirs, d)
|
||||||
|
if strings.HasSuffix(p, "/") { // it's a dir
|
||||||
|
dirs = append(dirs, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: remove duplicates for efficiency?
|
||||||
|
|
||||||
|
result := []string{}
|
||||||
|
for _, d := range dirs {
|
||||||
|
p := DirParents(d) // TODO: memoize
|
||||||
|
p = append(p, d) // include self
|
||||||
|
for _, x := range p {
|
||||||
|
if StrInList(x, input) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out := StrRemoveDuplicatesInList(result) // avoid duplicates
|
||||||
|
sort.Sort(PathSlice(out))
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TimeAfterOrBlock is aspecial version of time.After that blocks when given a
|
// TimeAfterOrBlock is aspecial version of time.After that blocks when given a
|
||||||
// negative integer. When used in a case statement, the timer restarts on each
|
// negative integer. When used in a case statement, the timer restarts on each
|
||||||
// select call to it.
|
// select call to it.
|
||||||
|
|||||||
@@ -1055,6 +1055,233 @@ func TestRemovePathSuffix0(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDirParents0(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
out []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: "",
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "/",
|
||||||
|
out: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "/tmp/x1/mod1/files/",
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
"/tmp/x1/mod1/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "/tmp/x1/mod1/files/foo",
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
"/tmp/x1/mod1/",
|
||||||
|
"/tmp/x1/mod1/files/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for index, tt := range tests {
|
||||||
|
result := DirParents(tt.in)
|
||||||
|
if a, b := len(result), len(tt.out); a != b {
|
||||||
|
t.Errorf("test #%d: expected length differs (%d != %d)", index, a, b)
|
||||||
|
t.Errorf("test #%d: actual: %+v", index, result)
|
||||||
|
t.Errorf("test #%d: expected: %+v", index, tt.out)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := range result {
|
||||||
|
if result[i] != tt.out[i] {
|
||||||
|
t.Errorf("test #%d: parents diff: wanted: %s got: %s", index, tt.out[i], result[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingMkdirs0(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in []string
|
||||||
|
out []string
|
||||||
|
fail bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: []string{},
|
||||||
|
out: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/",
|
||||||
|
},
|
||||||
|
out: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/tmp/x1/metadata.yaml",
|
||||||
|
"/tmp/x1/main.mcl",
|
||||||
|
"/tmp/x1/files/",
|
||||||
|
"/tmp/x1/second.mcl",
|
||||||
|
"/tmp/x1/mod1/metadata.yaml",
|
||||||
|
"/tmp/x1/mod1/main.mcl",
|
||||||
|
"/tmp/x1/mod1/files/",
|
||||||
|
},
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
"/tmp/x1/mod1/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/tmp/x1/files/",
|
||||||
|
"/tmp/x1/main.mcl",
|
||||||
|
"/tmp/x1/metadata.yaml",
|
||||||
|
"/tmp/x1/mod1/files/",
|
||||||
|
"/tmp/x1/mod1/main.mcl",
|
||||||
|
"/tmp/x1/mod1/metadata.yaml",
|
||||||
|
"/tmp/x1/second.mcl",
|
||||||
|
},
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
"/tmp/x1/mod1/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/tmp/x1/files/",
|
||||||
|
"/tmp/x1/files/a/b/c/",
|
||||||
|
},
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
//"/tmp/x1/files/", // already exists!
|
||||||
|
"/tmp/x1/files/a/",
|
||||||
|
"/tmp/x1/files/a/b/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/tmp/x1/files/",
|
||||||
|
"/tmp/x1/files/a/b/c/",
|
||||||
|
"/tmp/x1/files/a/b/c/",
|
||||||
|
"/tmp/x1/files/a/b/c/",
|
||||||
|
"/tmp/x1/files/a/b/c/", // duplicates
|
||||||
|
},
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
//"/tmp/x1/files/", // already exists!
|
||||||
|
"/tmp/x1/files/a/",
|
||||||
|
"/tmp/x1/files/a/b/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/tmp/x1/files/",
|
||||||
|
"/tmp/x1/files/a/b/c/d1",
|
||||||
|
"/tmp/x1/files/a/b/c/d2",
|
||||||
|
},
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
//"/tmp/x1/files/", // already exists!
|
||||||
|
"/tmp/x1/files/a/",
|
||||||
|
"/tmp/x1/files/a/b/",
|
||||||
|
"/tmp/x1/files/a/b/c/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/tmp/x1/files/",
|
||||||
|
"/tmp/x1/files/a/b/c/d1",
|
||||||
|
"/tmp/x1/files/a/b/c/d1",
|
||||||
|
"/tmp/x1/files/a/b/c/d1",
|
||||||
|
"/tmp/x1/files/a/b/c/d1", // duplicates!
|
||||||
|
"/tmp/x1/files/a/b/c/d2",
|
||||||
|
},
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
//"/tmp/x1/files/", // already exists!
|
||||||
|
"/tmp/x1/files/a/",
|
||||||
|
"/tmp/x1/files/a/b/",
|
||||||
|
"/tmp/x1/files/a/b/c/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: []string{
|
||||||
|
"/tmp/x1/files/",
|
||||||
|
"/tmp/x1/files/a/b/c/d1",
|
||||||
|
"/tmp/x1/files/a/b/c/d1",
|
||||||
|
"/tmp/x1/files/a/b/c/d1",
|
||||||
|
"/tmp/x1/files/a/b/c/d1", // duplicates!
|
||||||
|
"/tmp/x1/files/a/b/",
|
||||||
|
"/tmp/x1/files/a/b/c/d2",
|
||||||
|
},
|
||||||
|
out: []string{
|
||||||
|
"/",
|
||||||
|
"/tmp/",
|
||||||
|
"/tmp/x1/",
|
||||||
|
//"/tmp/x1/files/", // already exists!
|
||||||
|
"/tmp/x1/files/a/",
|
||||||
|
"/tmp/x1/files/a/b/c/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// invalid path list, so undefined results
|
||||||
|
//{
|
||||||
|
// in: []string{
|
||||||
|
// "/tmp/x1/files/",
|
||||||
|
// "/tmp/x1/files/a/b/c/d",
|
||||||
|
// "/tmp/x1/files/a/b/c/d/", // error: same name as file
|
||||||
|
// "/tmp/x1/files/a/b/c/d1",
|
||||||
|
// },
|
||||||
|
// out: []string{},
|
||||||
|
// fail: true, // TODO: put a specific error?
|
||||||
|
//},
|
||||||
|
// TODO: add more tests
|
||||||
|
}
|
||||||
|
for index, tt := range tests {
|
||||||
|
result, err := MissingMkdirs(tt.in)
|
||||||
|
|
||||||
|
if !tt.fail && err != nil {
|
||||||
|
t.Errorf("test #%d: failed with: %+v", index, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tt.fail && err == nil {
|
||||||
|
t.Errorf("test #%d: passed, expected fail", index)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !tt.fail && result == nil {
|
||||||
|
t.Errorf("test #%d: output was nil", index)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, b := len(result), len(tt.out); a != b {
|
||||||
|
t.Errorf("test #%d: expected length differs (%d != %d)", index, a, b)
|
||||||
|
t.Errorf("test #%d: actual: %+v", index, result)
|
||||||
|
t.Errorf("test #%d: expected: %+v", index, tt.out)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := range result {
|
||||||
|
if result[i] != tt.out[i] {
|
||||||
|
t.Errorf("test #%d: missing mkdirs diff: wanted: %s got: %s", index, tt.out[i], result[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPriorityStrSliceSort0(t *testing.T) {
|
func TestPriorityStrSliceSort0(t *testing.T) {
|
||||||
in := []string{"foo", "bar", "baz"}
|
in := []string{"foo", "bar", "baz"}
|
||||||
ex := []string{"bar", "baz", "foo"}
|
ex := []string{"bar", "baz", "foo"}
|
||||||
|
|||||||
Reference in New Issue
Block a user