diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-03-21 09:35:15 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-04-08 13:26:17 +0200 |
commit | d070bdf10f14d233288f7318a4e9f7555f070a65 (patch) | |
tree | fff8d59f98bdab3027bb45c4e10ca88594332872 /hugofs/fs.go | |
parent | b08193971a821fc27e549a73120c15e5e5186775 (diff) |
Rework the Destination filesystem to make --renderStaticToDisk work
See #9626
Diffstat (limited to 'hugofs/fs.go')
-rw-r--r-- | hugofs/fs.go | 142 |
1 files changed, 121 insertions, 21 deletions
diff --git a/hugofs/fs.go b/hugofs/fs.go index 95645204e..436387f13 100644 --- a/hugofs/fs.go +++ b/hugofs/fs.go @@ -19,6 +19,8 @@ import ( "os" "strings" + "github.com/bep/overlayfs" + "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/config" "github.com/spf13/afero" ) @@ -26,32 +28,43 @@ import ( // Os points to the (real) Os filesystem. var Os = &afero.OsFs{} -// Fs abstracts the file system to separate source and destination file systems -// and allows both to be mocked for testing. +// Fs holds the core filesystems used by Hugo. type Fs struct { // Source is Hugo's source file system. + // Note that this will always be a "plain" Afero filesystem: + // * afero.OsFs when running in production + // * afero.MemMapFs for many of the tests. Source afero.Fs - // Destination is Hugo's destination file system. - Destination afero.Fs + // PublishDir is where Hugo publishes its rendered content. + // It's mounted inside publishDir (default /public). + PublishDir afero.Fs - // Destination used for `renderStaticToDisk` - DestinationStatic afero.Fs + // PublishDirStatic is the file system used for static files when --renderStaticToDisk is set. + // When this is set, PublishDir is set to write to memory. + PublishDirStatic afero.Fs + + // PublishDirServer is the file system used for serving the public directory with Hugo's development server. + // This will typically be the same as PublishDir, but not if --renderStaticToDisk is set. + PublishDirServer afero.Fs // Os is an OS file system. // NOTE: Field is currently unused. Os afero.Fs - // WorkingDir is a read-only file system + // WorkingDirReadOnly is a read-only file system + // restricted to the project working dir. + WorkingDirReadOnly afero.Fs + + // WorkingDirWritable is a writable file system // restricted to the project working dir. - // TODO(bep) get rid of this (se BaseFs) - WorkingDir *afero.BasePathFs + WorkingDirWritable afero.Fs } // NewDefault creates a new Fs with the OS file system // as source and destination file systems. func NewDefault(cfg config.Provider) *Fs { - fs := &afero.OsFs{} + fs := Os return newFs(fs, cfg) } @@ -71,23 +84,49 @@ func NewFrom(fs afero.Fs, cfg config.Provider) *Fs { } func newFs(base afero.Fs, cfg config.Provider) *Fs { + workingDir := cfg.GetString("workingDir") + publishDir := cfg.GetString("publishDir") + if publishDir == "" { + panic("publishDir is empty") + } + + // Sanity check + if IsOsFs(base) && len(workingDir) < 2 { + panic("workingDir is too short") + } + + absPublishDir := paths.AbsPathify(workingDir, publishDir) + + // Make sure we always have the /public folder ready to use. + if err := base.MkdirAll(absPublishDir, 0777); err != nil && !os.IsExist(err) { + panic(err) + } + + pubFs := afero.NewBasePathFs(base, absPublishDir) + return &Fs{ - Source: base, - Destination: base, - DestinationStatic: base, - Os: &afero.OsFs{}, - WorkingDir: getWorkingDirFs(base, cfg), + Source: base, + PublishDir: pubFs, + PublishDirServer: pubFs, + PublishDirStatic: pubFs, + Os: &afero.OsFs{}, + WorkingDirReadOnly: getWorkingDirFsReadOnly(base, workingDir), + WorkingDirWritable: getWorkingDirFsWritable(base, workingDir), } } -func getWorkingDirFs(base afero.Fs, cfg config.Provider) *afero.BasePathFs { - workingDir := cfg.GetString("workingDir") - - if workingDir != "" { - return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir).(*afero.BasePathFs) +func getWorkingDirFsReadOnly(base afero.Fs, workingDir string) afero.Fs { + if workingDir == "" { + return afero.NewReadOnlyFs(base) } + return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir) +} - return nil +func getWorkingDirFsWritable(base afero.Fs, workingDir string) afero.Fs { + if workingDir == "" { + return base + } + return afero.NewBasePathFs(base, workingDir) } func isWrite(flag int) bool { @@ -117,3 +156,64 @@ func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error) }) return counter, fs.RemoveAll(dir) } + +// HasOsFs returns whether fs is an OsFs or if it fs wraps an OsFs. +// TODO(bep) make this nore robust. +func IsOsFs(fs afero.Fs) bool { + var isOsFs bool + WalkFilesystems(fs, func(fs afero.Fs) bool { + switch base := fs.(type) { + case *afero.MemMapFs: + isOsFs = false + case *afero.OsFs: + isOsFs = true + case *afero.BasePathFs: + _, supportsLstat, _ := base.LstatIfPossible("asdfasdfasdf") + isOsFs = supportsLstat + } + return isOsFs + }) + return isOsFs +} + +// FilesystemsUnwrapper returns the underlying filesystems. +type FilesystemsUnwrapper interface { + UnwrapFilesystems() []afero.Fs +} + +// FilesystemsProvider returns the underlying filesystem. +type FilesystemUnwrapper interface { + UnwrapFilesystem() afero.Fs +} + +// WalkFn is the walk func for WalkFilesystems. +type WalkFn func(fs afero.Fs) bool + +// WalkFilesystems walks fs recursively and calls fn. +// If fn returns true, walking is stopped. +func WalkFilesystems(fs afero.Fs, fn WalkFn) bool { + if fn(fs) { + return true + } + + if afs, ok := fs.(FilesystemUnwrapper); ok { + if WalkFilesystems(afs.UnwrapFilesystem(), fn) { + return true + } + + } else if bfs, ok := fs.(FilesystemsUnwrapper); ok { + for _, sf := range bfs.UnwrapFilesystems() { + if WalkFilesystems(sf, fn) { + return true + } + } + } else if cfs, ok := fs.(overlayfs.FilesystemIterator); ok { + for i := 0; i < cfs.NumFilesystems(); i++ { + if WalkFilesystems(cfs.Filesystem(i), fn) { + return true + } + } + } + + return false +} |