github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/cache/filecache/filecache_config.go (about)

     1  // Copyright 2018 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 filecache
    15  
    16  import (
    17  	"path"
    18  	"path/filepath"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/gohugoio/hugo/common/maps"
    23  
    24  	"github.com/gohugoio/hugo/config"
    25  
    26  	"github.com/gohugoio/hugo/helpers"
    27  
    28  	"github.com/mitchellh/mapstructure"
    29  	"github.com/pkg/errors"
    30  	"github.com/spf13/afero"
    31  )
    32  
    33  const (
    34  	cachesConfigKey = "caches"
    35  
    36  	resourcesGenDir = ":resourceDir/_gen"
    37  )
    38  
    39  var defaultCacheConfig = Config{
    40  	MaxAge: -1, // Never expire
    41  	Dir:    ":cacheDir/:project",
    42  }
    43  
    44  const (
    45  	cacheKeyGetJSON = "getjson"
    46  	cacheKeyGetCSV  = "getcsv"
    47  	cacheKeyImages  = "images"
    48  	cacheKeyAssets  = "assets"
    49  	cacheKeyModules = "modules"
    50  )
    51  
    52  type Configs map[string]Config
    53  
    54  func (c Configs) CacheDirModules() string {
    55  	return c[cacheKeyModules].Dir
    56  }
    57  
    58  var defaultCacheConfigs = Configs{
    59  	cacheKeyModules: {
    60  		MaxAge: -1,
    61  		Dir:    ":cacheDir/modules",
    62  	},
    63  	cacheKeyGetJSON: defaultCacheConfig,
    64  	cacheKeyGetCSV:  defaultCacheConfig,
    65  	cacheKeyImages: {
    66  		MaxAge: -1,
    67  		Dir:    resourcesGenDir,
    68  	},
    69  	cacheKeyAssets: {
    70  		MaxAge: -1,
    71  		Dir:    resourcesGenDir,
    72  	},
    73  }
    74  
    75  type Config struct {
    76  	// Max age of cache entries in this cache. Any items older than this will
    77  	// be removed and not returned from the cache.
    78  	// a negative value means forever, 0 means cache is disabled.
    79  	MaxAge time.Duration
    80  
    81  	// The directory where files are stored.
    82  	Dir string
    83  
    84  	// Will resources/_gen will get its own composite filesystem that
    85  	// also checks any theme.
    86  	isResourceDir bool
    87  }
    88  
    89  // GetJSONCache gets the file cache for getJSON.
    90  func (f Caches) GetJSONCache() *Cache {
    91  	return f[cacheKeyGetJSON]
    92  }
    93  
    94  // GetCSVCache gets the file cache for getCSV.
    95  func (f Caches) GetCSVCache() *Cache {
    96  	return f[cacheKeyGetCSV]
    97  }
    98  
    99  // ImageCache gets the file cache for processed images.
   100  func (f Caches) ImageCache() *Cache {
   101  	return f[cacheKeyImages]
   102  }
   103  
   104  // ModulesCache gets the file cache for Hugo Modules.
   105  func (f Caches) ModulesCache() *Cache {
   106  	return f[cacheKeyModules]
   107  }
   108  
   109  // AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
   110  func (f Caches) AssetsCache() *Cache {
   111  	return f[cacheKeyAssets]
   112  }
   113  
   114  func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
   115  	c := make(Configs)
   116  	valid := make(map[string]bool)
   117  	// Add defaults
   118  	for k, v := range defaultCacheConfigs {
   119  		c[k] = v
   120  		valid[k] = true
   121  	}
   122  
   123  	m := cfg.GetStringMap(cachesConfigKey)
   124  
   125  	_, isOsFs := fs.(*afero.OsFs)
   126  
   127  	for k, v := range m {
   128  		if _, ok := v.(maps.Params); !ok {
   129  			continue
   130  		}
   131  		cc := defaultCacheConfig
   132  
   133  		dc := &mapstructure.DecoderConfig{
   134  			Result:           &cc,
   135  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   136  			WeaklyTypedInput: true,
   137  		}
   138  
   139  		decoder, err := mapstructure.NewDecoder(dc)
   140  		if err != nil {
   141  			return c, err
   142  		}
   143  
   144  		if err := decoder.Decode(v); err != nil {
   145  			return nil, errors.Wrap(err, "failed to decode filecache config")
   146  		}
   147  
   148  		if cc.Dir == "" {
   149  			return c, errors.New("must provide cache Dir")
   150  		}
   151  
   152  		name := strings.ToLower(k)
   153  		if !valid[name] {
   154  			return nil, errors.Errorf("%q is not a valid cache name", name)
   155  		}
   156  
   157  		c[name] = cc
   158  	}
   159  
   160  	// This is a very old flag in Hugo, but we need to respect it.
   161  	disabled := cfg.GetBool("ignoreCache")
   162  
   163  	for k, v := range c {
   164  		dir := filepath.ToSlash(filepath.Clean(v.Dir))
   165  		hadSlash := strings.HasPrefix(dir, "/")
   166  		parts := strings.Split(dir, "/")
   167  
   168  		for i, part := range parts {
   169  			if strings.HasPrefix(part, ":") {
   170  				resolved, isResource, err := resolveDirPlaceholder(fs, cfg, part)
   171  				if err != nil {
   172  					return c, err
   173  				}
   174  				if isResource {
   175  					v.isResourceDir = true
   176  				}
   177  				parts[i] = resolved
   178  			}
   179  		}
   180  
   181  		dir = path.Join(parts...)
   182  		if hadSlash {
   183  			dir = "/" + dir
   184  		}
   185  		v.Dir = filepath.Clean(filepath.FromSlash(dir))
   186  
   187  		if !v.isResourceDir {
   188  			if isOsFs && !filepath.IsAbs(v.Dir) {
   189  				return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir)
   190  			}
   191  
   192  			// Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
   193  			if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
   194  				return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
   195  			}
   196  		}
   197  
   198  		if !strings.HasPrefix(v.Dir, "_gen") {
   199  			// We do cache eviction (file removes) and since the user can set
   200  			// his/hers own cache directory, we really want to make sure
   201  			// we do not delete any files that do not belong to this cache.
   202  			// We do add the cache name as the root, but this is an extra safe
   203  			// guard. We skip the files inside /resources/_gen/ because
   204  			// that would be breaking.
   205  			v.Dir = filepath.Join(v.Dir, filecacheRootDirname, k)
   206  		} else {
   207  			v.Dir = filepath.Join(v.Dir, k)
   208  		}
   209  
   210  		if disabled {
   211  			v.MaxAge = 0
   212  		}
   213  
   214  		c[k] = v
   215  	}
   216  
   217  	return c, nil
   218  }
   219  
   220  // Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
   221  func resolveDirPlaceholder(fs afero.Fs, cfg config.Provider, placeholder string) (cacheDir string, isResource bool, err error) {
   222  	workingDir := cfg.GetString("workingDir")
   223  
   224  	switch strings.ToLower(placeholder) {
   225  	case ":resourcedir":
   226  		return "", true, nil
   227  	case ":cachedir":
   228  		d, err := helpers.GetCacheDir(fs, cfg)
   229  		return d, false, err
   230  	case ":project":
   231  		return filepath.Base(workingDir), false, nil
   232  	}
   233  
   234  	return "", false, errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
   235  }