From 756c147579679963877d0d0afcececfb6e3b0d0f Mon Sep 17 00:00:00 2001 From: Syuparn Date: Fri, 19 Feb 2021 09:39:40 +0900 Subject: [PATCH] add test --- .github/workflows/test.yml | 24 +++++++ README.md | 2 + funcmap.go | 78 +++++++++++++++++++++ funcmap_test.go | 137 +++++++++++++++++++++++++++++++++++++ main.go | 60 ---------------- 5 files changed, 241 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 funcmap.go create mode 100644 funcmap_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..49b8694 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: GoTest + +on: [push] + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.15 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Build + run: go build + + - name: GoTest + run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... diff --git a/README.md b/README.md index 03b379d..eff74ed 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # tmplscript +![](https://github.com/Syuparn/tmplscript/workflows/GoTest/badge.svg?branch=master) + executable go-template command (like awk and jq!) # usage diff --git a/funcmap.go b/funcmap.go new file mode 100644 index 0000000..292647a --- /dev/null +++ b/funcmap.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + "reflect" + "sort" + "strings" + "text/template" + + "github.com/Masterminds/sprig" +) + +func funcMap() template.FuncMap { + // NOTE: FuncMap is for html/template, TxtFuncMap is for text/template + funcMap := sprig.TxtFuncMap() + + // add (meta-)functions to describe functions + funcMap["searchFunc"] = searchFunc(funcMap) + funcMap["docFunc"] = docFunc(funcMap) + return funcMap +} + +func searchFunc(funcMap template.FuncMap) func(string) []string { + return func(prefix string) []string { + keys := []string{} + for k := range funcMap { + if strings.HasPrefix(k, prefix) { + keys = append(keys, k) + } + } + + // sort alphabetically + sort.Strings(keys) + + return keys + } +} + +func docFunc(funcMap template.FuncMap) func(string) string { + return func(name string) string { + f, ok := funcMap[name] + if !ok { + return fmt.Sprintf("function %s is not defined (or embedded)", name) + } + + rt := reflect.TypeOf(f) + if rt.Kind() != reflect.Func { + return fmt.Sprintf("%s is not a function", name) + } + + paramTypes := []string{} + for i := 0; i < rt.NumIn(); i++ { + paramTypes = append(paramTypes, rt.In(i).String()) + } + + if rt.IsVariadic() { + paramTypes[len(paramTypes)-1] = toVariadic(paramTypes[len(paramTypes)-1]) + } + + returnTypes := []string{} + for i := 0; i < rt.NumOut(); i++ { + returnTypes = append(returnTypes, rt.Out(i).String()) + } + + paramList := strings.Join(paramTypes, " ") + returnList := strings.Join(returnTypes, " ") + + if paramList == "" { + return fmt.Sprintf("%s -> (%s)", name, returnList) + } + return fmt.Sprintf("%s %s -> (%s)", name, paramList, returnList) + } +} + +func toVariadic(typeStr string) string { + // replace prefix `[]` to `...` in variadic parameter + return "..." + strings.TrimPrefix(typeStr, "[]") +} diff --git a/funcmap_test.go b/funcmap_test.go new file mode 100644 index 0000000..f0f8cd5 --- /dev/null +++ b/funcmap_test.go @@ -0,0 +1,137 @@ +package main + +import ( + "fmt" + "testing" + "text/template" +) + +func TestSearchFunc(t *testing.T) { + tests := []struct { + title string + funcMap template.FuncMap + key string + expected []string + }{ + { + "not found", + map[string]interface{}{}, + "", + []string{}, + }, + { + "empty key gets all functions", + map[string]interface{}{ + "a": func() {}, + "b": func() {}, + }, + "", + []string{"a", "b"}, + }, + { + "key gets all functions whose key starts with it", + map[string]interface{}{ + "fail": func() {}, + "find": func() {}, + "finish": func() {}, + "get": func() {}, + }, + "fi", + []string{"find", "finish"}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(fmt.Sprintf(tt.title), func(t *testing.T) { + actual := searchFunc(tt.funcMap)(tt.key) + + if len(actual) != len(tt.expected) { + t.Fatalf("wrong length. got %d, expected %d", len(actual), len(tt.expected)) + } + + for i, elem := range actual { + if elem != tt.expected[i] { + t.Errorf("actual[%d] is wrong. got %s, expected %s", + i, elem, tt.expected[i]) + } + } + }) + } +} + +func TestDocFunc(t *testing.T) { + tests := []struct { + title string + funcMap template.FuncMap + key string + expected string + }{ + { + "arity 0, return 0", + map[string]interface{}{ + "fail": func() {}, + }, + "fail", + "fail -> ()", + }, + { + "arity 1, return 1", + map[string]interface{}{ + "itoa": func(i int) string { return fmt.Sprint(i) }, + }, + "itoa", + "itoa int -> (string)", + }, + { + "arity 3, return 1", + map[string]interface{}{ + "myTernary": func(i int, s string, f float64) bool { return false }, + }, + "myTernary", + "myTernary int string float64 -> (bool)", + }, + { + "arity 1, return 3", + map[string]interface{}{ + "myFunc": func(i int) (string, bool, error) { return "", true, nil }, + }, + "myFunc", + "myFunc int -> (string bool error)", + }, + { + "variadic", + map[string]interface{}{ + "myFunc": func(i int, strs ...string) {}, + }, + "myFunc", + "myFunc int ...string -> ()", + }, + { + "not found", + map[string]interface{}{}, + "myFunc", + "function myFunc is not defined (or embedded)", + }, + { + "not function", + map[string]interface{}{ + "val": 1, + }, + "val", + "val is not a function", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(fmt.Sprintf(tt.title), func(t *testing.T) { + actual := docFunc(tt.funcMap)(tt.key) + + if actual != tt.expected { + t.Errorf("wrong output. expected `%s`, got `%s`", + tt.expected, actual) + } + }) + } +} diff --git a/main.go b/main.go index 9cb2842..406cf8a 100644 --- a/main.go +++ b/main.go @@ -7,11 +7,7 @@ import ( "fmt" "io/ioutil" "os" - "reflect" - "strings" "text/template" - - "github.com/Masterminds/sprig" ) var ( @@ -117,59 +113,3 @@ func runREPLMode() { func diffStr(newStr, previousStr string) string { return newStr[len(previousStr):] } - -func funcMap() template.FuncMap { - // NOTE: FuncMap is for html/template, TxtFuncMap is for text/template - funcMap := sprig.TxtFuncMap() - - // add (meta-)functions to describe functions - funcMap["searchFunc"] = searchFunc(funcMap) - funcMap["docFunc"] = docFunc(funcMap) - return funcMap -} - -func searchFunc(funcMap template.FuncMap) func(string) []string { - return func(prefix string) []string { - keys := []string{} - for k := range funcMap { - if strings.HasPrefix(k, prefix) { - keys = append(keys, k) - } - } - - return keys - } -} - -func docFunc(funcMap template.FuncMap) func(string) string { - return func(name string) string { - f, ok := funcMap[name] - if !ok { - return fmt.Sprintf("function %s is not defined (or embedded)", name) - } - - rt := reflect.TypeOf(f) - if rt.Kind() != reflect.Func { - return fmt.Sprintf("%s is not a function", name) - } - - paramTypes := []string{} - for i := 0; i < rt.NumIn(); i++ { - paramTypes = append(paramTypes, rt.In(i).String()) - } - - // add `...` to variadic parameter - if rt.IsVariadic() { - paramTypes[len(paramTypes)-1] = "..." + paramTypes[len(paramTypes)-1] - } - - returnTypes := []string{} - for i := 0; i < rt.NumOut(); i++ { - returnTypes = append(returnTypes, rt.Out(i).String()) - } - - paramList := strings.Join(paramTypes, " ") - returnList := strings.Join(returnTypes, " ") - return fmt.Sprintf("%s %s -> (%s)", name, paramList, returnList) - } -}