Skip to content

Commit 2ada9d4

Browse files
committed
Initial commit
0 parents  commit 2ada9d4

File tree

15 files changed

+743
-0
lines changed

15 files changed

+743
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Fielder
2+
3+
Fielder is a tool to generate Go code that extracts fields from a struct and transforms them into ENUM.
4+
Also, it adds useful methods and functions.
5+
6+
## Motivation
7+
8+
When using ORM-s like `gorm`, `go-pg`, `bun` you have to pass column names to update rows in DB.
9+
This is a pain to use raw strings for that, and it also might be a security risk.
10+
Much better to rely on ENUM which represents columns for a specific table.
11+
12+
## Features
13+
14+
The library provides the following features:
15+
16+
* Generates ENUM for the struct fields
17+
* Generates method to validate ENUM values
18+
* Generates function to list all fields
19+
* A tag can be used when extracting field names from a struct
20+
* Extracting embedded struct fields are supported
21+
* Fields excluding is supported
22+
* Different formats of generated field values are supported (camel, pascal, snake)
23+
24+
## Installation
25+
26+
$ go install https://github.com/kpeu3i/[email protected]
27+
28+
## Usage
29+
30+
Put the `go:generate` directive in the same package as the struct you want to generate.
31+
For example:
32+
33+
```go
34+
//go:generate fielder -type=UserAccount
35+
36+
package domain
37+
38+
type UserAccount struct {
39+
FirstName string
40+
LastName string
41+
Email string
42+
Password string
43+
}
44+
```
45+
46+
Then, run command bellow to generate the code:
47+
48+
$ go generate ./...
49+
50+
You can check for example in the `example` folder.
51+
52+
The following CLI flags are allowed:
53+
54+
```shell
55+
Usage of fielder:
56+
-embedded
57+
Extract embedded fields
58+
-excluded string
59+
Comma separated list of excluded fields
60+
-format string
61+
Format of the generated type values (default "as_is")
62+
-output string
63+
Output file name (default <src_dir>/<type>_fielder.go)
64+
-pkg string
65+
Package name to extract type from (default ".")
66+
-suffix string
67+
Generated type name suffix (default "Field")
68+
-tag string
69+
Extract field names from struct tag
70+
-tpl string
71+
Override template file
72+
-type string
73+
Type name to generate fields for
74+
```

config.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/iancoleman/strcase"
11+
)
12+
13+
const (
14+
formatAsIs = "as_is"
15+
formatSnakeCase = "snake_case"
16+
formatCamelCase = "camel_case"
17+
formatPascalCase = "pascal_case"
18+
)
19+
20+
type config struct {
21+
Pkg string
22+
Type string
23+
Suffix string
24+
Tag string
25+
Format string
26+
Embedded bool
27+
Excluded []string
28+
TemplateFilename string
29+
OutputFilename string
30+
}
31+
32+
func initConfig() (config, error) {
33+
pkg := flag.String("pkg", ".", "Package name to extract type from")
34+
typ := flag.String("type", "", "Type name to generate fields for")
35+
suffix := flag.String("suffix", "Field", "Generated type name suffix")
36+
tag := flag.String("tag", "", "Extract field names from struct tag")
37+
format := flag.String("format", formatAsIs, "Format of the generated type values")
38+
embedded := flag.Bool("embedded", false, "Extract embedded fields")
39+
excluded := flag.String("excluded", "", "Comma separated list of excluded fields")
40+
templateFilename := flag.String("tpl", "", "Override template file")
41+
outputFilename := flag.String("output", "", "Output file name (default <src_dir>/<type>_fielder.go)")
42+
43+
flag.Parse()
44+
45+
if *typ == "" {
46+
return config{}, errors.New("type is required")
47+
}
48+
49+
switch *format {
50+
case formatAsIs, formatSnakeCase, formatCamelCase, formatPascalCase:
51+
default:
52+
return config{}, fmt.Errorf("invalid format %s", *format)
53+
}
54+
55+
conf := config{}
56+
conf.Pkg = *pkg
57+
conf.Type = *typ
58+
conf.Suffix = *suffix
59+
conf.Tag = *tag
60+
conf.Format = *format
61+
conf.Embedded = *embedded
62+
conf.TemplateFilename = *templateFilename
63+
64+
if *excluded != "" {
65+
conf.Excluded = strings.Split(*excluded, ",")
66+
}
67+
68+
conf.OutputFilename = filepath.Join(".", fmt.Sprintf("%s_fielder.go", strcase.ToSnake(conf.Type)))
69+
if *outputFilename != "" {
70+
conf.OutputFilename = *outputFilename
71+
}
72+
73+
return conf, nil
74+
}

