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