/
generate.go
132 lines (110 loc) · 4.17 KB
/
generate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright © 2020 David Arnold <dar@xoe.solutions>
// SPDX-License-Identifier: MIT
package generate
import (
"fmt"
"go/types"
"log"
"reflect"
"regexp"
"strings"
. "github.com/dave/jennifer/jen"
"github.com/xoe-labs/go-generators/ddd-domain-gen/pkg/directive"
)
// StructTag Key
var (
// structTagGenKey = "gen" // use https://github.com/phelmkamp/metatag instead
structTagDDDKey = "ddd"
)
// A simple regexp pattern to match tag values
var (
structRequiredTagPattern = regexp.MustCompile(`required,([^;]+)`)
structPrivateTagPattern = regexp.MustCompile(`private`)
structGetterTagPattern = regexp.MustCompile(`getter`)
structSetterTagPattern = regexp.MustCompile(`setter`)
structStringerTagPattern = regexp.MustCompile(`stringer`)
structEqualTagPattern = regexp.MustCompile(`equal(,reflect)?`)
)
func generate(sourceTypeName, validatorMethod, goPackagePath string, structType *types.Struct) error {
log.Printf("Generating code for: %s.%s\n", goPackagePath, sourceTypeName)
// Start a new file in this package
// return fmt.Errorf(goPackage)
f := NewFile(goPackage)
// Add a package comment, so IDEs detect files as generated
f.PackageComment("Code generated by ddd-domain-gen, DO NOT EDIT.")
// 1. define code region variables
var (
privateFlds []*types.Var
publicFlds []*types.Var
validations []directive.Validation
equalFlds []directive.EqualFld
genGetterFields []*types.Var
genSetterFields []*types.Var
genStringerFields []*types.Var
)
// 2. iterate over struct fields and populate those variables
for i := 0; i < structType.NumFields(); i++ {
field := structType.Field(i)
tag := reflect.StructTag(structType.Tag(i))
// 2.1 error if pointer field encountered
if _, ok := field.Type().(*types.Pointer); ok {
return fmt.Errorf("%s type is a pointer - can evade validation", field.Name())
}
// 2.2 match and classify fields according to tags
var private bool
if structTagDDDKeyValue, ok := tag.Lookup(structTagDDDKey); ok {
if matches := structGetterTagPattern.FindStringSubmatch(structTagDDDKeyValue); matches != nil {
genGetterFields = append(genGetterFields, field)
}
if matches := structSetterTagPattern.FindStringSubmatch(structTagDDDKeyValue); matches != nil {
genSetterFields = append(genSetterFields, field)
}
if matches := structStringerTagPattern.FindStringSubmatch(structTagDDDKeyValue); matches != nil {
genStringerFields = append(genStringerFields, field)
}
if matches := structPrivateTagPattern.FindStringSubmatch(structTagDDDKeyValue); matches != nil {
private = true
privateFlds = append(privateFlds, field)
} else {
publicFlds = append(publicFlds, field)
}
if requiredMatches := structRequiredTagPattern.FindStringSubmatch(structTagDDDKeyValue); requiredMatches != nil {
if private {
return fmt.Errorf("private field %s cannot be required", field.Name())
}
errMsg := requiredMatches[1]
validations = append(validations, directive.Validation{Field: field, ErrMsg: errMsg})
}
if equalMatches := structEqualTagPattern.FindStringSubmatch(structTagDDDKeyValue); equalMatches != nil {
if (len(equalMatches) > 1 && equalMatches[1] != "") {
equalFlds = append(equalFlds, directive.EqualFld{Field: field, IsDeepEqual: true})
} else {
equalFlds = append(equalFlds, directive.EqualFld{Field: field, IsDeepEqual: false})
}
}
} else {
publicFlds = append(publicFlds, field)
}
}
// 3. assemble methods ...
f.Comment("Constructors ...")
f.Line()
directive.GenNew(f, sourceTypeName, publicFlds, validations, validatorMethod)
directive.GenMustNew(f, sourceTypeName, publicFlds)
f.Comment("Marshalers ...")
f.Line()
directive.GenUnmarshalFromRepository(f, sourceTypeName, publicFlds, privateFlds)
f.Comment("Accessors ...")
f.Line()
directive.GenGetters(f, sourceTypeName, genGetterFields)
directive.GenSetters(f, sourceTypeName, genSetterFields)
f.Comment("Utilities ...")
f.Line()
directive.GenEqual(f, sourceTypeName, equalFlds)
directive.GenStringer(f, sourceTypeName, genStringerFields)
// Write generated file
return f.Save(targetFilename)
}
func isPointer(s string) bool {
return strings.HasPrefix(s, "*")
}