Skip to content

Commit

Permalink
tpl: Add reflect namespace
Browse files Browse the repository at this point in the history
Adds KindIs, KindOf, TypeIs, TypeIsLike, and TypeOf funcs in a reflect
namespace.

Fixes gohugoio#4081
  • Loading branch information
moorereason committed Dec 15, 2017
1 parent 9df3736 commit b775f2a
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 0 deletions.
72 changes: 72 additions & 0 deletions tpl/reflect/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2017 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 reflect

import (
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
)

const name = "reflect"

func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := New()

ns := &internal.TemplateFuncsNamespace{
Name: name,
Context: func(args ...interface{}) interface{} { return ctx },
}

ns.AddMethodMapping(ctx.KindIs,
nil,
[][2]string{
{`{{ reflect.KindIs "map" (dict "one" 1) }}`, "true"},
},
)

ns.AddMethodMapping(ctx.KindOf,
nil,
[][2]string{
{`{{ reflect.KindOf (dict "one" 1) }}`, "map"},
},
)

ns.AddMethodMapping(ctx.TypeIs,
nil,
[][2]string{
{`{{ reflect.TypeIs "map[string]interface {}" (dict "one" 1) }}`, "true"},
},
)

ns.AddMethodMapping(ctx.TypeIsLike,
nil,
[][2]string{
{`{{ reflect.TypeIsLike "map[string]interface {}" (dict "one" 1) }}`, "true"},
},
)

ns.AddMethodMapping(ctx.TypeOf,
nil,
[][2]string{
{`{{ reflect.TypeOf (dict "one" 1) }}`, "map[string]interface {}"},
},
)

return ns

}

internal.AddTemplateFuncsNamespace(f)
}
38 changes: 38 additions & 0 deletions tpl/reflect/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2017 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 reflect

import (
"testing"

"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/stretchr/testify/require"
)

func TestInit(t *testing.T) {
var found bool
var ns *internal.TemplateFuncsNamespace

for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
ns = nsf(&deps.Deps{})
if ns.Name == name {
found = true
break
}
}

require.True(t, found)
require.IsType(t, &Namespace{}, ns.Context())
}
53 changes: 53 additions & 0 deletions tpl/reflect/reflect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2017 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 reflect

import (
"fmt"
"reflect"
)

// New returns a new instance of the reflect-namespaced template functions.
func New() *Namespace {
return &Namespace{}
}

// Namespace provides template functions for the "reflect" namespace.
type Namespace struct{}

// KindIs reports whether v is of kind k.
func (ns *Namespace) KindIs(k string, v interface{}) bool {
return ns.KindOf(v) == k
}

// KindOf reports v's kind.
func (ns *Namespace) KindOf(v interface{}) string {
return reflect.ValueOf(v).Kind().String()
}

// TypeIs reports whether v is of type t.
func (ns *Namespace) TypeIs(t string, v interface{}) bool {
return ns.TypeOf(v) == t
}

// TypeIsLike reports whether v is of type t of a pointer to type t.
func (ns *Namespace) TypeIsLike(t string, v interface{}) bool {
s := ns.TypeOf(v)
return s == t || s == "*"+t
}

// TypeOf reports v's type.
func (ns *Namespace) TypeOf(v interface{}) string {
return fmt.Sprintf("%T", v)
}
150 changes: 150 additions & 0 deletions tpl/reflect/reflect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright 2017 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 reflect

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

var ns = New()

type tstNoStringer struct{}

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

for i, test := range []struct {
k string
v interface{}
expect interface{}
}{
{"invalid", nil, true},
{"string", "foo", true},
{"int16", int16(1), true},
{"slice", []int{1}, true},
{"map", map[int]int{1: 1}, true},
{"map", map[int]interface{}{1: "a"}, true},
{"struct", tstNoStringer{}, true},
{"ptr", &tstNoStringer{}, true},
{"float", 1.1, false},
} {
errMsg := fmt.Sprintf("[%d] %v", i, test)

result := ns.KindIs(test.k, test.v)

assert.Equal(t, test.expect, result, errMsg)
}
}

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

for i, test := range []struct {
v interface{}
expect interface{}
}{
{nil, "invalid"},
{"foo", "string"},
{int16(1), "int16"},
{[]int{1, 2}, "slice"},
{map[int]int{1: 1}, "map"},
{tstNoStringer{}, "struct"},
{&tstNoStringer{}, "ptr"},
} {
errMsg := fmt.Sprintf("[%d] %v", i, test)

result := ns.KindOf(test.v)

assert.Equal(t, test.expect, result, errMsg)
}
}

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

for i, test := range []struct {
t string
v interface{}
expect interface{}
}{
{"<nil>", nil, true},
{"string", "foo", true},
{"int16", int16(1), true},
{"[]int", []int{1, 2}, true},
{"map[int]int", map[int]int{1: 1}, true},
{"map[int]interface {}", map[int]interface{}{1: "a"}, true},
{"reflect.tstNoStringer", tstNoStringer{}, true},
{"reflect.tstNoStringer", &tstNoStringer{}, false},
{"*reflect.tstNoStringer", &tstNoStringer{}, true},
{"*reflect.tstNoStringer", tstNoStringer{}, false},
{"float", 1.1, false},
} {
errMsg := fmt.Sprintf("[%d] %v", i, test)

result := ns.TypeIs(test.t, test.v)

assert.Equal(t, test.expect, result, errMsg)
}
}

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

for i, test := range []struct {
t string
v interface{}
expect interface{}
}{
{"<nil>", nil, true},
{"string", "foo", true},
{"reflect.tstNoStringer", tstNoStringer{}, true},
{"reflect.tstNoStringer", &tstNoStringer{}, true},
{"*reflect.tstNoStringer", &tstNoStringer{}, true},
{"*reflect.tstNoStringer", tstNoStringer{}, false},
{"float", 1.1, false},
} {
errMsg := fmt.Sprintf("[%d] %v", i, test)

result := ns.TypeIsLike(test.t, test.v)

assert.Equal(t, test.expect, result, errMsg)
}
}

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

for i, test := range []struct {
v interface{}
expect interface{}
}{
{nil, "<nil>"},
{"foo", "string"},
{int16(1), "int16"},
{[]int{1, 2}, "[]int"},
{map[int]int{1: 1}, "map[int]int"},
{map[int]interface{}{1: 1}, "map[int]interface {}"},
{tstNoStringer{}, "reflect.tstNoStringer"},
{&tstNoStringer{}, "*reflect.tstNoStringer"},
} {
errMsg := fmt.Sprintf("[%d] %v", i, test)

result := ns.TypeOf(test.v)

assert.Equal(t, test.expect, result, errMsg)
}
}
1 change: 1 addition & 0 deletions tpl/tplimpl/template_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
_ "github.com/gohugoio/hugo/tpl/math"
_ "github.com/gohugoio/hugo/tpl/os"
_ "github.com/gohugoio/hugo/tpl/partials"
_ "github.com/gohugoio/hugo/tpl/reflect"
_ "github.com/gohugoio/hugo/tpl/safe"
_ "github.com/gohugoio/hugo/tpl/strings"
_ "github.com/gohugoio/hugo/tpl/time"
Expand Down

0 comments on commit b775f2a

Please sign in to comment.