URI: 
       fs.go - hugo - [fork] hugo port for 9front
  HTML git clone https://git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
       fs.go (7093B)
       ---
            1 // Copyright 2019 The Hugo Authors. All rights reserved.
            2 //
            3 // Licensed under the Apache License, Version 2.0 (the "License");
            4 // you may not use this file except in compliance with the License.
            5 // You may obtain a copy of the License at
            6 // http://www.apache.org/licenses/LICENSE-2.0
            7 //
            8 // Unless required by applicable law or agreed to in writing, software
            9 // distributed under the License is distributed on an "AS IS" BASIS,
           10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           11 // See the License for the specific language governing permissions and
           12 // limitations under the License.
           13 
           14 // Package hugofs provides the file systems used by Hugo.
           15 package hugofs
           16 
           17 import (
           18         "fmt"
           19         "os"
           20         "strings"
           21 
           22         "github.com/bep/overlayfs"
           23         "github.com/gohugoio/hugo/common/paths"
           24         "github.com/gohugoio/hugo/config"
           25         "github.com/spf13/afero"
           26 )
           27 
           28 // Os points to the (real) Os filesystem.
           29 var Os = &afero.OsFs{}
           30 
           31 // Fs holds the core filesystems used by Hugo.
           32 type Fs struct {
           33         // Source is Hugo's source file system.
           34         // Note that this will always be a "plain" Afero filesystem:
           35         // * afero.OsFs when running in production
           36         // * afero.MemMapFs for many of the tests.
           37         Source afero.Fs
           38 
           39         // PublishDir is where Hugo publishes its rendered content.
           40         // It's mounted inside publishDir (default /public).
           41         PublishDir afero.Fs
           42 
           43         // PublishDirStatic is the file system used for static files.
           44         PublishDirStatic afero.Fs
           45 
           46         // PublishDirServer is the file system used for serving the public directory with Hugo's development server.
           47         // This will typically be the same as PublishDir, but not if --renderStaticToDisk is set.
           48         PublishDirServer afero.Fs
           49 
           50         // Os is an OS file system.
           51         // NOTE: Field is currently unused.
           52         Os afero.Fs
           53 
           54         // WorkingDirReadOnly is a read-only file system
           55         // restricted to the project working dir.
           56         WorkingDirReadOnly afero.Fs
           57 
           58         // WorkingDirWritable is a writable file system
           59         // restricted to the project working dir.
           60         WorkingDirWritable afero.Fs
           61 }
           62 
           63 func NewDefault(cfg config.Provider) *Fs {
           64         workingDir, publishDir := getWorkingPublishDir(cfg)
           65         fs := Os
           66         return newFs(fs, fs, workingDir, publishDir)
           67 }
           68 
           69 // NewFrom creates a new Fs based on the provided Afero Fs
           70 // as source and destination file systems.
           71 // Useful for testing.
           72 func NewFrom(fs afero.Fs, conf config.BaseConfig) *Fs {
           73         return newFs(fs, fs, conf.WorkingDir, conf.PublishDir)
           74 }
           75 
           76 func NewFromOld(fs afero.Fs, cfg config.Provider) *Fs {
           77         workingDir, publishDir := getWorkingPublishDir(cfg)
           78         return newFs(fs, fs, workingDir, publishDir)
           79 }
           80 
           81 // NewFromSourceAndDestination creates a new Fs based on the provided Afero Fss
           82 // as the source and destination file systems.
           83 func NewFromSourceAndDestination(source, destination afero.Fs, cfg config.Provider) *Fs {
           84         workingDir, publishDir := getWorkingPublishDir(cfg)
           85         return newFs(source, destination, workingDir, publishDir)
           86 }
           87 
           88 func getWorkingPublishDir(cfg config.Provider) (string, string) {
           89         workingDir := cfg.GetString("workingDir")
           90         publishDir := cfg.GetString("publishDirDynamic")
           91         if publishDir == "" {
           92                 publishDir = cfg.GetString("publishDir")
           93         }
           94         return workingDir, publishDir
           95 }
           96 
           97 func newFs(source, destination afero.Fs, workingDir, publishDir string) *Fs {
           98         if publishDir == "" {
           99                 panic("publishDir is empty")
          100         }
          101 
          102         if workingDir == "." {
          103                 workingDir = ""
          104         }
          105 
          106         // Sanity check
          107         if IsOsFs(source) && len(workingDir) < 2 {
          108                 panic("workingDir is too short")
          109         }
          110 
          111         // If this does not exist, it will be created later.
          112         absPublishDir := paths.AbsPathify(workingDir, publishDir)
          113 
          114         pubFs := NewBasePathFs(destination, absPublishDir)
          115 
          116         return &Fs{
          117                 Source:             source,
          118                 PublishDir:         pubFs,
          119                 PublishDirServer:   pubFs,
          120                 PublishDirStatic:   pubFs,
          121                 Os:                 &afero.OsFs{},
          122                 WorkingDirReadOnly: getWorkingDirFsReadOnly(source, workingDir),
          123                 WorkingDirWritable: getWorkingDirFsWritable(source, workingDir),
          124         }
          125 }
          126 
          127 func getWorkingDirFsReadOnly(base afero.Fs, workingDir string) afero.Fs {
          128         if workingDir == "" {
          129                 return NewReadOnlyFs(base)
          130         }
          131         return NewBasePathFs(NewReadOnlyFs(base), workingDir)
          132 }
          133 
          134 func getWorkingDirFsWritable(base afero.Fs, workingDir string) afero.Fs {
          135         if workingDir == "" {
          136                 return base
          137         }
          138         return NewBasePathFs(base, workingDir)
          139 }
          140 
          141 func isWrite(flag int) bool {
          142         return flag&os.O_RDWR != 0 || flag&os.O_WRONLY != 0
          143 }
          144 
          145 // MakeReadableAndRemoveAllModulePkgDir makes any subdir in dir readable and then
          146 // removes the root.
          147 // TODO(bep) move this to a more suitable place.
          148 func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error) {
          149         // Safe guard
          150         // Note that the base directory changed from pkg to gomod_cache in Go 1.23.
          151         if !strings.Contains(dir, "pkg") && !strings.Contains(dir, "gomod") {
          152                 panic(fmt.Sprint("invalid dir:", dir))
          153         }
          154 
          155         counter := 0
          156         afero.Walk(fs, dir, func(path string, info os.FileInfo, err error) error {
          157                 if err != nil {
          158                         return nil
          159                 }
          160                 if info.IsDir() {
          161                         counter++
          162                         fs.Chmod(path, 0o777)
          163                 }
          164                 return nil
          165         })
          166 
          167         return counter, fs.RemoveAll(dir)
          168 }
          169 
          170 // IsOsFs returns whether fs is an OsFs or if it fs wraps an OsFs.
          171 // TODO(bep) make this more robust.
          172 func IsOsFs(fs afero.Fs) bool {
          173         var isOsFs bool
          174         WalkFilesystems(fs, func(fs afero.Fs) bool {
          175                 switch fs.(type) {
          176                 case *afero.MemMapFs:
          177                         isOsFs = false
          178                 case *afero.OsFs:
          179                         isOsFs = true
          180                 }
          181                 return isOsFs
          182         })
          183         return isOsFs
          184 }
          185 
          186 // FilesystemsUnwrapper returns the underlying filesystems.
          187 type FilesystemsUnwrapper interface {
          188         UnwrapFilesystems() []afero.Fs
          189 }
          190 
          191 // FilesystemUnwrapper returns the underlying filesystem.
          192 type FilesystemUnwrapper interface {
          193         UnwrapFilesystem() afero.Fs
          194 }
          195 
          196 // WalkFn is the walk func for WalkFilesystems.
          197 type WalkFn func(fs afero.Fs) bool
          198 
          199 // WalkFilesystems walks fs recursively and calls fn.
          200 // If fn returns true, walking is stopped.
          201 func WalkFilesystems(fs afero.Fs, fn WalkFn) bool {
          202         if fn(fs) {
          203                 return true
          204         }
          205 
          206         if afs, ok := fs.(FilesystemUnwrapper); ok {
          207                 if WalkFilesystems(afs.UnwrapFilesystem(), fn) {
          208                         return true
          209                 }
          210         } else if bfs, ok := fs.(FilesystemsUnwrapper); ok {
          211                 for _, sf := range bfs.UnwrapFilesystems() {
          212                         if WalkFilesystems(sf, fn) {
          213                                 return true
          214                         }
          215                 }
          216         } else if cfs, ok := fs.(overlayfs.FilesystemIterator); ok {
          217                 for i := range cfs.NumFilesystems() {
          218                         if WalkFilesystems(cfs.Filesystem(i), fn) {
          219                                 return true
          220                         }
          221                 }
          222         }
          223 
          224         return false
          225 }
          226 
          227 var _ FilesystemUnwrapper = (*filesystemsWrapper)(nil)
          228 
          229 // NewBasePathFs creates a new BasePathFs.
          230 func NewBasePathFs(source afero.Fs, path string) afero.Fs {
          231         return WrapFilesystem(afero.NewBasePathFs(source, path), source)
          232 }
          233 
          234 // NewReadOnlyFs creates a new ReadOnlyFs.
          235 func NewReadOnlyFs(source afero.Fs) afero.Fs {
          236         return WrapFilesystem(afero.NewReadOnlyFs(source), source)
          237 }
          238 
          239 // WrapFilesystem is typically used to wrap a afero.BasePathFs to allow
          240 // access to the underlying filesystem if needed.
          241 func WrapFilesystem(container, content afero.Fs) afero.Fs {
          242         return filesystemsWrapper{Fs: container, content: content}
          243 }
          244 
          245 type filesystemsWrapper struct {
          246         afero.Fs
          247         content afero.Fs
          248 }
          249 
          250 func (w filesystemsWrapper) UnwrapFilesystem() afero.Fs {
          251         return w.content
          252 }