URI: 
       Add module.replacements - 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 173187e2633f3fc037c83e1e3de2902ae3c93b92
   DIR parent 8a1c637c4494751046142e0ef345fce38fc1431b
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Thu, 29 Oct 2020 17:14:04 +0100
       
       Add module.replacements
       
       Fixes #7904
       Fixes #7908
       
       Diffstat:
         M modules/client.go                   |       9 +++++++++
         M modules/client_test.go              |      28 ++++++++++++++++++++++++++++
         M modules/collect.go                  |      13 ++++++++-----
         M modules/config.go                   |      49 +++++++++++++++++++++++++++++++
         M modules/config_test.go              |      72 ++++++++++++++++++++++---------
       
       5 files changed, 146 insertions(+), 25 deletions(-)
       ---
   DIR diff --git a/modules/client.go b/modules/client.go
       @@ -613,6 +613,15 @@ func (c *Client) shouldVendor(path string) bool {
                return c.noVendor == nil || !c.noVendor.Match(path)
        }
        
       +func (c *Client) createThemeDirname(modulePath string, isProjectMod bool) (string, error) {
       +        modulePath = filepath.Clean(modulePath)
       +        moduleDir := filepath.Join(c.ccfg.ThemesDir, modulePath)
       +        if !isProjectMod && !strings.HasPrefix(moduleDir, c.ccfg.ThemesDir) {
       +                return "", errors.Errorf("invalid module path %q; must be relative to themesDir when defined outside of the project", modulePath)
       +        }
       +        return moduleDir, nil
       +}
       +
        // ClientConfig configures the module Client.
        type ClientConfig struct {
                Fs     afero.Fs
   DIR diff --git a/modules/client_test.go b/modules/client_test.go
       @@ -15,6 +15,8 @@ package modules
        
        import (
                "bytes"
       +        "os"
       +        "path/filepath"
                "testing"
        
                "github.com/gohugoio/hugo/hugofs/glob"
       @@ -41,10 +43,14 @@ github.com/gohugoio/hugoTestModules1_darwin/modh2_2@v1.4.0 github.com/gohugoio/h
        
                        workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, modName)
                        c.Assert(err, qt.IsNil)
       +                themesDir := filepath.Join(workingDir, "themes")
       +                err = os.Mkdir(themesDir, 0777)
       +                c.Assert(err, qt.IsNil)
        
                        ccfg := ClientConfig{
                                Fs:         hugofs.Os,
                                WorkingDir: workingDir,
       +                        ThemesDir:  themesDir,
                        }
        
                        withConfig(&ccfg)
       @@ -131,6 +137,28 @@ project github.com/gohugoio/hugoTestModules1_darwin/modh2_2_2@v1.3.0+vendor
                        c.Assert(graphb.String(), qt.Equals, expect)
                })
        
       +        // https://github.com/gohugoio/hugo/issues/7908
       +        c.Run("createThemeDirname", func(c *qt.C) {
       +                mcfg := DefaultModuleConfig
       +                client, clean := newClient(
       +                        c, func(cfg *ClientConfig) {
       +                                cfg.ModuleConfig = mcfg
       +                        })
       +                defer clean()
       +
       +                dirname, err := client.createThemeDirname("foo", false)
       +                c.Assert(err, qt.IsNil)
       +                c.Assert(dirname, qt.Equals, filepath.Join(client.ccfg.ThemesDir, "foo"))
       +
       +                dirname, err = client.createThemeDirname("../../foo", true)
       +                c.Assert(err, qt.IsNil)
       +                c.Assert(dirname, qt.Equals, filepath.Join(client.ccfg.ThemesDir, "../../foo"))
       +
       +                dirname, err = client.createThemeDirname("../../foo", false)
       +                c.Assert(err, qt.Not(qt.IsNil))
       +
       +        })
       +
        }
        
        var globAll, _ = glob.GetGlob("**")
   DIR diff --git a/modules/collect.go b/modules/collect.go
       @@ -274,10 +274,14 @@ func (c *collector) add(owner *moduleAdapter, moduleImport Import, disabled bool
                                        }
                                }
        
       -                        // Fall back to /themes/<mymodule>
       +                        // Fall back to project/themes/<mymodule>
                                if moduleDir == "" {
       -                                moduleDir = filepath.Join(c.ccfg.ThemesDir, modulePath)
       -
       +                                var err error
       +                                moduleDir, err = c.createThemeDirname(modulePath, owner.projectMod)
       +                                if err != nil {
       +                                        c.err = err
       +                                        return nil, nil
       +                                }
                                        if found, _ := afero.Exists(c.fs, moduleDir); !found {
                                                c.err = c.wrapModuleNotFound(errors.Errorf(`module %q not found; either add it as a Hugo Module or store it in %q.`, modulePath, c.ccfg.ThemesDir))
                                                return nil, nil
       @@ -441,7 +445,7 @@ func (c *collector) applyThemeConfig(tc *moduleAdapter) error {
                        tc.cfg = cfg
                }
        
       -        config, err := DecodeConfig(cfg)
       +        config, err := decodeConfig(cfg, c.moduleConfig.replacementsMap)
                if err != nil {
                        return err
                }
       @@ -605,7 +609,6 @@ func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mou
        
                        mnt.Source = filepath.Clean(mnt.Source)
                        mnt.Target = filepath.Clean(mnt.Target)
       -
                        var sourceDir string
        
                        if owner.projectMod && filepath.IsAbs(mnt.Source) {
   DIR diff --git a/modules/config.go b/modules/config.go
       @@ -18,6 +18,8 @@ import (
                "path/filepath"
                "strings"
        
       +        "github.com/pkg/errors"
       +
                "github.com/gohugoio/hugo/common/hugo"
        
                "github.com/gohugoio/hugo/config"
       @@ -40,6 +42,14 @@ var DefaultModuleConfig = Config{
                // Comma separated glob list matching paths that should be
                // treated as private.
                Private: "*.*",
       +
       +        // A list of replacement directives mapping a module path to a directory
       +        // or a theme component in the themes folder.
       +        // Note that this will turn the component into a traditional theme component
       +        // that does not partake in vendoring etc.
       +        // The syntax is the similar to the replacement directives used in go.mod, e.g:
       +        //    github.com/mod1 -> ../mod1,github.com/mod2 -> ../mod2
       +        Replacements: nil,
        }
        
        // ApplyProjectConfigDefaults applies default/missing module configuration for
       @@ -182,7 +192,12 @@ func ApplyProjectConfigDefaults(cfg config.Provider, mod Module) error {
        
        // DecodeConfig creates a modules Config from a given Hugo configuration.
        func DecodeConfig(cfg config.Provider) (Config, error) {
       +        return decodeConfig(cfg, nil)
       +}
       +
       +func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Config, error) {
                c := DefaultModuleConfig
       +        c.replacementsMap = pathReplacements
        
                if cfg == nil {
                        return c, nil
       @@ -197,6 +212,37 @@ func DecodeConfig(cfg config.Provider) (Config, error) {
                                return c, err
                        }
        
       +                if c.replacementsMap == nil {
       +
       +                        if len(c.Replacements) == 1 {
       +                                c.Replacements = strings.Split(c.Replacements[0], ",")
       +                        }
       +
       +                        for i, repl := range c.Replacements {
       +                                c.Replacements[i] = strings.TrimSpace(repl)
       +                        }
       +
       +                        c.replacementsMap = make(map[string]string)
       +                        for _, repl := range c.Replacements {
       +                                parts := strings.Split(repl, "->")
       +                                if len(parts) != 2 {
       +                                        return c, errors.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl)
       +                                }
       +
       +                                c.replacementsMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
       +                        }
       +                }
       +
       +                if c.replacementsMap != nil && c.Imports != nil {
       +                        for i, imp := range c.Imports {
       +                                if newImp, found := c.replacementsMap[imp.Path]; found {
       +                                        imp.Path = newImp
       +                                        c.Imports[i] = imp
       +                                }
       +                        }
       +
       +                }
       +
                        for i, mnt := range c.Mounts {
                                mnt.Source = filepath.Clean(mnt.Source)
                                mnt.Target = filepath.Clean(mnt.Target)
       @@ -233,6 +279,9 @@ type Config struct {
                // "github.com/**".
                NoVendor string
        
       +        Replacements    []string
       +        replacementsMap map[string]string
       +
                // Configures GOPROXY.
                Proxy string
                // Configures GONOPROXY.
   DIR diff --git a/modules/config_test.go b/modules/config_test.go
       @@ -41,7 +41,9 @@ func TestConfigHugoVersionIsValid(t *testing.T) {
        
        func TestDecodeConfig(t *testing.T) {
                c := qt.New(t)
       -        tomlConfig := `
       +
       +        c.Run("Basic", func(c *qt.C) {
       +                tomlConfig := `
        [module]
        
        [module.hugoVersion]
       @@ -63,31 +65,61 @@ source="src/markdown/blog"
        target="content/blog"
        lang="en"
        `
       -        cfg, err := config.FromConfigString(tomlConfig, "toml")
       -        c.Assert(err, qt.IsNil)
       +                cfg, err := config.FromConfigString(tomlConfig, "toml")
       +                c.Assert(err, qt.IsNil)
        
       -        mcfg, err := DecodeConfig(cfg)
       -        c.Assert(err, qt.IsNil)
       +                mcfg, err := DecodeConfig(cfg)
       +                c.Assert(err, qt.IsNil)
        
       -        v056 := hugo.VersionString("0.56.0")
       +                v056 := hugo.VersionString("0.56.0")
        
       -        hv := mcfg.HugoVersion
       +                hv := mcfg.HugoVersion
        
       -        c.Assert(v056.Compare(hv.Min), qt.Equals, -1)
       -        c.Assert(v056.Compare(hv.Max), qt.Equals, 1)
       -        c.Assert(hv.Extended, qt.Equals, true)
       +                c.Assert(v056.Compare(hv.Min), qt.Equals, -1)
       +                c.Assert(v056.Compare(hv.Max), qt.Equals, 1)
       +                c.Assert(hv.Extended, qt.Equals, true)
        
       -        if hugo.IsExtended {
       -                c.Assert(hv.IsValid(), qt.Equals, true)
       -        }
       +                if hugo.IsExtended {
       +                        c.Assert(hv.IsValid(), qt.Equals, true)
       +                }
       +
       +                c.Assert(len(mcfg.Mounts), qt.Equals, 1)
       +                c.Assert(len(mcfg.Imports), qt.Equals, 1)
       +                imp := mcfg.Imports[0]
       +                imp.Path = "github.com/bep/mycomponent"
       +                c.Assert(imp.Mounts[1].Source, qt.Equals, "src/markdown/blog")
       +                c.Assert(imp.Mounts[1].Target, qt.Equals, "content/blog")
       +                c.Assert(imp.Mounts[1].Lang, qt.Equals, "en")
       +        })
       +
       +        c.Run("Replacements", func(c *qt.C) {
       +                for _, tomlConfig := range []string{`
       +[module]
       +replacements="a->b,github.com/bep/mycomponent->c"
       +[[module.imports]]
       +path="github.com/bep/mycomponent"
       +`, `
       +[module]
       +replacements=["a->b","github.com/bep/mycomponent->c"]
       +[[module.imports]]
       +path="github.com/bep/mycomponent"
       +`} {
       +
       +                        cfg, err := config.FromConfigString(tomlConfig, "toml")
       +                        c.Assert(err, qt.IsNil)
       +
       +                        mcfg, err := DecodeConfig(cfg)
       +                        c.Assert(err, qt.IsNil)
       +                        c.Assert(mcfg.Replacements, qt.DeepEquals, []string{"a->b", "github.com/bep/mycomponent->c"})
       +                        c.Assert(mcfg.replacementsMap, qt.DeepEquals, map[string]string{
       +                                "a":                          "b",
       +                                "github.com/bep/mycomponent": "c",
       +                        })
       +
       +                        c.Assert(mcfg.Imports[0].Path, qt.Equals, "c")
        
       -        c.Assert(len(mcfg.Mounts), qt.Equals, 1)
       -        c.Assert(len(mcfg.Imports), qt.Equals, 1)
       -        imp := mcfg.Imports[0]
       -        imp.Path = "github.com/bep/mycomponent"
       -        c.Assert(imp.Mounts[1].Source, qt.Equals, "src/markdown/blog")
       -        c.Assert(imp.Mounts[1].Target, qt.Equals, "content/blog")
       -        c.Assert(imp.Mounts[1].Lang, qt.Equals, "en")
       +                }
       +        })
        
        }