github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/hugolib/config.go (about)

     1  // Copyright 2016-present 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 hugolib
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  
    20  	"github.com/gohugoio/hugo/hugolib/paths"
    21  
    22  	"io"
    23  	"strings"
    24  
    25  	"github.com/gohugoio/hugo/langs"
    26  
    27  	"github.com/gohugoio/hugo/config"
    28  	"github.com/gohugoio/hugo/config/privacy"
    29  	"github.com/gohugoio/hugo/config/services"
    30  	"github.com/gohugoio/hugo/helpers"
    31  	"github.com/spf13/afero"
    32  	"github.com/spf13/viper"
    33  )
    34  
    35  // SiteConfig represents the config in .Site.Config.
    36  type SiteConfig struct {
    37  	// This contains all privacy related settings that can be used to
    38  	// make the YouTube template etc. GDPR compliant.
    39  	Privacy privacy.Config
    40  
    41  	// Services contains config for services such as Google Analytics etc.
    42  	Services services.Config
    43  }
    44  
    45  func loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err error) {
    46  	privacyConfig, err := privacy.DecodeConfig(cfg)
    47  	if err != nil {
    48  		return
    49  	}
    50  
    51  	servicesConfig, err := services.DecodeConfig(cfg)
    52  	if err != nil {
    53  		return
    54  	}
    55  
    56  	scfg.Privacy = privacyConfig
    57  	scfg.Services = servicesConfig
    58  
    59  	return
    60  }
    61  
    62  // ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
    63  type ConfigSourceDescriptor struct {
    64  	Fs afero.Fs
    65  
    66  	// Full path to the config file to use, i.e. /my/project/config.toml
    67  	Filename string
    68  
    69  	// The path to the directory to look for configuration. Is used if Filename is not
    70  	// set.
    71  	Path string
    72  
    73  	// The project's working dir. Is used to look for additional theme config.
    74  	WorkingDir string
    75  }
    76  
    77  func (d ConfigSourceDescriptor) configFilenames() []string {
    78  	return strings.Split(d.Filename, ",")
    79  }
    80  
    81  // LoadConfigDefault is a convenience method to load the default "config.toml" config.
    82  func LoadConfigDefault(fs afero.Fs) (*viper.Viper, error) {
    83  	v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs, Filename: "config.toml"})
    84  	return v, err
    85  }
    86  
    87  var ErrNoConfigFile = errors.New("Unable to locate Config file. Perhaps you need to create a new site.\n       Run `hugo help new` for details.\n")
    88  
    89  // LoadConfig loads Hugo configuration into a new Viper and then adds
    90  // a set of defaults.
    91  func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provider) error) (*viper.Viper, []string, error) {
    92  	var configFiles []string
    93  
    94  	fs := d.Fs
    95  	v := viper.New()
    96  	v.SetFs(fs)
    97  
    98  	if d.Path == "" {
    99  		d.Path = "."
   100  	}
   101  
   102  	configFilenames := d.configFilenames()
   103  	v.AutomaticEnv()
   104  	v.SetEnvPrefix("hugo")
   105  	v.SetConfigFile(configFilenames[0])
   106  	v.AddConfigPath(d.Path)
   107  
   108  	var configFileErr error
   109  
   110  	err := v.ReadInConfig()
   111  	if err != nil {
   112  		if _, ok := err.(viper.ConfigParseError); ok {
   113  			return nil, configFiles, err
   114  		}
   115  		configFileErr = ErrNoConfigFile
   116  	}
   117  
   118  	if configFileErr == nil {
   119  
   120  		if cf := v.ConfigFileUsed(); cf != "" {
   121  			configFiles = append(configFiles, cf)
   122  		}
   123  
   124  		for _, configFile := range configFilenames[1:] {
   125  			var r io.Reader
   126  			var err error
   127  			if r, err = fs.Open(configFile); err != nil {
   128  				return nil, configFiles, fmt.Errorf("Unable to open Config file.\n (%s)\n", err)
   129  			}
   130  			if err = v.MergeConfig(r); err != nil {
   131  				return nil, configFiles, fmt.Errorf("Unable to parse/merge Config file (%s).\n (%s)\n", configFile, err)
   132  			}
   133  			configFiles = append(configFiles, configFile)
   134  		}
   135  
   136  	}
   137  
   138  	if err := loadDefaultSettingsFor(v); err != nil {
   139  		return v, configFiles, err
   140  	}
   141  
   142  	if configFileErr == nil {
   143  
   144  		themeConfigFiles, err := loadThemeConfig(d, v)
   145  		if err != nil {
   146  			return v, configFiles, err
   147  		}
   148  
   149  		if len(themeConfigFiles) > 0 {
   150  			configFiles = append(configFiles, themeConfigFiles...)
   151  		}
   152  	}
   153  
   154  	// We create languages based on the settings, so we need to make sure that
   155  	// all configuration is loaded/set before doing that.
   156  	for _, d := range doWithConfig {
   157  		if err := d(v); err != nil {
   158  			return v, configFiles, err
   159  		}
   160  	}
   161  
   162  	if err := loadLanguageSettings(v, nil); err != nil {
   163  		return v, configFiles, err
   164  	}
   165  
   166  	return v, configFiles, configFileErr
   167  
   168  }
   169  
   170  func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {
   171  
   172  	defaultLang := cfg.GetString("defaultContentLanguage")
   173  
   174  	var languages map[string]interface{}
   175  
   176  	languagesFromConfig := cfg.GetStringMap("languages")
   177  	disableLanguages := cfg.GetStringSlice("disableLanguages")
   178  
   179  	if len(disableLanguages) == 0 {
   180  		languages = languagesFromConfig
   181  	} else {
   182  		languages = make(map[string]interface{})
   183  		for k, v := range languagesFromConfig {
   184  			for _, disabled := range disableLanguages {
   185  				if disabled == defaultLang {
   186  					return fmt.Errorf("cannot disable default language %q", defaultLang)
   187  				}
   188  
   189  				if strings.EqualFold(k, disabled) {
   190  					v.(map[string]interface{})["disabled"] = true
   191  					break
   192  				}
   193  			}
   194  			languages[k] = v
   195  		}
   196  	}
   197  
   198  	var (
   199  		languages2 langs.Languages
   200  		err        error
   201  	)
   202  
   203  	if len(languages) == 0 {
   204  		languages2 = append(languages2, langs.NewDefaultLanguage(cfg))
   205  	} else {
   206  		languages2, err = toSortedLanguages(cfg, languages)
   207  		if err != nil {
   208  			return fmt.Errorf("Failed to parse multilingual config: %s", err)
   209  		}
   210  	}
   211  
   212  	if oldLangs != nil {
   213  		// When in multihost mode, the languages are mapped to a server, so
   214  		// some structural language changes will need a restart of the dev server.
   215  		// The validation below isn't complete, but should cover the most
   216  		// important cases.
   217  		var invalid bool
   218  		if languages2.IsMultihost() != oldLangs.IsMultihost() {
   219  			invalid = true
   220  		} else {
   221  			if languages2.IsMultihost() && len(languages2) != len(oldLangs) {
   222  				invalid = true
   223  			}
   224  		}
   225  
   226  		if invalid {
   227  			return errors.New("language change needing a server restart detected")
   228  		}
   229  
   230  		if languages2.IsMultihost() {
   231  			// We need to transfer any server baseURL to the new language
   232  			for i, ol := range oldLangs {
   233  				nl := languages2[i]
   234  				nl.Set("baseURL", ol.GetString("baseURL"))
   235  			}
   236  		}
   237  	}
   238  
   239  	// The defaultContentLanguage is something the user has to decide, but it needs
   240  	// to match a language in the language definition list.
   241  	langExists := false
   242  	for _, lang := range languages2 {
   243  		if lang.Lang == defaultLang {
   244  			langExists = true
   245  			break
   246  		}
   247  	}
   248  
   249  	if !langExists {
   250  		return fmt.Errorf("site config value %q for defaultContentLanguage does not match any language definition", defaultLang)
   251  	}
   252  
   253  	cfg.Set("languagesSorted", languages2)
   254  	cfg.Set("multilingual", len(languages2) > 1)
   255  
   256  	multihost := languages2.IsMultihost()
   257  
   258  	if multihost {
   259  		cfg.Set("defaultContentLanguageInSubdir", true)
   260  		cfg.Set("multihost", true)
   261  	}
   262  
   263  	if multihost {
   264  		// The baseURL may be provided at the language level. If that is true,
   265  		// then every language must have a baseURL. In this case we always render
   266  		// to a language sub folder, which is then stripped from all the Permalink URLs etc.
   267  		for _, l := range languages2 {
   268  			burl := l.GetLocal("baseURL")
   269  			if burl == nil {
   270  				return errors.New("baseURL must be set on all or none of the languages")
   271  			}
   272  		}
   273  
   274  	}
   275  
   276  	return nil
   277  }
   278  
   279  func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error) {
   280  	themesDir := paths.AbsPathify(d.WorkingDir, v1.GetString("themesDir"))
   281  	themes := config.GetStringSlicePreserveString(v1, "theme")
   282  
   283  	//  CollectThemes(fs afero.Fs, themesDir string, themes []strin
   284  	themeConfigs, err := paths.CollectThemes(d.Fs, themesDir, themes)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  	v1.Set("allThemes", themeConfigs)
   289  
   290  	var configFilenames []string
   291  	for _, tc := range themeConfigs {
   292  		if tc.ConfigFilename != "" {
   293  			configFilenames = append(configFilenames, tc.ConfigFilename)
   294  			if err := applyThemeConfig(v1, tc); err != nil {
   295  				return nil, err
   296  			}
   297  		}
   298  	}
   299  
   300  	return configFilenames, nil
   301  
   302  }
   303  
   304  func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
   305  
   306  	const (
   307  		paramsKey    = "params"
   308  		languagesKey = "languages"
   309  		menuKey      = "menu"
   310  	)
   311  
   312  	v2 := theme.Cfg
   313  
   314  	for _, key := range []string{paramsKey, "outputformats", "mediatypes"} {
   315  		mergeStringMapKeepLeft("", key, v1, v2)
   316  	}
   317  
   318  	themeLower := strings.ToLower(theme.Name)
   319  	themeParamsNamespace := paramsKey + "." + themeLower
   320  
   321  	// Set namespaced params
   322  	if v2.IsSet(paramsKey) && !v1.IsSet(themeParamsNamespace) {
   323  		// Set it in the default store to make sure it gets in the same or
   324  		// behind the others.
   325  		v1.SetDefault(themeParamsNamespace, v2.Get(paramsKey))
   326  	}
   327  
   328  	// Only add params and new menu entries, we do not add language definitions.
   329  	if v1.IsSet(languagesKey) && v2.IsSet(languagesKey) {
   330  		v1Langs := v1.GetStringMap(languagesKey)
   331  		for k, _ := range v1Langs {
   332  			langParamsKey := languagesKey + "." + k + "." + paramsKey
   333  			mergeStringMapKeepLeft(paramsKey, langParamsKey, v1, v2)
   334  		}
   335  		v2Langs := v2.GetStringMap(languagesKey)
   336  		for k, _ := range v2Langs {
   337  			if k == "" {
   338  				continue
   339  			}
   340  			langParamsKey := languagesKey + "." + k + "." + paramsKey
   341  			langParamsThemeNamespace := langParamsKey + "." + themeLower
   342  			// Set namespaced params
   343  			if v2.IsSet(langParamsKey) && !v1.IsSet(langParamsThemeNamespace) {
   344  				v1.SetDefault(langParamsThemeNamespace, v2.Get(langParamsKey))
   345  			}
   346  
   347  			langMenuKey := languagesKey + "." + k + "." + menuKey
   348  			if v2.IsSet(langMenuKey) {
   349  				// Only add if not in the main config.
   350  				v2menus := v2.GetStringMap(langMenuKey)
   351  				for k, v := range v2menus {
   352  					menuEntry := menuKey + "." + k
   353  					menuLangEntry := langMenuKey + "." + k
   354  					if !v1.IsSet(menuEntry) && !v1.IsSet(menuLangEntry) {
   355  						v1.Set(menuLangEntry, v)
   356  					}
   357  				}
   358  			}
   359  		}
   360  	}
   361  
   362  	// Add menu definitions from theme not found in project
   363  	if v2.IsSet("menu") {
   364  		v2menus := v2.GetStringMap(menuKey)
   365  		for k, v := range v2menus {
   366  			menuEntry := menuKey + "." + k
   367  			if !v1.IsSet(menuEntry) {
   368  				v1.SetDefault(menuEntry, v)
   369  			}
   370  		}
   371  	}
   372  
   373  	return nil
   374  
   375  }
   376  
   377  func mergeStringMapKeepLeft(rootKey, key string, v1, v2 config.Provider) {
   378  	if !v2.IsSet(key) {
   379  		return
   380  	}
   381  
   382  	if !v1.IsSet(key) && !(rootKey != "" && rootKey != key && v1.IsSet(rootKey)) {
   383  		v1.Set(key, v2.Get(key))
   384  		return
   385  	}
   386  
   387  	m1 := v1.GetStringMap(key)
   388  	m2 := v2.GetStringMap(key)
   389  
   390  	for k, v := range m2 {
   391  		if _, found := m1[k]; !found {
   392  			if rootKey != "" && v1.IsSet(rootKey+"."+k) {
   393  				continue
   394  			}
   395  			m1[k] = v
   396  		}
   397  	}
   398  }
   399  
   400  func loadDefaultSettingsFor(v *viper.Viper) error {
   401  
   402  	c, err := helpers.NewContentSpec(v)
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	v.RegisterAlias("indexes", "taxonomies")
   408  
   409  	v.SetDefault("cleanDestinationDir", false)
   410  	v.SetDefault("watch", false)
   411  	v.SetDefault("metaDataFormat", "toml")
   412  	v.SetDefault("contentDir", "content")
   413  	v.SetDefault("layoutDir", "layouts")
   414  	v.SetDefault("staticDir", "static")
   415  	v.SetDefault("resourceDir", "resources")
   416  	v.SetDefault("archetypeDir", "archetypes")
   417  	v.SetDefault("publishDir", "public")
   418  	v.SetDefault("dataDir", "data")
   419  	v.SetDefault("i18nDir", "i18n")
   420  	v.SetDefault("themesDir", "themes")
   421  	v.SetDefault("buildDrafts", false)
   422  	v.SetDefault("buildFuture", false)
   423  	v.SetDefault("buildExpired", false)
   424  	v.SetDefault("uglyURLs", false)
   425  	v.SetDefault("verbose", false)
   426  	v.SetDefault("ignoreCache", false)
   427  	v.SetDefault("canonifyURLs", false)
   428  	v.SetDefault("relativeURLs", false)
   429  	v.SetDefault("removePathAccents", false)
   430  	v.SetDefault("titleCaseStyle", "AP")
   431  	v.SetDefault("taxonomies", map[string]string{"tag": "tags", "category": "categories"})
   432  	v.SetDefault("permalinks", make(PermalinkOverrides, 0))
   433  	v.SetDefault("sitemap", Sitemap{Priority: -1, Filename: "sitemap.xml"})
   434  	v.SetDefault("pygmentsStyle", "monokai")
   435  	v.SetDefault("pygmentsUseClasses", false)
   436  	v.SetDefault("pygmentsCodeFences", false)
   437  	v.SetDefault("pygmentsUseClassic", false)
   438  	v.SetDefault("pygmentsOptions", "")
   439  	v.SetDefault("disableLiveReload", false)
   440  	v.SetDefault("pluralizeListTitles", true)
   441  	v.SetDefault("preserveTaxonomyNames", false)
   442  	v.SetDefault("forceSyncStatic", false)
   443  	v.SetDefault("footnoteAnchorPrefix", "")
   444  	v.SetDefault("footnoteReturnLinkContents", "")
   445  	v.SetDefault("newContentEditor", "")
   446  	v.SetDefault("paginate", 10)
   447  	v.SetDefault("paginatePath", "page")
   448  	v.SetDefault("summaryLength", 70)
   449  	v.SetDefault("blackfriday", c.BlackFriday)
   450  	v.SetDefault("rSSUri", "index.xml")
   451  	v.SetDefault("rssLimit", -1)
   452  	v.SetDefault("sectionPagesMenu", "")
   453  	v.SetDefault("disablePathToLower", false)
   454  	v.SetDefault("hasCJKLanguage", false)
   455  	v.SetDefault("enableEmoji", false)
   456  	v.SetDefault("pygmentsCodeFencesGuessSyntax", false)
   457  	v.SetDefault("useModTimeAsFallback", false)
   458  	v.SetDefault("defaultContentLanguage", "en")
   459  	v.SetDefault("defaultContentLanguageInSubdir", false)
   460  	v.SetDefault("enableMissingTranslationPlaceholders", false)
   461  	v.SetDefault("enableGitInfo", false)
   462  	v.SetDefault("ignoreFiles", make([]string, 0))
   463  	v.SetDefault("disableAliases", false)
   464  	v.SetDefault("debug", false)
   465  	v.SetDefault("disableFastRender", false)
   466  	v.SetDefault("timeout", 10000) // 10 seconds
   467  
   468  	// Remove in Hugo 0.39
   469  
   470  	if v.GetBool("useModTimeAsFallback") {
   471  
   472  		helpers.Deprecated("Site config", "useModTimeAsFallback", `Replace with this in your config.toml:
   473      
   474  [frontmatter]
   475  date = [ "date",":fileModTime", ":default"]
   476  lastmod = ["lastmod" ,":fileModTime", ":default"]
   477  `, false)
   478  
   479  	}
   480  
   481  	return nil
   482  }