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  }