-
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?
Changes from 7 commits
bf5e4c5
3fd6298
b674fe9
dc69ef8
5d26c4e
0e91528
ea3fac2
f97e568
ccbc3a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
module github.com/chezmoi/templatefuncs | ||
|
||
go 1.19 | ||
go 1.22 | ||
|
||
require github.com/alecthomas/assert/v2 v2.9.0 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,17 @@ | ||
package templatefuncs | ||
|
||
import ( | ||
"cmp" | ||
"encoding/hex" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"os/exec" | ||
"reflect" | ||
"regexp" | ||
"slices" | ||
"strconv" | ||
"strings" | ||
"text/template" | ||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be more generic so that it can accept |
||
"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 commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as for |
||
"hasPrefix": reverseArgs2(strings.HasPrefix), | ||
"hasSuffix": reverseArgs2(strings.HasSuffix), | ||
"hexDecode": eachStringErr(hex.DecodeString), | ||
"hexEncode": eachByteSlice(hex.EncodeToString), | ||
"indexOf": reverseArgs2(slices.Index[[]any]), | ||
"join": reverseArgs2(strings.Join), | ||
"list": listTemplateFunc, | ||
"lookPath": eachStringErr(lookPathTemplateFunc), | ||
|
@@ -44,15 +51,24 @@ func NewFuncMap() template.FuncMap { | |
"quote": eachString(strconv.Quote), | ||
"regexpReplaceAll": regexpReplaceAllTemplateFunc, | ||
"replaceAll": replaceAllTemplateFunc, | ||
"reverse": reverseTemplateFunc, | ||
"sort": sortTemplateFunc, | ||
"stat": eachString(statTemplateFunc), | ||
"toJSON": toJSONTemplateFunc, | ||
"toLower": eachString(strings.ToLower), | ||
"toString": toStringTemplateFunc, | ||
"toUpper": eachString(strings.ToUpper), | ||
"trimSpace": eachString(strings.TrimSpace), | ||
"uniq": uniqTemplateFunc, | ||
} | ||
} | ||
|
||
// compactTemplateFunc is the core implementation of the `compact` template | ||
// function. | ||
func compactTemplateFunc(list []any) []any { | ||
return slices.DeleteFunc(list, isZeroValue) | ||
} | ||
|
||
// eqFoldTemplateFunc is the core implementation of the `eqFold` template | ||
// function. | ||
func eqFoldTemplateFunc(first, second string, more ...string) bool { | ||
|
@@ -159,6 +175,62 @@ func regexpReplaceAllTemplateFunc(expr, repl, s string) string { | |
return regexp.MustCompile(expr).ReplaceAllString(s, repl) | ||
} | ||
|
||
// reverseTemplateFunc is the core implementation of the `reverse` | ||
// template function. | ||
func reverseTemplateFunc(list []any) []any { | ||
listcopy := append([]any(nil), list...) | ||
slices.Reverse(listcopy) | ||
return listcopy | ||
} | ||
|
||
// 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 commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is what I described as terrible sort roughly sufficient? |
||
if len(list) < 2 { | ||
return list | ||
} | ||
|
||
firstElemType := reflect.TypeOf(list[0]) | ||
|
||
for _, elem := range list[1:] { | ||
if reflect.TypeOf(elem) != firstElemType { | ||
return list | ||
} | ||
} | ||
|
||
switch firstElemType.Kind() { //nolint:exhaustive | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I don't disable
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I'm using {
"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}"
}
]
}
} |
||
case reflect.Int: | ||
return convertAndSortSlice[int](list) | ||
case reflect.Int8: | ||
return convertAndSortSlice[int8](list) | ||
case reflect.Int16: | ||
return convertAndSortSlice[int16](list) | ||
case reflect.Int32: | ||
return convertAndSortSlice[int32](list) | ||
case reflect.Int64: | ||
return convertAndSortSlice[int64](list) | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. This will not work as expected, as |
||
case reflect.Uint16: | ||
return convertAndSortSlice[uint16](list) | ||
case reflect.Uint32: | ||
return convertAndSortSlice[uint32](list) | ||
case reflect.Uint64: | ||
return convertAndSortSlice[uint64](list) | ||
case reflect.Uintptr: | ||
return convertAndSortSlice[uintptr](list) | ||
case reflect.Float32: | ||
return convertAndSortSlice[float32](list) | ||
case reflect.Float64: | ||
return convertAndSortSlice[float64](list) | ||
case reflect.String: | ||
return convertAndSortSlice[string](list) | ||
default: | ||
return list | ||
} | ||
} | ||
|
||
// statTemplateFunc is the core implementation of the `stat` template function. | ||
func statTemplateFunc(name string) any { | ||
switch fileInfo, err := os.Stat(name); { | ||
|
@@ -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 commentThe 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. |
||
result := []any{} | ||
|
||
for _, v := range list { | ||
if _, ok := seen[v]; !ok { | ||
result = append(result, v) | ||
seen[v] = struct{}{} | ||
} | ||
} | ||
|
||
return result | ||
} | ||
|
||
// convertAndSortSlice creates a `[]T` copy of its input and sorts it. | ||
func convertAndSortSlice[T cmp.Ordered](slice []any) []T { | ||
l := make([]T, len(slice)) | ||
for i, elem := range slice { | ||
v, ok := elem.(T) | ||
if !ok { | ||
panic(fmt.Sprintf("unable to convert %v (type %T) to %T", elem, elem, v)) | ||
} | ||
l[i] = v | ||
} | ||
slices.Sort(l) | ||
return l | ||
} | ||
|
||
// eachByteSlice transforms a function that takes a single `[]byte` and returns | ||
// a `T` to a function that takes zero or more `[]byte`-like arguments and | ||
// returns zero or more `T`s. | ||
|
@@ -377,6 +478,38 @@ func fileInfoToMap(fileInfo fs.FileInfo) map[string]any { | |
} | ||
} | ||
|
||
// 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 { | ||
truth, ok := template.IsTrue(v) | ||
if !ok { | ||
panic(fmt.Sprintf("unable to determine zero value for %v", v)) | ||
} | ||
return !truth | ||
// 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() | ||
// } | ||
} | ||
|
||
// reverseArgs2 transforms a function that takes two arguments and returns an | ||
// `R` into a function that takes the arguments in reverse order and returns an | ||
// `R`. | ||
|
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
.