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  )