From cfedee740ce59638de70382652a3d637398bcab2 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sat, 1 Feb 2025 06:54:55 -0600 Subject: [PATCH] goimport for protobuf --- Makefile | 2 +- example/fruit.proto | 3 +- example/fruit.proto.new | 77 +++++++++++++++++++ main.go | 1 + protoReformat.go | 161 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 example/fruit.proto.new create mode 100644 protoReformat.go diff --git a/Makefile b/Makefile index 1380f9a..3ac295e 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ install: test GO111MODULE=off go install \ -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}" -auto: +proto: # rm -f auto.pb.go autogenpb --proto file.proto --package main # rm -f auto.sort.pb.go auto.newsort.pb.go # auto.marshal.pb.go diff --git a/example/fruit.proto b/example/fruit.proto index b119652..6df0b89 100644 --- a/example/fruit.proto +++ b/example/fruit.proto @@ -71,7 +71,6 @@ message Fruits { // `autogenpb:marshal` `autogenpb:mutex` string uuid = 1; // `autogenpb:uuid:be926ad9-f07f-484c-adf2-d96eeabf3079` string version = 2; // `autogenpb:version:v0.0.1` repeated Fruit Fruits = 3; // THIS MUST BE "Fruit" and then "Fruit" + "s" - // you can add additional things here but the three lines above must conform to the standard above - int64 cost = 4; + int64 cost = 4; // you can add additional things here but the three lines above must conform to the standard above map junk = 5; } diff --git a/example/fruit.proto.new b/example/fruit.proto.new new file mode 100644 index 0000000..f9f39a0 --- /dev/null +++ b/example/fruit.proto.new @@ -0,0 +1,77 @@ +syntax = "proto3"; + +// this file is called "fruit.proto" +// +// for autogenpb to work, you must have: +// +// "Fruit" must exist. you can put anything in it +// +// and +// +// "Fruits" MUST EXIST and start exactly this way +// It must be "Fruit" + 's' and must match the name of this file: "fruit.proto" + +package main; + +import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp + +message Apple { + string name = 1; // `autogenpb:unique` // generates SortByxxx() and AppendUnique() functions + string genus = 2; // `autogenpb:unique` // generates same thing here but SortByGenus() + google.protobuf.Timestamp ctime = 3; // when the apple was born +} + +message Apples { + string name = 1; // `autogenpb:unique` // generates SortByxxx() and AppendUnique() functions + string genus = 2; // `autogenpb:unique` // generates same thing here but SortByGenus() + repeated Apple apples = 3; +} + +message Pear { // `autogenpb:nomutex` + string name = 1; // `autogenpb:sort` + string favorite = 2; // `autogenpb:sort` `autogenpb:unique` +} + +message Pears { // `autogenpb:nomutex` + string name = 1; // `autogenpb:sort` + string favorite = 2; // `autogenpb:sort` `autogenpb:unique` + repeated Pear pears = 3; +} + +message Banana { // `autogenpb:nomutex` + repeated string name = 1; // `autogenpb:sort` + string favorite = 2; // `autogenpb:sort` `autogenpb:unique` + string country = 3; // `autogenpb:sort` +} + +message Basket { // `autogenpb:nomutex` + repeated string name = 1; // `autogenpb:sort` `autogenpb:unique` + string favorite = 2; // `autogenpb:sort` `autogenpb:unique` + int64 price = 3; // `autogenpb:sort` + repeated Banana banna = 4; + repeated Pear pears = 5; + repeated Apple stacks = 6; +} + +// "Fruit" must exist. you can put anything in it +message Fruit { + string brand = 1; // `autogenpb:unique` `autogenpb:sort` + Apple apples = 2; + repeated Pear pears = 3; + string UPC = 4; // `autogenpb:sort` `autogenpb:unique` + string city = 5; // `autogenpb:sort` + Pears notpears = 6; + Pears fakepears = 7; + repeated Basket gifts = 8; +} + +// "Fruits" MUST EXIST and start exactly this way +// It must be "Fruit" + 's' and must match the name of this file: "fruit.proto" +message Fruits { // `autogenpb:marshal` `autogenpb:mutex` + string uuid = 1; // `autogenpb:uuid:be926ad9-f07f-484c-adf2-d96eeabf3079` + string version = 2; // `autogenpb:version:v0.0.1` + repeated Fruit Fruits = 3; // THIS MUST BE "Fruit" and then "Fruit" + "s" + int64 cost = 4; // you can add additional things here but the three lines above must conform to the standard above + map junk = 5; +} + diff --git a/main.go b/main.go index 78e58a2..cbf8eea 100644 --- a/main.go +++ b/main.go @@ -77,6 +77,7 @@ func main() { log.Info("autogenpb parse error:", err) badExit(err) } + protoReformat(argv.Proto) if pf.Bases == nil { badExit(fmt.Errorf("Base was nil. 'message %s {` did not exist", pf.Filebase)) diff --git a/protoReformat.go b/protoReformat.go new file mode 100644 index 0000000..26fae1f --- /dev/null +++ b/protoReformat.go @@ -0,0 +1,161 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "go.wit.com/log" +) + +// like 'goimport' but for .proto files + +var maxVarname int +var maxVartype int + +func protoReformat(filename string) error { + // read in the .proto file + data, err := os.ReadFile(filename) + if err != nil { + log.Info("file read failed", filename, err) + return err + } + + pf, err := os.OpenFile(filename+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + log.Info("file open error. permissions?", filename, err) + return err + } + defer pf.Close() + + var inMessage bool + var curmsg []string + + // gets the max vartype and varname + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "message ") { + inMessage = true + continue + } + + // find the end of the message + if strings.HasPrefix(line, "}") { + inMessage = false + formatMessage(curmsg) + curmsg = nil + continue + } + + // don't format or change anything when not in a "message {" section + if !inMessage { + continue + } + curmsg = append(curmsg, line) + } + + // parse the proto file for message struct names + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "message ") { + inMessage = true + parts := strings.Fields(line) + if len(parts) > 3 { + start := parts[0] + " " + parts[1] + " " + parts[2] + end := strings.Join(parts[3:], " ") + offset := maxVarname + maxVartype + 16 - len(start) + pad := fmt.Sprintf("%d", offset) + hmm := "%s %" + pad + "s %s" + line = fmt.Sprintf(hmm, start, " ", end) + } + fmt.Fprintln(pf, line) + continue + } + + // find the end of the message + if strings.HasPrefix(line, "}") { + inMessage = false + for _, newline := range formatMessage(curmsg) { + fmt.Fprintln(pf, newline) + } + fmt.Fprintln(pf, line) + curmsg = nil + continue + } + + // don't format or change anything when not in a "message {" section + if !inMessage { + fmt.Fprintln(pf, line) + continue + } + curmsg = append(curmsg, line) + } + + // for i, s := range slices.Backward(pf.ToSort) { + return nil +} + +func formatMessage(curmsg []string) []string { + var newmsg []string + + // find the max length of varname and vartype + for _, line := range curmsg { + parts := strings.Split(line, ";") + if len(parts) < 2 { + log.Info("parse error", line) + return curmsg + } + + vartype, varname, _, _ := tokenMsgVar(line) + if len(vartype) > maxVartype { + maxVartype = len(vartype) + } + if len(varname) > maxVarname { + maxVarname = len(varname) + } + } + + for _, line := range curmsg { + mt := fmt.Sprintf("%d", maxVartype) + mv := fmt.Sprintf("%d", maxVarname) + + hmm := " %-" + mt + "s %-" + mv + "s = %-3s %s" + + vartype, varname, id, end := tokenMsgVar(line) + end = strings.TrimSpace(end) + id = id + ";" + + newline := fmt.Sprintf(hmm, vartype, varname, id, end) + newmsg = append(newmsg, newline) + } + return newmsg +} + +// returns vartype, varname, id, end +func tokenMsgVar(line string) (string, string, string, string) { + parts := strings.Split(line, ";") + front := parts[0] + end := strings.Join(parts[1:], ";") + + var id string + var varname string + var vartype string + + parts = strings.Fields(front) + parts, id = slicesPop(parts) + parts, _ = slicesPop(parts) // this is the "=" sign + parts, varname = slicesPop(parts) + vartype = strings.Join(parts, " ") + + return vartype, varname, id, end +} + +func slicesPop(parts []string) ([]string, string) { + if len(parts) == 0 { + return nil, "" + } + if len(parts) == 1 { + return nil, parts[0] + } + x := len(parts) + end := parts[x-1] + return parts[0 : x-1], end +}