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 }