Skip to content

Commit

Permalink
Add /config dir support
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Dec 6, 2018
1 parent 931a132 commit 08c409b
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 6 deletions.
7 changes: 7 additions & 0 deletions helpers/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@ func FileAndExt(in string) (string, string) {
return fileAndExt(in, fpb)
}

// FileAndExtNoDelimiter takes a path and returns the file and extension separated,
// the extension excluding the delmiter, e.g "md".
func FileAndExtNoDelimiter(in string) (string, string) {
file, ext := fileAndExt(in, fpb)
return file, strings.TrimPrefix(ext, ".")
}

// Filename takes a path, strips out the extension,
// and returns the name of the file.
func Filename(in string) (name string) {
Expand Down
53 changes: 53 additions & 0 deletions htesting/testdata_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package htesting

import (
"path/filepath"
"testing"

"github.com/spf13/afero"
)

type testFile struct {
name string
content string
}

type testdataBuilder struct {
t testing.TB
fs afero.Fs
workingDir string

files []testFile
}

func NewTestdataBuilder(fs afero.Fs, workingDir string, t testing.TB) *testdataBuilder {
workingDir = filepath.Clean(workingDir)
return &testdataBuilder{fs: fs, workingDir: workingDir, t: t}
}

func (b *testdataBuilder) Add(filename, content string) *testdataBuilder {
b.files = append(b.files, testFile{name: filename, content: content})
return b
}

func (b *testdataBuilder) Build() *testdataBuilder {
for _, f := range b.files {
if err := afero.WriteFile(b.fs, filepath.Join(b.workingDir, f.name), []byte(f.content), 0666); err != nil {
b.t.Fatalf("failed to add %q: %s", f.name, err)
}
}
return b
}
93 changes: 87 additions & 6 deletions hugolib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
package hugolib

import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"

"github.com/gohugoio/hugo/common/herrors"
"github.com/pkg/errors"

"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/gohugoio/hugo/parser/metadecoders"
_errors "github.com/pkg/errors"

"github.com/gohugoio/hugo/langs"
Expand Down Expand Up @@ -74,6 +77,9 @@ type ConfigSourceDescriptor struct {

// The project's working dir. Is used to look for additional theme config.
WorkingDir string

// The (optional) directory for additional configuration files.
AbsConfigDir string
}

func (d ConfigSourceDescriptor) configFilenames() []string {
Expand All @@ -95,6 +101,11 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid

fs := d.Fs
v := viper.New()

// Set up aliases
// TODO(bep) config update docs
v.RegisterAlias("menu", "menus")

v.SetFs(fs)

if d.Path == "" {
Expand Down Expand Up @@ -135,19 +146,24 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
}

for _, configFile := range configFilenames[1:] {
var r io.Reader
var r io.ReadCloser
var err error
if r, err = fs.Open(configFile); err != nil {
return nil, configFiles, fmt.Errorf("Unable to open Config file.\n (%s)\n", err)
}
if err = v.MergeConfig(r); err != nil {
return nil, configFiles, applyFileContext(configFile, err)
}
r.Close()
configFiles = append(configFiles, configFile)
}

}

if configFileErr == nil && d.AbsConfigDir != "" {
configFileErr = loadConfigFromConfigDir(d.Fs, d.AbsConfigDir, v)
}

if err := loadDefaultSettingsFor(v); err != nil {
return v, configFiles, err
}
Expand Down Expand Up @@ -180,6 +196,71 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid

}

func loadConfigFromConfigDir(fs afero.Fs, configDir string, v *viper.Viper) error {
return afero.Walk(fs, configDir, func(path string, info os.FileInfo, err error) error {
if info != nil && !info.IsDir() {
// TODO(bep) config hugofs.LanguageAnnouncer language merge earlier on fs level?
rel := strings.TrimPrefix(path, configDir)
rel = strings.TrimPrefix(rel, helpers.FilePathSeparator)
name, ext := helpers.FileAndExtNoDelimiter(rel)
name, ext = strings.ToLower(name), strings.ToLower(ext)

var r io.ReadCloser
var err error
if r, err = fs.Open(path); err != nil {
return errors.Wrapf(err, "unable to open Config file %q: %s", path, err)
}

switch name {
case "config":
if err = v.MergeConfig(r); err != nil {
// TODO(bep) config File error context this and the others
return errors.Wrapf(err, "unable to merge Config file %q: %s", path, err)
}
default:

format := metadecoders.FormatFromString(ext)
if format == "" {
return errors.Errorf("config format %q in %q not supported", ext, rel)
}

// Can be params.jp, menus.en etc.
name, lang := helpers.FileAndExtNoDelimiter(name)

if lang != "" {
name = "languages." + lang
switch name {
case "menu", "menus":
name = name + ".menus"
case "params":
name = name + ".params"
}

fmt.Println(">>>", name, lang)
}

b, err := ioutil.ReadAll(r)
if err != nil {
return err
}
m, err := metadecoders.Unmarshal(b, format)
if err != nil {
return err
}

if v.IsSet(name) {
fmt.Println(">>>", name, "is")
}

v.Set(name, m)
}

r.Close()
}
return nil
})
}

