Codegen for automation types & functions

This commit is contained in:
Denis Arh
2021-03-12 13:12:07 +01:00
parent d44e3474e8
commit d56f2fa82f
5 changed files with 485 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
package codegen
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/automation/types"
. "github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
"os"
"path"
"strings"
"text/template"
)
type (
// definitions are in one file
aFuncDefs struct {
Package string
Name string
Source string
Prefix string
outputDir string
// List of imports
// Used only by generated file and not pre-generated-user-file
Imports []string
Functions aFunctionSet
}
aFunctionSet []aFuncDef
aFuncDef struct {
Name string
Meta *types.FunctionMeta
Params aFuncParamSet
Results aFuncResultSet
}
aFuncParamSet []*aFuncParamDef
aFuncResultSet []*aFuncResultDef
aFuncParamDef struct {
Name string
Required bool
SetOf bool
Types []*aFuncParamTypeVarDef
Meta *types.ParamMeta
}
aFuncParamTypeVarDef struct {
WorkflowType string `yaml:"wf"`
GoType string `yaml:"go"`
Suffix string
}
aFuncResultDef struct {
Name string
Required bool
SetOf bool
WorkflowType string `yaml:"wf"`
GoType string `yaml:"go"`
Meta *types.ParamMeta
}
)
func procAutomationFunctions(mm ...string) (dd []*aFuncDefs, err error) {
for _, m := range mm {
f, err := os.Open(m)
if err != nil {
return nil, fmt.Errorf("%s read failed: %w", m, err)
}
defer f.Close()
var (
d = &aFuncDefs{
Package: "automation",
Source: m,
Name: path.Base(m),
outputDir: path.Dir(m),
}
)
d.Name = d.Name[:len(d.Name)-13]
if err := yaml.NewDecoder(f).Decode(d); err != nil {
return nil, err
}
dd = append(dd, d)
}
return
}
func (set *aFunctionSet) UnmarshalYAML(n *yaml.Node) error {
return Each(n, func(k *yaml.Node, v *yaml.Node) (err error) {
def := aFuncDef{Name: k.Value}
if err = v.Decode(&def); err != nil {
return err
}
*set = append(*set, def)
return nil
})
}
func (set *aFuncParamSet) UnmarshalYAML(n *yaml.Node) error {
return Each(n, func(k *yaml.Node, v *yaml.Node) (err error) {
def := aFuncParamDef{}
if k != nil {
def.Name = k.Value
}
*set = append(*set, &def)
return v.Decode(&def)
})
}
func (set *aFuncResultSet) UnmarshalYAML(n *yaml.Node) error {
return Each(n, func(k *yaml.Node, v *yaml.Node) (err error) {
def := aFuncResultDef{}
if k != nil {
def.Name = k.Value
}
*set = append(*set, &def)
return v.Decode(&def)
})
}
func genAutomationFunctions(tpl *template.Template, dd ...*aFuncDefs) (err error) {
var (
// Will only be generated if file does not exist previously
tplAFuncGen = tpl.Lookup("afunc.gen.go.tpl")
dst string
)
for _, d := range dd {
// Generic code, actions for every resource goes to a separated file
dst = path.Join(d.outputDir, path.Base(d.Source)[:strings.LastIndex(path.Base(d.Source), ".")]+".gen.go")
json.NewEncoder(os.Stdout).SetIndent("", " ")
err = goTemplate(dst, tplAFuncGen, d)
if err != nil {
return
}
}
return nil
}
+171
View File
@@ -0,0 +1,171 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
atypes "github.com/cortezaproject/corteza-server/automation/types"
"context"
"github.com/cortezaproject/corteza-server/pkg/expr"
{{- range .Imports }}
{{ normalizeImport . }}
{{- end }}
)
var (
{{ $.Name }} = &{{ $.Name }}Handler{}
)
func (h {{ $.Name }}Handler) register(reg func(*atypes.Function)) {
{{- range .Functions }}
reg(h.{{ export .Name }}())
{{- end }}
}
{{ range .Functions }}
{{ $REF := unexport $.Prefix $.Name .Name }}
{{ $ARGS := unexport $.Name .Name "Args" }}
{{ $RESULTS := unexport $.Name .Name "Results" }}
type (
{{ $ARGS }} struct {
{{ range .Params }}
{{ $NAME := .Name }}
has{{ export .Name }} bool
{{ if gt (len .Types) 1 }}
{{ export .Name }} interface{}
{{- range .Types }}
{{ $NAME }}{{ export .Suffix }} {{ .GoType }}
{{- end }}
{{- else -}}
{{ range .Types }}
{{ export $NAME }}{{ export .Suffix }} {{ .GoType }}
{{- end }}
{{- end -}}
{{- end }}
}
{{ if .Results }}
{{ unexport $.Name .Name }}Results struct {
{{ range .Results }}
{{ export .Name }} {{ .GoType }}
{{- end }}
}
{{- end }}
)
//
//
// expects implementation of {{ .Name }} function:
// func (h {{ $.Name }}) {{ .Name }}(ctx context.Context, args *{{ $ARGS }}) (results *{{ $RESULTS }}, err error) {
// return
// }
func (h {{ $.Name }}Handler) {{ export .Name }}() *atypes.Function {
return &atypes.Function{
Ref: {{ printf "%q" ( $REF ) }},
{{- if .Meta }}
Meta: &atypes.FunctionMeta{
{{- if .Meta.Short }}
Short: {{ printf "%q" .Meta.Short }},
{{- end }}
{{- if .Meta.Description }}
Description: {{ printf "%q" .Meta.Description }},
{{- end }}
{{- if .Meta.Visual }}
Visual: {{ printf "%#v" .Meta.Visual }},
{{- end }}
},
{{- end }}
Parameters: []*atypes.Param{
{{- range .Params }}
{
Name: {{ printf "%q" .Name }},
Types: []string{ {{ range .Types }}({{ .WorkflowType }}{}).Type(),{{ end }} },
{{- if .Required }}Required: true,{{ end }}
{{- if .SetOf }}SetOf: true,{{ end }}
{{- if .Meta }}
Meta: &atypes.ParamMeta{
{{- if .Meta.Label }}
Label: {{ printf "%#v" .Meta.Label }},
{{- end }}
{{- if .Meta.Description }}
Description: {{ printf "%#v" .Meta.Description }},
{{- end }}
{{- if .Meta.Visual }}
Visual: {{ printf "%#v" .Meta.Visual }},
{{- end }}
},
{{ end }}
},
{{- end }}
},
{{ if .Results }}
Results: []*atypes.Param{
{{ range .Results }}
atypes.NewParam({{ printf "%q" .Name }},
atypes.Types(&{{ .WorkflowType }}{}),
),
{{ end }}
},
{{ end }}
Handler: func(ctx context.Context, in expr.Vars) (out expr.Vars, err error) {
var (
args = &{{ $ARGS }}{
{{- range .Params }}
has{{ export .Name }}: in.Has({{ printf "%q" .Name }}),
{{- end }}
}
{{ if .Results }}
results *{{ $RESULTS }}
{{ end }}
)
if err = in.Decode(args); err != nil {
return
}
{{ range .Params }}
{{ $NAME := .Name }}
{{ if gt (len .Types) 1 }}
// Converting {{ export .Name }} to go type
switch casted := args.{{ export .Name }}.(type) {
{{- range .Types }}
case {{ .GoType }}:
args.{{ $NAME }}{{ export .Suffix }} = casted
{{- end -}}
}
{{- end }}
{{ end }}
{{ if .Results }}
if results, err = h.{{ .Name }}(ctx, args); err != nil {
return
}
out = expr.Vars{}
{{- range .Results }}
if out[{{ printf "%q" .Name }}], err = ({{ .WorkflowType }}{}).Cast(results.{{ export .Name }}); err != nil {
return nil, err
}
{{- end }}
return
{{- else }}
return out, h.{{ .Name }}(ctx, args)
{{- end }}
},
}
}
{{ end }}
+47
View File
@@ -0,0 +1,47 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// {{ .Source }}
{{ if .Imports }}
import (
{{- range .Imports }}
{{ normalizeImport . }}
{{- end }}
{{- if ne .Package "expr" }}
"github.com/cortezaproject/corteza-server/pkg/expr"
{{- end }}
)
{{ end }}
{{ $TypedValue := "TypedValue" }}
{{ if ne .Package "expr" }}
{{ $TypedValue = "expr.TypedValue" }}
{{ end }}
{{ range $exprType, $nativeType := .Types }}
// {{ $exprType }} is an expression type, wrapper for {{ $nativeType }} type
type {{ $exprType }} struct{ value {{ $nativeType }} }
// New{{ $exprType }} creates new instance of {{ $exprType }} expression type
func New{{ $exprType }}(new interface{}) ({{ $TypedValue }}, error) {
t := &{{ $exprType }}{}
return t, t.Set(new)
}
// Returns underlying value on {{ $exprType }}
func (t {{ $exprType }}) Get() interface{} { return t.value }
// Returns type name
func ({{ $exprType }}) Type() string { return "{{ $exprType }}" }
// Casts value to {{ $nativeType }}
func ({{ $exprType }}) Cast(value interface{}) ({{ $TypedValue }}, error) { return New{{ $exprType }}(value) }
{{ end }}
+38
View File
@@ -47,6 +47,12 @@ func Proc() {
typeSrc []string
typeDefs []*typesDef
// workaround because
// filepath.Join merges "*","*" into "**" instead of "*/*"
exprTypeSrcPath = filepath.Join("*"+string(filepath.Separator)+"*", "expr_types.yaml")
exprTypeSrc []string
exprTypeDefs []*exprTypesDef
restSrcPath = filepath.Join("*", "rest.yaml")
restSrc []string
restDefs []*restDef
@@ -59,6 +65,10 @@ func Proc() {
optionSrc []string
optionDefs []*optionsDef
aFuncsSrcPath = filepath.Join("*", "automation", "*_handler.yaml")
aFuncsSrc []string
aFuncsDefs []*aFuncDefs
tpls *template.Template
tplBase = template.New("").
Funcs(map[string]interface{}{
@@ -142,6 +152,9 @@ func Proc() {
typeSrc = glob(typeSrcPath)
output("loaded %d type definitions from %s\n", len(typeSrc), typeSrcPath)
exprTypeSrc = glob(exprTypeSrcPath)
output("loaded %d exprType definitions from %s\n", len(exprTypeSrc), exprTypeSrcPath)
restSrc = glob(restSrcPath)
output("loaded %d rest definitions from %s\n", len(restSrc), restSrcPath)
@@ -151,6 +164,9 @@ func Proc() {
optionSrc = glob(optionSrcPath)
output("loaded %d option definitions from %s\n", len(optionSrc), optionSrcPath)
aFuncsSrc = glob(aFuncsSrcPath)
output("loaded %d function definitions from %s\n", len(aFuncsSrc), aFuncsSrcPath)
if watchChanges {
if watcher != nil {
watcher.Close()
@@ -163,9 +179,11 @@ func Proc() {
fileList = append(fileList, actionSrc...)
fileList = append(fileList, eventSrc...)
fileList = append(fileList, typeSrc...)
fileList = append(fileList, exprTypeSrc...)
fileList = append(fileList, restSrc...)
fileList = append(fileList, storeSrc...)
fileList = append(fileList, optionSrc...)
fileList = append(fileList, aFuncsSrc...)
for _, d := range fileList {
handleError(watcher.Add(d))
@@ -211,6 +229,16 @@ func Proc() {
return
}
if exprTypeDefs, err = procExprTypes(exprTypeSrc...); err == nil {
if genCode {
err = genExprTypes(tpls, exprTypeDefs...)
}
}
if outputErr(err, "failed to process expr types:\n") {
return
}
if restDefs, err = procRest(restSrc...); err == nil {
if genCode {
err = genRest(tpls, restDefs...)
@@ -245,6 +273,16 @@ func Proc() {
return
}
if aFuncsDefs, err = procAutomationFunctions(aFuncsSrc...); err == nil {
if genCode {
err = genAutomationFunctions(tpls, aFuncsDefs...)
}
}
if outputErr(err, "failed to process store:\n") {
return
}
}()
if !watchChanges {
+74
View File
@@ -0,0 +1,74 @@
package codegen
import (
"fmt"
"gopkg.in/yaml.v3"
"os"
"path"
"text/template"
)
type (
exprTypesDef struct {
// source file path
Source string
// outputDir
// dir where the source file is
outputDir string
Imports []string
Package string `yaml:"package"`
Types map[string]string `yaml:"types"`
}
)
func procExprTypes(mm ...string) (dd []*exprTypesDef, err error) {
dd = make([]*exprTypesDef, 0)
for _, m := range mm {
var (
d = &exprTypesDef{
Source: m,
outputDir: path.Dir(m),
Package: "types",
Types: make(map[string]string),
}
)
f, err := os.Open(m)
if err != nil {
return nil, fmt.Errorf("%s read failed: %w", m, err)
}
defer f.Close()
if err := yaml.NewDecoder(f).Decode(d); err != nil {
return nil, fmt.Errorf("%s decode failed: %w", m, err)
}
dd = append(dd, d)
}
return dd, nil
}
// Generates all type set files & accompanying tests
//
// generates 2 files per type definition
func genExprTypes(tpl *template.Template, dd ...*exprTypesDef) (err error) {
var (
typeGen = tpl.Lookup("expr_types.gen.go.tpl")
)
for _, d := range dd {
err = goTemplate(path.Join(d.outputDir, "expr_types.gen.go"), typeGen, d)
if err != nil {
return
}
}
return nil
}