autogenpb/protoReformat.go

487 lines
11 KiB
Go

// 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
}