func loadLanguageSettings(cfg config.Provider, oldLangs langs.Languages) error {

defaultLang := cfg.GetString("defaultContentLanguage")
Expand Down Expand Up @@ -293,7 +374,6 @@ func loadThemeConfig(d ConfigSourceDescriptor, v1 *viper.Viper) ([]string, error
themesDir := paths.AbsPathify(d.WorkingDir, v1.GetString("themesDir"))
themes := config.GetStringSlicePreserveString(v1, "theme")

// CollectThemes(fs afero.Fs, themesDir string, themes []strin
themeConfigs, err := paths.CollectThemes(d.Fs, themesDir, themes)
if err != nil {
return nil, err
Expand Down Expand Up @@ -324,7 +404,8 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
const (
paramsKey = "params"
languagesKey = "languages"
menuKey = "menu"
// TODO(bep) config vs alias
menuKey = "menu"
)

v2 := theme.Cfg
Expand Down Expand Up @@ -378,7 +459,7 @@ func applyThemeConfig(v1 *viper.Viper, theme paths.ThemeConfig) error {
}

// Add menu definitions from theme not found in project
if v2.IsSet("menu") {
if v2.IsSet(menuKey) {
v2menus := v2.GetStringMap(menuKey)
for k, v := range v2menus {
menuEntry := menuKey + "." + k
Expand Down
98 changes: 98 additions & 0 deletions hugolib/configdir_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hugolib

import (
"fmt"
"testing"

"github.com/gohugoio/hugo/htesting"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
)

func TestLoadConfigDir(t *testing.T) {
t.Parallel()

assert := require.New(t)

// Add a random config variable for testing.
// side = page in Norwegian.
configContent := `
baseURL = "https://example.org"
[languages.en]
weight = 0
languageName = "English"
[languages.no]
weight = 10
languageName = "FOO"
[params]
p1 = "p1base"
`

mm := afero.NewMemMapFs()

writeToFs(t, mm, "hugo.toml", configContent)

fb := htesting.NewTestdataBuilder(mm, "config", t)

// Will replace any settings in the base
fb.Add("config.toml", `paginatePath = "side"`)

fb.Add("params.yaml", `p2: "p2params"`)
fb.Add("menus.toml", `
[[docs]]
name = "About Hugo"
weight = 1
[[docs]]
name = "Home"
weight = 2
`)

fb.Add("menus.no.toml", `
[[docs]]
name = "Om Hugo"
weight = 1
`)

fb.Add("params.no.toml", `p3 = "p3params"`)
fb.Add("languages.no.toml", `languageName = "Norsk"`)

fb.Build()

cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Filename: "hugo.toml", AbsConfigDir: "config"})
require.NoError(t, err)

// Set in /config/config.toml
assert.Equal("side", cfg.GetString("paginatePath"))

fmt.Println(">>LA", cfg.Get("languages"))

assert.Equal("Norsk", cfg.GetString("languages.no.languageName"))
assert.Equal(10, cfg.Get("languages.no.weight"))

assert.Equal("p1base", cfg.GetString("params.p1"))
assert.Equal("p2params", cfg.GetString("params.p2"))
assert.Equal("p3params", cfg.GetString("languages.no.params.p3"))

assert.Equal(2, len(cfg.Get("menus.docs").(([]map[string]interface{}))))
noMenus := cfg.Get("languages.no.menus.docs")
assert.NotNil(noMenus)
assert.Equal(1, len(noMenus.(([]map[string]interface{}))))

}

0 comments on commit 08c409b

Please sign in to comment.