diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/herrors/errors.go | 11 | ||||
-rw-r--r-- | common/hugio/copy.go | 90 | ||||
-rw-r--r-- | common/hugo/vars_extended.go | 2 | ||||
-rw-r--r-- | common/hugo/vars_regular.go | 2 | ||||
-rw-r--r-- | common/hugo/version.go | 16 | ||||
-rw-r--r-- | common/hugo/version_test.go | 6 | ||||
-rw-r--r-- | common/loggers/loggers.go | 5 | ||||
-rw-r--r-- | common/maps/params.go | 96 | ||||
-rw-r--r-- | common/maps/params_test.go | 45 |
9 files changed, 269 insertions, 4 deletions
diff --git a/common/herrors/errors.go b/common/herrors/errors.go index be98ceb39..1a6107050 100644 --- a/common/herrors/errors.go +++ b/common/herrors/errors.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "os" + "runtime/debug" _errors "github.com/pkg/errors" ) @@ -46,6 +47,16 @@ func FprintStackTrace(w io.Writer, err error) { } } +// Recover is a helper function that can be used to capture panics. +// Put this at the top of a method/function that crashes in a template: +// defer herrors.Recover() +func Recover() { + if r := recover(); r != nil { + fmt.Println("stacktrace from panic: \n" + string(debug.Stack())) + } + +} + // ErrFeatureNotAvailable denotes that a feature is unavailable. // // We will, at least to begin with, make some Hugo features (SCSS with libsass) optional, diff --git a/common/hugio/copy.go b/common/hugio/copy.go new file mode 100644 index 000000000..2b756cb44 --- /dev/null +++ b/common/hugio/copy.go @@ -0,0 +1,90 @@ +// Copyright 2019 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 hugio + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + + "github.com/spf13/afero" +) + +// CopyFile copies a file. +func CopyFile(fs afero.Fs, from, to string) error { + sf, err := os.Open(from) + if err != nil { + return err + } + defer sf.Close() + df, err := os.Create(to) + if err != nil { + return err + } + defer df.Close() + _, err = io.Copy(df, sf) + if err == nil { + si, err := os.Stat(from) + if err != nil { + err = os.Chmod(to, si.Mode()) + + if err != nil { + return err + } + } + + } + return nil +} + +// CopyDir copies a directory. +func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool) error { + fi, err := os.Stat(from) + if err != nil { + return err + } + + if !fi.IsDir() { + return errors.Errorf("%q is not a directory", from) + } + + err = fs.MkdirAll(to, 0777) // before umask + if err != nil { + return err + } + + entries, _ := ioutil.ReadDir(from) + for _, entry := range entries { + fromFilename := filepath.Join(from, entry.Name()) + toFilename := filepath.Join(to, entry.Name()) + if entry.IsDir() { + if shouldCopy != nil && !shouldCopy(fromFilename) { + continue + } + if err := CopyDir(fs, fromFilename, toFilename, shouldCopy); err != nil { + return err + } + } else { + if err := CopyFile(fs, fromFilename, toFilename); err != nil { + return err + } + } + + } + + return nil +} diff --git a/common/hugo/vars_extended.go b/common/hugo/vars_extended.go index 20683b804..bb96bade6 100644 --- a/common/hugo/vars_extended.go +++ b/common/hugo/vars_extended.go @@ -15,4 +15,4 @@ package hugo -var isExtended = true +var IsExtended = true diff --git a/common/hugo/vars_regular.go b/common/hugo/vars_regular.go index e1ece83fb..fae18df14 100644 --- a/common/hugo/vars_regular.go +++ b/common/hugo/vars_regular.go @@ -15,4 +15,4 @@ package hugo -var isExtended = false +var IsExtended = false diff --git a/common/hugo/version.go b/common/hugo/version.go index 47641f10c..848393f97 100644 --- a/common/hugo/version.go +++ b/common/hugo/version.go @@ -15,6 +15,7 @@ package hugo import ( "fmt" + "strconv" "runtime" "strings" @@ -133,7 +134,7 @@ func BuildVersionString() string { if commitHash != "" { version += "-" + strings.ToUpper(commitHash) } - if isExtended { + if IsExtended { version += "/extended" } @@ -235,3 +236,16 @@ func compareFloatVersions(version float32, v float32) int { } return 1 } + +func GoMinorVersion() int { + return goMinorVersion(runtime.Version()) +} + +func goMinorVersion(version string) int { + if strings.HasPrefix(version, "devel") { + return 9999 // magic + } + i, _ := strconv.Atoi(strings.Split(version, ".")[1]) + return i + +} diff --git a/common/hugo/version_test.go b/common/hugo/version_test.go index 08059189e..e2aeeabbf 100644 --- a/common/hugo/version_test.go +++ b/common/hugo/version_test.go @@ -77,3 +77,9 @@ func TestParseHugoVersion(t *testing.T) { require.Equal(t, "0.25-DEV", MustParseVersion("0.25-DEV").String()) } + +func TestGoMinorVersion(t *testing.T) { + assert := require.New(t) + assert.Equal(12, goMinorVersion("go1.12.5")) + assert.True(GoMinorVersion() >= 11) +} diff --git a/common/loggers/loggers.go b/common/loggers/loggers.go index e711de468..082fd1487 100644 --- a/common/loggers/loggers.go +++ b/common/loggers/loggers.go @@ -40,6 +40,7 @@ func init() { type Logger struct { *jww.Notepad ErrorCounter *jww.Counter + WarnCounter *jww.Counter // This is only set in server mode. errors *bytes.Buffer @@ -143,9 +144,10 @@ func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) { func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *Logger { errorCounter := &jww.Counter{} + warnCounter := &jww.Counter{} outHandle, logHandle = getLogWriters(outHandle, logHandle) - listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError)} + listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)} var errorBuff *bytes.Buffer if saveErrors { errorBuff = new(bytes.Buffer) @@ -164,6 +166,7 @@ func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle return &Logger{ Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...), ErrorCounter: errorCounter, + WarnCounter: warnCounter, errors: errorBuff, } } diff --git a/common/maps/params.go b/common/maps/params.go new file mode 100644 index 000000000..0b81057b1 --- /dev/null +++ b/common/maps/params.go @@ -0,0 +1,96 @@ +// Copyright 2019 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 maps + +import ( + "strings" + + "github.com/pkg/errors" + + "github.com/spf13/cast" +) + +// GetNestedParam gets the first match of the keyStr in the candidates given. +// It will first try the exact match and then try to find it as a nested map value, +// using the given separator, e.g. "mymap.name". +// It assumes that all the maps given have lower cased keys. +func GetNestedParam(keyStr, separator string, candidates ...map[string]interface{}) (interface{}, error) { + keyStr = strings.ToLower(keyStr) + + lookupFn := func(key string) interface{} { + for _, m := range candidates { + if v, ok := m[key]; ok { + return v + } + } + + return nil + } + + v, _, _, err := GetNestedParamFn(keyStr, separator, lookupFn) + return v, err +} + +func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) { + result, _ := traverseDirectParams(keyStr, lookupFn) + if result != nil { + return result, keyStr, nil, nil + } + + keySegments := strings.Split(keyStr, separator) + if len(keySegments) == 1 { + return nil, keyStr, nil, nil + } + + return traverseNestedParams(keySegments, lookupFn) +} + +func traverseDirectParams(keyStr string, lookupFn func(key string) interface{}) (interface{}, error) { + return lookupFn(keyStr), nil +} + +func traverseNestedParams(keySegments []string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) { + firstKey, rest := keySegments[0], keySegments[1:] + result := lookupFn(firstKey) + if result == nil || len(rest) == 0 { + return result, firstKey, nil, nil + } + + switch m := result.(type) { + case map[string]interface{}: + v, key, owner := traverseParams(rest, m) + return v, key, owner, nil + default: + return nil, "", nil, errors.Errorf("unsupported Params type: %T", result) + } +} + +func traverseParams(keys []string, m map[string]interface{}) (interface{}, string, map[string]interface{}) { + // Shift first element off. + firstKey, rest := keys[0], keys[1:] + result := m[firstKey] + + // No point in continuing here. + if result == nil { + return result, "", nil + } + + if len(rest) == 0 { + // That was the last key. + return result, firstKey, m + } + + // That was not the last key. + return traverseParams(rest, cast.ToStringMap(result)) +} diff --git a/common/maps/params_test.go b/common/maps/params_test.go new file mode 100644 index 000000000..89b149617 --- /dev/null +++ b/common/maps/params_test.go @@ -0,0 +1,45 @@ +// Copyright 2019 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 maps + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetNestedParam(t *testing.T) { + + m := map[string]interface{}{ + "first": 1, + "with_underscore": 2, + "nested": map[string]interface{}{ + "color": "blue", + }, + } + + assert := require.New(t) + + must := func(keyStr, separator string, candidates ...map[string]interface{}) interface{} { + v, err := GetNestedParam(keyStr, separator, candidates...) + assert.NoError(err) + return v + } + + assert.Equal(1, must("first", "_", m)) + assert.Equal(1, must("First", "_", m)) + assert.Equal(2, must("with_underscore", "_", m)) + assert.Equal("blue", must("nested_color", "_", m)) + +} |