porting many functions

This commit is contained in:
Kristin Laemmert 2018-05-23 14:52:50 -07:00 committed by Martin Atkins
parent 083ea05295
commit 10ef61c71c
3 changed files with 319 additions and 4 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/gocty"
)
var JoinFunc = function.New(&function.Spec{
@ -126,6 +127,99 @@ var ChompFunc = function.New(&function.Spec{
},
})
// IndentFunc constructions a function that adds a given number of spaces to the
// beginnings of all but the first line in a given multi-line string.
var IndentFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "spaces",
Type: cty.Number,
},
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var spaces int
if err := gocty.FromCtyValue(args[0], &spaces); err != nil {
return cty.UnknownVal(cty.String), err
}
data := args[1].AsString()
pad := strings.Repeat(" ", spaces)
return cty.StringVal(strings.Replace(data, "\n", "\n"+pad, -1)), nil
},
})
// ReplaceFunc constructions a function that searches a given string for another
// given substring, and replaces each occurence with a given replacement string.
var ReplaceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
{
Name: "substr",
Type: cty.String,
},
{
Name: "replace",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
str := args[0].AsString()
substr := args[1].AsString()
replace := args[2].AsString()
// We search/replace using a regexp if the string is surrounded
// in forward slashes.
if len(substr) > 1 && substr[0] == '/' && substr[len(substr)-1] == '/' {
re, err := regexp.Compile(substr[1 : len(substr)-1])
if err != nil {
return cty.UnknownVal(cty.String), err
}
return cty.StringVal(re.ReplaceAllString(str, replace)), nil
}
return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil
},
})
// TitleFunc constructions a function that converts the first letter of each word
// in the given string to uppercase.
var TitleFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return cty.StringVal(strings.Title(args[0].AsString())), nil
},
})
// TrimSpaceFunc constructions a function that removes any space characters from
// the start and end of the given string.
var TrimSpaceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "str",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
return cty.StringVal(strings.TrimSpace(args[0].AsString())), nil
},
})
// Join concatenates together the string elements of one or more lists with a
// given separator.
func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) {
@ -151,3 +245,25 @@ func Split(sep, str cty.Value) (cty.Value, error) {
func Chomp(str cty.Value) (cty.Value, error) {
return ChompFunc.Call([]cty.Value{str})
}
// Indent adds a given number of spaces to the beginnings of all but the first
// line in a given multi-line string.
func Indent(spaces, str cty.Value) (cty.Value, error) {
return IndentFunc.Call([]cty.Value{spaces, str})
}
// Replace searches a given string for another given substring,
// and replaces all occurences with a given replacement string.
func Replace(str, substr, replace cty.Value) (cty.Value, error) {
return ReplaceFunc.Call([]cty.Value{str, substr, replace})
}
// Title converts the first letter of each word in the given string to uppercase.
func Title(str cty.Value) (cty.Value, error) {
return TitleFunc.Call([]cty.Value{str})
}
// TrimSpace removes any space characters from the start and end of the given string.
func TrimSpace(str cty.Value) (cty.Value, error) {
return TrimSpaceFunc.Call([]cty.Value{str})
}

View File

@ -307,3 +307,202 @@ func TestChomp(t *testing.T) {
})
}
}
func TestIndent(t *testing.T) {
tests := []struct {
String cty.Value
Spaces cty.Value
Want cty.Value
Err bool
}{
{
cty.StringVal(`Fleas:
Adam
Had'em
E.E. Cummings`),
cty.NumberIntVal(4),
cty.StringVal("Fleas:\n Adam\n Had'em\n \n E.E. Cummings"),
false,
},
{
cty.StringVal("oneliner"),
cty.NumberIntVal(4),
cty.StringVal("oneliner"),
false,
},
{
cty.StringVal(`#!/usr/bin/env bash
date
pwd`),
cty.NumberIntVal(4),
cty.StringVal("#!/usr/bin/env bash\n date\n pwd"),
false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("indent(%#v, %#v)", test.Spaces, test.String), func(t *testing.T) {
got, err := Indent(test.Spaces, test.String)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestReplace(t *testing.T) {
tests := []struct {
String cty.Value
Substr cty.Value
Replace cty.Value
Want cty.Value
Err bool
}{
{ // Regular search and replace
cty.StringVal("hello"),
cty.StringVal("hel"),
cty.StringVal("bel"),
cty.StringVal("bello"),
false,
},
{ // Search string doesn't match
cty.StringVal("hello"),
cty.StringVal("nope"),
cty.StringVal("bel"),
cty.StringVal("hello"),
false,
},
{ // Regular expression
cty.StringVal("hello"),
cty.StringVal("/l/"),
cty.StringVal("L"),
cty.StringVal("heLLo"),
false,
},
{
cty.StringVal("helo"),
cty.StringVal("/(l)/"),
cty.StringVal("$1$1"),
cty.StringVal("hello"),
false,
},
{ // Bad regexp
cty.StringVal("hello"),
cty.StringVal("/(l/"),
cty.StringVal("$1$1"),
cty.UnknownVal(cty.String),
true,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("replace(%#v, %#v, %#v)", test.String, test.Substr, test.Replace), func(t *testing.T) {
got, err := Replace(test.String, test.Substr, test.Replace)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestTitle(t *testing.T) {
tests := []struct {
String cty.Value
Want cty.Value
Err bool
}{
{
cty.StringVal("hello"),
cty.StringVal("Hello"),
false,
},
{
cty.StringVal("hello world"),
cty.StringVal("Hello World"),
false,
},
{
cty.StringVal(""),
cty.StringVal(""),
false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("title(%#v)", test.String), func(t *testing.T) {
got, err := Title(test.String)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
func TestTrimSpace(t *testing.T) {
tests := []struct {
String cty.Value
Want cty.Value
Err bool
}{
{
cty.StringVal(" hello "),
cty.StringVal("hello"),
false,
},
{
cty.StringVal(""),
cty.StringVal(""),
false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("trimspace(%#v)", test.String), func(t *testing.T) {
got, err := TrimSpace(test.String)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}

View File

@ -60,7 +60,7 @@ func (s *Scope) Functions() map[string]function.Function {
"format": stdlib.FormatFunc,
"formatlist": stdlib.FormatListFunc,
"indent": unimplFunc, // TODO
"index": unimplFunc, // TODO
"index": funcs.IndentFunc,
"join": funcs.JoinFunc,
"jsondecode": stdlib.JSONDecodeFunc,
"jsonencode": stdlib.JSONEncodeFunc,
@ -77,7 +77,7 @@ func (s *Scope) Functions() map[string]function.Function {
"min": stdlib.MinFunc,
"pathexpand": funcs.PathExpandFunc,
"pow": funcs.PowFunc,
"replace": unimplFunc, // TODO
"replace": funcs.ReplaceFunc,
"rsadecrypt": funcs.RsaDecryptFunc,
"sha1": funcs.Sha1Func,
"sha256": funcs.Sha256Func,
@ -89,9 +89,9 @@ func (s *Scope) Functions() map[string]function.Function {
"substr": stdlib.SubstrFunc,
"timestamp": funcs.TimestampFunc,
"timeadd": funcs.TimeAddFunc,
"title": unimplFunc, // TODO
"title": funcs.TitleFunc,
"transpose": unimplFunc, // TODO
"trimspace": unimplFunc, // TODO
"trimspace": funcs.TrimSpace,
"upper": stdlib.UpperFunc,
"urlencode": funcs.URLEncodeFunc,
"uuid": funcs.UUIDFunc,