Skip to content

Commit

Permalink
improve code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
mobiletoly committed Oct 20, 2021
1 parent 503f5fa commit fe30783
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 45 deletions.
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ comment annotations, and you have few options to choose from:
constructor but still want for gobetter to process another fields, such as marked with `gob:getter` to generate
getters;

- `//+gob:ptr` - specifies that generated getter functions (if any) should be operated on pointer receivers
instead of value receiver. It means that `func (v *Person) FirstName() string` function will be generated instead
of default `func (v Person) FirstName() string`


### Integration with IntelliJ

Expand Down Expand Up @@ -170,19 +174,27 @@ This will do it. Now when you save Go file - `go generate` will be automatically
`-output <output-file-name>` - optional file name to save generated data into. if this switch is not specified
then gobetter will create a filename with suffix `_gob.go` in the same directory where the input file resides.

`-generate-for all|exported` - sometimes you don't want to annotate structures with *//+gob:* constructor
annotation, or you don't have this option, because files with a structures could be auto-generated for you by
some other tool. In this case you can invoke gobetter from some other file and pass `-generate-for` flag to
specify that you want to generate constructors for all struct types. `all` option will process all exported
and package-level structs while `exported` will process only exported (started with uppercase character)
structures.
`-generate-for all|exported|annotated` - sometimes you don't want to annotate structures with *//+gob:*
constructor annotation, or you don't have this option, because files with a structures could be
auto-generated for you by some other tool. In this case you can invoke gobetter from some other file
and pass **-generate-for** flag to specify that you want to process structures that don't have
annotation comments. **all** value will process all exported and package-level structs while
**exported** will process only exported (started with uppercase character) structures. **annotated**
value disables automatic processing of structures (this is default behavior) and requires structure
annotation comments.

`-constructor exported|package|none` - this flag makes sense only for structures processed by
**-generate-for** flag. **exported** value enforces creation of exported struct constructors (for
package-level structures package-level constructors will be generated). **package** value enforces
creation of package-level constructors for all structures. **none** means no constructorы will be
provided (but gobetter will process structure in order to generate getters if necessary).

Example:

