深入理解Hugo: Collect Module

package main
import "fmt"

relative path in source repo, e.g. “scss”

type Mount struct {

relative target path, e.g. “assets/bootstrap/scss”

    Source string

any language code associated with this mount.

    Target string
    Lang string
}

Module path

type Import struct {
    Path string
}

Config holds a module config.

type Config struct {
    Mounts  []Mount
    Imports []Import
}

Config The decoded module config and mounts.

type Module interface {

Owner In the dependency tree, this is the first module that defines this module as a dependency.

    Config() Config

Mounts Any directory remappings.

    Owner() Module
    Mounts() []Mount
}
type Modules []Module
var modules Modules

moduleAdapter implemented Module interface

type moduleAdapter struct {
    projectMod bool
    owner      Module
    mounts     []Mount
    config     Config
}
func (m *moduleAdapter) Config() Config {
    return m.config
}
func (m *moduleAdapter) Mounts() []Mount {
    return m.mounts
}
func (m *moduleAdapter) Owner() Module {
    return m.owner
}

happy path to easily understand

func main() {

project module config

    moduleConfig := Config{}
    imports := []string{"mytheme"}
    for _, imp := range imports {
        moduleConfig.Imports = append(
            moduleConfig.Imports, Import{
                Path: imp,
            })
    }

Need to run these after the modules are loaded, but before they are finalized.

    collectHook := func(mods Modules) {

Apply default project mounts. Default folder structure for hugo project

        ApplyProjectConfigDefaults(mods[0])
    }
    collectModules(moduleConfig, collectHook)
    for _, m := range modules {
        fmt.Printf("%#v\n", m)
    }
}

Module folder structure

const (
    ComponentFolderArchetypes = "archetypes"
    ComponentFolderStatic     = "static"
    ComponentFolderLayouts    = "layouts"
    ComponentFolderContent    = "content"
    ComponentFolderData       = "data"
    ComponentFolderAssets     = "assets"
    ComponentFolderI18n       = "i18n"
)

ApplyProjectConfigDefaults applies default/missing module configuration for the main project.

func ApplyProjectConfigDefaults(mod Module) {
    projectMod := mod.(*moduleAdapter)
    type dirKeyComponent struct {
        key          string
        component    string
        multilingual bool
    }
    dirKeys := []dirKeyComponent{
        {"contentDir", ComponentFolderContent, true},
        {"dataDir", ComponentFolderData, false},
        {"layoutDir", ComponentFolderLayouts, false},
        {"i18nDir", ComponentFolderI18n, false},
        {"archetypeDir", ComponentFolderArchetypes,
            false},
        {"assetDir", ComponentFolderAssets, false},
        {"", ComponentFolderStatic, false},
    }

based on language content configuration multiple language has multiple source folders

    var mounts []Mount
    for _, d := range dirKeys {
        if d.multilingual {
            if d.component == ComponentFolderContent {
                mounts = append(mounts, Mount{
                    Lang:   "en",
                    Source: "mycontent",
                    Target: d.component})
            }
        } else {
            mounts = append(mounts,
                Mount{
                    Source: d.component,
                    Target: d.component})
        }
    }
    projectMod.mounts = mounts
}
func collectModules(modConfig Config,
    hookBeforeFinalize func(m Modules)) {
    projectMod := &moduleAdapter{
        projectMod: true,
        config:     modConfig,
    }

module structure, [project, others…]

    addAndRecurse(projectMod)

Add the project mod on top.

    modules = append(Modules{projectMod}, modules...)
    if hookBeforeFinalize != nil {
        hookBeforeFinalize(modules)
    }
}

addAndRecurse Project Imports -> Import imports

func addAndRecurse(owner *moduleAdapter) {
    moduleConfig := owner.Config()

theme may depend on other theme

    for _, moduleImport := range moduleConfig.Imports {
        tc := add(owner, moduleImport)
        if tc == nil {
            continue
        }

tc is mytheme with no config file

        addAndRecurse(tc)
    }
}
func add(owner *moduleAdapter,
    moduleImport Import) *moduleAdapter {

in the example, mytheme has no other import

    fmt.Printf("start to create `%s` module\n",
        moduleImport.Path)
    ma := &moduleAdapter{
        owner: owner,
        config: Config{},
    }
    modules = append(modules, ma)
    return ma
}

collect theme as module

start to create `mytheme` module

project module has no owner with default mounts

&main.moduleAdapter{
  projectMod:true, owner:main.Module(nil),
  mounts:[]main.Mount{
    main.Mount{
      Source:"mycontent", Target:"content",Lang:"en"},
    main.Mount{Source:"data", Target:"data", Lang:""},
    main.Mount{
      Source:"layouts", Target:"layouts", Lang:""},
    main.Mount{Source:"i18n", Target:"i18n", Lang:""},
    main.Mount{
      Source:"archetypes", Target:"archetypes", Lang:""},
    main.Mount{Source:"assets", Target:"assets", Lang:""},
    main.Mount{
      Source:"static", Target:"static", Lang:""}},
    config:main.Config{Mounts:[]main.Mount(nil),
    Imports:[]main.Import{main.Import{Path:"mytheme"}}}}

theme module owned by project module with no import in the example

&main.moduleAdapter{projectMod:false,
owner:(*main.moduleAdapter)(0xc000102120),
mounts:[]main.Mount(nil),
config:main.Config{
  Mounts:[]main.Mount(nil), Imports:[]main.Import(nil)}}
Program exited.

Next example: Create Site.