Skip to content

Commit

Permalink
Merge pull request #2 from lestrrat-go/genoptions
Browse files Browse the repository at this point in the history
Add a genoption tool to automatically generate options
  • Loading branch information
lestrrat authored Mar 16, 2022
2 parents b486fe8 + 253db77 commit 6c4c703
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 0 deletions.
21 changes: 21 additions & 0 deletions cmd/genoptions/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module github.com/lestrrat-go/option/cmd/genoptions

go 1.17

require (
github.com/goccy/go-yaml v1.9.5
github.com/lestrrat-go/codegen v1.0.3
github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b
)

require (
github.com/fatih/color v1.10.0 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/mod v0.3.0 // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
golang.org/x/tools v0.0.0-20200918232735-d647fc253266 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
66 changes: 66 additions & 0 deletions cmd/genoptions/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/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=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0=
github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lestrrat-go/codegen v1.0.3 h1:HviEmojOS51tH5L762bRMAZPj8DTIkwk24Il8M+54sE=
github.com/lestrrat-go/codegen v1.0.3/go.mod h1:q3mmYUQW1bg3Z74ap7RgaVv1LmrBtlAKQOElxZTKlRM=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b h1:3laG8JWIeDGb7lf00nMRznLdCHy0aZPd/CGz7Okn1SY=
github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266 h1:k7tVuG0g1JwmD3Jh8oAl1vQ1C3jb4Hi/dUl1wWDBJpQ=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
255 changes: 255 additions & 0 deletions cmd/genoptions/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package main

import (
"bytes"
"flag"
"fmt"
"os"
"sort"
"strings"

"github.com/goccy/go-yaml"
"github.com/lestrrat-go/codegen"
"github.com/lestrrat-go/xstrings"
)

var objectsFile = flag.String(`objects`, `objects.yaml`, `specify file containing object definitions`)

func main() {
flag.Parse()

if err := _main(); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}

func writeComment(o *codegen.Output, comment string) bool {
comment = strings.TrimSpace(comment)
if comment == "" {
return false
}
for i, line := range strings.Split(comment, "\n") {
if i == 0 {
o.LL(`// %s`, line)
} else {
o.L(`// %s`, line)
}
}
return true
}

type Objects struct {
Output string
PackageName string `yaml:"package_name"`
Imports []string `yaml:"imports"`
Interfaces []*struct {
Name string
Comment string
ConcreteType string `yaml:"concrete_type"`
Methods []string
Embeds []string
} `yaml:"interfaces"`
Options []*struct {
Ident string
OptionName string `yaml:"option_name"` // usually "With" + $Ident
SkipOption bool `yaml:"skip_option"`
Interface string
ConcreteType string
Comment string
ArgumentType string `yaml:"argument_type"`
ConstantValue string `yaml:"constant_value"`
} `yaml:"options"`
}

func _main() error {
var objects Objects

{
buf, err := os.ReadFile(*objectsFile)
if err != nil {
return err
}
if err := yaml.Unmarshal(buf, &objects); err != nil {
return err
}
}

for _, iface := range objects.Interfaces {
if iface.ConcreteType == "" {
iface.ConcreteType = xstrings.LcFirst(iface.Name)
}
if len(iface.Methods) == 0 {
iface.Methods = append(iface.Methods, iface.ConcreteType)
}
}

for _, option := range objects.Options {
if option.OptionName == "" {
option.OptionName = `With` + option.Ident
}
if option.ConcreteType == "" {
option.ConcreteType = xstrings.LcFirst(option.Interface)
}
}

sort.Slice(objects.Interfaces, func(i, j int) bool {
return objects.Interfaces[i].Name < objects.Interfaces[j].Name
})
sort.Slice(objects.Options, func(i, j int) bool {
return objects.Options[i].Ident < objects.Options[j].Ident
})

if err := genOptions(&objects); err != nil {
return fmt.Errorf(`failed to generate %q`, objects.Output)
}

if err := genOptionTests(&objects); err != nil {
return fmt.Errorf(`failed to generate tests for %q`, objects.Output)
}

return nil
}

func genOptions(objects *Objects) error {
var buf bytes.Buffer

o := codegen.NewOutput(&buf)

o.L("// This file is auto-generated by github.com/lestrrat-go/option/cmd/genoptions. DO NOT EDIT")

o.LL(`package %s`, objects.PackageName)

imports := append(objects.Imports, []string{
`github.com/lestrrat-go/jwx/v2/jwa`,
`github.com/lestrrat-go/jwx/v2/jwe`,
`github.com/lestrrat-go/jwx/v2/jwk`,
`github.com/lestrrat-go/jwx/v2/jws`,
`github.com/lestrrat-go/jwx/v2/jwt`,
}...)
// Write all imports -- they will be pruned by golang.org/x/tools/imports eventually,
// so it's okay to be redundant
o.WriteImports(imports...)

o.LL(`type Option = option.Interface`)

for _, iface := range objects.Interfaces {
if writeComment(o, iface.Comment) {
o.L(`type %s interface {`, iface.Name)
} else {
o.LL(`type %s interface {`, iface.Name)
}
if len(iface.Embeds) < 1 {
o.L(`Option`)
} else {
for _, embed := range iface.Embeds {
o.L(embed)
}
}

for _, method := range iface.Methods {
o.L(`%s()`, method)
}
o.L(`}`)

o.LL(`type %s struct {`, iface.ConcreteType)
o.L(`Option`)
o.L(`}`)

for _, method := range iface.Methods {
o.LL(`func (*%s) %s() {}`, iface.ConcreteType, method)
}
}

o.L(``)

{
seen := make(map[string]struct{})
for _, option := range objects.Options {
_, ok := seen[option.Ident]
if !ok {
o.L(`type ident%s struct{}`, option.Ident)
seen[option.Ident] = struct{}{}
}
}
}

{
seen := make(map[string]struct{})
for _, option := range objects.Options {
_, ok := seen[option.Ident]
if !ok {
o.LL(`func (ident%s) String() string {`, option.Ident)
o.L(`return %q`, option.OptionName)
o.L(`}`)
seen[option.Ident] = struct{}{}
}
}
}

for _, option := range objects.Options {
if option.SkipOption {
continue
}

if writeComment(o, option.Comment) {
o.L(`func %s(`, option.OptionName)
} else {
o.LL(`func %s(`, option.OptionName)
}
if argType := option.ArgumentType; argType != "" {
o.R(`v %s`, argType)
}
o.R(`) %s {`, option.Interface)

value := `v`
if cv := option.ConstantValue; cv != "" {
value = cv
}

o.L(`return &%s{option.New(ident%s{}, %s)}`, option.ConcreteType, option.Ident, value)
o.L(`}`)
}

if err := o.WriteFile(objects.Output, codegen.WithFormatCode(true)); err != nil {
if cfe, ok := err.(codegen.CodeFormatError); ok {
fmt.Fprint(os.Stderr, cfe.Source())
}
return fmt.Errorf(`failed to write to headers_gen.go: %w`, err)
}
return nil
}

func genOptionTests(objects *Objects) error {
var buf bytes.Buffer

o := codegen.NewOutput(&buf)

o.L("// This file is auto-generated by internal/cmd/genoptions/main.go. DO NOT EDIT")

o.LL(`package %s`, objects.PackageName)

o.LL(`func TestOptionIdent(t *testing.T) {`)
seen := make(map[string]struct{})
for _, option := range objects.Options {
_, ok := seen[option.Ident]
if ok {
continue
}

o.L(`require.Equal(t, %q, ident%s{}.String())`, option.OptionName, option.Ident)
seen[option.Ident] = struct{}{}
}

o.L(`}`)

filename := strings.Replace(objects.Output, `.go`, `_test.go`, -1)
if err := o.WriteFile(filename, codegen.WithFormatCode(true)); err != nil {
if cfe, ok := err.(codegen.CodeFormatError); ok {
fmt.Fprint(os.Stderr, cfe.Source())
}
return fmt.Errorf(`failed to write to headers_gen.go: %w`, err)
}

return nil
}

0 comments on commit 6c4c703

Please sign in to comment.