package main // auto run protoc with the correct args import ( "bufio" "fmt" "os" "strings" "go.wit.com/log" "golang.org/x/text/cases" "golang.org/x/text/language" "github.com/google/uuid" ) // this parses the .proto file and handles anything with `autogenpb: ` // does the fruit.proto file have "message Fruits" func (pb *Files) hasPluralMessage(f *File) error { file, err := os.Open(f.Filename) if err != nil { return err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() base := cases.Title(language.English, cases.NoLower).String(f.Filebase) prefix := "message " + base + "s {" // to conform, it must have an added 's' if !strings.HasPrefix(line, prefix) { // log.Info("nope", prefix, "line", line) // nope, not this line continue } scanner.Scan() line = scanner.Text() fields := strings.Fields(line) // log.Info("GOT LINE", line) if fields[0] == "string" && fields[1] != "uuid" { f.noUuid() return fmt.Errorf("proto file does not have a UUID") } // ok, uuid is here f.Uuid = line log.Info("found UUID:", line) scanner.Scan() line = scanner.Text() fields = strings.Fields(line) // log.Info("GOT LINE", line) if fields[0] == "string" && fields[1] != "version" { f.noUuid() return fmt.Errorf("proto file does not have a version") } // found "version", the .proto file conforms f.Version = line log.Info("found Version:", line) return nil } f.noPluralMessage() return fmt.Errorf("proto file error %s", f.Filename) } func (pf *File) noPluralMessage() { base := cases.Title(language.English, cases.NoLower).String(pf.Filebase) log.Info("") log.Info("###########################################################################") log.Info("Your proto file", pf.Filename, "does not contain the correct 'message' definitions") log.Info("") log.Info("For autogenpb to work on your file", pf.Filename, ", you must have both names exactly:") log.Info("") log.Printf("message %s {\n", base) log.Info("}") log.Printf("message %s {\n", base+"s") log.Info("}") log.Info("") log.Info("###########################################################################") badExit(fmt.Errorf("proto file error %s", pf.Filename)) } // 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" // } func (pf *File) noUuid() { base := cases.Title(language.English, cases.NoLower).String(pf.Filebase) id := uuid.New() log.Info("") log.Info("###########################################################################") log.Info("Your proto file", pf.Filename, "is incorrect for 'message' ", base+"s") log.Info("") log.Info("For autogenpb to work on your file", pf.Filename, ",", base+"s must be exactly:") log.Info("") log.Info("message", base+"s", "{ // `autogenpb:marshal` `autogenpb:mutex`") log.Info(" string uuid = 1; // `autogenpb:uuid:" + id.String() + "`") log.Info(" string version = 2; // `autogenpb:version:v0.0.1`") log.Info(" repeated", base, base+"s", " = 3; // THIS MUST BE ", base, " and then ", base+"s") log.Info("}") log.Info("") log.Info("If you don't have a UUID, you can use the randomly generated one here") log.Info("") log.Info("###########################################################################") badExit(fmt.Errorf("proto file error %s", pf.Filename)) } func (pb *Files) protoParse(pf *File) error { // does the file conform to the standard? (also reads in UUID & Version) if err := pb.hasPluralMessage(pf); err != nil { return err } log.Info(pf.Filename, "is valid so far") // read in the .proto file data, err := os.ReadFile(pf.Filename) if err != nil { return err } var curmsg *MsgName // parse the proto file for message struct names for _, line := range strings.Split(string(data), "\n") { if strings.HasPrefix(line, "message ") { curmsg = pf.parseForMessage(line) } // this logic isn't right. find end of message with more bravado if strings.HasPrefix(line, "}") { curmsg = nil } if curmsg == nil { // log.Info("curmsg == nil", line) // can't contiue on nil below here continue } // log.Info("curmsg != nil", line) parts := strings.Fields(line) msgvar := parseMsgVar(line) if msgvar == nil { // log.Info("Junk in .proto file? line did not contain a message var:", line) continue } if msgvar.IsRepeated { log.Info("ADDING ITER MAP", curmsg.Name, msgvar.VarType) pf.IterMap[curmsg.Name] = msgvar.VarType } if strings.Contains(line, "autogenpb:sort") { newS := cases.Title(language.English, cases.NoLower).String(parts[1]) log.Info("Addded Sort:", newS, "in struct", curmsg.Name) curmsg.Sort = append(curmsg.Sort, newS) msgvar.HasSort = true } if strings.Contains(line, "autogenpb:unique") { newU := parts[1] newU = cases.Title(language.English, cases.NoLower).String(newU) log.Info("Added Unique:", newU, "in struct", curmsg.Name) curmsg.Unique = append(curmsg.Unique, newU) msgvar.HasUnique = true } curmsg.Vars = append(curmsg.Vars, msgvar) } pf.makeSortTable() // for i, s := range slices.Backward(pf.ToSort) { return nil } func (pf *File) makeSortTable() { pf.sortWhat(pf.Bases) pf.sortWhat(pf.Base) // everything else for _, msg := range pf.MsgNames { pf.sortWhat(msg) } } func (pf *File) sortWhat(msg *MsgName) { for _, v := range msg.Vars { if !v.IsRepeated { continue } if check := pf.findMsg(v.VarType); check == nil { // the VarType must be a struct continue } s := new(Sort) s.MsgName = msg.Name s.VarType = v.VarType s.VarName = v.VarName s.Lockname = msg.Lockname pf.ToSort = append(pf.ToSort, s) } } func parseMsgVar(line string) *MsgVar { if strings.Contains(line, "//") { parts := strings.Split(line, "//") if len(parts) == 0 { // log.Info("parseMsgVar() nothing line", line) return nil } line = parts[0] } parts := strings.Fields(line) if len(parts) < 3 { // log.Info("parseMsgVar() len < 3", parts) return nil } if parts[0] == "message" { // this is the struct return nil } v := new(MsgVar) if parts[0] == "repeated" { v.IsRepeated = true v.VarType = parts[1] v.VarName = cases.Title(language.English, cases.NoLower).String(parts[2]) return v } v.VarType = parts[0] v.VarName = cases.Title(language.English, cases.NoLower).String(parts[1]) return v } // looks for mutex and marshal entries func (pf *File) parseForMessage(line string) *MsgName { fields := strings.Fields(line) if len(fields) == 0 || fields[0] != "message" { return nil } var msg *MsgName msg = new(MsgName) base := cases.Title(language.English, cases.NoLower).String(pf.Filebase) prefix := "message " + base + "s {" // only look for this for now if strings.HasPrefix(line, prefix) { pf.Bases = msg } else { prefix := "message " + base + " {" // only look for this for now if strings.HasPrefix(line, prefix) { pf.Base = msg } else { pf.MsgNames = append(pf.MsgNames, msg) } } msgName := cases.Title(language.English, cases.NoLower).String(fields[1]) log.Info("found messge:", msgName) msg.Name = msgName msg.Lockname = pf.Filebase + "Mu" // this should be lowercase. do not export the Mutex msg.NeedIter = true if strings.Contains(line, "`autogenpb:mutex`") { msg.DoMutex = true log.Info("Added Mutex=true:", msg.Name) } if strings.Contains(line, "`autogenpb:nomutex`") { msg.NoMutex = true log.Info("Added Mutex=true:", msg.Name) } if strings.Contains(line, "`autogenpb:marshal`") { msg.DoMarshal = true log.Info("Added Marshal=true:", msg.Name) } return msg }