Compare commits
	
		
			5 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| dacc97e2ce | |||
| f8c0c0afa0 | |||
| 2fbd5cf965 | |||
| 75f71fe3db | |||
| ab1a1ab6f6 | 
										
											Binary file not shown.
										
									
								
							| @@ -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<name>[A-Za-z0-9_]+)\s | ||||
|  | ||||
| var rexCSIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumCharsetIDGenerator = "(?P<cs>[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() | ||||
| } | ||||
|   | ||||
							
								
								
									
										190
									
								
								bfcodegen/csid-generate.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								bfcodegen/csid-generate.template
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| // Code generated by csid-generate.go DO NOT EDIT. | ||||
|  | ||||
| package {{.PkgName}} | ||||
|  | ||||
| import "crypto/rand" | ||||
| import "crypto/sha256" | ||||
| 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 generateIDFromSeed(prefix string, seed string) string { | ||||
| 	h := sha256.New() | ||||
|  | ||||
| 	iddata := "" | ||||
| 	for len(iddata) < idlen-len(prefix)-checklen { | ||||
| 		h.Write([]byte(seed)) | ||||
| 		bs := h.Sum(nil) | ||||
| 		iddata += langext.NewAnyBaseConverter(idCharset).Encode(bs) | ||||
| 	} | ||||
|  | ||||
| 	checksum := 0 | ||||
| 	for i := 0; i < idlen-len(prefix)-checklen; i++ { | ||||
| 		ichr := int(iddata[i]) | ||||
| 		checksum = (checksum + charSetReverseMap[ichr]) % (idCharsetLen) | ||||
| 	} | ||||
|  | ||||
| 	checkstr := string(idCharset[checksum%idCharsetLen]) | ||||
|  | ||||
| 	return prefix + iddata[:(idlen-len(prefix)-checklen)] + 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}} | ||||
							
								
								
									
										52
									
								
								bfcodegen/csid-generate_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								bfcodegen/csid-generate_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||
| } | ||||
| @@ -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<name>[A-Za-z0-9_]+)\s+( | ||||
|  | ||||
| var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[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() | ||||
| } | ||||
|   | ||||
							
								
								
									
										97
									
								
								bfcodegen/enum-generate.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								bfcodegen/enum-generate.template
									
									
									
									
									
										Normal file
									
								
							| @@ -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}} | ||||
| @@ -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() | ||||
| } | ||||
|   | ||||
| @@ -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<name>[A-Za-z0-9_]+)\s+s | ||||
|  | ||||
| var rexIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumIDGenerator = "(?P<cs>[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() | ||||
| } | ||||
|   | ||||
							
								
								
									
										47
									
								
								bfcodegen/id-generate.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								bfcodegen/id-generate.template
									
									
									
									
									
										Normal file
									
								
							| @@ -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}} | ||||
							
								
								
									
										52
									
								
								bfcodegen/id-generate_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								bfcodegen/id-generate_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||
