-
Notifications
You must be signed in to change notification settings - Fork 1
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: add more list funcs #29
base: master
Are you sure you want to change the base?
Conversation
I'm struggling a bit with trying to implement a sort function. I wanted to use Obviously, if a slice contains elements that are different types, it cannot be sorted in this way, so I would need to somehow check if every element in the slice is the same type, and that type implements I've tried a type switch, and I've tried reflection, but it always goes to the default case and just returns the existing list. |
We could create a |
templatefuncs.go
Outdated
// isZeroValue returns whether a value is the zero value for its type. | ||
// An empty array, map or slice is assumed to be a zero value. | ||
func isZeroValue(v any) bool { | ||
vval := reflect.ValueOf(v) | ||
if !vval.IsValid() { | ||
return true | ||
} | ||
switch vval.Kind() { //nolint:exhaustive | ||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String: | ||
return vval.Len() == 0 | ||
case reflect.Bool: | ||
return !vval.Bool() | ||
case reflect.Complex64, reflect.Complex128: | ||
return vval.Complex() == 0 | ||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
return vval.Int() == 0 | ||
case reflect.Float32, reflect.Float64: | ||
return vval.Float() == 0 | ||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | ||
return vval.Uint() == 0 | ||
case reflect.Struct: | ||
return false | ||
default: | ||
return vval.IsNil() | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be replaced with something like
truth, ok := template.IsTrue(v)
if !ok {
panic(fmt.Sprintf("unable to determine zero value for %v", v))
}
return !truth
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this, but there are several problems. See the individual review comments.
The underlying problem is that we're trying to build dynamically-typed template functions on top of strict, statically-typed Go functions, and an extra conversion layer is needed.
docs/templatefuncs.md
Outdated
If *list* cannot be sorted, it is simply returned. | ||
|
||
```text | ||
{{ list list "c" "a" "b" | sort }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: duplicated list
.
templatefuncs.go
Outdated
@@ -29,13 +32,17 @@ var fileModeTypeNames = map[fs.FileMode]string{ | |||
// functions. | |||
func NewFuncMap() template.FuncMap { | |||
return template.FuncMap{ | |||
"compact": compactTemplateFunc, | |||
"concat": slices.Concat[[]any], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be more generic so that it can accept []string
and other slices. Note that Go cannot automatically convert []any
to []string
. See https://blog.merovius.de/posts/2018-06-03-why-doesnt-go-have-variance-in/ if you want the gory details.
templatefuncs.go
Outdated
"contains": reverseArgs2(strings.Contains), | ||
"eqFold": eqFoldTemplateFunc, | ||
"fromJSON": eachByteSliceErr(fromJSONTemplateFunc), | ||
"has": reverseArgs2(slices.Contains[[]any]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as for concat
.
templatefuncs.go
Outdated
case reflect.Uint: | ||
return convertAndSortSlice[uint](list) | ||
case reflect.Uint8: | ||
return convertAndSortSlice[uint8](list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will not work as expected, as []byte
is an alias for []uint8
and in other template functions we treat []byte
s as string
s.
templatefuncs.go
Outdated
} | ||
} | ||
|
||
switch firstElemType.Kind() { //nolint:exhaustive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to be exhaustive here. It's sufficient to cover the common types (int
, float64
, string
, and bool
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I don't disable exhaustive
the linter complains because I'm not accounting for the other Kind
cases:
missing cases in switch of type reflect.Kind: reflect.Invalid, reflect.Bool, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer|reflect.Ptr, reflect.Slice, reflect.Struct, reflect.UnsafePointer (exhaustive)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use a default:
case which panic
s with an "unsupported type" message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, in that case feel free to disregard/disable the linter. Note that this exhaustive checking might be covered by multiple linters and you might need to disable all of them. Also, check to see if the warning is coming from golangci-lint with chezmoi's configuration, or if it's an extra linter being run by your IDE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://pkg.go.dev/github.com/nishanths/exhaustive#hdr-Definition_of_exhaustiveness
We can add the -default-signifies-exhaustive
flag to the linter if you'd prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I'm using golangci-lint
as the linter in VS Code with Error Lens, as well as Run on Save to format:
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"emeraldwalk.runonsave": {
"commands": [
{
"match": "\\.go$",
"cmd": "gci write ${file} --skip-generated -s standard -s default -s prefix(github.com/chezmoi/templatefuncs)"
},
{
"match": "\\.go$",
"cmd": "golines --base-formatter=\"gofumpt -extra\" --max-len=128 --write-output ${file}"
}
]
}
}
templatefuncs.go
Outdated
} | ||
|
||
// sortTemplateFunc is the core implementation of the `sort` template function. | ||
func sortTemplateFunc(list []any) any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to write a generic sort function is hard. I would instead have separate sortInts
and sortFloat64s
functions, with sort
converting all arguments to string
s and then sorting those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is what I described as terrible sort roughly sufficient?
templatefuncs.go
Outdated
@@ -207,6 +279,35 @@ func toStringTemplateFunc(arg any) string { | |||
} | |||
} | |||
|
|||
// uniqTemplateFunc is the core implementation of the `uniq` template function. | |||
func uniqTemplateFunc(list []any) []any { | |||
seen := make(map[any]struct{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will not work as expected. For example, see https://go.dev/play/p/qemCBLcOyPh. I think it can fail in other ways too.
I think this highlights that we need more robust test data. I didn't notice most of these because I was only testing with It would help to test with some predefined |
Okay, I'm going to essentially start over in some new commits. First up is (a hopefully better) |
I might squash and merge this tomorrow (unless there are more mistakes) as I can't really come up with any more list function implementations that I'm happy with at the moment. It could benefit from a quality pass for error message consistency etc. and also to revert the Go version change, as it's only necessary for the I can make I'm also happy to change if v.Kind() != reflect.Slice {
...
} to a |
WIP