config.go - hugo - [fork] hugo port for 9front
HTML git clone https://git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
config.go (12053B)
---
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 "os"
19 "path/filepath"
20 "strings"
21
22 "github.com/gohugoio/hugo/common/hugo"
23 "github.com/gohugoio/hugo/hugofs/files"
24
25 "github.com/gohugoio/hugo/config"
26 "github.com/mitchellh/mapstructure"
27 )
28
29 const WorkspaceDisabled = "off"
30
31 var DefaultModuleConfig = Config{
32 // Default to direct, which means "git clone" and similar. We
33 // will investigate proxy settings in more depth later.
34 // See https://github.com/golang/go/issues/26334
35 Proxy: "direct",
36
37 // Comma separated glob list matching paths that should not use the
38 // proxy configured above.
39 NoProxy: "none",
40
41 // Comma separated glob list matching paths that should be
42 // treated as private.
43 Private: "*.*",
44
45 // Default is no workspace resolution.
46 Workspace: WorkspaceDisabled,
47
48 // A list of replacement directives mapping a module path to a directory
49 // or a theme component in the themes folder.
50 // Note that this will turn the component into a traditional theme component
51 // that does not partake in vendoring etc.
52 // The syntax is the similar to the replacement directives used in go.mod, e.g:
53 // github.com/mod1 -> ../mod1,github.com/mod2 -> ../mod2
54 Replacements: nil,
55 }
56
57 // ApplyProjectConfigDefaults applies default/missing module configuration for
58 // the main project.
59 func ApplyProjectConfigDefaults(mod Module, cfgs ...config.AllProvider) error {
60 moda := mod.(*moduleAdapter)
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 var mounts []Mount
73
74 for _, component := range []string{
75 files.ComponentFolderContent,
76 files.ComponentFolderData,
77 files.ComponentFolderLayouts,
78 files.ComponentFolderI18n,
79 files.ComponentFolderArchetypes,
80 files.ComponentFolderAssets,
81 files.ComponentFolderStatic,
82 } {
83 if componentsConfigured[component] {
84 continue
85 }
86
87 first := cfgs[0]
88 dirsBase := first.DirsBase()
89 isMultihost := first.IsMultihost()
90
91 for i, cfg := range cfgs {
92 dirs := cfg.Dirs()
93 var dir string
94 var dropLang bool
95 switch component {
96 case files.ComponentFolderContent:
97 dir = dirs.ContentDir
98 dropLang = dir == dirsBase.ContentDir
99 case files.ComponentFolderData:
100 //lint:ignore SA1019 Keep as adapter for now.
101 dir = dirs.DataDir
102 case files.ComponentFolderLayouts:
103 //lint:ignore SA1019 Keep as adapter for now.
104 dir = dirs.LayoutDir
105 case files.ComponentFolderI18n:
106 //lint:ignore SA1019 Keep as adapter for now.
107 dir = dirs.I18nDir
108 case files.ComponentFolderArchetypes:
109 //lint:ignore SA1019 Keep as adapter for now.
110 dir = dirs.ArcheTypeDir
111 case files.ComponentFolderAssets:
112 //lint:ignore SA1019 Keep as adapter for now.
113 dir = dirs.AssetDir
114 case files.ComponentFolderStatic:
115 // For static dirs, we only care about the language in multihost setups.
116 dropLang = !isMultihost
117 }
118
119 var perLang bool
120 switch component {
121 case files.ComponentFolderContent, files.ComponentFolderStatic:
122 perLang = true
123 default:
124 }
125 if i > 0 && !perLang {
126 continue
127 }
128
129 var lang string
130 if perLang && !dropLang {
131 lang = cfg.Language().Lang
132 }
133
134 // Static mounts are a little special.
135 if component == files.ComponentFolderStatic {
136 staticDirs := cfg.StaticDirs()
137 for _, dir := range staticDirs {
138 mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: component})
139 }
140 continue
141 }
142
143 if dir != "" {
144 mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: component})
145 }
146 }
147 }
148
149 moda.mounts = append(moda.mounts, mounts...)
150
151 // Temporary: Remove duplicates.
152 seen := make(map[string]bool)
153 var newMounts []Mount
154 for _, m := range moda.mounts {
155 key := m.Source + m.Target + m.Lang
156 if seen[key] {
157 continue
158 }
159 seen[key] = true
160 newMounts = append(newMounts, m)
161 }
162 moda.mounts = newMounts
163
164 return nil
165 }
166
167 // DecodeConfig creates a modules Config from a given Hugo configuration.
168 func DecodeConfig(cfg config.Provider) (Config, error) {
169 return decodeConfig(cfg, nil)
170 }
171
172 func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Config, error) {
173 c := DefaultModuleConfig
174 c.replacementsMap = pathReplacements
175
176 if cfg == nil {
177 return c, nil
178 }
179
180 themeSet := cfg.IsSet("theme")
181 moduleSet := cfg.IsSet("module")
182
183 if moduleSet {
184 m := cfg.GetStringMap("module")
185 if err := mapstructure.WeakDecode(m, &c); err != nil {
186 return c, err
187 }
188
189 if c.replacementsMap == nil {
190
191 if len(c.Replacements) == 1 {
192 c.Replacements = strings.Split(c.Replacements[0], ",")
193 }
194
195 for i, repl := range c.Replacements {
196 c.Replacements[i] = strings.TrimSpace(repl)
197 }
198
199 c.replacementsMap = make(map[string]string)
200 for _, repl := range c.Replacements {
201 parts := strings.Split(repl, "->")
202 if len(parts) != 2 {
203 return c, fmt.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl)
204 }
205
206 c.replacementsMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
207 }
208 }
209
210 if c.replacementsMap != nil && c.Imports != nil {
211 for i, imp := range c.Imports {
212 if newImp, found := c.replacementsMap[imp.Path]; found {
213 imp.Path = newImp
214 imp.pathProjectReplaced = true
215 c.Imports[i] = imp
216 }
217 }
218 }
219
220 for i, mnt := range c.Mounts {
221 mnt.Source = filepath.Clean(mnt.Source)
222 mnt.Target = filepath.Clean(mnt.Target)
223 c.Mounts[i] = mnt
224 }
225
226 if c.Workspace == "" {
227 c.Workspace = WorkspaceDisabled
228 }
229 if c.Workspace != WorkspaceDisabled {
230 c.Workspace = filepath.Clean(c.Workspace)
231 if !filepath.IsAbs(c.Workspace) {
232 workingDir := cfg.GetString("workingDir")
233 c.Workspace = filepath.Join(workingDir, c.Workspace)
234 }
235 if _, err := os.Stat(c.Workspace); err != nil {
236 //lint:ignore ST1005 end user message.
237 return c, fmt.Errorf("module workspace %q does not exist. Check your module.workspace setting (or HUGO_MODULE_WORKSPACE env var).", c.Workspace)
238 }
239 }
240 }
241
242 if themeSet {
243 imports := config.GetStringSlicePreserveString(cfg, "theme")
244 for _, imp := range imports {
245 c.Imports = append(c.Imports, Import{
246 Path: imp,
247 })
248 }
249 }
250
251 return c, nil
252 }
253
254 // Config holds a module config.
255 type Config struct {
256 // File system mounts.
257 Mounts []Mount
258
259 // Module imports.
260 Imports []Import
261
262 // Meta info about this module (license information etc.).
263 Params map[string]any
264
265 // Will be validated against the running Hugo version.
266 HugoVersion HugoVersion
267
268 // Optional Glob pattern matching module paths to skip when vendoring, e.g. “github.com/**”
269 NoVendor string
270
271 // When enabled, we will pick the vendored module closest to the module
272 // using it.
273 // The default behavior is to pick the first.
274 // Note that there can still be only one dependency of a given module path,
275 // so once it is in use it cannot be redefined.
276 VendorClosest bool
277
278 // A comma separated (or a slice) list of module path to directory replacement mapping,
279 // e.g. github.com/bep/my-theme -> ../..,github.com/bep/shortcodes -> /some/path.
280 // This is mostly useful for temporary locally development of a module, and then it makes sense to set it as an
281 // OS environment variable, e.g: env HUGO_MODULE_REPLACEMENTS="github.com/bep/my-theme -> ../..".
282 // Any relative path is relate to themesDir, and absolute paths are allowed.
283 Replacements []string
284 replacementsMap map[string]string
285
286 // Defines the proxy server to use to download remote modules. Default is direct, which means “git clone” and similar.
287 // Configures GOPROXY when running the Go command for module operations.
288 Proxy string
289
290 // Comma separated glob list matching paths that should not use the proxy configured above.
291 // Configures GONOPROXY when running the Go command for module operations.
292 NoProxy string
293
294 // Comma separated glob list matching paths that should be treated as private.
295 // Configures GOPRIVATE when running the Go command for module operations.
296 Private string
297
298 // Configures GOAUTH when running the Go command for module operations.
299 // This is a semicolon-separated list of authentication commands for go-import and HTTPS module mirror interactions.
300 // This is useful for private repositories.
301 // See `go help goauth` for more information.
302 Auth string
303
304 // Defaults to "off".
305 // Set to a work file, e.g. hugo.work, to enable Go "Workspace" mode.
306 // Can be relative to the working directory or absolute.
307 // Requires Go 1.18+.
308 // Note that this can also be set via OS env, e.g. export HUGO_MODULE_WORKSPACE=/my/hugo.work.
309 Workspace string
310 }
311
312 // hasModuleImport reports whether the project config have one or more
313 // modules imports, e.g. github.com/bep/myshortcodes.
314 func (c Config) hasModuleImport() bool {
315 for _, imp := range c.Imports {
316 if isProbablyModule(imp.Path) {
317 return true
318 }
319 }
320 return false
321 }
322
323 // HugoVersion holds Hugo binary version requirements for a module.
324 type HugoVersion struct {
325 // The minimum Hugo version that this module works with.
326 Min hugo.VersionString
327
328 // The maximum Hugo version that this module works with.
329 Max hugo.VersionString
330
331 // Set if the extended version is needed.
332 Extended bool
333 }
334
335 func (v HugoVersion) String() string {
336 extended := ""
337 if v.Extended {
338 extended = " extended"
339 }
340
341 if v.Min != "" && v.Max != "" {
342 return fmt.Sprintf("%s/%s%s", v.Min, v.Max, extended)
343 }
344
345 if v.Min != "" {
346 return fmt.Sprintf("Min %s%s", v.Min, extended)
347 }
348
349 if v.Max != "" {
350 return fmt.Sprintf("Max %s%s", v.Max, extended)
351 }
352
353 return extended
354 }
355
356 // IsValid reports whether this version is valid compared to the running
357 // Hugo binary.
358 func (v HugoVersion) IsValid() bool {
359 current := hugo.CurrentVersion.Version()
360 if v.Extended && !hugo.IsExtended {
361 return false
362 }
363
364 isValid := true
365
366 if v.Min != "" && current.Compare(v.Min) > 0 {
367 isValid = false
368 }
369
370 if v.Max != "" && current.Compare(v.Max) < 0 {
371 isValid = false
372 }
373
374 return isValid
375 }
376
377 type Import struct {
378 // Module path
379 Path string
380 // Set when Path is replaced in project config.
381 pathProjectReplaced bool
382 // Ignore any config in config.toml (will still follow imports).
383 IgnoreConfig bool
384 // Do not follow any configured imports.
385 IgnoreImports bool
386 // Do not mount any folder in this import.
387 NoMounts bool
388 // Never vendor this import (only allowed in main project).
389 NoVendor bool
390 // Turn off this module.
391 Disable bool
392 // File mounts.
393 Mounts []Mount
394 }
395
396 type Mount struct {
397 // Relative path in source repo, e.g. "scss".
398 Source string
399
400 // Relative target path, e.g. "assets/bootstrap/scss".
401 Target string
402
403 // Any file in this mount will be associated with this language.
404 Lang string
405
406 // Include only files matching the given Glob patterns (string or slice).
407 IncludeFiles any
408
409 // Exclude all files matching the given Glob patterns (string or slice).
410 ExcludeFiles any
411
412 // Disable watching in watch mode for this mount.
413 DisableWatch bool
414 }
415
416 // Used as key to remove duplicates.
417 func (m Mount) key() string {
418 return strings.Join([]string{m.Lang, m.Source, m.Target}, "/")
419 }
420
421 func (m Mount) Component() string {
422 return strings.Split(m.Target, fileSeparator)[0]
423 }
424
425 func (m Mount) ComponentAndName() (string, string) {
426 c, n, _ := strings.Cut(m.Target, fileSeparator)
427 return c, n
428 }