3
0
corteza/pkg/expr/func_str.go
2021-11-02 18:40:51 +01:00

256 lines
5.4 KiB
Go

package expr
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"reflect"
"regexp"
"strings"
"github.com/spf13/cast"
"github.com/PaesslerAG/gval"
valid "github.com/asaskevich/govalidator"
)
func StringFunctions() []gval.Language {
return []gval.Language{
gval.Function("trim", strings.TrimSpace),
gval.Function("trimLeft", strings.TrimLeft),
gval.Function("trimRight", strings.TrimRight),
gval.Function("toLower", strings.ToLower),
gval.Function("toUpper", strings.ToUpper),
gval.Function("shortest", shortest),
gval.Function("longest", longest),
gval.Function("format", fmt.Sprintf),
gval.Function("title", title),
gval.Function("untitle", untitle),
gvalFunc("repeat", strings.Repeat),
gvalFunc("replace", strings.Replace),
gval.Function("isUrl", valid.IsURL),
gval.Function("isEmail", valid.IsEmail),
gval.Function("split", strings.Split),
gval.Function("join", join),
gval.Function("hasSubstring", hasSubstring),
gvalFunc("substring", substring),
gval.Function("hasPrefix", strings.HasPrefix),
gval.Function("hasSuffix", strings.HasSuffix),
gvalFunc("shorten", shorten),
gval.Function("camelize", camelize),
gval.Function("snakify", snakify),
gval.Function("match", match),
gval.Function("base64encode", base64encode),
}
}
// gvalFunc cast any nth number of float64 param to int
func gvalFunc(name string, fn interface{}) gval.Language {
return gval.Function(name, func(params ...interface{}) (out interface{}) {
in := make([]reflect.Value, len(params))
for i, param := range params {
if reflect.TypeOf(param).Kind() == reflect.Float64 {
param = cast.ToInt(param)
}
in[i] = reflect.ValueOf(param)
}
fun := reflect.ValueOf(fn)
res := fun.Call(in)
if len(res) > 0 {
out = cast.ToString(reflect.ValueOf(res[0]).Interface())
}
return
})
}
func shortest(f string, aa ...string) string {
for _, s := range aa {
if len(f) > len(s) {
f = s
}
}
return f
}
func longest(f string, aa ...string) string {
for _, s := range aa {
if len(f) < len(s) {
f = s
}
}
return f
}
// title works similarly as strings.ToTitle, with the expception
// of uppercasing only the first word in line
func title(s string) string {
split := strings.Split(s, " ")
return fmt.Sprintf("%s %s", strings.Title(split[0]), strings.Join(split[1:], " "))
}
// untitle works only on the first word in line
func untitle(s string) string {
split := strings.Split(s, " ")
first := strings.ToLower(split[0][:1]) + split[0][1:]
return fmt.Sprintf("%s %s", first, strings.Join(split[1:], " "))
}
func join(arr interface{}, sep string) (out string, err error) {
if arr == nil {
// If base is empty, nothing to do
return "", nil
} else if i, is := arr.([]string); is {
// If string slice, we are good to go
return strings.Join(i, sep), nil
} else if i, is := arr.([]interface{}); is {
// If slice of interfaces, we can try to cast them
var aux []string
aux, err = CastToStringSlice(i)
if err != nil {
return
}
return strings.Join(aux, sep), nil
} else if arr, err = toSlice(arr); err != nil {
return
}
// Make an aux string slice so the join operation can use it
stv, is := arr.([]TypedValue)
if !is {
return "", errors.New("could not cast array to string array")
}
aux := make([]string, len(stv))
for i, rv := range stv {
aux[i], err = CastToString(rv)
if err != nil {
return
}
}
return strings.Join(aux, sep), nil
}
// hasSubstring checks if a substring exists in original string
// use watchCase if need case sensitivity
func hasSubstring(s, substring string, watchCase bool) bool {
if watchCase {
return strings.Contains(s, substring)
}
return strings.Contains(strings.ToLower(s), strings.ToLower(substring))
}
// substring extracts a substring from original string
// specifying end value will not match till end of string
func substring(s string, start, end int) string {
if end == -1 {
end = len(s)
}
if start >= len(s) {
return ""
}
end++
if end > len(s) {
end = len(s)
}
return s[start:end]
}
// shorten trims by whole words or only chars by
// the specified amount
func shorten(s, t string, count int) string {
var joined string
if t == "char" {
if count > len(s) {
return ""
}
joined = s[:count]
} else {
fields := strings.Fields(s)
if len(fields) == 0 {
return ""
}
joined = strings.Join(fields[:count], " ")
}
reg, err := regexp.Compile("[^a-zA-Z0-9]$")
if err != nil {
return ""
}
joined = reg.ReplaceAllString(joined, "")
return joined + " …"
}
func camelize(s string) string {
return untitle(strings.Replace(strings.Title(s), " ", "", -1))
}
func snakify(s string) string {
return strings.ToLower(strings.Replace(strings.Title(s), " ", "_", -1))
}
func base64encode(s interface{}) string {
var (
buf bytes.Buffer
)
switch s.(type) {
case string:
s = []byte(s.(string))
case []byte:
s = s.([]byte)
case io.Reader:
buf := new(bytes.Buffer)
buf.ReadFrom(s.(io.Reader))
s = buf.Bytes()
default:
return ""
}
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
if _, err := encoder.Write(s.([]byte)); err != nil {
return ""
}
if err := encoder.Close(); err != nil {
return ""
}
return buf.String()
}
func match(s string, regex interface{}) (b bool, err error) {
var (
r *regexp.Regexp
)
switch v := regex.(type) {
case *regexp.Regexp:
r = v
case string:
if r, err = regexp.Compile(v); err != nil {
return
}
}
b = r.MatchString(s)
return
}