signald-go/tools/generator/main.go

254 lines
6.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"strings"
"text/template"
)
type Protocol struct {
Types map[string]map[string]*Type
Actions map[string]map[string]*Action
}
type Type struct {
Fields map[string]*DataType
Request bool `json:"-"`
Doc string
Error bool
}
type DataType struct {
List bool
Type string
Version string
FieldName string
Doc string
}
type Action struct {
FnName string
Request string
Response string
Doc string
}
type StructsTemplateInput struct {
Types map[string]*Type
Version string
ImportVersions []string
Banner string
}
type ActionsTemplateInput struct {
Actions map[string]*Action
Version string
Responses bool
Banner string
}
var typeMap = map[string]string{
"int": "int32",
"Integer": "int32",
"Boolean": "bool",
"long": "int64",
"Long": "int64",
"UUID": "string",
"boolean": "bool",
"String": "string",
"Map": "map[string]string", // TODO: make signald print the actual key and value types
"Object": "interface{}",
}
var fieldNameMap = map[string]string{
"id": "ID",
"recipientGroupId": "RecipientGroupID",
"uuid": "UUID",
"gv1-migration": "Gv1Migration",
}
var banner = "DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo"
func (d *DataType) fixForVersion(field, version string) {
response, ok := typeMap[d.Type]
if ok {
if d.Type == "byte" && d.List {
d.List = false
}
d.Type = response
} else {
if d.Version != "" {
if d.Version == version {
d.Type = fmt.Sprintf("*%s", d.Type)
} else {
d.Type = fmt.Sprintf("*%s.%s", d.Version, d.Type)
}
}
}
fieldName, ok := fieldNameMap[field]
if ok {
d.FieldName = fieldName
} else {
components := strings.Split(field, "_")
for i, c := range components {
components[i] = strings.Title(c)
}
d.FieldName = strings.Join(components, "")
}
}
func main() {
var response Protocol
err := json.NewDecoder(os.Stdin).Decode(&response)
if err != nil {
log.Fatal(err, "\nError parsing stdin")
}
tmpl, err := template.ParseGlob("tools/generator/*.tmpl")
if err != nil {
log.Fatal(err, "\nError parsing templates from tools/generator/*.tmpl")
}
for version, actions := range response.Actions {
inputs := ActionsTemplateInput{Version: version, Responses: false, Banner: banner}
for action, a := range actions {
actions[action].FnName = strings.Title(action)
if a.Request != "" {
response.Types[version][a.Request].Request = true
}
if a.Response != "" {
inputs.Responses = true
}
if r, ok := typeMap[a.Response]; ok {
a.Response = r
}
}
inputs.Actions = actions
outputDir := fmt.Sprintf("signald/client-protocol/%s", version)
err = os.MkdirAll(outputDir, os.ModePerm)
if err != nil {
log.Fatal("Error creating", outputDir, err)
}
outputFilename := fmt.Sprintf("%s/%s", outputDir, "requests.go")
log.Println("Opening", outputFilename)
f, err := os.Create(outputFilename)
if err != nil {
log.Fatal(err, "\nfailed to open output file ", outputFilename)
}
err = tmpl.ExecuteTemplate(f, "requests.go.tmpl", inputs)
if err != nil {
log.Fatal(err, "\nfailed to render template")
}
err = exec.Command("gofmt", "-w", outputFilename).Run()
if err != nil {
log.Fatal(err, " error running gofmt on ", outputFilename)
}
fmt.Println(outputFilename)
}
for version, types := range response.Types {
inputs := StructsTemplateInput{
Version: version,
Types: make(map[string]*Type),
Banner: banner,
}
for typeName, t := range types {
for fieldName, field := range t.Fields {
types[typeName].Fields[fieldName].fixForVersion(fieldName, version)
if field.Version != "" && field.Version != version {
found := false
for _, v := range inputs.ImportVersions {
if v == field.Version {
found = true
break
}
}
if !found {
inputs.ImportVersions = append(inputs.ImportVersions, field.Version)
}
}
}
if !t.Error {
inputs.Types[typeName] = t
}
}
outputDir := fmt.Sprintf("signald/client-protocol/%s", version)
err = os.MkdirAll(outputDir, os.ModePerm)
if err != nil {
log.Fatal("Error creating", outputDir, err)
}
outputFilename := fmt.Sprintf("%s/%s", outputDir, "structs.go")
log.Println("Opening", outputFilename)
f, err := os.Create(outputFilename)
if err != nil {
log.Fatal(err, "\nfailed to open output file ", outputFilename)
}
err = tmpl.ExecuteTemplate(f, "structs.go.tmpl", inputs)
if err != nil {
log.Fatal(err, "\nfailed to render template")
}
err = exec.Command("gofmt", "-w", outputFilename).Run()
if err != nil {
log.Fatal(err, " error running gofmt on ", outputFilename)
}
fmt.Println(outputFilename)
}
// errors
for version, types := range response.Types {
inputs := StructsTemplateInput{
Version: version,
Types: make(map[string]*Type),
Banner: banner,
}
for typeName, t := range types {
for fieldName, field := range t.Fields {
types[typeName].Fields[fieldName].fixForVersion(fieldName, version)
if field.Version != "" && field.Version != version {
found := false
for _, v := range inputs.ImportVersions {
if v == field.Version {
found = true
break
}
}
if !found {
inputs.ImportVersions = append(inputs.ImportVersions, field.Version)
}
}
}
if t.Error {
inputs.Types[typeName] = t
}
}
if len(inputs.Types) == 0 {
continue
}
outputDir := fmt.Sprintf("signald/client-protocol/%s", version)
err = os.MkdirAll(outputDir, os.ModePerm)
if err != nil {
log.Fatal("Error creating", outputDir, err)
}
outputFilename := fmt.Sprintf("%s/%s", outputDir, "errors.go")
log.Println("Opening", outputFilename)
f, err := os.Create(outputFilename)
if err != nil {
log.Fatal(err, "\nfailed to open output file ", outputFilename)
}
err = tmpl.ExecuteTemplate(f, "errors.go.tmpl", inputs)
if err != nil {
log.Fatal(err, "\nfailed to render template")
}
err = exec.Command("gofmt", "-w", outputFilename).Run()
if err != nil {
log.Fatal(err, " error running gofmt on ", outputFilename)
}
fmt.Println(outputFilename)
}
}