github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/internal/walker/walker.go (about)

     1  // Copyright 2017 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package walker
     6  
     7  import (
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  )
    12  
    13  // Walker walks a recursive directory hierarchy, exposing a scanner-like interface.
    14  type Walker struct {
    15  	root string
    16  	err  error
    17  	path string
    18  	info os.FileInfo
    19  	todo []string
    20  }
    21  
    22  // Init initializes a walker to walk from a root path.
    23  func (w *Walker) Init(root string) {
    24  	w.root = root
    25  	w.todo = append(w.todo, w.root)
    26  }
    27  
    28  // Scan advances the walker to the next entry in the hierarchy.
    29  // It returns false either when the scan stops because we have
    30  // reached the end of the input or else because there was error.
    31  // After Scan returns, the Err method returns any error that occurred
    32  // during scanning.
    33  func (w *Walker) Scan() bool {
    34  again:
    35  	if len(w.todo) == 0 || w.err != nil {
    36  		return false
    37  	}
    38  	w.path, w.todo = w.todo[0], w.todo[1:]
    39  	w.info, w.err = os.Stat(w.path)
    40  	if os.IsNotExist(w.err) {
    41  		w.err = nil
    42  		goto again
    43  	} else if w.err != nil {
    44  		return false
    45  	}
    46  	if w.info.IsDir() {
    47  		var paths []string
    48  		paths, w.err = readDirNames(w.path)
    49  		if w.err != nil {
    50  			return false
    51  		}
    52  		for i := range paths {
    53  			paths[i] = filepath.Join(w.path, paths[i])
    54  		}
    55  		w.todo = append(paths, w.todo...)
    56  	}
    57  	return true
    58  }
    59  
    60  // Path returns the most recent path that was scanned.
    61  func (w *Walker) Path() string {
    62  	return w.path
    63  }
    64  
    65  // Relpath returns the most recent path that was scanned, relative to
    66  // the scan root directory.
    67  func (w *Walker) Relpath() string {
    68  	path, err := filepath.Rel(w.root, w.Path())
    69  	if err != nil {
    70  		panic("bad path")
    71  	}
    72  	return path
    73  }
    74  
    75  // Info returns the os.FileInfo for the most recent path scanned.
    76  func (w *Walker) Info() os.FileInfo {
    77  	return w.info
    78  }
    79  
    80  // Err returns the first error that occurred while scanning.
    81  func (w *Walker) Err() error {
    82  	return w.err
    83  }
    84  
    85  // readDirNames reads the directory named by dirname and returns
    86  // a sorted list of directory entries.
    87  func readDirNames(dirname string) ([]string, error) {
    88  	f, err := os.Open(dirname)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	names, err := f.Readdirnames(-1)
    93  	f.Close()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	sort.Strings(names)
    98  	return names, nil
    99  }