github.com/schumacherfm/hugo@v0.47.1/hugolib/paths/themes.go (about)

     1  // Copyright 2018 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 paths
    15  
    16  import (
    17  	"path/filepath"
    18  	"strings"
    19  
    20  	"github.com/gohugoio/hugo/config"
    21  	"github.com/spf13/afero"
    22  	"github.com/spf13/cast"
    23  	"github.com/spf13/viper"
    24  )
    25  
    26  type ThemeConfig struct {
    27  	// The theme name as provided by the folder name below /themes.
    28  	Name string
    29  
    30  	// Optional configuration filename (e.g. "/themes/mytheme/config.json").
    31  	ConfigFilename string
    32  
    33  	// Optional config read from the ConfigFile above.
    34  	Cfg config.Provider
    35  }
    36  
    37  // Create file system, an ordered theme list from left to right, no duplicates.
    38  type themesCollector struct {
    39  	themesDir string
    40  	fs        afero.Fs
    41  	seen      map[string]bool
    42  	themes    []ThemeConfig
    43  }
    44  
    45  func (c *themesCollector) isSeen(theme string) bool {
    46  	loki := strings.ToLower(theme)
    47  	if c.seen[loki] {
    48  		return true
    49  	}
    50  	c.seen[loki] = true
    51  	return false
    52  }
    53  
    54  func (c *themesCollector) addAndRecurse(themes ...string) error {
    55  	for i := 0; i < len(themes); i++ {
    56  		theme := themes[i]
    57  		configFilename := c.getConfigFileIfProvided(theme)
    58  		if !c.isSeen(theme) {
    59  			tc, err := c.add(theme, configFilename)
    60  			if err != nil {
    61  				return err
    62  			}
    63  			if err := c.addTemeNamesFromTheme(tc); err != nil {
    64  				return err
    65  			}
    66  		}
    67  	}
    68  	return nil
    69  }
    70  
    71  func (c *themesCollector) add(name, configFilename string) (ThemeConfig, error) {
    72  	var cfg config.Provider
    73  	var tc ThemeConfig
    74  
    75  	if configFilename != "" {
    76  		v := viper.New()
    77  		v.SetFs(c.fs)
    78  		v.AutomaticEnv()
    79  		v.SetEnvPrefix("hugo")
    80  		v.SetConfigFile(configFilename)
    81  
    82  		err := v.ReadInConfig()
    83  		if err != nil {
    84  			return tc, err
    85  		}
    86  		cfg = v
    87  
    88  	}
    89  
    90  	tc = ThemeConfig{Name: name, ConfigFilename: configFilename, Cfg: cfg}
    91  	c.themes = append(c.themes, tc)
    92  	return tc, nil
    93  
    94  }
    95  
    96  func collectThemeNames(p *Paths) ([]ThemeConfig, error) {
    97  	return CollectThemes(p.Fs.Source, p.AbsPathify(p.ThemesDir), p.Themes())
    98  
    99  }
   100  
   101  func CollectThemes(fs afero.Fs, themesDir string, themes []string) ([]ThemeConfig, error) {
   102  	if len(themes) == 0 {
   103  		return nil, nil
   104  	}
   105  
   106  	c := &themesCollector{
   107  		fs:        fs,
   108  		themesDir: themesDir,
   109  		seen:      make(map[string]bool)}
   110  
   111  	for i := 0; i < len(themes); i++ {
   112  		theme := themes[i]
   113  		if err := c.addAndRecurse(theme); err != nil {
   114  			return nil, err
   115  		}
   116  	}
   117  
   118  	return c.themes, nil
   119  
   120  }
   121  
   122  func (c *themesCollector) getConfigFileIfProvided(theme string) string {
   123  	configDir := filepath.Join(c.themesDir, theme)
   124  
   125  	var (
   126  		configFilename string
   127  		exists         bool
   128  	)
   129  
   130  	// Viper supports more, but this is the sub-set supported by Hugo.
   131  	for _, configFormats := range []string{"toml", "yaml", "yml", "json"} {
   132  		configFilename = filepath.Join(configDir, "config."+configFormats)
   133  		exists, _ = afero.Exists(c.fs, configFilename)
   134  		if exists {
   135  			break
   136  		}
   137  	}
   138  
   139  	if !exists {
   140  		// No theme config set.
   141  		return ""
   142  	}
   143  
   144  	return configFilename
   145  
   146  }
   147  
   148  func (c *themesCollector) addTemeNamesFromTheme(theme ThemeConfig) error {
   149  	if theme.Cfg != nil && theme.Cfg.IsSet("theme") {
   150  		v := theme.Cfg.Get("theme")
   151  		switch vv := v.(type) {
   152  		case []string:
   153  			return c.addAndRecurse(vv...)
   154  		case []interface{}:
   155  			return c.addAndRecurse(cast.ToStringSlice(vv)...)
   156  		default:
   157  			return c.addAndRecurse(cast.ToString(vv))
   158  		}
   159  	}
   160  
   161  	return nil
   162  }