Repository: gohugoio/hugo (language: go)

Issue:
Make hugo.toml the new config.toml
So, the naming of Hugo's default config file `config.toml` (and json ...) was a mistake. The reasoning:

* It's very generic, so there are potential naming conflicts in multi-framework setups
* It's very generic, so it cannot be used for discoverability (_that_ is a Hugo project)

I have looked at this before and just brushed it off with a "there are too many projects out there", but I now think we should just do it:

* So, `config.toml` will still be a fall back, but we first try `hugo.toml`.
* We may, some time in the future, add some warnings to the `config.toml` case, but that is not in scope for now.
* I also suggest, as part of this, that we make having a `hugo.toml` (or JSON ...) eventually be a requirement for any Hugo project (even if it's empty).
* 


Discussion / hints:
If `ROOT/config.EXTENSION` is replaced with `ROOT/hugo.EXTENSION`, I wonder if any of the following should be changed for consistency:

* Should `configDir` be renamed `hugoDir`?
* Should `ROOT/config/_default/hugo.EXTENSION` be renamed `ROOT/hugo/_default/hugo.EXTENSION`?
* Should `--config` switch be renamed `--hugo` switch?

I don't have an opinion about this, but it's something to think about.

Thanks for all your great work bep and others!
@nancym, it's still the hugo config.  The goal is to make the file name less ambiguous in the context of a generic filesystem.  The `--config` option is scoped to the hugo command, so there's no real ambiguity there.

The `ROOT/config/` directory name has a similar ambiguity, so that's something to consider.  Even if that's changed, I'm not sure `configDir` needs to change, so it's scoped to the hugo config.
@moorereason is right. My primary motivation behind this is to get to a state where _every Hugo project has a hugo.* file). So, if you have a `config` dir, we will probably also enforce a (empty?) hugo.* file. There will be situations where people would want to name them differently (having multiple configs), so the options for `configDir` etc. still make sense.

Fix patch:
diff --git a/commands/commands.go b/commands/commands.go
index b81b867f9a0..c1042459d2f 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -177,7 +177,7 @@ Complete documentation is available at https://gohugo.io/.`,
 		},
 	})
 
-	cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)")
+	cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)")
 	cc.cmd.PersistentFlags().StringVar(&cc.cfgDir, "configDir", "config", "config dir")
 	cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode")
 
diff --git a/commands/new_site.go b/commands/new_site.go
index 438553ae7c0..fc4127f8b63 100644
--- a/commands/new_site.go
+++ b/commands/new_site.go
@@ -83,6 +83,7 @@ func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error
 			return errors.New(basepath + " already exists and is not empty. See --force.")
 
 		case !isEmpty && force:
