diff options
Diffstat (limited to 'funcs')
46 files changed, 0 insertions, 6182 deletions
diff --git a/funcs/aws.go b/funcs/aws.go deleted file mode 100644 index ef95d612..00000000 --- a/funcs/aws.go +++ /dev/null @@ -1,152 +0,0 @@ -package funcs - -import ( - "context" - "sync" - - "github.com/hairyhenderson/gomplate/v4/aws" - "github.com/hairyhenderson/gomplate/v4/conv" -) - -// AWSNS - the aws namespace -// -// Deprecated: don't use -// -//nolint:golint -func AWSNS() *Funcs { - return &Funcs{} -} - -// AWSFuncs - -// -// Deprecated: use [CreateAWSFuncs] instead -func AWSFuncs(f map[string]interface{}) { - f2 := CreateAWSFuncs(context.Background()) - for k, v := range f2 { - f[k] = v - } -} - -// CreateAWSFuncs - -func CreateAWSFuncs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - - ns := &Funcs{ - ctx: ctx, - awsopts: aws.GetClientOptions(), - } - - f["aws"] = func() interface{} { return ns } - - // global aliases - for backwards compatibility - f["ec2meta"] = ns.EC2Meta - f["ec2dynamic"] = ns.EC2Dynamic - f["ec2tag"] = ns.EC2Tag - f["ec2tags"] = ns.EC2Tags - f["ec2region"] = ns.EC2Region - return f -} - -// Funcs - -type Funcs struct { - ctx context.Context - - meta *aws.Ec2Meta - info *aws.Ec2Info - kms *aws.KMS - sts *aws.STS - metaInit sync.Once - infoInit sync.Once - kmsInit sync.Once - stsInit sync.Once - awsopts aws.ClientOptions -} - -// EC2Region - -func (a *Funcs) EC2Region(def ...string) (string, error) { - a.metaInit.Do(a.initMeta) - return a.meta.Region(def...) -} - -// EC2Meta - -func (a *Funcs) EC2Meta(key string, def ...string) (string, error) { - a.metaInit.Do(a.initMeta) - return a.meta.Meta(key, def...) -} - -// EC2Dynamic - -func (a *Funcs) EC2Dynamic(key string, def ...string) (string, error) { - a.metaInit.Do(a.initMeta) - return a.meta.Dynamic(key, def...) -} - -// EC2Tag - -func (a *Funcs) EC2Tag(tag string, def ...string) (string, error) { - a.infoInit.Do(a.initInfo) - return a.info.Tag(tag, def...) -} - -// EC2Tag - -func (a *Funcs) EC2Tags() (map[string]string, error) { - a.infoInit.Do(a.initInfo) - return a.info.Tags() -} - -// KMSEncrypt - -func (a *Funcs) KMSEncrypt(keyID, plaintext interface{}) (string, error) { - a.kmsInit.Do(a.initKMS) - return a.kms.Encrypt(conv.ToString(keyID), conv.ToString(plaintext)) -} - -// KMSDecrypt - -func (a *Funcs) KMSDecrypt(ciphertext interface{}) (string, error) { - a.kmsInit.Do(a.initKMS) - return a.kms.Decrypt(conv.ToString(ciphertext)) -} - -// UserID - Gets the unique identifier of the calling entity. The exact value -// depends on the type of entity making the call. The values returned are those -// listed in the aws:userid column in the Principal table -// (http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html#principaltable) -// found on the Policy Variables reference page in the IAM User Guide. -func (a *Funcs) UserID() (string, error) { - a.stsInit.Do(a.initSTS) - return a.sts.UserID() -} - -// Account - Gets the AWS account ID number of the account that owns or -// contains the calling entity. -func (a *Funcs) Account() (string, error) { - a.stsInit.Do(a.initSTS) - return a.sts.Account() -} - -// ARN - Gets the AWS ARN associated with the calling entity -func (a *Funcs) ARN() (string, error) { - a.stsInit.Do(a.initSTS) - return a.sts.Arn() -} - -func (a *Funcs) initMeta() { - if a.meta == nil { - a.meta = aws.NewEc2Meta(a.awsopts) - } -} - -func (a *Funcs) initInfo() { - if a.info == nil { - a.info = aws.NewEc2Info(a.awsopts) - } -} - -func (a *Funcs) initKMS() { - if a.kms == nil { - a.kms = aws.NewKMS(a.awsopts) - } -} - -func (a *Funcs) initSTS() { - if a.sts == nil { - a.sts = aws.NewSTS(a.awsopts) - } -} diff --git a/funcs/aws_test.go b/funcs/aws_test.go deleted file mode 100644 index 2a1efc5d..00000000 --- a/funcs/aws_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/hairyhenderson/gomplate/v4/aws" - "github.com/stretchr/testify/assert" -) - -func TestCreateAWSFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateAWSFuncs(ctx) - actual := fmap["aws"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*Funcs).ctx) - }) - } -} - -func TestAWSFuncs(t *testing.T) { - t.Parallel() - - m := aws.NewDummyEc2Meta() - i := aws.NewDummyEc2Info(m) - af := &Funcs{meta: m, info: i} - assert.Equal(t, "unknown", must(af.EC2Region())) - assert.Equal(t, "", must(af.EC2Meta("foo"))) - assert.Equal(t, "", must(af.EC2Tag("foo"))) - assert.Equal(t, "unknown", must(af.EC2Region())) -} - -func must(r interface{}, err error) interface{} { - if err != nil { - panic(err) - } - return r -} diff --git a/funcs/base64.go b/funcs/base64.go deleted file mode 100644 index b25a9091..00000000 --- a/funcs/base64.go +++ /dev/null @@ -1,77 +0,0 @@ -package funcs - -import ( - "context" - - "github.com/hairyhenderson/gomplate/v4/base64" - "github.com/hairyhenderson/gomplate/v4/conv" -) - -// Base64NS - the base64 namespace -// -// Deprecated: don't use -func Base64NS() *Base64Funcs { - return &Base64Funcs{} -} - -// AddBase64Funcs - -// -// Deprecated: use [CreateBase64Funcs] instead -func AddBase64Funcs(f map[string]interface{}) { - for k, v := range CreateBase64Funcs(context.Background()) { - f[k] = v - } -} - -// CreateBase64Funcs - -func CreateBase64Funcs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - - ns := &Base64Funcs{ctx} - f["base64"] = func() interface{} { return ns } - - return f -} - -// Base64Funcs - -type Base64Funcs struct { - ctx context.Context -} - -// Encode - -func (Base64Funcs) Encode(in interface{}) (string, error) { - b := toBytes(in) - return base64.Encode(b) -} - -// Decode - -func (Base64Funcs) Decode(in interface{}) (string, error) { - out, err := base64.Decode(conv.ToString(in)) - return string(out), err -} - -// DecodeBytes - -func (Base64Funcs) DecodeBytes(in interface{}) ([]byte, error) { - out, err := base64.Decode(conv.ToString(in)) - return out, err -} - -type byter interface { - Bytes() []byte -} - -func toBytes(in interface{}) []byte { - if in == nil { - return []byte{} - } - if s, ok := in.([]byte); ok { - return s - } - if s, ok := in.(byter); ok { - return s.Bytes() - } - if s, ok := in.(string); ok { - return []byte(s) - } - return []byte(conv.ToString(in)) -} diff --git a/funcs/base64_test.go b/funcs/base64_test.go deleted file mode 100644 index f0cca886..00000000 --- a/funcs/base64_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package funcs - -import ( - "bytes" - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateBase64Funcs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateBase64Funcs(ctx) - actual := fmap["base64"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*Base64Funcs).ctx) - }) - } -} - -func TestBase64Encode(t *testing.T) { - t.Parallel() - - bf := &Base64Funcs{} - assert.Equal(t, "Zm9vYmFy", must(bf.Encode("foobar"))) -} - -func TestBase64Decode(t *testing.T) { - t.Parallel() - - bf := &Base64Funcs{} - assert.Equal(t, "foobar", must(bf.Decode("Zm9vYmFy"))) -} - -func TestBase64DecodeBytes(t *testing.T) { - t.Parallel() - - bf := &Base64Funcs{} - out, err := bf.DecodeBytes("Zm9vYmFy") - require.NoError(t, err) - assert.Equal(t, "foobar", string(out)) -} - -func TestToBytes(t *testing.T) { - t.Parallel() - - assert.Equal(t, []byte{0, 1, 2, 3}, toBytes([]byte{0, 1, 2, 3})) - - buf := &bytes.Buffer{} - buf.WriteString("hi") - assert.Equal(t, []byte("hi"), toBytes(buf)) - assert.Equal(t, []byte{}, toBytes(nil)) - assert.Equal(t, []byte("42"), toBytes(42)) -} - -func BenchmarkToBytes(b *testing.B) { - for i := 0; i < b.N; i++ { - b.StopTimer() - buf := &bytes.Buffer{} - buf.WriteString("hi") - bin := []byte{0, 1, 2, 3} - b.StartTimer() - - toBytes(bin) - - toBytes(buf) - toBytes(nil) - toBytes(42) - } -} diff --git a/funcs/coll.go b/funcs/coll.go deleted file mode 100644 index c8cb4dcc..00000000 --- a/funcs/coll.go +++ /dev/null @@ -1,218 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - "reflect" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/internal/deprecated" - "github.com/hairyhenderson/gomplate/v4/internal/texttemplate" - - "github.com/hairyhenderson/gomplate/v4/coll" -) - -// CollNS - -// -// Deprecated: don't use -func CollNS() *CollFuncs { - return &CollFuncs{} -} - -// AddCollFuncs - -// -// Deprecated: use CreateCollFuncs instead -func AddCollFuncs(f map[string]interface{}) { - for k, v := range CreateCollFuncs(context.Background()) { - f[k] = v - } -} - -// CreateCollFuncs - -func CreateCollFuncs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - - ns := &CollFuncs{ctx} - f["coll"] = func() interface{} { return ns } - - f["has"] = ns.Has - f["slice"] = ns.deprecatedSlice - f["dict"] = ns.Dict - f["keys"] = ns.Keys - f["values"] = ns.Values - f["append"] = ns.Append - f["prepend"] = ns.Prepend - f["uniq"] = ns.Uniq - f["reverse"] = ns.Reverse - f["merge"] = ns.Merge - f["sort"] = ns.Sort - f["jsonpath"] = ns.JSONPath - f["jq"] = ns.JQ - f["flatten"] = ns.Flatten - return f -} - -// CollFuncs - -type CollFuncs struct { - ctx context.Context -} - -// Slice - -func (CollFuncs) Slice(args ...interface{}) []interface{} { - return coll.Slice(args...) -} - -// deprecatedSlice - -// Deprecated: use coll.Slice instead -func (f *CollFuncs) deprecatedSlice(args ...interface{}) []interface{} { - deprecated.WarnDeprecated(f.ctx, "the 'slice' alias for coll.Slice is deprecated - use coll.Slice instead") - return coll.Slice(args...) -} - -// GoSlice - same as text/template's 'slice' function -func (CollFuncs) GoSlice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { - return texttemplate.GoSlice(item, indexes...) -} - -// Has - -func (CollFuncs) Has(in interface{}, key string) bool { - return coll.Has(in, key) -} - -// Index returns the result of indexing the last argument with the preceding -// index keys. This is similar to the `index` built-in template function, but -// the arguments are ordered differently for pipeline compatibility. Also, this -// function is more strict, and will return an error when the value doesn't -// contain the given key. -func (CollFuncs) Index(args ...interface{}) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf("wrong number of args: wanted at least 2, got %d", len(args)) - } - - item := args[len(args)-1] - indexes := args[:len(args)-1] - - return coll.Index(item, indexes...) -} - -// Dict - -func (CollFuncs) Dict(in ...interface{}) (map[string]interface{}, error) { - return coll.Dict(in...) -} - -// Keys - -func (CollFuncs) Keys(in ...map[string]interface{}) ([]string, error) { - return coll.Keys(in...) -} - -// Values - -func (CollFuncs) Values(in ...map[string]interface{}) ([]interface{}, error) { - return coll.Values(in...) -} - -// Append - -func (CollFuncs) Append(v interface{}, list interface{}) ([]interface{}, error) { - return coll.Append(v, list) -} - -// Prepend - -func (CollFuncs) Prepend(v interface{}, list interface{}) ([]interface{}, error) { - return coll.Prepend(v, list) -} - -// Uniq - -func (CollFuncs) Uniq(in interface{}) ([]interface{}, error) { - return coll.Uniq(in) -} - -// Reverse - -func (CollFuncs) Reverse(in interface{}) ([]interface{}, error) { - return coll.Reverse(in) -} - -// Merge - -func (CollFuncs) Merge(dst map[string]interface{}, src ...map[string]interface{}) (map[string]interface{}, error) { - return coll.Merge(dst, src...) -} - -// Sort - -func (CollFuncs) Sort(args ...interface{}) ([]interface{}, error) { - var ( - key string - list interface{} - ) - if len(args) == 0 || len(args) > 2 { - return nil, fmt.Errorf("wrong number of args: wanted 1 or 2, got %d", len(args)) - } - if len(args) == 1 { - list = args[0] - } - if len(args) == 2 { - key = conv.ToString(args[0]) - list = args[1] - } - return coll.Sort(key, list) -} - -// JSONPath - -func (CollFuncs) JSONPath(p string, in interface{}) (interface{}, error) { - return coll.JSONPath(p, in) -} - -// JQ - -func (f *CollFuncs) JQ(jqExpr string, in interface{}) (interface{}, error) { - return coll.JQ(f.ctx, jqExpr, in) -} - -// Flatten - -func (CollFuncs) Flatten(args ...interface{}) ([]interface{}, error) { - if len(args) == 0 || len(args) > 2 { - return nil, fmt.Errorf("wrong number of args: wanted 1 or 2, got %d", len(args)) - } - list := args[0] - depth := -1 - if len(args) == 2 { - depth = conv.ToInt(args[0]) - list = args[1] - } - return coll.Flatten(list, depth) -} - -func pickOmitArgs(args ...interface{}) (map[string]interface{}, []string, error) { - if len(args) <= 1 { - return nil, nil, fmt.Errorf("wrong number of args: wanted 2 or more, got %d", len(args)) - } - - m, ok := args[len(args)-1].(map[string]interface{}) - if !ok { - return nil, nil, fmt.Errorf("wrong map type: must be map[string]interface{}, got %T", args[len(args)-1]) - } - - keys := make([]string, len(args)-1) - for i, v := range args[0 : len(args)-1] { - k, ok := v.(string) - if !ok { - return nil, nil, fmt.Errorf("wrong key type: must be string, got %T (%+v)", args[i], args[i]) - } - keys[i] = k - } - return m, keys, nil -} - -// Pick - -func (CollFuncs) Pick(args ...interface{}) (map[string]interface{}, error) { - m, keys, err := pickOmitArgs(args...) - if err != nil { - return nil, err - } - return coll.Pick(m, keys...), nil -} - -// Omit - -func (CollFuncs) Omit(args ...interface{}) (map[string]interface{}, error) { - m, keys, err := pickOmitArgs(args...) - if err != nil { - return nil, err - } - return coll.Omit(m, keys...), nil -} diff --git a/funcs/coll_test.go b/funcs/coll_test.go deleted file mode 100644 index 71900a59..00000000 --- a/funcs/coll_test.go +++ /dev/null @@ -1,179 +0,0 @@ -package funcs - -import ( - "context" - "reflect" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateCollFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateCollFuncs(ctx) - actual := fmap["coll"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*CollFuncs).ctx) - }) - } -} - -func TestFlatten(t *testing.T) { - t.Parallel() - - c := CollFuncs{} - - _, err := c.Flatten() - assert.Error(t, err) - - _, err = c.Flatten(42) - assert.Error(t, err) - - out, err := c.Flatten([]interface{}{1, []interface{}{[]int{2}, 3}}) - require.NoError(t, err) - assert.EqualValues(t, []interface{}{1, 2, 3}, out) - - out, err = c.Flatten(1, []interface{}{1, []interface{}{[]int{2}, 3}}) - require.NoError(t, err) - assert.EqualValues(t, []interface{}{1, []int{2}, 3}, out) -} - -func TestPick(t *testing.T) { - t.Parallel() - - c := &CollFuncs{} - - _, err := c.Pick() - assert.Error(t, err) - - _, err = c.Pick("") - assert.Error(t, err) - - _, err = c.Pick("foo", nil) - assert.Error(t, err) - - _, err = c.Pick("foo", "bar") - assert.Error(t, err) - - _, err = c.Pick(map[string]interface{}{}, "foo", "bar", map[string]interface{}{}) - assert.Error(t, err) - - in := map[string]interface{}{ - "foo": "bar", - "bar": true, - "": "baz", - } - out, err := c.Pick("baz", in) - require.NoError(t, err) - assert.EqualValues(t, map[string]interface{}{}, out) - - expected := map[string]interface{}{ - "foo": "bar", - "bar": true, - } - out, err = c.Pick("foo", "bar", in) - require.NoError(t, err) - assert.EqualValues(t, expected, out) - - expected = map[string]interface{}{ - "": "baz", - } - out, err = c.Pick("", in) - require.NoError(t, err) - assert.EqualValues(t, expected, out) - - out, err = c.Pick("foo", "bar", "", in) - require.NoError(t, err) - assert.EqualValues(t, in, out) -} - -func TestOmit(t *testing.T) { - t.Parallel() - - c := &CollFuncs{} - - _, err := c.Omit() - assert.Error(t, err) - - _, err = c.Omit("") - assert.Error(t, err) - - _, err = c.Omit("foo", nil) - assert.Error(t, err) - - _, err = c.Omit("foo", "bar") - assert.Error(t, err) - - _, err = c.Omit(map[string]interface{}{}, "foo", "bar", map[string]interface{}{}) - assert.Error(t, err) - - in := map[string]interface{}{ - "foo": "bar", - "bar": true, - "": "baz", - } - out, err := c.Omit("baz", in) - require.NoError(t, err) - assert.EqualValues(t, in, out) - - expected := map[string]interface{}{ - "foo": "bar", - "bar": true, - } - out, err = c.Omit("", in) - require.NoError(t, err) - assert.EqualValues(t, expected, out) - - expected = map[string]interface{}{ - "": "baz", - } - out, err = c.Omit("foo", "bar", in) - require.NoError(t, err) - assert.EqualValues(t, expected, out) - - out, err = c.Omit("foo", "bar", "", in) - require.NoError(t, err) - assert.EqualValues(t, map[string]interface{}{}, out) -} - -func TestGoSlice(t *testing.T) { - t.Parallel() - - c := &CollFuncs{} - - in := reflect.ValueOf(nil) - _, err := c.GoSlice(in) - assert.Error(t, err) - - in = reflect.ValueOf(42) - _, err = c.GoSlice(in) - assert.Error(t, err) - - // invalid index type - in = reflect.ValueOf([]interface{}{1}) - _, err = c.GoSlice(in, reflect.ValueOf([]interface{}{[]int{2}})) - assert.Error(t, err) - - // valid slice, no slicing - in = reflect.ValueOf([]int{1}) - out, err := c.GoSlice(in) - require.NoError(t, err) - assert.Equal(t, reflect.TypeOf([]int{}), out.Type()) - assert.EqualValues(t, []int{1}, out.Interface()) - - // valid slice, slicing - in = reflect.ValueOf([]string{"foo", "bar", "baz"}) - out, err = c.GoSlice(in, reflect.ValueOf(1), reflect.ValueOf(3)) - require.NoError(t, err) - assert.Equal(t, reflect.TypeOf([]string{}), out.Type()) - assert.EqualValues(t, []string{"bar", "baz"}, out.Interface()) -} diff --git a/funcs/conv.go b/funcs/conv.go deleted file mode 100644 index 3361879d..00000000 --- a/funcs/conv.go +++ /dev/null @@ -1,166 +0,0 @@ -package funcs - -import ( - "context" - "net/url" - "text/template" - - "github.com/hairyhenderson/gomplate/v4/coll" - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/internal/deprecated" -) - -// ConvNS - -// -// Deprecated: don't use -func ConvNS() *ConvFuncs { - return &ConvFuncs{} -} - -// AddConvFuncs - -// -// Deprecated: use [CreateConvFuncs] instead -func AddConvFuncs(f map[string]interface{}) { - for k, v := range CreateConvFuncs(context.Background()) { - f[k] = v - } -} - -// CreateConvFuncs - -func CreateConvFuncs(ctx context.Context) map[string]interface{} { - ns := &ConvFuncs{ctx} - - f := map[string]interface{}{} - f["conv"] = func() interface{} { return ns } - - f["urlParse"] = ns.URL - f["bool"] = ns.Bool - f["join"] = ns.Join - f["default"] = ns.Default - return f -} - -// ConvFuncs - -type ConvFuncs struct { - ctx context.Context -} - -// Bool - -// -// Deprecated: use [ToBool] instead -func (f *ConvFuncs) Bool(s interface{}) bool { - deprecated.WarnDeprecated(f.ctx, "conv.Bool is deprecated - use conv.ToBool instead") - return conv.Bool(conv.ToString(s)) -} - -// ToBool - -func (ConvFuncs) ToBool(in interface{}) bool { - return conv.ToBool(in) -} - -// ToBools - -func (ConvFuncs) ToBools(in ...interface{}) []bool { - return conv.ToBools(in...) -} - -// Slice - -// -// Deprecated: use [CollFuncs.Slice] instead -func (f *ConvFuncs) Slice(args ...interface{}) []interface{} { - deprecated.WarnDeprecated(f.ctx, "conv.Slice is deprecated - use coll.Slice instead") - return coll.Slice(args...) -} - -// Join - -func (ConvFuncs) Join(in interface{}, sep string) (string, error) { - return conv.Join(in, sep) -} - -// Has - -// -// Deprecated: use [CollFuncs.Has] instead -func (f *ConvFuncs) Has(in interface{}, key string) bool { - deprecated.WarnDeprecated(f.ctx, "conv.Has is deprecated - use coll.Has instead") - return coll.Has(in, key) -} - -// ParseInt - -func (ConvFuncs) ParseInt(s interface{}, base, bitSize int) int64 { - return conv.MustParseInt(conv.ToString(s), base, bitSize) -} - -// ParseFloat - -func (ConvFuncs) ParseFloat(s interface{}, bitSize int) float64 { - return conv.MustParseFloat(conv.ToString(s), bitSize) -} - -// ParseUint - -func (ConvFuncs) ParseUint(s interface{}, base, bitSize int) uint64 { - return conv.MustParseUint(conv.ToString(s), base, bitSize) -} - -// Atoi - -func (ConvFuncs) Atoi(s interface{}) int { - return conv.MustAtoi(conv.ToString(s)) -} - -// URL - -func (ConvFuncs) URL(s interface{}) (*url.URL, error) { - return url.Parse(conv.ToString(s)) -} - -// ToInt64 - -func (ConvFuncs) ToInt64(in interface{}) int64 { - return conv.ToInt64(in) -} - -// ToInt - -func (ConvFuncs) ToInt(in interface{}) int { - return conv.ToInt(in) -} - -// ToInt64s - -func (ConvFuncs) ToInt64s(in ...interface{}) []int64 { - return conv.ToInt64s(in...) -} - -// ToInts - -func (ConvFuncs) ToInts(in ...interface{}) []int { - return conv.ToInts(in...) -} - -// ToFloat64 - -func (ConvFuncs) ToFloat64(in interface{}) float64 { - return conv.ToFloat64(in) -} - -// ToFloat64s - -func (ConvFuncs) ToFloat64s(in ...interface{}) []float64 { - return conv.ToFloat64s(in...) -} - -// ToString - -func (ConvFuncs) ToString(in interface{}) string { - return conv.ToString(in) -} - -// ToStrings - -func (ConvFuncs) ToStrings(in ...interface{}) []string { - return conv.ToStrings(in...) -} - -// Default - -func (ConvFuncs) Default(def, in interface{}) interface{} { - if truth, ok := template.IsTrue(in); truth && ok { - return in - } - return def -} - -// Dict - -// -// Deprecated: use [CollFuncs.Dict] instead -func (f *ConvFuncs) Dict(in ...interface{}) (map[string]interface{}, error) { - deprecated.WarnDeprecated(f.ctx, "conv.Dict is deprecated - use coll.Dict instead") - return coll.Dict(in...) -} diff --git a/funcs/conv_test.go b/funcs/conv_test.go deleted file mode 100644 index b20f013c..00000000 --- a/funcs/conv_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateConvFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateConvFuncs(ctx) - actual := fmap["conv"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*ConvFuncs).ctx) - }) - } -} - -func TestDefault(t *testing.T) { - t.Parallel() - - s := struct{}{} - c := &ConvFuncs{} - def := "DEFAULT" - data := []struct { - val interface{} - empty bool - }{ - {0, true}, - {1, false}, - {nil, true}, - {"", true}, - {"foo", false}, - {[]string{}, true}, - {[]string{"foo"}, false}, - {[]string{""}, false}, - {c, false}, - {s, false}, - } - - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%T/%#v empty==%v", d.val, d.val, d.empty), func(t *testing.T) { - t.Parallel() - - if d.empty { - assert.Equal(t, def, c.Default(def, d.val)) - } else { - assert.Equal(t, d.val, c.Default(def, d.val)) - } - }) - } -} diff --git a/funcs/crypto.go b/funcs/crypto.go deleted file mode 100644 index e3628fad..00000000 --- a/funcs/crypto.go +++ /dev/null @@ -1,399 +0,0 @@ -package funcs - -import ( - "context" - gcrypto "crypto" - "crypto/elliptic" - "crypto/sha1" //nolint: gosec - "crypto/sha256" - "crypto/sha512" - "encoding/base64" - "encoding/hex" - "fmt" - "strings" - "unicode/utf8" - - "golang.org/x/crypto/bcrypt" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/crypto" -) - -// CryptoNS - the crypto namespace -// -// Deprecated: don't use -func CryptoNS() *CryptoFuncs { - return &CryptoFuncs{} -} - -// AddCryptoFuncs - -// -// Deprecated: use [CreateCryptoFuncs] instead -func AddCryptoFuncs(f map[string]interface{}) { - for k, v := range CreateCryptoFuncs(context.Background()) { - f[k] = v - } -} - -// CreateCryptoFuncs - -func CreateCryptoFuncs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - - ns := &CryptoFuncs{ctx} - - f["crypto"] = func() interface{} { return ns } - return f -} - -// CryptoFuncs - -type CryptoFuncs struct { - ctx context.Context -} - -// PBKDF2 - Run the Password-Based Key Derivation Function #2 as defined in -// RFC 2898 (PKCS #5 v2.0). This function outputs the binary result in hex -// format. -func (CryptoFuncs) PBKDF2(password, salt, iter, keylen interface{}, hashFunc ...string) (k string, err error) { - var h gcrypto.Hash - if len(hashFunc) == 0 { - h = gcrypto.SHA1 - } else { - h, err = crypto.StrToHash(hashFunc[0]) - if err != nil { - return "", err - } - } - pw := toBytes(password) - s := toBytes(salt) - i := conv.ToInt(iter) - kl := conv.ToInt(keylen) - - dk, err := crypto.PBKDF2(pw, s, i, kl, h) - return fmt.Sprintf("%02x", dk), err -} - -// WPAPSK - Convert an ASCII passphrase to WPA PSK for a given SSID -func (f CryptoFuncs) WPAPSK(ssid, password interface{}) (string, error) { - return f.PBKDF2(password, ssid, 4096, 32) -} - -// SHA1 - Note: SHA-1 is cryptographically broken and should not be used for secure applications. -func (f CryptoFuncs) SHA1(input interface{}) string { - out, _ := f.SHA1Bytes(input) - return fmt.Sprintf("%02x", out) -} - -// SHA224 - -func (f CryptoFuncs) SHA224(input interface{}) string { - out, _ := f.SHA224Bytes(input) - return fmt.Sprintf("%02x", out) -} - -// SHA256 - -func (f CryptoFuncs) SHA256(input interface{}) string { - out, _ := f.SHA256Bytes(input) - return fmt.Sprintf("%02x", out) -} - -// SHA384 - -func (f CryptoFuncs) SHA384(input interface{}) string { - out, _ := f.SHA384Bytes(input) - return fmt.Sprintf("%02x", out) -} - -// SHA512 - -func (f CryptoFuncs) SHA512(input interface{}) string { - out, _ := f.SHA512Bytes(input) - return fmt.Sprintf("%02x", out) -} - -// SHA512_224 - -// -//nolint:revive,stylecheck -func (f CryptoFuncs) SHA512_224(input interface{}) string { - out, _ := f.SHA512_224Bytes(input) - return fmt.Sprintf("%02x", out) -} - -// SHA512_256 - -// -//nolint:revive,stylecheck -func (f CryptoFuncs) SHA512_256(input interface{}) string { - out, _ := f.SHA512_256Bytes(input) - return fmt.Sprintf("%02x", out) -} - -// SHA1 - Note: SHA-1 is cryptographically broken and should not be used for secure applications. -func (CryptoFuncs) SHA1Bytes(input interface{}) ([]byte, error) { - //nolint:gosec - b := sha1.Sum(toBytes(input)) - out := make([]byte, len(b)) - copy(out, b[:]) - return out, nil -} - -// SHA224 - -func (CryptoFuncs) SHA224Bytes(input interface{}) ([]byte, error) { - b := sha256.Sum224(toBytes(input)) - out := make([]byte, len(b)) - copy(out, b[:]) - return out, nil -} - -// SHA256 - -func (CryptoFuncs) SHA256Bytes(input interface{}) ([]byte, error) { - b := sha256.Sum256(toBytes(input)) - out := make([]byte, len(b)) - copy(out, b[:]) - return out, nil -} - -// SHA384 - -func (CryptoFuncs) SHA384Bytes(input interface{}) ([]byte, error) { - b := sha512.Sum384(toBytes(input)) - out := make([]byte, len(b)) - copy(out, b[:]) - return out, nil -} - -// SHA512 - -func (CryptoFuncs) SHA512Bytes(input interface{}) ([]byte, error) { - b := sha512.Sum512(toBytes(input)) - out := make([]byte, len(b)) - copy(out, b[:]) - return out, nil -} - -// SHA512_224 - -func (CryptoFuncs) SHA512_224Bytes(input interface{}) ([]byte, error) { - b := sha512.Sum512_224(toBytes(input)) - out := make([]byte, len(b)) - copy(out, b[:]) - return out, nil -} - -// SHA512_256 - -func (CryptoFuncs) SHA512_256Bytes(input interface{}) ([]byte, error) { - b := sha512.Sum512_256(toBytes(input)) - out := make([]byte, len(b)) - copy(out, b[:]) - return out, nil -} - -// Bcrypt - -func (CryptoFuncs) Bcrypt(args ...interface{}) (string, error) { - input := "" - cost := bcrypt.DefaultCost - if len(args) == 0 { - return "", fmt.Errorf("bcrypt requires at least an 'input' value") - } - if len(args) == 1 { - input = conv.ToString(args[0]) - } - if len(args) == 2 { - cost = conv.ToInt(args[0]) - input = conv.ToString(args[1]) - } - hash, err := bcrypt.GenerateFromPassword([]byte(input), cost) - return string(hash), err -} - -// RSAEncrypt - -// Experimental! -func (f *CryptoFuncs) RSAEncrypt(key string, in interface{}) ([]byte, error) { - if err := checkExperimental(f.ctx); err != nil { - return nil, err - } - msg := toBytes(in) - return crypto.RSAEncrypt(key, msg) -} - -// RSADecrypt - -// Experimental! -func (f *CryptoFuncs) RSADecrypt(key string, in []byte) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - out, err := crypto.RSADecrypt(key, in) - return string(out), err -} - -// RSADecryptBytes - -// Experimental! -func (f *CryptoFuncs) RSADecryptBytes(key string, in []byte) ([]byte, error) { - if err := checkExperimental(f.ctx); err != nil { - return nil, err - } - out, err := crypto.RSADecrypt(key, in) - return out, err -} - -// RSAGenerateKey - -// Experimental! -func (f *CryptoFuncs) RSAGenerateKey(args ...interface{}) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - bits := 4096 - if len(args) == 1 { - bits = conv.ToInt(args[0]) - } else if len(args) > 1 { - return "", fmt.Errorf("wrong number of args: want 0 or 1, got %d", len(args)) - } - out, err := crypto.RSAGenerateKey(bits) - return string(out), err -} - -// RSADerivePublicKey - -// Experimental! -func (f *CryptoFuncs) RSADerivePublicKey(privateKey string) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - out, err := crypto.RSADerivePublicKey([]byte(privateKey)) - return string(out), err -} - -// ECDSAGenerateKey - -// Experimental! -func (f *CryptoFuncs) ECDSAGenerateKey(args ...interface{}) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - - curve := elliptic.P256() - if len(args) == 1 { - c := conv.ToString(args[0]) - c = strings.ToUpper(c) - c = strings.ReplaceAll(c, "-", "") - var ok bool - curve, ok = crypto.Curves(c) - if !ok { - return "", fmt.Errorf("unknown curve: %s", c) - } - } else if len(args) > 1 { - return "", fmt.Errorf("wrong number of args: want 0 or 1, got %d", len(args)) - } - - out, err := crypto.ECDSAGenerateKey(curve) - return string(out), err -} - -// ECDSADerivePublicKey - -// Experimental! -func (f *CryptoFuncs) ECDSADerivePublicKey(privateKey string) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - - out, err := crypto.ECDSADerivePublicKey([]byte(privateKey)) - return string(out), err -} - -// Ed25519GenerateKey - -// Experimental! -func (f *CryptoFuncs) Ed25519GenerateKey() (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - out, err := crypto.Ed25519GenerateKey() - return string(out), err -} - -// Ed25519GenerateKeyFromSeed - -// Experimental! -func (f *CryptoFuncs) Ed25519GenerateKeyFromSeed(encoding, seed string) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - if !utf8.ValidString(seed) { - return "", fmt.Errorf("given seed is not valid UTF-8") // Don't print out seed (private). - } - var seedB []byte - var err error - switch encoding { - case "base64": - seedB, err = base64.StdEncoding.DecodeString(seed) - case "hex": - seedB, err = hex.DecodeString(seed) - default: - return "", fmt.Errorf("invalid encoding given: %s - only 'hex' or 'base64' are valid options", encoding) - } - if err != nil { - return "", fmt.Errorf("could not decode given seed: %w", err) - } - out, err := crypto.Ed25519GenerateKeyFromSeed(seedB) - return string(out), err -} - -// Ed25519DerivePublicKey - -// Experimental! -func (f *CryptoFuncs) Ed25519DerivePublicKey(privateKey string) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - out, err := crypto.Ed25519DerivePublicKey([]byte(privateKey)) - return string(out), err -} - -// EncryptAES - -// Experimental! -func (f *CryptoFuncs) EncryptAES(key string, args ...interface{}) ([]byte, error) { - if err := checkExperimental(f.ctx); err != nil { - return nil, err - } - - k, msg, err := parseAESArgs(key, args...) - if err != nil { - return nil, err - } - - return crypto.EncryptAESCBC(k, msg) -} - -// DecryptAES - -// Experimental! -func (f *CryptoFuncs) DecryptAES(key string, args ...interface{}) (string, error) { - if err := checkExperimental(f.ctx); err != nil { - return "", err - } - - out, err := f.DecryptAESBytes(key, args...) - return conv.ToString(out), err -} - -// DecryptAESBytes - -// Experimental! -func (f *CryptoFuncs) DecryptAESBytes(key string, args ...interface{}) ([]byte, error) { - if err := checkExperimental(f.ctx); err != nil { - return nil, err - } - - k, msg, err := parseAESArgs(key, args...) - if err != nil { - return nil, err - } - - return crypto.DecryptAESCBC(k, msg) -} - -func parseAESArgs(key string, args ...interface{}) ([]byte, []byte, error) { - keyBits := 256 // default to AES-256-CBC - - var msg []byte - - switch len(args) { - case 1: - msg = toBytes(args[0]) - case 2: - keyBits = conv.ToInt(args[0]) - msg = toBytes(args[1]) - default: - return nil, nil, fmt.Errorf("wrong number of args: want 2 or 3, got %d", len(args)) - } - - k := make([]byte, keyBits/8) - copy(k, []byte(key)) - - return k, msg, nil -} diff --git a/funcs/crypto_test.go b/funcs/crypto_test.go deleted file mode 100644 index 4d1b5660..00000000 --- a/funcs/crypto_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package funcs - -import ( - "context" - "encoding/base64" - "strconv" - "strings" - "testing" - - "github.com/hairyhenderson/gomplate/v4/internal/config" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateCryptoFuncs(t *testing.T) { - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateCryptoFuncs(ctx) - actual := fmap["crypto"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*CryptoFuncs).ctx) - }) - } -} - -func testCryptoNS() *CryptoFuncs { - return &CryptoFuncs{ctx: config.SetExperimental(context.Background())} -} - -func TestPBKDF2(t *testing.T) { - t.Parallel() - - c := testCryptoNS() - dk, err := c.PBKDF2("password", []byte("IEEE"), "4096", 32) - assert.Equal(t, "f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e", dk) - require.NoError(t, err) - - dk, err = c.PBKDF2([]byte("password"), "IEEE", 4096, "64", "SHA-512") - assert.Equal(t, "c16f4cb6d03e23614399dee5e7f676fb1da0eb9471b6a74a6c5bc934c6ec7d2ab7028fbb1000b1beb97f17646045d8144792352f6676d13b20a4c03754903d7e", dk) - require.NoError(t, err) - - _, err = c.PBKDF2(nil, nil, nil, nil, "bogus") - assert.Error(t, err) -} - -func TestWPAPSK(t *testing.T) { - t.Parallel() - - c := testCryptoNS() - dk, err := c.WPAPSK("password", "MySSID") - assert.Equal(t, "3a98def84b11644a17ebcc9b17955d2360ce8b8a85b8a78413fc551d722a84e7", dk) - require.NoError(t, err) -} - -func TestSHA(t *testing.T) { - t.Parallel() - - in := "abc" - sha1 := "a9993e364706816aba3e25717850c26c9cd0d89d" - sha224 := "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7" - sha256 := "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" - sha384 := "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7" - sha512 := "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" - sha512_224 := "4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa" - sha512_256 := "53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23" - c := testCryptoNS() - assert.Equal(t, sha1, c.SHA1(in)) - assert.Equal(t, sha224, c.SHA224(in)) - assert.Equal(t, sha256, c.SHA256(in)) - assert.Equal(t, sha384, c.SHA384(in)) - assert.Equal(t, sha512, c.SHA512(in)) - assert.Equal(t, sha512_224, c.SHA512_224(in)) - assert.Equal(t, sha512_256, c.SHA512_256(in)) -} - -func TestBcrypt(t *testing.T) { - t.Parallel() - - if testing.Short() { - t.Skip("skipping slow test") - } - - in := "foo" - c := testCryptoNS() - - t.Run("no arg default", func(t *testing.T) { - t.Parallel() - - actual, err := c.Bcrypt(in) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(actual, "$2a$10$")) - }) - - t.Run("cost less than min", func(t *testing.T) { - t.Parallel() - - actual, err := c.Bcrypt(0, in) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(actual, "$2a$10$")) - }) - - t.Run("cost equal to min", func(t *testing.T) { - t.Parallel() - - actual, err := c.Bcrypt(4, in) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(actual, "$2a$04$")) - }) - - t.Run("no args errors", func(t *testing.T) { - t.Parallel() - - _, err := c.Bcrypt() - assert.Error(t, err) - }) -} - -func TestRSAGenerateKey(t *testing.T) { - t.Parallel() - - c := testCryptoNS() - _, err := c.RSAGenerateKey(0) - assert.Error(t, err) - - _, err = c.RSAGenerateKey(0, "foo", true) - assert.Error(t, err) - - key, err := c.RSAGenerateKey(2048) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(key, - "-----BEGIN RSA PRIVATE KEY-----")) - assert.True(t, strings.HasSuffix(key, - "-----END RSA PRIVATE KEY-----\n")) -} - -func TestECDSAGenerateKey(t *testing.T) { - c := testCryptoNS() - _, err := c.ECDSAGenerateKey("") - assert.Error(t, err) - - _, err = c.ECDSAGenerateKey(0, "P-999", true) - assert.Error(t, err) - - key, err := c.ECDSAGenerateKey("P-256") - require.NoError(t, err) - assert.True(t, strings.HasPrefix(key, - "-----BEGIN EC PRIVATE KEY-----")) - assert.True(t, strings.HasSuffix(key, - "-----END EC PRIVATE KEY-----\n")) -} - -func TestECDSADerivePublicKey(t *testing.T) { - c := testCryptoNS() - - _, err := c.ECDSADerivePublicKey("") - assert.Error(t, err) - - key, _ := c.ECDSAGenerateKey("P-256") - pub, err := c.ECDSADerivePublicKey(key) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(pub, - "-----BEGIN PUBLIC KEY-----")) - assert.True(t, strings.HasSuffix(pub, - "-----END PUBLIC KEY-----\n")) -} - -func TestEd25519GenerateKey(t *testing.T) { - c := testCryptoNS() - key, err := c.Ed25519GenerateKey() - require.NoError(t, err) - - assert.True(t, strings.HasPrefix(key, - "-----BEGIN PRIVATE KEY-----")) - assert.True(t, strings.HasSuffix(key, - "-----END PRIVATE KEY-----\n")) -} - -func TestEd25519GenerateKeyFromSeed(t *testing.T) { - c := testCryptoNS() - enc := "" - seed := "" - _, err := c.Ed25519GenerateKeyFromSeed(enc, seed) - assert.Error(t, err) - - enc = "base64" - seed = "0000000000000000000000000000000" // 31 bytes, instead of wanted 32. - _, err = c.Ed25519GenerateKeyFromSeed(enc, seed) - assert.Error(t, err) - - seed += "0" // 32 bytes. - b64seed := base64.StdEncoding.EncodeToString([]byte(seed)) - key, err := c.Ed25519GenerateKeyFromSeed(enc, b64seed) - require.NoError(t, err) - - assert.True(t, strings.HasPrefix(key, - "-----BEGIN PRIVATE KEY-----")) - assert.True(t, strings.HasSuffix(key, - "-----END PRIVATE KEY-----\n")) -} - -func TestEd25519DerivePublicKey(t *testing.T) { - c := testCryptoNS() - - _, err := c.Ed25519DerivePublicKey("") - assert.Error(t, err) - - key, _ := c.Ed25519GenerateKey() - pub, err := c.Ed25519DerivePublicKey(key) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(pub, - "-----BEGIN PUBLIC KEY-----")) - assert.True(t, strings.HasSuffix(pub, - "-----END PUBLIC KEY-----\n")) -} - -func TestRSACrypt(t *testing.T) { - t.Parallel() - - if testing.Short() { - t.Skip("skipping slow test") - } - - c := testCryptoNS() - key, err := c.RSAGenerateKey() - require.NoError(t, err) - pub, err := c.RSADerivePublicKey(key) - require.NoError(t, err) - - in := "hello world" - enc, err := c.RSAEncrypt(pub, in) - require.NoError(t, err) - - dec, err := c.RSADecrypt(key, enc) - require.NoError(t, err) - assert.Equal(t, in, dec) - - b, err := c.RSADecryptBytes(key, enc) - require.NoError(t, err) - assert.Equal(t, dec, string(b)) -} - -func TestAESCrypt(t *testing.T) { - c := testCryptoNS() - key := "0123456789012345" - in := "hello world" - - _, err := c.EncryptAES(key, 1, 2, 3, 4) - assert.Error(t, err) - - _, err = c.DecryptAES(key, 1, 2, 3, 4) - assert.Error(t, err) - - enc, err := c.EncryptAES(key, in) - require.NoError(t, err) - - dec, err := c.DecryptAES(key, enc) - require.NoError(t, err) - assert.Equal(t, in, dec) - - b, err := c.DecryptAESBytes(key, enc) - require.NoError(t, err) - assert.Equal(t, dec, string(b)) - - enc, err = c.EncryptAES(key, 128, in) - require.NoError(t, err) - - dec, err = c.DecryptAES(key, 128, enc) - require.NoError(t, err) - assert.Equal(t, in, dec) - - b, err = c.DecryptAESBytes(key, 128, enc) - require.NoError(t, err) - assert.Equal(t, dec, string(b)) -} diff --git a/funcs/data.go b/funcs/data.go deleted file mode 100644 index 32f5b2da..00000000 --- a/funcs/data.go +++ /dev/null @@ -1,140 +0,0 @@ -package funcs - -import ( - "context" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/data" - "github.com/hairyhenderson/gomplate/v4/internal/parsers" -) - -// DataNS - -// -// Deprecated: don't use -func DataNS() *DataFuncs { - return &DataFuncs{} -} - -// AddDataFuncs - -// -// Deprecated: use [CreateDataFuncs] instead -func AddDataFuncs(f map[string]interface{}, d *data.Data) { - for k, v := range CreateDataFuncs(context.Background(), d) { - f[k] = v - } -} - -// CreateDataFuncs - -// -//nolint:staticcheck -func CreateDataFuncs(ctx context.Context, d *data.Data) map[string]interface{} { - f := map[string]interface{}{} - f["datasource"] = d.Datasource - f["ds"] = d.Datasource - f["datasourceExists"] = d.DatasourceExists - f["datasourceReachable"] = d.DatasourceReachable - f["defineDatasource"] = d.DefineDatasource - f["include"] = d.Include - f["listDatasources"] = d.ListDatasources - - ns := &DataFuncs{ctx} - - f["data"] = func() interface{} { return ns } - - f["json"] = ns.JSON - f["jsonArray"] = ns.JSONArray - f["yaml"] = ns.YAML - f["yamlArray"] = ns.YAMLArray - f["toml"] = ns.TOML - f["csv"] = ns.CSV - f["csvByRow"] = ns.CSVByRow - f["csvByColumn"] = ns.CSVByColumn - f["cue"] = ns.CUE - f["toJSON"] = ns.ToJSON - f["toJSONPretty"] = ns.ToJSONPretty - f["toYAML"] = ns.ToYAML - f["toTOML"] = ns.ToTOML - f["toCSV"] = ns.ToCSV - f["toCUE"] = ns.ToCUE - return f -} - -// DataFuncs - -type DataFuncs struct { - ctx context.Context -} - -// JSON - -func (f *DataFuncs) JSON(in interface{}) (map[string]interface{}, error) { - return parsers.JSON(conv.ToString(in)) -} - -// JSONArray - -func (f *DataFuncs) JSONArray(in interface{}) ([]interface{}, error) { - return parsers.JSONArray(conv.ToString(in)) -} - -// YAML - -func (f *DataFuncs) YAML(in interface{}) (map[string]interface{}, error) { - return parsers.YAML(conv.ToString(in)) -} - -// YAMLArray - -func (f *DataFuncs) YAMLArray(in interface{}) ([]interface{}, error) { - return parsers.YAMLArray(conv.ToString(in)) -} - -// TOML - -func (f *DataFuncs) TOML(in interface{}) (interface{}, error) { - return parsers.TOML(conv.ToString(in)) -} - -// CSV - -func (f *DataFuncs) CSV(args ...string) ([][]string, error) { - return parsers.CSV(args...) -} - -// CSVByRow - -func (f *DataFuncs) CSVByRow(args ...string) (rows []map[string]string, err error) { - return parsers.CSVByRow(args...) -} - -// CSVByColumn - -func (f *DataFuncs) CSVByColumn(args ...string) (cols map[string][]string, err error) { - return parsers.CSVByColumn(args...) -} - -// CUE - -func (f *DataFuncs) CUE(in interface{}) (interface{}, error) { - return parsers.CUE(conv.ToString(in)) -} - -// ToCSV - -func (f *DataFuncs) ToCSV(args ...interface{}) (string, error) { - return parsers.ToCSV(args...) -} - -// ToCUE - -func (f *DataFuncs) ToCUE(in interface{}) (string, error) { - return parsers.ToCUE(in) -} - -// ToJSON - -func (f *DataFuncs) ToJSON(in interface{}) (string, error) { - return parsers.ToJSON(in) -} - -// ToJSONPretty - -func (f *DataFuncs) ToJSONPretty(indent string, in interface{}) (string, error) { - return parsers.ToJSONPretty(indent, in) -} - -// ToYAML - -func (f *DataFuncs) ToYAML(in interface{}) (string, error) { - return parsers.ToYAML(in) -} - -// ToTOML - -func (f *DataFuncs) ToTOML(in interface{}) (string, error) { - return parsers.ToTOML(in) -} diff --git a/funcs/data_test.go b/funcs/data_test.go deleted file mode 100644 index c536beb3..00000000 --- a/funcs/data_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateDataFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateDataFuncs(ctx, nil) - actual := fmap["data"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*DataFuncs).ctx) - }) - } -} diff --git a/funcs/doc.go b/funcs/doc.go deleted file mode 100644 index 69a01281..00000000 --- a/funcs/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Package funcs is an internal package that provides gomplate namespaces and -functions to be used in 'text/template' templates. - -The different namespaces can be added individually: - - f := template.FuncMap{} - for k, v := range funcs.CreateMathFuncs(ctx) { - f[k] = v - } - for k, v := range funcs.CreateNetFuncs(ctx) { - f[k] = v - } - -Even though the functions are exported, these are not intended to be called -programmatically by external consumers, but instead only to be used as template -functions. - -Deprecated: This package will be made internal in a future major version. -*/ -package funcs diff --git a/funcs/env.go b/funcs/env.go deleted file mode 100644 index 0df63b3e..00000000 --- a/funcs/env.go +++ /dev/null @@ -1,49 +0,0 @@ -package funcs - -import ( - "context" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/env" -) - -// EnvNS - the Env namespace -// -// Deprecated: don't use -func EnvNS() *EnvFuncs { - return &EnvFuncs{} -} - -// AddEnvFuncs - -// -// Deprecated: use [CreateEnvFuncs] instead -func AddEnvFuncs(f map[string]interface{}) { - for k, v := range CreateEnvFuncs(context.Background()) { - f[k] = v - } -} - -// CreateEnvFuncs - -func CreateEnvFuncs(ctx context.Context) map[string]interface{} { - ns := &EnvFuncs{ctx} - - return map[string]interface{}{ - "env": func() interface{} { return ns }, - "getenv": ns.Getenv, - } -} - -// EnvFuncs - -type EnvFuncs struct { - ctx context.Context -} - -// Getenv - -func (EnvFuncs) Getenv(key interface{}, def ...string) string { - return env.Getenv(conv.ToString(key), def...) -} - -// ExpandEnv - -func (EnvFuncs) ExpandEnv(s interface{}) string { - return env.ExpandEnv(conv.ToString(s)) -} diff --git a/funcs/env_test.go b/funcs/env_test.go deleted file mode 100644 index a6ee43ac..00000000 --- a/funcs/env_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package funcs - -import ( - "context" - "os" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateEnvFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateEnvFuncs(ctx) - actual := fmap["env"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*EnvFuncs).ctx) - }) - } -} - -func TestEnvGetenv(t *testing.T) { - t.Parallel() - - ef := &EnvFuncs{} - expected := os.Getenv("USER") - assert.Equal(t, expected, ef.Getenv("USER")) - - assert.Equal(t, "foo", ef.Getenv("bogusenvvar", "foo")) -} diff --git a/funcs/file.go b/funcs/file.go deleted file mode 100644 index 32af47cf..00000000 --- a/funcs/file.go +++ /dev/null @@ -1,128 +0,0 @@ -package funcs - -import ( - "context" - "io/fs" - "path/filepath" - - osfs "github.com/hack-pad/hackpadfs/os" - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/internal/datafs" - "github.com/hairyhenderson/gomplate/v4/internal/iohelpers" -) - -// FileNS - the File namespace -// -// Deprecated: don't use -func FileNS() *FileFuncs { - return &FileFuncs{} -} - -// AddFileFuncs - -// -// Deprecated: use [CreateFileFuncs] instead -func AddFileFuncs(f map[string]interface{}) { - for k, v := range CreateFileFuncs(context.Background()) { - f[k] = v - } -} - -// CreateFileFuncs - -func CreateFileFuncs(ctx context.Context) map[string]interface{} { - fsys, err := datafs.FSysForPath(ctx, "/") - if err != nil { - fsys = datafs.WrapWdFS(osfs.NewFS()) - } - - ns := &FileFuncs{ - ctx: ctx, - fs: fsys, - } - - return map[string]interface{}{ - "file": func() interface{} { return ns }, - } -} - -// FileFuncs - -type FileFuncs struct { - ctx context.Context - fs fs.FS -} - -// Read - -func (f *FileFuncs) Read(path interface{}) (string, error) { - b, err := fs.ReadFile(f.fs, conv.ToString(path)) - return string(b), err -} - -// Stat - -func (f *FileFuncs) Stat(path interface{}) (fs.FileInfo, error) { - return fs.Stat(f.fs, conv.ToString(path)) -} - -// Exists - -func (f *FileFuncs) Exists(path interface{}) bool { - _, err := f.Stat(conv.ToString(path)) - return err == nil -} - -// IsDir - -func (f *FileFuncs) IsDir(path interface{}) bool { - i, err := f.Stat(conv.ToString(path)) - return err == nil && i.IsDir() -} - -// ReadDir - -func (f *FileFuncs) ReadDir(path interface{}) ([]string, error) { - des, err := fs.ReadDir(f.fs, conv.ToString(path)) - if err != nil { - return nil, err - } - - names := make([]string, len(des)) - for i, de := range des { - names[i] = de.Name() - } - - return names, nil -} - -// Walk - -func (f *FileFuncs) Walk(path interface{}) ([]string, error) { - files := make([]string, 0) - err := fs.WalkDir(f.fs, conv.ToString(path), func(subpath string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - // fs.WalkDir always uses slash-separated paths, even on Windows. We - // need to convert them to the OS-specific separator as that was the - // previous behavior. - subpath = filepath.FromSlash(subpath) - - files = append(files, subpath) - return nil - }) - return files, err -} - -// Write - -func (f *FileFuncs) Write(path interface{}, data interface{}) (s string, err error) { - type byteser interface{ Bytes() []byte } - - var content []byte - fname := conv.ToString(path) - - if b, ok := data.([]byte); ok { - content = b - } else if b, ok := data.(byteser); ok { - content = b.Bytes() - } else { - content = []byte(conv.ToString(data)) - } - - err = iohelpers.WriteFile(f.fs, fname, content) - - return "", err -} diff --git a/funcs/file_test.go b/funcs/file_test.go deleted file mode 100644 index d9fe5ccc..00000000 --- a/funcs/file_test.go +++ /dev/null @@ -1,177 +0,0 @@ -package funcs - -import ( - "bytes" - "context" - "io/fs" - "os" - "path/filepath" - "strconv" - "testing" - "testing/fstest" - - "github.com/hack-pad/hackpadfs" - osfs "github.com/hack-pad/hackpadfs/os" - "github.com/hairyhenderson/gomplate/v4/internal/datafs" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - tfs "gotest.tools/v3/fs" -) - -func TestCreateFileFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateFileFuncs(ctx) - actual := fmap["file"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*FileFuncs).ctx) - }) - } -} - -func TestFileExists(t *testing.T) { - t.Parallel() - - fsys := fstest.MapFS{ - "tmp": &fstest.MapFile{Mode: fs.ModeDir | 0o777}, - "tmp/foo": &fstest.MapFile{Data: []byte("foo")}, - } - ff := &FileFuncs{fs: datafs.WrapWdFS(fsys)} - - assert.True(t, ff.Exists("/tmp/foo")) - assert.False(t, ff.Exists("/tmp/bar")) -} - -func TestFileIsDir(t *testing.T) { - t.Parallel() - - fsys := fstest.MapFS{ - "tmp": &fstest.MapFile{Mode: fs.ModeDir | 0o777}, - "tmp/foo": &fstest.MapFile{Data: []byte("foo")}, - } - - ff := &FileFuncs{fs: datafs.WrapWdFS(fsys)} - - assert.True(t, ff.IsDir("/tmp")) - assert.False(t, ff.IsDir("/tmp/foo")) -} - -func TestFileWalk(t *testing.T) { - t.Parallel() - - fsys := fstest.MapFS{ - "tmp": &fstest.MapFile{Mode: fs.ModeDir | 0o777}, - "tmp/bar": &fstest.MapFile{Mode: fs.ModeDir | 0o777}, - "tmp/bar/baz": &fstest.MapFile{Mode: fs.ModeDir | 0o777}, - "tmp/bar/baz/foo": &fstest.MapFile{Data: []byte("foo")}, - } - - ff := &FileFuncs{fs: datafs.WrapWdFS(fsys)} - - expectedLists := [][]string{{"tmp"}, {"tmp", "bar"}, {"tmp", "bar", "baz"}, {"tmp", "bar", "baz", "foo"}} - expectedPaths := make([]string, 0) - for _, path := range expectedLists { - expectedPaths = append(expectedPaths, string(filepath.Separator)+filepath.Join(path...)) - } - - actualPaths, err := ff.Walk(string(filepath.Separator) + "tmp") - - require.NoError(t, err) - assert.Equal(t, expectedPaths, actualPaths) -} - -func TestReadDir(t *testing.T) { - fsys := fs.FS(fstest.MapFS{ - "tmp": &fstest.MapFile{Mode: fs.ModeDir | 0o777}, - "tmp/foo": &fstest.MapFile{Data: []byte("foo")}, - "tmp/bar": &fstest.MapFile{Data: []byte("bar")}, - "tmp/baz": &fstest.MapFile{Data: []byte("baz")}, - "tmp/qux": &fstest.MapFile{Mode: fs.ModeDir | 0o777}, - "tmp/qux/quux": &fstest.MapFile{Data: []byte("quux")}, - }) - - fsys = datafs.WrapWdFS(fsys) - - ff := &FileFuncs{ - ctx: context.Background(), - fs: fsys, - } - - actual, err := ff.ReadDir("/tmp") - require.NoError(t, err) - assert.Equal(t, []string{"bar", "baz", "foo", "qux"}, actual) - - _, err = ff.ReadDir("/tmp/foo") - assert.Error(t, err) -} - -func TestWrite(t *testing.T) { - oldwd, _ := os.Getwd() - defer os.Chdir(oldwd) - - rootDir := tfs.NewDir(t, "gomplate-test") - t.Cleanup(rootDir.Remove) - - // we want to use a real filesystem here, so we can test interactions with - // the current working directory - fsys := datafs.WrapWdFS(osfs.NewFS()) - - f := &FileFuncs{ - ctx: context.Background(), - fs: fsys, - } - - newwd := rootDir.Join("the", "path", "we", "want") - badwd := rootDir.Join("some", "other", "dir") - hackpadfs.MkdirAll(fsys, newwd, 0o755) - hackpadfs.MkdirAll(fsys, badwd, 0o755) - newwd, _ = filepath.EvalSymlinks(newwd) - badwd, _ = filepath.EvalSymlinks(badwd) - - err := os.Chdir(newwd) - require.NoError(t, err) - - _, err = f.Write("/foo", []byte("Hello world")) - assert.Error(t, err) - - rel, err := filepath.Rel(newwd, badwd) - require.NoError(t, err) - _, err = f.Write(rel, []byte("Hello world")) - assert.Error(t, err) - - foopath := filepath.Join(newwd, "foo") - _, err = f.Write(foopath, []byte("Hello world")) - require.NoError(t, err) - - out, err := fs.ReadFile(fsys, foopath) - require.NoError(t, err) - assert.Equal(t, "Hello world", string(out)) - - _, err = f.Write(foopath, []byte("truncate")) - require.NoError(t, err) - - out, err = fs.ReadFile(fsys, foopath) - require.NoError(t, err) - assert.Equal(t, "truncate", string(out)) - - foopath = filepath.Join(newwd, "nonexistant", "subdir", "foo") - _, err = f.Write(foopath, "Hello subdirranean world!") - require.NoError(t, err) - - out, err = fs.ReadFile(fsys, foopath) - require.NoError(t, err) - assert.Equal(t, "Hello subdirranean world!", string(out)) - - _, err = f.Write(foopath, bytes.NewBufferString("Hello from a byte buffer!")) - require.NoError(t, err) - - out, err = fs.ReadFile(fsys, foopath) - require.NoError(t, err) - assert.Equal(t, "Hello from a byte buffer!", string(out)) -} diff --git a/funcs/filepath.go b/funcs/filepath.go deleted file mode 100644 index 726a1d7c..00000000 --- a/funcs/filepath.go +++ /dev/null @@ -1,100 +0,0 @@ -package funcs - -import ( - "context" - "path/filepath" - - "github.com/hairyhenderson/gomplate/v4/conv" -) - -// FilePathNS - the Path namespace -// -// Deprecated: don't use -func FilePathNS() *FilePathFuncs { - return &FilePathFuncs{} -} - -// AddFilePathFuncs - -// -// Deprecated: use [CreateFilePathFuncs] instead -func AddFilePathFuncs(f map[string]interface{}) { - for k, v := range CreateFilePathFuncs(context.Background()) { - f[k] = v - } -} - -// CreateFilePathFuncs - -func CreateFilePathFuncs(ctx context.Context) map[string]interface{} { - ns := &FilePathFuncs{ctx} - - return map[string]interface{}{ - "filepath": func() interface{} { return ns }, - } -} - -// FilePathFuncs - -type FilePathFuncs struct { - ctx context.Context -} - -// Base - -func (f *FilePathFuncs) Base(in interface{}) string { - return filepath.Base(conv.ToString(in)) -} - -// Clean - -func (f *FilePathFuncs) Clean(in interface{}) string { - return filepath.Clean(conv.ToString(in)) -} - -// Dir - -func (f *FilePathFuncs) Dir(in interface{}) string { - return filepath.Dir(conv.ToString(in)) -} - -// Ext - -func (f *FilePathFuncs) Ext(in interface{}) string { - return filepath.Ext(conv.ToString(in)) -} - -// FromSlash - -func (f *FilePathFuncs) FromSlash(in interface{}) string { - return filepath.FromSlash(conv.ToString(in)) -} - -// IsAbs - -func (f *FilePathFuncs) IsAbs(in interface{}) bool { - return filepath.IsAbs(conv.ToString(in)) -} - -// Join - -func (f *FilePathFuncs) Join(elem ...interface{}) string { - s := conv.ToStrings(elem...) - return filepath.Join(s...) -} - -// Match - -func (f *FilePathFuncs) Match(pattern, name interface{}) (matched bool, err error) { - return filepath.Match(conv.ToString(pattern), conv.ToString(name)) -} - -// Rel - -func (f *FilePathFuncs) Rel(basepath, targpath interface{}) (string, error) { - return filepath.Rel(conv.ToString(basepath), conv.ToString(targpath)) -} - -// Split - -func (f *FilePathFuncs) Split(in interface{}) []string { - dir, file := filepath.Split(conv.ToString(in)) - return []string{dir, file} -} - -// ToSlash - -func (f *FilePathFuncs) ToSlash(in interface{}) string { - return filepath.ToSlash(conv.ToString(in)) -} - -// VolumeName - -func (f *FilePathFuncs) VolumeName(in interface{}) string { - return filepath.VolumeName(conv.ToString(in)) -} diff --git a/funcs/filepath_test.go b/funcs/filepath_test.go deleted file mode 100644 index 0c4bbe80..00000000 --- a/funcs/filepath_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateFilePathFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateFilePathFuncs(ctx) - actual := fmap["filepath"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*FilePathFuncs).ctx) - }) - } -} diff --git a/funcs/filepath_unix_test.go b/funcs/filepath_unix_test.go deleted file mode 100644 index 381e7432..00000000 --- a/funcs/filepath_unix_test.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !windows -// +build !windows - -package funcs - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFilePathFuncs(t *testing.T) { - t.Parallel() - - f := &FilePathFuncs{} - assert.Equal(t, "bar", f.Base("foo/bar")) - assert.Equal(t, "bar", f.Base("/foo/bar")) - - assert.Equal(t, "/foo/baz", f.Clean("/foo/bar/../baz")) - assert.Equal(t, "foo", f.Dir("foo/bar")) - assert.Equal(t, ".txt", f.Ext("/foo/bar/baz.txt")) - assert.False(t, f.IsAbs("foo/bar")) - assert.True(t, f.IsAbs("/foo/bar")) - assert.Equal(t, "foo/bar/qux", f.Join("foo", "bar", "baz", "..", "qux")) - m, _ := f.Match("*.txt", "foo.json") - assert.False(t, m) - m, _ = f.Match("*.txt", "foo.txt") - assert.True(t, m) - r, _ := f.Rel("/foo/bar", "/foo/bar/baz") - assert.Equal(t, "baz", r) - assert.Equal(t, []string{"/foo/bar/", "baz"}, f.Split("/foo/bar/baz")) - assert.Equal(t, "", f.VolumeName("/foo/bar")) -} diff --git a/funcs/filepath_windows_test.go b/funcs/filepath_windows_test.go deleted file mode 100644 index 190dfedb..00000000 --- a/funcs/filepath_windows_test.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build windows -// +build windows - -package funcs - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFilePathFuncs(t *testing.T) { - t.Parallel() - - f := &FilePathFuncs{} - assert.Equal(t, "bar", f.Base(`foo\bar`)) - assert.Equal(t, "bar", f.Base("C:/foo/bar")) - assert.Equal(t, "bar", f.Base(`C:\foo\bar`)) - - assert.Equal(t, `C:\foo\baz`, f.Clean(`C:\foo\bar\..\baz`)) - assert.Equal(t, "foo", f.Dir(`foo\bar`)) - assert.Equal(t, ".txt", f.Ext(`C:\foo\bar\baz.txt`)) - assert.False(t, f.IsAbs(`foo\bar`)) - assert.True(t, f.IsAbs(`C:\foo\bar`)) - assert.False(t, f.IsAbs(`\foo\bar`)) - assert.Equal(t, `foo\bar\qux`, f.Join("foo", "bar", "baz", "..", "qux")) - m, _ := f.Match("*.txt", "foo.json") - assert.False(t, m) - m, _ = f.Match("*.txt", "foo.txt") - assert.True(t, m) - r, _ := f.Rel(`C:\foo\bar`, `C:\foo\bar\baz`) - assert.Equal(t, "baz", r) - assert.Equal(t, []string{`C:\foo\bar\`, "baz"}, f.Split(`C:\foo\bar\baz`)) - assert.Equal(t, "D:", f.VolumeName(`D:\foo\bar`)) -} diff --git a/funcs/funcs.go b/funcs/funcs.go deleted file mode 100644 index f5549e61..00000000 --- a/funcs/funcs.go +++ /dev/null @@ -1,15 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - - "github.com/hairyhenderson/gomplate/v4/internal/config" -) - -func checkExperimental(ctx context.Context) error { - if !config.ExperimentalEnabled(ctx) { - return fmt.Errorf("experimental function, but experimental mode not enabled") - } - return nil -} diff --git a/funcs/gcp.go b/funcs/gcp.go deleted file mode 100644 index 040a451b..00000000 --- a/funcs/gcp.go +++ /dev/null @@ -1,52 +0,0 @@ -package funcs - -import ( - "context" - "sync" - - "github.com/hairyhenderson/gomplate/v4/gcp" -) - -// GCPNS - the gcp namespace -// -// Deprecated: don't use -func GCPNS() *GcpFuncs { - return &GcpFuncs{gcpopts: gcp.GetClientOptions()} -} - -// AddGCPFuncs - -// -// Deprecated: use [CreateGCPFuncs] instead -func AddGCPFuncs(f map[string]interface{}) { - for k, v := range CreateGCPFuncs(context.Background()) { - f[k] = v - } -} - -// CreateGCPFuncs - -func CreateGCPFuncs(ctx context.Context) map[string]interface{} { - ns := &GcpFuncs{ - ctx: ctx, - gcpopts: gcp.GetClientOptions(), - } - return map[string]interface{}{ - "gcp": func() interface{} { return ns }, - } -} - -// GcpFuncs - -type GcpFuncs struct { - ctx context.Context - - meta *gcp.MetaClient - gcpopts gcp.ClientOptions -} - -// Meta - -func (a *GcpFuncs) Meta(key string, def ...string) (string, error) { - a.meta = sync.OnceValue[*gcp.MetaClient](func() *gcp.MetaClient { - return gcp.NewMetaClient(a.ctx, a.gcpopts) - })() - - return a.meta.Meta(key, def...) -} diff --git a/funcs/gcp_test.go b/funcs/gcp_test.go deleted file mode 100644 index 91ab54fb..00000000 --- a/funcs/gcp_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateGCPFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateGCPFuncs(ctx) - actual := fmap["gcp"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*GcpFuncs).ctx) - }) - } -} diff --git a/funcs/math.go b/funcs/math.go deleted file mode 100644 index 68316101..00000000 --- a/funcs/math.go +++ /dev/null @@ -1,247 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - gmath "math" - "strconv" - - "github.com/hairyhenderson/gomplate/v4/conv" - - "github.com/hairyhenderson/gomplate/v4/math" -) - -// MathNS - the math namespace -// -// Deprecated: don't use -func MathNS() *MathFuncs { - return &MathFuncs{} -} - -// AddMathFuncs - -// -// Deprecated: use [CreateMathFuncs] instead -func AddMathFuncs(f map[string]interface{}) { - for k, v := range CreateMathFuncs(context.Background()) { - f[k] = v - } -} - -// CreateMathFuncs - -func CreateMathFuncs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - - ns := &MathFuncs{ctx} - f["math"] = func() interface{} { return ns } - - f["add"] = ns.Add - f["sub"] = ns.Sub - f["mul"] = ns.Mul - f["div"] = ns.Div - f["rem"] = ns.Rem - f["pow"] = ns.Pow - f["seq"] = ns.Seq - return f -} - -// MathFuncs - -type MathFuncs struct { - ctx context.Context -} - -// IsInt - -func (f MathFuncs) IsInt(n interface{}) bool { - switch i := n.(type) { - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - return true - case string: - _, err := strconv.ParseInt(i, 0, 64) - return err == nil - } - return false -} - -// IsFloat - -func (f MathFuncs) IsFloat(n interface{}) bool { - switch i := n.(type) { - case float32, float64: - return true - case string: - _, err := strconv.ParseFloat(i, 64) - if err != nil { - return false - } - if f.IsInt(i) { - return false - } - return true - } - return false -} - -func (f MathFuncs) containsFloat(n ...interface{}) bool { - c := false - for _, v := range n { - if f.IsFloat(v) { - return true - } - } - return c -} - -// IsNum - -func (f MathFuncs) IsNum(n interface{}) bool { - return f.IsInt(n) || f.IsFloat(n) -} - -// Abs - -func (f MathFuncs) Abs(n interface{}) interface{} { - m := gmath.Abs(conv.ToFloat64(n)) - if f.IsInt(n) { - return conv.ToInt64(m) - } - return m -} - -// Add - -func (f MathFuncs) Add(n ...interface{}) interface{} { - if f.containsFloat(n...) { - nums := conv.ToFloat64s(n...) - var x float64 - for _, v := range nums { - x += v - } - return x - } - nums := conv.ToInt64s(n...) - var x int64 - for _, v := range nums { - x += v - } - return x -} - -// Mul - -func (f MathFuncs) Mul(n ...interface{}) interface{} { - if f.containsFloat(n...) { - nums := conv.ToFloat64s(n...) - x := 1. - for _, v := range nums { - x *= v - } - return x - } - nums := conv.ToInt64s(n...) - x := int64(1) - for _, v := range nums { - x *= v - } - return x -} - -// Sub - -func (f MathFuncs) Sub(a, b interface{}) interface{} { - if f.containsFloat(a, b) { - return conv.ToFloat64(a) - conv.ToFloat64(b) - } - return conv.ToInt64(a) - conv.ToInt64(b) -} - -// Div - -func (f MathFuncs) Div(a, b interface{}) (interface{}, error) { - divisor := conv.ToFloat64(a) - dividend := conv.ToFloat64(b) - if dividend == 0 { - return 0, fmt.Errorf("error: division by 0") - } - return divisor / dividend, nil -} - -// Rem - -func (f MathFuncs) Rem(a, b interface{}) interface{} { - return conv.ToInt64(a) % conv.ToInt64(b) -} - -// Pow - -func (f MathFuncs) Pow(a, b interface{}) interface{} { - r := gmath.Pow(conv.ToFloat64(a), conv.ToFloat64(b)) - if f.IsFloat(a) { - return r - } - return conv.ToInt64(r) -} - -// Seq - return a sequence from `start` to `end`, in steps of `step` -// start and step are optional, and default to 1. -func (f MathFuncs) Seq(n ...interface{}) ([]int64, error) { - start := int64(1) - end := int64(0) - step := int64(1) - if len(n) == 0 { - return nil, fmt.Errorf("math.Seq must be given at least an 'end' value") - } - if len(n) == 1 { - end = conv.ToInt64(n[0]) - } - if len(n) == 2 { - start = conv.ToInt64(n[0]) - end = conv.ToInt64(n[1]) - } - if len(n) == 3 { - start = conv.ToInt64(n[0]) - end = conv.ToInt64(n[1]) - step = conv.ToInt64(n[2]) - } - return math.Seq(conv.ToInt64(start), conv.ToInt64(end), conv.ToInt64(step)), nil -} - -// Max - -func (f MathFuncs) Max(a interface{}, b ...interface{}) (interface{}, error) { - if f.IsFloat(a) || f.containsFloat(b...) { - m := conv.ToFloat64(a) - for _, n := range conv.ToFloat64s(b...) { - m = gmath.Max(m, n) - } - return m, nil - } - m := conv.ToInt64(a) - for _, n := range conv.ToInt64s(b...) { - if n > m { - m = n - } - } - return m, nil -} - -// Min - -func (f MathFuncs) Min(a interface{}, b ...interface{}) (interface{}, error) { - if f.IsFloat(a) || f.containsFloat(b...) { - m := conv.ToFloat64(a) - for _, n := range conv.ToFloat64s(b...) { - m = gmath.Min(m, n) - } - return m, nil - } - m := conv.ToInt64(a) - for _, n := range conv.ToInt64s(b...) { - if n < m { - m = n - } - } - return m, nil -} - -// Ceil - -func (f MathFuncs) Ceil(n interface{}) interface{} { - return gmath.Ceil(conv.ToFloat64(n)) -} - -// Floor - -func (f MathFuncs) Floor(n interface{}) interface{} { - return gmath.Floor(conv.ToFloat64(n)) -} - -// Round - -func (f MathFuncs) Round(n interface{}) interface{} { - return gmath.Round(conv.ToFloat64(n)) -} diff --git a/funcs/math_test.go b/funcs/math_test.go deleted file mode 100644 index 45681e21..00000000 --- a/funcs/math_test.go +++ /dev/null @@ -1,400 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - gmath "math" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateMathFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateMathFuncs(ctx) - actual := fmap["math"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*MathFuncs).ctx) - }) - } -} - -func TestAdd(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - assert.Equal(t, int64(12), m.Add(1, 1, 2, 3, 5)) - assert.Equal(t, int64(2), m.Add(1, 1)) - assert.Equal(t, int64(1), m.Add(1)) - assert.Equal(t, int64(0), m.Add(-5, 5)) - assert.InDelta(t, float64(5.1), m.Add(4.9, "0.2"), 0.000000001) -} - -func TestMul(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - assert.Equal(t, int64(30), m.Mul(1, 1, 2, 3, 5)) - assert.Equal(t, int64(1), m.Mul(1, 1)) - assert.Equal(t, int64(1), m.Mul(1)) - assert.Equal(t, int64(-25), m.Mul("-5", 5)) - assert.Equal(t, int64(28), m.Mul(14, "2")) - assert.Equal(t, float64(0.5), m.Mul("-1", -0.5)) -} - -func TestSub(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - assert.Equal(t, int64(0), m.Sub(1, 1)) - assert.Equal(t, int64(-10), m.Sub(-5, 5)) - assert.Equal(t, int64(-41), m.Sub(true, "42")) - assert.InDelta(t, -5.3, m.Sub(10, 15.3), 0.000000000000001) -} - -func mustDiv(a, b interface{}) interface{} { - m := MathFuncs{} - r, err := m.Div(a, b) - if err != nil { - return -1 - } - return r -} - -func TestDiv(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - _, err := m.Div(1, 0) - assert.Error(t, err) - assert.Equal(t, 1., mustDiv(1, 1)) - assert.Equal(t, -1., mustDiv(-5, 5)) - assert.Equal(t, 1./42, mustDiv(true, "42")) - assert.InDelta(t, 0.5, mustDiv(1, 2), 1e-12) -} - -func TestRem(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - assert.Equal(t, int64(0), m.Rem(1, 1)) - assert.Equal(t, int64(2), m.Rem(5, 3.0)) -} - -func TestPow(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - assert.Equal(t, int64(4), m.Pow(2, "2")) - assert.Equal(t, 2.25, m.Pow(1.5, 2)) -} - -func mustSeq(t *testing.T, n ...interface{}) []int64 { - m := MathFuncs{} - s, err := m.Seq(n...) - if err != nil { - t.Fatal(err) - } - return s -} - -func TestSeq(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - assert.EqualValues(t, []int64{0, 1, 2, 3}, mustSeq(t, 0, 3)) - assert.EqualValues(t, []int64{1, 0}, mustSeq(t, 0)) - assert.EqualValues(t, []int64{0, 2, 4}, mustSeq(t, 0, 4, 2)) - assert.EqualValues(t, []int64{0, 2, 4}, mustSeq(t, 0, 5, 2)) - assert.EqualValues(t, []int64{0}, mustSeq(t, 0, 5, 8)) - _, err := m.Seq() - assert.Error(t, err) -} - -func TestIsIntFloatNum(t *testing.T) { - t.Parallel() - - tests := []struct { - in interface{} - isInt bool - isFloat bool - }{ - {0, true, false}, - {1, true, false}, - {-1, true, false}, - {uint(42), true, false}, - {uint8(255), true, false}, - {uint16(42), true, false}, - {uint32(42), true, false}, - {uint64(42), true, false}, - {int(42), true, false}, - {int8(127), true, false}, - {int16(42), true, false}, - {int32(42), true, false}, - {int64(42), true, false}, - {float32(18.3), false, true}, - {float64(18.3), false, true}, - {1.5, false, true}, - {-18.6, false, true}, - {"42", true, false}, - {"052", true, false}, - {"0xff", true, false}, - {"-42", true, false}, - {"-0", true, false}, - {"3.14", false, true}, - {"-3.14", false, true}, - {"0.00", false, true}, - {"NaN", false, true}, - {"-Inf", false, true}, - {"+Inf", false, true}, - {"", false, false}, - {"foo", false, false}, - {nil, false, false}, - {true, false, false}, - } - m := MathFuncs{} - for _, tt := range tests { - tt := tt - t.Run(fmt.Sprintf("%T(%#v)", tt.in, tt.in), func(t *testing.T) { - t.Parallel() - - assert.Equal(t, tt.isInt, m.IsInt(tt.in)) - assert.Equal(t, tt.isFloat, m.IsFloat(tt.in)) - assert.Equal(t, tt.isInt || tt.isFloat, m.IsNum(tt.in)) - }) - } -} - -func BenchmarkIsFloat(b *testing.B) { - data := []interface{}{ - 0, 1, -1, uint(42), uint8(255), uint16(42), uint32(42), uint64(42), int(42), int8(127), int16(42), int32(42), int64(42), float32(18.3), float64(18.3), 1.5, -18.6, "42", "052", "0xff", "-42", "-0", "3.14", "-3.14", "0.00", "NaN", "-Inf", "+Inf", "", "foo", nil, true, - } - m := MathFuncs{} - for _, n := range data { - n := n - b.Run(fmt.Sprintf("%T(%v)", n, n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - m.IsFloat(n) - } - }) - } -} - -func TestMax(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - data := []struct { - expected interface{} - n []interface{} - }{ - {int64(0), []interface{}{nil}}, - {int64(0), []interface{}{0}}, - {int64(0), []interface{}{"not a number"}}, - {int64(1), []interface{}{1}}, - {int64(-1), []interface{}{-1}}, - {int64(1), []interface{}{-1, 0, 1}}, - {3.9, []interface{}{3.14, 3, 3.9}}, - {int64(255), []interface{}{"14", "0xff", -5}}, - } - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%v==%v", d.n, d.expected), func(t *testing.T) { - t.Parallel() - - var actual interface{} - if len(d.n) == 1 { - actual, _ = m.Max(d.n[0]) - } else { - actual, _ = m.Max(d.n[0], d.n[1:]...) - } - assert.Equal(t, d.expected, actual) - }) - } -} - -func TestMin(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - data := []struct { - expected interface{} - n []interface{} - }{ - {int64(0), []interface{}{nil}}, - {int64(0), []interface{}{0}}, - {int64(0), []interface{}{"not a number"}}, - {int64(1), []interface{}{1}}, - {int64(-1), []interface{}{-1}}, - {int64(-1), []interface{}{-1, 0, 1}}, - {3., []interface{}{3.14, 3, 3.9}}, - {int64(-5), []interface{}{"14", "0xff", -5}}, - } - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%v==%v", d.n, d.expected), func(t *testing.T) { - t.Parallel() - - var actual interface{} - if len(d.n) == 1 { - actual, _ = m.Min(d.n[0]) - } else { - actual, _ = m.Min(d.n[0], d.n[1:]...) - } - assert.Equal(t, d.expected, actual) - }) - } -} - -func TestContainsFloat(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - data := []struct { - n []interface{} - expected bool - }{ - {[]interface{}{nil}, false}, - {[]interface{}{0}, false}, - {[]interface{}{"not a number"}, false}, - {[]interface{}{1}, false}, - {[]interface{}{-1}, false}, - {[]interface{}{-1, 0, 1}, false}, - {[]interface{}{3.14, 3, 3.9}, true}, - {[]interface{}{"14", "0xff", -5}, false}, - {[]interface{}{"14.8", "0xff", -5}, true}, - {[]interface{}{"-Inf", 2}, true}, - {[]interface{}{"NaN"}, true}, - } - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%v==%v", d.n, d.expected), func(t *testing.T) { - t.Parallel() - - if d.expected { - assert.True(t, m.containsFloat(d.n...)) - } else { - assert.False(t, m.containsFloat(d.n...)) - } - }) - } -} - -func TestCeil(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - data := []struct { - n interface{} - a float64 - }{ - {"", 0.}, - {nil, 0.}, - {"Inf", gmath.Inf(1)}, - {0, 0.}, - {4.99, 5.}, - {42.1, 43}, - {-1.9, -1}, - } - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%v==%v", d.n, d.a), func(t *testing.T) { - t.Parallel() - - assert.InDelta(t, d.a, m.Ceil(d.n), 1e-12) - }) - } -} - -func TestFloor(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - data := []struct { - n interface{} - a float64 - }{ - {"", 0.}, - {nil, 0.}, - {"Inf", gmath.Inf(1)}, - {0, 0.}, - {4.99, 4.}, - {42.1, 42}, - {-1.9, -2.}, - } - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%v==%v", d.n, d.a), func(t *testing.T) { - t.Parallel() - - assert.InDelta(t, d.a, m.Floor(d.n), 1e-12) - }) - } -} - -func TestRound(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - data := []struct { - n interface{} - a float64 - }{ - {"", 0.}, - {nil, 0.}, - {"Inf", gmath.Inf(1)}, - {0, 0.}, - {4.99, 5}, - {42.1, 42}, - {-1.9, -2.}, - {3.5, 4}, - {-3.5, -4}, - {4.5, 5}, - {-4.5, -5}, - } - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%v==%v", d.n, d.a), func(t *testing.T) { - t.Parallel() - - assert.InDelta(t, d.a, m.Round(d.n), 1e-12) - }) - } -} - -func TestAbs(t *testing.T) { - t.Parallel() - - m := MathFuncs{} - data := []struct { - n interface{} - a interface{} - }{ - {"", 0.}, - {nil, 0.}, - {"-Inf", gmath.Inf(1)}, - {0, int64(0)}, - {0., 0.}, - {gmath.Copysign(0, -1), 0.}, - {3.14, 3.14}, - {-1.9, 1.9}, - {2, int64(2)}, - {-2, int64(2)}, - } - for _, d := range data { - d := d - t.Run(fmt.Sprintf("%#v==%v", d.n, d.a), func(t *testing.T) { - t.Parallel() - - assert.Equal(t, d.a, m.Abs(d.n)) - }) - } -} diff --git a/funcs/net.go b/funcs/net.go deleted file mode 100644 index 3342bd27..00000000 --- a/funcs/net.go +++ /dev/null @@ -1,300 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - "math/big" - stdnet "net" - "net/netip" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/internal/cidr" - "github.com/hairyhenderson/gomplate/v4/internal/deprecated" - "github.com/hairyhenderson/gomplate/v4/net" - "go4.org/netipx" - "inet.af/netaddr" -) - -// NetNS - the net namespace -// -// Deprecated: don't use -func NetNS() *NetFuncs { - return &NetFuncs{} -} - -// AddNetFuncs - -// -// Deprecated: use [CreateNetFuncs] instead -func AddNetFuncs(f map[string]interface{}) { - for k, v := range CreateNetFuncs(context.Background()) { - f[k] = v - } -} - -// CreateNetFuncs - -func CreateNetFuncs(ctx context.Context) map[string]interface{} { - ns := &NetFuncs{ctx} - return map[string]interface{}{ - "net": func() interface{} { return ns }, - } -} - -// NetFuncs - -type NetFuncs struct { - ctx context.Context -} - -// LookupIP - -func (f NetFuncs) LookupIP(name interface{}) (string, error) { - return net.LookupIP(conv.ToString(name)) -} - -// LookupIPs - -func (f NetFuncs) LookupIPs(name interface{}) ([]string, error) { - return net.LookupIPs(conv.ToString(name)) -} - -// LookupCNAME - -func (f NetFuncs) LookupCNAME(name interface{}) (string, error) { - return net.LookupCNAME(conv.ToString(name)) -} - -// LookupSRV - -func (f NetFuncs) LookupSRV(name interface{}) (*stdnet.SRV, error) { - return net.LookupSRV(conv.ToString(name)) -} - -// LookupSRVs - -func (f NetFuncs) LookupSRVs(name interface{}) ([]*stdnet.SRV, error) { - return net.LookupSRVs(conv.ToString(name)) -} - -// LookupTXT - -func (f NetFuncs) LookupTXT(name interface{}) ([]string, error) { - return net.LookupTXT(conv.ToString(name)) -} - -// ParseIP - -// -// Deprecated: use [ParseAddr] instead -func (f *NetFuncs) ParseIP(ip interface{}) (netaddr.IP, error) { - deprecated.WarnDeprecated(f.ctx, "net.ParseIP is deprecated - use net.ParseAddr instead") - return netaddr.ParseIP(conv.ToString(ip)) -} - -// ParseIPPrefix - -// -// Deprecated: use [ParsePrefix] instead -func (f *NetFuncs) ParseIPPrefix(ipprefix interface{}) (netaddr.IPPrefix, error) { - deprecated.WarnDeprecated(f.ctx, "net.ParseIPPrefix is deprecated - use net.ParsePrefix instead") - return netaddr.ParseIPPrefix(conv.ToString(ipprefix)) -} - -// ParseIPRange - -// -// Deprecated: use [ParseRange] instead -func (f *NetFuncs) ParseIPRange(iprange interface{}) (netaddr.IPRange, error) { - deprecated.WarnDeprecated(f.ctx, "net.ParseIPRange is deprecated - use net.ParseRange instead") - return netaddr.ParseIPRange(conv.ToString(iprange)) -} - -// ParseAddr - -func (f NetFuncs) ParseAddr(ip interface{}) (netip.Addr, error) { - return netip.ParseAddr(conv.ToString(ip)) -} - -// ParsePrefix - -func (f NetFuncs) ParsePrefix(ipprefix interface{}) (netip.Prefix, error) { - return netip.ParsePrefix(conv.ToString(ipprefix)) -} - -// ParseRange - -// -// Experimental: this API may change in the future -func (f NetFuncs) ParseRange(iprange interface{}) (netipx.IPRange, error) { - return netipx.ParseIPRange(conv.ToString(iprange)) -} - -// func (f *NetFuncs) parseStdnetIPNet(prefix interface{}) (*stdnet.IPNet, error) { -// switch p := prefix.(type) { -// case *stdnet.IPNet: -// return p, nil -// case netaddr.IPPrefix: -// deprecated.WarnDeprecated(f.ctx, -// "support for netaddr.IPPrefix is deprecated - use net.ParsePrefix to produce a netip.Prefix instead") -// return p.Masked().IPNet(), nil -// case netip.Prefix: -// net := &stdnet.IPNet{ -// IP: p.Masked().Addr().AsSlice(), -// Mask: stdnet.CIDRMask(p.Bits(), p.Addr().BitLen()), -// } -// return net, nil -// default: -// _, network, err := stdnet.ParseCIDR(conv.ToString(prefix)) -// return network, err -// } -// } - -// TODO: look at using this instead of parseStdnetIPNet -func (f *NetFuncs) parseNetipPrefix(prefix interface{}) (netip.Prefix, error) { - switch p := prefix.(type) { - case *stdnet.IPNet: - return f.ipPrefixFromIPNet(p), nil - case netaddr.IPPrefix: - deprecated.WarnDeprecated(f.ctx, - "support for netaddr.IPPrefix is deprecated - use net.ParsePrefix to produce a netip.Prefix instead") - return f.ipPrefixFromIPNet(p.Masked().IPNet()), nil - case netip.Prefix: - return p, nil - default: - return netip.ParsePrefix(conv.ToString(prefix)) - } -} - -// func (f NetFuncs) ipFromNetIP(n stdnet.IP) netip.Addr { -// ip, _ := netip.AddrFromSlice(n) -// return ip -// } - -func (f NetFuncs) ipPrefixFromIPNet(n *stdnet.IPNet) netip.Prefix { - ip, _ := netip.AddrFromSlice(n.IP) - ones, _ := n.Mask.Size() - return netip.PrefixFrom(ip, ones) -} - -// CIDRHost - -// Experimental! -func (f *NetFuncs) CIDRHost(hostnum interface{}, prefix interface{}) (netip.Addr, error) { - if err := checkExperimental(f.ctx); err != nil { - return netip.Addr{}, err - } - - network, err := f.parseNetipPrefix(prefix) - if err != nil { - return netip.Addr{}, err - } - - ip, err := cidr.HostBig(network, big.NewInt(conv.ToInt64(hostnum))) - - return ip, err -} - -// CIDRNetmask - -// Experimental! -func (f *NetFuncs) CIDRNetmask(prefix interface{}) (netip.Addr, error) { - if err := checkExperimental(f.ctx); err != nil { - return netip.Addr{}, err - } - - p, err := f.parseNetipPrefix(prefix) - if err != nil { - return netip.Addr{}, err - } - - // fill an appropriately sized byte slice with as many 1s as prefix bits - b := make([]byte, p.Addr().BitLen()/8) - for i := 0; i < p.Bits(); i++ { - b[i/8] |= 1 << uint(7-i%8) - } - - m, ok := netip.AddrFromSlice(b) - if !ok { - return netip.Addr{}, fmt.Errorf("invalid netmask") - } - - return m, nil -} - -// CIDRSubnets - -// Experimental! -func (f *NetFuncs) CIDRSubnets(newbits interface{}, prefix interface{}) ([]netip.Prefix, error) { - if err := checkExperimental(f.ctx); err != nil { - return nil, err - } - - network, err := f.parseNetipPrefix(prefix) - if err != nil { - return nil, err - } - - nBits := conv.ToInt(newbits) - if nBits < 1 { - return nil, fmt.Errorf("must extend prefix by at least one bit") - } - - maxNetNum := int64(1 << uint64(nBits)) - retValues := make([]netip.Prefix, maxNetNum) - for i := int64(0); i < maxNetNum; i++ { - subnet, err := cidr.SubnetBig(network, nBits, big.NewInt(i)) - if err != nil { - return nil, err - } - retValues[i] = subnet - } - - return retValues, nil -} - -// CIDRSubnetSizes - -// Experimental! -func (f *NetFuncs) CIDRSubnetSizes(args ...interface{}) ([]netip.Prefix, error) { - if err := checkExperimental(f.ctx); err != nil { - return nil, err - } - - if len(args) < 2 { - return nil, fmt.Errorf("wrong number of args: want 2 or more, got %d", len(args)) - } - - network, err := f.parseNetipPrefix(args[len(args)-1]) - if err != nil { - return nil, err - } - newbits := conv.ToInts(args[:len(args)-1]...) - - startPrefixLen := network.Bits() - firstLength := newbits[0] - - firstLength += startPrefixLen - retValues := make([]netip.Prefix, len(newbits)) - - current, _ := cidr.PreviousSubnet(network, firstLength) - - for i, length := range newbits { - if length < 1 { - return nil, fmt.Errorf("must extend prefix by at least one bit") - } - // For portability with 32-bit systems where the subnet number - // will be a 32-bit int, we only allow extension of 32 bits in - // one call even if we're running on a 64-bit machine. - // (Of course, this is significant only for IPv6.) - if length > 32 { - return nil, fmt.Errorf("may not extend prefix by more than 32 bits") - } - - length += startPrefixLen - if length > network.Addr().BitLen() { - protocol := "IP" - switch { - case network.Addr().Is4(): - protocol = "IPv4" - case network.Addr().Is6(): - protocol = "IPv6" - } - return nil, fmt.Errorf("would extend prefix to %d bits, which is too long for an %s address", length, protocol) - } - - next, rollover := cidr.NextSubnet(current, length) - if rollover || !network.Contains(next.Addr()) { - // If we run out of suffix bits in the base CIDR prefix then - // NextSubnet will start incrementing the prefix bits, which - // we don't allow because it would then allocate addresses - // outside of the caller's given prefix. - return nil, fmt.Errorf("not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String()) - } - current = next - retValues[i] = current - } - - return retValues, nil -} diff --git a/funcs/net_test.go b/funcs/net_test.go deleted file mode 100644 index 7c3afffe..00000000 --- a/funcs/net_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package funcs - -import ( - "context" - stdnet "net" - "net/netip" - "strconv" - "testing" - - "github.com/hairyhenderson/gomplate/v4/internal/config" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "inet.af/netaddr" -) - -func TestCreateNetFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateNetFuncs(ctx) - actual := fmap["net"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*NetFuncs).ctx) - }) - } -} - -func TestNetLookupIP(t *testing.T) { - t.Parallel() - - n := NetFuncs{} - assert.Equal(t, "127.0.0.1", must(n.LookupIP("localhost"))) -} - -func TestParseIP(t *testing.T) { - t.Parallel() - - n := testNetNS() - _, err := n.ParseIP("not an IP") - assert.Error(t, err) - - ip, err := n.ParseIP("2001:470:20::2") - require.NoError(t, err) - assert.Equal(t, netaddr.IPFrom16([16]byte{ - 0x20, 0x01, 0x04, 0x70, - 0, 0x20, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0x02, - }), ip) -} - -func TestParseIPPrefix(t *testing.T) { - t.Parallel() - - n := testNetNS() - _, err := n.ParseIPPrefix("not an IP") - assert.Error(t, err) - - _, err = n.ParseIPPrefix("1.1.1.1") - assert.Error(t, err) - - ipprefix, err := n.ParseIPPrefix("192.168.0.2/28") - require.NoError(t, err) - assert.Equal(t, "192.168.0.0/28", ipprefix.Masked().String()) -} - -func TestParseIPRange(t *testing.T) { - t.Parallel() - - n := testNetNS() - _, err := n.ParseIPRange("not an IP") - assert.Error(t, err) - - _, err = n.ParseIPRange("1.1.1.1") - assert.Error(t, err) - - iprange, err := n.ParseIPRange("192.168.0.2-192.168.23.255") - require.NoError(t, err) - assert.Equal(t, "192.168.0.2-192.168.23.255", iprange.String()) -} - -func TestParseAddr(t *testing.T) { - t.Parallel() - - n := testNetNS() - _, err := n.ParseAddr("not an IP") - assert.Error(t, err) - - ip, err := n.ParseAddr("2001:470:20::2") - require.NoError(t, err) - assert.Equal(t, netip.AddrFrom16([16]byte{ - 0x20, 0x01, 0x04, 0x70, - 0, 0x20, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0x02, - }), ip) -} - -func TestParsePrefix(t *testing.T) { - t.Parallel() - - n := testNetNS() - _, err := n.ParsePrefix("not an IP") - assert.Error(t, err) - - _, err = n.ParsePrefix("1.1.1.1") - assert.Error(t, err) - - ipprefix, err := n.ParsePrefix("192.168.0.2/28") - require.NoError(t, err) - assert.Equal(t, "192.168.0.0/28", ipprefix.Masked().String()) -} - -func TestParseRange(t *testing.T) { - t.Parallel() - - n := testNetNS() - _, err := n.ParseRange("not an IP") - assert.Error(t, err) - - _, err = n.ParseRange("1.1.1.1") - assert.Error(t, err) - - iprange, err := n.ParseRange("192.168.0.2-192.168.23.255") - require.NoError(t, err) - assert.Equal(t, "192.168.0.2-192.168.23.255", iprange.String()) -} - -func testNetNS() *NetFuncs { - return &NetFuncs{ctx: config.SetExperimental(context.Background())} -} - -func TestCIDRHost(t *testing.T) { - n := testNetNS() - - // net.IPNet - _, netIP, _ := stdnet.ParseCIDR("10.12.127.0/20") - - ip, err := n.CIDRHost(16, netIP) - require.NoError(t, err) - assert.Equal(t, "10.12.112.16", ip.String()) - - ip, err = n.CIDRHost(268, netIP) - require.NoError(t, err) - assert.Equal(t, "10.12.113.12", ip.String()) - - _, netIP, _ = stdnet.ParseCIDR("fd00:fd12:3456:7890:00a2::/72") - ip, err = n.CIDRHost(34, netIP) - require.NoError(t, err) - assert.Equal(t, "fd00:fd12:3456:7890::22", ip.String()) - - // inet.af/netaddr.IPPrefix - ipPrefix, _ := n.ParseIPPrefix("10.12.127.0/20") - - ip, err = n.CIDRHost(16, ipPrefix) - require.NoError(t, err) - assert.Equal(t, "10.12.112.16", ip.String()) - - ip, err = n.CIDRHost(268, ipPrefix) - require.NoError(t, err) - assert.Equal(t, "10.12.113.12", ip.String()) - - ipPrefix, _ = n.ParseIPPrefix("fd00:fd12:3456:7890:00a2::/72") - ip, err = n.CIDRHost(34, ipPrefix) - require.NoError(t, err) - assert.Equal(t, "fd00:fd12:3456:7890::22", ip.String()) - - // net/netip.Prefix - prefix := netip.MustParsePrefix("10.12.127.0/20") - - ip, err = n.CIDRHost(16, prefix) - require.NoError(t, err) - assert.Equal(t, "10.12.112.16", ip.String()) - - ip, err = n.CIDRHost(268, prefix) - require.NoError(t, err) - assert.Equal(t, "10.12.113.12", ip.String()) - - prefix = netip.MustParsePrefix("fd00:fd12:3456:7890:00a2::/72") - ip, err = n.CIDRHost(34, prefix) - require.NoError(t, err) - assert.Equal(t, "fd00:fd12:3456:7890::22", ip.String()) -} - -func TestCIDRNetmask(t *testing.T) { - n := testNetNS() - - ip, err := n.CIDRNetmask("10.0.0.0/12") - require.NoError(t, err) - assert.Equal(t, "255.240.0.0", ip.String()) - - ip, err = n.CIDRNetmask("fd00:fd12:3456:7890:00a2::/72") - require.NoError(t, err) - assert.Equal(t, "ffff:ffff:ffff:ffff:ff00::", ip.String()) -} - -func TestCIDRSubnets(t *testing.T) { - n := testNetNS() - network := netip.MustParsePrefix("10.0.0.0/16") - - subnets, err := n.CIDRSubnets(-1, network) - assert.Error(t, err) - assert.Nil(t, subnets) - - subnets, err = n.CIDRSubnets(2, network) - require.NoError(t, err) - assert.Len(t, subnets, 4) - assert.Equal(t, "10.0.0.0/18", subnets[0].String()) - assert.Equal(t, "10.0.64.0/18", subnets[1].String()) - assert.Equal(t, "10.0.128.0/18", subnets[2].String()) - assert.Equal(t, "10.0.192.0/18", subnets[3].String()) -} - -func TestCIDRSubnetSizes(t *testing.T) { - n := testNetNS() - - subnets, err := n.CIDRSubnetSizes(netip.MustParsePrefix("10.1.0.0/16")) - assert.Error(t, err) - assert.Nil(t, subnets) - - subnets, err = n.CIDRSubnetSizes(32, netip.MustParsePrefix("10.1.0.0/16")) - assert.Error(t, err) - assert.Nil(t, subnets) - - subnets, err = n.CIDRSubnetSizes(127, netip.MustParsePrefix("ffff::/48")) - assert.Error(t, err) - assert.Nil(t, subnets) - - subnets, err = n.CIDRSubnetSizes(-1, netip.MustParsePrefix("10.1.0.0/16")) - assert.Error(t, err) - assert.Nil(t, subnets) - - network := netip.MustParsePrefix("8000::/1") - subnets, err = n.CIDRSubnetSizes(1, 2, 2, network) - require.NoError(t, err) - assert.Len(t, subnets, 3) - assert.Equal(t, "8000::/2", subnets[0].String()) - assert.Equal(t, "c000::/3", subnets[1].String()) - assert.Equal(t, "e000::/3", subnets[2].String()) - - network = netip.MustParsePrefix("10.1.0.0/16") - subnets, err = n.CIDRSubnetSizes(4, 4, 8, 4, network) - require.NoError(t, err) - assert.Len(t, subnets, 4) - assert.Equal(t, "10.1.0.0/20", subnets[0].String()) - assert.Equal(t, "10.1.16.0/20", subnets[1].String()) - assert.Equal(t, "10.1.32.0/24", subnets[2].String()) - assert.Equal(t, "10.1.48.0/20", subnets[3].String()) - - network = netip.MustParsePrefix("2016:1234:5678:9abc:ffff:ffff:ffff:cafe/64") - subnets, err = n.CIDRSubnetSizes(2, 2, 3, 3, 6, 6, 8, 10, network) - require.NoError(t, err) - assert.Len(t, subnets, 8) - assert.Equal(t, "2016:1234:5678:9abc::/66", subnets[0].String()) - assert.Equal(t, "2016:1234:5678:9abc:4000::/66", subnets[1].String()) - assert.Equal(t, "2016:1234:5678:9abc:8000::/67", subnets[2].String()) - assert.Equal(t, "2016:1234:5678:9abc:a000::/67", subnets[3].String()) - assert.Equal(t, "2016:1234:5678:9abc:c000::/70", subnets[4].String()) - assert.Equal(t, "2016:1234:5678:9abc:c400::/70", subnets[5].String()) - assert.Equal(t, "2016:1234:5678:9abc:c800::/72", subnets[6].String()) - assert.Equal(t, "2016:1234:5678:9abc:c900::/74", subnets[7].String()) -} diff --git a/funcs/path.go b/funcs/path.go deleted file mode 100644 index 324f188a..00000000 --- a/funcs/path.go +++ /dev/null @@ -1,79 +0,0 @@ -package funcs - -import ( - "context" - "path" - - "github.com/hairyhenderson/gomplate/v4/conv" -) - -// PathNS - the Path namespace -// -// Deprecated: don't use -func PathNS() *PathFuncs { - return &PathFuncs{} -} - -// AddPathFuncs - -// -// Deprecated: use [CreatePathFuncs] instead -func AddPathFuncs(f map[string]interface{}) { - for k, v := range CreatePathFuncs(context.Background()) { - f[k] = v - } -} - -// CreatePathFuncs - -func CreatePathFuncs(ctx context.Context) map[string]interface{} { - ns := &PathFuncs{ctx} - return map[string]interface{}{ - "path": func() interface{} { return ns }, - } -} - -// PathFuncs - -type PathFuncs struct { - ctx context.Context -} - -// Base - -func (PathFuncs) Base(in interface{}) string { - return path.Base(conv.ToString(in)) -} - -// Clean - -func (PathFuncs) Clean(in interface{}) string { - return path.Clean(conv.ToString(in)) -} - -// Dir - -func (PathFuncs) Dir(in interface{}) string { - return path.Dir(conv.ToString(in)) -} - -// Ext - -func (PathFuncs) Ext(in interface{}) string { - return path.Ext(conv.ToString(in)) -} - -// IsAbs - -func (PathFuncs) IsAbs(in interface{}) bool { - return path.IsAbs(conv.ToString(in)) -} - -// Join - -func (PathFuncs) Join(elem ...interface{}) string { - s := conv.ToStrings(elem...) - return path.Join(s...) -} - -// Match - -func (PathFuncs) Match(pattern, name interface{}) (matched bool, err error) { - return path.Match(conv.ToString(pattern), conv.ToString(name)) -} - -// Split - -func (PathFuncs) Split(in interface{}) []string { - dir, file := path.Split(conv.ToString(in)) - return []string{dir, file} -} diff --git a/funcs/path_test.go b/funcs/path_test.go deleted file mode 100644 index a314933b..00000000 --- a/funcs/path_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreatePathFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreatePathFuncs(ctx) - actual := fmap["path"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*PathFuncs).ctx) - }) - } -} - -func TestPathFuncs(t *testing.T) { - t.Parallel() - - p := PathFuncs{} - assert.Equal(t, "bar", p.Base("foo/bar")) - assert.Equal(t, "bar", p.Base("/foo/bar")) - - assert.Equal(t, "/foo/baz", p.Clean("/foo/bar/../baz")) - assert.Equal(t, "foo", p.Dir("foo/bar")) - assert.Equal(t, ".txt", p.Ext("/foo/bar/baz.txt")) - assert.False(t, false, p.IsAbs("foo/bar")) - assert.True(t, p.IsAbs("/foo/bar")) - assert.Equal(t, "foo/bar/qux", p.Join("foo", "bar", "baz", "..", "qux")) - m, _ := p.Match("*.txt", "foo.json") - assert.False(t, m) - m, _ = p.Match("*.txt", "foo.txt") - assert.True(t, m) - assert.Equal(t, []string{"/foo/bar/", "baz"}, p.Split("/foo/bar/baz")) -} diff --git a/funcs/random.go b/funcs/random.go deleted file mode 100644 index 6aa863be..00000000 --- a/funcs/random.go +++ /dev/null @@ -1,157 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - "strconv" - "unicode/utf8" - - "github.com/hairyhenderson/gomplate/v4/conv" - iconv "github.com/hairyhenderson/gomplate/v4/internal/conv" - "github.com/hairyhenderson/gomplate/v4/random" -) - -// RandomNS - -// -// Deprecated: don't use -func RandomNS() *RandomFuncs { - return &RandomFuncs{} -} - -// AddRandomFuncs - -// -// Deprecated: use [CreateRandomFuncs] instead -func AddRandomFuncs(f map[string]interface{}) { - for k, v := range CreateRandomFuncs(context.Background()) { - f[k] = v - } -} - -// CreateRandomFuncs - -func CreateRandomFuncs(ctx context.Context) map[string]interface{} { - ns := &RandomFuncs{ctx} - return map[string]interface{}{ - "random": func() interface{} { return ns }, - } -} - -// RandomFuncs - -type RandomFuncs struct { - ctx context.Context -} - -// ASCII - -func (RandomFuncs) ASCII(count interface{}) (string, error) { - return random.StringBounds(conv.ToInt(count), ' ', '~') -} - -// Alpha - -func (RandomFuncs) Alpha(count interface{}) (string, error) { - return random.StringRE(conv.ToInt(count), "[[:alpha:]]") -} - -// AlphaNum - -func (RandomFuncs) AlphaNum(count interface{}) (string, error) { - return random.StringRE(conv.ToInt(count), "[[:alnum:]]") -} - -// String - -func (RandomFuncs) String(count interface{}, args ...interface{}) (s string, err error) { - c := conv.ToInt(count) - if c == 0 { - return "", fmt.Errorf("count must be greater than 0") - } - m := "" - switch len(args) { - case 0: - m = "" - case 1: - m = conv.ToString(args[0]) - case 2: - var l, u rune - if isString(args[0]) && isString(args[1]) { - l, u, err = toCodePoints(args[0].(string), args[1].(string)) - if err != nil { - return "", err - } - } else { - l = rune(conv.ToInt(args[0])) - u = rune(conv.ToInt(args[1])) - } - - return random.StringBounds(c, l, u) - } - - return random.StringRE(c, m) -} - -func isString(s interface{}) bool { - switch s.(type) { - case string: - return true - default: - return false - } -} - -var rlen = utf8.RuneCountInString - -func toCodePoints(l, u string) (rune, rune, error) { - // no way are these representing valid printable codepoints - we'll treat - // them as runes - if rlen(l) == 1 && rlen(u) == 1 { - lower, _ := utf8.DecodeRuneInString(l) - upper, _ := utf8.DecodeRuneInString(u) - return lower, upper, nil - } - - li, err := strconv.ParseInt(l, 0, 32) - if err != nil { - return 0, 0, err - } - ui, err := strconv.ParseInt(u, 0, 32) - if err != nil { - return 0, 0, err - } - - return rune(li), rune(ui), nil -} - -// Item - -func (RandomFuncs) Item(items interface{}) (interface{}, error) { - i, err := iconv.InterfaceSlice(items) - if err != nil { - return nil, err - } - return random.Item(i) -} - -// Number - -func (RandomFuncs) Number(args ...interface{}) (int64, error) { - var min, max int64 - min, max = 0, 100 - switch len(args) { - case 0: - case 1: - max = conv.ToInt64(args[0]) - case 2: - min = conv.ToInt64(args[0]) - max = conv.ToInt64(args[1]) - } - return random.Number(min, max) -} - -// Float - -func (RandomFuncs) Float(args ...interface{}) (float64, error) { - var min, max float64 - min, max = 0, 1.0 - switch len(args) { - case 0: - case 1: - max = conv.ToFloat64(args[0]) - case 2: - min = conv.ToFloat64(args[0]) - max = conv.ToFloat64(args[1]) - } - return random.Float(min, max) -} diff --git a/funcs/random_test.go b/funcs/random_test.go deleted file mode 100644 index 7f166ffc..00000000 --- a/funcs/random_test.go +++ /dev/null @@ -1,223 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - "unicode/utf8" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateRandomFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateRandomFuncs(ctx) - actual := fmap["random"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*RandomFuncs).ctx) - }) - } -} - -func TestASCII(t *testing.T) { - t.Parallel() - - f := RandomFuncs{} - s, err := f.ASCII(0) - require.NoError(t, err) - assert.Empty(t, s) - - s, err = f.ASCII(100) - require.NoError(t, err) - assert.Len(t, s, 100) - assert.Regexp(t, "^[[:print:]]*$", s) -} - -func TestAlpha(t *testing.T) { - t.Parallel() - - if testing.Short() { - t.Skip("skipping slow test") - } - - f := RandomFuncs{} - s, err := f.Alpha(0) - require.NoError(t, err) - assert.Empty(t, s) - - s, err = f.Alpha(100) - require.NoError(t, err) - assert.Len(t, s, 100) - assert.Regexp(t, "^[[:alpha:]]*$", s) -} - -func TestAlphaNum(t *testing.T) { - t.Parallel() - - if testing.Short() { - t.Skip("skipping slow test") - } - - f := RandomFuncs{} - s, err := f.AlphaNum(0) - require.NoError(t, err) - assert.Empty(t, s) - - s, err = f.AlphaNum(100) - require.NoError(t, err) - assert.Len(t, s, 100) - assert.Regexp(t, "^[[:alnum:]]*$", s) -} - -func TestToCodePoints(t *testing.T) { - t.Parallel() - - l, u, err := toCodePoints("a", "b") - require.NoError(t, err) - assert.Equal(t, 'a', l) - assert.Equal(t, 'b', u) - - _, _, err = toCodePoints("foo", "bar") - assert.Error(t, err) - - _, _, err = toCodePoints("0755", "bar") - assert.Error(t, err) - - l, u, err = toCodePoints("0xD700", "0x0001FFFF") - require.NoError(t, err) - assert.Equal(t, '\ud700', l) - assert.Equal(t, '\U0001ffff', u) - - l, u, err = toCodePoints("0011", "0777") - require.NoError(t, err) - assert.Equal(t, rune(0o011), l) - assert.Equal(t, rune(0o777), u) - - l, u, err = toCodePoints("♬", "♟") - require.NoError(t, err) - assert.Equal(t, rune(0x266C), l) - assert.Equal(t, '♟', u) -} - -func TestString(t *testing.T) { - t.Parallel() - - if testing.Short() { - t.Skip("skipping slow test") - } - - f := RandomFuncs{} - out, err := f.String(1) - require.NoError(t, err) - assert.Len(t, out, 1) - - out, err = f.String(42) - require.NoError(t, err) - assert.Len(t, out, 42) - - _, err = f.String(0) - assert.Error(t, err) - - out, err = f.String(8, "[a-z]") - require.NoError(t, err) - assert.Regexp(t, "^[a-z]{8}$", out) - - out, err = f.String(10, 0x23, 0x26) - require.NoError(t, err) - assert.Regexp(t, "^[#$%&]{10}$", out) - - out, err = f.String(8, '\U0001f062', '\U0001f093') - require.NoError(t, err) - assert.Regexp(t, "^[🁢-🂓]{8}$", out) - - out, err = f.String(8, '\U0001f062', '\U0001f093') - require.NoError(t, err) - assert.Regexp(t, "^[🁢-🂓]{8}$", out) - - out, err = f.String(8, "♚", "♟") - require.NoError(t, err) - assert.Regexp(t, "^[♚-♟]{8}$", out) - - out, err = f.String(100, "♠", "♣") - require.NoError(t, err) - assert.Equal(t, 100, utf8.RuneCountInString(out)) - assert.Regexp(t, "^[♠-♣]{100}$", out) -} - -func TestItem(t *testing.T) { - t.Parallel() - - f := RandomFuncs{} - _, err := f.Item(nil) - assert.Error(t, err) - - _, err = f.Item("foo") - assert.Error(t, err) - - i, err := f.Item([]string{"foo"}) - require.NoError(t, err) - assert.Equal(t, "foo", i) - - in := []string{"foo", "bar"} - got := "" - for j := 0; j < 10; j++ { - i, err = f.Item(in) - require.NoError(t, err) - got += i.(string) - } - assert.NotEqual(t, "foofoofoofoofoofoofoofoofoofoo", got) - assert.NotEqual(t, "barbarbarbarbarbarbarbarbarbar", got) -} - -func TestNumber(t *testing.T) { - t.Parallel() - - f := RandomFuncs{} - n, err := f.Number() - require.NoError(t, err) - assert.True(t, 0 <= n && n <= 100, n) - - _, err = f.Number(-1) - assert.Error(t, err) - - n, err = f.Number(0) - require.NoError(t, err) - assert.Equal(t, int64(0), n) - - n, err = f.Number(9, 9) - require.NoError(t, err) - assert.Equal(t, int64(9), n) - - n, err = f.Number(-10, -10) - require.NoError(t, err) - assert.Equal(t, int64(-10), n) -} - -func TestFloat(t *testing.T) { - t.Parallel() - - f := RandomFuncs{} - n, err := f.Float() - require.NoError(t, err) - assert.InDelta(t, 0.5, n, 0.5) - - n, err = f.Float(0.5) - require.NoError(t, err) - assert.InDelta(t, 0.25, n, 0.25) - - n, err = f.Float(490, 500) - require.NoError(t, err) - assert.InDelta(t, 495, n, 5) - - n, err = f.Float(-500, 500) - require.NoError(t, err) - assert.InDelta(t, 0, n, 500) -} diff --git a/funcs/regexp.go b/funcs/regexp.go deleted file mode 100644 index 66cbe4c4..00000000 --- a/funcs/regexp.go +++ /dev/null @@ -1,106 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/regexp" -) - -// ReNS - -// -// Deprecated: don't use -func ReNS() *ReFuncs { - return &ReFuncs{} -} - -// AddReFuncs - -// -// Deprecated: use [CreateReFuncs] instead -func AddReFuncs(f map[string]interface{}) { - for k, v := range CreateReFuncs(context.Background()) { - f[k] = v - } -} - -// CreateReFuncs - -func CreateReFuncs(ctx context.Context) map[string]interface{} { - ns := &ReFuncs{ctx} - return map[string]interface{}{ - "regexp": func() interface{} { return ns }, - } -} - -// ReFuncs - -type ReFuncs struct { - ctx context.Context -} - -// Find - -func (ReFuncs) Find(re, input interface{}) (string, error) { - return regexp.Find(conv.ToString(re), conv.ToString(input)) -} - -// FindAll - -func (ReFuncs) FindAll(args ...interface{}) ([]string, error) { - re := "" - n := 0 - input := "" - switch len(args) { - case 2: - n = -1 - re = conv.ToString(args[0]) - input = conv.ToString(args[1]) - case 3: - re = conv.ToString(args[0]) - n = conv.ToInt(args[1]) - input = conv.ToString(args[2]) - default: - return nil, fmt.Errorf("wrong number of args: want 2 or 3, got %d", len(args)) - } - return regexp.FindAll(re, n, input) -} - -// Match - -func (ReFuncs) Match(re, input interface{}) bool { - return regexp.Match(conv.ToString(re), conv.ToString(input)) -} - -// QuoteMeta - -func (ReFuncs) QuoteMeta(in interface{}) string { - return regexp.QuoteMeta(conv.ToString(in)) -} - -// Replace - -func (ReFuncs) Replace(re, replacement, input interface{}) string { - return regexp.Replace(conv.ToString(re), - conv.ToString(replacement), - conv.ToString(input)) -} - -// ReplaceLiteral - -func (ReFuncs) ReplaceLiteral(re, replacement, input interface{}) (string, error) { - return regexp.ReplaceLiteral(conv.ToString(re), - conv.ToString(replacement), - conv.ToString(input)) -} - -// Split - -func (ReFuncs) Split(args ...interface{}) ([]string, error) { - re := "" - n := -1 - input := "" - switch len(args) { - case 2: - re = conv.ToString(args[0]) - input = conv.ToString(args[1]) - case 3: - re = conv.ToString(args[0]) - n = conv.ToInt(args[1]) - input = conv.ToString(args[2]) - default: - return nil, fmt.Errorf("wrong number of args: want 2 or 3, got %d", len(args)) - } - return regexp.Split(re, n, input) -} diff --git a/funcs/regexp_test.go b/funcs/regexp_test.go deleted file mode 100644 index 181c74df..00000000 --- a/funcs/regexp_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateReFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateReFuncs(ctx) - actual := fmap["regexp"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*ReFuncs).ctx) - }) - } -} - -func TestReplace(t *testing.T) { - t.Parallel() - - re := &ReFuncs{} - assert.Equal(t, "hello world", re.Replace("i", "ello", "hi world")) -} - -func TestMatch(t *testing.T) { - t.Parallel() - - re := &ReFuncs{} - assert.True(t, re.Match(`i\ `, "hi world")) -} - -func TestFind(t *testing.T) { - t.Parallel() - - re := &ReFuncs{} - f, err := re.Find(`[a-z]+`, `foo bar baz`) - require.NoError(t, err) - assert.Equal(t, "foo", f) - - _, err = re.Find(`[a-`, "") - assert.Error(t, err) - - f, err = re.Find("4", 42) - require.NoError(t, err) - assert.Equal(t, "4", f) - - f, err = re.Find(false, 42) - require.NoError(t, err) - assert.Equal(t, "", f) -} - -func TestFindAll(t *testing.T) { - t.Parallel() - - re := &ReFuncs{} - f, err := re.FindAll(`[a-z]+`, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar", "baz"}, f) - - f, err = re.FindAll(`[a-z]+`, -1, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar", "baz"}, f) - - _, err = re.FindAll(`[a-`, "") - assert.Error(t, err) - - _, err = re.FindAll("") - assert.Error(t, err) - - _, err = re.FindAll("", "", "", "") - assert.Error(t, err) - - f, err = re.FindAll(`[a-z]+`, 0, `foo bar baz`) - require.NoError(t, err) - assert.Nil(t, f) - - f, err = re.FindAll(`[a-z]+`, 2, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar"}, f) - - f, err = re.FindAll(`[a-z]+`, 14, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar", "baz"}, f) - - f, err = re.FindAll(`qux`, `foo bar baz`) - require.NoError(t, err) - assert.Nil(t, f) -} - -func TestSplit(t *testing.T) { - t.Parallel() - - re := &ReFuncs{} - f, err := re.Split(` `, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar", "baz"}, f) - - f, err = re.Split(`\s+`, -1, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar", "baz"}, f) - - _, err = re.Split(`[a-`, "") - assert.Error(t, err) - - _, err = re.Split("") - assert.Error(t, err) - - _, err = re.Split("", "", "", "") - assert.Error(t, err) - - f, err = re.Split(` `, 0, `foo bar baz`) - require.NoError(t, err) - assert.Nil(t, f) - - f, err = re.Split(`\s+`, 2, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar baz"}, f) - - f, err = re.Split(`\s`, 14, `foo bar baz`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "", "bar", "baz"}, f) - - f, err = re.Split(`[\s,.]`, 14, `foo bar.baz,qux`) - require.NoError(t, err) - assert.EqualValues(t, []string{"foo", "bar", "baz", "qux"}, f) -} - -func TestReplaceLiteral(t *testing.T) { - t.Parallel() - - re := &ReFuncs{} - r, err := re.ReplaceLiteral("i", "ello$1", "hi world") - require.NoError(t, err) - assert.Equal(t, "hello$1 world", r) -} diff --git a/funcs/semver.go b/funcs/semver.go deleted file mode 100644 index 0212c998..00000000 --- a/funcs/semver.go +++ /dev/null @@ -1,40 +0,0 @@ -package funcs - -import ( - "context" - - "github.com/Masterminds/semver/v3" -) - -// CreateSemverFuncs - -func CreateSemverFuncs(ctx context.Context) map[string]interface{} { - ns := &SemverFuncs{ctx} - return map[string]interface{}{ - "semver": func() interface{} { return ns }, - } -} - -// SemverFuncs - -type SemverFuncs struct { - ctx context.Context -} - -// Semver - -func (SemverFuncs) Semver(version string) (*semver.Version, error) { - return semver.NewVersion(version) -} - -// CheckConstraint - -func (SemverFuncs) CheckConstraint(constraint, in string) (bool, error) { - c, err := semver.NewConstraint(constraint) - if err != nil { - return false, err - } - - v, err := semver.NewVersion(in) - if err != nil { - return false, err - } - - return c.Check(v), nil -} diff --git a/funcs/semver_test.go b/funcs/semver_test.go deleted file mode 100644 index f10aad5c..00000000 --- a/funcs/semver_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package funcs - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSemverFuncs_MatchConstraint(t *testing.T) { - tests := []struct { - name string - constraint string - in string - want bool - wantErr bool - }{ - { - name: "mached constraint", - constraint: ">=1.0.0", - in: "v1.1.1", - want: true, - wantErr: false, - }, - { - name: "not matched constraint", - constraint: "<1.0.0", - in: "v1.1.1", - want: false, - wantErr: false, - }, - { - name: "wrong constraint", - constraint: "abc", - in: "v1.1.1", - want: false, - wantErr: true, - }, - { - name: "wrong in", - constraint: ">1.0.0", - in: "va.b.c", - want: false, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := SemverFuncs{ - ctx: context.Background(), - } - got, err := s.CheckConstraint(tt.constraint, tt.in) - if tt.wantErr { - assert.Errorf(t, err, "SemverFuncs.CheckConstraint() error = %v, wantErr %v", err, tt.wantErr) - } else { - assert.NoErrorf(t, err, "SemverFuncs.CheckConstraint() error = %v, wantErr %v", err, tt.wantErr) - assert.Equal(t, tt.want, got) - } - }) - } -} diff --git a/funcs/sockaddr.go b/funcs/sockaddr.go deleted file mode 100644 index e41fb162..00000000 --- a/funcs/sockaddr.go +++ /dev/null @@ -1,130 +0,0 @@ -package funcs - -import ( - "context" - - "github.com/hashicorp/go-sockaddr" - "github.com/hashicorp/go-sockaddr/template" -) - -// SockaddrNS - the sockaddr namespace -// -// Deprecated: don't use -func SockaddrNS() *SockaddrFuncs { - return &SockaddrFuncs{} -} - -// AddSockaddrFuncs - -// -// Deprecated: use [CreateSockaddrFuncs] instead -func AddSockaddrFuncs(f map[string]interface{}) { - f["sockaddr"] = SockaddrNS -} - -// CreateSockaddrFuncs - -func CreateSockaddrFuncs(ctx context.Context) map[string]interface{} { - ns := &SockaddrFuncs{ctx} - return map[string]interface{}{ - "sockaddr": func() interface{} { return ns }, - } -} - -// SockaddrFuncs - -type SockaddrFuncs struct { - ctx context.Context -} - -// GetAllInterfaces - -func (SockaddrFuncs) GetAllInterfaces() (sockaddr.IfAddrs, error) { - return sockaddr.GetAllInterfaces() -} - -// GetDefaultInterfaces - -func (SockaddrFuncs) GetDefaultInterfaces() (sockaddr.IfAddrs, error) { - return sockaddr.GetDefaultInterfaces() -} - -// GetPrivateInterfaces - -func (SockaddrFuncs) GetPrivateInterfaces() (sockaddr.IfAddrs, error) { - return sockaddr.GetPrivateInterfaces() -} - -// GetPublicInterfaces - -func (SockaddrFuncs) GetPublicInterfaces() (sockaddr.IfAddrs, error) { - return sockaddr.GetPublicInterfaces() -} - -// Sort - -func (SockaddrFuncs) Sort(selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { - return sockaddr.SortIfBy(selectorParam, inputIfAddrs) -} - -// Exclude - -func (SockaddrFuncs) Exclude(selectorName, selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { - return sockaddr.ExcludeIfs(selectorName, selectorParam, inputIfAddrs) -} - -// Include - -func (SockaddrFuncs) Include(selectorName, selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { - return sockaddr.IncludeIfs(selectorName, selectorParam, inputIfAddrs) -} - -// Attr - -func (SockaddrFuncs) Attr(selectorName string, ifAddrsRaw interface{}) (string, error) { - return template.Attr(selectorName, ifAddrsRaw) -} - -// Join - -func (SockaddrFuncs) Join(selectorName, joinString string, inputIfAddrs sockaddr.IfAddrs) (string, error) { - return sockaddr.JoinIfAddrs(selectorName, joinString, inputIfAddrs) -} - -// Limit - -func (SockaddrFuncs) Limit(lim uint, in sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { - return sockaddr.LimitIfAddrs(lim, in) -} - -// Offset - -func (SockaddrFuncs) Offset(off int, in sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { - return sockaddr.OffsetIfAddrs(off, in) -} - -// Unique - -func (SockaddrFuncs) Unique(selectorName string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { - return sockaddr.UniqueIfAddrsBy(selectorName, inputIfAddrs) -} - -// Math - -func (SockaddrFuncs) Math(operation, value string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { - return sockaddr.IfAddrsMath(operation, value, inputIfAddrs) -} - -// GetPrivateIP - -func (SockaddrFuncs) GetPrivateIP() (string, error) { - return sockaddr.GetPrivateIP() -} - -// GetPrivateIPs - -func (SockaddrFuncs) GetPrivateIPs() (string, error) { - return sockaddr.GetPrivateIPs() -} - -// GetPublicIP - -func (SockaddrFuncs) GetPublicIP() (string, error) { - return sockaddr.GetPublicIP() -} - -// GetPublicIPs - -func (SockaddrFuncs) GetPublicIPs() (string, error) { - return sockaddr.GetPublicIPs() -} - -// GetInterfaceIP - -func (SockaddrFuncs) GetInterfaceIP(namedIfRE string) (string, error) { - return sockaddr.GetInterfaceIP(namedIfRE) -} - -// GetInterfaceIPs - -func (SockaddrFuncs) GetInterfaceIPs(namedIfRE string) (string, error) { - return sockaddr.GetInterfaceIPs(namedIfRE) -} diff --git a/funcs/sockaddr_test.go b/funcs/sockaddr_test.go deleted file mode 100644 index caf86bf8..00000000 --- a/funcs/sockaddr_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateSockaddrFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateSockaddrFuncs(ctx) - actual := fmap["sockaddr"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*SockaddrFuncs).ctx) - }) - } -} diff --git a/funcs/strings.go b/funcs/strings.go deleted file mode 100644 index f95e6bf5..00000000 --- a/funcs/strings.go +++ /dev/null @@ -1,370 +0,0 @@ -package funcs - -// Namespace strings contains mostly wrappers of equivalently-named -// functions in the standard library `strings` package, with -// differences in argument order where it makes pipelining -// in templates easier. - -import ( - "context" - "fmt" - "reflect" - "strings" - "unicode/utf8" - - "github.com/Masterminds/goutils" - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/internal/deprecated" - gompstrings "github.com/hairyhenderson/gomplate/v4/strings" - - "github.com/gosimple/slug" - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -// StrNS - -// -// Deprecated: don't use -func StrNS() *StringFuncs { - return &StringFuncs{} -} - -// AddStringFuncs - -// -// Deprecated: use [CreateStringFuncs] instead -func AddStringFuncs(f map[string]interface{}) { - for k, v := range CreateStringFuncs(context.Background()) { - f[k] = v - } -} - -// CreateStringFuncs - -func CreateStringFuncs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - - ns := &StringFuncs{ctx, language.Und} - f["strings"] = func() interface{} { return ns } - - f["replaceAll"] = ns.ReplaceAll - f["title"] = ns.Title - f["toUpper"] = ns.ToUpper - f["toLower"] = ns.ToLower - f["trimSpace"] = ns.TrimSpace - f["indent"] = ns.Indent - f["quote"] = ns.Quote - f["shellQuote"] = ns.ShellQuote - f["squote"] = ns.Squote - - // these are legacy aliases with non-pipelinable arg order - f["contains"] = ns.oldContains - f["hasPrefix"] = ns.oldHasPrefix - f["hasSuffix"] = ns.oldHasSuffix - f["split"] = ns.oldSplit - f["splitN"] = ns.oldSplitN - f["trim"] = ns.oldTrim - - return f -} - -// StringFuncs - -type StringFuncs struct { - ctx context.Context - - // tag - the selected BCP 47 language tag. Currently gomplate only supports - // Und (undetermined) - tag language.Tag -} - -// ---- legacy aliases with non-pipelinable arg order - -// oldContains - -// -// Deprecated: use [strings.Contains] instead -func (f *StringFuncs) oldContains(s, substr string) bool { - deprecated.WarnDeprecated(f.ctx, "contains is deprecated - use strings.Contains instead") - return strings.Contains(s, substr) -} - -// oldHasPrefix - -// -// Deprecated: use [strings.HasPrefix] instead -func (f *StringFuncs) oldHasPrefix(s, prefix string) bool { - deprecated.WarnDeprecated(f.ctx, "hasPrefix is deprecated - use strings.HasPrefix instead") - return strings.HasPrefix(s, prefix) -} - -// oldHasSuffix - -// -// Deprecated: use [strings.HasSuffix] instead -func (f *StringFuncs) oldHasSuffix(s, suffix string) bool { - deprecated.WarnDeprecated(f.ctx, "hasSuffix is deprecated - use strings.HasSuffix instead") - return strings.HasSuffix(s, suffix) -} - -// oldSplit - -// -// Deprecated: use [strings.Split] instead -func (f *StringFuncs) oldSplit(s, sep string) []string { - deprecated.WarnDeprecated(f.ctx, "split is deprecated - use strings.Split instead") - return strings.Split(s, sep) -} - -// oldSplitN - -// -// Deprecated: use [strings.SplitN] instead -func (f *StringFuncs) oldSplitN(s, sep string, n int) []string { - deprecated.WarnDeprecated(f.ctx, "splitN is deprecated - use strings.SplitN instead") - return strings.SplitN(s, sep, n) -} - -// oldTrim - -// -// Deprecated: use [strings.Trim] instead -func (f *StringFuncs) oldTrim(s, cutset string) string { - deprecated.WarnDeprecated(f.ctx, "trim is deprecated - use strings.Trim instead") - return strings.Trim(s, cutset) -} - -// ---- - -// Abbrev - -func (StringFuncs) Abbrev(args ...interface{}) (string, error) { - str := "" - offset := 0 - maxWidth := 0 - if len(args) < 2 { - return "", fmt.Errorf("abbrev requires a 'maxWidth' and 'input' argument") - } - if len(args) == 2 { - maxWidth = conv.ToInt(args[0]) - str = conv.ToString(args[1]) - } - if len(args) == 3 { - offset = conv.ToInt(args[0]) - maxWidth = conv.ToInt(args[1]) - str = conv.ToString(args[2]) - } - if len(str) <= maxWidth { - return str, nil - } - return goutils.AbbreviateFull(str, offset, maxWidth) -} - -// ReplaceAll - -func (StringFuncs) ReplaceAll(old, new string, s interface{}) string { - return strings.ReplaceAll(conv.ToString(s), old, new) -} - -// Contains - -func (StringFuncs) Contains(substr string, s interface{}) bool { - return strings.Contains(conv.ToString(s), substr) -} - -// HasPrefix - -func (StringFuncs) HasPrefix(prefix string, s interface{}) bool { - return strings.HasPrefix(conv.ToString(s), prefix) -} - -// HasSuffix - -func (StringFuncs) HasSuffix(suffix string, s interface{}) bool { - return strings.HasSuffix(conv.ToString(s), suffix) -} - -// Repeat - -func (StringFuncs) Repeat(count int, s interface{}) (string, error) { - if count < 0 { - return "", fmt.Errorf("negative count %d", count) - } - str := conv.ToString(s) - if count > 0 && len(str)*count/count != len(str) { - return "", fmt.Errorf("count %d too long: causes overflow", count) - } - return strings.Repeat(str, count), nil -} - -// SkipLines - -func (StringFuncs) SkipLines(skip int, in string) (string, error) { - return gompstrings.SkipLines(skip, in) -} - -// Sort - -// -// Deprecated: use [CollFuncs.Sort] instead -func (f *StringFuncs) Sort(list interface{}) ([]string, error) { - deprecated.WarnDeprecated(f.ctx, "strings.Sort is deprecated - use coll.Sort instead") - - switch v := list.(type) { - case []string: - return gompstrings.Sort(v), nil - case []interface{}: - l := len(v) - b := make([]string, len(v)) - for i := 0; i < l; i++ { - b[i] = conv.ToString(v[i]) - } - return gompstrings.Sort(b), nil - default: - return nil, fmt.Errorf("wrong type for value; expected []string; got %T", list) - } -} - -// Split - -func (StringFuncs) Split(sep string, s interface{}) []string { - return strings.Split(conv.ToString(s), sep) -} - -// SplitN - -func (StringFuncs) SplitN(sep string, n int, s interface{}) []string { - return strings.SplitN(conv.ToString(s), sep, n) -} - -// Trim - -func (StringFuncs) Trim(cutset string, s interface{}) string { - return strings.Trim(conv.ToString(s), cutset) -} - -// TrimPrefix - -func (StringFuncs) TrimPrefix(cutset string, s interface{}) string { - return strings.TrimPrefix(conv.ToString(s), cutset) -} - -// TrimSuffix - -func (StringFuncs) TrimSuffix(cutset string, s interface{}) string { - return strings.TrimSuffix(conv.ToString(s), cutset) -} - -// Title - -func (f *StringFuncs) Title(s interface{}) string { - return cases.Title(f.tag, cases.NoLower).String(conv.ToString(s)) -} - -// ToUpper - -func (f *StringFuncs) ToUpper(s interface{}) string { - return cases.Upper(f.tag).String(conv.ToString(s)) -} - -// ToLower - -func (f *StringFuncs) ToLower(s interface{}) string { - return cases.Lower(f.tag).String(conv.ToString(s)) -} - -// TrimSpace - -func (StringFuncs) TrimSpace(s interface{}) string { - return strings.TrimSpace(conv.ToString(s)) -} - -// Trunc - -func (StringFuncs) Trunc(length int, s interface{}) string { - return gompstrings.Trunc(length, conv.ToString(s)) -} - -// Indent - -func (StringFuncs) Indent(args ...interface{}) (string, error) { - input := conv.ToString(args[len(args)-1]) - indent := " " - width := 1 - var ok bool - switch len(args) { - case 2: - indent, ok = args[0].(string) - if !ok { - width, ok = args[0].(int) - if !ok { - return "", fmt.Errorf("indent: invalid arguments") - } - indent = " " - } - case 3: - width, ok = args[0].(int) - if !ok { - return "", fmt.Errorf("indent: invalid arguments") - } - indent, ok = args[1].(string) - if !ok { - return "", fmt.Errorf("indent: invalid arguments") - } - } - return gompstrings.Indent(width, indent, input), nil -} - -// Slug - -func (StringFuncs) Slug(in interface{}) string { - return slug.Make(conv.ToString(in)) -} - -// Quote - -func (StringFuncs) Quote(in interface{}) string { - return fmt.Sprintf("%q", conv.ToString(in)) -} - -// ShellQuote - -func (StringFuncs) ShellQuote(in interface{}) string { - val := reflect.ValueOf(in) - switch val.Kind() { - case reflect.Array, reflect.Slice: - var sb strings.Builder - max := val.Len() - for n := 0; n < max; n++ { - sb.WriteString(gompstrings.ShellQuote(conv.ToString(val.Index(n)))) - if n+1 != max { - sb.WriteRune(' ') - } - } - return sb.String() - } - return gompstrings.ShellQuote(conv.ToString(in)) -} - -// Squote - -func (StringFuncs) Squote(in interface{}) string { - s := conv.ToString(in) - s = strings.ReplaceAll(s, `'`, `''`) - return fmt.Sprintf("'%s'", s) -} - -// SnakeCase - -func (StringFuncs) SnakeCase(in interface{}) (string, error) { - return gompstrings.SnakeCase(conv.ToString(in)), nil -} - -// CamelCase - -func (StringFuncs) CamelCase(in interface{}) (string, error) { - return gompstrings.CamelCase(conv.ToString(in)), nil -} - -// KebabCase - -func (StringFuncs) KebabCase(in interface{}) (string, error) { - return gompstrings.KebabCase(conv.ToString(in)), nil -} - -// WordWrap - -func (StringFuncs) WordWrap(args ...interface{}) (string, error) { - if len(args) == 0 || len(args) > 3 { - return "", fmt.Errorf("expected 1, 2, or 3 args, got %d", len(args)) - } - in := conv.ToString(args[len(args)-1]) - - opts := gompstrings.WordWrapOpts{} - if len(args) == 2 { - switch a := (args[0]).(type) { - case string: - opts.LBSeq = a - default: - opts.Width = uint(conv.ToInt(a)) - } - } - if len(args) == 3 { - opts.Width = uint(conv.ToInt(args[0])) - opts.LBSeq = conv.ToString(args[1]) - } - return gompstrings.WordWrap(in, opts), nil -} - -// RuneCount - like len(s), but for runes -func (StringFuncs) RuneCount(args ...interface{}) (int, error) { - s := "" - for _, arg := range args { - s += conv.ToString(arg) - } - return utf8.RuneCountInString(s), nil -} diff --git a/funcs/strings_test.go b/funcs/strings_test.go deleted file mode 100644 index fdc64a47..00000000 --- a/funcs/strings_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateStringFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateStringFuncs(ctx) - actual := fmap["strings"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*StringFuncs).ctx) - }) - } -} - -func TestReplaceAll(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - - assert.Equal(t, "Replaced", - sf.ReplaceAll("Orig", "Replaced", "Orig")) - assert.Equal(t, "ReplacedReplaced", - sf.ReplaceAll("Orig", "Replaced", "OrigOrig")) -} - -func TestIndent(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - - testdata := []struct { - out string - args []interface{} - }{ - {" foo\n bar\n baz", []interface{}{"foo\nbar\nbaz"}}, - {" foo\n bar\n baz", []interface{}{" ", "foo\nbar\nbaz"}}, - {"---foo\n---bar\n---baz", []interface{}{3, "-", "foo\nbar\nbaz"}}, - {" foo\n bar\n baz", []interface{}{3, "foo\nbar\nbaz"}}, - } - - for _, d := range testdata { - out, err := sf.Indent(d.args...) - require.NoError(t, err) - assert.Equal(t, d.out, out) - } -} - -func TestTrimPrefix(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - - assert.Equal(t, "Bar", - sf.TrimPrefix("Foo", "FooBar")) -} - -func TestTitle(t *testing.T) { - sf := &StringFuncs{} - testdata := []struct { - in interface{} - out string - }{ - {``, ``}, - {`foo`, `Foo`}, - {`foo bar`, `Foo Bar`}, - {`ljoo džar`, `Ljoo Džar`}, - {`foo bar᳇baz`, `Foo Bar᳇Baz`}, // ᳇ should be treated as punctuation - {`foo,bar&baz`, `Foo,Bar&Baz`}, - {`FOO`, `FOO`}, - {`bar FOO`, `Bar FOO`}, - } - - for _, d := range testdata { - up := sf.Title(d.in) - assert.Equal(t, d.out, up) - } -} - -func TestTrunc(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - assert.Equal(t, "", sf.Trunc(5, "")) - assert.Equal(t, "", sf.Trunc(0, nil)) - assert.Equal(t, "123", sf.Trunc(3, 123456789)) - assert.Equal(t, "hello, world", sf.Trunc(-1, "hello, world")) -} - -func TestAbbrev(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - _, err := sf.Abbrev() - assert.Error(t, err) - - _, err = sf.Abbrev("foo") - assert.Error(t, err) - - s, err := sf.Abbrev(3, "foo") - require.NoError(t, err) - assert.Equal(t, "foo", s) - - s, err = sf.Abbrev(2, 6, "foobar") - require.NoError(t, err) - assert.Equal(t, "foobar", s) - - s, err = sf.Abbrev(6, 9, "foobarbazquxquux") - require.NoError(t, err) - assert.Equal(t, "...baz...", s) -} - -func TestSlug(t *testing.T) { - sf := &StringFuncs{} - s := sf.Slug(nil) - assert.Equal(t, "nil", s) - - s = sf.Slug(0) - assert.Equal(t, "0", s) - - s = sf.Slug(1.85e-5) - assert.Equal(t, "1-85e-05", s) - - s = sf.Slug("Hello, World!") - assert.Equal(t, "hello-world", s) - - s = sf.Slug("foo@example.com") - assert.Equal(t, "fooatexample-com", s) - - s = sf.Slug("rock & roll!") - assert.Equal(t, "rock-and-roll", s) - - s = sf.Slug("foo@example.com") - assert.Equal(t, "fooatexample-com", s) - - s = sf.Slug(`100%`) - assert.Equal(t, "100", s) -} - -func TestSort(t *testing.T) { - t.Parallel() - sf := &StringFuncs{ctx: context.Background()} - - in := []string{"foo", "bar", "baz"} - out := []string{"bar", "baz", "foo"} - assert.Equal(t, out, must(sf.Sort(in))) - - assert.Equal(t, out, must(sf.Sort([]interface{}{"foo", "bar", "baz"}))) -} - -func TestQuote(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - testdata := []struct { - in interface{} - out string - }{ - {``, `""`}, - {`foo`, `"foo"`}, - {nil, `"nil"`}, - {123.4, `"123.4"`}, - {`hello "world"`, `"hello \"world\""`}, - {`it's its`, `"it's its"`}, - } - - for _, d := range testdata { - assert.Equal(t, d.out, sf.Quote(d.in)) - } -} - -func TestShellQuote(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - testdata := []struct { - in interface{} - out string - }{ - // conventional cases are covered in gompstrings.ShellQuote() tests - // we cover only cases that require type conversion or array/slice combining here - {nil, `'nil'`}, - {123.4, `'123.4'`}, - // array and slice cases - {[]string{}, ``}, - {[]string{"", ""}, `'' ''`}, - {[...]string{"one'two", "three four"}, `'one'"'"'two' 'three four'`}, - {[]string{"one'two", "three four"}, `'one'"'"'two' 'three four'`}, - } - - for _, d := range testdata { - assert.Equal(t, d.out, sf.ShellQuote(d.in)) - } -} - -func TestSquote(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - testdata := []struct { - in interface{} - out string - }{ - {``, `''`}, - {`foo`, `'foo'`}, - {nil, `'nil'`}, - {123.4, `'123.4'`}, - {`hello "world"`, `'hello "world"'`}, - {`it's its`, `'it''s its'`}, - } - - for _, d := range testdata { - assert.Equal(t, d.out, sf.Squote(d.in)) - } -} - -func TestRuneCount(t *testing.T) { - t.Parallel() - - sf := &StringFuncs{} - - n, err := sf.RuneCount("") - require.NoError(t, err) - assert.Equal(t, 0, n) - - n, err = sf.RuneCount("foo") - require.NoError(t, err) - assert.Equal(t, 3, n) - - n, err = sf.RuneCount("foo", "bar") - require.NoError(t, err) - assert.Equal(t, 6, n) - - n, err = sf.RuneCount(42, true) - require.NoError(t, err) - assert.Equal(t, 6, n) - - n, err = sf.RuneCount("😂\U0001F602") - require.NoError(t, err) - assert.Equal(t, 2, n) - - n, err = sf.RuneCount("\U0001F600", 3.14) - require.NoError(t, err) - assert.Equal(t, 5, n) -} diff --git a/funcs/test.go b/funcs/test.go deleted file mode 100644 index 875cf631..00000000 --- a/funcs/test.go +++ /dev/null @@ -1,120 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - "reflect" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/test" -) - -// TestNS - -// -// Deprecated: don't use -func TestNS() *TestFuncs { - return &TestFuncs{} -} - -// AddTestFuncs - -// -// Deprecated: use [CreateTestFuncs] instead -func AddTestFuncs(f map[string]interface{}) { - for k, v := range CreateTestFuncs(context.Background()) { - f[k] = v - } -} - -// CreateTestFuncs - -func CreateTestFuncs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - - ns := &TestFuncs{ctx} - f["test"] = func() interface{} { return ns } - - f["assert"] = ns.Assert - f["fail"] = ns.Fail - f["required"] = ns.Required - f["ternary"] = ns.Ternary - f["kind"] = ns.Kind - f["isKind"] = ns.IsKind - return f -} - -// TestFuncs - -type TestFuncs struct { - ctx context.Context -} - -// Assert - -func (TestFuncs) Assert(args ...interface{}) (string, error) { - input := conv.ToBool(args[len(args)-1]) - switch len(args) { - case 1: - return test.Assert(input, "") - case 2: - message, ok := args[0].(string) - if !ok { - return "", fmt.Errorf("at <1>: expected string; found %T", args[0]) - } - return test.Assert(input, message) - default: - return "", fmt.Errorf("wrong number of args: want 1 or 2, got %d", len(args)) - } -} - -// Fail - -func (TestFuncs) Fail(args ...interface{}) (string, error) { - switch len(args) { - case 0: - return "", test.Fail("") - case 1: - return "", test.Fail(conv.ToString(args[0])) - default: - return "", fmt.Errorf("wrong number of args: want 0 or 1, got %d", len(args)) - } -} - -// Required - -func (TestFuncs) Required(args ...interface{}) (interface{}, error) { - switch len(args) { - case 1: - return test.Required("", args[0]) - case 2: - message, ok := args[0].(string) - if !ok { - return nil, fmt.Errorf("at <1>: expected string; found %T", args[0]) - } - return test.Required(message, args[1]) - default: - return nil, fmt.Errorf("wrong number of args: want 1 or 2, got %d", len(args)) - } -} - -// Ternary - -func (TestFuncs) Ternary(tval, fval, b interface{}) interface{} { - if conv.ToBool(b) { - return tval - } - return fval -} - -// Kind - return the kind of the argument -func (TestFuncs) Kind(arg interface{}) string { - return reflect.ValueOf(arg).Kind().String() -} - -// IsKind - return whether or not the argument is of the given kind -func (f TestFuncs) IsKind(kind string, arg interface{}) bool { - k := f.Kind(arg) - if kind == "number" { - switch k { - case "int", "int8", "int16", "int32", "int64", - "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", - "float32", "float64", - "complex64", "complex128": - kind = k - } - } - return k == kind -} diff --git a/funcs/test_test.go b/funcs/test_test.go deleted file mode 100644 index 26909813..00000000 --- a/funcs/test_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package funcs - -import ( - "context" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateTestFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateTestFuncs(ctx) - actual := fmap["test"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*TestFuncs).ctx) - }) - } -} - -func TestAssert(t *testing.T) { - t.Parallel() - - f := TestNS() - _, err := f.Assert(false) - assert.Error(t, err) - - _, err = f.Assert(true) - require.NoError(t, err) - - _, err = f.Assert("foo", true) - require.NoError(t, err) - - _, err = f.Assert("foo", "false") - assert.EqualError(t, err, "assertion failed: foo") -} - -func TestRequired(t *testing.T) { - t.Parallel() - - f := TestNS() - errMsg := "can not render template: a required value was not set" - v, err := f.Required("") - assert.Error(t, err) - assert.EqualError(t, err, errMsg) - assert.Nil(t, v) - - v, err = f.Required(nil) - assert.Error(t, err) - assert.EqualError(t, err, errMsg) - assert.Nil(t, v) - - errMsg = "hello world" - v, err = f.Required(errMsg, nil) - assert.Error(t, err) - assert.EqualError(t, err, errMsg) - assert.Nil(t, v) - - v, err = f.Required(42, nil) - assert.Error(t, err) - assert.EqualError(t, err, "at <1>: expected string; found int") - assert.Nil(t, v) - - v, err = f.Required() - assert.Error(t, err) - assert.EqualError(t, err, "wrong number of args: want 1 or 2, got 0") - assert.Nil(t, v) - - v, err = f.Required("", 2, 3) - assert.Error(t, err) - assert.EqualError(t, err, "wrong number of args: want 1 or 2, got 3") - assert.Nil(t, v) - - v, err = f.Required(0) - require.NoError(t, err) - assert.Equal(t, v, 0) - - v, err = f.Required("foo") - require.NoError(t, err) - assert.Equal(t, v, "foo") -} - -func TestTernary(t *testing.T) { - t.Parallel() - - f := TestNS() - testdata := []struct { - tval, fval, b interface{} - expected interface{} - }{ - {"foo", 42, false, 42}, - {"foo", 42, "yes", "foo"}, - {false, true, true, false}, - } - for _, d := range testdata { - assert.Equal(t, d.expected, f.Ternary(d.tval, d.fval, d.b)) - } -} - -func TestKind(t *testing.T) { - t.Parallel() - - f := TestNS() - testdata := []struct { - arg interface{} - expected string - }{ - {"foo", "string"}, - {nil, "invalid"}, - {false, "bool"}, - {[]string{"foo", "bar"}, "slice"}, - {map[string]string{"foo": "bar"}, "map"}, - {42, "int"}, - {42.0, "float64"}, - {uint(42), "uint"}, - {struct{}{}, "struct"}, - } - for _, d := range testdata { - assert.Equal(t, d.expected, f.Kind(d.arg)) - } -} - -func TestIsKind(t *testing.T) { - t.Parallel() - - f := TestNS() - truedata := []struct { - arg interface{} - kind string - }{ - {"foo", "string"}, - {nil, "invalid"}, - {false, "bool"}, - {[]string{"foo", "bar"}, "slice"}, - {map[string]string{"foo": "bar"}, "map"}, - {42, "int"}, - {42.0, "float64"}, - {uint(42), "uint"}, - {struct{}{}, "struct"}, - {42.0, "number"}, - {42, "number"}, - {uint32(64000), "number"}, - {complex128(64000), "number"}, - } - for _, d := range truedata { - assert.True(t, f.IsKind(d.kind, d.arg)) - } - - falsedata := []struct { - arg interface{} - kind string - }{ - {"foo", "bool"}, - {nil, "struct"}, - {false, "string"}, - {[]string{"foo", "bar"}, "map"}, - {map[string]string{"foo": "bar"}, "int"}, - {42, "int64"}, - {42.0, "float32"}, - {uint(42), "int"}, - {struct{}{}, "interface"}, - } - for _, d := range falsedata { - assert.False(t, f.IsKind(d.kind, d.arg)) - } -} diff --git a/funcs/time.go b/funcs/time.go deleted file mode 100644 index ca02a35a..00000000 --- a/funcs/time.go +++ /dev/null @@ -1,229 +0,0 @@ -package funcs - -import ( - "context" - "fmt" - "strconv" - "strings" - gotime "time" - - "github.com/hairyhenderson/gomplate/v4/conv" - "github.com/hairyhenderson/gomplate/v4/env" - "github.com/hairyhenderson/gomplate/v4/time" -) - -// TimeNS - -// -// Deprecated: don't use -func TimeNS() *TimeFuncs { - return &TimeFuncs{ - ANSIC: gotime.ANSIC, - UnixDate: gotime.UnixDate, - RubyDate: gotime.RubyDate, - RFC822: gotime.RFC822, - RFC822Z: gotime.RFC822Z, - RFC850: gotime.RFC850, - RFC1123: gotime.RFC1123, - RFC1123Z: gotime.RFC1123Z, - RFC3339: gotime.RFC3339, - RFC3339Nano: gotime.RFC3339Nano, - Kitchen: gotime.Kitchen, - Stamp: gotime.Stamp, - StampMilli: gotime.StampMilli, - StampMicro: gotime.StampMicro, - StampNano: gotime.StampNano, - } -} - -// AddTimeFuncs - -// -// Deprecated: use [CreateTimeFuncs] instead -func AddTimeFuncs(f map[string]interface{}) { - for k, v := range CreateTimeFuncs(context.Background()) { - f[k] = v - } -} - -// CreateTimeFuncs - -func CreateTimeFuncs(ctx context.Context) map[string]interface{} { - ns := &TimeFuncs{ - ctx: ctx, - ANSIC: gotime.ANSIC, - UnixDate: gotime.UnixDate, - RubyDate: gotime.RubyDate, - RFC822: gotime.RFC822, - RFC822Z: gotime.RFC822Z, - RFC850: gotime.RFC850, - RFC1123: gotime.RFC1123, - RFC1123Z: gotime.RFC1123Z, - RFC3339: gotime.RFC3339, - RFC3339Nano: gotime.RFC3339Nano, - Kitchen: gotime.Kitchen, - Stamp: gotime.Stamp, - StampMilli: gotime.StampMilli, - StampMicro: gotime.StampMicro, - StampNano: gotime.StampNano, - } - - return map[string]interface{}{ - "time": func() interface{} { return ns }, - } -} - -// TimeFuncs - -type TimeFuncs struct { - ctx context.Context - ANSIC string - UnixDate string - RubyDate string - RFC822 string - RFC822Z string - RFC850 string - RFC1123 string - RFC1123Z string - RFC3339 string - RFC3339Nano string - Kitchen string - Stamp string - StampMilli string - StampMicro string - StampNano string -} - -// ZoneName - return the local system's time zone's name -func (TimeFuncs) ZoneName() string { - return time.ZoneName() -} - -// ZoneOffset - return the local system's time zone's name -func (TimeFuncs) ZoneOffset() int { - return time.ZoneOffset() -} - -// Parse - -func (TimeFuncs) Parse(layout string, value interface{}) (gotime.Time, error) { - return gotime.Parse(layout, conv.ToString(value)) -} - -// ParseLocal - -func (f TimeFuncs) ParseLocal(layout string, value interface{}) (gotime.Time, error) { - tz := env.Getenv("TZ", "Local") - return f.ParseInLocation(layout, tz, value) -} - -// ParseInLocation - -func (TimeFuncs) ParseInLocation(layout, location string, value interface{}) (gotime.Time, error) { - loc, err := gotime.LoadLocation(location) - if err != nil { - return gotime.Time{}, err - } - return gotime.ParseInLocation(layout, conv.ToString(value), loc) -} - -// Now - -func (TimeFuncs) Now() gotime.Time { - return gotime.Now() -} - -// Unix - convert UNIX time (in seconds since the UNIX epoch) into a time.Time for further processing -// Takes a string or number (int or float) -func (TimeFuncs) Unix(in interface{}) (gotime.Time, error) { - sec, nsec, err := parseNum(in) - if err != nil { - return gotime.Time{}, err - } - return gotime.Unix(sec, nsec), nil -} - -// Nanosecond - -func (TimeFuncs) Nanosecond(n interface{}) gotime.Duration { - return gotime.Nanosecond * gotime.Duration(conv.ToInt64(n)) -} - -// Microsecond - -func (TimeFuncs) Microsecond(n interface{}) gotime.Duration { - return gotime.Microsecond * gotime.Duration(conv.ToInt64(n)) -} - -// Millisecond - -func (TimeFuncs) Millisecond(n interface{}) gotime.Duration { - return gotime.Millisecond * gotime.Duration(conv.ToInt64(n)) -} - -// Second - -func (TimeFuncs) Second(n interface{}) gotime.Duration { - return gotime.Second * gotime.Duration(conv.ToInt64(n)) -} - -// Minute - -func (TimeFuncs) Minute(n interface{}) gotime.Duration { - return gotime.Minute * gotime.Duration(conv.ToInt64(n)) -} - -// Hour - -func (TimeFuncs) Hour(n interface{}) gotime.Duration { - return gotime.Hour * gotime.Duration(conv.ToInt64(n)) -} - -// ParseDuration - -func (TimeFuncs) ParseDuration(n interface{}) (gotime.Duration, error) { - return gotime.ParseDuration(conv.ToString(n)) -} - -// Since - -func (TimeFuncs) Since(n gotime.Time) gotime.Duration { - return gotime.Since(n) -} - -// Until - -func (TimeFuncs) Until(n gotime.Time) gotime.Duration { - return gotime.Until(n) -} - -// convert a number input to a pair of int64s, representing the integer portion and the decimal remainder -// this can handle a string as well as any integer or float type -// precision is at the "nano" level (i.e. 1e+9) -func parseNum(in interface{}) (integral int64, fractional int64, err error) { - if s, ok := in.(string); ok { - ss := strings.Split(s, ".") - if len(ss) > 2 { - return 0, 0, fmt.Errorf("can not parse '%s' as a number - too many decimal points", s) - } - if len(ss) == 1 { - integral, err := strconv.ParseInt(s, 0, 64) - return integral, 0, err - } - integral, err := strconv.ParseInt(ss[0], 0, 64) - if err != nil { - return integral, 0, err - } - fractional, err = strconv.ParseInt(padRight(ss[1], "0", 9), 0, 64) - return integral, fractional, err - } - if s, ok := in.(fmt.Stringer); ok { - return parseNum(s.String()) - } - if i, ok := in.(int); ok { - return int64(i), 0, nil - } - if u, ok := in.(uint64); ok { - return int64(u), 0, nil - } - if f, ok := in.(float64); ok { - return 0, 0, fmt.Errorf("can not parse floating point number (%f) - use a string instead", f) - } - if in == nil { - return 0, 0, nil - } - return 0, 0, nil -} - -// pads a number with zeroes -func padRight(in, pad string, length int) string { - for { - in += pad - if len(in) > length { - return in[0:length] - } - } -} diff --git a/funcs/time_test.go b/funcs/time_test.go deleted file mode 100644 index 8b2d6e4a..00000000 --- a/funcs/time_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package funcs - -import ( - "context" - "math" - "math/big" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateTimeFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateTimeFuncs(ctx) - actual := fmap["time"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*TimeFuncs).ctx) - }) - } -} - -func TestParseNum(t *testing.T) { - t.Parallel() - - i, f, _ := parseNum("42") - assert.Equal(t, int64(42), i) - assert.Equal(t, int64(0), f) - - i, f, _ = parseNum(42) - assert.Equal(t, int64(42), i) - assert.Equal(t, int64(0), f) - - i, f, _ = parseNum(big.NewInt(42)) - assert.Equal(t, int64(42), i) - assert.Equal(t, int64(0), f) - - i, f, _ = parseNum(big.NewFloat(42.0)) - assert.Equal(t, int64(42), i) - assert.Equal(t, int64(0), f) - - i, f, _ = parseNum(uint64(math.MaxInt64)) - assert.Equal(t, int64(uint64(math.MaxInt64)), i) - assert.Equal(t, int64(0), f) - - i, f, _ = parseNum("9223372036854775807.999999999") - assert.Equal(t, int64(9223372036854775807), i) - assert.Equal(t, int64(999999999), f) - - i, f, _ = parseNum("999999999999999.123456789123") - assert.Equal(t, int64(999999999999999), i) - assert.Equal(t, int64(123456789), f) - - i, f, _ = parseNum("123456.789") - assert.Equal(t, int64(123456), i) - assert.Equal(t, int64(789000000), f) - - _, _, err := parseNum("bogus.9223372036854775807") - assert.Error(t, err) - - _, _, err = parseNum("bogus") - assert.Error(t, err) - - _, _, err = parseNum("1.2.3") - assert.Error(t, err) - - _, _, err = parseNum(1.1) - assert.Error(t, err) - - i, f, err = parseNum(nil) - assert.Zero(t, i) - assert.Zero(t, f) - require.NoError(t, err) -} diff --git a/funcs/uuid.go b/funcs/uuid.go deleted file mode 100644 index 2ad9d874..00000000 --- a/funcs/uuid.go +++ /dev/null @@ -1,83 +0,0 @@ -package funcs - -import ( - "context" - - "github.com/hairyhenderson/gomplate/v4/conv" - - "github.com/google/uuid" -) - -// UUIDNS - -// -// Deprecated: don't use -func UUIDNS() *UUIDFuncs { - return &UUIDFuncs{} -} - -// AddUUIDFuncs - -// -// Deprecated: use [CreateUUIDFuncs] instead -func AddUUIDFuncs(f map[string]interface{}) { - for k, v := range CreateUUIDFuncs(context.Background()) { - f[k] = v - } -} - -// CreateUUIDFuncs - -func CreateUUIDFuncs(ctx context.Context) map[string]interface{} { - ns := &UUIDFuncs{ctx} - return map[string]interface{}{ - "uuid": func() interface{} { return ns }, - } -} - -// UUIDFuncs - -type UUIDFuncs struct { - ctx context.Context -} - -// V1 - return a version 1 UUID (based on the current MAC Address and the -// current date/time). Use V4 instead in most cases. -func (UUIDFuncs) V1() (string, error) { - u, err := uuid.NewUUID() - if err != nil { - return "", err - } - return u.String(), nil -} - -// V4 - return a version 4 (random) UUID -func (UUIDFuncs) V4() (string, error) { - u, err := uuid.NewRandom() - if err != nil { - return "", err - } - return u.String(), nil -} - -// Nil - -func (UUIDFuncs) Nil() (string, error) { - return uuid.Nil.String(), nil -} - -// IsValid - checks if the given UUID is in the correct format. It does not -// validate whether the version or variant are correct. -func (f UUIDFuncs) IsValid(in interface{}) (bool, error) { - _, err := f.Parse(in) - return err == nil, nil -} - -// Parse - parse a UUID for further manipulation or inspection. -// -// Both the standard UUID forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and -// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the -// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex -// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. -func (UUIDFuncs) Parse(in interface{}) (uuid.UUID, error) { - u, err := uuid.Parse(conv.ToString(in)) - if err != nil { - return uuid.Nil, err - } - return u, err -} diff --git a/funcs/uuid_test.go b/funcs/uuid_test.go deleted file mode 100644 index c21981ea..00000000 --- a/funcs/uuid_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package funcs - -import ( - "context" - "net/url" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateUUIDFuncs(t *testing.T) { - t.Parallel() - - for i := 0; i < 10; i++ { - // Run this a bunch to catch race conditions - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - fmap := CreateUUIDFuncs(ctx) - actual := fmap["uuid"].(func() interface{}) - - assert.Equal(t, ctx, actual().(*UUIDFuncs).ctx) - }) - } -} - -const ( - uuidV1Pattern = "^[[:xdigit:]]{8}-[[:xdigit:]]{4}-1[[:xdigit:]]{3}-[89ab][[:xdigit:]]{3}-[[:xdigit:]]{12}$" - uuidV4Pattern = "^[[:xdigit:]]{8}-[[:xdigit:]]{4}-4[[:xdigit:]]{3}-[89ab][[:xdigit:]]{3}-[[:xdigit:]]{12}$" -) - -func TestV1(t *testing.T) { - t.Parallel() - - u := UUIDNS() - i, err := u.V1() - require.NoError(t, err) - assert.Regexp(t, uuidV1Pattern, i) -} - -func TestV4(t *testing.T) { - t.Parallel() - - u := UUIDNS() - i, err := u.V4() - require.NoError(t, err) - assert.Regexp(t, uuidV4Pattern, i) -} - -func TestNil(t *testing.T) { - t.Parallel() - - u := UUIDNS() - i, err := u.Nil() - require.NoError(t, err) - assert.Equal(t, "00000000-0000-0000-0000-000000000000", i) -} - -func TestIsValid(t *testing.T) { - t.Parallel() - - u := UUIDNS() - in := interface{}(false) - i, err := u.IsValid(in) - require.NoError(t, err) - assert.False(t, i) - - in = 12345 - i, err = u.IsValid(in) - require.NoError(t, err) - assert.False(t, i) - - testdata := []interface{}{ - "123456781234123412341234567890ab", - "12345678-1234-1234-1234-1234567890ab", - "urn:uuid:12345678-1234-1234-1234-1234567890ab", - "{12345678-1234-1234-1234-1234567890ab}", - } - - for _, d := range testdata { - i, err = u.IsValid(d) - require.NoError(t, err) - assert.True(t, i) - } -} - -func TestParse(t *testing.T) { - t.Parallel() - - u := UUIDNS() - in := interface{}(false) - _, err := u.Parse(in) - assert.Error(t, err) - - in = 12345 - _, err = u.Parse(in) - assert.Error(t, err) - - in = "12345678-1234-1234-1234-1234567890ab" - testdata := []interface{}{ - "123456781234123412341234567890ab", - "12345678-1234-1234-1234-1234567890ab", - "urn:uuid:12345678-1234-1234-1234-1234567890ab", - must(url.Parse("urn:uuid:12345678-1234-1234-1234-1234567890ab")), - "{12345678-1234-1234-1234-1234567890ab}", - } - - for _, d := range testdata { - uid, err := u.Parse(d) - require.NoError(t, err) - assert.Equal(t, in, uid.String()) - } -} |
