Skip to content

Commit

Permalink
Introducing new functions for gotemplate #21
Browse files Browse the repository at this point in the history
  • Loading branch information
akranga committed Dec 30, 2022
1 parent 12614d4 commit 9fd7633
Show file tree
Hide file tree
Showing 2 changed files with 326 additions and 1 deletion.
186 changes: 185 additions & 1 deletion cmd/hub/lifecycle/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
gotemplate "text/template"

Expand Down Expand Up @@ -566,8 +568,190 @@ func bcryptStr(str string) (string, error) {
return string(bytes), nil
}

// Splits the string into a list of strings
// First argument is a string to split
// Second optional argument is a separator (default is space)
// Example:
// split "a b c" => ["a", "b", "c"]
// split "a-b-c", "-" => ["a", "b", "c"]
func split(args ...string) ([]string, error) {
if len(args) == 0 {
return nil, fmt.Errorf("split expects one or two arguments")
}
if len(args) == 1 {
return strings.Fields(args[0]), nil
}
return strings.Split(args[0], args[1]), nil
}

// Removes empty string from the list of strings
// Accepts variable arguments arguments (easier tolerate template nature):
//
// Example:
// compact "string1" (compatibility with parametersx)
// compact "string1" "string2" "string3"
// compact ["string1", "string2", "string3"]
func compact(args ...interface{}) ([]string, error) {
var results []string
for _, arg := range args {
a := reflect.ValueOf(arg)
if a.Kind() == reflect.Slice {
if a.Len() == 0 {
continue
}
ret := make([]interface{}, a.Len())
for i := 0; i < a.Len(); i++ {
ret[i] = a.Index(i).Interface()
}
res, _ := compact(ret...)
results = append(results, res...)
continue
}
if a.Kind() == reflect.String {
trimmed := strings.TrimSpace(a.String())
if trimmed == "" {
continue
}
results = append(results, trimmed)
continue
}
return nil, fmt.Errorf("Argument type %T not yet supported", arg)
}
return results, nil
}

// Joins the list of strings into a single string
// Last argument is a delimiter (default is space)
// Accepts variable arguments arguments (easier tolerate template nature)
//
// Example:
// join "string1" "string2" "delimiter"
// join ["string1", "string2"] "delimiter"
// join ["string1", "string2"]
// join "string1"
func join(args ...interface{}) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("join expects at least one argument")
}
var del string
if len(args) > 1 {
del = fmt.Sprintf("%v", args[len(args)-1])
args = args[:len(args)-1]
}
if del == "" {
del = " "
}

var result []string
for _, arg := range args {
a := reflect.ValueOf(arg)
if a.Kind() == reflect.Slice {
if a.Len() == 0 {
continue
}
for i := 0; i < a.Len(); i++ {
result = append(result, fmt.Sprintf("%v", a.Index(i).Interface()))
}
continue
}
if a.Kind() == reflect.String {
result = append(result, a.String())
continue
}
return "", fmt.Errorf("Argument type %T not yet supported", arg)
}

return strings.Join(result, del), nil
}

// Returns the first argument from list
//
// Example:
// first ["string1" "string2" "string3"] => "string1"
func first(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("first expects at least one argument")
}
return args[0], nil
}

// Converts the string into kubernetes acceptable name
// which consist of kebab lower case with alphanumeric characters.
// '.' is not allowed
//
// Arguments:
// First argument is a text to convert
// Second optional argument is a size of the name (default is 63)
// Third optional argument is a delimiter (default is '-')
func formatSubdomain(args ...interface{}) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("hostname expects at least one argument")
}
arg0 := reflect.ValueOf(args[0])
if arg0.Kind() != reflect.String {
return "", fmt.Errorf("hostname expects string as first argument")
}
text := strings.TrimSpace(arg0.String())
if text == "" {
return "", nil
}

size := 63
if len(args) > 1 {
arg1 := reflect.ValueOf(args[1])
if arg1.Kind() == reflect.Int {
size = int(reflect.ValueOf(args[1]).Int())
} else if arg1.Kind() == reflect.String {
size, _ = strconv.Atoi(arg1.String())
} else {
return "", fmt.Errorf("Argument type %T not yet supported", args[1])
}
}

var del = "-"
if len(args) > 2 {
del = fmt.Sprintf("%v", args[2])
}

var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
var matchNonAlphanumericEnd = regexp.MustCompile("[^a-zA-Z0-9]+$")
var matchNonLetterStart = regexp.MustCompile("^[^a-zA-Z]+")
var matchNonAnumericOrDash = regexp.MustCompile("[^a-zA-Z0-9-]+")
var matchTwoOrMoreDashes = regexp.MustCompile("-{2,}")

