diff --git a/bfcodegen/_test_example.tgz b/bfcodegen/_test_example.tgz index 500f74a..a9acdf9 100644 Binary files a/bfcodegen/_test_example.tgz and b/bfcodegen/_test_example.tgz differ diff --git a/bfcodegen/csid-generate.go b/bfcodegen/csid-generate.go index e23587b..c92422e 100644 --- a/bfcodegen/csid-generate.go +++ b/bfcodegen/csid-generate.go @@ -1,6 +1,8 @@ package bfcodegen import ( + "bytes" + _ "embed" "errors" "fmt" "gogs.mikescher.com/BlackForestBytes/goext" @@ -14,6 +16,7 @@ import ( "path/filepath" "regexp" "strings" + "text/template" "time" ) @@ -30,6 +33,9 @@ var rexCSIDDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P[A-Za-z0-9_]+)\s var rexCSIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumCharsetIDGenerator = "(?P[A-Za-z0-9_]*)"`)) +//go:embed csid-generate.template +var templateCSIDGenerateText string + func GenerateCharsetIDSpecs(sourceDir string, destFile string) error { files, err := os.ReadDir(sourceDir) @@ -168,198 +174,19 @@ func processCSIDFile(basedir string, fn string) ([]CSIDDef, string, error) { } func fmtCSIDOutput(cs string, ids []CSIDDef, pkgname string) string { - str := "// Code generated by id-generate.go DO NOT EDIT.\n" - str += "\n" - str += "package " + pkgname + "\n" - str += "\n" + templ := template.Must(template.New("csid-generate").Parse(templateCSIDGenerateText)) - str += `import "crypto/rand"` + "\n" - str += `import "fmt"` + "\n" - str += `import "github.com/go-playground/validator/v10"` + "\n" - str += `import "github.com/rs/zerolog/log"` + "\n" - str += `import "gogs.mikescher.com/BlackForestBytes/goext/exerr"` + "\n" - str += `import "gogs.mikescher.com/BlackForestBytes/goext/langext"` + "\n" - str += `import "gogs.mikescher.com/BlackForestBytes/goext/rext"` + "\n" - str += `import "math/big"` + "\n" - str += `import "reflect"` + "\n" - str += `import "regexp"` + "\n" - str += `import "strings"` + "\n" - str += "\n" - - str += "const ChecksumCharsetIDGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n" - str += "\n" - - str += "const idlen = 24\n" - str += "\n" - str += "const checklen = 1\n" - str += "\n" - str += `const idCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"` + "\n" - str += "const idCharsetLen = len(idCharset)\n" - str += "\n" - str += "var charSetReverseMap = generateCharsetMap()\n" - str += "\n" - str += "const (\n" - for _, iddef := range ids { - str += " prefix" + iddef.Name + " = \"" + iddef.Prefix + "\"" + "\n" - } - str += ")\n" - str += "\n" - str += "var (\n" - for _, iddef := range ids { - str += " regex" + iddef.Name + " = generateRegex(prefix" + iddef.Name + ")" + "\n" - } - str += ")\n" - str += "\n" - str += "func generateRegex(prefix string) rext.Regex {\n" - str += " return rext.W(regexp.MustCompile(fmt.Sprintf(\"^%s[%s]{%d}[%s]{%d}$\", prefix, idCharset, idlen-len(prefix)-checklen, idCharset, checklen)))\n" - str += "}\n" - str += "\n" - - str += `func generateCharsetMap() []int {` + "\n" - str += ` result := make([]int, 128)` + "\n" - str += ` for i := 0; i < len(result); i++ {` + "\n" - str += ` result[i] = -1` + "\n" - str += ` }` + "\n" - str += ` for idx, chr := range idCharset {` + "\n" - str += ` result[int(chr)] = idx` + "\n" - str += ` }` + "\n" - str += ` return result` + "\n" - str += `}` + "\n" - str += "\n" - str += `func generateID(prefix string) string {` + "\n" - str += ` k := ""` + "\n" - str += ` max := big.NewInt(int64(idCharsetLen))` + "\n" - str += ` checksum := 0` + "\n" - str += ` for i := 0; i < idlen-len(prefix)-checklen; i++ {` + "\n" - str += ` v, err := rand.Int(rand.Reader, max)` + "\n" - str += ` if err != nil {` + "\n" - str += ` panic(err)` + "\n" - str += ` }` + "\n" - str += ` v64 := v.Int64()` + "\n" - str += ` k += string(idCharset[v64])` + "\n" - str += ` checksum = (checksum + int(v64)) % (idCharsetLen)` + "\n" - str += ` }` + "\n" - str += ` checkstr := string(idCharset[checksum%idCharsetLen])` + "\n" - str += ` return prefix + k + checkstr` + "\n" - str += `}` + "\n" - str += "\n" - str += `func validateID(prefix string, value string) error {` + "\n" - str += ` if len(value) != idlen {` + "\n" - str += ` return exerr.New(exerr.TypeInvalidCSID, "id has the wrong length").Str("value", value).Build()` + "\n" - str += ` }` + "\n" - str += "\n" - str += ` if !strings.HasPrefix(value, prefix) {` + "\n" - str += ` return exerr.New(exerr.TypeInvalidCSID, "id is missing the correct prefix").Str("value", value).Str("prefix", prefix).Build()` + "\n" - str += ` }` + "\n" - str += "\n" - str += ` checksum := 0` + "\n" - str += ` for i := len(prefix); i < len(value)-checklen; i++ {` + "\n" - str += ` ichr := int(value[i])` + "\n" - str += ` if ichr < 0 || ichr >= len(charSetReverseMap) || charSetReverseMap[ichr] == -1 {` + "\n" - str += ` return exerr.New(exerr.TypeInvalidCSID, "id contains invalid characters").Str("value", value).Build()` + "\n" - str += ` }` + "\n" - str += ` checksum = (checksum + charSetReverseMap[ichr]) % (idCharsetLen)` + "\n" - str += ` }` + "\n" - str += "\n" - str += ` checkstr := string(idCharset[checksum%idCharsetLen])` + "\n" - str += "\n" - str += ` if !strings.HasSuffix(value, checkstr) {` + "\n" - str += ` return exerr.New(exerr.TypeInvalidCSID, "id checkstring is invalid").Str("value", value).Str("checkstr", checkstr).Build()` + "\n" - str += ` }` + "\n" - str += "\n" - str += ` return nil` + "\n" - str += `}` + "\n" - str += "\n" - str += `func getRawData(prefix string, value string) string {` + "\n" - str += ` if len(value) != idlen {` + "\n" - str += ` return ""` + "\n" - str += ` }` + "\n" - str += ` return value[len(prefix) : idlen-checklen]` + "\n" - str += `}` + "\n" - str += "\n" - str += `func getCheckString(prefix string, value string) string {` + "\n" - str += ` if len(value) != idlen {` + "\n" - str += ` return ""` + "\n" - str += ` }` + "\n" - str += ` return value[idlen-checklen:]` + "\n" - str += `}` + "\n" - str += "\n" - str += `func ValidateEntityID(vfl validator.FieldLevel) bool {` + "\n" - str += ` if !vfl.Field().CanInterface() {` + "\n" - str += ` log.Error().Msgf("Failed to validate EntityID (cannot interface ?!?)")` + "\n" - str += ` return false` + "\n" - str += ` }` + "\n" - str += "\n" - str += ` ifvalue := vfl.Field().Interface()` + "\n" - str += "\n" - str += ` if value1, ok := ifvalue.(EntityID); ok {` + "\n" - str += "\n" - str += ` if vfl.Field().Type().Kind() == reflect.Pointer && langext.IsNil(value1) {` + "\n" - str += ` return true` + "\n" - str += ` }` + "\n" - str += "\n" - str += ` if err := value1.Valid(); err != nil {` + "\n" - str += ` log.Debug().Msgf("Failed to validate EntityID '%s' (%s)", value1.String(), err.Error())` + "\n" - str += ` return false` + "\n" - str += ` } else {` + "\n" - str += ` return true` + "\n" - str += ` }` + "\n" - str += "\n" - str += ` } else {` + "\n" - str += ` log.Error().Msgf("Failed to validate EntityID (wrong type: %T)", ifvalue)` + "\n" - str += ` return false` + "\n" - str += ` }` + "\n" - str += `}` + "\n" - str += "\n" - - for _, iddef := range ids { - - str += "// ================================ " + iddef.Name + " (" + iddef.FileRelative + ") ================================" + "\n" - str += "" + "\n" - - str += "func New" + iddef.Name + "() " + iddef.Name + " {" + "\n" - str += " return " + iddef.Name + "(generateID(prefix" + iddef.Name + "))" + "\n" - str += "}" + "\n" - - str += "" + "\n" - - str += "func (id " + iddef.Name + ") Valid() error {" + "\n" - str += " return validateID(prefix" + iddef.Name + ", string(id))" + "\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 + ") Prefix() string {" + "\n" - str += " return prefix" + iddef.Name + "" + "\n" - str += "}" + "\n" - - str += "" + "\n" - - str += "func (id " + iddef.Name + ") Raw() string {" + "\n" - str += " return getRawData(prefix" + iddef.Name + ", string(id))" + "\n" - str += "}" + "\n" - - str += "" + "\n" - - str += "func (id " + iddef.Name + ") CheckString() string {" + "\n" - str += " return getCheckString(prefix" + iddef.Name + ", string(id))" + "\n" - str += "}" + "\n" - - str += "" + "\n" - - str += "func (id " + iddef.Name + ") Regex() rext.Regex {" + "\n" - str += " return regex" + iddef.Name + "" + "\n" - str += "}" + "\n" - - str += "" + "\n" + buffer := bytes.Buffer{} + err := templ.Execute(&buffer, langext.H{ + "PkgName": pkgname, + "Checksum": cs, + "GoextVersion": goext.GoextVersion, + "IDs": ids, + }) + if err != nil { + panic(err) } - return str + return buffer.String() } diff --git a/bfcodegen/csid-generate.template b/bfcodegen/csid-generate.template new file mode 100644 index 0000000..a512320 --- /dev/null +++ b/bfcodegen/csid-generate.template @@ -0,0 +1,168 @@ +// Code generated by csid-generate.go DO NOT EDIT. + +package {{.PkgName}} + +import "crypto/rand" +import "fmt" +import "github.com/go-playground/validator/v10" +import "github.com/rs/zerolog/log" +import "gogs.mikescher.com/BlackForestBytes/goext/exerr" +import "gogs.mikescher.com/BlackForestBytes/goext/langext" +import "gogs.mikescher.com/BlackForestBytes/goext/rext" +import "math/big" +import "reflect" +import "regexp" +import "strings" + +const ChecksumCharsetIDGenerator = "{{.Checksum}}" // GoExtVersion: {{.GoextVersion}} + +const idlen = 24 + +const checklen = 1 + +const idCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +const idCharsetLen = len(idCharset) + +var charSetReverseMap = generateCharsetMap() + +const ({{range .IDs}} + prefix{{.Name}} = "{{.Prefix}}" {{end}} +) + +var ({{range .IDs}} + regex{{.Name}} = generateRegex(prefix{{.Name}}) {{end}} +) + +func generateRegex(prefix string) rext.Regex { + return rext.W(regexp.MustCompile(fmt.Sprintf("^%s[%s]{%d}[%s]{%d}$", prefix, idCharset, idlen-len(prefix)-checklen, idCharset, checklen))) +} + +func generateCharsetMap() []int { + result := make([]int, 128) + for i := 0; i < len(result); i++ { + result[i] = -1 + } + for idx, chr := range idCharset { + result[int(chr)] = idx + } + return result +} + +func generateID(prefix string) string { + k := "" + csMax := big.NewInt(int64(idCharsetLen)) + checksum := 0 + for i := 0; i < idlen-len(prefix)-checklen; i++ { + v, err := rand.Int(rand.Reader, csMax) + if err != nil { + panic(err) + } + v64 := v.Int64() + k += string(idCharset[v64]) + checksum = (checksum + int(v64)) % (idCharsetLen) + } + checkstr := string(idCharset[checksum%idCharsetLen]) + return prefix + k + checkstr +} + +func validateID(prefix string, value string) error { + if len(value) != idlen { + return exerr.New(exerr.TypeInvalidCSID, "id has the wrong length").Str("value", value).Build() + } + + if !strings.HasPrefix(value, prefix) { + return exerr.New(exerr.TypeInvalidCSID, "id is missing the correct prefix").Str("value", value).Str("prefix", prefix).Build() + } + + checksum := 0 + for i := len(prefix); i < len(value)-checklen; i++ { + ichr := int(value[i]) + if ichr < 0 || ichr >= len(charSetReverseMap) || charSetReverseMap[ichr] == -1 { + return exerr.New(exerr.TypeInvalidCSID, "id contains invalid characters").Str("value", value).Build() + } + checksum = (checksum + charSetReverseMap[ichr]) % (idCharsetLen) + } + + checkstr := string(idCharset[checksum%idCharsetLen]) + + if !strings.HasSuffix(value, checkstr) { + return exerr.New(exerr.TypeInvalidCSID, "id checkstring is invalid").Str("value", value).Str("checkstr", checkstr).Build() + } + + return nil +} + +func getRawData(prefix string, value string) string { + if len(value) != idlen { + return "" + } + return value[len(prefix) : idlen-checklen] +} + +func getCheckString(prefix string, value string) string { + if len(value) != idlen { + return "" + } + return value[idlen-checklen:] +} + +func ValidateEntityID(vfl validator.FieldLevel) bool { + if !vfl.Field().CanInterface() { + log.Error().Msgf("Failed to validate EntityID (cannot interface ?!?)") + return false + } + + ifvalue := vfl.Field().Interface() + + if value1, ok := ifvalue.(EntityID); ok { + + if vfl.Field().Type().Kind() == reflect.Pointer && langext.IsNil(value1) { + return true + } + + if err := value1.Valid(); err != nil { + log.Debug().Msgf("Failed to validate EntityID '%s' (%s)", value1.String(), err.Error()) + return false + } else { + return true + } + + } else { + log.Error().Msgf("Failed to validate EntityID (wrong type: %T)", ifvalue) + return false + } +} + +{{range .IDs}} + +// ================================ {{.Name}} ({{.FileRelative}}) ================================ + +func New{{.Name}}() {{.Name}} { + return {{.Name}}(generateID(prefix{{.Name}})) +} + +func (id {{.Name}}) Valid() error { + return validateID(prefix{{.Name}}, string(id)) +} + +func (i {{.Name}}) String() string { + return string(i) +} + +func (i {{.Name}}) Prefix() string { + return prefix{{.Name}} +} + +func (id {{.Name}}) Raw() string { + return getRawData(prefix{{.Name}}, string(id)) +} + +func (id {{.Name}}) CheckString() string { + return getCheckString(prefix{{.Name}}, string(id)) +} + +func (id {{.Name}}) Regex() rext.Regex { + return regex{{.Name}} +} + +{{end}} \ No newline at end of file diff --git a/bfcodegen/csid-generate_test.go b/bfcodegen/csid-generate_test.go new file mode 100644 index 0000000..8f7c665 --- /dev/null +++ b/bfcodegen/csid-generate_test.go @@ -0,0 +1,52 @@ +package bfcodegen + +import ( + _ "embed" + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/cmdext" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "gogs.mikescher.com/BlackForestBytes/goext/tst" + "os" + "path/filepath" + "testing" + "time" +) + +//go:embed _test_example.tgz +var CSIDExampleModels []byte + +func TestGenerateCSIDSpecs(t *testing.T) { + + tmpFile := filepath.Join(t.TempDir(), langext.MustHexUUID()+".tgz") + + tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) + + err := os.WriteFile(tmpFile, CSIDExampleModels, 0o777) + tst.AssertNoErr(t, err) + + t.Cleanup(func() { _ = os.Remove(tmpFile) }) + + err = os.Mkdir(tmpDir, 0o777) + tst.AssertNoErr(t, err) + + t.Cleanup(func() { _ = os.RemoveAll(tmpFile) }) + + _, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run() + tst.AssertNoErr(t, err) + + err = GenerateCharsetIDSpecs(tmpDir, tmpDir+"/csid_gen.go") + tst.AssertNoErr(t, err) + + err = GenerateCharsetIDSpecs(tmpDir, tmpDir+"/csid_gen.go") + tst.AssertNoErr(t, err) + + fmt.Println() + fmt.Println() + fmt.Println() + fmt.Println("=====================================================================================================") + fmt.Println(string(tst.Must(os.ReadFile(tmpDir + "/csid_gen.go"))(t))) + fmt.Println("=====================================================================================================") + fmt.Println() + fmt.Println() + fmt.Println() +} diff --git a/bfcodegen/enum-generate.go b/bfcodegen/enum-generate.go index 6df552d..cee3c53 100644 --- a/bfcodegen/enum-generate.go +++ b/bfcodegen/enum-generate.go @@ -1,6 +1,8 @@ package bfcodegen import ( + "bytes" + _ "embed" "errors" "fmt" "gogs.mikescher.com/BlackForestBytes/goext" @@ -14,6 +16,7 @@ import ( "path/filepath" "regexp" "strings" + "text/template" "time" ) @@ -39,6 +42,9 @@ var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P[A-Za-z0-9_]+)\s+( var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P[A-Za-z0-9_]*)"`)) +//go:embed enum-generate.template +var templateEnumGenerateText string + func GenerateEnumSpecs(sourceDir string, destFile string) error { files, err := os.ReadDir(sourceDir) @@ -204,133 +210,32 @@ func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) { } func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string { - str := "// Code generated by enum-generate.go DO NOT EDIT.\n" - str += "\n" - str += "package " + pkgname + "\n" - str += "\n" - str += "import \"gogs.mikescher.com/BlackForestBytes/goext/langext\"" + "\n" - str += "import \"gogs.mikescher.com/BlackForestBytes/goext/enums\"" + "\n" - str += "\n" + templ := template.New("enum-generate") - str += "const ChecksumEnumGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n" - str += "\n" + templ = templ.Funcs(template.FuncMap{ + "boolToStr": func(b bool) string { return langext.Conditional(b, "true", "false") }, + "deref": func(v *string) string { return *v }, + "trimSpace": func(str string) string { return strings.TrimSpace(str) }, + "hasStr": func(v EnumDef) bool { return v.Type == "string" }, + "hasDescr": func(v EnumDef) bool { + return langext.ArrAll(v.Values, func(val EnumDefVal) bool { return val.Description != nil }) + }, + }) - for _, enumdef := range enums { + templ = template.Must(templ.Parse(templateEnumGenerateText)) - hasDescr := langext.ArrAll(enumdef.Values, func(val EnumDefVal) bool { return val.Description != nil }) - hasStr := enumdef.Type == "string" - - str += "// ================================ " + enumdef.EnumTypeName + " ================================" + "\n" - str += "//" + "\n" - str += "// File: " + enumdef.FileRelative + "\n" - str += "// StringEnum: " + langext.Conditional(hasStr, "true", "false") + "\n" - str += "// DescrEnum: " + langext.Conditional(hasDescr, "true", "false") + "\n" - str += "//" + "\n" - str += "" + "\n" - - str += "var __" + enumdef.EnumTypeName + "Values = []" + enumdef.EnumTypeName + "{" + "\n" - for _, v := range enumdef.Values { - str += " " + v.VarName + "," + "\n" - } - str += "}" + "\n" - str += "" + "\n" - - if hasDescr { - str += "var __" + enumdef.EnumTypeName + "Descriptions = map[" + enumdef.EnumTypeName + "]string{" + "\n" - for _, v := range enumdef.Values { - str += " " + v.VarName + ": \"" + strings.TrimSpace(*v.Description) + "\"," + "\n" - } - str += "}" + "\n" - str += "" + "\n" - } - - str += "var __" + enumdef.EnumTypeName + "Varnames = map[" + enumdef.EnumTypeName + "]string{" + "\n" - for _, v := range enumdef.Values { - str += " " + v.VarName + ": \"" + v.VarName + "\"," + "\n" - } - str += "}" + "\n" - str += "" + "\n" - - str += "func (e " + enumdef.EnumTypeName + ") Valid() bool {" + "\n" - str += " return langext.InArray(e, __" + enumdef.EnumTypeName + "Values)" + "\n" - str += "}" + "\n" - str += "" + "\n" - - str += "func (e " + enumdef.EnumTypeName + ") Values() []" + enumdef.EnumTypeName + " {" + "\n" - str += " return __" + enumdef.EnumTypeName + "Values" + "\n" - str += "}" + "\n" - str += "" + "\n" - - str += "func (e " + enumdef.EnumTypeName + ") ValuesAny() []any {" + "\n" - str += " return langext.ArrCastToAny(__" + enumdef.EnumTypeName + "Values)" + "\n" - str += "}" + "\n" - str += "" + "\n" - - str += "func (e " + enumdef.EnumTypeName + ") ValuesMeta() []enums.EnumMetaValue {" + "\n" - str += " return " + enumdef.EnumTypeName + "ValuesMeta()" - str += "}" + "\n" - str += "" + "\n" - - if hasStr { - str += "func (e " + enumdef.EnumTypeName + ") String() string {" + "\n" - str += " return string(e)" + "\n" - str += "}" + "\n" - str += "" + "\n" - } - - if hasDescr { - str += "func (e " + enumdef.EnumTypeName + ") Description() string {" + "\n" - str += " if d, ok := __" + enumdef.EnumTypeName + "Descriptions[e]; ok {" + "\n" - str += " return d" + "\n" - str += " }" + "\n" - str += " return \"\"" + "\n" - str += "}" + "\n" - str += "" + "\n" - } - - str += "func (e " + enumdef.EnumTypeName + ") VarName() string {" + "\n" - str += " if d, ok := __" + enumdef.EnumTypeName + "Varnames[e]; ok {" + "\n" - str += " return d" + "\n" - str += " }" + "\n" - str += " return \"\"" + "\n" - str += "}" + "\n" - str += "" + "\n" - - str += "func (e " + enumdef.EnumTypeName + ") Meta() enums.EnumMetaValue {" + "\n" - if hasDescr { - str += " return enums.EnumMetaValue{VarName: e.VarName(), Value: e, Description: langext.Ptr(e.Description())}" - } else { - str += " return enums.EnumMetaValue{VarName: e.VarName(), Value: e, Description: nil}" - } - str += "}" + "\n" - str += "" + "\n" - - str += "func Parse" + enumdef.EnumTypeName + "(vv string) (" + enumdef.EnumTypeName + ", bool) {" + "\n" - str += " for _, ev := range __" + enumdef.EnumTypeName + "Values {" + "\n" - str += " if string(ev) == vv {" + "\n" - str += " return ev, true" + "\n" - str += " }" + "\n" - str += " }" + "\n" - str += " return \"\", false" + "\n" - str += "}" + "\n" - str += "" + "\n" - - str += "func " + enumdef.EnumTypeName + "Values() []" + enumdef.EnumTypeName + " {" + "\n" - str += " return __" + enumdef.EnumTypeName + "Values" + "\n" - str += "}" + "\n" - str += "" + "\n" - - str += "func " + enumdef.EnumTypeName + "ValuesMeta() []enums.EnumMetaValue {" + "\n" - str += " return []enums.EnumMetaValue{" + "\n" - for _, v := range enumdef.Values { - str += " " + v.VarName + ".Meta(),\n" - } - str += " }" + "\n" - str += "}" + "\n" - str += "" + "\n" + buffer := bytes.Buffer{} + err := templ.Execute(&buffer, langext.H{ + "PkgName": pkgname, + "Checksum": cs, + "GoextVersion": goext.GoextVersion, + "Enums": enums, + }) + if err != nil { + panic(err) } - return str + return buffer.String() } diff --git a/bfcodegen/enum-generate.template b/bfcodegen/enum-generate.template new file mode 100644 index 0000000..11c3249 --- /dev/null +++ b/bfcodegen/enum-generate.template @@ -0,0 +1,97 @@ +// Code generated by enum-generate.go DO NOT EDIT. + +package {{.PkgName}} + +import "gogs.mikescher.com/BlackForestBytes/goext/langext" +import "gogs.mikescher.com/BlackForestBytes/goext/enums" + +const ChecksumEnumGenerator = "{{.Checksum}}" // GoExtVersion: {{.GoextVersion}} + +{{range .Enums}} + +{{ $hasStr := ( . | hasStr ) }} +{{ $hasDescr := ( . | hasDescr ) }} + +// ================================ {{.EnumTypeName}} ================================ +// +// File: {{.FileRelative}} +// StringEnum: {{$hasStr | boolToStr}} +// DescrEnum: {{$hasDescr | boolToStr}} +// + +var __{{.EnumTypeName}}Values = []{{.EnumTypeName}}{ {{range .Values}} + {{.VarName}}, {{end}} +} + +{{if $hasDescr}} +var __{{.EnumTypeName}}Descriptions = map[{{.EnumTypeName}}]string{ {{range .Values}} + {{.VarName}}: "{{.Description | deref | trimSpace}}", {{end}} +} +{{end}} + +var __{{.EnumTypeName}}Varnames = map[{{.EnumTypeName}}]string{ {{range .Values}} + {{.VarName}}: "{{.VarName}}", {{end}} +} + +func (e {{.EnumTypeName}}) Valid() bool { + return langext.InArray(e, __{{.EnumTypeName}}Values) +} + +func (e {{.EnumTypeName}}) Values() []{{.EnumTypeName}} { + return __{{.EnumTypeName}}Values +} + +func (e {{.EnumTypeName}}) ValuesAny() []any { + return langext.ArrCastToAny(__{{.EnumTypeName}}Values) +} + +func (e {{.EnumTypeName}}) ValuesMeta() []enums.EnumMetaValue { + return {{.EnumTypeName}}ValuesMeta() +} + +{{if $hasStr}} +func (e {{.EnumTypeName}}) String() string { + return string(e) +} +{{end}} + +{{if $hasDescr}} +func (e {{.EnumTypeName}}) Description() string { + if d, ok := __{{.EnumTypeName}}Descriptions[e]; ok { + return d + } + return "" +} +{{end}} + +func (e {{.EnumTypeName}}) VarName() string { + if d, ok := __{{.EnumTypeName}}Varnames[e]; ok { + return d + } + return "" +} + +func (e {{.EnumTypeName}}) Meta() enums.EnumMetaValue { + {{if $hasDescr}} return enums.EnumMetaValue{VarName: e.VarName(), Value: e, Description: langext.Ptr(e.Description())} {{else}} return enums.EnumMetaValue{VarName: e.VarName(), Value: e, Description: nil} {{end}} +} + +func Parse{{.EnumTypeName}}(vv string) ({{.EnumTypeName}}, bool) { + for _, ev := range __{{.EnumTypeName}}Values { + if string(ev) == vv { + return ev, true + } + } + return "", false +} + +func {{.EnumTypeName}}Values() []{{.EnumTypeName}} { + return __{{.EnumTypeName}}Values +} + +func {{.EnumTypeName}}ValuesMeta() []enums.EnumMetaValue { + return []enums.EnumMetaValue{ {{range .Values}} + {{.VarName}}.Meta(), {{end}} + } +} + +{{end}} \ No newline at end of file diff --git a/bfcodegen/enum-generate_test.go b/bfcodegen/enum-generate_test.go index e92ae4c..72dcce7 100644 --- a/bfcodegen/enum-generate_test.go +++ b/bfcodegen/enum-generate_test.go @@ -2,6 +2,7 @@ package bfcodegen import ( _ "embed" + "fmt" "gogs.mikescher.com/BlackForestBytes/goext/cmdext" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/tst" @@ -12,7 +13,7 @@ import ( ) //go:embed _test_example.tgz -var ExampleModels []byte +var EnumExampleModels []byte func TestGenerateEnumSpecs(t *testing.T) { @@ -20,7 +21,7 @@ func TestGenerateEnumSpecs(t *testing.T) { tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) - err := os.WriteFile(tmpFile, ExampleModels, 0o777) + err := os.WriteFile(tmpFile, EnumExampleModels, 0o777) tst.AssertNoErr(t, err) t.Cleanup(func() { _ = os.Remove(tmpFile) }) @@ -39,4 +40,13 @@ func TestGenerateEnumSpecs(t *testing.T) { err = GenerateEnumSpecs(tmpDir, tmpDir+"/enums_gen.go") tst.AssertNoErr(t, err) + fmt.Println() + fmt.Println() + fmt.Println() + fmt.Println("=====================================================================================================") + fmt.Println(string(tst.Must(os.ReadFile(tmpDir + "/enums_gen.go"))(t))) + fmt.Println("=====================================================================================================") + fmt.Println() + fmt.Println() + fmt.Println() } diff --git a/bfcodegen/id-generate.go b/bfcodegen/id-generate.go index 700dfbb..951b579 100644 --- a/bfcodegen/id-generate.go +++ b/bfcodegen/id-generate.go @@ -1,6 +1,8 @@ package bfcodegen import ( + "bytes" + _ "embed" "errors" "fmt" "gogs.mikescher.com/BlackForestBytes/goext" @@ -14,6 +16,7 @@ import ( "path/filepath" "regexp" "strings" + "text/template" "time" ) @@ -29,6 +32,9 @@ var rexIDDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P[A-Za-z0-9_]+)\s+s var rexIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumIDGenerator = "(?P[A-Za-z0-9_]*)"`)) +//go:embed id-generate.template +var templateIDGenerateText string + func GenerateIDSpecs(sourceDir string, destFile string) error { files, err := os.ReadDir(sourceDir) @@ -166,71 +172,22 @@ func processIDFile(basedir string, fn string) ([]IDDef, string, error) { } 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" + templ := template.Must(template.New("id-generate").Parse(templateIDGenerateText)) - 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" + buffer := bytes.Buffer{} 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" - + err := templ.Execute(&buffer, langext.H{ + "PkgName": pkgname, + "Checksum": cs, + "GoextVersion": goext.GoextVersion, + "IDs": ids, + "AnyDef": anyDef, + }) + if err != nil { + panic(err) } - return str + return buffer.String() } diff --git a/bfcodegen/id-generate.template b/bfcodegen/id-generate.template new file mode 100644 index 0000000..7111fe4 --- /dev/null +++ b/bfcodegen/id-generate.template @@ -0,0 +1,47 @@ +// Code generated by id-generate.go DO NOT EDIT. + +package {{.PkgName}} + +import "go.mongodb.org/mongo-driver/bson" +import "go.mongodb.org/mongo-driver/bson/bsontype" +import "go.mongodb.org/mongo-driver/bson/primitive" +import "gogs.mikescher.com/BlackForestBytes/goext/exerr" + +const ChecksumIDGenerator = "{{.Checksum}}" // GoExtVersion: {{.GoextVersion}} + +{{range .IDs}} + +// ================================ {{.Name}} ({{.FileRelative}}) ================================ + +func (i {{.Name}}) MarshalBSONValue() (bsontype.Type, []byte, error) { + if objId, err := primitive.ObjectIDFromHex(string(i)); err == nil { + return bson.MarshalValue(objId) + } else { + return 0, nil, exerr.New(exerr.TypeMarshalEntityID, "Failed to marshal {{.Name}}("+i.String()+") to ObjectId").Str("value", string(i)).Type("type", i).Build() + } +} + +func (i {{.Name}}) String() string { + return string(i) +} + +func (i {{.Name}}) ObjID() (primitive.ObjectID, error) { + return primitive.ObjectIDFromHex(string(i)) +} + +func (i {{.Name}}) Valid() bool { + _, err := primitive.ObjectIDFromHex(string(i)) + return err == nil +} + +{{if ne $.AnyDef nil}} +func (i {{.Name}}) AsAny() {{$.AnyDef.Name}} { + return {{$.AnyDef.Name}}(i) +} +{{end}} + +func New{{.Name}}() {{.Name}} { + return {{.Name}}(primitive.NewObjectID().Hex()) +} + +{{end}} \ No newline at end of file diff --git a/bfcodegen/id-generate_test.go b/bfcodegen/id-generate_test.go new file mode 100644 index 0000000..f37b163 --- /dev/null +++ b/bfcodegen/id-generate_test.go @@ -0,0 +1,52 @@ +package bfcodegen + +import ( + _ "embed" + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/cmdext" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "gogs.mikescher.com/BlackForestBytes/goext/tst" + "os" + "path/filepath" + "testing" + "time" +) + +//go:embed _test_example.tgz +var IDExampleModels []byte + +func TestGenerateIDSpecs(t *testing.T) { + + tmpFile := filepath.Join(t.TempDir(), langext.MustHexUUID()+".tgz") + + tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) + + err := os.WriteFile(tmpFile, IDExampleModels, 0o777) + tst.AssertNoErr(t, err) + + t.Cleanup(func() { _ = os.Remove(tmpFile) }) + + err = os.Mkdir(tmpDir, 0o777) + tst.AssertNoErr(t, err) + + t.Cleanup(func() { _ = os.RemoveAll(tmpFile) }) + + _, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run() + tst.AssertNoErr(t, err) + + err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go") + tst.AssertNoErr(t, err) + + err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go") + tst.AssertNoErr(t, err) + + fmt.Println() + fmt.Println() + fmt.Println() + fmt.Println("=====================================================================================================") + fmt.Println(string(tst.Must(os.ReadFile(tmpDir + "/id_gen.go"))(t))) + fmt.Println("=====================================================================================================") + fmt.Println() + fmt.Println() + fmt.Println() +} diff --git a/goextVersion.go b/goextVersion.go index a134df4..1e16ad5 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.293" +const GoextVersion = "0.0.294" -const GoextVersionTimestamp = "2023-10-30T13:37:31+0100" +const GoextVersionTimestamp = "2023-10-31T22:58:28+0100"