hugo

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

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

filecache_config.go (6155B)

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