From e2201ef15fdefe257ad284b2df4ccc8f8c38fac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 2 Oct 2018 17:35:33 +0200 Subject: [PATCH 1/7] tpl/collections: Fix handling of different interface types in Slice In Hugo `0.49` we improved type support in `slice`. This has an unfortunate side effect in that `resources.Concat` now expects something that can resolve to `resource.Resources`. This worked for most situations, but when you try to `slice` different `Resource` objects, you would be getting `[]interface {}` and not `resource.Resources`. And `concat` would fail: ```bash error calling Concat: slice []interface {} not supported in concat. ``` This commit fixes that by simplifying the type checking logic in `Slice`: * If the first item implements the `Slicer` interface, we try that * If the above fails or the first item does not implement `Slicer`, we just return the `[]interface {}` Fixes #5269 --- hugolib/resource_chain_test.go | 4 ++ tpl/collections/collections.go | 41 +++++++++--------- tpl/collections/collections_test.go | 64 ++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 20 deletions(-) diff --git a/hugolib/resource_chain_test.go b/hugolib/resource_chain_test.go index 045c9517a4b..1f55976f06d 100644 --- a/hugolib/resource_chain_test.go +++ b/hugolib/resource_chain_test.go @@ -238,6 +238,10 @@ T1: Content: {{ $combined.Content }}|RelPermalink: {{ $combined.RelPermalink }}| {{ $combinedText := . | resources.Concat "bundle/concattxt.txt" }} T2: Content: {{ $combinedText.Content }}|{{ $combinedText.RelPermalink }} {{ end }} +{{/* https://github.com/gohugoio/hugo/issues/5269 */}} +{{ $css := "body { color: blue; }" | resources.FromString "styles.css" }} +{{ $minified := resources.Get "css/styles1.css" | minify }} +{{ slice $css $minified | resources.Concat "bundle/mixed.css" }} `) }, func(b *sitesBuilder) { b.AssertFileContent("public/index.html", `T1: Content: ABC|RelPermalink: /bundle/concat.txt|Permalink: http://example.com/bundle/concat.txt|MediaType: text/plain`) diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index 4914216315c..1d817077f5c 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -522,33 +522,36 @@ func (ns *Namespace) Slice(args ...interface{}) interface{} { first := args[0] firstType := reflect.TypeOf(first) - allTheSame := firstType != nil - if allTheSame && len(args) > 1 { + if firstType == nil { + return args + } + + if g, ok := first.(collections.Slicer); ok { + v, err := g.Slice(args) + if err == nil { + return v + } + + // If Slice fails, the items are not of the same type and + // []interface{} is the best we can do. + return args + } + + if len(args) > 1 { // This can be a mix of types. for i := 1; i < len(args); i++ { if firstType != reflect.TypeOf(args[i]) { - allTheSame = false - break + // []interface{} is the best we can do + return args } } } - if allTheSame { - if g, ok := first.(collections.Slicer); ok { - v, err := g.Slice(args) - if err == nil { - return v - } - } else { - slice := reflect.MakeSlice(reflect.SliceOf(firstType), len(args), len(args)) - for i, arg := range args { - slice.Index(i).Set(reflect.ValueOf(arg)) - } - return slice.Interface() - } + slice := reflect.MakeSlice(reflect.SliceOf(firstType), len(args), len(args)) + for i, arg := range args { + slice.Index(i).Set(reflect.ValueOf(arg)) } - - return args + return slice.Interface() } type intersector struct { diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index 7b15151d263..8f122569c57 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -643,16 +643,76 @@ func TestShuffleRandomising(t *testing.T) { } var _ collections.Slicer = (*tstSlicer)(nil) +var _ collections.Slicer = (*tstSlicerIn1)(nil) +var _ collections.Slicer = (*tstSlicerIn2)(nil) +var _ testSlicerInterface = (*tstSlicerIn1)(nil) +var _ testSlicerInterface = (*tstSlicerIn1)(nil) + +type testSlicerInterface interface { + Name() string +} + +type testSlicerInterfaces []testSlicerInterface + +type tstSlicerIn1 struct { + name string +} + +type tstSlicerIn2 struct { + name string +} type tstSlicer struct { name string } +func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) { + items := in.([]interface{}) + result := make(testSlicerInterfaces, len(items)) + for i, v := range items { + switch vv := v.(type) { + case testSlicerInterface: + result[i] = vv + default: + return nil, errors.New("invalid type") + } + + } + return result, nil +} + +func (p *tstSlicerIn2) Slice(in interface{}) (interface{}, error) { + items := in.([]interface{}) + result := make(testSlicerInterfaces, len(items)) + for i, v := range items { + switch vv := v.(type) { + case testSlicerInterface: + result[i] = vv + default: + return nil, errors.New("invalid type") + } + } + return result, nil +} + +func (p *tstSlicerIn1) Name() string { + return p.Name() +} + +func (p *tstSlicerIn2) Name() string { + return p.Name() +} + func (p *tstSlicer) Slice(in interface{}) (interface{}, error) { items := in.([]interface{}) result := make(tstSlicers, len(items)) for i, v := range items { - result[i] = v.(*tstSlicer) + switch vv := v.(type) { + case *tstSlicer: + result[i] = vv + default: + return nil, errors.New("invalid type") + } } return result, nil } @@ -675,6 +735,8 @@ func TestSlice(t *testing.T) { {[]interface{}{nil}, []interface{}{nil}}, {[]interface{}{5, "b"}, []interface{}{5, "b"}}, {[]interface{}{tstNoStringer{}}, []tstNoStringer{tstNoStringer{}}}, + {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}}, + {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}}, } { errMsg := fmt.Sprintf("[%d] %v", i, test.args) From 23f48c300cb5ffe0fe43c88464f38c68831a17ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 8 Oct 2018 10:25:15 +0200 Subject: [PATCH 2/7] common/maps: Improve append in Scratch This commit consolidates the reflective collections handling in `.Scratch` vs the `tpl` package so they use the same code paths. This commit also adds support for a corner case where a typed slice is appended to a nil or empty `[]interface{}`. Fixes #5275 --- common/collections/append.go | 83 ++++++++++++++++++ common/collections/append_test.go | 70 ++++++++++++++++ common/collections/collections.go | 7 -- common/collections/slice.go | 66 +++++++++++++++ common/collections/slice_test.go | 125 ++++++++++++++++++++++++++++ common/maps/scratch.go | 11 ++- common/maps/scratch_test.go | 47 ++++++++--- tpl/collections/append.go | 42 +--------- tpl/collections/append_test.go | 13 +-- tpl/collections/apply.go | 2 +- tpl/collections/collections.go | 34 +------- tpl/collections/collections_test.go | 83 +----------------- 12 files changed, 391 insertions(+), 192 deletions(-) create mode 100644 common/collections/append.go create mode 100644 common/collections/append_test.go create mode 100644 common/collections/slice.go create mode 100644 common/collections/slice_test.go diff --git a/common/collections/append.go b/common/collections/append.go new file mode 100644 index 00000000000..e1008843b96 --- /dev/null +++ b/common/collections/append.go @@ -0,0 +1,83 @@ +// 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 + +import ( + "fmt" + "reflect" +) + +// Append appends from to a slice to and returns the resulting slice. +// If lenght of from is one and the only element is a slice of same type as to, +// it will be appended. +func Append(to interface{}, from ...interface{}) (interface{}, error) { + tov, toIsNil := indirect(reflect.ValueOf(to)) + + toIsNil = toIsNil || to == nil + var tot reflect.Type + + if !toIsNil { + if tov.Kind() != reflect.Slice { + return nil, fmt.Errorf("expected a slice, got %T", to) + } + + tot = tov.Type().Elem() + toIsNil = tov.Len() == 0 + + if len(from) == 1 { + fromv := reflect.ValueOf(from[0]) + if fromv.Kind() == reflect.Slice { + if toIsNil { + // If we get nil []string, we just return the []string + return from[0], nil + } + + fromt := reflect.TypeOf(from[0]).Elem() + + // If we get []string []string, we append the from slice to to + if tot == fromt { + return reflect.AppendSlice(tov, fromv).Interface(), nil + } + } + } + } + + if toIsNil { + return Slice(from...), nil + } + + for _, f := range from { + fv := reflect.ValueOf(f) + if tot != fv.Type() { + return nil, fmt.Errorf("append element type mismatch: expected %v, got %v", tot, fv.Type()) + } + tov = reflect.Append(tov, fv) + } + + return tov.Interface(), nil +} + +// indirect is borrowed from the Go stdlib: 'text/template/exec.go' +// TODO(bep) consolidate +func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { + for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { + if v.IsNil() { + return v, true + } + if v.Kind() == reflect.Interface && v.NumMethod() > 0 { + break + } + } + return v, false +} diff --git a/common/collections/append_test.go b/common/collections/append_test.go new file mode 100644 index 00000000000..e3361fb26be --- /dev/null +++ b/common/collections/append_test.go @@ -0,0 +1,70 @@ +// 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 + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAppend(t *testing.T) { + t.Parallel() + + for i, test := range []struct { + start interface{} + addend []interface{} + expected interface{} + }{ + {[]string{"a", "b"}, []interface{}{"c"}, []string{"a", "b", "c"}}, + {[]string{"a", "b"}, []interface{}{"c", "d", "e"}, []string{"a", "b", "c", "d", "e"}}, + {[]string{"a", "b"}, []interface{}{[]string{"c", "d", "e"}}, []string{"a", "b", "c", "d", "e"}}, + {nil, []interface{}{"a", "b"}, []string{"a", "b"}}, + {nil, []interface{}{nil}, []interface{}{nil}}, + {[]interface{}{}, []interface{}{[]string{"c", "d", "e"}}, []string{"c", "d", "e"}}, + {tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}, + []interface{}{&tstSlicer{"c"}}, + tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}, &tstSlicer{"c"}}}, + {&tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}, + []interface{}{&tstSlicer{"c"}}, + tstSlicers{&tstSlicer{"a"}, + &tstSlicer{"b"}, + &tstSlicer{"c"}}}, + // Errors + {"", []interface{}{[]string{"a", "b"}}, false}, + // No string concatenation. + {"ab", + []interface{}{"c"}, + false}, + } { + + errMsg := fmt.Sprintf("[%d]", i) + + result, err := Append(test.start, test.addend...) + + if b, ok := test.expected.(bool); ok && !b { + require.Error(t, err, errMsg) + continue + } + + require.NoError(t, err, errMsg) + + if !reflect.DeepEqual(test.expected, result) { + t.Fatalf("%s got\n%T: %v\nexpected\n%T: %v", errMsg, result, result, test.expected, test.expected) + } + } + +} diff --git a/common/collections/collections.go b/common/collections/collections.go index f2dd3071d82..bb47c8acca6 100644 --- a/common/collections/collections.go +++ b/common/collections/collections.go @@ -19,10 +19,3 @@ package collections 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/collections/slice.go b/common/collections/slice.go new file mode 100644 index 00000000000..380d3d3293e --- /dev/null +++ b/common/collections/slice.go @@ -0,0 +1,66 @@ +// 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 + +import ( + "reflect" +) + +// 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) +} + +// Slice returns a slice of all passed arguments. +func Slice(args ...interface{}) interface{} { + if len(args) == 0 { + return args + } + + first := args[0] + firstType := reflect.TypeOf(first) + + if firstType == nil { + return args + } + + if g, ok := first.(Slicer); ok { + v, err := g.Slice(args) + if err == nil { + return v + } + + // If Slice fails, the items are not of the same type and + // []interface{} is the best we can do. + return args + } + + if len(args) > 1 { + // This can be a mix of types. + for i := 1; i < len(args); i++ { + if firstType != reflect.TypeOf(args[i]) { + // []interface{} is the best we can do + return args + } + } + } + + slice := reflect.MakeSlice(reflect.SliceOf(firstType), len(args), len(args)) + for i, arg := range args { + slice.Index(i).Set(reflect.ValueOf(arg)) + } + return slice.Interface() +} diff --git a/common/collections/slice_test.go b/common/collections/slice_test.go new file mode 100644 index 00000000000..1103e2feaa2 --- /dev/null +++ b/common/collections/slice_test.go @@ -0,0 +1,125 @@ +// 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 + +import ( + "errors" + "fmt" + "testing" + + "github.com/alecthomas/assert" +) + +var _ Slicer = (*tstSlicer)(nil) +var _ Slicer = (*tstSlicerIn1)(nil) +var _ Slicer = (*tstSlicerIn2)(nil) +var _ testSlicerInterface = (*tstSlicerIn1)(nil) +var _ testSlicerInterface = (*tstSlicerIn1)(nil) + +type testSlicerInterface interface { + Name() string +} + +type testSlicerInterfaces []testSlicerInterface + +type tstSlicerIn1 struct { + name string +} + +type tstSlicerIn2 struct { + name string +} + +type tstSlicer struct { + name string +} + +func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) { + items := in.([]interface{}) + result := make(testSlicerInterfaces, len(items)) + for i, v := range items { + switch vv := v.(type) { + case testSlicerInterface: + result[i] = vv + default: + return nil, errors.New("invalid type") + } + + } + return result, nil +} + +func (p *tstSlicerIn2) Slice(in interface{}) (interface{}, error) { + items := in.([]interface{}) + result := make(testSlicerInterfaces, len(items)) + for i, v := range items { + switch vv := v.(type) { + case testSlicerInterface: + result[i] = vv + default: + return nil, errors.New("invalid type") + } + } + return result, nil +} + +func (p *tstSlicerIn1) Name() string { + return p.Name() +} + +func (p *tstSlicerIn2) Name() string { + return p.Name() +} + +func (p *tstSlicer) Slice(in interface{}) (interface{}, error) { + items := in.([]interface{}) + result := make(tstSlicers, len(items)) + for i, v := range items { + switch vv := v.(type) { + case *tstSlicer: + result[i] = vv + default: + return nil, errors.New("invalid type") + } + } + return result, nil +} + +type tstSlicers []*tstSlicer + +func TestSlice(t *testing.T) { + t.Parallel() + + for i, test := range []struct { + args []interface{} + expected interface{} + }{ + {[]interface{}{"a", "b"}, []string{"a", "b"}}, + {[]interface{}{&tstSlicer{"a"}, &tstSlicer{"b"}}, tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}}, + {[]interface{}{&tstSlicer{"a"}, "b"}, []interface{}{&tstSlicer{"a"}, "b"}}, + {[]interface{}{}, []interface{}{}}, + {[]interface{}{nil}, []interface{}{nil}}, + {[]interface{}{5, "b"}, []interface{}{5, "b"}}, + {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}}, + {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}}, + } { + errMsg := fmt.Sprintf("[%d] %v", i, test.args) + + result := Slice(test.args...) + + assert.Equal(t, test.expected, result, errMsg) + } + + assert.Len(t, Slice(), 0) +} diff --git a/common/maps/scratch.go b/common/maps/scratch.go index 4b062d13930..2972e202200 100644 --- a/common/maps/scratch.go +++ b/common/maps/scratch.go @@ -18,6 +18,7 @@ import ( "sort" "sync" + "github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/common/math" ) @@ -40,14 +41,12 @@ func (c *Scratch) Add(key string, newAddend interface{}) (string, error) { if found { var err error - addendV := reflect.ValueOf(existingAddend) + addendV := reflect.TypeOf(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() + newVal, err = collections.Append(existingAddend, newAddend) + if err != nil { + return "", err } } else { newVal, err = math.DoArithmetic(existingAddend, newAddend, '+') diff --git a/common/maps/scratch_test.go b/common/maps/scratch_test.go index 8397ba830a7..bf37d79dfb5 100644 --- a/common/maps/scratch_test.go +++ b/common/maps/scratch_test.go @@ -18,29 +18,31 @@ import ( "sync" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestScratchAdd(t *testing.T) { t.Parallel() + assert := require.New(t) + 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")) + assert.Equal(int64(30), scratch.Get("int1")) + assert.Equal(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")) + assert.Equal(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")) + assert.Equal("Hello big World!", scratch.Get("string1")) scratch.Add("scratch", scratch) _, err := scratch.Add("scratch", scratch) @@ -53,12 +55,14 @@ func TestScratchAdd(t *testing.T) { func TestScratchAddSlice(t *testing.T) { t.Parallel() + assert := require.New(t) + scratch := NewScratch() _, err := scratch.Add("intSlice", []int{1, 2}) - assert.Nil(t, err) + assert.NoError(err) _, err = scratch.Add("intSlice", 3) - assert.Nil(t, err) + assert.NoError(err) sl := scratch.Get("intSlice") expected := []int{1, 2, 3} @@ -66,10 +70,9 @@ func TestScratchAddSlice(t *testing.T) { 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) + assert.NoError(err) sl = scratch.Get("intSlice") expected = []int{1, 2, 3, 4, 5} @@ -77,29 +80,47 @@ func TestScratchAddSlice(t *testing.T) { if !reflect.DeepEqual(expected, sl) { t.Errorf("Slice difference, go %q expected %q", sl, expected) } +} + +// https://github.com/gohugoio/hugo/issues/5275 +func TestScratchAddTypedSliceToInterfaceSlice(t *testing.T) { + t.Parallel() + assert := require.New(t) + + scratch := NewScratch() + scratch.Set("slice", []interface{}{}) + + _, err := scratch.Add("slice", []int{1, 2}) + assert.NoError(err) + assert.Equal([]int{1, 2}, scratch.Get("slice")) } func TestScratchSet(t *testing.T) { t.Parallel() + assert := require.New(t) + scratch := NewScratch() scratch.Set("key", "val") - assert.Equal(t, "val", scratch.Get("key")) + assert.Equal("val", scratch.Get("key")) } func TestScratchDelete(t *testing.T) { t.Parallel() + assert := require.New(t) + scratch := NewScratch() scratch.Set("key", "val") scratch.Delete("key") scratch.Add("key", "Lucy Parsons") - assert.Equal(t, "Lucy Parsons", scratch.Get("key")) + assert.Equal("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++ { @@ -142,13 +163,15 @@ func TestScratchGet(t *testing.T) { func TestScratchSetInMap(t *testing.T) { t.Parallel() + assert := require.New(t) + 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")) + assert.Equal([]interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"}, scratch.GetSortedMapValues("key")) } func TestScratchGetSortedMapValues(t *testing.T) { diff --git a/tpl/collections/append.go b/tpl/collections/append.go index 20afa0e7229..297328dc88a 100644 --- a/tpl/collections/append.go +++ b/tpl/collections/append.go @@ -15,8 +15,8 @@ package collections import ( "errors" - "fmt" - "reflect" + + "github.com/gohugoio/hugo/common/collections" ) // Append appends the arguments up to the last one to the slice in the last argument. @@ -33,42 +33,6 @@ func (ns *Namespace) Append(args ...interface{}) (interface{}, error) { to := args[len(args)-1] from := args[:len(args)-1] - tov, toIsNil := indirect(reflect.ValueOf(to)) - - toIsNil = toIsNil || to == nil - var tot reflect.Type - - if !toIsNil { - if tov.Kind() != reflect.Slice { - return nil, fmt.Errorf("expected a slice, got %T", to) - } - - tot = tov.Type().Elem() - toIsNil = tov.Len() == 0 - - if len(from) == 1 { - // If we get []string []string, we append the from slice to to - fromv := reflect.ValueOf(from[0]) - if fromv.Kind() == reflect.Slice { - fromt := reflect.TypeOf(from[0]).Elem() - if tot == fromt { - return reflect.AppendSlice(tov, fromv).Interface(), nil - } - } - } - } - - if toIsNil { - return ns.Slice(from...), nil - } - - for _, f := range from { - fv := reflect.ValueOf(f) - if tot != fv.Type() { - return nil, fmt.Errorf("append element type mismatch: expected %v, got %v", tot, fv.Type()) - } - tov = reflect.Append(tov, fv) - } + return collections.Append(to, from...) - return tov.Interface(), nil } diff --git a/tpl/collections/append_test.go b/tpl/collections/append_test.go index b0a751fb812..f886aca2238 100644 --- a/tpl/collections/append_test.go +++ b/tpl/collections/append_test.go @@ -18,11 +18,11 @@ import ( "reflect" "testing" - "github.com/alecthomas/assert" "github.com/gohugoio/hugo/deps" "github.com/stretchr/testify/require" ) +// Also see tests in common/collection. func TestAppend(t *testing.T) { t.Parallel() @@ -36,16 +36,6 @@ func TestAppend(t *testing.T) { {[]string{"a", "b"}, []interface{}{"c"}, []string{"a", "b", "c"}}, {[]string{"a", "b"}, []interface{}{"c", "d", "e"}, []string{"a", "b", "c", "d", "e"}}, {[]string{"a", "b"}, []interface{}{[]string{"c", "d", "e"}}, []string{"a", "b", "c", "d", "e"}}, - {nil, []interface{}{"a", "b"}, []string{"a", "b"}}, - {nil, []interface{}{nil}, []interface{}{nil}}, - {tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}, - []interface{}{&tstSlicer{"c"}}, - tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}, &tstSlicer{"c"}}}, - {&tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}, - []interface{}{&tstSlicer{"c"}}, - tstSlicers{&tstSlicer{"a"}, - &tstSlicer{"b"}, - &tstSlicer{"c"}}}, // Errors {"", []interface{}{[]string{"a", "b"}}, false}, {[]string{"a", "b"}, []interface{}{}, false}, @@ -73,5 +63,4 @@ func TestAppend(t *testing.T) { } } - assert.Len(t, ns.Slice(), 0) } diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go index 0b2b0062197..d715aeb007d 100644 --- a/tpl/collections/apply.go +++ b/tpl/collections/apply.go @@ -136,7 +136,7 @@ func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) { return m, true } -// indirect is taken from 'text/template/exec.go' +// indirect is borrowed from the Go stdlib: 'text/template/exec.go' func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { if v.IsNil() { diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index 1d817077f5c..1f7ce6fa347 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -519,39 +519,7 @@ func (ns *Namespace) Slice(args ...interface{}) interface{} { return args } - first := args[0] - firstType := reflect.TypeOf(first) - - if firstType == nil { - return args - } - - if g, ok := first.(collections.Slicer); ok { - v, err := g.Slice(args) - if err == nil { - return v - } - - // If Slice fails, the items are not of the same type and - // []interface{} is the best we can do. - return args - } - - if len(args) > 1 { - // This can be a mix of types. - for i := 1; i < len(args); i++ { - if firstType != reflect.TypeOf(args[i]) { - // []interface{} is the best we can do - return args - } - } - } - - slice := reflect.MakeSlice(reflect.SliceOf(firstType), len(args), len(args)) - for i, arg := range args { - slice.Index(i).Set(reflect.ValueOf(arg)) - } - return slice.Interface() + return collections.Slice(args...) } type intersector struct { diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index 8f122569c57..cf17727ff26 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -25,7 +25,6 @@ import ( "testing" "time" - "github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" @@ -642,83 +641,7 @@ func TestShuffleRandomising(t *testing.T) { } } -var _ collections.Slicer = (*tstSlicer)(nil) -var _ collections.Slicer = (*tstSlicerIn1)(nil) -var _ collections.Slicer = (*tstSlicerIn2)(nil) -var _ testSlicerInterface = (*tstSlicerIn1)(nil) -var _ testSlicerInterface = (*tstSlicerIn1)(nil) - -type testSlicerInterface interface { - Name() string -} - -type testSlicerInterfaces []testSlicerInterface - -type tstSlicerIn1 struct { - name string -} - -type tstSlicerIn2 struct { - name string -} - -type tstSlicer struct { - name string -} - -func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) { - items := in.([]interface{}) - result := make(testSlicerInterfaces, len(items)) - for i, v := range items { - switch vv := v.(type) { - case testSlicerInterface: - result[i] = vv - default: - return nil, errors.New("invalid type") - } - - } - return result, nil -} - -func (p *tstSlicerIn2) Slice(in interface{}) (interface{}, error) { - items := in.([]interface{}) - result := make(testSlicerInterfaces, len(items)) - for i, v := range items { - switch vv := v.(type) { - case testSlicerInterface: - result[i] = vv - default: - return nil, errors.New("invalid type") - } - } - return result, nil -} - -func (p *tstSlicerIn1) Name() string { - return p.Name() -} - -func (p *tstSlicerIn2) Name() string { - return p.Name() -} - -func (p *tstSlicer) Slice(in interface{}) (interface{}, error) { - items := in.([]interface{}) - result := make(tstSlicers, len(items)) - for i, v := range items { - switch vv := v.(type) { - case *tstSlicer: - result[i] = vv - default: - return nil, errors.New("invalid type") - } - } - return result, nil -} - -type tstSlicers []*tstSlicer - +// Also see tests in commons/collection. func TestSlice(t *testing.T) { t.Parallel() @@ -729,14 +652,10 @@ func TestSlice(t *testing.T) { expected interface{} }{ {[]interface{}{"a", "b"}, []string{"a", "b"}}, - {[]interface{}{&tstSlicer{"a"}, &tstSlicer{"b"}}, tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}}, - {[]interface{}{&tstSlicer{"a"}, "b"}, []interface{}{&tstSlicer{"a"}, "b"}}, {[]interface{}{}, []interface{}{}}, {[]interface{}{nil}, []interface{}{nil}}, {[]interface{}{5, "b"}, []interface{}{5, "b"}}, {[]interface{}{tstNoStringer{}}, []tstNoStringer{tstNoStringer{}}}, - {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}}, - {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}}, } { errMsg := fmt.Sprintf("[%d] %v", i, test.args) From ac0fac934711ef3fd1cd8c3949d8fb405f602ede Mon Sep 17 00:00:00 2001 From: hugoreleaser Date: Wed, 10 Oct 2018 10:28:25 +0000 Subject: [PATCH 3/7] releaser: Add release notes draft for 0.49.1 Rename to *-ready.md to continue. [ci skip] --- temp/0.49.1-relnotes.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 temp/0.49.1-relnotes.md diff --git a/temp/0.49.1-relnotes.md b/temp/0.49.1-relnotes.md new file mode 100644 index 00000000000..5c041457a7b --- /dev/null +++ b/temp/0.49.1-relnotes.md @@ -0,0 +1,27 @@ + + +This is a bug-fix release with a couple of important fixes. + + +Hugo now has: + +* 29397+ [stars](https://github.com/gohugoio/hugo/stargazers) +* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors) +* 268+ [themes](http://themes.gohugo.io/) + +## Enhancements + +### Other + +* Improve append in Scratch [23f48c30](https://github.com/gohugoio/hugo/commit/23f48c300cb5ffe0fe43c88464f38c68831a17ad) [@bep](https://github.com/bep) [#5275](https://github.com/gohugoio/hugo/issues/5275) + +## Fixes + +### Templates + +* Fix handling of different interface types in Slice [e2201ef1](https://github.com/gohugoio/hugo/commit/e2201ef15fdefe257ad284b2df4ccc8f8c38fac2) [@bep](https://github.com/bep) [#5269](https://github.com/gohugoio/hugo/issues/5269) + + + + + From 235acf22321475895442ce49ca5d16be273c1e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 10 Oct 2018 12:31:46 +0200 Subject: [PATCH 4/7] Relase 0.49.1 --- ....1-relnotes.md => 0.49.1-relnotes-ready.md} | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) rename temp/{0.49.1-relnotes.md => 0.49.1-relnotes-ready.md} (59%) diff --git a/temp/0.49.1-relnotes.md b/temp/0.49.1-relnotes-ready.md similarity index 59% rename from temp/0.49.1-relnotes.md rename to temp/0.49.1-relnotes-ready.md index 5c041457a7b..b99473107c9 100644 --- a/temp/0.49.1-relnotes.md +++ b/temp/0.49.1-relnotes-ready.md @@ -1,26 +1,12 @@ -This is a bug-fix release with a couple of important fixes. +This is a bug-fix release with a 2 related fixed. This was introduced in Hugo 0.49. The most notable error situation was that `resources.Concat` could fail in some situations. -Hugo now has: - -* 29397+ [stars](https://github.com/gohugoio/hugo/stargazers) -* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors) -* 268+ [themes](http://themes.gohugo.io/) - -## Enhancements - -### Other +* Fix handling of different interface types in Slice [e2201ef1](https://github.com/gohugoio/hugo/commit/e2201ef15fdefe257ad284b2df4ccc8f8c38fac2) [@bep](https://github.com/bep) [#5269](https://github.com/gohugoio/hugo/issues/5269) * Improve append in Scratch [23f48c30](https://github.com/gohugoio/hugo/commit/23f48c300cb5ffe0fe43c88464f38c68831a17ad) [@bep](https://github.com/bep) [#5275](https://github.com/gohugoio/hugo/issues/5275) -## Fixes - -### Templates - -* Fix handling of different interface types in Slice [e2201ef1](https://github.com/gohugoio/hugo/commit/e2201ef15fdefe257ad284b2df4ccc8f8c38fac2) [@bep](https://github.com/bep) [#5269](https://github.com/gohugoio/hugo/issues/5269) - From 8ba9a273669e98a0e8d2f7db0291acc3a0a66ee1 Mon Sep 17 00:00:00 2001 From: hugoreleaser Date: Wed, 10 Oct 2018 10:36:37 +0000 Subject: [PATCH 5/7] releaser: Bump versions for release of 0.49.1 [ci skip] --- docs/config.toml | 2 +- helpers/hugo.go | 2 +- snapcraft.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/config.toml b/docs/config.toml index 22790b8b5ef..f54bc7f921d 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -69,7 +69,7 @@ twitter = "GoHugoIO" [params] description = "The world’s fastest framework for building websites" ## Used for views in rendered HTML (i.e., rather than using the .Hugo variable) - release = "0.49" + release = "0.49.1" ## Setting this to true will add a "noindex" to *EVERY* page on the site removefromexternalsearch = false ## Gh repo for site footer (include trailing slash) diff --git a/helpers/hugo.go b/helpers/hugo.go index d95a155c94a..d54e049c7d3 100644 --- a/helpers/hugo.go +++ b/helpers/hugo.go @@ -126,7 +126,7 @@ func (v HugoVersion) NextPatchLevel(level int) HugoVersion { // This should be the only one. var CurrentHugoVersion = HugoVersion{ Number: 0.49, - PatchLevel: 0, + PatchLevel: 1, Suffix: "", } diff --git a/snapcraft.yaml b/snapcraft.yaml index ee6833bb5c6..63a7e8652e0 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: hugo -version: "0.49" +version: "0.49.1" summary: Fast and Flexible Static Site Generator description: | Hugo is a static HTML and CSS website generator written in Go. It is From 821adf3ae877fdddce67afcccd751d47f4589538 Mon Sep 17 00:00:00 2001 From: hugoreleaser Date: Wed, 10 Oct 2018 10:36:37 +0000 Subject: [PATCH 6/7] releaser: Add release notes to /docs for release of 0.49.1 [ci skip] --- docs/content/en/news/0.49.1-relnotes/index.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 docs/content/en/news/0.49.1-relnotes/index.md diff --git a/docs/content/en/news/0.49.1-relnotes/index.md b/docs/content/en/news/0.49.1-relnotes/index.md new file mode 100644 index 00000000000..9327a706089 --- /dev/null +++ b/docs/content/en/news/0.49.1-relnotes/index.md @@ -0,0 +1,24 @@ + +--- +date: 2018-10-10 +title: "0.49.1" +description: "0.49.1" +categories: ["Releases"] +images: +- images/blog/hugo-bug-poster.png + +--- + + + +This is a bug-fix release with a 2 related fixed. This was introduced in Hugo 0.49. The most notable error situation was that `resources.Concat` could fail in some situations. + + +* Fix handling of different interface types in Slice [e2201ef1](https://github.com/gohugoio/hugo/commit/e2201ef15fdefe257ad284b2df4ccc8f8c38fac2) [@bep](https://github.com/bep) [#5269](https://github.com/gohugoio/hugo/issues/5269) + +* Improve append in Scratch [23f48c30](https://github.com/gohugoio/hugo/commit/23f48c300cb5ffe0fe43c88464f38c68831a17ad) [@bep](https://github.com/bep) [#5275](https://github.com/gohugoio/hugo/issues/5275) + + + + + From e5b4cb2419b67b57ac0a76c4cf82368952780f1e Mon Sep 17 00:00:00 2001 From: hugoreleaser Date: Wed, 10 Oct 2018 10:38:45 +0000 Subject: [PATCH 7/7] releaser: Prepare repository for 0.50-DEV [ci skip] --- docs/config.toml | 2 +- helpers/hugo.go | 6 +++--- snapcraft.yaml | 4 ++-- temp/0.49.1-relnotes-ready.md | 13 ------------- 4 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 temp/0.49.1-relnotes-ready.md diff --git a/docs/config.toml b/docs/config.toml index f54bc7f921d..054ccab5685 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -69,7 +69,7 @@ twitter = "GoHugoIO" [params] description = "The world’s fastest framework for building websites" ## Used for views in rendered HTML (i.e., rather than using the .Hugo variable) - release = "0.49.1" + release = "0.50-DEV" ## Setting this to true will add a "noindex" to *EVERY* page on the site removefromexternalsearch = false ## Gh repo for site footer (include trailing slash) diff --git a/helpers/hugo.go b/helpers/hugo.go index d54e049c7d3..b3ded9d5d0b 100644 --- a/helpers/hugo.go +++ b/helpers/hugo.go @@ -125,9 +125,9 @@ func (v HugoVersion) NextPatchLevel(level int) HugoVersion { // CurrentHugoVersion represents the current build version. // This should be the only one. var CurrentHugoVersion = HugoVersion{ - Number: 0.49, - PatchLevel: 1, - Suffix: "", + Number: 0.50, + PatchLevel: 0, + Suffix: "-DEV", } func hugoVersion(version float32, patchVersion int, suffix string) string { diff --git a/snapcraft.yaml b/snapcraft.yaml index 63a7e8652e0..64493c5cd94 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,12 +1,12 @@ name: hugo -version: "0.49.1" +version: "0.50-DEV" summary: Fast and Flexible Static Site Generator description: | Hugo is a static HTML and CSS website generator written in Go. It is optimized for speed, easy use and configurability. Hugo takes a directory with content and templates and renders them into a full HTML website. confinement: strict -grade: stable # "devel" or "stable" +grade: devel # "devel" or "stable" environment: GO111MODULE: on diff --git a/temp/0.49.1-relnotes-ready.md b/temp/0.49.1-relnotes-ready.md deleted file mode 100644 index b99473107c9..00000000000 --- a/temp/0.49.1-relnotes-ready.md +++ /dev/null @@ -1,13 +0,0 @@ - - -This is a bug-fix release with a 2 related fixed. This was introduced in Hugo 0.49. The most notable error situation was that `resources.Concat` could fail in some situations. - - -* Fix handling of different interface types in Slice [e2201ef1](https://github.com/gohugoio/hugo/commit/e2201ef15fdefe257ad284b2df4ccc8f8c38fac2) [@bep](https://github.com/bep) [#5269](https://github.com/gohugoio/hugo/issues/5269) - -* Improve append in Scratch [23f48c30](https://github.com/gohugoio/hugo/commit/23f48c300cb5ffe0fe43c88464f38c68831a17ad) [@bep](https://github.com/bep) [#5275](https://github.com/gohugoio/hugo/issues/5275) - - - - -