URI: 
       cache/filecache: Add a :project placeholder - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit 94f0f7e59788e802e706a55cac0d52a9e70ff745
   DIR parent 3c29c5af8ee865ef20741f576088e031e940c3d2
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Wed, 14 Nov 2018 17:18:32 +0100
       
       cache/filecache: Add a :project placeholder
       
       This allows for "cache per Hugo project", making `hugo --gc` work as expected, even if you have several Hugo projects running on the same PC.
       
       See #5439
       
       Diffstat:
         M cache/filecache/filecache.go        |       5 ++++-
         M cache/filecache/filecache_config.go |      37 +++++++++++++++++++------------
         M cache/filecache/filecache_config_t… |      32 +++++++++++++++++++++++++++++++
         M docs/content/en/getting-started/co… |       7 +++++--
         M hugolib/site_test.go                |      11 -----------
       
       5 files changed, 64 insertions(+), 28 deletions(-)
       ---
   DIR diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go
       @@ -272,7 +272,10 @@ func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
        }
        
        func (c *Cache) isExpired(modTime time.Time) bool {
       -        return c.maxAge >= 0 && time.Now().Sub(modTime) > c.maxAge
       +        if c.maxAge < 0 {
       +                return false
       +        }
       +        return c.maxAge == 0 || time.Now().Sub(modTime) > c.maxAge
        }
        
        // For testing
   DIR diff --git a/cache/filecache/filecache_config.go b/cache/filecache/filecache_config.go
       @@ -35,7 +35,7 @@ const (
        
        var defaultCacheConfig = cacheConfig{
                MaxAge: -1, // Never expire
       -        Dir:    ":cacheDir",
       +        Dir:    ":cacheDir/:project",
        }
        
        const (
       @@ -139,26 +139,33 @@ func decodeConfig(p *paths.Paths) (cachesConfig, error) {
                disabled := cfg.GetBool("ignoreCache")
        
                for k, v := range c {
       -                v.Dir = filepath.Clean(v.Dir)
       -                dir := filepath.ToSlash(v.Dir)
       +                dir := filepath.ToSlash(filepath.Clean(v.Dir))
       +                hadSlash := strings.HasPrefix(dir, "/")
                        parts := strings.Split(dir, "/")
       -                first := parts[0]
        
       -                if strings.HasPrefix(first, ":") {
       -                        resolved, err := resolveDirPlaceholder(p, first)
       -                        if err != nil {
       -                                return c, err
       +                for i, part := range parts {
       +                        if strings.HasPrefix(part, ":") {
       +                                resolved, err := resolveDirPlaceholder(p, part)
       +                                if err != nil {
       +                                        return c, err
       +                                }
       +                                parts[i] = resolved
                                }
       -                        resolved = filepath.ToSlash(resolved)
       +                }
        
       -                        v.Dir = filepath.FromSlash(path.Join((append([]string{resolved}, parts[1:]...))...))
       +                dir = path.Join(parts...)
       +                if hadSlash {
       +                        dir = "/" + dir
       +                }
       +                v.Dir = filepath.Clean(filepath.FromSlash(dir))
        
       -                } else if isOsFs && !path.IsAbs(dir) {
       -                        return c, errors.Errorf("%q must either start with a placeholder (e.g. :cacheDir, :resourceDir) or be absolute", v.Dir)
       +                if isOsFs && !filepath.IsAbs(v.Dir) {
       +                        return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir)
                        }
        
       -                if len(v.Dir) < 5 {
       -                        return c, errors.Errorf("%q is not a valid cache dir", v.Dir)
       +                // Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
       +                if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
       +                        return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
                        }
        
                        if disabled {
       @@ -178,6 +185,8 @@ func resolveDirPlaceholder(p *paths.Paths, placeholder string) (string, error) {
                        return p.AbsResourcesDir, nil
                case ":cachedir":
                        return helpers.GetCacheDir(p.Fs.Source, p.Cfg)
       +        case ":project":
       +                return filepath.Base(p.WorkingDir), nil
                }
        
                return "", errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
   DIR diff --git a/cache/filecache/filecache_config_test.go b/cache/filecache/filecache_config_test.go
       @@ -16,6 +16,7 @@ package filecache
        import (
                "path/filepath"
                "runtime"
       +        "strings"
                "testing"
                "time"
        
       @@ -107,6 +108,8 @@ dir = "/path/to/c3"
        func TestDecodeConfigDefault(t *testing.T) {
                assert := require.New(t)
                cfg := viper.New()
       +        cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
       +
                if runtime.GOOS == "windows" {
                        cfg.Set("resourceDir", "c:\\cache\\resources")
                        cfg.Set("cacheDir", "c:\\cache\\thecache")
       @@ -130,5 +133,34 @@ func TestDecodeConfigDefault(t *testing.T) {
                        assert.Equal("c:\\cache\\resources\\_gen", decoded[cacheKeyImages].Dir)
                } else {
                        assert.Equal("/cache/resources/_gen", decoded[cacheKeyImages].Dir)
       +                assert.Equal("/cache/thecache/hugoproject", decoded[cacheKeyGetJSON].Dir)
       +        }
       +}
       +
       +func TestDecodeConfigInvalidDir(t *testing.T) {
       +        t.Parallel()
       +
       +        assert := require.New(t)
       +
       +        configStr := `
       +resourceDir = "myresources"
       +[caches]
       +[caches.getJSON]
       +maxAge = "10m"
       +dir = "/"
       +
       +`
       +        if runtime.GOOS == "windows" {
       +                configStr = strings.Replace(configStr, "/", "c:\\\\", 1)
                }
       +
       +        cfg, err := config.FromConfigString(configStr, "toml")
       +        assert.NoError(err)
       +        fs := hugofs.NewMem(cfg)
       +        p, err := paths.New(fs, cfg)
       +        assert.NoError(err)
       +
       +        _, err = decodeConfig(p)
       +        assert.Error(err)
       +
        }
   DIR diff --git a/docs/content/en/getting-started/configuration.md b/docs/content/en/getting-started/configuration.md
       @@ -413,10 +413,10 @@ Since Hugo 0.52 you can configure more than just the `cacheDir`. This is the def
        ```toml
        [caches]
        [caches.getjson]
       -dir = ":cacheDir"
       +dir = ":cacheDir/:project"
        maxAge = -1
        [caches.getcsv]
       -dir = ":cacheDir"
       +dir = ":cacheDir/:project"
        maxAge = -1
        [caches.images]
        dir = ":resourceDir/_gen"
       @@ -434,6 +434,9 @@ You can override any of these cache setting in your own `config.toml`.
        :cacheDir
        : This is the value of the `cacheDir` config option if set (can also be set via OS env variable `HUGO_CACHEDIR`). It will fall back to `/opt/build/cache/hugo_cache/` on Netlify, or a `hugo_cache` directory below the OS temp dir for the others. This means that if you run your builds on Netlify, all caches configured with `:cacheDir` will be saved and restored on the next build. For other CI vendors, please read their documentation. For an CircleCI example, see [this configuration](https://github.com/bep/hugo-sass-test/blob/6c3960a8f4b90e8938228688bc49bdcdd6b2d99e/.circleci/config.yml).
        
       +:project
       +
       +The base directory name of the current Hugo project. This means that, in its default setting, every project will have separated file caches, which means that when you do `hugo --gc` you will not touch files related to other Hugo projects running on the same PC.
        
        :resourceDir
        : This is the value of the `resourceDir` config option.
   DIR diff --git a/hugolib/site_test.go b/hugolib/site_test.go
       @@ -15,7 +15,6 @@ package hugolib
        
        import (
                "fmt"
       -        "os"
                "path/filepath"
                "strings"
                "testing"
       @@ -25,7 +24,6 @@ import (
                "github.com/gohugoio/hugo/helpers"
        
                "github.com/gohugoio/hugo/deps"
       -        "github.com/gohugoio/hugo/hugofs"
                "github.com/stretchr/testify/assert"
                "github.com/stretchr/testify/require"
        )
       @@ -351,15 +349,6 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
        
        }
        
       -func TestNewSiteDefaultLang(t *testing.T) {
       -        t.Parallel()
       -        defer os.Remove("resources")
       -        s, err := NewSiteDefaultLang()
       -        require.NoError(t, err)
       -        require.Equal(t, hugofs.Os, s.Fs.Source)
       -        require.Equal(t, hugofs.Os, s.Fs.Destination)
       -}
       -
        // Issue #3355
        func TestShouldNotWriteZeroLengthFilesToDestination(t *testing.T) {
                cfg, fs := newTestCfg()