example/common/entity.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package common
2+
3+
import (
4+
"time"
5+
)
6+
7+
type Entity struct {
8+
ID int64 `db:"id"`
9+
CreatedAt time.Time `db:"created_at"`
10+
UpdatedAt time.Time `db:"updated_at"`
11+
}

example/user_account.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//go:generate fielder -type=UserAccount -suffix=Column -embedded=true -tag=db -excluded=FullName
2+
3+
package example
4+
5+
import (
6+
"github.com/kpeu3i/fielder/example/common"
7+
)
8+
9+
type UserAccount struct {
10+
common.Entity
11+
12+
FirstName string `db:"name"`
13+
LastName string `db:"surname"`
14+
Email string `db:"email"`
15+
Password string `db:"password"`
16+
17+
FullName string
18+
}

example/user_account_fielder.go

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fileutil.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/fs"
6+
"os"
7+
)
8+
9+
func writeToFile(filename string, output []byte) error {
10+
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fs.ModePerm)
11+
if err != nil {
12+
return fmt.Errorf("could not open file %q: %v", filename, err)
13+
}
14+
defer f.Close()
15+
16+
_, err = f.Write(output)
17+
if err != nil {
18+
return fmt.Errorf("could not write to file: %v", err)
19+
}
20+
21+
return nil
22+
}

genutil.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"embed"
6+
"fmt"
7+
goformat "go/format"
8+
"os"
9+
"strings"
10+
"text/template"
11+
)
12+
13+
const (
14+
templateName = "output.tpl"
15+
)
16+
17+
var (
18+
//go:embed output.tpl
19+
templatesFS embed.FS
20+
)
21+
22+
func generateOutput(pkg string, typ, suffix, tplFilename string, fields []field) ([]byte, error) {
23+
var (
24+
err error
25+
buf bytes.Buffer
26+
)
27+
28+
t := template.New(templateName)
29+
30+
if tplFilename == "" {
31+
t, err = t.ParseFS(templatesFS, templateName)
32+
if err != nil {
33+
return nil, fmt.Errorf("could not parse template: %v", err)
34+
}
35+
} else {
36+
t, err = t.ParseFiles(tplFilename)
37+
if err != nil {
38+
return nil, fmt.Errorf("could not parse template: %v", err)
39+
}
40+
}
41+
42+
err = t.Execute(&buf, map[string]any{
43+
"Package": pkg,
44+
"Type": typ,
45+
"Suffix": suffix,
46+
"Fields": fields,
47+
"CMD": fmt.Sprintf("fielder %s", strings.Join(os.Args[1:], " ")),
48+
})
49+
if err != nil {
50+
return nil, fmt.Errorf("could not execute template: %v", err)
51+
}
52+
53+
formatted, err := goformat.Source(buf.Bytes())
54+
if err != nil {
55+
return nil, fmt.Errorf("could not format source: %v", err)
56+
}
57+
58+
return formatted, nil
59+
}

go.mod

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module github.com/kpeu3i/fielder
2+
3+
go 1.18
4+
5+
require (
6+
github.com/fatih/structtag v1.2.0
7+
github.com/iancoleman/strcase v0.2.0
8+
github.com/samber/lo v1.27.0
9+
github.com/stretchr/testify v1.7.1
10+
golang.org/x/tools v0.1.9-0.20211216111533-8d383106f7e7
11+
)
12+
13+
require (
14+
github.com/davecgh/go-spew v1.1.1 // indirect
15+
github.com/kr/pretty v0.3.0 // indirect
16+
github.com/pmezard/go-difflib v1.0.0 // indirect
17+
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
18+
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
19+
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
20+
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
21+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
22+
gopkg.in/yaml.v3 v3.0.1 // indirect
23+
)

go.sum

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
6+
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
7+
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
8+
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
9+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
10+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
11+
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
12+
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
13+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
14+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
15+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
16+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
17+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
18+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
19+
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
20+
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
21+
github.com/samber/lo v1.27.0 h1:GOyDWxsblvqYobqsmUuMddPa2/mMzkKyojlXol4+LaQ=
22+
github.com/samber/lo v1.27.0/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
23+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
24+
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
25+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
26+
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
27+
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
28+
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
29+
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4=
30+
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
31+
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
32+
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
33+
golang.org/x/tools v0.1.9-0.20211216111533-8d383106f7e7 h1:M1gcVrIb2lSn2FIL19DG0+/b8nNVKJ7W7b4WcAGZAYM=
34+
golang.org/x/tools v0.1.9-0.20211216111533-8d383106f7e7/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
35+
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
36+
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
37+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
38+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
39+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
40+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
41+
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
42+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
43+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
44+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)