goext/bfcodegen/id-generate.go
Mike Schwörer da52bb5c90
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 49s
v0.0.264 added Valid() to id-gen
2023-09-18 11:46:17 +02:00

237 lines
6.2 KiB
Go

package bfcodegen
import (
"errors"
"fmt"
"gogs.mikescher.com/BlackForestBytes/goext"
"gogs.mikescher.com/BlackForestBytes/goext/cmdext"
"gogs.mikescher.com/BlackForestBytes/goext/cryptext"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rext"
"io"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"time"
)
type IDDef struct {
File string
FileRelative string
Name string
}
var rexIDPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]+)\s*$`))
var rexIDDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+string\s*//\s*(@id:type).*$`))
var rexIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumIDGenerator = "(?P<cs>[A-Za-z0-9_]*)"`))
func GenerateIDSpecs(sourceDir string, destFile string) error {
files, err := os.ReadDir(sourceDir)
if err != nil {
return err
}
oldChecksum := "N/A"
if _, err := os.Stat(destFile); !os.IsNotExist(err) {
content, err := os.ReadFile(destFile)
if err != nil {
return err
}
if m, ok := rexIDChecksumConst.MatchFirst(string(content)); ok {
oldChecksum = m.GroupByName("cs").Value()
}
}
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return v.Name() != path.Base(destFile) })
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return strings.HasSuffix(v.Name(), ".go") })
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return !strings.HasSuffix(v.Name(), "_gen.go") })
langext.SortBy(files, func(v os.DirEntry) string { return v.Name() })
newChecksumStr := goext.GoextVersion
for _, f := range files {
content, err := os.ReadFile(path.Join(sourceDir, f.Name()))
if err != nil {
return err
}
newChecksumStr += "\n" + f.Name() + "\t" + cryptext.BytesSha256(content)
}
newChecksum := cryptext.BytesSha256([]byte(newChecksumStr))
if newChecksum != oldChecksum {
fmt.Printf("[IDGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum)
} else {
fmt.Printf("[IDGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum)
return nil
}
allIDs := make([]IDDef, 0)
pkgname := ""
for _, f := range files {
fmt.Printf("========= %s =========\n\n", f.Name())
fileIDs, pn, err := processIDFile(sourceDir, path.Join(sourceDir, f.Name()))
if err != nil {
return err
}
fmt.Printf("\n")
allIDs = append(allIDs, fileIDs...)
if pn != "" {
pkgname = pn
}
}
if pkgname == "" {
return errors.New("no package name found in any file")
}
err = os.WriteFile(destFile, []byte(fmtIDOutput(newChecksum, allIDs, pkgname)), 0o755)
if err != nil {
return err
}
res, err := cmdext.RunCommand("go", []string{"fmt", destFile}, langext.Ptr(2*time.Second))
if err != nil {
return err
}
if res.CommandTimedOut {
fmt.Println(res.StdCombined)
return errors.New("go fmt timed out")
}
if res.ExitCode != 0 {
fmt.Println(res.StdCombined)
return errors.New("go fmt did not succeed")
}
return nil
}
func processIDFile(basedir string, fn string) ([]IDDef, string, error) {
file, err := os.Open(fn)
if err != nil {
return nil, "", err
}
defer func() { _ = file.Close() }()
bin, err := io.ReadAll(file)
if err != nil {
return nil, "", err
}
lines := strings.Split(string(bin), "\n")
ids := make([]IDDef, 0)
pkgname := ""
for i, line := range lines {
if i == 0 && strings.HasPrefix(line, "// Code generated by") {
break
}
if match, ok := rexIDPackage.MatchFirst(line); i == 0 && ok {
pkgname = match.GroupByName("name").Value()
continue
}
if match, ok := rexIDDef.MatchFirst(line); ok {
rfp, err := filepath.Rel(basedir, fn)
if err != nil {
return nil, "", err
}
def := IDDef{
File: fn,
FileRelative: rfp,
Name: match.GroupByName("name").Value(),
}
fmt.Printf("Found ID definition { '%s' }\n", def.Name)
ids = append(ids, def)
}
}
return ids, pkgname, nil
}
func fmtIDOutput(cs string, ids []IDDef, pkgname string) string {
str := "// Code generated by id-generate.go DO NOT EDIT.\n"
str += "\n"
str += "package " + pkgname + "\n"
str += "\n"
str += "import \"go.mongodb.org/mongo-driver/bson\"" + "\n"
str += "import \"go.mongodb.org/mongo-driver/bson/bsontype\"" + "\n"
str += "import \"go.mongodb.org/mongo-driver/bson/primitive\"" + "\n"
str += "import \"gogs.mikescher.com/BlackForestBytes/goext/exerr\"" + "\n"
str += "\n"
str += "const ChecksumIDGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n"
str += "\n"
anyDef := langext.ArrFirstOrNil(ids, func(def IDDef) bool { return def.Name == "AnyID" || def.Name == "AnyId" })
for _, iddef := range ids {
str += "// ================================ " + iddef.Name + " (" + iddef.FileRelative + ") ================================" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") MarshalBSONValue() (bsontype.Type, []byte, error) {" + "\n"
str += " if objId, err := primitive.ObjectIDFromHex(string(i)); err == nil {" + "\n"
str += " return bson.MarshalValue(objId)" + "\n"
str += " } else {" + "\n"
str += " return 0, nil, exerr.New(exerr.TypeMarshalEntityID, \"Failed to marshal " + iddef.Name + "(\"+i.String()+\") to ObjectId\").Str(\"value\", string(i)).Type(\"type\", i).Build()" + "\n"
str += " }" + "\n"
str += "}" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") String() string {" + "\n"
str += " return string(i)" + "\n"
str += "}" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") ObjID() (primitive.ObjectID, error) {" + "\n"
str += " return primitive.ObjectIDFromHex(string(i))" + "\n"
str += "}" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") Valid() bool {" + "\n"
str += " _, err := primitive.ObjectIDFromHex(string(i))" + "\n"
str += " return err == nil" + "\n"
str += "}" + "\n"
str += "" + "\n"
if anyDef != nil {
str += "func (i " + iddef.Name + ") AsAny() " + anyDef.Name + " {" + "\n"
str += " return " + anyDef.Name + "(i)" + "\n"
str += "}" + "\n"
str += "" + "\n"
}
str += "func New" + iddef.Name + "() " + iddef.Name + " {" + "\n"
str += " return " + iddef.Name + "(primitive.NewObjectID().Hex())" + "\n"
str += "}" + "\n"
str += "" + "\n"
}
return str
}