From 36df350f52915e04220e8ff0643179e403ae1098 Mon Sep 17 00:00:00 2001 From: johannesengl Date: Wed, 8 Mar 2023 15:09:28 +0100 Subject: [PATCH 1/2] debug: Add method debug.List The method returns a slice of field names and method names of the struct/pointer or keys of the map. This method scans the provided value shallow, non-recursively. The method is aimed to make debugging variables easier. Fixes 9148 # Conflicts: # tpl/debug/debug.go --- tpl/debug/debug.go | 48 ++++++++++++++++++++++++++++++++ tpl/debug/debug_test.go | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tpl/debug/debug_test.go diff --git a/tpl/debug/debug.go b/tpl/debug/debug.go index ae1acf5eb..120f9b773 100644 --- a/tpl/debug/debug.go +++ b/tpl/debug/debug.go @@ -15,6 +15,10 @@ package debug import ( + "reflect" + "sort" + + "github.com/sanity-io/litter" "encoding/json" "sort" "sync" @@ -181,3 +185,47 @@ func (ns *Namespace) TestDeprecationErr(item, alternative string) string { hugo.Deprecate(item, alternative, v.String()) return "" } + +// List returns a slice of field names and method names of the struct/pointer or keys of the map +// This method scans the provided value shallow, non-recursively. +func (ns *Namespace) List(val any) []string { + + fields := make([]string, 0) + value := reflect.ValueOf(val) + + if value.Kind() == reflect.Map { + for _, key := range value.MapKeys() { + fields = append(fields, key.String()) + sort.Strings(fields) + } + } + + // Dereference the pointer if needed + if value.Kind() == reflect.Pointer { + value = value.Elem() + } + + if value.Kind() == reflect.Struct { + // Iterate over the fields + for i := 0; i < value.NumField(); i++ { + field := value.Type().Field(i) + + // Only add exported fields + if field.PkgPath == "" { + fields = append(fields, field.Name) + } + } + + // Calling NumMethod() on the pointer type returns the number of methods + // defined for the pointer type as well as the non pointer type. + // Calling NumMethod() on the non pointer type returns on the other hand only the number of non pointer methods. + pointerType := reflect.PointerTo(value.Type()) + + for i := 0; i < pointerType.NumMethod(); i++ { + method := pointerType.Method(i) + fields = append(fields, method.Name) + } + } + + return fields +} diff --git a/tpl/debug/debug_test.go b/tpl/debug/debug_test.go new file mode 100644 index 000000000..133db11bf --- /dev/null +++ b/tpl/debug/debug_test.go @@ -0,0 +1,61 @@ +// Copyright 2023 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 debug + +import ( + "fmt" + "reflect" + "testing" +) + +type User struct { + Name string + Address any + foo string +} + +func (u *User) M1() string { return "" } +func (u *User) M2(v string) string { return "" } +func (u *User) m3(v string) string { return "" } + +// Non Pointer type methods +func (u User) M4(v string) string { return "" } +func (u User) m5(v string) string { return "" } + +func TestList(t *testing.T) { + t.Parallel() + + namespace := new(Namespace) + + for i, test := range []struct { + val any + expect []string + }{ + // Map + {map[string]any{"key1": 1, "key2": 2, "key3": 3}, []string{"key1", "key2", "key3"}}, + // Map non string keys + {map[int]any{1: 1, 2: 2, 3: 3}, []string{"", "", ""}}, + // Struct + {User{}, []string{"Name", "Address", "M1", "M2", "M4"}}, + // Pointer + {&User{}, []string{"Name", "Address", "M1", "M2", "M4"}}, + } { + t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) { + result := namespace.List(test.val) + + if !reflect.DeepEqual(result, test.expect) { + t.Fatalf("List called with value: %#v got\n%#v but expected\n%#v", test.val, result, test.expect) + } + }) + } +} From c91de885b578639e30e66040cf919d4c73d44f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20P=C3=A9rez?= Date: Tue, 12 Nov 2024 17:12:58 +0100 Subject: [PATCH 2/2] refactor: user struct to for and bar in debug tests to improve tests abstraction #9148 and rebased #10806 --- tpl/debug/debug.go | 2 -- tpl/debug/debug_test.go | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/tpl/debug/debug.go b/tpl/debug/debug.go index 120f9b773..3f771856f 100644 --- a/tpl/debug/debug.go +++ b/tpl/debug/debug.go @@ -18,9 +18,7 @@ import ( "reflect" "sort" - "github.com/sanity-io/litter" "encoding/json" - "sort" "sync" "time" diff --git a/tpl/debug/debug_test.go b/tpl/debug/debug_test.go index 133db11bf..86f767a62 100644 --- a/tpl/debug/debug_test.go +++ b/tpl/debug/debug_test.go @@ -18,19 +18,18 @@ import ( "testing" ) -type User struct { - Name string - Address any - foo string +type Foo struct { + Bar string + foo any } -func (u *User) M1() string { return "" } -func (u *User) M2(v string) string { return "" } -func (u *User) m3(v string) string { return "" } +func (f *Foo) M1() string { return "" } +func (f *Foo) M2(v string) string { return "" } +func (f *Foo) m3(v string) string { return "" } // Non Pointer type methods -func (u User) M4(v string) string { return "" } -func (u User) m5(v string) string { return "" } +func (f Foo) M4(v string) string { return "" } +func (f Foo) m5(v string) string { return "" } func TestList(t *testing.T) { t.Parallel() @@ -46,9 +45,9 @@ func TestList(t *testing.T) { // Map non string keys {map[int]any{1: 1, 2: 2, 3: 3}, []string{"", "", ""}}, // Struct - {User{}, []string{"Name", "Address", "M1", "M2", "M4"}}, + {Foo{}, []string{"Bar", "M1", "M2", "M4"}}, // Pointer - {&User{}, []string{"Name", "Address", "M1", "M2", "M4"}}, + {&Foo{}, []string{"Bar", "M1", "M2", "M4"}}, } { t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) { result := namespace.List(test.val)