Skip to content

Commit

Permalink
feat(gnoland): in config, refer to fields using toml struct tags (#…
Browse files Browse the repository at this point in the history
…1769)

After briefly discussing #1605, me and @zivkovicmilos convened that it
would be better if the `config` commands took, for the keys in the
configuration, their TOML name rather than the internal Go struct field
name. This PR implements that.
  • Loading branch information
thehowl committed Mar 26, 2024
1 parent 234a0a1 commit 281815a
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 157 deletions.
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 @@ import (
"flag"
"fmt"
"reflect"
"strings"

"github.com/gnolang/gno/tm2/pkg/commands"
)
Expand Down Expand Up @@ -47,7 +48,7 @@ func (c *configCfg) RegisterFlags(fs *flag.FlagSet) {
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 @@ func getFieldAtPath(currentValue reflect.Value, path []string) (*reflect.Value,
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 {
// 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()
}

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
}
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())

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

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

0 comments on commit 281815a

Please sign in to comment.