Skip to content

Commit 021aec7

Browse files
committed
Add basic tests and fix pkg import and struct initialization
Signed-off-by: Saswata Mukherjee <[email protected]>
1 parent aa0018e commit 021aec7

File tree

4 files changed

+114
-19
lines changed

4 files changed

+114
-19
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/Kunde21/markdownfmt/v3 v3.1.0
77
github.com/alecthomas/kingpin/v2 v2.3.2
88
github.com/charmbracelet/glamour v0.6.0
9-
github.com/dave/jennifer v1.4.1
9+
github.com/dave/jennifer v1.6.1
1010
github.com/efficientgo/core v1.0.0-rc.2
1111
github.com/efficientgo/tools/extkingpin v0.0.0-20230505153745-6b7392939a60
1212
github.com/fatih/structtag v1.2.0

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
8989
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
9090
github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw=
9191
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
92+
github.com/dave/jennifer v1.6.1 h1:T4T/67t6RAA5AIV6+NP8Uk/BIsXgDoqEowgycdQQLuk=
93+
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
9294
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9395
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9496
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

pkg/yamlgen/yamlgen.go

+51-18
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ package yamlgen
66
import (
77
"bytes"
88
"context"
9-
"fmt"
109
"go/ast"
1110
"go/parser"
1211
"go/token"
1312
"go/types"
14-
"io/ioutil"
1513
"os"
1614
"os/exec"
1715
"path/filepath"
1816
"regexp"
17+
"strings"
1918

2019
"github.com/dave/jennifer/jen"
2120
"github.com/pkg/errors"
@@ -31,18 +30,15 @@ func GenGoCode(src []byte) (string, error) {
3130
// Create new main file.
3231
fset := token.NewFileSet()
3332
generatedCode := jen.NewFile("main")
33+
// Don't really need to format here, saves time.
34+
// generatedCode.NoFormat = true
3435

3536
// Parse source file.
3637
f, err := parser.ParseFile(fset, "", src, parser.AllErrors)
3738
if err != nil {
3839
return "", err
3940
}
4041

41-
// Add imports if needed(will not be used if not required in code).
42-
for _, s := range f.Imports {
43-
generatedCode.ImportName(s.Path.Value[1:len(s.Path.Value)-1], "")
44-
}
45-
4642
// Init statements for structs.
4743
var init []jen.Code
4844
// Declare config map, i.e, `configs := map[string]interface{}{}`.
@@ -54,33 +50,70 @@ func GenGoCode(src []byte) (string, error) {
5450
if genericDecl, ok := decl.(*ast.GenDecl); ok {
5551
// Check if declaration spec is `type`.
5652
if typeDecl, ok := genericDecl.Specs[0].(*ast.TypeSpec); ok {
57-
var structFields []jen.Code
5853
// Cast to `type struct`.
5954
structDecl, ok := typeDecl.Type.(*ast.StructType)
6055
if !ok {
6156
generatedCode.Type().Id(typeDecl.Name.Name).Id(string(src[typeDecl.Type.Pos()-1 : typeDecl.Type.End()-1]))
6257
continue
6358
}
64-
fields := structDecl.Fields.List
59+
60+
var structFields []jen.Code
6561
arrayInit := make(jen.Dict)
6662

6763
// Loop and generate fields for each field.
64+
fields := structDecl.Fields.List
6865
for _, field := range fields {
6966
// Each field might have multiple names.
7067
names := field.Names
7168
for _, n := range names {
7269
if n.IsExported() {
7370
pos := n.Obj.Decl.(*ast.Field)
7471

75-
// Check if field is a slice type.
76-
sliceRe := regexp.MustCompile(`.*\[.*\].*`)
77-
if sliceRe.MatchString(types.ExprString(field.Type)) {
78-
arrayInit[jen.Id(n.Name)] = jen.Id(string(src[pos.Type.Pos()-1 : pos.Type.End()-1])).Values(jen.Id(string(src[pos.Type.Pos()+1 : pos.Type.End()-1])).Values())
72+
// Copy struct field to generated code, with imports in case of other package imported field.
73+
if pos.Tag != nil {
74+
typeStr := string(src[pos.Type.Pos()-1 : pos.Type.End()-1])
75+
// Check if field is imported from other package.
76+
if strings.Contains(typeStr, ".") {
77+
typeArr := strings.SplitN(typeStr, ".", 2)
78+
// Match the import name with the import statement.
79+
for _, s := range f.Imports {
80+
moduleName := ""
81+
// Choose to copy same alias as in source file or use package name.
82+
if s.Name == nil {
83+
_, moduleName = filepath.Split(s.Path.Value[1 : len(s.Path.Value)-1])
84+
generatedCode.ImportName(s.Path.Value[1:len(s.Path.Value)-1], moduleName)
85+
} else {
86+
moduleName = s.Name.String()
87+
generatedCode.ImportAlias(s.Path.Value[1:len(s.Path.Value)-1], moduleName)
88+
}
89+
// Add field to struct only if import name matches.
90+
if moduleName == typeArr[0] {
91+
structFields = append(structFields, jen.Id(n.Name).Qual(s.Path.Value[1:len(s.Path.Value)-1], typeArr[1]).Id(pos.Tag.Value))
92+
}
93+
}
94+
} else {
95+
structFields = append(structFields, jen.Id(n.Name).Id(string(src[pos.Type.Pos()-1:pos.Type.End()-1])).Id(pos.Tag.Value))
96+
}
7997
}
8098

81-
// Copy struct field to generated code.
82-
if pos.Tag != nil {
83-
structFields = append(structFields, jen.Id(n.Name).Id(string(src[pos.Type.Pos()-1:pos.Type.End()-1])).Id(pos.Tag.Value))
99+
// Check if field is a slice type.
100+
sliceRe := regexp.MustCompile(`^\[.*\].*$`)
101+
typeStr := types.ExprString(field.Type)
102+
if sliceRe.MatchString(typeStr) {
103+
iArr := "[]int"
104+
fArr := "[]float"
105+
cArr := "[]complex"
106+
uArr := "[]uint"
107+
switch typeStr {
108+
case "[]bool", "[]string", "[]byte", "[]rune",
109+
iArr, iArr + "8", iArr + "16", iArr + "32", iArr + "64",
110+
fArr + "32", fArr + "64",
111+
cArr + "64", cArr + "128",
112+
uArr, uArr + "8", uArr + "16", uArr + "32", uArr + "64", uArr + "ptr":
113+
arrayInit[jen.Id(n.Name)] = jen.Id(typeStr + "{}")
114+
default:
115+
arrayInit[jen.Id(n.Name)] = jen.Id(string(src[pos.Type.Pos()-1 : pos.Type.End()-1])).Values(jen.Id(string(src[pos.Type.Pos()+1 : pos.Type.End()-1])).Values())
116+
}
84117
}
85118
}
86119
}
@@ -109,12 +142,12 @@ func GenGoCode(src []byte) (string, error) {
109142

110143
// Generate main function in new module.
111144
generatedCode.Func().Id("main").Params().Block(init...)
112-
return fmt.Sprintf("%#v", generatedCode), nil
145+
return generatedCode.GoString(), nil
113146
}
114147

115148
// execGoCode executes and returns output from generated Go code.
116149
func ExecGoCode(ctx context.Context, mainGo string) ([]byte, error) {
117-
tmpDir, err := ioutil.TempDir("", "structgen")
150+
tmpDir, err := os.MkdirTemp("", "structgen")
118151
if err != nil {
119152
return nil, err
120153
}

pkg/yamlgen/yamlgen_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Bartłomiej Płotka @bwplotka
2+
// Licensed under the Apache License 2.0.
3+
4+
package yamlgen
5+
6+
import (
7+
"testing"
8+
9+
"github.com/efficientgo/core/testutil"
10+
"golang.org/x/net/context"
11+
)
12+
13+
func TestYAMLGen_GenGoCode(t *testing.T) {
14+
t.Run("normal struct", func(t *testing.T) {
15+
source := []byte("package main\n\ntype TestConfig struct {\n\tUrl string `yaml:\"url\"`\n\tID int `yaml:\"id\"`\n\tToken string `yaml:\"token\"`\n}\n")
16+
generatedCode, err := GenGoCode(source)
17+
testutil.Ok(t, err)
18+
19+
expected := "package main\n\nimport (\n\t\"fmt\"\n\tyamlgen \"github.com/bwplotka/mdox/pkg/yamlgen\"\n\t\"os\"\n)\n\ntype TestConfig struct {\n\tUrl string `yaml:\"url\"`\n\tID int `yaml:\"id\"`\n\tToken string `yaml:\"token\"`\n}\n\nfunc main() {\n\tconfigs := map[string]interface{}{}\n\tconfigs[\"TestConfig\"] = TestConfig{}\n\tfor k, config := range configs {\n\t\tfmt.Println(\"---\")\n\t\tfmt.Println(k)\n\t\tyamlgen.Generate(config, os.Stderr)\n\t}\n}\n"
20+
testutil.Equals(t, expected, generatedCode)
21+
})
22+
23+
t.Run("struct with unexported field", func(t *testing.T) {
24+
source := []byte("package main\n\nimport \"regexp\"\n\ntype ValidatorConfig struct {\n\tType string `yaml:\"type\"`\n\tRegex string `yaml:\"regex\"`\n\tToken string `yaml:\"token\"`\n\n\tr *regexp.Regexp\n}\n")
25+
generatedCode, err := GenGoCode(source)
26+
testutil.Ok(t, err)
27+
28+
expected := "package main\n\nimport (\n\t\"fmt\"\n\tyamlgen \"github.com/bwplotka/mdox/pkg/yamlgen\"\n\t\"os\"\n)\n\ntype ValidatorConfig struct {\n\tType string `yaml:\"type\"`\n\tRegex string `yaml:\"regex\"`\n\tToken string `yaml:\"token\"`\n}\n\nfunc main() {\n\tconfigs := map[string]interface{}{}\n\tconfigs[\"ValidatorConfig\"] = ValidatorConfig{}\n\tfor k, config := range configs {\n\t\tfmt.Println(\"---\")\n\t\tfmt.Println(k)\n\t\tyamlgen.Generate(config, os.Stderr)\n\t}\n}\n"
29+
testutil.Equals(t, expected, generatedCode)
30+
})
31+
32+
t.Run("struct with array fields", func(t *testing.T) {
33+
source := []byte("package main\n\nimport \"regexp\"\n\ntype Config struct {\n\tVersion int `yaml:\"version\"`\n\n\tValidator []ValidatorConfig `yaml:\"validators\"`\n\tIgnore []IgnoreConfig `yaml:\"ignore\"`\n}\n\ntype ValidatorConfig struct {\n\tType string `yaml:\"type\"`\n\tRegex string `yaml:\"regex\"`\n\tToken string `yaml:\"token\"`\n\n\tr *regexp.Regexp\n}\n\ntype IgnoreConfig struct {\n\tUrl string `yaml:\"url\"`\n\tID int `yaml:\"id\"`\n\tToken string `yaml:\"token\"`\n}\n")
34+
generatedCode, err := GenGoCode(source)
35+
testutil.Ok(t, err)
36+
37+
expected := "package main\n\nimport (\n\t\"fmt\"\n\tyamlgen \"github.com/bwplotka/mdox/pkg/yamlgen\"\n\t\"os\"\n)\n\ntype Config struct {\n\tVersion int `yaml:\"version\"`\n\tValidator []ValidatorConfig `yaml:\"validators\"`\n\tIgnore []IgnoreConfig `yaml:\"ignore\"`\n}\ntype ValidatorConfig struct {\n\tType string `yaml:\"type\"`\n\tRegex string `yaml:\"regex\"`\n\tToken string `yaml:\"token\"`\n}\ntype IgnoreConfig struct {\n\tUrl string `yaml:\"url\"`\n\tID int `yaml:\"id\"`\n\tToken string `yaml:\"token\"`\n}\n\nfunc main() {\n\tconfigs := map[string]interface{}{}\n\tconfigs[\"Config\"] = Config{\n\t\tIgnore: []IgnoreConfig{IgnoreConfig{}},\n\t\tValidator: []ValidatorConfig{ValidatorConfig{}},\n\t}\n\tconfigs[\"ValidatorConfig\"] = ValidatorConfig{}\n\tconfigs[\"IgnoreConfig\"] = IgnoreConfig{}\n\tfor k, config := range configs {\n\t\tfmt.Println(\"---\")\n\t\tfmt.Println(k)\n\t\tyamlgen.Generate(config, os.Stderr)\n\t}\n}\n"
38+
testutil.Equals(t, expected, generatedCode)
39+
})
40+
}
41+
42+
func TestYAMLGen_ExecGoCode(t *testing.T) {
43+
t.Run("normal struct", func(t *testing.T) {
44+
generatedCode := "package main\n\nimport (\n\t\"fmt\"\n\tyamlgen \"github.com/bwplotka/mdox/pkg/yamlgen\"\n\t\"os\"\n)\n\ntype TestConfig struct {\n\tUrl string `yaml:\"url\"`\n\tID int `yaml:\"id\"`\n\tToken string `yaml:\"token\"`\n}\n\nfunc main() {\n\tconfigs := map[string]interface{}{}\n\tconfigs[\"TestConfig\"] = TestConfig{}\n\tfor k, config := range configs {\n\t\tfmt.Println(\"---\")\n\t\tfmt.Println(k)\n\t\tyamlgen.Generate(config, os.Stderr)\n\t}\n}\n"
45+
output, err := ExecGoCode(context.TODO(), generatedCode)
46+
testutil.Ok(t, err)
47+
48+
expected := "---\nTestConfig\nurl: \"\"\nid: 0\ntoken: \"\"\n"
49+
testutil.Equals(t, expected, string(output))
50+
})
51+
52+
t.Run("struct with array fields", func(t *testing.T) {
53+
generatedCode := "package main\n\nimport (\n\t\"fmt\"\n\tyamlgen \"github.com/bwplotka/mdox/pkg/yamlgen\"\n\t\"os\"\n)\n\ntype Config struct {\n\tVersion int `yaml:\"version\"`\n\tValidator []ValidatorConfig `yaml:\"validators\"`\n\tIgnore []IgnoreConfig `yaml:\"ignore\"`\n}\ntype ValidatorConfig struct {\n\tType string `yaml:\"type\"`\n\tRegex string `yaml:\"regex\"`\n\tToken string `yaml:\"token\"`\n}\ntype IgnoreConfig struct {\n\tUrl string `yaml:\"url\"`\n\tID int `yaml:\"id\"`\n\tToken string `yaml:\"token\"`\n}\n\nfunc main() {\n\tconfigs := map[string]interface{}{}\n\tconfigs[\"Config\"] = Config{\n\t\tIgnore: []IgnoreConfig{IgnoreConfig{}},\n\t\tValidator: []ValidatorConfig{ValidatorConfig{}},\n\t}\n\tconfigs[\"ValidatorConfig\"] = ValidatorConfig{}\n\tconfigs[\"IgnoreConfig\"] = IgnoreConfig{}\n\tfor k, config := range configs {\n\t\tfmt.Println(\"---\")\n\t\tfmt.Println(k)\n\t\tyamlgen.Generate(config, os.Stderr)\n\t}\n}\n"
54+
output, err := ExecGoCode(context.TODO(), generatedCode)
55+
testutil.Ok(t, err)
56+
57+
expected := "---\nConfig\nversion: 0\nvalidators:\n - type: \"\"\n regex: \"\"\n token: \"\"\nignore:\n - url: \"\"\n id: 0\n token: \"\"\n---\nValidatorConfig\ntype: \"\"\nregex: \"\"\ntoken: \"\"\n---\nIgnoreConfig\nurl: \"\"\nid: 0\ntoken: \"\"\n"
58+
testutil.Equals(t, expected, string(output))
59+
})
60+
}

0 commit comments

Comments
 (0)