Some checks failed
CI / Lint (push) Successful in -27m59s
CI / Test (1.25) (push) Successful in -27m46s
CI / Test (1.24) (push) Failing after 59s
CI / Build (push) Successful in -28m14s
Integration Tests / Integration Tests (push) Failing after -28m16s
Release / Build and Release (push) Successful in 1m1s
feat(templ): ✨ added templ to command line that reads go template and outputs code Reviewed-on: #1 Co-authored-by: Hein <hein.puth@gmail.com> Co-committed-by: Hein <hein.puth@gmail.com>
283 lines
6.5 KiB
Go
283 lines
6.5 KiB
Go
package template
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/reflectutil"
|
|
)
|
|
|
|
// EnumeratedItem represents an item with its index
|
|
type EnumeratedItem struct {
|
|
Index int
|
|
Value interface{}
|
|
}
|
|
|
|
// Enumerate returns a slice with index-value pairs
|
|
// Usage: {{ range enumerate .Tables }}{{ .Index }}: {{ .Value.Name }}{{ end }}
|
|
func Enumerate(slice interface{}) []EnumeratedItem {
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return []EnumeratedItem{}
|
|
}
|
|
|
|
result := make([]EnumeratedItem, v.Len())
|
|
for i := 0; i < v.Len(); i++ {
|
|
result[i] = EnumeratedItem{
|
|
Index: i,
|
|
Value: v.Index(i).Interface(),
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Batch splits a slice into chunks of specified size
|
|
// Usage: {{ range batch .Columns 3 }}...{{ end }}
|
|
func Batch(slice interface{}, size int) [][]interface{} {
|
|
if size <= 0 {
|
|
return [][]interface{}{}
|
|
}
|
|
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return [][]interface{}{}
|
|
}
|
|
|
|
length := v.Len()
|
|
if length == 0 {
|
|
return [][]interface{}{}
|
|
}
|
|
|
|
numBatches := (length + size - 1) / size
|
|
result := make([][]interface{}, numBatches)
|
|
|
|
for i := 0; i < numBatches; i++ {
|
|
start := i * size
|
|
end := start + size
|
|
if end > length {
|
|
end = length
|
|
}
|
|
|
|
batch := make([]interface{}, end-start)
|
|
for j := start; j < end; j++ {
|
|
batch[j-start] = v.Index(j).Interface()
|
|
}
|
|
result[i] = batch
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Chunk is an alias for Batch
|
|
func Chunk(slice interface{}, size int) [][]interface{} {
|
|
return Batch(slice, size)
|
|
}
|
|
|
|
// Reverse reverses a slice
|
|
// Usage: {{ range reverse .Tables }}...{{ end }}
|
|
func Reverse(slice interface{}) []interface{} {
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return []interface{}{}
|
|
}
|
|
|
|
length := v.Len()
|
|
result := make([]interface{}, length)
|
|
for i := 0; i < length; i++ {
|
|
result[length-1-i] = v.Index(i).Interface()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// First returns the first N items from a slice
|
|
// Usage: {{ range first .Tables 5 }}...{{ end }}
|
|
func First(slice interface{}, n int) []interface{} {
|
|
if n <= 0 {
|
|
return []interface{}{}
|
|
}
|
|
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return []interface{}{}
|
|
}
|
|
|
|
length := v.Len()
|
|
if n > length {
|
|
n = length
|
|
}
|
|
|
|
result := make([]interface{}, n)
|
|
for i := 0; i < n; i++ {
|
|
result[i] = v.Index(i).Interface()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Last returns the last N items from a slice
|
|
// Usage: {{ range last .Tables 5 }}...{{ end }}
|
|
func Last(slice interface{}, n int) []interface{} {
|
|
if n <= 0 {
|
|
return []interface{}{}
|
|
}
|
|
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return []interface{}{}
|
|
}
|
|
|
|
length := v.Len()
|
|
if n > length {
|
|
n = length
|
|
}
|
|
|
|
result := make([]interface{}, n)
|
|
start := length - n
|
|
for i := 0; i < n; i++ {
|
|
result[i] = v.Index(start + i).Interface()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Skip skips the first N items and returns the rest
|
|
// Usage: {{ range skip .Tables 2 }}...{{ end }}
|
|
func Skip(slice interface{}, n int) []interface{} {
|
|
if n < 0 {
|
|
n = 0
|
|
}
|
|
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return []interface{}{}
|
|
}
|
|
|
|
length := v.Len()
|
|
if n >= length {
|
|
return []interface{}{}
|
|
}
|
|
|
|
result := make([]interface{}, length-n)
|
|
for i := n; i < length; i++ {
|
|
result[i-n] = v.Index(i).Interface()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Take returns the first N items (alias for First)
|
|
// Usage: {{ range take .Tables 5 }}...{{ end }}
|
|
func Take(slice interface{}, n int) []interface{} {
|
|
return First(slice, n)
|
|
}
|
|
|
|
// Concat concatenates multiple slices
|
|
// Usage: {{ $all := concat .Schema1.Tables .Schema2.Tables }}
|
|
func Concat(slices ...interface{}) []interface{} {
|
|
result := make([]interface{}, 0)
|
|
|
|
for _, slice := range slices {
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
continue
|
|
}
|
|
|
|
for i := 0; i < v.Len(); i++ {
|
|
result = append(result, v.Index(i).Interface())
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Unique removes duplicates from a slice (compares by string representation)
|
|
// Usage: {{ $unique := unique .Items }}
|
|
func Unique(slice interface{}) []interface{} {
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return []interface{}{}
|
|
}
|
|
|
|
seen := make(map[interface{}]bool)
|
|
result := make([]interface{}, 0)
|
|
|
|
for i := 0; i < v.Len(); i++ {
|
|
item := v.Index(i).Interface()
|
|
if !seen[item] {
|
|
seen[item] = true
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// SortBy sorts a slice by a field name (for structs) or key (for maps)
|
|
// Usage: {{ $sorted := sortBy .Tables "Name" }}
|
|
// Note: This is a basic implementation that works for simple cases
|
|
func SortBy(slice interface{}, field string) []interface{} {
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return []interface{}{}
|
|
}
|
|
|
|
// Convert to interface slice
|
|
result := make([]interface{}, v.Len())
|
|
for i := 0; i < v.Len(); i++ {
|
|
result[i] = v.Index(i).Interface()
|
|
}
|
|
|
|
// Sort by field
|
|
sort.Slice(result, func(i, j int) bool {
|
|
vi := getFieldValue(result[i], field)
|
|
vj := getFieldValue(result[j], field)
|
|
return compareValues(vi, vj) < 0
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
// GroupBy groups a slice by a field value
|
|
// Usage: {{ $grouped := groupBy .Tables "Schema" }}
|
|
func GroupBy(slice interface{}, field string) map[interface{}][]interface{} {
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return map[interface{}][]interface{}{}
|
|
}
|
|
|
|
result := make(map[interface{}][]interface{})
|
|
|
|
for i := 0; i < v.Len(); i++ {
|
|
item := v.Index(i).Interface()
|
|
key := getFieldValue(item, field)
|
|
result[key] = append(result[key], item)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// CountIf counts items matching a condition
|
|
// Note: Since templates can't pass functions, this is limited
|
|
// Usage in code (not directly in templates)
|
|
func CountIf(slice interface{}, matches func(interface{}) bool) int {
|
|
v := reflect.ValueOf(slice)
|
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
|
return 0
|
|
}
|
|
|
|
count := 0
|
|
for i := 0; i < v.Len(); i++ {
|
|
if matches(v.Index(i).Interface()) {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// getFieldValue extracts a field value from a struct, map, or pointer
|
|
func getFieldValue(item interface{}, field string) interface{} {
|
|
return reflectutil.GetFieldValue(item, field)
|
|
}
|
|
|
|
// compareValues compares two values for sorting
|
|
func compareValues(a, b interface{}) int {
|
|
return reflectutil.CompareValues(a, b)
|
|
}
|