hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

fs.go (6292B)

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