summaryrefslogtreecommitdiffstats
path: root/hugofs/walk.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugofs/walk.go')
-rw-r--r--hugofs/walk.go260
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
-}