diff options
Diffstat (limited to 'hugofs/walk.go')
-rw-r--r-- | hugofs/walk.go | 260 |
1 files changed, 71 insertions, 189 deletions
diff --git a/hugofs/walk.go b/hugofs/walk.go index e883f892e..18667a5fc 100644 --- a/hugofs/walk.go +++ b/hugofs/walk.go @@ -15,73 +15,60 @@ package hugofs import ( "fmt" - "os" + "io/fs" "path/filepath" "sort" "strings" "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/loggers" - - "errors" + "github.com/gohugoio/hugo/common/paths" "github.com/spf13/afero" ) type ( - WalkFunc func(path string, info FileMetaInfo, err error) error + WalkFunc func(path string, info FileMetaInfo) error WalkHook func(dir FileMetaInfo, path string, readdir []FileMetaInfo) ([]FileMetaInfo, error) ) type Walkway struct { - fs afero.Fs - root string - basePath string - logger loggers.Logger - // May be pre-set - fi FileMetaInfo - dirEntries []FileMetaInfo - - walkFn WalkFunc + // Prevent a walkway to be walked more than once. walked bool - // We may traverse symbolic links and bite ourself. - seen map[string]bool - - // Optional hooks - hookPre WalkHook - hookPost WalkHook + // Config from client. + cfg WalkwayConfig } type WalkwayConfig struct { - Fs afero.Fs - Root string - BasePath string + // The filesystem to walk. + Fs afero.Fs + // The root to start from in Fs. + Root string + + // The logger to use. Logger loggers.Logger // One or both of these may be pre-set. - Info FileMetaInfo - DirEntries []FileMetaInfo + Info FileMetaInfo // The start info. + DirEntries []FileMetaInfo // The start info's dir entries. + // Will be called in order. + HookPre WalkHook // Optional. WalkFn WalkFunc - HookPre WalkHook - HookPost WalkHook + HookPost WalkHook // Optional. + + // Some optional flags. + FailOnNotExist bool // If set, return an error if a directory is not found. + SortDirEntries bool // If set, sort the dir entries by Name before calling the WalkFn, default is ReaDir order. } func NewWalkway(cfg WalkwayConfig) *Walkway { - var fs afero.Fs - if cfg.Info != nil { - fs = cfg.Info.Meta().Fs - } else { - fs = cfg.Fs - } - - basePath := cfg.BasePath - if basePath != "" && !strings.HasSuffix(basePath, filepathSeparator) { - basePath += filepathSeparator + if cfg.Fs == nil { + panic("fs must be set") } logger := cfg.Logger @@ -90,16 +77,8 @@ func NewWalkway(cfg WalkwayConfig) *Walkway { } return &Walkway{ - fs: fs, - root: cfg.Root, - basePath: basePath, - fi: cfg.Info, - dirEntries: cfg.DirEntries, - walkFn: cfg.WalkFn, - hookPre: cfg.HookPre, - hookPost: cfg.HookPost, - logger: logger, - seen: make(map[string]bool), + cfg: cfg, + logger: logger, } } @@ -109,53 +88,16 @@ func (w *Walkway) Walk() error { } w.walked = true - if w.fs == NoOpFs { + if w.cfg.Fs == NoOpFs { return nil } - var fi FileMetaInfo - if w.fi != nil { - fi = w.fi - } else { - info, _, err := lstatIfPossible(w.fs, w.root) - if err != nil { - if herrors.IsNotExist(err) { - return nil - } - - if w.checkErr(w.root, err) { - return nil - } - return w.walkFn(w.root, nil, fmt.Errorf("walk: %q: %w", w.root, err)) - } - fi = info.(FileMetaInfo) - } - - if !fi.IsDir() { - return w.walkFn(w.root, nil, errors.New("file to walk must be a directory")) - } - - return w.walk(w.root, fi, w.dirEntries, w.walkFn) -} - -// if the filesystem supports it, use Lstat, else use fs.Stat -func lstatIfPossible(fs afero.Fs, path string) (os.FileInfo, bool, error) { - if lfs, ok := fs.(afero.Lstater); ok { - fi, b, err := lfs.LstatIfPossible(path) - return fi, b, err - } - fi, err := fs.Stat(path) - return fi, false, err + return w.walk(w.cfg.Root, w.cfg.Info, w.cfg.DirEntries) } // checkErr returns true if the error is handled. func (w *Walkway) checkErr(filename string, err error) bool { - if err == ErrPermissionSymlink { - logUnsupportedSymlink(filename, w.logger) - return true - } - - if herrors.IsNotExist(err) { + if herrors.IsNotExist(err) && !w.cfg.FailOnNotExist { // The file may be removed in process. // This may be a ERROR situation, but it is not possible // to determine as a general case. @@ -166,115 +108,73 @@ func (w *Walkway) checkErr(filename string, err error) bool { return false } -func logUnsupportedSymlink(filename string, logger loggers.Logger) { - logger.Warnf("Unsupported symlink found in %q, skipping.", filename) -} - // walk recursively descends path, calling walkFn. -// It follow symlinks if supported by the filesystem, but only the same path once. -func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo, walkFn WalkFunc) error { - err := walkFn(path, info, nil) +func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo) error { + pathRel := strings.TrimPrefix(path, w.cfg.Root) + + if info == nil { + var err error + fi, err := w.cfg.Fs.Stat(path) + if err != nil { + if path == w.cfg.Root && herrors.IsNotExist(err) { + return nil + } + if w.checkErr(path, err) { + return nil + } + return fmt.Errorf("walk: stat: %s", err) + } + info = fi.(FileMetaInfo) + } + + err := w.cfg.WalkFn(path, info) if err != nil { if info.IsDir() && err == filepath.SkipDir { return nil } return err } + if !info.IsDir() { return nil } - meta := info.Meta() - filename := meta.Filename - if dirEntries == nil { - f, err := w.fs.Open(path) + f, err := w.cfg.Fs.Open(path) if err != nil { if w.checkErr(path, err) { return nil } - return walkFn(path, info, fmt.Errorf("walk: open %q (%q): %w", path, w.root, err)) + return fmt.Errorf("walk: open: path: %q filename: %q: %s", path, info.Meta().Filename, err) } + fis, err := f.(fs.ReadDirFile).ReadDir(-1) - fis, err := f.Readdir(-1) f.Close() if err != nil { - if w.checkErr(filename, err) { + if w.checkErr(path, err) { return nil } - return walkFn(path, info, fmt.Errorf("walk: Readdir: %w", err)) + return fmt.Errorf("walk: Readdir: %w", err) } - dirEntries = fileInfosToFileMetaInfos(fis) + dirEntries = DirEntriesToFileMetaInfos(fis) + for _, fi := range dirEntries { + if fi.Meta().PathInfo == nil { + fi.Meta().PathInfo = paths.Parse("", filepath.Join(pathRel, fi.Name())) + } + } - if !meta.IsOrdered { + if w.cfg.SortDirEntries { sort.Slice(dirEntries, func(i, j int) bool { - fii := dirEntries[i] - fij := dirEntries[j] - - fim, fjm := fii.Meta(), fij.Meta() - - // Pull bundle headers to the top. - ficlass, fjclass := fim.Classifier, fjm.Classifier - if ficlass != fjclass { - return ficlass < fjclass - } - - // With multiple content dirs with different languages, - // there can be duplicate files, and a weight will be added - // to the closest one. - fiw, fjw := fim.Weight, fjm.Weight - if fiw != fjw { - - return fiw > fjw - } - - // When we walk into a symlink, we keep the reference to - // the original name. - fin, fjn := fim.Name, fjm.Name - if fin != "" && fjn != "" { - return fin < fjn - } - - return fii.Name() < fij.Name() + return dirEntries[i].Name() < dirEntries[j].Name() }) } - } - - // First add some metadata to the dir entries - for _, fi := range dirEntries { - fim := fi.(FileMetaInfo) - - meta := fim.Meta() - - // Note that we use the original Name even if it's a symlink. - name := meta.Name - if name == "" { - name = fim.Name() - } - - if name == "" { - panic(fmt.Sprintf("[%s] no name set in %v", path, meta)) - } - pathn := filepath.Join(path, name) - - pathMeta := pathn - if w.basePath != "" { - pathMeta = strings.TrimPrefix(pathn, w.basePath) - } - - meta.Path = normalizeFilename(pathMeta) - meta.PathWalk = pathn - if fim.IsDir() && meta.IsSymlink && w.isSeen(meta.Filename) { - // Prevent infinite recursion - // Possible cyclic reference - meta.SkipDir = true - } } - if w.hookPre != nil { - dirEntries, err = w.hookPre(info, path, dirEntries) + if w.cfg.HookPre != nil { + var err error + dirEntries, err = w.cfg.HookPre(info, path, dirEntries) if err != nil { if err == filepath.SkipDir { return nil @@ -283,24 +183,19 @@ func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo } } - for _, fi := range dirEntries { - fim := fi.(FileMetaInfo) - meta := fim.Meta() - - if meta.SkipDir { - continue - } - - err := w.walk(meta.PathWalk, fim, nil, walkFn) + for _, fim := range dirEntries { + nextPath := filepath.Join(path, fim.Name()) + err := w.walk(nextPath, fim, nil) if err != nil { - if !fi.IsDir() || err != filepath.SkipDir { + if !fim.IsDir() || err != filepath.SkipDir { return err } } } - if w.hookPost != nil { - dirEntries, err = w.hookPost(info, path, dirEntries) + if w.cfg.HookPost != nil { + var err error + dirEntries, err = w.cfg.HookPost(info, path, dirEntries) if err != nil { if err == filepath.SkipDir { return nil @@ -310,16 +205,3 @@ func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo } return nil } - -func (w *Walkway) isSeen(filename string) bool { - if filename == "" { - return false - } - - if w.seen[filename] { - return true - } - - w.seen[filename] = true - return false -} |