// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 package main import ( "fmt" "iter" "os" "regexp" "strings" sync "sync" "go.wit.com/log" ) // like 'goimport', but for .proto files var allTheLines *LinesScanner type EnumMessage struct { msgPB *FormatMsg } type StdMessage struct { msgPB *FormatMsg } func (msg *EnumMessage) name() string { return "fuckit enum" } func (msg *StdMessage) name() string { return "fuckit std" } type Messages interface { format() []string name() string } 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 } var newfile string /* check the comment preprocessor log.Info("filename", filename) alltest := makeLineIter(data) // gets the max vartype and varname for line := range alltest { newfile += fmt.Sprintln(commentPreprocessor(line)) } saveFile(filename, newfile) os.Exit(-1) */ var fmtmsg *FormatMsg fmtmsg = new(FormatMsg) var bigName int64 var bigType int64 var inMessage bool var allLinesIter iter.Seq[string] allLinesIter = makeLineIter(data) // gets the max vartype and varname for line := range allLinesIter { if strings.HasPrefix(line, "message ") { inMessage = true continue } // find the end of the message if strings.HasPrefix(line, "}") { inMessage = false setMaxSizes(fmtmsg) if bigName < fmtmsg.MaxVarname { bigName = fmtmsg.MaxVarname } if bigType < fmtmsg.MaxVartype { bigType = fmtmsg.MaxVartype } fmtmsg = new(FormatMsg) continue } // don't format or change anything when not in a "message {" section if !inMessage { continue } fmtmsg.Lines = append(fmtmsg.Lines, line) } fmtmsg.MaxVarname = bigName fmtmsg.MaxVartype = bigType // write out the messages allTheLines = newLinesScanner(strings.Split(string(data), "\n")) for allTheLines.Scan() { line := allTheLines.Next() if strings.HasPrefix(line, "oneof ") { newmsg := newStdMessage(fmtmsg, line) loadMsgDefinition(newmsg) for _, newline := range newmsg.format() { newfile += fmt.Sprintln(newline) } continue } if strings.HasPrefix(line, "enum ") { newmsg := newEnumMessage(fmtmsg, line) loadEnumDefinition(newmsg) for _, newline := range newmsg.format() { newfile += fmt.Sprintln(newline) } continue } if strings.HasPrefix(line, "message ") { newmsg := newStdMessage(fmtmsg, line) log.Info("got to message", line) for i, msg := range loadMsgDefinition(newmsg) { log.Info("got in", i, msg.name()) for _, newline := range msg.format() { newfile += fmt.Sprintln(newline) } } /* parts := strings.Fields(line) if len(parts) > 3 { // hack to actually indent comments on the message line itself. you're welcome start := parts[0] + " " + parts[1] + " " + parts[2] end := strings.Join(parts[3:], " ") offset := int(bigName) + int(bigType) + 16 - len(start) pad := fmt.Sprintf("%d", offset) hmm := "%s %" + pad + "s %s" line = fmt.Sprintf(hmm, start, " ", end) } newfile += fmt.Sprintln(line) */ continue } newfile += fmt.Sprintln(line) } return saveFile(filename, newfile) } func saveFile(filename string, data string) error { pf, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { log.Info("file open error. permissions?", filename, err) return err } data = strings.TrimSpace(data) fmt.Fprintln(pf, data) pf.Close() // for i, s := range slices.Backward(pf.ToSort) { return nil } func newStdMessage(fmtmsg *FormatMsg, header string) *StdMessage { newmsg := new(FormatMsg) newmsg.MaxVarname = fmtmsg.MaxVarname newmsg.MaxVartype = fmtmsg.MaxVartype newmsg.Header = header newstd := new(StdMessage) newstd.msgPB = newmsg return newstd } func newEnumMessage(fmtmsg *FormatMsg, header string) *EnumMessage { newmsg := new(FormatMsg) newmsg.MaxVarname = fmtmsg.MaxVarname newmsg.MaxVartype = fmtmsg.MaxVartype newmsg.Header = header newstd := new(EnumMessage) newstd.msgPB = newmsg return newstd } func loadMsgDefinition(msg *StdMessage) []Messages { var allMessages []Messages allMessages = append(allMessages, msg) fmtmsg := msg.msgPB for allTheLines.Scan() { line := allTheLines.Next() if strings.HasPrefix(line, "oneof ") { newmsg := newStdMessage(fmtmsg, line) allMessages = append(allMessages, newmsg) // fmtmsg.Oneofs = append(fmtmsg.Oneofs, newmsg) continue } if strings.HasPrefix(line, "enum ") { newmsg := newEnumMessage(fmtmsg, line) loadEnumDefinition(newmsg) allMessages = append(allMessages, newmsg) // fmtmsg.Enums = append(fmtmsg.Enums, newmsg) // log.Info("got here:", line) // os.Exit(-1) continue } if strings.HasPrefix(line, "message ") { // message inception. search for the architect. don't forget your totem newmsg := newStdMessage(fmtmsg, line) newAll := loadMsgDefinition(newmsg) allMessages = append(allMessages, newAll...) // fmtmsg.InceptionMsgs = append(fmtmsg.InceptionMsgs, newmsg) continue } if strings.HasPrefix(line, "}") { fmtmsg.Footer = line return allMessages } fmtmsg.Lines = append(fmtmsg.Lines, line) } return allMessages } // 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 } // 'for x := range' syntax using the smartly done golang 1.24 'iter' func makeLineIter(data []byte) iter.Seq[string] { items := strings.Split(string(data), "\n") // log.Println("Made All() Iter.Seq[] with length", len(items)) return func(yield func(string) bool) { for _, v := range items { if !yield(v) { return } } } } func loadEnumDefinition(newMsg *EnumMessage) *EnumMessage { curmsg := newMsg.msgPB for allTheLines.Scan() { line := allTheLines.Next() if strings.HasPrefix(line, "}") { curmsg.Footer = line newMsg.msgPB = curmsg return newMsg } curmsg.Lines = append(curmsg.Lines, line) } newMsg.msgPB = curmsg return newMsg } // find the max length of varname and vartype func setMaxSizes(curmsg *FormatMsg) { for _, line := range curmsg.Lines { parts := strings.Split(line, ";") if len(parts) < 2 { // line is blank or just a comment continue } vartype, varname, _, _ := tokenMsgVar(line) if len(vartype) > int(curmsg.MaxVartype) { curmsg.MaxVartype = int64(len(vartype)) } if len(varname) > int(curmsg.MaxVarname) { curmsg.MaxVarname = int64(len(varname)) } } } func (curmsg *FormatMsg) format() []string { return formatEnum(curmsg) } func (curmsg *EnumMessage) format() []string { return formatEnum(curmsg.msgPB) } func formatEnum(curmsg *FormatMsg) []string { var newmsg []string newmsg = append(newmsg, curmsg.Header) // +" //header") for _, line := range curmsg.Lines { line = " " + strings.TrimSpace(line) newmsg = append(newmsg, line) } newmsg = append(newmsg, curmsg.Footer) // +" //footer") return newmsg } func (curmsg *StdMessage) format() []string { return formatMessage(curmsg.msgPB) } func formatMessage(curmsg *FormatMsg) []string { var newmsg []string if curmsg.Header != "" { line := curmsg.Header parts := strings.Fields(line) if len(parts) > 3 { // hack to actually indent comments on the message line itself. you're welcome start := parts[0] + " " + parts[1] + " " + parts[2] end := strings.Join(parts[3:], " ") offset := int(curmsg.MaxVarname) + int(curmsg.MaxVartype) + 16 - len(start) pad := fmt.Sprintf("%d", offset) hmm := "%s %" + pad + "s %s" line = fmt.Sprintf(hmm, start, " ", end) } newmsg = append(newmsg, line) // +" //header") } // find the max length of varname and vartype setMaxSizes(curmsg) /* for _, line := range curmsg.Lines { parts := strings.Split(line, ";") if len(parts) < 2 { // line is blank or just a comment continue } vartype, varname, _, _ := tokenMsgVar(line) if len(vartype) > int(curmsg.MaxVartype) { curmsg.MaxVartype = int64(len(vartype)) } if len(varname) > int(curmsg.MaxVarname) { curmsg.MaxVarname = int64(len(varname)) } } */ /* for _, msg := range curmsg.Enums { for _, newline := range formatEnum(msg) { newmsg = append(newmsg, newline) } } for _, msg := range curmsg.Oneofs { for _, newline := range formatEnum(msg) { newmsg = append(newmsg, newline) } } for _, msg := range curmsg.Msgs { for _, newline := range msg.format() { newmsg = append(newmsg, newline) } } */ for _, line := range curmsg.Lines { line = strings.TrimSpace(line) if line == "" { newmsg = append(newmsg, line) continue } if strings.HasPrefix(line, "//") { pad := fmt.Sprintf("%d", curmsg.MaxVartype+curmsg.MaxVarname+21) hmm := "%" + pad + "s %s" line = fmt.Sprintf(hmm, " ", line) // todo: compute 50 newmsg = append(newmsg, line) continue } mt := fmt.Sprintf("%d", curmsg.MaxVartype) mv := fmt.Sprintf("%d", curmsg.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) newline = strings.TrimRight(newline, " ") newmsg = append(newmsg, newline) } newmsg = append(newmsg, curmsg.Footer) // +" //footer") return newmsg } // DEFINE THE Lines ITERATOR. // itializes a new iterator. func newLinesScanner(things []string) *LinesScanner { return &LinesScanner{things: things} } type LinesScanner struct { sync.Mutex things []string index int } func (it *LinesScanner) Scan() bool { if it.index >= len(it.things) { return false } it.Lock() it.index++ it.Unlock() return true } // Next() returns the next thing in the array func (it *LinesScanner) Next() string { if it.index-1 == len(it.things) { fmt.Println("Next() error in LinesScanner", it.index) } // out := commentPreprocessor(it.things[it.index-1]) out := it.things[it.index-1] out = commentPreprocessor(out) // return strings.TrimSpace(out) return out } // END DEFINE THE ITERATOR // turns: "/* test */ reserved /* linkPreviews */ 4;" // into: func commentPreprocessor(line string) string { // Match all /* comment */ blocks re := regexp.MustCompile(`/\*([^*]+)\*/`) matches := re.FindAllStringSubmatch(line, -1) // Extract just the comment texts var comments []string for _, match := range matches { comments = append(comments, strings.TrimSpace(match[1])) } // Remove the block comments from the original line line = re.ReplaceAllString(line, "") line = strings.TrimSpace(line) // Append comments at the end with // for _, comment := range comments { line += " // " + comment } return line }