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  }