github.com/SuCicada/su-hugo@v1.0.0/hugofs/fs.go (about) 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 // NewDefault creates a new Fs with the OS file system 64 // as source and destination file systems. 65 func NewDefault(cfg config.Provider) *Fs { 66 fs := Os 67 return newFs(fs, fs, cfg) 68 } 69 70 // NewMem creates a new Fs with the MemMapFs 71 // as source and destination file systems. 72 // Useful for testing. 73 func NewMem(cfg config.Provider) *Fs { 74 fs := &afero.MemMapFs{} 75 return newFs(fs, fs, cfg) 76 } 77 78 // NewFrom creates a new Fs based on the provided Afero Fs 79 // as source and destination file systems. 80 // Useful for testing. 81 func NewFrom(fs afero.Fs, cfg config.Provider) *Fs { 82 return newFs(fs, fs, cfg) 83 } 84 85 // NewFrom creates a new Fs based on the provided Afero Fss 86 // as the source and destination file systems. 87 func NewFromSourceAndDestination(source, destination afero.Fs, cfg config.Provider) *Fs { 88 return newFs(source, destination, cfg) 89 } 90 91 func newFs(source, destination afero.Fs, cfg config.Provider) *Fs { 92 workingDir := cfg.GetString("workingDir") 93 publishDir := cfg.GetString("publishDir") 94 if publishDir == "" { 95 panic("publishDir is empty") 96 } 97 98 // Sanity check 99 if IsOsFs(source) && len(workingDir) < 2 { 100 panic("workingDir is too short") 101 } 102 103 absPublishDir := paths.AbsPathify(workingDir, publishDir) 104 105 // Make sure we always have the /public folder ready to use. 106 if err := source.MkdirAll(absPublishDir, 0777); err != nil && !os.IsExist(err) { 107 panic(err) 108 } 109 110 pubFs := afero.NewBasePathFs(destination, absPublishDir) 111 112 return &Fs{ 113 Source: source, 114 PublishDir: pubFs, 115 PublishDirServer: pubFs, 116 PublishDirStatic: pubFs, 117 Os: &afero.OsFs{}, 118 WorkingDirReadOnly: getWorkingDirFsReadOnly(source, workingDir), 119 WorkingDirWritable: getWorkingDirFsWritable(source, workingDir), 120 } 121 } 122 123 func getWorkingDirFsReadOnly(base afero.Fs, workingDir string) afero.Fs { 124 if workingDir == "" { 125 return afero.NewReadOnlyFs(base) 126 } 127 return afero.NewBasePathFs(afero.NewReadOnlyFs(base), workingDir) 128 } 129 130 func getWorkingDirFsWritable(base afero.Fs, workingDir string) afero.Fs { 131 if workingDir == "" { 132 return base 133 } 134 return afero.NewBasePathFs(base, workingDir) 135 } 136 137 func isWrite(flag int) bool { 138 return flag&os.O_RDWR != 0 || flag&os.O_WRONLY != 0 139 } 140 141 // MakeReadableAndRemoveAllModulePkgDir makes any subdir in dir readable and then 142 // removes the root. 143 // TODO(bep) move this to a more suitable place. 144 func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error) { 145 // Safe guard 146 if !strings.Contains(dir, "pkg") { 147 panic(fmt.Sprint("invalid dir:", dir)) 148 } 149 150 counter := 0 151 afero.Walk(fs, dir, func(path string, info os.FileInfo, err error) error { 152 if err != nil { 153 return nil 154 } 155 if info.IsDir() { 156 counter++ 157 fs.Chmod(path, 0777) 158 } 159 return nil 160 }) 161 return counter, fs.RemoveAll(dir) 162 } 163 164 // HasOsFs returns whether fs is an OsFs or if it fs wraps an OsFs. 165 // TODO(bep) make this nore robust. 166 func IsOsFs(fs afero.Fs) bool { 167 var isOsFs bool 168 WalkFilesystems(fs, func(fs afero.Fs) bool { 169 switch base := fs.(type) { 170 case *afero.MemMapFs: 171 isOsFs = false 172 case *afero.OsFs: 173 isOsFs = true 174 case *afero.BasePathFs: 175 _, supportsLstat, _ := base.LstatIfPossible("asdfasdfasdf") 176 isOsFs = supportsLstat 177 } 178 return isOsFs 179 }) 180 return isOsFs 181 } 182 183 // FilesystemsUnwrapper returns the underlying filesystems. 184 type FilesystemsUnwrapper interface { 185 UnwrapFilesystems() []afero.Fs 186 } 187 188 // FilesystemsProvider returns the underlying filesystem. 189 type FilesystemUnwrapper interface { 190 UnwrapFilesystem() afero.Fs 191 } 192 193 // WalkFn is the walk func for WalkFilesystems. 194 type WalkFn func(fs afero.Fs) bool 195 196 // WalkFilesystems walks fs recursively and calls fn. 197 // If fn returns true, walking is stopped. 198 func WalkFilesystems(fs afero.Fs, fn WalkFn) bool { 199 if fn(fs) { 200 return true 201 } 202 203 if afs, ok := fs.(FilesystemUnwrapper); ok { 204 if WalkFilesystems(afs.UnwrapFilesystem(), fn) { 205 return true 206 } 207 208 } else if bfs, ok := fs.(FilesystemsUnwrapper); ok { 209 for _, sf := range bfs.UnwrapFilesystems() { 210 if WalkFilesystems(sf, fn) { 211 return true 212 } 213 } 214 } else if cfs, ok := fs.(overlayfs.FilesystemIterator); ok { 215 for i := 0; i < cfs.NumFilesystems(); i++ { 216 if WalkFilesystems(cfs.Filesystem(i), fn) { 217 return true 218 } 219 } 220 } 221 222 return false 223 }