diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/collections/collections.go | 28 | ||||
-rw-r--r-- | common/errors/errors.go | 25 | ||||
-rw-r--r-- | common/hugio/readers.go | 53 | ||||
-rw-r--r-- | common/hugio/writers.go | 43 | ||||
-rw-r--r-- | common/loggers/loggers.go | 37 | ||||
-rw-r--r-- | common/maps/maps.go | 44 | ||||
-rw-r--r-- | common/maps/maps_test.go | 72 | ||||
-rw-r--r-- | common/maps/scratch.go | 136 | ||||
-rw-r--r-- | common/maps/scratch_test.go | 170 | ||||
-rw-r--r-- | common/math/math.go | 135 | ||||
-rw-r--r-- | common/math/math_test.go | 109 | ||||
-rw-r--r-- | common/types/evictingqueue.go | 89 | ||||
-rw-r--r-- | common/types/evictingqueue_test.go | 71 | ||||
-rw-r--r-- | common/types/types.go | 52 | ||||
-rw-r--r-- | common/types/types_test.go | 29 |
15 files changed, 1093 insertions, 0 deletions
diff --git a/common/collections/collections.go b/common/collections/collections.go new file mode 100644 index 000000000..854f705b3 --- /dev/null +++ b/common/collections/collections.go @@ -0,0 +1,28 @@ +// 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 collections contains common Hugo functionality related to collection +// handling. +package collections + +// Grouper defines a very generic way to group items by a given key. +type Grouper interface { + Group(key interface{}, items interface{}) (interface{}, error) +} + +// Slicer definse a very generic way to create a typed slice. This is used +// in collections.Slice template func to get types such as Pages, PageGroups etc. +// instead of the less useful []interface{}. +type Slicer interface { + Slice(items []interface{}) (interface{}, error) +} diff --git a/common/errors/errors.go b/common/errors/errors.go new file mode 100644 index 000000000..673cd23ff --- /dev/null +++ b/common/errors/errors.go @@ -0,0 +1,25 @@ +// 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 errors contains common Hugo errors and error related utilities. +package errors + +import ( + "errors" +) + +// ErrFeatureNotAvailable denotes that a feature is unavailable. +// +// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional, +// and this error is used to signal those situations. +var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version") diff --git a/common/hugio/readers.go b/common/hugio/readers.go new file mode 100644 index 000000000..ba55e2d08 --- /dev/null +++ b/common/hugio/readers.go @@ -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 hugio + +import ( + "io" + "strings" +) + +// ReadSeeker wraps io.Reader and io.Seeker. +type ReadSeeker interface { + io.Reader + io.Seeker +} + +// ReadSeekCloser is implemented by afero.File. We use this as the common type for +// content in Resource objects, even for strings. +type ReadSeekCloser interface { + ReadSeeker + io.Closer +} + +// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close. +type ReadSeekerNoOpCloser struct { + ReadSeeker +} + +// Close does nothing. +func (r ReadSeekerNoOpCloser) Close() error { + return nil +} + +// NewReadSeekerNoOpCloser creates a new ReadSeekerNoOpCloser with the given ReadSeeker. +func NewReadSeekerNoOpCloser(r ReadSeeker) ReadSeekerNoOpCloser { + return ReadSeekerNoOpCloser{r} +} + +// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser +// from the given string. +func NewReadSeekerNoOpCloserFromString(content string) ReadSeekerNoOpCloser { + return ReadSeekerNoOpCloser{strings.NewReader(content)} +} diff --git a/common/hugio/writers.go b/common/hugio/writers.go new file mode 100644 index 000000000..2766146e1 --- /dev/null +++ b/common/hugio/writers.go @@ -0,0 +1,43 @@ +// 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 hugio + +import ( + "io" +) + +type multiWriteCloser struct { + io.Writer + closers []io.WriteCloser +} + +func (m multiWriteCloser) Close() error { + var err error + for _, c := range m.closers { + if closeErr := c.Close(); err != nil { + err = closeErr + } + } + return err +} + +// NewMultiWriteCloser creates a new io.WriteCloser that duplicates its writes to all the +// provided writers. +func NewMultiWriteCloser(writeClosers ...io.WriteCloser) io.WriteCloser { + writers := make([]io.Writer, len(writeClosers)) + for i, w := range writeClosers { + writers[i] = w + } + return multiWriteCloser{Writer: io.MultiWriter(writers...), closers: writeClosers} +} diff --git a/common/loggers/loggers.go b/common/loggers/loggers.go new file mode 100644 index 000000000..2f7f36b34 --- /dev/null +++ b/common/loggers/loggers.go @@ -0,0 +1,37 @@ +// 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 loggers + +import ( + "io/ioutil" + "log" + "os" + + jww "github.com/spf13/jwalterweatherman" +) + +// NewDebugLogger is a convenience function to create a debug logger. +func NewDebugLogger() *jww.Notepad { + return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) +} + +// NewWarningLogger is a convenience function to create a warning logger. +func NewWarningLogger() *jww.Notepad { + return jww.NewNotepad(jww.LevelWarn, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) +} + +// NewErrorLogger is a convenience function to create an error logger. +func NewErrorLogger() *jww.Notepad { + return jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) +} diff --git a/common/maps/maps.go b/common/maps/maps.go new file mode 100644 index 000000000..a114b557c --- /dev/null +++ b/common/maps/maps.go @@ -0,0 +1,44 @@ +// 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 maps + +import ( + "strings" + + "github.com/spf13/cast" +) + +// ToLower makes all the keys in the given map lower cased and will do so +// recursively. +// Notes: +// * This will modify the map given. +// * Any nested map[interface{}]interface{} will be converted to map[string]interface{}. +func ToLower(m map[string]interface{}) { + for k, v := range m { + switch v.(type) { + case map[interface{}]interface{}: + v = cast.ToStringMap(v) + ToLower(v.(map[string]interface{})) + case map[string]interface{}: + ToLower(v.(map[string]interface{})) + } + + lKey := strings.ToLower(k) + if k != lKey { + delete(m, k) + m[lKey] = v + } + + } +} diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go new file mode 100644 index 000000000..37add5dc5 --- /dev/null +++ b/common/maps/maps_test.go @@ -0,0 +1,72 @@ +// 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 maps + +import ( + "reflect" + "testing" +) + +func TestToLower(t *testing.T) { + + tests := []struct { + input map[string]interface{} + expected map[string]interface{} + }{ + { + map[string]interface{}{ + "abC": 32, + }, + map[string]interface{}{ + "abc": 32, + }, + }, + { + map[string]interface{}{ + "abC": 32, + "deF": map[interface{}]interface{}{ + 23: "A value", + 24: map[string]interface{}{ + "AbCDe": "A value", + "eFgHi": "Another value", + }, + }, + "gHi": map[string]interface{}{ + "J": 25, + }, + }, + map[string]interface{}{ + "abc": 32, + "def": map[string]interface{}{ + "23": "A value", + "24": map[string]interface{}{ + "abcde": "A value", + "efghi": "Another value", + }, + }, + "ghi": map[string]interface{}{ + "j": 25, + }, + }, + }, + } + + for i, test := range tests { + // ToLower modifies input. + ToLower(test.input) + if !reflect.DeepEqual(test.expected, test.input) { + t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input) + } + } +} diff --git a/common/maps/scratch.go b/common/maps/scratch.go new file mode 100644 index 000000000..4b062d139 --- /dev/null +++ b/common/maps/scratch.go @@ -0,0 +1,136 @@ +// 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 maps + +import ( + "reflect" + "sort" + "sync" + + "github.com/gohugoio/hugo/common/math" +) + +// Scratch is a writable context used for stateful operations in Page/Node rendering. +type Scratch struct { + values map[string]interface{} + mu sync.RWMutex +} + +// Add will, for single values, add (using the + operator) the addend to the existing addend (if found). +// Supports numeric values and strings. +// +// If the first add for a key is an array or slice, then the next value(s) will be appended. +func (c *Scratch) Add(key string, newAddend interface{}) (string, error) { + + var newVal interface{} + c.mu.RLock() + existingAddend, found := c.values[key] + c.mu.RUnlock() + if found { + var err error + + addendV := reflect.ValueOf(existingAddend) + + if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array { + nav := reflect.ValueOf(newAddend) + if nav.Kind() == reflect.Slice || nav.Kind() == reflect.Array { + newVal = reflect.AppendSlice(addendV, nav).Interface() + } else { + newVal = reflect.Append(addendV, nav).Interface() + } + } else { + newVal, err = math.DoArithmetic(existingAddend, newAddend, '+') + if err != nil { + return "", err + } + } + } else { + newVal = newAddend + } + c.mu.Lock() + c.values[key] = newVal + c.mu.Unlock() + return "", nil // have to return something to make it work with the Go templates +} + +// Set stores a value with the given key in the Node context. +// This value can later be retrieved with Get. +func (c *Scratch) Set(key string, value interface{}) string { + c.mu.Lock() + c.values[key] = value + c.mu.Unlock() + return "" +} + +// Delete deletes the given key. +func (c *Scratch) Delete(key string) string { + c.mu.Lock() + delete(c.values, key) + c.mu.Unlock() + return "" +} + +// Get returns a value previously set by Add or Set. +func (c *Scratch) Get(key string) interface{} { + c.mu.RLock() + val := c.values[key] + c.mu.RUnlock() + + return val +} + +// SetInMap stores a value to a map with the given key in the Node context. +// This map can later be retrieved with GetSortedMapValues. +func (c *Scratch) SetInMap(key string, mapKey string, value interface{}) string { + c.mu.Lock() + _, found := c.values[key] + if !found { + c.values[key] = make(map[string]interface{}) + } + + c.values[key].(map[string]interface{})[mapKey] = value + c.mu.Unlock() + return "" +} + +// GetSortedMapValues returns a sorted map previously filled with SetInMap. +func (c *Scratch) GetSortedMapValues(key string) interface{} { + c.mu.RLock() + + if c.values[key] == nil { + c.mu.RUnlock() + return nil + } + + unsortedMap := c.values[key].(map[string]interface{}) + c.mu.RUnlock() + var keys []string + for mapKey := range unsortedMap { + keys = append(keys, mapKey) + } + + sort.Strings(keys) + + sortedArray := make([]interface{}, len(unsortedMap)) + for i, mapKey := range keys { + sortedArray[i] = unsortedMap[mapKey] + } + + return sortedArray +} + +// NewScratch returns a new instance Scratch. +func NewScratch() *Scratch { + return &Scratch{values: make(map[string]interface{})} +} diff --git a/common/maps/scratch_test.go b/common/maps/scratch_test.go new file mode 100644 index 000000000..8397ba830 --- /dev/null +++ b/common/maps/scratch_test.go @@ -0,0 +1,170 @@ +// 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 maps + +import ( + "reflect" + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestScratchAdd(t *testing.T) { + t.Parallel() + scratch := NewScratch() + scratch.Add("int1", 10) + scratch.Add("int1", 20) + scratch.Add("int2", 20) + + assert.Equal(t, int64(30), scratch.Get("int1")) + assert.Equal(t, 20, scratch.Get("int2")) + + scratch.Add("float1", float64(10.5)) + scratch.Add("float1", float64(20.1)) + + assert.Equal(t, float64(30.6), scratch.Get("float1")) + + scratch.Add("string1", "Hello ") + scratch.Add("string1", "big ") + scratch.Add("string1", "World!") + + assert.Equal(t, "Hello big World!", scratch.Get("string1")) + + scratch.Add("scratch", scratch) + _, err := scratch.Add("scratch", scratch) + + if err == nil { + t.Errorf("Expected error from invalid arithmetic") + } + +} + +func TestScratchAddSlice(t *testing.T) { + t.Parallel() + scratch := NewScratch() + + _, err := scratch.Add("intSlice", []int{1, 2}) + assert.Nil(t, err) + _, err = scratch.Add("intSlice", 3) + assert.Nil(t, err) + + sl := scratch.Get("intSlice") + expected := []int{1, 2, 3} + + if !reflect.DeepEqual(expected, sl) { + t.Errorf("Slice difference, go %q expected %q", sl, expected) + } + + _, err = scratch.Add("intSlice", []int{4, 5}) + + assert.Nil(t, err) + + sl = scratch.Get("intSlice") + expected = []int{1, 2, 3, 4, 5} + + if !reflect.DeepEqual(expected, sl) { + t.Errorf("Slice difference, go %q expected %q", sl, expected) + } + +} + +func TestScratchSet(t *testing.T) { + t.Parallel() + scratch := NewScratch() + scratch.Set("key", "val") + assert.Equal(t, "val", scratch.Get("key")) +} + +func TestScratchDelete(t *testing.T) { + t.Parallel() + scratch := NewScratch() + scratch.Set("key", "val") + scratch.Delete("key") + scratch.Add("key", "Lucy Parsons") + assert.Equal(t, "Lucy Parsons", scratch.Get("key")) +} + +// Issue #2005 +func TestScratchInParallel(t *testing.T) { + var wg sync.WaitGroup + scratch := NewScratch() + key := "counter" + scratch.Set(key, int64(1)) + for i := 1; i <= 10; i++ { + wg.Add(1) + go func(j int) { + for k := 0; k < 10; k++ { + newVal := int64(k + j) + + _, err := scratch.Add(key, newVal) + if err != nil { + t.Errorf("Got err %s", err) + } + + scratch.Set(key, newVal) + + val := scratch.Get(key) + + if counter, ok := val.(int64); ok { + if counter < 1 { + t.Errorf("Got %d", counter) + } + } else { + t.Errorf("Got %T", val) + } + } + wg.Done() + }(i) + } + wg.Wait() +} + +func TestScratchGet(t *testing.T) { + t.Parallel() + scratch := NewScratch() + nothing := scratch.Get("nothing") + if nothing != nil { + t.Errorf("Should not return anything, but got %v", nothing) + } +} + +func TestScratchSetInMap(t *testing.T) { + t.Parallel() + scratch := NewScratch() + scratch.SetInMap("key", "lux", "Lux") + scratch.SetInMap("key", "abc", "Abc") + scratch.SetInMap("key", "zyx", "Zyx") + scratch.SetInMap("key", "abc", "Abc (updated)") + scratch.SetInMap("key", "def", "Def") + assert.Equal(t, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"}, scratch.GetSortedMapValues("key")) +} + +func TestScratchGetSortedMapValues(t *testing.T) { + t.Parallel() + scratch := NewScratch() + nothing := scratch.GetSortedMapValues("nothing") + if nothing != nil { + t.Errorf("Should not return anything, but got %v", nothing) + } +} + +func BenchmarkScratchGet(b *testing.B) { + scratch := NewScratch() + scratch.Add("A", 1) + b.ResetTimer() + for i := 0; i < b.N; i++ { + scratch.Get("A") + } +} diff --git a/common/math/math.go b/common/math/math.go new file mode 100644 index 000000000..3c5ef1f9d --- /dev/null +++ b/common/math/math.go @@ -0,0 +1,135 @@ +// 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 math + +import ( + "errors" + "reflect" +) + +// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to +// determine the type of the two terms. +func DoArithmetic(a, b interface{}, op rune) (interface{}, error) { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + var ai, bi int64 + var af, bf float64 + var au, bu uint64 + switch av.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ai = av.Int() + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bi = bv.Int() + case reflect.Float32, reflect.Float64: + af = float64(ai) // may overflow + ai = 0 + bf = bv.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bu = bv.Uint() + if ai >= 0 { + au = uint64(ai) + ai = 0 + } else { + bi = int64(bu) // may overflow + bu = 0 + } + default: + return nil, errors.New("Can't apply the operator to the values") + } + case reflect.Float32, reflect.Float64: + af = av.Float() + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bf = float64(bv.Int()) // may overflow + case reflect.Float32, reflect.Float64: + bf = bv.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bf = float64(bv.Uint()) // may overflow + default: + return nil, errors.New("Can't apply the operator to the values") + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + au = av.Uint() + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bi = bv.Int() + if bi >= 0 { + bu = uint64(bi) + bi = 0 + } else { + ai = int64(au) // may overflow + au = 0 + } + case reflect.Float32, reflect.Float64: + af = float64(au) // may overflow + au = 0 + bf = bv.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bu = bv.Uint() + default: + return nil, errors.New("Can't apply the operator to the values") + } + case reflect.String: + as := av.String() + if bv.Kind() == reflect.String && op == '+' { + bs := bv.String() + return as + bs, nil + } + return nil, errors.New("Can't apply the operator to the values") + default: + return nil, errors.New("Can't apply the operator to the values") + } + + switch op { + case '+': + if ai != 0 || bi != 0 { + return ai + bi, nil + } else if af != 0 || bf != 0 { + return af + bf, nil + } else if au != 0 || bu != 0 { + return au + bu, nil + } + return 0, nil + case '-': + if ai != 0 || bi != 0 { + return ai - bi, nil + } else if af != 0 || bf != 0 { + return af - bf, nil + } else if au != 0 || bu != 0 { + return au - bu, nil + } + return 0, nil + case '*': + if ai != 0 || bi != 0 { + return ai * bi, nil + } else if af != 0 || bf != 0 { + return af * bf, nil + } else if au != 0 || bu != 0 { + return au * bu, nil + } + return 0, nil + case '/': + if bi != 0 { + return ai / bi, nil + } else if bf != 0 { + return af / bf, nil + } else if bu != 0 { + return au / bu, nil + } + return nil, errors.New("Can't divide the value by 0") + default: + return nil, errors.New("There is no such an operation") + } +} diff --git a/common/math/math_test.go b/common/math/math_test.go new file mode 100644 index 000000000..613ac3073 --- /dev/null +++ b/common/math/math_test.go @@ -0,0 +1,109 @@ +// 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 Lice |