go-hep.org/x/hep@v0.38.1/groot/riofs/walk.go (about) 1 // Copyright ©2018 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package riofs 6 7 import ( 8 "errors" 9 "fmt" 10 stdpath "path" 11 "strings" 12 13 "go-hep.org/x/hep/groot/root" 14 ) 15 16 // SkipDir is used as a return value from WalkFuncs to indicate that 17 // the directory named in the call is to be skipped. It is not returned 18 // as an error by any function. 19 var SkipDir = errors.New("riofs: skip this directory") //lint:ignore ST1012 EOF-like sentry 20 21 // Walk walks the ROOT file tree rooted at dir, calling walkFn for each ROOT object 22 // or Directory in the ROOT file tree, including dir. 23 // 24 // If an object exists with multiple cycle values, only the latest one is considered. 25 func Walk(dir Directory, walkFn WalkFunc) error { 26 // prepare a "stable" top directory. 27 // depending on whether the dir is rooted in a file that was created 28 // with an absolute path, the call to Name() may return a path like: 29 // ./data/file.root 30 // the first call to walkFn will be given "./data/file.root" as a 'path' 31 // argument. 32 // but the subsequent calls (walking through directories' hierarchy) will 33 // be given "data/file.root/dir11", instead of the probably expected 34 // "./data/file.root/dir11". 35 // 36 // side-step this by providing directly the "stable" top directory in a 37 // more regularized form. 38 top := stdpath.Join(dir.(root.Named).Name(), ".") 39 err := walk(top, dir.(root.Object), walkFn) 40 if err == SkipDir { 41 return nil 42 } 43 return err 44 } 45 46 // walk recursively descends path, calling walkFn. 47 func walk(path string, obj root.Object, walkFn WalkFunc) error { 48 dir, ok := obj.(Directory) 49 if !ok { 50 return walkFn(path, obj, nil) 51 } 52 53 err := walkFn(path, obj, nil) 54 if err != nil { 55 return err 56 } 57 58 keys := dir.Keys() 59 set := make(map[string]int, len(keys)) 60 for _, key := range keys { 61 if cycle, dup := set[key.Name()]; dup && key.Cycle() < cycle { 62 continue 63 } 64 set[key.Name()] = key.Cycle() 65 } 66 67 for _, key := range keys { 68 if key.Cycle() != set[key.Name()] { 69 continue 70 } 71 dirname := stdpath.Join(path, key.Name()) 72 obj, err := dir.Get(key.Name()) 73 switch err { 74 case nil: 75 err = walk(dirname, obj, walkFn) 76 if err != nil && err != SkipDir { 77 return err 78 } 79 default: 80 err := walkFn(dirname, obj, err) 81 if err != nil && err != SkipDir { 82 return err 83 } 84 } 85 86 } 87 88 return nil 89 } 90 91 // WalkFunc is the type of the function called for each object or directory 92 // visited by Walk. The path argument contains the argument to Walk as a 93 // prefix; that is, if Walk is called with "dir", which is a directory 94 // containing the file "a", the walk function will be called with argument 95 // "dir/a". The obj argument is the root.Object for the named path. 96 // 97 // If there was a problem walking to the file or directory named by path, the 98 // incoming error will describe the problem and the function can decide how 99 // to handle that error (and Walk will not descend into that directory). In the 100 // case of an error, the obj argument will be nil. If an error is returned, 101 // processing stops. The sole exception is when the function returns the special 102 // value SkipDir. If the function returns SkipDir when invoked on a directory, 103 // Walk skips the directory's contents entirely. If the function returns SkipDir 104 // when invoked on a non-directory root.Object, Walk skips the remaining keys in the 105 // containing directory. 106 type WalkFunc func(path string, obj root.Object, err error) error 107 108 // recDir handles nested paths. 109 type recDir struct { 110 dir Directory 111 } 112 113 func (dir *recDir) Get(namecycle string) (root.Object, error) { return dir.get(namecycle) } 114 func (dir *recDir) Put(name string, v root.Object) error { return dir.put(name, v) } 115 func (dir *recDir) Keys() []Key { return dir.dir.Keys() } 116 func (dir *recDir) Mkdir(name string) (Directory, error) { return dir.mkdir(name) } 117 func (dir *recDir) Parent() Directory { return dir.dir.Parent() } 118 119 func (dir *recDir) get(namecycle string) (root.Object, error) { 120 switch namecycle { 121 case "", "/": 122 return dir.dir.(root.Object), nil 123 } 124 name, cycle := decodeNameCycle(namecycle) 125 name = strings.TrimPrefix(name, "/") 126 path := strings.Split(name, "/") 127 return dir.walkdir(dir.dir, path, cycle) 128 } 129 130 func (dir *recDir) put(name string, v root.Object) error { 131 pdir, n := stdpath.Split(name) 132 pdir = strings.TrimRight(pdir, "/") 133 switch pdir { 134 case "": 135 return dir.dir.Put(name, v) 136 default: 137 p, err := dir.mkdir(pdir) 138 if err != nil { 139 return fmt.Errorf("riofs: could not create parent directory %q for %q: %w", pdir, name, err) 140 } 141 return p.Put(n, v) 142 } 143 } 144 145 func (dir *recDir) mkdir(path string) (Directory, error) { 146 if path == "" || path == "/" { 147 return nil, fmt.Errorf("riofs: invalid path %q to Mkdir", path) 148 } 149 150 if o, err := dir.get(path); err == nil { 151 d, ok := o.(Directory) 152 if ok { 153 return d, nil 154 } 155 return nil, keyTypeError{key: path, class: d.(root.Object).Class()} 156 } 157 158 ps := strings.Split(path, "/") 159 if len(ps) == 1 { 160 return dir.dir.Mkdir(path) 161 } 162 for i := range ps { 163 p := strings.Join(ps[:i+1], "/") 164 _, err := dir.get(p) 165 if err == nil { 166 continue 167 } 168 switch { 169 case errors.As(err, &noKeyError{}): 170 pname, name := stdpath.Split(p) 171 pname = strings.TrimRight(pname, "/") 172 d, err := dir.get(pname) 173 if err != nil { 174 return nil, err 175 } 176 pdir := d.(Directory) 177 _, err = pdir.Mkdir(name) 178 if err != nil { 179 return nil, err 180 } 181 _, err = pdir.Get(name) 182 if err != nil { 183 return nil, err 184 } 185 continue 186 187 default: 188 return nil, fmt.Errorf("riofs: unknown error accessing %q: %w", p, err) 189 } 190 } 191 o, err := dir.get(path) 192 if err != nil { 193 return nil, err 194 } 195 d, ok := o.(Directory) 196 if !ok { 197 return nil, fmt.Errorf("riofs: could not create directory %q", path) 198 } 199 return d, nil 200 } 201 202 func (rd *recDir) walkdir(dir Directory, path []string, cycle int16) (root.Object, error) { 203 if len(path) == 1 { 204 name := fmt.Sprintf("%s;%d", path[0], cycle) 205 return dir.Get(name) 206 } 207 208 o, err := dir.Get(path[0]) 209 if err != nil { 210 return nil, err 211 } 212 switch sub := o.(type) { 213 case Directory: 214 return rd.walkdir(sub, path[1:], cycle) 215 case root.ObjectFinder: 216 return rd.walkobj(sub, path[1:]) 217 default: 218 return nil, fmt.Errorf("riofs: not a directory %q", strings.Join([]string{dir.(root.Named).Name(), path[0]}, "/")) 219 } 220 } 221 222 func (rd *recDir) walkobj(dir root.ObjectFinder, path []string) (root.Object, error) { 223 if len(path) == 1 { 224 name := path[0] 225 return dir.Get(name) 226 } 227 228 o, err := dir.Get(path[0]) 229 if err != nil { 230 return nil, err 231 } 232 switch sub := o.(type) { 233 case root.ObjectFinder: 234 return rd.walkobj(sub, path[1:]) 235 default: 236 return nil, fmt.Errorf("riofs: not an object-finder %q", strings.Join([]string{dir.(root.Named).Name(), path[0]}, "/")) 237 } 238 } 239 240 // Dir wraps the given directory to handle fully specified directory names: 241 // 242 // rdir := Dir(dir) 243 // obj, err := rdir.Get("some/dir/object/name;1") 244 func Dir(dir Directory) Directory { 245 return &recDir{dir} 246 } 247 248 func fileOf(d Directory) *File { 249 const max = 1<<31 - 1 250 for range max { 251 p := d.Parent() 252 if p == nil { 253 switch d := d.(type) { 254 case *File: 255 return d 256 case *recDir: 257 return fileOf(d.dir) 258 default: 259 panic(fmt.Errorf("riofs: unknown Directory type %T", d)) 260 } 261 } 262 d = p 263 } 264 panic("impossible") 265 } 266 267 // Get retrieves the named key from the provided directory. 268 func Get[T any](dir Directory, key string) (T, error) { 269 obj, err := Dir(dir).Get(key) 270 if err != nil { 271 var v T 272 return v, err 273 } 274 275 v, ok := obj.(T) 276 if !ok { 277 return v, fmt.Errorf("riofs: could not convert %q (%T) to %T", key, obj, *new(T)) 278 } 279 280 return v, nil 281 } 282 283 var ( 284 _ Directory = (*recDir)(nil) 285 )