github.com/neohugo/neohugo@v0.123.8/config/allconfig/alldecoders.go (about)

     1  // Copyright 2024 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 allconfig
    15  
    16  import (
    17  	"fmt"
    18  	"strings"
    19  
    20  	"github.com/neohugo/neohugo/cache/filecache"
    21  	"github.com/neohugo/neohugo/common/maps"
    22  	"github.com/neohugo/neohugo/common/types"
    23  	"github.com/neohugo/neohugo/config"
    24  	"github.com/neohugo/neohugo/config/privacy"
    25  	"github.com/neohugo/neohugo/config/security"
    26  	"github.com/neohugo/neohugo/config/services"
    27  	"github.com/neohugo/neohugo/deploy/deployconfig"
    28  	"github.com/neohugo/neohugo/langs"
    29  	"github.com/neohugo/neohugo/markup/markup_config"
    30  	"github.com/neohugo/neohugo/media"
    31  	"github.com/neohugo/neohugo/minifiers"
    32  	"github.com/neohugo/neohugo/modules"
    33  	"github.com/neohugo/neohugo/navigation"
    34  	"github.com/neohugo/neohugo/output"
    35  	"github.com/neohugo/neohugo/related"
    36  	"github.com/neohugo/neohugo/resources/images"
    37  	"github.com/neohugo/neohugo/resources/page"
    38  	"github.com/neohugo/neohugo/resources/page/pagemeta"
    39  
    40  	"github.com/mitchellh/mapstructure"
    41  	"github.com/spf13/afero"
    42  	"github.com/spf13/cast"
    43  )
    44  
    45  type decodeConfig struct {
    46  	p    config.Provider
    47  	c    *Config
    48  	fs   afero.Fs
    49  	bcfg config.BaseConfig
    50  }
    51  
    52  type decodeWeight struct {
    53  	key                  string
    54  	decode               func(decodeWeight, decodeConfig) error
    55  	getCompiler          func(c *Config) configCompiler
    56  	weight               int
    57  	internalOrDeprecated bool // Hide it from the docs.
    58  }
    59  
    60  var allDecoderSetups = map[string]decodeWeight{
    61  	"": {
    62  		key:    "",
    63  		weight: -100, // Always first.
    64  		decode: func(d decodeWeight, p decodeConfig) error {
    65  			if err := mapstructure.WeakDecode(p.p.Get(""), &p.c.RootConfig); err != nil {
    66  				return err
    67  			}
    68  
    69  			// This need to match with Lang which is always lower case.
    70  			p.c.RootConfig.DefaultContentLanguage = strings.ToLower(p.c.RootConfig.DefaultContentLanguage)
    71  
    72  			return nil
    73  		},
    74  	},
    75  	"imaging": {
    76  		key: "imaging",
    77  		decode: func(d decodeWeight, p decodeConfig) error {
    78  			var err error
    79  			p.c.Imaging, err = images.DecodeConfig(p.p.GetStringMap(d.key))
    80  			return err
    81  		},
    82  	},
    83  	"caches": {
    84  		key: "caches",
    85  		decode: func(d decodeWeight, p decodeConfig) error {
    86  			var err error
    87  			p.c.Caches, err = filecache.DecodeConfig(p.fs, p.bcfg, p.p.GetStringMap(d.key))
    88  			if p.c.IgnoreCache {
    89  				// Set MaxAge in all caches to 0.
    90  				for k, cache := range p.c.Caches {
    91  					cache.MaxAge = 0
    92  					p.c.Caches[k] = cache
    93  				}
    94  			}
    95  			return err
    96  		},
    97  	},
    98  	"build": {
    99  		key: "build",
   100  		decode: func(d decodeWeight, p decodeConfig) error {
   101  			p.c.Build = config.DecodeBuildConfig(p.p)
   102  			return nil
   103  		},
   104  		getCompiler: func(c *Config) configCompiler {
   105  			return &c.Build
   106  		},
   107  	},
   108  	"frontmatter": {
   109  		key: "frontmatter",
   110  		decode: func(d decodeWeight, p decodeConfig) error {
   111  			var err error
   112  			p.c.Frontmatter, err = pagemeta.DecodeFrontMatterConfig(p.p)
   113  			return err
   114  		},
   115  	},
   116  	"markup": {
   117  		key: "markup",
   118  		decode: func(d decodeWeight, p decodeConfig) error {
   119  			var err error
   120  			p.c.Markup, err = markup_config.Decode(p.p)
   121  			return err
   122  		},
   123  	},
   124  	"server": {
   125  		key: "server",
   126  		decode: func(d decodeWeight, p decodeConfig) error {
   127  			var err error
   128  			p.c.Server, err = config.DecodeServer(p.p)
   129  			return err
   130  		},
   131  		getCompiler: func(c *Config) configCompiler {
   132  			return &c.Server
   133  		},
   134  	},
   135  	"minify": {
   136  		key: "minify",
   137  		decode: func(d decodeWeight, p decodeConfig) error {
   138  			var err error
   139  			p.c.Minify, err = minifiers.DecodeConfig(p.p.Get(d.key))
   140  			return err
   141  		},
   142  	},
   143  	"mediatypes": {
   144  		key: "mediatypes",
   145  		decode: func(d decodeWeight, p decodeConfig) error {
   146  			var err error
   147  			p.c.MediaTypes, err = media.DecodeTypes(p.p.GetStringMap(d.key))
   148  			return err
   149  		},
   150  	},
   151  	"outputs": {
   152  		key: "outputs",
   153  		decode: func(d decodeWeight, p decodeConfig) error {
   154  			defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
   155  			m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
   156  			p.c.Outputs = make(map[string][]string)
   157  			for k, v := range m {
   158  				s := types.ToStringSlicePreserveString(v)
   159  				for i, v := range s {
   160  					s[i] = strings.ToLower(v)
   161  				}
   162  				p.c.Outputs[k] = s
   163  			}
   164  			// Apply defaults.
   165  			for k, v := range defaults {
   166  				if _, found := p.c.Outputs[k]; !found {
   167  					p.c.Outputs[k] = v
   168  				}
   169  			}
   170  			return nil
   171  		},
   172  	},
   173  	"outputformats": {
   174  		key: "outputformats",
   175  		decode: func(d decodeWeight, p decodeConfig) error {
   176  			var err error
   177  			p.c.OutputFormats, err = output.DecodeConfig(p.c.MediaTypes.Config, p.p.Get(d.key))
   178  			return err
   179  		},
   180  	},
   181  	"params": {
   182  		key: "params",
   183  		decode: func(d decodeWeight, p decodeConfig) error {
   184  			p.c.Params = maps.CleanConfigStringMap(p.p.GetStringMap("params"))
   185  			if p.c.Params == nil {
   186  				p.c.Params = make(map[string]any)
   187  			}
   188  
   189  			// Before Hugo 0.112.0 this was configured via site Params.
   190  			if mainSections, found := p.c.Params["mainsections"]; found {
   191  				p.c.MainSections = types.ToStringSlicePreserveString(mainSections)
   192  				if p.c.MainSections == nil {
   193  					p.c.MainSections = []string{}
   194  				}
   195  			}
   196  
   197  			return nil
   198  		},
   199  	},
   200  	"module": {
   201  		key: "module",
   202  		decode: func(d decodeWeight, p decodeConfig) error {
   203  			var err error
   204  			p.c.Module, err = modules.DecodeConfig(p.p)
   205  			return err
   206  		},
   207  	},
   208  	"permalinks": {
   209  		key: "permalinks",
   210  		decode: func(d decodeWeight, p decodeConfig) error {
   211  			var err error
   212  			p.c.Permalinks, err = page.DecodePermalinksConfig(p.p.GetStringMap(d.key))
   213  			return err
   214  		},
   215  	},
   216  	"sitemap": {
   217  		key: "sitemap",
   218  		decode: func(d decodeWeight, p decodeConfig) error {
   219  			var err error
   220  			p.c.Sitemap, err = config.DecodeSitemap(config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"}, p.p.GetStringMap(d.key))
   221  			return err
   222  		},
   223  	},
   224  	"taxonomies": {
   225  		key: "taxonomies",
   226  		decode: func(d decodeWeight, p decodeConfig) error {
   227  			p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
   228  			return nil
   229  		},
   230  	},
   231  	"related": {
   232  		key:    "related",
   233  		weight: 100, // This needs to be decoded after taxonomies.
   234  		decode: func(d decodeWeight, p decodeConfig) error {
   235  			if p.p.IsSet(d.key) {
   236  				var err error
   237  				p.c.Related, err = related.DecodeConfig(p.p.GetParams(d.key))
   238  				if err != nil {
   239  					return fmt.Errorf("failed to decode related config: %w", err)
   240  				}
   241  			} else {
   242  				p.c.Related = related.DefaultConfig
   243  				if _, found := p.c.Taxonomies["tag"]; found {
   244  					p.c.Related.Add(related.IndexConfig{Name: "tags", Weight: 80, Type: related.TypeBasic})
   245  				}
   246  			}
   247  			return nil
   248  		},
   249  	},
   250  	"languages": {
   251  		key: "languages",
   252  		decode: func(d decodeWeight, p decodeConfig) error {
   253  			var err error
   254  			m := p.p.GetStringMap(d.key)
   255  			if len(m) == 1 {
   256  				// In v0.112.4 we moved this to the language config, but it's very commmon for mono language sites to have this at the top level.
   257  				var first maps.Params
   258  				var ok bool
   259  				for _, v := range m {
   260  					first, ok = v.(maps.Params)
   261  					if ok {
   262  						break
   263  					}
   264  				}
   265  				if first != nil {
   266  					if _, found := first["languagecode"]; !found {
   267  						first["languagecode"] = p.p.GetString("languagecode")
   268  					}
   269  				}
   270  			}
   271  			p.c.Languages, err = langs.DecodeConfig(m)
   272  			if err != nil {
   273  				return err
   274  			}
   275  
   276  			// Validate defaultContentLanguage.
   277  			var found bool
   278  			for lang := range p.c.Languages {
   279  				if lang == p.c.DefaultContentLanguage {
   280  					found = true
   281  					break
   282  				}
   283  			}
   284  			if !found {
   285  				return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
   286  			}
   287  
   288  			return nil
   289  		},
   290  	},
   291  	"cascade": {
   292  		key: "cascade",
   293  		decode: func(d decodeWeight, p decodeConfig) error {
   294  			var err error
   295  			p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
   296  			return err
   297  		},
   298  	},
   299  	"menus": {
   300  		key: "menus",
   301  		decode: func(d decodeWeight, p decodeConfig) error {
   302  			var err error
   303  			p.c.Menus, err = navigation.DecodeConfig(p.p.Get(d.key))
   304  			return err
   305  		},
   306  	},
   307  	"privacy": {
   308  		key: "privacy",
   309  		decode: func(d decodeWeight, p decodeConfig) error {
   310  			var err error
   311  			p.c.Privacy, err = privacy.DecodeConfig(p.p)
   312  			return err
   313  		},
   314  	},
   315  	"security": {
   316  		key: "security",
   317  		decode: func(d decodeWeight, p decodeConfig) error {
   318  			var err error
   319  			p.c.Security, err = security.DecodeConfig(p.p)
   320  			return err
   321  		},
   322  	},
   323  	"services": {
   324  		key: "services",
   325  		decode: func(d decodeWeight, p decodeConfig) error {
   326  			var err error
   327  			p.c.Services, err = services.DecodeConfig(p.p)
   328  			return err
   329  		},
   330  	},
   331  	"deployment": {
   332  		key: "deployment",
   333  		decode: func(d decodeWeight, p decodeConfig) error {
   334  			var err error
   335  			p.c.Deployment, err = deployconfig.DecodeConfig(p.p)
   336  			return err
   337  		},
   338  	},
   339  	"author": {
   340  		key: "author",
   341  		decode: func(d decodeWeight, p decodeConfig) error {
   342  			p.c.Author = maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
   343  			return nil
   344  		},
   345  		internalOrDeprecated: true,
   346  	},
   347  	"social": {
   348  		key: "social",
   349  		decode: func(d decodeWeight, p decodeConfig) error {
   350  			p.c.Social = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
   351  			return nil
   352  		},
   353  		internalOrDeprecated: true,
   354  	},
   355  	"uglyurls": {
   356  		key: "uglyurls",
   357  		decode: func(d decodeWeight, p decodeConfig) error {
   358  			v := p.p.Get(d.key)
   359  			switch vv := v.(type) {
   360  			case bool:
   361  				p.c.UglyURLs = vv
   362  			case string:
   363  				p.c.UglyURLs = vv == "true"
   364  			default:
   365  				p.c.UglyURLs = cast.ToStringMapBool(v)
   366  			}
   367  			return nil
   368  		},
   369  		internalOrDeprecated: true,
   370  	},
   371  	"internal": {
   372  		key: "internal",
   373  		decode: func(d decodeWeight, p decodeConfig) error {
   374  			return mapstructure.WeakDecode(p.p.GetStringMap(d.key), &p.c.Internal)
   375  		},
   376  		internalOrDeprecated: true,
   377  	},
   378  }
   379  
   380  func init() {
   381  	for k, v := range allDecoderSetups {
   382  		// Verify that k and v.key is all lower case.
   383  		if k != strings.ToLower(k) {
   384  			panic(fmt.Sprintf("key %q is not lower case", k))
   385  		}
   386  		if v.key != strings.ToLower(v.key) {
   387  			panic(fmt.Sprintf("key %q is not lower case", v.key))
   388  		}
   389  
   390  		if k != v.key {
   391  			panic(fmt.Sprintf("key %q is not the same as the map key %q", k, v.key))
   392  		}
   393  	}
   394  }