```
package main
//go:generate gobetter -input=./internal/graph/model/models_gen.go -default-types=all
//go:generate gobetter -input=./internal/graph/model/models_gen.go -generate-for=exported -receiver=pointer -constructor=package
import (
...
Expand Down
2 changes: 1 addition & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
)

type Person struct { //+gob:Constructor
type Person struct {
firstName, lastName *string //+gob:getter
Age int `json:"age"`
Description *string `json:"description"` //+gob:_
Expand Down
59 changes: 45 additions & 14 deletions gobld.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type StructParser struct {
constructorExportedRegexp *regexp.Regexp
constructorPackageRegexp *regexp.Regexp
constructorNoRegexp *regexp.Regexp
flagReceiverPtrRegexp *regexp.Regexp
flagOptionalRegexp *regexp.Regexp
flagGetterRegexp *regexp.Regexp
}
Expand All @@ -38,6 +39,12 @@ type GobBuilder struct {
astFile *ast.File
}

type StructFlags struct {
ProcessStruct bool
Visibility Visibility
PtrReceiver bool
}

func (bld *GobBuilder) appendPackage() {
bld.common.WriteString("// Code generated by gobetter; DO NOT EDIT.\n\n")
bld.common.WriteString(fmt.Sprintf("package %s\n\n", bld.astFile.Name.Name))
Expand All @@ -51,13 +58,15 @@ func (bld *GobBuilder) appendImports() {
bld.common.WriteString(")\n\n")
}

func (bld *GobBuilder) appendArgStruct(structName string, fieldName string, fieldType string, visibility Visibility) (structArgName string) {
structArgName = newStructArgName(structName, fieldName, visibility)
func (bld *GobBuilder) appendArgStruct(structName string, fieldName string, fieldType string, structFlags StructFlags) (structArgName string) {
structArgName = newStructArgName(structName, fieldName, structFlags.Visibility)
bld.common.WriteString(fmt.Sprintf("// %s represents field %s of struct %s\n", structArgName, fieldName, structName))
bld.common.WriteString(fmt.Sprintf("type %s struct {\n", structArgName))
bld.common.WriteString(fmt.Sprintf("\tArg %s\n}\n", fieldType))
bld.common.WriteString(fmt.Sprintf("// %s%s creates argument for field %s\n", structName, strings.Title(fieldName), fieldName))
bld.common.WriteString(fmt.Sprintf("func %s_%s(arg %s) %s {\n", structName, strings.Title(fieldName),
bld.common.WriteString(fmt.Sprintf("func %s_%s(arg %s) %s {\n",
convertStructNameAccordingToVisibility(structName, structFlags.Visibility),
strings.Title(fieldName),
fieldType, structArgName))
bld.common.WriteString(fmt.Sprintf("\treturn %s{Arg: arg}\n}\n\n", structArgName))
return
Expand All @@ -77,16 +86,20 @@ func newStructArgName(structName string, fieldName string, visibility Visibility

func convertStructNameAccordingToVisibility(structName string, visibility Visibility) string {
if visibility == PackageLevelVisibility {
return string(unicode.ToLower(rune(structName[0]))) + structName[1:]
return untitle(structName)
} else {
return structName
}
}

func (bld *GobBuilder) appendBeginConstructorDef(structName string, visibility Visibility) {
func untitle(value string) string {
return string(unicode.ToLower(rune(value[0]))) + value[1:]
}

func (bld *GobBuilder) appendBeginConstructorDef(structName string, structFlags StructFlags) {
var funcName = ""
firstChar := rune(structName[0])
if unicode.IsLower(firstChar) || visibility == PackageLevelVisibility {
if unicode.IsLower(firstChar) || structFlags.Visibility == PackageLevelVisibility {
funcName = "new" + strings.Title(structName)
} else {
funcName = "New" + strings.Title(structName)
Expand All @@ -112,8 +125,12 @@ func (bld *GobBuilder) appendConstructorArg(fieldName string, structArgName stri
bld.constructorPtrBody.WriteString(value)
}

func (bld *GobBuilder) appendGetter(structName string, fieldName string, fieldType string) {
bld.common.WriteString(fmt.Sprintf("func (v %s) %s() %s {\n", structName, strings.Title(fieldName), fieldType))
func (bld *GobBuilder) appendGetter(structName string, fieldName string, fieldType string, flags StructFlags) {
ptr := ""
if flags.PtrReceiver {
ptr = "*"
}
bld.common.WriteString(fmt.Sprintf("func (v %s%s) %s() %s {\n", ptr, structName, strings.Title(fieldName), fieldType))
bld.common.WriteString(fmt.Sprintf("\treturn v.%s\n", fieldName))
bld.common.WriteString(fmt.Sprint("}\n\n"))
}
Expand Down Expand Up @@ -156,6 +173,7 @@ func NewStructParser(fileSet *token.FileSet, fileContent []byte) StructParser {
constructorExportedRegexp: regexp.MustCompile("\\b+gob:Constructor\\b"),
constructorPackageRegexp: regexp.MustCompile("\\b+gob:constructor\\b"),
constructorNoRegexp: regexp.MustCompile("\\b+gob:_\\b"),
flagReceiverPtrRegexp: regexp.MustCompile("\\b+gob:ptr\\b"),
flagOptionalRegexp: regexp.MustCompile("\\b+gob:_\\b"),
flagGetterRegexp: regexp.MustCompile("\\b+gob:getter\\b"),
}
Expand All @@ -175,18 +193,31 @@ func (sp *StructParser) fieldGetter(field *ast.Field) bool {
return sp.flagGetterRegexp.MatchString(field.Comment.Text())
}

func (sp *StructParser) constructorVisibility(st *ast.StructType) (processStruct bool, visibility Visibility) {
func (sp *StructParser) constructorFlags(st *ast.StructType) StructFlags {
begin := st.Struct
endLine := sp.fileSet.File(begin).Line(begin) + 1
end := sp.fileSet.File(begin).LineStart(endLine)
result := string(sp.fileContent[sp.fileSet.Position(begin).Offset:sp.fileSet.Position(end).Offset])
flags := StructFlags{
ProcessStruct: false,
Visibility: ExportedVisibility,
PtrReceiver: false,
}
if sp.constructorPackageRegexp.MatchString(result) {
return true, PackageLevelVisibility
flags.ProcessStruct = true
flags.Visibility = PackageLevelVisibility
} else if sp.constructorExportedRegexp.MatchString(result) {
return true, ExportedVisibility
flags.ProcessStruct = true
flags.Visibility = ExportedVisibility
} else if sp.constructorNoRegexp.MatchString(result) {
return true, NoVisibility
} else {
return false, NoVisibility
flags.ProcessStruct = true
flags.Visibility = NoVisibility
}

if sp.flagReceiverPtrRegexp.MatchString(result) {
flags.ProcessStruct = true
flags.PtrReceiver = true
}

return flags
}
89 changes: 66 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ func makeOutputFilename(inFilename string) string {
func parseCommandLineArgs() (
inFilename string,
outFilename string,
defaultTypes *string,
generateFor *string,
usePtrReceiver bool,
constructorVisibility string,
) {
_, err := exec.LookPath("goimports")
if err != nil {
Expand All @@ -39,14 +41,30 @@ func parseCommandLineArgs() (
os.Exit(1)
}

inputFilePtr := flag.String("input", "filename", "go input file")
outputFilePtr := flag.String("output", "filename", "go output file (optional)")
defaultTypes = flag.String("generate-for", "exported", "parse even non-annotated "+
"struct types (\"all\" for exported and package-level, \"exported\" for exported only)")
boolPtr := flag.Bool("print-version", true, "a bool")
inputFilePtr := flag.String("input", "", "go input file path")
outputFilePtr := flag.String("output", "", "go output file path (optional)")
generateForPtr := flag.String("generate-for", "annotated",
`allows parsing of non-annotated struct types:
| all - process exported and package-level classes
| exported - process exported classes only
| annotated - process specifically annotated class only
`)
receiverTypePtr := flag.String("receiver", "value",
`specify function receiver type:
| value - receiver must be a value type, e.g. { func (v *Class) Name }
| pointer - receiver must be a pointer type, e.g. { func (v Class) Name }
`)
constructorVisibilityPtr := flag.String("constructor", "exported",
`generate exported or package-level constructors:
| exported - exported (upper-cased) constructors will be created
| package - package-level (lower-cased) constructors will be created
| none - no constructors will be created
`)
//constructorScopePtr := flag.String("constructor", "exported", "{exported|package|none}")
flag.Bool("print-version", false, "print current version")

flag.Parse()
if isFlagPassed("print-version") && *boolPtr {
if isFlagPassed("print-version") {
println("gobetter version 0.3")
}

Expand All @@ -67,18 +85,34 @@ func parseCommandLineArgs() (
outFilename = makeOutputFilename(inFilename)
}

if isFlagPassed("generate-for") {
if *defaultTypes != "all" && *defaultTypes != "exported" {
_, _ = fmt.Fprintln(os.Stderr, "Error: \"generate-for\" flag must be \"all\" or \"exported\"")
os.Exit(1)
}
if *generateForPtr == "all" || *generateForPtr == "exported" {
generateFor = generateForPtr
} else if *generateForPtr == "annotated" {
generateFor = nil
} else {
_, _ = fmt.Fprintln(os.Stderr, "Error: \"generate-for\" flag must be \"all\", \"exported\", or \"annotated\"")
os.Exit(1)
}

if *receiverTypePtr == "pointer" {
usePtrReceiver = true
} else if *receiverTypePtr == "value" {
usePtrReceiver = false
} else {
defaultTypes = nil
_, _ = fmt.Fprintln(os.Stderr, "Error: \"receiver\" flag must be \"pointer\" or \"value\"")
os.Exit(1)
}

println("Input file: " + inFilename)
println("Output file: " + outFilename)
return inFilename, outFilename, defaultTypes
if *constructorVisibilityPtr == "exported" || *constructorVisibilityPtr == "package" || *constructorVisibilityPtr == "none" {
constructorVisibility = *constructorVisibilityPtr
} else {
_, _ = fmt.Fprintln(os.Stderr, "Error: \"constructor\" flag must be \"exported\", \"package\", or \"none\"")
os.Exit(1)
}

println("Input file:", inFilename)
println("Output file:", outFilename)
return
}

func isFlagPassed(name string) bool {
Expand All @@ -93,7 +127,7 @@ func isFlagPassed(name string) bool {

func main() {

inFilename, outFilename, defaultTypes := parseCommandLineArgs()
inFilename, outFilename, defaultTypes, usePtrReceiver, constructorVisibility := parseCommandLineArgs()
fileContent, err := ioutil.ReadFile(inFilename)
fset := token.NewFileSet()
astFile, err := parser.ParseFile(fset, inFilename, nil, parser.ParseComments)
Expand All @@ -119,8 +153,8 @@ func main() {
}

structName := ts.Name.Name
processStruct, visibility := sp.constructorVisibility(st)
if !processStruct {
structFlags := sp.constructorFlags(st)
if !structFlags.ProcessStruct {
if defaultTypes == nil {
return true
}
Expand All @@ -129,25 +163,34 @@ func main() {
return true
}
}
structFlags.ProcessStruct = true
structFlags.PtrReceiver = usePtrReceiver
if constructorVisibility == "exported" {
structFlags.Visibility = ExportedVisibility
} else if constructorVisibility == "package" {
structFlags.Visibility = PackageLevelVisibility
} else {
structFlags.Visibility = NoVisibility
}
}

fmt.Printf("Process structure %s\n", structName)

for _, field := range st.Fields.List {
fieldTypeText := sp.fieldTypeText(field)
for _, fieldName := range field.Names {
if visibility != NoVisibility {
if structFlags.Visibility != NoVisibility {
if !sp.fieldOptional(field) {
structArgName := gobBld.appendArgStruct(structName, fieldName.Name, fieldTypeText, visibility)
structArgName := gobBld.appendArgStruct(structName, fieldName.Name, fieldTypeText, structFlags)
if gobBld.constructorValueDef.Len() == 0 {
gobBld.appendBeginConstructorDef(structName, visibility)
gobBld.appendBeginConstructorDef(structName, structFlags)
gobBld.appendBeginConstructorBody(structName)
}
gobBld.appendConstructorArg(fieldName.Name, structArgName)
}
}
if sp.fieldGetter(field) {
gobBld.appendGetter(structName, fieldName.Name, fieldTypeText)
gobBld.appendGetter(structName, fieldName.Name, fieldTypeText, structFlags)
}
}
}
Expand Down

0 comments on commit fe30783

Please sign in to comment.