+			// TODO(bep) eventually rename this to hugo.
 			all := append(dirs, filepath.Join(basepath, "config."+n.configFormat))
 			for _, path := range all {
 				if exists, _ := helpers.Exists(path, fs.Source); exists {
diff --git a/config/configLoader.go b/config/configLoader.go
index 6722c12fd73..95594fc62d2 100644
--- a/config/configLoader.go
+++ b/config/configLoader.go
@@ -29,11 +29,22 @@ import (
 )
 
 var (
+	// See issue #8979 for context.
+	// Hugo has always used config.toml etc. as the default config file name.
+	// But hugo.toml is a more descriptive name, but we need to check for both.
+	DefaultConfigNames = []string{"hugo", "config"}
+
+	DefaultConfigNamesSet = make(map[string]bool)
+
 	ValidConfigFileExtensions                    = []string{"toml", "yaml", "yml", "json"}
 	validConfigFileExtensionsMap map[string]bool = make(map[string]bool)
 )
 
 func init() {
+	for _, name := range DefaultConfigNames {
+		DefaultConfigNamesSet[name] = true
+	}
+
 	for _, ext := range ValidConfigFileExtensions {
 		validConfigFileExtensionsMap[ext] = true
 	}
@@ -142,8 +153,7 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
 			}
 
 			var keyPath []string
-
-			if name != "config" {
+			if !DefaultConfigNamesSet[name] {
 				// Can be params.jp, menus.en etc.
 				name, lang := paths.FileAndExtNoDelimiter(name)
 
diff --git a/hugolib/config.go b/hugolib/config.go
index 8e73a35ec12..059424e85fa 100644
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -69,18 +69,35 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
 	// use a partial configuration to do its job.
 	defer l.deleteMergeStrategies()
 
-	for _, name := range d.configFilenames() {
-		var filename string
-		filename, err := l.loadConfig(name)
-		if err == nil {
-			configFiles = append(configFiles, filename)
-		} else if err != ErrNoConfigFile {
-			return nil, nil, l.wrapFileError(err, filename)
+	names := d.configFilenames()
+
+	if names != nil {
+		for _, name := range names {
+			var filename string
+			filename, err := l.loadConfig(name)
+			if err == nil {
+				configFiles = append(configFiles, filename)
+			} else if err != ErrNoConfigFile {
+				return nil, nil, l.wrapFileError(err, filename)
+			}
+		}
+	} else {
+		for _, name := range config.DefaultConfigNames {
+			var filename string
+			filename, err := l.loadConfig(name)
+			if err == nil {
+				configFiles = append(configFiles, filename)
+				break
+			} else if err != ErrNoConfigFile {
+				return nil, nil, l.wrapFileError(err, filename)
+			}
 		}
 	}
 
 	if d.AbsConfigDir != "" {
+
 		dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, d.AbsConfigDir, l.Environment)
+
 		if err == nil {
 			if len(dirnames) > 0 {
 				l.cfg.Set("", dcfg.Get(""))
@@ -162,9 +179,9 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
 	return l.cfg, configFiles, err
 }
 
-// LoadConfigDefault is a convenience method to load the default "config.toml" config.
+// LoadConfigDefault is a convenience method to load the default "hugo.toml" config.
 func LoadConfigDefault(fs afero.Fs) (config.Provider, error) {
-	v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
+	v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs})
 	return v, err
 }
 
@@ -202,7 +219,7 @@ func (d ConfigSourceDescriptor) configFileDir() string {
 
 func (d ConfigSourceDescriptor) configFilenames() []string {
 	if d.Filename == "" {
-		return []string{"config"}
+		return nil
 	}
 	return strings.Split(d.Filename, ",")
 }
diff --git a/modules/collect.go b/modules/collect.go
index ae6df9be9b2..fcde1d37942 100644
--- a/modules/collect.go
+++ b/modules/collect.go
@@ -423,12 +423,14 @@ func (c *collector) applyThemeConfig(tc *moduleAdapter) error {
 		err            error
 	)
 
-	// Viper supports more, but this is the sub-set supported by Hugo.
-	for _, configFormats := range config.ValidConfigFileExtensions {
-		configFilename = filepath.Join(tc.Dir(), "config."+configFormats)
-		hasConfigFile, _ = afero.Exists(c.fs, configFilename)
-		if hasConfigFile {
-			break
+LOOP:
+	for _, configBaseName := range config.DefaultConfigNames {
+		for _, configFormats := range config.ValidConfigFileExtensions {
+			configFilename = filepath.Join(tc.Dir(), configBaseName+"."+configFormats)
+			hasConfigFile, _ = afero.Exists(c.fs, configFilename)
+			if hasConfigFile {
+				break LOOP
+			}
 		}
 	}
 


Test patch:
diff --git a/hugolib/config_test.go b/hugolib/config_test.go
index 882d83c8dd9..37605b4c266 100644
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -782,3 +782,55 @@ defaultMarkdownHandler = 'blackfriday'
 	b.Assert(err.Error(), qt.Contains, "Configured defaultMarkdownHandler \"blackfriday\" not found. Did you mean to use goldmark? Blackfriday was removed in Hugo v0.100.0.")
 
 }
+
+// Issue 8979
+func TestHugoConfig(t *testing.T) {
+	filesTemplate := `
+-- hugo.toml --
+theme = "mytheme"
+[params]
+rootparam = "rootvalue"
+-- config/_default/hugo.toml --
+[params]
+rootconfigparam = "rootconfigvalue"
+-- themes/mytheme/config/_default/hugo.toml --
+[params]
+themeconfigdirparam = "themeconfigdirvalue"
+-- themes/mytheme/hugo.toml --
+[params]
+themeparam = "themevalue"
+-- layouts/index.html --
+rootparam: {{ site.Params.rootparam }}
+rootconfigparam: {{ site.Params.rootconfigparam }}
+themeparam: {{ site.Params.themeparam }}
+themeconfigdirparam: {{ site.Params.themeconfigdirparam }}
+
+
+`
+
+	for _, configName := range []string{"hugo.toml", "config.toml"} {
+		configName := configName
+		t.Run(configName, func(t *testing.T) {
+			t.Parallel()
+
+			files := strings.ReplaceAll(filesTemplate, "hugo.toml", configName)
+
+			b, err := NewIntegrationTestBuilder(
+				IntegrationTestConfig{
+					T:           t,
+					TxtarString: files,
+				},
+			).BuildE()
+
+			b.Assert(err, qt.IsNil)
+			b.AssertFileContent("public/index.html",
+				"rootparam: rootvalue",
+				"rootconfigparam: rootconfigvalue",
+				"themeparam: themevalue",
+				"themeconfigdirparam: themeconfigdirvalue",
+			)
+
+		})
+	}
+
+}
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 9dcfe4830b6..5b457893d31 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -301,13 +301,18 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 			s.Assert(afero.WriteFile(afs, filename, data, 0666), qt.IsNil)
 		}
 
+		configDirFilename := filepath.Join(s.Cfg.WorkingDir, "config")
+		if _, err := afs.Stat(configDirFilename); err != nil {
+			configDirFilename = ""
+		}
+
 		cfg, _, err := LoadConfig(
 			ConfigSourceDescriptor{
-				WorkingDir: s.Cfg.WorkingDir,
-				Fs:         afs,
-				Logger:     logger,
-				Environ:    []string{},
-				Filename:   "config.toml",
+				WorkingDir:   s.Cfg.WorkingDir,
+				AbsConfigDir: configDirFilename,
+				Fs:           afs,
+				Logger:       logger,
+				Environ:      []string{},
 			},
 			func(cfg config.Provider) error {
 				return nil