From f42761a7f92188c7de3e8c67332b416e6aef1bc1 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 30 Jul 2024 14:14:13 +0200 Subject: [PATCH] Add a generator for golang --- .gitignore | 1 + go/cmd/declarations/declarations.go | 180 ++++++++++++++++++++++++++ go/generate.go | 3 + go/go.mod | 4 + go/go.sum | 7 + go/pkg/qtag/builder.go | 72 +++++++++++ go/pkg/qtag/builder_test.go | 15 +++ go/pkg/qtag/declarations/dbt.yaml | 2 - go/pkg/qtag/declarations/sundeck.yaml | 10 ++ 9 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 go/cmd/declarations/declarations.go create mode 100644 go/generate.go create mode 100644 go/pkg/qtag/builder.go create mode 100644 go/pkg/qtag/builder_test.go diff --git a/.gitignore b/.gitignore index a3f9b66..8d6f1ec 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ js/node_modules js/yarn-error.log **/*.jar **/gen/ +**/generated.go diff --git a/go/cmd/declarations/declarations.go b/go/cmd/declarations/declarations.go new file mode 100644 index 0000000..96aec04 --- /dev/null +++ b/go/cmd/declarations/declarations.go @@ -0,0 +1,180 @@ +// generate.go +package main + +import ( + "encoding/json" + "fmt" + "go/format" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/goccy/go-yaml" +) + +// Field represents a field in the YAML file +type Field struct { + Name string `yaml:"name"` + Type string `yaml:"type"` +} + +// Config represents the structure of the YAML file +type Config struct { + Fields []Field `yaml:"fields"` + Name string `yaml:"name"` + Identifier map[string]any `yaml:"identifier"` +} + +// MethodData represents data needed to generate a method +type MethodData struct { + ClassName string + MethodName string + Name string +} + +// ClassData represents data needed to generate a class +type ClassData struct { + ClassName string + Name string + Identifier string +} + +const structTemplate = ` +type {{.ClassName}} struct { + Builder + name string + identifier map[string]any + values map[string]any +} + +func New{{.ClassName}}() *{{.ClassName}} { + x := {{.ClassName}}{} + x.init() + return &x +} + +func (c *{{.ClassName}}) init() { +c.name = "{{.Name}}" +c.identifier= make(map[string]any) +json.Unmarshal([]byte("{{.Identifier}}"), &c.identifier) +c.values = make(map[string]any) +} + +func (c *{{.ClassName}}) Format() (string, error) { + return format(c.name, c.identifier, c.values) +} + +func (c *{{.ClassName}}) UnknownValue(name string, value any) { +c.values[name] = value +} +` + +// Template for generating methods +const methodTemplate = ` + +func (c *{{.ClassName}}) {{.MethodName}}(value any) { + c.values["{{.Name}}"] = value +} +` + +func CreateFile(path string) (*strings.Builder, error) { + yamlFile, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var config Config + err = yaml.Unmarshal(yamlFile, &config) + if err != nil { + return nil, err + } + b, _ := json.Marshal(config.Identifier) + + tmplM, err := template.New("method").Parse(methodTemplate) + if err != nil { + return nil, err + } + tmplC, err := template.New("class").Parse(structTemplate) + if err != nil { + return nil, err + } + + c := strings.Builder{} + escapedIdentifier := strings.ReplaceAll(string(b), `"`, `\"`) + cData := ClassData{ + ClassName: capitalize(config.Name), + Name: config.Name, + Identifier: escapedIdentifier, + } + err = tmplC.Execute(&c, cData) + if err != nil { + return nil, err + } + + // Iterate through fields and generate methods + for _, field := range config.Fields { + data := MethodData{ + ClassName: capitalize(config.Name), + MethodName: "Add" + capitalize(removeDash(field.Name)), + Name: removeDash(field.Name), + } + err = tmplM.Execute(&c, data) + if err != nil { + return nil, err + } + } + return &c, nil +} + +func main() { + // Read and parse the YAML file + yamlDir := "pkg/qtag/declarations" + files, err := ioutil.ReadDir(yamlDir) + if err != nil { + panic(err) + } + + c := strings.Builder{} + c.WriteString("package qtag\nimport (\n\t\"encoding/json\"\n)\n\n") + for _, f := range files { + var s *strings.Builder + s, err = CreateFile(filepath.Join(yamlDir, f.Name())) + if err != nil { + panic(err) + } + c.WriteString(s.String()) + } + + // Create the generated file + f, err := os.Create("pkg/qtag/generated.go") + if err != nil { + panic(err) + } + defer f.Close() + + formattedCode, err := format.Source([]byte(c.String())) + if err != nil { + panic(err) + } + f.Write(formattedCode) + fmt.Println("example/generated.go has been generated") +} + +// capitalize capitalizes the first letter of a string +func capitalize(s string) string { + if len(s) == 0 { + return "" + } + return string(s[0]-32) + s[1:] +} + +func removeDash(s string) string { + if len(s) == 0 { + return "" + } + if strings.Contains(s, "-") { + return strings.Replace(s, "-", "__", -1) + } + return s +} diff --git a/go/generate.go b/go/generate.go new file mode 100644 index 0000000..ab3450a --- /dev/null +++ b/go/generate.go @@ -0,0 +1,3 @@ +package main + +//go:generate go run cmd/declarations/declarations.go diff --git a/go/go.mod b/go/go.mod index 73c3c4b..be7f8d1 100644 --- a/go/go.mod +++ b/go/go.mod @@ -10,12 +10,16 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.10.0 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.12 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go/go.sum b/go/go.sum index 7dc409a..5bac306 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,6 +1,8 @@ github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -21,11 +23,14 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -61,5 +66,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= diff --git a/go/pkg/qtag/builder.go b/go/pkg/qtag/builder.go new file mode 100644 index 0000000..e452f18 --- /dev/null +++ b/go/pkg/qtag/builder.go @@ -0,0 +1,72 @@ +package qtag + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +type Builder interface { + init(name string) +} + +func format(name string, identifier map[string]any, values map[string]any) (string, error) { + modifiedValues := make(map[string]any) + for k, v := range values { + if strings.Contains(k, "__") { + modifiedValues[strings.ReplaceAll(k, "__", "-")] = v + } else { + modifiedValues[k] = v + } + } + if f, ok := identifier["fields"]; ok { + if m, ok := f.(map[string]any); ok { + for k, v := range m { + modifiedValues[k] = v + } + b, err := json.Marshal(modifiedValues) + if err != nil { + return "", err + } + return string(b), nil + } + return "", errors.New("field 'fields' should be a map") + } + if f, ok := identifier["prefix"]; ok { + b, err := json.Marshal(modifiedValues) + if err != nil { + return "", err + } + return fmt.Sprintf("%s %s", f, string(b)), nil + } + return "", fmt.Errorf("unknown qtag format") +} + +type UnknownQtag struct { + Builder + name string + identifier map[string]any + values map[string]any +} + +func NewUnknownQtag(name string) *UnknownQtag { + x := UnknownQtag{} + x.init() + x.name = name + return &x +} + +func (c *UnknownQtag) init() { + c.identifier = make(map[string]any) + json.Unmarshal([]byte("{\"fields\":{\"app\":\""+c.name+"\"}}"), &c.identifier) + c.values = make(map[string]any) +} + +func (c *UnknownQtag) Format() (string, error) { + return format(c.name, c.identifier, c.values) +} + +func (c *UnknownQtag) UnknownValue(name string, value any) { + c.values[name] = value +} diff --git a/go/pkg/qtag/builder_test.go b/go/pkg/qtag/builder_test.go new file mode 100644 index 0000000..ea1d024 --- /dev/null +++ b/go/pkg/qtag/builder_test.go @@ -0,0 +1,15 @@ +package qtag + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInit(t *testing.T) { + var x = NewDbt() + x.AddConnection_name("x") + s, err := x.Format() + assert.NoError(t, err) + assert.Equal(t, `{"app":"dbt","connection_name":"x"}`, s) +} diff --git a/go/pkg/qtag/declarations/dbt.yaml b/go/pkg/qtag/declarations/dbt.yaml index 476c9fe..a0b44a5 100644 --- a/go/pkg/qtag/declarations/dbt.yaml +++ b/go/pkg/qtag/declarations/dbt.yaml @@ -20,8 +20,6 @@ fields: type: DIMENSION - name: project_name type: DIMENSION - - name: target_name - type: DIMENSION - name: target_database type: DIMENSION - name: target_schema diff --git a/go/pkg/qtag/declarations/sundeck.yaml b/go/pkg/qtag/declarations/sundeck.yaml index ebcceda..2ab69cc 100644 --- a/go/pkg/qtag/declarations/sundeck.yaml +++ b/go/pkg/qtag/declarations/sundeck.yaml @@ -34,3 +34,13 @@ fields: type: DIMENSION - name: kind type: DIMENSION + - name: auto_routing_matched + type: DIMENSION + - name: auto_routing_matched_warehouse + type: DIMENSION + - name: auto_routing_matched_warehouse_size + type: DIMENSION + - name: auto_routing_num_computed_signatures + type: TRACE + - name: auto_routing_warehouse_pool + type: DIMENSION \ No newline at end of file