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 }