Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gnoland): in config, refer to fields using toml struct tags #1769

Merged
merged 5 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 75 additions & 14 deletions gno.land/cmd/gnoland/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"flag"
"fmt"
"reflect"
"strings"

"github.com/gnolang/gno/tm2/pkg/commands"
)
Expand Down Expand Up @@ -47,7 +48,7 @@
func getFieldAtPath(currentValue reflect.Value, path []string) (*reflect.Value, error) {
// Look at the current section, and figure out if
// it's a part of the current struct
field := currentValue.FieldByName(path[0])
field := fieldByTOMLName(currentValue, path[0])
if !field.IsValid() || !field.CanSet() {
return nil, newInvalidFieldError(path[0], currentValue)
}
Expand All @@ -67,24 +68,84 @@
return &field, nil
}

// newInvalidFieldError creates an error for non-existent struct fields
// being passed as arguments to [getFieldAtPath]
func newInvalidFieldError(field string, value reflect.Value) error {
var (
valueType = value.Type()
numFields = value.NumField()
)

fields := make([]string, 0, numFields)
func fieldByTOMLName(value reflect.Value, name string) reflect.Value {
var dst reflect.Value
eachTOMLField(value, func(val reflect.Value, tomlName string) bool {
if tomlName == name {
dst = val
return true
}
return false
})
return dst
}

for i := 0; i < numFields; i++ {
valueField := valueType.Field(i)
if !valueField.IsExported() {
// eachTOMLField iterates over each field in value (assumed to be a struct).
// For every field within the struct, iterationCallback is called with the value
// of the field and the associated name in the TOML representation
// (through the `toml:"..."` struct field, or just the field's name as fallback).
// If iterationCallback returns true, the function returns immediately with
// true. If it always returns false, or no fields were processed, eachTOMLField
// will return false.
func eachTOMLField(value reflect.Value, iterationCallback func(val reflect.Value, tomlName string) bool) bool {
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
// For reference:
// https://github.com/pelletier/go-toml/blob/7dad87762adb203e30b96a46026d1428ef2491a2/unmarshaler.go#L1251-L1270

currentType := value.Type()
nf := currentType.NumField()
for i := 0; i < nf; i++ {
fld := currentType.Field(i)
tomlName := fld.Tag.Get("toml")

// Ignore `toml:"-"`, strip away any "omitempty" or other options.
if tomlName == "-" || !fld.IsExported() {
continue
}
if pos := strings.IndexByte(tomlName, ','); pos != -1 {
tomlName = tomlName[:pos]
}

// Handle anonymous (embedded) fields.
// Anonymous fields will be treated regularly if they have a tag.
if fld.Anonymous && tomlName == "" {
anon := fld.Type
if anon.Kind() == reflect.Ptr {
anon = anon.Elem()

Check warning on line 113 in gno.land/cmd/gnoland/config.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoland/config.go#L113

Added line #L113 was not covered by tests
}

if anon.Kind() == reflect.Struct {
// NOTE: naive, if there is a conflict the embedder should take
// precedence over the embedded; but the TOML parser seems to
// ignore this, too, and this "unmarshaler" isn't fit for general
// purpose.
if eachTOMLField(value.Field(i), iterationCallback) {
return true
}
continue
}
// If it's not a struct, or *struct, it should be treated regularly.
}

fields = append(fields, valueField.Name)
// general case, simple struct field.
if tomlName == "" {
tomlName = fld.Name

Check warning on line 131 in gno.land/cmd/gnoland/config.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoland/config.go#L131

Added line #L131 was not covered by tests
}
if iterationCallback(value.Field(i), tomlName) {
return true
}
}
return false
}

// newInvalidFieldError creates an error for non-existent struct fields
// being passed as arguments to [getFieldAtPath]
func newInvalidFieldError(field string, value reflect.Value) error {
fields := make([]string, 0, value.NumField())

Check warning on line 143 in gno.land/cmd/gnoland/config.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoland/config.go#L142-L143

Added lines #L142 - L143 were not covered by tests

eachTOMLField(value, func(val reflect.Value, tomlName string) bool {
fields = append(fields, tomlName)
return false
})

Check warning on line 148 in gno.land/cmd/gnoland/config.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoland/config.go#L145-L148

Added lines #L145 - L148 were not covered by tests

return fmt.Errorf(
"field %q, is not a valid configuration key, available keys: %s",
Expand Down
Loading
Loading