text = matchNonLetterStart.ReplaceAllString(text, "")
text = matchAllCap.ReplaceAllString(text, "${1}-${2}")
text = matchNonAnumericOrDash.ReplaceAllString(text, "-")
text = matchTwoOrMoreDashes.ReplaceAllString(text, "-")
text = strings.ToLower(text)
if len(text) > size {
text = text[:size]
}
text = matchNonAlphanumericEnd.ReplaceAllString(text, "")
if del != "-" {
text = strings.ReplaceAll(text, "-", del)
}
return text, nil
}

// Removes single or double or back quotes from the string
func unquote(str string) (string, error) {
result, err := strconv.Unquote(str)
if err != nil && err.Error() == "invalid syntax" {
return str, err
}
return result, err
}

var hubGoTemplateFuncMap = map[string]interface{}{
"bcrypt": bcryptStr,
"bcrypt": bcryptStr,
"split": split,
"compact": compact,
"join": join,
"first": first,
"formatSubdomain": formatSubdomain,
"unquote": unquote,
"uquote": unquote,
}

func processGo(content, filename, componentName string, kv map[string]interface{}) (string, error) {
Expand Down
141 changes: 141 additions & 0 deletions cmd/hub/lifecycle/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) 2022 EPAM Systems, Inc.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package lifecycle

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestSplit(t *testing.T) {
result, _ := split("")
assert.Equal(t, 0, len(result))

result, _ = split("a/b/c", "/")
expected := []string{"a", "b", "c"}
assert.EqualValues(t, expected, result)

result, _ = split("a b c")
assert.EqualValues(t, expected, result)
}

func TestCompact(t *testing.T) {
sample := []string{}
result, _ := compact(sample)
assert.Equal(t, 0, len(result))

sample = []string{"a", "b", "c"}
result, _ = compact(sample)
assert.EqualValues(t, []string{"a", "b", "c"}, result)

sample = []string{"a", "", "c"}
result, _ = compact(sample)
assert.EqualValues(t, []string{"a", "c"}, result)

result, _ = compact("abc")
assert.EqualValues(t, []string{"abc"}, result)

result, _ = compact("a", "", "c")
assert.EqualValues(t, []string{"a", "c"}, result)
}

func TestFirst(t *testing.T) {
sample := []string{}
result, _ := first(sample)
assert.Equal(t, "", result)

sample = []string{"a", "b", "c"}
result, _ = first(sample)
assert.Equal(t, "a", result)
}

func TestJoin(t *testing.T) {
sample := []string{}
result, _ := join(sample)
assert.Equal(t, "", result)

sample = []string{"a", "b", "c"}
result, _ = join(sample)
assert.Equal(t, "a b c", result)

sample = []string{"a", "b", "c"}
result, _ = join(sample, "/")
assert.Equal(t, "a/b/c", result)

result, _ = join("a", "b", "c", "/")
assert.Equal(t, "a/b/c", result)
}

func TestFormatSubdomain(t *testing.T) {
result, _ := formatSubdomain("")
assert.Equal(t, "", result)

result, _ = formatSubdomain("a")
assert.Equal(t, "a", result)

result, _ = formatSubdomain("a b")
assert.Equal(t, "a-b", result)

// dashes cannot repeat
result, _ = formatSubdomain("A B c")
assert.Equal(t, "a-b-c", result)

// cannot start and finish with dash
result, _ = formatSubdomain("--a b c--")
assert.Equal(t, "a-b-c", result)

// cannot start but can finish with digit
result, _ = formatSubdomain("12a3 b c4")
assert.Equal(t, "a3-b-c4", result)

// max length
result, _ = formatSubdomain("a b c", 3)
assert.Equal(t, "a-b", result)

// second param may be string
result, _ = formatSubdomain("a b c", "3")
assert.Equal(t, "a-b", result)

// max length and delimiter
result, _ = formatSubdomain("a b c", 3, "_")
assert.Equal(t, "a_b", result)
}

func TestUnquote(t *testing.T) {
result, _ := unquote("")
assert.Equal(t, "", result)

result, _ = unquote("a")
assert.Equal(t, "a", result)

result, _ = unquote("'a'")
assert.Equal(t, "a", result)

result, _ = unquote("\"a\"")
assert.Equal(t, "a", result)

result, _ = unquote("\"a")
assert.Equal(t, "\"a", result)

result, _ = unquote("a\"")
assert.Equal(t, "a\"", result)

result, err := unquote("'a")
assert.Equal(t, "'a", result)
assert.EqualError(t, err, "invalid syntax")

result, err = unquote("a'")
assert.Equal(t, "a'", result)
assert.EqualError(t, err, "invalid syntax")

result, _ = unquote("\"a'b\"")
assert.Equal(t, "a'b", result)

_, err = unquote("'a\"b'")
assert.EqualError(t, err, "invalid syntax")
}

0 comments on commit 9fd7633

Please sign in to comment.