| } | ||||
| @@ -275,7 +275,7 @@ func (b *Builder) Any(key string, val any) *Builder { | ||||
| } | ||||
|  | ||||
| func (b *Builder) Stringer(key string, val fmt.Stringer) *Builder { | ||||
| 	if val == nil { | ||||
| 	if langext.IsNil(val) { | ||||
| 		return b.addMeta(key, MDTString, "(!nil)") | ||||
| 	} else { | ||||
| 		return b.addMeta(key, MDTString, val.String()) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| package goext | ||||
|  | ||||
| const GoextVersion = "0.0.292" | ||||
| const GoextVersion = "0.0.297" | ||||
|  | ||||
| const GoextVersionTimestamp = "2023-10-30T10:14:38+0100" | ||||
| const GoextVersionTimestamp = "2023-11-01T00:31:51+0100" | ||||
|   | ||||
							
								
								
									
										178
									
								
								langext/baseAny.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								langext/baseAny.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| package langext | ||||
|  | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"errors" | ||||
| 	"math" | ||||
| 	"math/big" | ||||
| ) | ||||
|  | ||||
| type AnyBaseConverter struct { | ||||
| 	base    uint64 | ||||
| 	charset []rune | ||||
| } | ||||
|  | ||||
| func NewAnyBaseConverter(cs string) AnyBaseConverter { | ||||
| 	rcs := []rune(cs) | ||||
| 	return AnyBaseConverter{ | ||||
| 		base:    uint64(len(rcs)), | ||||
| 		charset: rcs, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc AnyBaseConverter) Rand(rlen int) string { | ||||
| 	biBase := big.NewInt(int64(bc.base)) | ||||
|  | ||||
| 	randMax := big.NewInt(math.MaxInt64) | ||||
|  | ||||
| 	r := "" | ||||
|  | ||||
| 	for i := 0; i < rlen; i++ { | ||||
| 		v, err := rand.Int(rand.Reader, randMax) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | ||||
| 		r += string(bc.charset[v.Mod(v, biBase).Int64()]) | ||||
| 	} | ||||
|  | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (bc AnyBaseConverter) EncodeUInt64(num uint64) string { | ||||
| 	if num == 0 { | ||||
| 		return "0" | ||||
| 	} | ||||
|  | ||||
| 	b := "" | ||||
|  | ||||
| 	// loop as long the num is bigger than zero | ||||
| 	for num > 0 { | ||||
| 		r := num % bc.base | ||||
|  | ||||
| 		num -= r | ||||
| 		num /= base62Base | ||||
|  | ||||
| 		b += string(bc.charset[int(r)]) | ||||
| 	} | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (bc AnyBaseConverter) DecodeUInt64(str string) (uint64, error) { | ||||
| 	if str == "" { | ||||
| 		return 0, errors.New("empty string") | ||||
| 	} | ||||
|  | ||||
| 	result := uint64(0) | ||||
|  | ||||
| 	for _, v := range str { | ||||
| 		result *= base62Base | ||||
|  | ||||
| 		pos := ArrFirstIndex(bc.charset, v) | ||||
| 		if pos == -1 { | ||||
| 			return 0, errors.New("invalid character: " + string(v)) | ||||
| 		} | ||||
|  | ||||
| 		result += uint64(pos) | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func (bc AnyBaseConverter) Encode(src []byte) string { | ||||
| 	value := new(big.Int) | ||||
| 	value.SetBytes(src) | ||||
| 	return bc.EncodeBigInt(value) | ||||
| } | ||||
|  | ||||
| func (bc AnyBaseConverter) EncodeBigInt(src *big.Int) string { | ||||
| 	value := new(big.Int) | ||||
| 	value.Set(src) | ||||
|  | ||||
| 	isneg := value.Sign() < 0 | ||||
|  | ||||
| 	answer := "" | ||||
|  | ||||
| 	if isneg { | ||||
| 		value.Neg(value) | ||||
| 	} | ||||
|  | ||||
| 	biBase := big.NewInt(int64(bc.base)) | ||||
|  | ||||
| 	rem := new(big.Int) | ||||
|  | ||||
| 	for value.Sign() > 0 { | ||||
| 		value.QuoRem(value, biBase, rem) | ||||
| 		answer = string(bc.charset[rem.Int64()]) + answer | ||||
| 	} | ||||
|  | ||||
| 	if isneg { | ||||
| 		return "-" + answer | ||||
| 	} else { | ||||
| 		return answer | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bc AnyBaseConverter) Decode(src string) ([]byte, error) { | ||||
| 	value, err := bc.DecodeToBigInt(src) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return value.Bytes(), nil | ||||
| } | ||||
|  | ||||
| func (bc AnyBaseConverter) DecodeToBigInt(_src string) (*big.Int, error) { | ||||
| 	result := new(big.Int) | ||||
| 	result.SetInt64(0) | ||||
|  | ||||
| 	src := []rune(_src) | ||||
|  | ||||
| 	if len(src) == 0 { | ||||
| 		return nil, errors.New("string is empty") | ||||
| 	} | ||||
| 	if bc.base < 2 { | ||||
| 		return nil, errors.New("not enough digits") | ||||
| 	} | ||||
|  | ||||
| 	i := 0 | ||||
|  | ||||
| 	sign := new(big.Int) | ||||
| 	sign.SetInt64(1) | ||||
| 	if src[i] == '+' { | ||||
| 		i++ | ||||
| 	} else if src[i] == '-' { | ||||
| 		i++ | ||||
| 		sign.SetInt64(-1) | ||||
| 	} | ||||
|  | ||||
| 	if i >= len(src) { | ||||
| 		return nil, errors.New("no digits in input") | ||||
| 	} | ||||
|  | ||||
| 	biBase := big.NewInt(int64(bc.base)) | ||||
|  | ||||
| 	oldResult := new(big.Int) | ||||
|  | ||||
| 	for ; i < len(src); i++ { | ||||
| 		n := ArrFirstIndex(bc.charset, src[i]) | ||||
| 		if n < 0 { | ||||
| 			return nil, errors.New("invalid characters in input") | ||||
| 		} | ||||
|  | ||||
| 		oldResult.Set(result) | ||||
|  | ||||
| 		result.Mul(result, biBase) | ||||
| 		result.Add(result, big.NewInt(int64(n))) | ||||
|  | ||||
| 		if result.Cmp(oldResult) < 0 { | ||||
| 			return nil, errors.New("overflow") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if sign.Cmp(big.NewInt(0)) < 0 { | ||||
| 		result.Neg(result) | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
							
								
								
									
										80
									
								
								langext/baseAny_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								langext/baseAny_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| package langext | ||||
|  | ||||
| import ( | ||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func _anyEncStr(bc AnyBaseConverter, v string) string { | ||||
| 	vr := bc.Encode([]byte(v)) | ||||
| 	return vr | ||||
| } | ||||
|  | ||||
| func _anyDecStr(bc AnyBaseConverter, v string) string { | ||||
| 	vr, err := bc.Decode(v) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return string(vr) | ||||
| } | ||||
|  | ||||
| func TestAnyBase58DefaultEncoding(t *testing.T) { | ||||
| 	tst.AssertEqual(t, _anyEncStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "Hello"), "9Ajdvzr") | ||||
| 	tst.AssertEqual(t, _anyEncStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in."), "48638SMcJuah5okqPx4kCVf5d8QAdgbdNf28g7ReY13prUENNbMyssjq5GjsrJHF5zeZfqs4uJMUJHr7VbrU4XBUZ2Fw9DVtqtn9N1eXucEWSEZahXV6w4ysGSWqGdpeYTJf1MdDzTg8vfcQViifJjZX") | ||||
| } | ||||
|  | ||||
| func TestAnyBase58DefaultDecoding(t *testing.T) { | ||||
| 	tst.AssertEqual(t, _anyDecStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "9Ajdvzr"), "Hello") | ||||
| 	tst.AssertEqual(t, _anyDecStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "48638SMcJuah5okqPx4kCVf5d8QAdgbdNf28g7ReY13prUENNbMyssjq5GjsrJHF5zeZfqs4uJMUJHr7VbrU4XBUZ2Fw9DVtqtn9N1eXucEWSEZahXV6w4ysGSWqGdpeYTJf1MdDzTg8vfcQViifJjZX"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in.") | ||||
| } | ||||
|  | ||||
| func TestAnyBaseDecode(t *testing.T) { | ||||
|  | ||||
| 	const ( | ||||
| 		Binary  = "01" | ||||
| 		Decimal = "0123456789" | ||||
| 		Hex     = "0123456789ABCDEF" | ||||
| 		DNA     = "ACGT" | ||||
| 		Base32  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" | ||||
| 		Base58  = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" | ||||
| 		Base62  = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | ||||
| 		Base64  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | ||||
| 		Base256 = "🚀🪐☄🛰🌌🌑🌒🌓🌔🌕🌖🌗🌘🌍🌏🌎🐉☀💻🖥💾💿😂❤😍🤣😊🙏💕😭😘👍😅👏😁🔥🥰💔💖💙😢🤔😆🙄💪😉☺👌🤗💜😔😎😇🌹🤦🎉💞✌✨🤷😱😌🌸🙌😋💗💚😏💛🙂💓🤩😄😀🖤😃💯🙈👇🎶😒🤭❣😜💋👀😪😑💥🙋😞😩😡🤪👊🥳😥🤤👉💃😳✋😚😝😴🌟😬🙃🍀🌷😻😓⭐✅🥺🌈😈🤘💦✔😣🏃💐☹🎊💘😠☝😕🌺🎂🌻😐🖕💝🙊😹🗣💫💀👑🎵🤞😛🔴😤🌼😫⚽🤙☕🏆🤫👈😮🙆🍻🍃🐶💁😲🌿🧡🎁⚡🌞🎈❌✊👋😰🤨😶🤝🚶💰🍓💢🤟🙁🚨💨🤬✈🎀🍺🤓😙💟🌱😖👶🥴▶➡❓💎💸⬇😨🌚🦋😷🕺⚠🙅😟😵👎🤲🤠🤧📌🔵💅🧐🐾🍒😗🤑🌊🤯🐷☎💧😯💆👆🎤🙇🍑❄🌴💣🐸💌📍🥀🤢👅💡💩👐📸👻🤐🤮🎼🥵🚩🍎🍊👼💍📣🥂" | ||||
| 	) | ||||
|  | ||||
| 	type TestDef struct { | ||||
| 		FromCS  string | ||||
| 		FromVal string | ||||
| 		ToCS    string | ||||
| 		ToVal   string | ||||
| 	} | ||||
|  | ||||
| 	defs := []TestDef{ | ||||
| 		{Binary, "10100101011100000101010", Decimal, "5421098"}, | ||||
| 		{Decimal, "5421098", DNA, "CCAGGTGAAGGG"}, | ||||
| 		{Decimal, "5421098", DNA, "CCAGGTGAAGGG"}, | ||||
| 		{Decimal, "80085", Base256, "🪐💞🔵"}, | ||||
| 		{Hex, "48656C6C6C20576F526C5421", Base64, "SGVsbGwgV29SbFQh"}, | ||||
| 		{Base64, "SGVsbGw/gV29SbF+Qh", Base32, "CIMVWGY3B7QFO32SNRPZBB"}, | ||||
| 		{Base64, "SGVsbGw/gV29SbF+Qh", Base58, "2fUsGKQUcgQcwSqpvy6"}, | ||||
| 		{Base64, "SGVsbGw/gV29SbF+Qh", Base62, "V34nvybdQ3m3RHk9Sr"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, def := range defs { | ||||
|  | ||||
| 		d1 := NewAnyBaseConverter(def.FromCS) | ||||
| 		d2 := NewAnyBaseConverter(def.ToCS) | ||||
|  | ||||
| 		v1 := tst.Must(d1.Decode(def.FromVal))(t) | ||||
| 		v2 := tst.Must(d2.Decode(def.ToVal))(t) | ||||
|  | ||||
| 		tst.AssertArrayEqual(t, v1, v2) | ||||
|  | ||||
| 		str2 := d2.Encode(v1) | ||||
| 		tst.AssertEqual(t, str2, def.ToVal) | ||||
|  | ||||
| 		str1 := d1.Encode(v2) | ||||
| 		tst.AssertEqual(t, str1, def.FromVal) | ||||
|  | ||||
| 	} | ||||
| } | ||||
| @@ -14,6 +14,20 @@ func AssertEqual[T comparable](t *testing.T, actual T, expected T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func AssertArrayEqual[T comparable](t *testing.T, actual []T, expected []T) { | ||||
| 	t.Helper() | ||||
| 	if len(actual) != len(expected) { | ||||
| 		t.Errorf("values differ: Actual: '%v', Expected: '%v' (len %d <> %d)", actual, expected, len(actual), len(expected)) | ||||
| 		return | ||||
| 	} | ||||
| 	for i := 0; i < len(actual); i++ { | ||||
| 		if actual[i] != expected[i] { | ||||
| 			t.Errorf("values differ: Actual: '%v', Expected: '%v' (at index %d)", actual, expected, i) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func AssertNotEqual[T comparable](t *testing.T, actual T, expected T) { | ||||
| 	t.Helper() | ||||
| 	if actual == expected { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user