diff --git a/.gitignore b/.gitignore index a8bd7ea7..1d71084c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ tmp/ *WIP *_stringer.go bindata/*.go +generated_funcs.go +generated_funcs_test.go mgmt mgmt.static # crossbuild artifacts diff --git a/Makefile b/Makefile index 523769b5..6532ac8f 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ # along with this program. If not, see . SHELL = /usr/bin/env bash -.PHONY: all art cleanart version program lang path deps run race bindata generate build build-debug crossbuild clean test gofmt yamlfmt format docs rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms copr tag release +.PHONY: all art cleanart version program lang path deps run race bindata generate build build-debug crossbuild clean test gofmt yamlfmt format docs rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms copr tag release funcgen .SILENT: clean bindata # a large amount of output from this `find`, can cause `make` to be much slower! @@ -148,7 +148,7 @@ build-debug: $(PROGRAM) # extract os and arch from target pattern GOOS=$(firstword $(subst -, ,$*)) GOARCH=$(lastword $(subst -, ,$*)) -build/mgmt-%: $(GO_FILES) | bindata lang +build/mgmt-%: $(GO_FILES) | bindata lang funcgen @echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..." @# reassigning GOOS and GOARCH to make build command copy/pastable @# go 1.10 requires specifying the package for ldflags @@ -166,6 +166,8 @@ clean: ## clean things up $(MAKE) --quiet -C bindata clean $(MAKE) --quiet -C lang/funcs clean $(MAKE) --quiet -C lang clean + rm -f lang/funcs/core/generated_funcs.go || true + rm -f lang/funcs/core/generated_funcs_test.go || true [ ! -e $(PROGRAM) ] || rm $(PROGRAM) rm -f *_stringer.go # generated by `go generate` rm -f *_mock.go # generated by `go generate` @@ -408,4 +410,11 @@ help: ## show this help screen awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' @echo '' +funcgen: lang/funcs/core/generated_funcs_tests.go lang/funcs/core/generated_funcs.go + +lang/funcs/core/generated_funcs_tests.go: lang/funcs/core/generated_funcs.go + +lang/funcs/core/generated_funcs.go: lang/golang2mgmt/*.go lang/funcs/core/golang2mgmt.yaml lang/golang2mgmt/templates/*.tpl + go run lang/golang2mgmt/*.go + # vim: ts=8 diff --git a/lang/funcs/core/golang2mgmt.yaml b/lang/funcs/core/golang2mgmt.yaml new file mode 100644 index 00000000..62cd2e43 --- /dev/null +++ b/lang/funcs/core/golang2mgmt.yaml @@ -0,0 +1,55 @@ +# This file is to be used by github.com/purpleidea/mgmt/lang/golang2mgmt +# to generate mgmt functions +functions: +- mgmtName: to_upper + mgmtPackage: strings + help: turns a string to uppercase. + goPackage: strings + goFunc: ToUpper + args: [{name: a, type: string}] + return: [{type: string}] + tests: + - args: [{type: string, value: "Hello"}] + return: [{type: string, value: "HELLO"}] + - args: [{type: string, value: "HELLO 22"}] + return: [{type: string, value: "HELLO 22"}] +- mgmtName: trim + mgmtPackage: strings + help: returns a slice of the string s with all leading and trailing Unicode code points contained in cutset removed.. + goPackage: strings + goFunc: Trim + args: [{name: s, type: string}, {name: cutset, type: string}] + return: [{type: string}] + tests: + - args: [{type: string, value: "??Hello.."}, {type: string, value: "?."}] + return: [{type: string, value: "Hello"}] +- mgmtName: trim_left + mgmtPackage: strings + help: returns a slice of the string s with all leading Unicode code points contained in cutset removed. + goPackage: strings + goFunc: TrimLeft + args: [{name: s, type: string}, {name: cutset, type: string}] + return: [{type: string}] + tests: + - args: [{type: string, value: "??Hello.."}, {type: string, value: "?."}] + return: [{type: string, value: "Hello.."}] +- mgmtName: trim_space + mgmtPackage: strings + help: returns a slice of the string s, with all leading and trailing white space removed, as defined by Unicode. + goPackage: strings + goFunc: TrimSpace + args: [{name: s, type: string}] + return: [{type: string}] + tests: + - args: [{type: string, value: "Hello 2 "}] + return: [{type: string, value: "Hello 2"}] +- mgmtName: trim_right + mgmtPackage: strings + help: returns a slice of the string s with all trailing Unicode code points contained in cutset removed. + goPackage: strings + goFunc: TrimRight + args: [{name: s, type: string}, {name: cutset, type: string}] + return: [{type: string}] + tests: + - args: [{type: string, value: "??Hello.."}, {type: string, value: "?."}] + return: [{type: string, value: "??Hello"}] diff --git a/lang/golang2mgmt/config.go b/lang/golang2mgmt/config.go new file mode 100644 index 00000000..2921d4b8 --- /dev/null +++ b/lang/golang2mgmt/config.go @@ -0,0 +1,66 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package main + +import ( + "fmt" +) + +type config struct { + Functions functions `yaml:"functions"` +} + +type functions []function + +type testarg struct { + Name string `yaml:"name,omitempty"` + Type string `yaml:"type"` + Value string `yaml:"value"` +} + +type arg struct { + Name string `yaml:"name,omitempty"` + Type string `yaml:"type"` +} + +// ToMcl prints the arg signature as expected by mcl. +func (obj *arg) ToMcl() (string, error) { + if obj.Type == "string" { + if obj.Name != "" { + return fmt.Sprintf("%s str", obj.Name), nil + } + return "str", nil + } + return "", fmt.Errorf("cannot convert %v to mcl", obj) +} + +// ToGo prints the arg signature as expected by golang. +func (obj *arg) ToGo() (string, error) { + if obj.Type == "string" { + return "Str", nil + } + return "", fmt.Errorf("cannot convert %v to go", obj) +} + +// ToTestInput prints the arg signature as expected by tests. +func (obj *arg) ToTestInput() (string, error) { + if obj.Type == "string" { + return fmt.Sprintf("&types.StrValue{V: %s}", obj.Name), nil + } + return "", fmt.Errorf("cannot convert %v to test input", obj) +} diff --git a/lang/golang2mgmt/func.go b/lang/golang2mgmt/func.go new file mode 100644 index 00000000..2c34287e --- /dev/null +++ b/lang/golang2mgmt/func.go @@ -0,0 +1,181 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "text/template" +) + +type function struct { + MgmtPackage string `yaml:"mgmtPackage"` + MgmtName string `yaml:"mgmtName"` + Help string `yaml:"help"` + GoPackage string `yaml:"goPackage"` + GoFunc string `yaml:"goFunc"` + Args []arg `yaml:"args"` + Return []arg `yaml:"return"` + Tests []functest `yaml:"tests"` +} + +type functest struct { + Args []testarg `yaml:"args"` + Expect []testarg `yaml:"return"` +} + +type templateInput struct { + Func function + MgmtPackage string +} + +func parseFuncs(c config, path, templates string) error { + templateFiles, err := filepath.Glob(templates) + if err != nil { + return err + } + for _, tpl := range templateFiles { + log.Printf("Generating %s", tpl) + err = generateTemplate(c, path, tpl) + if err != nil { + return err + } + } + return nil +} + +func generateTemplate(c config, path, templateFile string) error { + log.Printf("Reading %s", templateFile) + basename := filepath.Base(templateFile) + tplFile, err := ioutil.ReadFile(templateFile) + if err != nil { + return err + } + t, err := template.New(basename).Parse(string(tplFile)) + if err != nil { + return err + } + finalName := strings.TrimSuffix(basename, ".tpl") + finalPath := filepath.Join(path, finalName) + log.Printf("Writing %s", finalPath) + finalFile, err := os.Create(finalPath) + if err != nil { + return err + } + if err = t.Execute(finalFile, c); err != nil { + return err + } + return nil +} + +// MakeGoArgs translates the func args to go args. +func (obj *function) MakeGoArgs() (string, error) { + var args []string + for i, a := range obj.Args { + gol, err := a.ToGo() + if err != nil { + return "", err + } + args = append(args, fmt.Sprintf("input[%d].%s()", i, gol)) + } + return strings.Join(args, ", "), nil +} + +// Signature generates the mcl signature of the function. +func (obj *function) Signature() (string, error) { + var args []string + for _, a := range obj.Args { + mcl, err := a.ToMcl() + if err != nil { + return "", err + } + args = append(args, mcl) + } + var returns []string + for _, a := range obj.Return { + mcl, err := a.ToMcl() + if err != nil { + return "", err + } + returns = append(returns, mcl) + } + return fmt.Sprintf("func(%s) %s", strings.Join(args, ", "), returns[0]), nil +} + +// MakeGoReturn returns the golang signature of the return. +func (obj *function) MakeGoReturn() (string, error) { + return obj.Return[0].ToGo() +} + +// MakeGoTypeReturn returns the mcl signature of the return. +func (obj *function) MakeGoTypeReturn() string { + return obj.Return[0].Type +} + +// MakeTestSign returns the signature of the test. +func (obj *function) MakeTestSign() string { + var args []string + for i, a := range obj.Args { + var nextSign string + if i+1 < len(obj.Args) { + nextSign = obj.Args[i+1].Type + } else { + nextSign = obj.MakeGoTypeReturn() + } + if nextSign == a.Type { + args = append(args, a.Name) + } else { + args = append(args, fmt.Sprintf("%s %s", a.Name, a.Type)) + } + } + args = append(args, fmt.Sprintf("expected %s", obj.MakeGoTypeReturn())) + return strings.Join(args, ", ") +} + +// TestInput generated a string that can be passed as test input. +func (obj *function) TestInput() (string, error) { + var values []string + for _, i := range obj.Args { + tti, err := i.ToTestInput() + if err != nil { + return "", err + } + values = append(values, tti) + } + return fmt.Sprintf("[]types.Value{%s}", strings.Join(values, ", ")), nil +} + +// MakeTestArgs generates a string that can be passed a test arguments. +func (obj *functest) MakeTestArgs() string { + var values []string + for _, i := range obj.Args { + if i.Type == "string" { + values = append(values, fmt.Sprintf(`"%s"`, i.Value)) + } + } + for _, i := range obj.Expect { + if i.Type == "string" { + values = append(values, fmt.Sprintf(`"%s"`, i.Value)) + } + } + return strings.Join(values, ", ") +} diff --git a/lang/golang2mgmt/main.go b/lang/golang2mgmt/main.go new file mode 100644 index 00000000..3973d7e1 --- /dev/null +++ b/lang/golang2mgmt/main.go @@ -0,0 +1,41 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package main + +import ( + "flag" + "log" +) + +var ( + pkg = flag.String("package", "lang/funcs/core", "path to the package") + filename = flag.String("filename", "golang2mgmt.yaml", "path to the config") + templates = flag.String("templates", "lang/golang2mgmt/templates/*.tpl", "path to the templates") +) + +func main() { + flag.Parse() + if *pkg == "" { + log.Fatalf("No package passed!") + } + + err := parsePkg(*pkg, *filename, *templates) + if err != nil { + log.Fatal(err) + } +} diff --git a/lang/funcs/core/strings/trim_space_func.go b/lang/golang2mgmt/pkg.go similarity index 64% rename from lang/funcs/core/strings/trim_space_func.go rename to lang/golang2mgmt/pkg.go index 64c7e3a0..1b8c3854 100644 --- a/lang/funcs/core/strings/trim_space_func.go +++ b/lang/golang2mgmt/pkg.go @@ -15,22 +15,31 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package corestrings +package main import ( - "strings" + "io/ioutil" + "log" + "path/filepath" - "github.com/purpleidea/mgmt/lang/funcs/simple" - "github.com/purpleidea/mgmt/lang/types" + yaml "gopkg.in/yaml.v2" ) -func init() { - simple.ModuleRegister(moduleName, "trim_space", &types.FuncValue{ - T: types.NewType("func(a str) str"), - V: func(input []types.Value) (types.Value, error) { - return &types.StrValue{ - V: strings.TrimSpace(input[0].Str()), - }, nil - }, - }) +func parsePkg(path, filename, templates string) error { + var c config + filePath := filepath.Join(path, filename) + log.Printf("Reading %s", filePath) + cfgFile, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + err = yaml.UnmarshalStrict(cfgFile, &c) + if err != nil { + return err + } + err = parseFuncs(c, path, templates) + if err != nil { + return err + } + return nil } diff --git a/lang/funcs/core/strings/to_upper_func.go b/lang/golang2mgmt/templates/generated_funcs.go.tpl similarity index 66% rename from lang/funcs/core/strings/to_upper_func.go rename to lang/golang2mgmt/templates/generated_funcs.go.tpl index 06c149c5..0864518a 100644 --- a/lang/funcs/core/strings/to_upper_func.go +++ b/lang/golang2mgmt/templates/generated_funcs.go.tpl @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package corestrings +package core import ( "strings" @@ -25,15 +25,17 @@ import ( ) func init() { - simple.ModuleRegister(moduleName, "to_upper", &types.FuncValue{ - T: types.NewType("func(a str) str"), - V: ToUpper, +{{ range $i, $func := .Functions }} simple.ModuleRegister("{{$func.MgmtPackage}}", "{{$func.MgmtName}}", &types.FuncValue{ + T: types.NewType("{{$func.Signature}}"), + V: {{$func.GoFunc}}, }) +{{ end }} } - -// ToUpper turns a string to uppercase. -func ToUpper(input []types.Value) (types.Value, error) { - return &types.StrValue{ - V: strings.ToUpper(input[0].Str()), +{{ range $i, $func := .Functions }} +// {{$func.GoFunc}} {{$func.Help}} +func {{$func.GoFunc}}(input []types.Value) (types.Value, error) { + return &types.{{$func.MakeGoReturn}}Value{ + V: {{$func.GoPackage}}.{{$func.GoFunc}}({{$func.MakeGoArgs}}), }, nil } +{{ end -}} diff --git a/lang/funcs/core/strings/to_upper_func_test.go b/lang/golang2mgmt/templates/generated_funcs_test.go.tpl similarity index 66% rename from lang/funcs/core/strings/to_upper_func_test.go rename to lang/golang2mgmt/templates/generated_funcs_test.go.tpl index 77a1c7a6..e4000337 100644 --- a/lang/funcs/core/strings/to_upper_func_test.go +++ b/lang/golang2mgmt/templates/generated_funcs_test.go.tpl @@ -15,30 +15,27 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package corestrings +package core import ( "testing" "github.com/purpleidea/mgmt/lang/types" ) - -func testToUpper(t *testing.T, input, expected string) { - inputStr := &types.StrValue{V: input} - value, err := ToUpper([]types.Value{inputStr}) +{{ range $i, $func := .Functions }} +func test{{$func.GoFunc}}(t *testing.T, {{$func.MakeTestSign}}) { + value, err := {{$func.GoFunc}}({{$func.TestInput}}) if err != nil { t.Error(err) return } - if value.Str() != expected { - t.Errorf("Invalid output, expected %s, got %s", expected, value.Str()) + if value.{{$func.MakeGoReturn}}() != expected { + t.Errorf("invalid output, expected %s, got %s", expected, value.{{$func.MakeGoReturn}}()) } } - -func TestToUpperSimple(t *testing.T) { - testToUpper(t, "Hello", "HELLO") -} - -func TestToUpperSameString(t *testing.T) { - testToUpper(t, "HELLO 22", "HELLO 22") +{{ range $index, $test := $func.Tests }} +func Test{{$func.GoFunc}}{{$index}}(t *testing.T) { + test{{$func.GoFunc}}(t, {{.MakeTestArgs}}) } +{{ end -}} +{{ end -}}