hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

config.go (11056B)

    1 // Copyright 2019 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 modules
   15 
   16 import (
   17 	"fmt"
   18 	"path/filepath"
   19 	"strings"
   20 
   21 	"github.com/gohugoio/hugo/common/hugo"
   22 
   23 	"github.com/gohugoio/hugo/config"
   24 	"github.com/gohugoio/hugo/hugofs/files"
   25 	"github.com/gohugoio/hugo/langs"
   26 	"github.com/mitchellh/mapstructure"
   27 )
   28 
   29 var DefaultModuleConfig = Config{
   30 
   31 	// Default to direct, which means "git clone" and similar. We
   32 	// will investigate proxy settings in more depth later.
   33 	// See https://github.com/golang/go/issues/26334
   34 	Proxy: "direct",
   35 
   36 	// Comma separated glob list matching paths that should not use the
   37 	// proxy configured above.
   38 	NoProxy: "none",
   39 
   40 	// Comma separated glob list matching paths that should be
   41 	// treated as private.
   42 	Private: "*.*",
   43 
   44 	// A list of replacement directives mapping a module path to a directory
   45 	// or a theme component in the themes folder.
   46 	// Note that this will turn the component into a traditional theme component
   47 	// that does not partake in vendoring etc.
   48 	// The syntax is the similar to the replacement directives used in go.mod, e.g:
   49 	//    github.com/mod1 -> ../mod1,github.com/mod2 -> ../mod2
   50 	Replacements: nil,
   51 }
   52 
   53 // ApplyProjectConfigDefaults applies default/missing module configuration for
   54 // the main project.
   55 func ApplyProjectConfigDefaults(cfg config.Provider, mod Module) error {
   56 	moda := mod.(*moduleAdapter)
   57 
   58 	// Map legacy directory config into the new module.
   59 	languages := cfg.Get("languagesSortedDefaultFirst").(langs.Languages)
   60 	isMultiHost := languages.IsMultihost()
   61 
   62 	// To bridge between old and new configuration format we need
   63 	// a way to make sure all of the core components are configured on
   64 	// the basic level.
   65 	componentsConfigured := make(map[string]bool)
   66 	for _, mnt := range moda.mounts {
   67 		if !strings.HasPrefix(mnt.Target, files.JsConfigFolderMountPrefix) {
   68 			componentsConfigured[mnt.Component()] = true
   69 		}
   70 	}
   71 
   72 	type dirKeyComponent struct {
   73 		key          string
   74 		component    string
   75 		multilingual bool
   76 	}
   77 
   78 	dirKeys := []dirKeyComponent{
   79 		{"contentDir", files.ComponentFolderContent, true},
   80 		{"dataDir", files.ComponentFolderData, false},
   81 		{"layoutDir", files.ComponentFolderLayouts, false},
   82 		{"i18nDir", files.ComponentFolderI18n, false},
   83 		{"archetypeDir", files.ComponentFolderArchetypes, false},
   84 		{"assetDir", files.ComponentFolderAssets, false},
   85 		{"", files.ComponentFolderStatic, isMultiHost},
   86 	}
   87 
   88 	createMountsFor := func(d dirKeyComponent, cfg config.Provider) []Mount {
   89 		var lang string
   90 		if language, ok := cfg.(*langs.Language); ok {
   91 			lang = language.Lang
   92 		}
   93 
   94 		// Static mounts are a little special.
   95 		if d.component == files.ComponentFolderStatic {
   96 			var mounts []Mount
   97 			staticDirs := getStaticDirs(cfg)
   98 			if len(staticDirs) > 0 {
   99 				componentsConfigured[d.component] = true
  100 			}
  101 
  102 			for _, dir := range staticDirs {
  103 				mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: d.component})
  104 			}
  105 
  106 			return mounts
  107 
  108 		}
  109 
  110 		if cfg.IsSet(d.key) {
  111 			source := cfg.GetString(d.key)
  112 			componentsConfigured[d.component] = true
  113 
  114 			return []Mount{{
  115 				// No lang set for layouts etc.
  116 				Source: source,
  117 				Target: d.component,
  118 			}}
  119 		}
  120 
  121 		return nil
  122 	}
  123 
  124 	createMounts := func(d dirKeyComponent) []Mount {
  125 		var mounts []Mount
  126 		if d.multilingual {
  127 			if d.component == files.ComponentFolderContent {
  128 				seen := make(map[string]bool)
  129 				hasContentDir := false
  130 				for _, language := range languages {
  131 					if language.ContentDir != "" {
  132 						hasContentDir = true
  133 						break
  134 					}
  135 				}
  136 
  137 				if hasContentDir {
  138 					for _, language := range languages {
  139 						contentDir := language.ContentDir
  140 						if contentDir == "" {
  141 							contentDir = files.ComponentFolderContent
  142 						}
  143 						if contentDir == "" || seen[contentDir] {
  144 							continue
  145 						}
  146 						seen[contentDir] = true
  147 						mounts = append(mounts, Mount{Lang: language.Lang, Source: contentDir, Target: d.component})
  148 					}
  149 				}
  150 
  151 				componentsConfigured[d.component] = len(seen) > 0
  152 
  153 			} else {
  154 				for _, language := range languages {
  155 					mounts = append(mounts, createMountsFor(d, language)...)
  156 				}
  157 			}
  158 		} else {
  159 			mounts = append(mounts, createMountsFor(d, cfg)...)
  160 		}
  161 
  162 		return mounts
  163 	}
  164 
  165 	var mounts []Mount
  166 	for _, dirKey := range dirKeys {
  167 		if componentsConfigured[dirKey.component] {
  168 			continue
  169 		}
  170 
  171 		mounts = append(mounts, createMounts(dirKey)...)
  172 
  173 	}
  174 
  175 	// Add default configuration
  176 	for _, dirKey := range dirKeys {
  177 		if componentsConfigured[dirKey.component] {
  178 			continue
  179 		}
  180 		mounts = append(mounts, Mount{Source: dirKey.component, Target: dirKey.component})
  181 	}
  182 
  183 	// Prepend the mounts from configuration.
  184 	mounts = append(moda.mounts, mounts...)
  185 
  186 	moda.mounts = mounts
  187 
  188 	return nil
  189 }
  190 
  191 // DecodeConfig creates a modules Config from a given Hugo configuration.
  192 func DecodeConfig(cfg config.Provider) (Config, error) {
  193 	return decodeConfig(cfg, nil)
  194 }
  195 
  196 func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Config, error) {
  197 	c := DefaultModuleConfig
  198 	c.replacementsMap = pathReplacements
  199 
  200 	if cfg == nil {
  201 		return c, nil
  202 	}
  203 
  204 	themeSet := cfg.IsSet("theme")
  205 	moduleSet := cfg.IsSet("module")
  206 
  207 	if moduleSet {
  208 		m := cfg.GetStringMap("module")
  209 		if err := mapstructure.WeakDecode(m, &c); err != nil {
  210 			return c, err
  211 		}
  212 
  213 		if c.replacementsMap == nil {
  214 
  215 			if len(c.Replacements) == 1 {
  216 				c.Replacements = strings.Split(c.Replacements[0], ",")
  217 			}
  218 
  219 			for i, repl := range c.Replacements {
  220 				c.Replacements[i] = strings.TrimSpace(repl)
  221 			}
  222 
  223 			c.replacementsMap = make(map[string]string)
  224 			for _, repl := range c.Replacements {
  225 				parts := strings.Split(repl, "->")
  226 				if len(parts) != 2 {
  227 					return c, fmt.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl)
  228 				}
  229 
  230 				c.replacementsMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
  231 			}
  232 		}
  233 
  234 		if c.replacementsMap != nil && c.Imports != nil {
  235 			for i, imp := range c.Imports {
  236 				if newImp, found := c.replacementsMap[imp.Path]; found {
  237 					imp.Path = newImp
  238 					imp.pathProjectReplaced = true
  239 					c.Imports[i] = imp
  240 				}
  241 			}
  242 		}
  243 
  244 		for i, mnt := range c.Mounts {
  245 			mnt.Source = filepath.Clean(mnt.Source)
  246 			mnt.Target = filepath.Clean(mnt.Target)
  247 			c.Mounts[i] = mnt
  248 		}
  249 
  250 	}
  251 
  252 	if themeSet {
  253 		imports := config.GetStringSlicePreserveString(cfg, "theme")
  254 		for _, imp := range imports {
  255 			c.Imports = append(c.Imports, Import{
  256 				Path: imp,
  257 			})
  258 		}
  259 
  260 	}
  261 
  262 	return c, nil
  263 }
  264 
  265 // Config holds a module config.
  266 type Config struct {
  267 	Mounts  []Mount
  268 	Imports []Import
  269 
  270 	// Meta info about this module (license information etc.).
  271 	Params map[string]any
  272 
  273 	// Will be validated against the running Hugo version.
  274 	HugoVersion HugoVersion
  275 
  276 	// A optional Glob pattern matching module paths to skip when vendoring, e.g.
  277 	// "github.com/**".
  278 	NoVendor string
  279 
  280 	// When enabled, we will pick the vendored module closest to the module
  281 	// using it.
  282 	// The default behaviour is to pick the first.
  283 	// Note that there can still be only one dependency of a given module path,
  284 	// so once it is in use it cannot be redefined.
  285 	VendorClosest bool
  286 
  287 	Replacements    []string
  288 	replacementsMap map[string]string
  289 
  290 	// Configures GOPROXY.
  291 	Proxy string
  292 	// Configures GONOPROXY.
  293 	NoProxy string
  294 	// Configures GOPRIVATE.
  295 	Private string
  296 
  297 	// Set the workspace file to use, e.g. hugo.work.
  298 	// Enables Go "Workspace" mode.
  299 	// Requires Go 1.18+
  300 	// See https://tip.golang.org/doc/go1.18
  301 	Workspace string
  302 }
  303 
  304 // hasModuleImport reports whether the project config have one or more
  305 // modules imports, e.g. github.com/bep/myshortcodes.
  306 func (c Config) hasModuleImport() bool {
  307 	for _, imp := range c.Imports {
  308 		if isProbablyModule(imp.Path) {
  309 			return true
  310 		}
  311 	}
  312 	return false
  313 }
  314 
  315 // HugoVersion holds Hugo binary version requirements for a module.
  316 type HugoVersion struct {
  317 	// The minimum Hugo version that this module works with.
  318 	Min hugo.VersionString
  319 
  320 	// The maximum Hugo version that this module works with.
  321 	Max hugo.VersionString
  322 
  323 	// Set if the extended version is needed.
  324 	Extended bool
  325 }
  326 
  327 func (v HugoVersion) String() string {
  328 	extended := ""
  329 	if v.Extended {
  330 		extended = " extended"
  331 	}
  332 
  333 	if v.Min != "" && v.Max != "" {
  334 		return fmt.Sprintf("%s/%s%s", v.Min, v.Max, extended)
  335 	}
  336 
  337 	if v.Min != "" {
  338 		return fmt.Sprintf("Min %s%s", v.Min, extended)
  339 	}
  340 
  341 	if v.Max != "" {
  342 		return fmt.Sprintf("Max %s%s", v.Max, extended)
  343 	}
  344 
  345 	return extended
  346 }
  347 
  348 // IsValid reports whether this version is valid compared to the running
  349 // Hugo binary.
  350 func (v HugoVersion) IsValid() bool {
  351 	current := hugo.CurrentVersion.Version()
  352 	if v.Extended && !hugo.IsExtended {
  353 		return false
  354 	}
  355 
  356 	isValid := true
  357 
  358 	if v.Min != "" && current.Compare(v.Min) > 0 {
  359 		isValid = false
  360 	}
  361 
  362 	if v.Max != "" && current.Compare(v.Max) < 0 {
  363 		isValid = false
  364 	}
  365 
  366 	return isValid
  367 }
  368 
  369 type Import struct {
  370 	Path                string // Module path
  371 	pathProjectReplaced bool   // Set when Path is replaced in project config.
  372 	IgnoreConfig        bool   // Ignore any config in config.toml (will still follow imports).
  373 	IgnoreImports       bool   // Do not follow any configured imports.
  374 	NoMounts            bool   // Do not mount any folder in this import.
  375 	NoVendor            bool   // Never vendor this import (only allowed in main project).
  376 	Disable             bool   // Turn off this module.
  377 	Mounts              []Mount
  378 }
  379 
  380 type Mount struct {
  381 	Source string // relative path in source repo, e.g. "scss"
  382 	Target string // relative target path, e.g. "assets/bootstrap/scss"
  383 
  384 	Lang string // any language code associated with this mount.
  385 
  386 	// Include only files matching the given Glob patterns (string or slice).
  387 	IncludeFiles any
  388 
  389 	// Exclude all files matching the given Glob patterns (string or slice).
  390 	ExcludeFiles any
  391 }
  392 
  393 // Used as key to remove duplicates.
  394 func (m Mount) key() string {
  395 	return strings.Join([]string{m.Lang, m.Source, m.Target}, "/")
  396 }
  397 
  398 func (m Mount) Component() string {
  399 	return strings.Split(m.Target, fileSeparator)[0]
  400 }
  401 
  402 func (m Mount) ComponentAndName() (string, string) {
  403 	c, n, _ := strings.Cut(m.Target, fileSeparator)
  404 	return c, n
  405 }
  406 
  407 func getStaticDirs(cfg config.Provider) []string {
  408 	var staticDirs []string
  409 	for i := -1; i <= 10; i++ {
  410 		staticDirs = append(staticDirs, getStringOrStringSlice(cfg, "staticDir", i)...)
  411 	}
  412 	return staticDirs
  413 }
  414 
  415 func getStringOrStringSlice(cfg config.Provider, key string, id int) []string {
  416 	if id >= 0 {
  417 		key = fmt.Sprintf("%s%d", key, id)
  418 	}
  419 
  420 	return config.GetStringSlicePreserveString(cfg, key)
  421 }