diff --git a/fs/walk.go b/fs/walk.go index bd3320dca..3b9610a50 100644 --- a/fs/walk.go +++ b/fs/walk.go @@ -250,6 +250,87 @@ func (dt DirTree) Dirs() (dirNames []string) { return dirNames } +// Prune remove directories from a directory tree. dirNames contains +// all directories to remove as keys, with true as values. dirNames +// will be modified in the function. +func (dt DirTree) Prune(dirNames map[string]bool) error { + // We use map[string]bool to avoid recursion (and potential + // stack exhaustion). + + // First we need delete directories from their parents. + for dName, remove := range dirNames { + if !remove { + // Currently all values should be + // true, therefore this should not + // happen. But this makes function + // more predictable. + Infof(dName, "Directory in the map for prune, but the value is false") + continue + } + if dName == "" { + // if dName is root, do nothing (no parent exist) + continue + } + parent := parentDir(dName) + // It may happen that dt does not have a dName key, + // since directory was excluded based on a filter. In + // such case the loop will be skipped. + for i, entry := range dt[parent] { + switch x := entry.(type) { + case Directory: + if x.Remote() == dName { + // the slice is not sorted yet + // to delete item + // a) replace it with the last one + dt[parent][i] = dt[parent][len(dt[parent])-1] + // b) remove last + dt[parent] = dt[parent][:len(dt[parent])-1] + // we modify a slice within a loop, but we stop + // iterating immediately + break + } + case Object: + // do nothing + default: + return errors.Errorf("unknown object type %T", entry) + + } + } + } + + for len(dirNames) > 0 { + // According to golang specs, if new keys were added + // during range iteration, they may be skipped. + for dName, remove := range dirNames { + if !remove { + Infof(dName, "Directory in the map for prune, but the value is false") + continue + } + // First, add all subdirectories to dirNames. + + // It may happen that dt[dName] does not exist. + // If so, the loop will be skipped. + for _, entry := range dt[dName] { + switch x := entry.(type) { + case Directory: + excludeDir := x.Remote() + dirNames[excludeDir] = true + case Object: + // do nothing + default: + return errors.Errorf("unknown object type %T", entry) + + } + } + // Then remove current directory from DirTree + delete(dt, dName) + // and from dirNames + delete(dirNames, dName) + } + } + return nil +} + // String emits a simple representation of the DirTree func (dt DirTree) String() string { out := new(bytes.Buffer)