github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/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 provides a file based cache for Hugo. 15 package filecache 16 17 import ( 18 "fmt" 19 "path" 20 "path/filepath" 21 "strings" 22 "time" 23 24 "github.com/gohugoio/hugo/common/maps" 25 "github.com/gohugoio/hugo/config" 26 27 "errors" 28 29 "github.com/mitchellh/mapstructure" 30 "github.com/spf13/afero" 31 ) 32 33 const ( 34 resourcesGenDir = ":resourceDir/_gen" 35 cacheDirProject = ":cacheDir/:project" 36 ) 37 38 var defaultCacheConfig = FileCacheConfig{ 39 MaxAge: -1, // Never expire 40 Dir: cacheDirProject, 41 } 42 43 const ( 44 CacheKeyGetJSON = "getjson" 45 CacheKeyGetCSV = "getcsv" 46 CacheKeyImages = "images" 47 CacheKeyAssets = "assets" 48 CacheKeyModules = "modules" 49 CacheKeyGetResource = "getresource" 50 ) 51 52 type Configs map[string]FileCacheConfig 53 54 // For internal use. 55 func (c Configs) CacheDirModules() string { 56 return c[CacheKeyModules].DirCompiled 57 } 58 59 var defaultCacheConfigs = Configs{ 60 CacheKeyModules: { 61 MaxAge: -1, 62 Dir: ":cacheDir/modules", 63 }, 64 CacheKeyGetJSON: defaultCacheConfig, 65 CacheKeyGetCSV: defaultCacheConfig, 66 CacheKeyImages: { 67 MaxAge: -1, 68 Dir: resourcesGenDir, 69 }, 70 CacheKeyAssets: { 71 MaxAge: -1, 72 Dir: resourcesGenDir, 73 }, 74 CacheKeyGetResource: FileCacheConfig{ 75 MaxAge: -1, // Never expire 76 Dir: cacheDirProject, 77 }, 78 } 79 80 type FileCacheConfig struct { 81 // Max age of cache entries in this cache. Any items older than this will 82 // be removed and not returned from the cache. 83 // A negative value means forever, 0 means cache is disabled. 84 // Hugo is lenient with what types it accepts here, but we recommend using 85 // a duration string, a sequence of decimal numbers, each with optional fraction and a unit suffix, 86 // such as "300ms", "1.5h" or "2h45m". 87 // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 88 MaxAge time.Duration 89 90 // The directory where files are stored. 91 Dir string 92 DirCompiled string `json:"-"` 93 94 // Will resources/_gen will get its own composite filesystem that 95 // also checks any theme. 96 IsResourceDir bool `json:"-"` 97 } 98 99 // GetJSONCache gets the file cache for getJSON. 100 func (f Caches) GetJSONCache() *Cache { 101 return f[CacheKeyGetJSON] 102 } 103 104 // GetCSVCache gets the file cache for getCSV. 105 func (f Caches) GetCSVCache() *Cache { 106 return f[CacheKeyGetCSV] 107 } 108 109 // ImageCache gets the file cache for processed images. 110 func (f Caches) ImageCache() *Cache { 111 return f[CacheKeyImages] 112 } 113 114 // ModulesCache gets the file cache for Hugo Modules. 115 func (f Caches) ModulesCache() *Cache { 116 return f[CacheKeyModules] 117 } 118 119 // AssetsCache gets the file cache for assets (processed resources, SCSS etc.). 120 func (f Caches) AssetsCache() *Cache { 121 return f[CacheKeyAssets] 122 } 123 124 // GetResourceCache gets the file cache for remote resources. 125 func (f Caches) GetResourceCache() *Cache { 126 return f[CacheKeyGetResource] 127 } 128 129 func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Configs, error) { 130 c := make(Configs) 131 valid := make(map[string]bool) 132 // Add defaults 133 for k, v := range defaultCacheConfigs { 134 c[k] = v 135 valid[k] = true 136 } 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 for k, v := range c { 174 dir := filepath.ToSlash(filepath.Clean(v.Dir)) 175 hadSlash := strings.HasPrefix(dir, "/") 176 parts := strings.Split(dir, "/") 177 178 for i, part := range parts { 179 if strings.HasPrefix(part, ":") { 180 resolved, isResource, err := resolveDirPlaceholder(fs, bcfg, part) 181 if err != nil { 182 return c, err 183 } 184 if isResource { 185 v.IsResourceDir = true 186 } 187 parts[i] = resolved 188 } 189 } 190 191 dir = path.Join(parts...) 192 if hadSlash { 193 dir = "/" + dir 194 } 195 v.DirCompiled = filepath.Clean(filepath.FromSlash(dir)) 196 197 if !v.IsResourceDir { 198 if isOsFs && !filepath.IsAbs(v.DirCompiled) { 199 return c, fmt.Errorf("%q must resolve to an absolute directory", v.DirCompiled) 200 } 201 202 // Avoid cache in root, e.g. / (Unix) or c:\ (Windows) 203 if len(strings.TrimPrefix(v.DirCompiled, filepath.VolumeName(v.DirCompiled))) == 1 { 204 return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.DirCompiled) 205 } 206 } 207 208 if !strings.HasPrefix(v.DirCompiled, "_gen") { 209 // We do cache eviction (file removes) and since the user can set 210 // his/hers own cache directory, we really want to make sure 211 // we do not delete any files that do not belong to this cache. 212 // We do add the cache name as the root, but this is an extra safe 213 // guard. We skip the files inside /resources/_gen/ because 214 // that would be breaking. 215 v.DirCompiled = filepath.Join(v.DirCompiled, FilecacheRootDirname, k) 216 } else { 217 v.DirCompiled = filepath.Join(v.DirCompiled, k) 218 } 219 220 c[k] = v 221 } 222 223 return c, nil 224 } 225 226 // Resolves :resourceDir => /myproject/resources etc., :cacheDir => ... 227 func resolveDirPlaceholder(fs afero.Fs, bcfg config.BaseConfig, placeholder string) (cacheDir string, isResource bool, err error) { 228 229 switch strings.ToLower(placeholder) { 230 case ":resourcedir": 231 return "", true, nil 232 case ":cachedir": 233 return bcfg.CacheDir, false, nil 234 case ":project": 235 return filepath.Base(bcfg.WorkingDir), false, nil 236 } 237 238 return "", false, fmt.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder) 239 }