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 } type ActionsTemplateInput struct { Actions map[string]*Action Version string Responses bool } 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", } 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} 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), } 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), } 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) } }