package fhelp // auto run protoc with the correct args import ( "bufio" "fmt" "os" "strings" "github.com/google/uuid" "go.wit.com/log" "golang.org/x/text/cases" "golang.org/x/text/language" ) /* This verifies a .proto file conforms to the autogenpb standard That means, for "toy.proto", there MUST exist: message toy { } message toys { } this parses the .proto file and handles anything with `autogenpb: ` does the fruit.proto file have "message Fruits" does it have a Uuid & valid version ? This returns (UUID, Version, error on failure) */ func ValidProtobuf(filename string) (string, string, error) { filebase := strings.TrimSuffix(filename, ".proto") base := cases.Title(language.English, cases.NoLower).String(filebase) pluralBase := base + "s" // to conform to autogenpb, it must have an added 's' file, err := os.Open(filename) if err != nil { return "", "", err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() prefix := "message " + pluralBase + " {" 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) if len(fields) < 2 { noUuidExit(filename) return "", "", fmt.Errorf("proto file does not have a UUID") } // log.Info("GOT LINE", line) if fields[0] == "string" && fields[1] != "uuid" { noUuidExit(filename) return "", "", fmt.Errorf("proto file does not have a UUID") } // ok, uuid is here UUID := parseUuid(line) if UUID == "" { noUuidExit(filename) return "", "", fmt.Errorf("proto file does not have a UUID") } log.Info("found UUID:", UUID) scanner.Scan() line = scanner.Text() fields = strings.Fields(line) // log.Info("GOT LINE", line) if fields[0] == "string" && fields[1] != "version" { noUuidExit(filename) return "", "", fmt.Errorf("proto file does not have a version") } // found "version", the .proto file conforms version := parseVersion(line) if version == "" { noUuidExit(filename) return "", "", fmt.Errorf("proto file does not have a version") } log.Info("found Version:", version) return UUID, version, nil } // right now, noPluralMessage does os.Exit(-1) noPluralMessageExit(filename) return "", "", fmt.Errorf("proto file does not have message %s", pluralBase) } func parseUuid(line string) string { fields := strings.Split(line, "autogenpb:uuid:") if len(fields) < 2 { return "" } end := fields[1] fields = strings.Fields(end) uuid := fields[0] uuid = strings.Trim(uuid, "`") // log.Info("fhelp.parseUuid() uuid =", uuid) return uuid } func parseVersion(line string) string { fields := strings.Split(line, "autogenpb:version:") if len(fields) < 2 { return "" } end := fields[1] fields = strings.Fields(end) ver := fields[0] ver = strings.Trim(ver, "`") // log.Info("fhelp.parseVersion() ver =", ver) return ver } func noPluralMessageExit(filename string) { filebase := strings.TrimSuffix(filename, ".proto") base := cases.Title(language.English, cases.NoLower).String(filebase) log.Info("") log.Info("###########################################################################") log.Info("Your proto file", filename, "does not contain the correct 'message' definitions") log.Info("") log.Info("For autogenpb to work on your file", 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", 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 noUuidExit(filename string) { filebase := strings.TrimSuffix(filename, ".proto") base := cases.Title(language.English, cases.NoLower).String(filebase) id := uuid.New() log.Info("") log.Info("###########################################################################") log.Info("Your proto file", filename, "is incorrect for 'message' ", base+"s") log.Info("") log.Info("For autogenpb to work on your file", 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("If you wish to ignore this, set PROTOBUF_REGRET=true") log.Info("You will probably regret not trying to follow the standard here. This format is used as a 'handshake'.") log.Info("###########################################################################") if os.Getenv("PROTOBUF_REGRET") == "true" { log.Info("PROTOBUF_REGRET=true was set") log.Info("###########################################################################") return } badExit(fmt.Errorf("proto file error %s", filename)) } func userInstructions() { log.Info("This is likely because you don't have protoc and protoc-gen-go installed") log.Info("") log.Info("On debian, you can:") log.Info(" apt install protobuf-compiler # for protoc") log.Info(" apt install protoc-gen-go # for protoc-gen-go") log.Info("") }