Clean code

This commit is contained in:
binwiederhier 2025-07-19 22:30:07 +02:00
parent f0d5392e9e
commit 8b4834929d
5 changed files with 287 additions and 269 deletions

View file

@ -0,0 +1,7 @@
package sprig
import "errors"
func fail(msg string) (string, error) {
return "", errors.New(msg)
}

View file

@ -1,14 +1,9 @@
package sprig
import (
"errors"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"math/rand"
"path"
"path/filepath"
"reflect"
"strconv"
"strings"
"text/template"
"time"
@ -27,220 +22,195 @@ const (
//
// TxtFuncMap returns a 'text/template'.FuncMap
func TxtFuncMap() template.FuncMap {
gfm := make(map[string]any, len(genericMap))
for k, v := range genericMap {
gfm[k] = v
return map[string]any{
// Date functions
"ago": dateAgo,
"date": date,
"date_in_zone": dateInZone,
"date_modify": dateModify,
"dateInZone": dateInZone,
"dateModify": dateModify,
"duration": duration,
"durationRound": durationRound,
"htmlDate": htmlDate,
"htmlDateInZone": htmlDateInZone,
"must_date_modify": mustDateModify,
"mustDateModify": mustDateModify,
"mustToDate": mustToDate,
"now": time.Now,
"toDate": toDate,
"unixEpoch": unixEpoch,
// Strings
"trunc": trunc,
"trim": strings.TrimSpace,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": title,
"substr": substring,
"repeat": repeat,
"trimAll": trimAll,
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"contains": contains,
"hasPrefix": hasPrefix,
"hasSuffix": hasSuffix,
"quote": quote,
"squote": squote,
"cat": cat,
"indent": indent,
"nindent": nindent,
"replace": replace,
"plural": plural,
"sha1sum": sha1sum,
"sha256sum": sha256sum,
"sha512sum": sha512sum,
"adler32sum": adler32sum,
"toString": strval,
// Wrap Atoi to stop errors.
"atoi": atoi,
"seq": seq,
"toDecimal": toDecimal,
"split": split,
"splitList": splitList,
"splitn": splitn,
"toStrings": strslice,
"until": until,
"untilStep": untilStep,
// Basic arithmetic
"add1": add1,
"add": add,
"sub": sub,
"div": div,
"mod": mod,
"mul": mul,
"randInt": randInt,
"biggest": maxAsInt64,
"max": maxAsInt64,
"min": minAsInt64,
"maxf": maxAsFloat64,
"minf": minAsFloat64,
"ceil": ceil,
"floor": floor,
"round": round,
// string slices. Note that we reverse the order b/c that's better
// for template processing.
"join": join,
"sortAlpha": sortAlpha,
// Defaults
"default": dfault,
"empty": empty,
"coalesce": coalesce,
"all": all,
"any": anyNonEmpty,
"compact": compact,
"mustCompact": mustCompact,
"fromJSON": fromJSON,
"toJSON": toJSON,
"toPrettyJSON": toPrettyJSON,
"toRawJSON": toRawJSON,
"mustFromJSON": mustFromJSON,
"mustToJSON": mustToJSON,
"mustToPrettyJSON": mustToPrettyJSON,
"mustToRawJSON": mustToRawJSON,
"ternary": ternary,
// Reflection
"typeOf": typeOf,
"typeIs": typeIs,
"typeIsLike": typeIsLike,
"kindOf": kindOf,
"kindIs": kindIs,
"deepEqual": reflect.DeepEqual,
// Paths
"base": path.Base,
"dir": path.Dir,
"clean": path.Clean,
"ext": path.Ext,
"isAbs": path.IsAbs,
// Filepaths
"osBase": filepath.Base,
"osClean": filepath.Clean,
"osDir": filepath.Dir,
"osExt": filepath.Ext,
"osIsAbs": filepath.IsAbs,
// Encoding
"b64enc": base64encode,
"b64dec": base64decode,
"b32enc": base32encode,
"b32dec": base32decode,
// Data Structures
"tuple": list, // FIXME: with the addition of append/prepend these are no longer immutable.
"list": list,
"dict": dict,
"get": get,
"set": set,
"unset": unset,
"hasKey": hasKey,
"pluck": pluck,
"keys": keys,
"pick": pick,
"omit": omit,
"values": values,
"append": push,
"push": push,
"mustAppend": mustPush,
"mustPush": mustPush,
"prepend": prepend,
"mustPrepend": mustPrepend,
"first": first,
"mustFirst": mustFirst,
"rest": rest,
"mustRest": mustRest,
"last": last,
"mustLast": mustLast,
"initial": initial,
"mustInitial": mustInitial,
"reverse": reverse,
"mustReverse": mustReverse,
"uniq": uniq,
"mustUniq": mustUniq,
"without": without,
"mustWithout": mustWithout,
"has": has,
"mustHas": mustHas,
"slice": slice,
"mustSlice": mustSlice,
"concat": concat,
"dig": dig,
"chunk": chunk,
"mustChunk": mustChunk,
// Flow Control
"fail": fail,
// Regex
"regexMatch": regexMatch,
"mustRegexMatch": mustRegexMatch,
"regexFindAll": regexFindAll,
"mustRegexFindAll": mustRegexFindAll,
"regexFind": regexFind,
"mustRegexFind": mustRegexFind,
"regexReplaceAll": regexReplaceAll,
"mustRegexReplaceAll": mustRegexReplaceAll,
"regexReplaceAllLiteral": regexReplaceAllLiteral,
"mustRegexReplaceAllLiteral": mustRegexReplaceAllLiteral,
"regexSplit": regexSplit,
"mustRegexSplit": mustRegexSplit,
"regexQuoteMeta": regexQuoteMeta,
// URLs
"urlParse": urlParse,
"urlJoin": urlJoin,
}
return gfm
}
var genericMap = map[string]any{
// Date functions
"ago": dateAgo,
"date": date,
"date_in_zone": dateInZone,
"date_modify": dateModify,
"dateInZone": dateInZone,
"dateModify": dateModify,
"duration": duration,
"durationRound": durationRound,
"htmlDate": htmlDate,
"htmlDateInZone": htmlDateInZone,
"must_date_modify": mustDateModify,
"mustDateModify": mustDateModify,
"mustToDate": mustToDate,
"now": time.Now,
"toDate": toDate,
"unixEpoch": unixEpoch,
// Strings
"trunc": trunc,
"trim": strings.TrimSpace,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": func(s string) string {
return cases.Title(language.English).String(s)
},
"substr": substring,
// Switch order so that "foo" | repeat 5
"repeat": repeat,
"trimAll": func(a, b string) string { return strings.Trim(b, a) },
"trimSuffix": func(a, b string) string { return strings.TrimSuffix(b, a) },
"trimPrefix": func(a, b string) string { return strings.TrimPrefix(b, a) },
// Switch order so that "foobar" | contains "foo"
"contains": func(substr string, str string) bool { return strings.Contains(str, substr) },
"hasPrefix": func(substr string, str string) bool { return strings.HasPrefix(str, substr) },
"hasSuffix": func(substr string, str string) bool { return strings.HasSuffix(str, substr) },
"quote": quote,
"squote": squote,
"cat": cat,
"indent": indent,
"nindent": nindent,
"replace": replace,
"plural": plural,
"sha1sum": sha1sum,
"sha256sum": sha256sum,
"sha512sum": sha512sum,
"adler32sum": adler32sum,
"toString": strval,
// Wrap Atoi to stop errors.
"atoi": func(a string) int { i, _ := strconv.Atoi(a); return i },
"seq": seq,
"toDecimal": toDecimal,
// split "/" foo/bar returns map[int]string{0: foo, 1: bar}
"split": split,
"splitList": func(sep, orig string) []string { return strings.Split(orig, sep) },
// splitn "/" foo/bar/fuu returns map[int]string{0: foo, 1: bar/fuu}
"splitn": splitn,
"toStrings": strslice,
"until": until,
"untilStep": untilStep,
// VERY basic arithmetic.
"add1": func(i any) int64 { return toInt64(i) + 1 },
"add": func(i ...any) int64 {
var a int64 = 0
for _, b := range i {
a += toInt64(b)
}
return a
},
"sub": func(a, b any) int64 { return toInt64(a) - toInt64(b) },
"div": func(a, b any) int64 { return toInt64(a) / toInt64(b) },
"mod": func(a, b any) int64 { return toInt64(a) % toInt64(b) },
"mul": func(a any, v ...any) int64 {
val := toInt64(a)
for _, b := range v {
val = val * toInt64(b)
}
return val
},
"randInt": func(min, max int) int { return rand.Intn(max-min) + min },
"biggest": max,
"max": max,
"min": min,
"maxf": maxf,
"minf": minf,
"ceil": ceil,
"floor": floor,
"round": round,
// string slices. Note that we reverse the order b/c that's better
// for template processing.
"join": join,
"sortAlpha": sortAlpha,
// Defaults
"default": dfault,
"empty": empty,
"coalesce": coalesce,
"all": all,
"any": anyNonEmpty,
"compact": compact,
"mustCompact": mustCompact,
"fromJSON": fromJSON,
"toJSON": toJSON,
"toPrettyJSON": toPrettyJSON,
"toRawJSON": toRawJSON,
"mustFromJSON": mustFromJSON,
"mustToJSON": mustToJSON,
"mustToPrettyJSON": mustToPrettyJSON,
"mustToRawJSON": mustToRawJSON,
"ternary": ternary,
// Reflection
"typeOf": typeOf,
"typeIs": typeIs,
"typeIsLike": typeIsLike,
"kindOf": kindOf,
"kindIs": kindIs,
"deepEqual": reflect.DeepEqual,
// Paths:
"base": path.Base,
"dir": path.Dir,
"clean": path.Clean,
"ext": path.Ext,
"isAbs": path.IsAbs,
// Filepaths:
"osBase": filepath.Base,
"osClean": filepath.Clean,
"osDir": filepath.Dir,
"osExt": filepath.Ext,
"osIsAbs": filepath.IsAbs,
// Encoding:
"b64enc": base64encode,
"b64dec": base64decode,
"b32enc": base32encode,
"b32dec": base32decode,
// Data Structures:
"tuple": list, // FIXME: with the addition of append/prepend these are no longer immutable.
"list": list,
"dict": dict,
"get": get,
"set": set,
"unset": unset,
"hasKey": hasKey,
"pluck": pluck,
"keys": keys,
"pick": pick,
"omit": omit,
"values": values,
"append": push,
"push": push,
"mustAppend": mustPush,
"mustPush": mustPush,
"prepend": prepend,
"mustPrepend": mustPrepend,
"first": first,
"mustFirst": mustFirst,
"rest": rest,
"mustRest": mustRest,
"last": last,
"mustLast": mustLast,
"initial": initial,
"mustInitial": mustInitial,
"reverse": reverse,
"mustReverse": mustReverse,
"uniq": uniq,
"mustUniq": mustUniq,
"without": without,
"mustWithout": mustWithout,
"has": has,
"mustHas": mustHas,
"slice": slice,
"mustSlice": mustSlice,
"concat": concat,
"dig": dig,
"chunk": chunk,
"mustChunk": mustChunk,
// Flow Control:
"fail": func(msg string) (string, error) { return "", errors.New(msg) },
// Regex
"regexMatch": regexMatch,
"mustRegexMatch": mustRegexMatch,
"regexFindAll": regexFindAll,
"mustRegexFindAll": mustRegexFindAll,
"regexFind": regexFind,
"mustRegexFind": mustRegexFind,
"regexReplaceAll": regexReplaceAll,
"mustRegexReplaceAll": mustRegexReplaceAll,
"regexReplaceAllLiteral": regexReplaceAllLiteral,
"mustRegexReplaceAllLiteral": mustRegexReplaceAllLiteral,
"regexSplit": regexSplit,
"mustRegexSplit": mustRegexSplit,
"regexQuoteMeta": regexQuoteMeta,
// URLs:
"urlParse": urlParse,
"urlJoin": urlJoin,
}

View file

@ -1,28 +0,0 @@
package sprig
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOsBase(t *testing.T) {
assert.NoError(t, runt(`{{ osBase "C:\\foo\\bar" }}`, "bar"))
}
func TestOsDir(t *testing.T) {
assert.NoError(t, runt(`{{ osDir "C:\\foo\\bar\\baz" }}`, "C:\\foo\\bar"))
}
func TestOsIsAbs(t *testing.T) {
assert.NoError(t, runt(`{{ osIsAbs "C:\\foo" }}`, "true"))
assert.NoError(t, runt(`{{ osIsAbs "foo" }}`, "false"))
}
func TestOsClean(t *testing.T) {
assert.NoError(t, runt(`{{ osClean "C:\\foo\\..\\foo\\..\\bar" }}`, "C:\\bar"))
}
func TestOsExt(t *testing.T) {
assert.NoError(t, runt(`{{ osExt "C:\\foo\\bar\\baz.txt" }}`, ".txt"))
}

View file

@ -3,6 +3,7 @@ package sprig
import (
"fmt"
"math"
"math/rand"
"reflect"
"strconv"
"strings"
@ -78,7 +79,43 @@ func toInt64(v any) int64 {
}
}
func max(a any, i ...any) int64 {
func add1(i any) int64 {
return toInt64(i) + 1
}
func add(i ...any) int64 {
var a int64
for _, b := range i {
a += toInt64(b)
}
return a
}
func sub(a, b any) int64 {
return toInt64(a) - toInt64(b)
}
func div(a, b any) int64 {
return toInt64(a) / toInt64(b)
}
func mod(a, b any) int64 {
return toInt64(a) % toInt64(b)
}
func mul(a any, v ...any) int64 {
val := toInt64(a)
for _, b := range v {
val = val * toInt64(b)
}
return val
}
func randInt(min, max int) int {
return rand.Intn(max-min) + min
}
func maxAsInt64(a any, i ...any) int64 {
aa := toInt64(a)
for _, b := range i {
bb := toInt64(b)
@ -89,16 +126,15 @@ func max(a any, i ...any) int64 {
return aa
}
func maxf(a any, i ...any) float64 {
aa := toFloat64(a)
func maxAsFloat64(a any, i ...any) float64 {
m := toFloat64(a)
for _, b := range i {
bb := toFloat64(b)
aa = math.Max(aa, bb)
m = math.Max(m, toFloat64(b))
}
return aa
return m
}
func min(a any, i ...any) int64 {
func minAsInt64(a any, i ...any) int64 {
aa := toInt64(a)
for _, b := range i {
bb := toInt64(b)
@ -109,13 +145,12 @@ func min(a any, i ...any) int64 {
return aa
}
func minf(a any, i ...any) float64 {
aa := toFloat64(a)
func minAsFloat64(a any, i ...any) float64 {
m := toFloat64(a)
for _, b := range i {
bb := toFloat64(b)
aa = math.Min(aa, bb)
m = math.Min(m, toFloat64(b))
}
return aa
return m
}
func until(count int) []int {
@ -131,12 +166,10 @@ func untilStep(start, stop, step int) []int {
if step == 0 {
return v
}
iterations := math.Abs(float64(stop)-float64(start)) / float64(step)
if iterations > loopExecutionLimit {
panic(fmt.Sprintf("too many iterations in untilStep; max allowed is %d, got %f", loopExecutionLimit, iterations))
}
if stop < start {
if step >= 0 {
return v
@ -146,7 +179,6 @@ func untilStep(start, stop, step int) []int {
}
return v
}
if step <= 0 {
return v
}
@ -157,13 +189,11 @@ func untilStep(start, stop, step int) []int {
}
func floor(a any) float64 {
aa := toFloat64(a)
return math.Floor(aa)
return math.Floor(toFloat64(a))
}
func ceil(a any) float64 {
aa := toFloat64(a)
return math.Ceil(aa)
return math.Ceil(toFloat64(a))
}
func round(a any, p int, rOpt ...float64) float64 {
@ -195,6 +225,11 @@ func toDecimal(v any) int64 {
return result
}
func atoi(a string) int {
i, _ := strconv.Atoi(a)
return i
}
func seq(params ...int) string {
increment := 1
switch len(params) {
@ -231,6 +266,6 @@ func seq(params ...int) string {
}
}
func intArrayToString(slice []int, delimeter string) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(slice)), delimeter), "[]")
func intArrayToString(slice []int, delimiter string) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(slice)), delimiter), "[]")
}

View file

@ -4,6 +4,8 @@ import (
"encoding/base32"
"encoding/base64"
"fmt"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"reflect"
"strconv"
"strings"
@ -149,6 +151,10 @@ func trunc(c int, s string) string {
return s
}
func title(s string) string {
return cases.Title(language.English).String(s)
}
func join(sep string, v any) string {
return strings.Join(strslice(v), sep)
}
@ -162,6 +168,10 @@ func split(sep, orig string) map[string]string {
return res
}
func splitList(sep, orig string) []string {
return strings.Split(orig, sep)
}
func splitn(sep string, n int, orig string) map[string]string {
parts := strings.SplitN(orig, sep, n)
res := make(map[string]string, len(parts))
@ -196,3 +206,27 @@ func repeat(count int, str string) string {
}
return strings.Repeat(str, count)
}
func trimAll(a, b string) string {
return strings.Trim(b, a)
}
func trimPrefix(a, b string) string {
return strings.TrimPrefix(b, a)
}
func trimSuffix(a, b string) string {
return strings.TrimSuffix(b, a)
}
func contains(substr string, str string) bool {
return strings.Contains(str, substr)
}
func hasPrefix(substr string, str string) bool {
return strings.HasPrefix(str, substr)
}
func hasSuffix(substr string, str string) bool {
return strings.HasSuffix(str, substr)
}