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