Compare commits
	
		
			24 Commits
		
	
	
		
			29a3f73f15
			...
			v0.0.272
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 64772d0474 | |||
| 127764556e | |||
| 170f43d806 | |||
| 9dffc41274 | |||
| c63cf442f8 | |||
| a2ba283632 | |||
| 4a1fb1ae18 | |||
| a127b24e62 | |||
| 69d6290376 | |||
| c08a739158 | |||
| 5f5f0e44f0 | |||
| 6e6797eac5 | |||
| cd9406900a | |||
| 6c81f7f6bc | |||
| d56a0235af | |||
| de2ca763c1 | |||
| da52bb5c90 | |||
| 3d4afe7b25 | |||
| f5766d639c | |||
| cdf2a6e76b | |||
| 6d7cfb86f8 | |||
| 1e9d663ffe | |||
| 5b8d7ebf87 | |||
| 11dc6d2640 | 
							
								
								
									
										6
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								Makefile
									
									
									
									
									
								
							| @@ -7,5 +7,11 @@ test: | |||||||
| 	which gotestsum || go install gotest.tools/gotestsum@latest | 	which gotestsum || go install gotest.tools/gotestsum@latest | ||||||
| 	gotestsum --format "testname" -- -tags="timetzdata sqlite_fts5 sqlite_foreign_keys" "./..." | 	gotestsum --format "testname" -- -tags="timetzdata sqlite_fts5 sqlite_foreign_keys" "./..." | ||||||
|  |  | ||||||
|  | test-in-docker: | ||||||
|  | 	tag="goext_temp_test_image:$(shell uuidgen | tr -d '-')";        \ | ||||||
|  | 	docker build --tag $$tag . -f .gitea/workflows/Dockerfile_tests; \ | ||||||
|  | 	docker run --rm $$tag;                                           \ | ||||||
|  | 	docker rmi $$tag | ||||||
|  |  | ||||||
| version: | version: | ||||||
| 	_data/version.sh | 	_data/version.sh | ||||||
							
								
								
									
										12
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								TODO.md
									
									
									
									
									
								
							| @@ -2,12 +2,6 @@ | |||||||
|  |  | ||||||
|  - cronext |  - cronext | ||||||
|  |  | ||||||
|  - cursortoken |  - rfctime.DateOnly | ||||||
|  |  - rfctime.HMSTimeOnly | ||||||
|  - typed/geenric mongo wrapper |  - rfctime.NanoTimeOnly | ||||||
|  |  | ||||||
|  - error package |  | ||||||
|  |  | ||||||
| - rfctime.DateOnly |  | ||||||
| - rfctime.HMSTimeOnly |  | ||||||
| - rfctime.NanoTimeOnly |  | ||||||
| @@ -31,13 +31,13 @@ type EnumDef struct { | |||||||
| 	Values       []EnumDefVal | 	Values       []EnumDefVal | ||||||
| } | } | ||||||
|  |  | ||||||
| var rexPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]+)\s*$`)) | var rexEnumPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]+)\s*$`)) | ||||||
|  |  | ||||||
| var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`)) | var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`)) | ||||||
|  |  | ||||||
| var rexValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:]+"|[0-9]+))\s*(//(?P<descr>.*))?.*$`)) | var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:]+"|[0-9]+))\s*(//(?P<descr>.*))?.*$`)) | ||||||
|  |  | ||||||
| var rexChecksumConst = rext.W(regexp.MustCompile(`const ChecksumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`)) | var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`)) | ||||||
|  |  | ||||||
| func GenerateEnumSpecs(sourceDir string, destFile string) error { | func GenerateEnumSpecs(sourceDir string, destFile string) error { | ||||||
|  |  | ||||||
| @@ -52,13 +52,14 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		if m, ok := rexChecksumConst.MatchFirst(string(content)); ok { | 		if m, ok := rexEnumChecksumConst.MatchFirst(string(content)); ok { | ||||||
| 			oldChecksum = m.GroupByName("cs").Value() | 			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 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(), ".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() }) | 	langext.SortBy(files, func(v os.DirEntry) string { return v.Name() }) | ||||||
|  |  | ||||||
| 	newChecksumStr := goext.GoextVersion | 	newChecksumStr := goext.GoextVersion | ||||||
| @@ -85,7 +86,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { | |||||||
|  |  | ||||||
| 	for _, f := range files { | 	for _, f := range files { | ||||||
| 		fmt.Printf("========= %s =========\n\n", f.Name()) | 		fmt.Printf("========= %s =========\n\n", f.Name()) | ||||||
| 		fileEnums, pn, err := processFile(sourceDir, path.Join(sourceDir, f.Name())) | 		fileEnums, pn, err := processEnumFile(sourceDir, path.Join(sourceDir, f.Name())) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -103,7 +104,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { | |||||||
| 		return errors.New("no package name found in any file") | 		return errors.New("no package name found in any file") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = os.WriteFile(destFile, []byte(fmtOutput(newChecksum, allEnums, pkgname)), 0o755) | 	err = os.WriteFile(destFile, []byte(fmtEnumOutput(newChecksum, allEnums, pkgname)), 0o755) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -125,7 +126,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func processFile(basedir string, fn string) ([]EnumDef, string, error) { | func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) { | ||||||
| 	file, err := os.Open(fn) | 	file, err := os.Open(fn) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, "", err | 		return nil, "", err | ||||||
| @@ -149,7 +150,7 @@ func processFile(basedir string, fn string) ([]EnumDef, string, error) { | |||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if match, ok := rexPackage.MatchFirst(line); i == 0 && ok { | 		if match, ok := rexEnumPackage.MatchFirst(line); i == 0 && ok { | ||||||
| 			pkgname = match.GroupByName("name").Value() | 			pkgname = match.GroupByName("name").Value() | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| @@ -172,7 +173,7 @@ func processFile(basedir string, fn string) ([]EnumDef, string, error) { | |||||||
| 			fmt.Printf("Found enum definition { '%s' -> '%s' }\n", def.EnumTypeName, def.Type) | 			fmt.Printf("Found enum definition { '%s' -> '%s' }\n", def.EnumTypeName, def.Type) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if match, ok := rexValueDef.MatchFirst(line); ok { | 		if match, ok := rexEnumValueDef.MatchFirst(line); ok { | ||||||
| 			typename := match.GroupByName("type").Value() | 			typename := match.GroupByName("type").Value() | ||||||
| 			def := EnumDefVal{ | 			def := EnumDefVal{ | ||||||
| 				VarName:     match.GroupByName("name").Value(), | 				VarName:     match.GroupByName("name").Value(), | ||||||
| @@ -202,7 +203,7 @@ func processFile(basedir string, fn string) ([]EnumDef, string, error) { | |||||||
| 	return enums, pkgname, nil | 	return enums, pkgname, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func fmtOutput(cs string, enums []EnumDef, pkgname string) string { | func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string { | ||||||
| 	str := "// Code generated by enum-generate.go DO NOT EDIT.\n" | 	str := "// Code generated by enum-generate.go DO NOT EDIT.\n" | ||||||
| 	str += "\n" | 	str += "\n" | ||||||
| 	str += "package " + pkgname + "\n" | 	str += "package " + pkgname + "\n" | ||||||
| @@ -212,7 +213,7 @@ func fmtOutput(cs string, enums []EnumDef, pkgname string) string { | |||||||
| 	str += "import \"gogs.mikescher.com/BlackForestBytes/goext/enums\"" + "\n" | 	str += "import \"gogs.mikescher.com/BlackForestBytes/goext/enums\"" + "\n" | ||||||
| 	str += "\n" | 	str += "\n" | ||||||
|  |  | ||||||
| 	str += "const ChecksumGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n" | 	str += "const ChecksumEnumGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n" | ||||||
| 	str += "\n" | 	str += "\n" | ||||||
|  |  | ||||||
| 	for _, enumdef := range enums { | 	for _, enumdef := range enums { | ||||||
|   | |||||||
							
								
								
									
										236
									
								
								bfcodegen/id-generate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								bfcodegen/id-generate.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | |||||||
|  | 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 | ||||||
|  | } | ||||||
| @@ -133,9 +133,6 @@ func run(opt CommandRunner) (CommandResult, error) { | |||||||
|  |  | ||||||
| 	case <-stderrFailChan: | 	case <-stderrFailChan: | ||||||
| 		_ = cmd.Process.Kill() | 		_ = cmd.Process.Kill() | ||||||
| 		for _, lstr := range opt.listener { |  | ||||||
| 			lstr.Timeout() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if fallback, ok := syncext.ReadChannelWithTimeout(outputChan, 32*time.Millisecond); ok { | 		if fallback, ok := syncext.ReadChannelWithTimeout(outputChan, 32*time.Millisecond); ok { | ||||||
| 			// most of the time the cmd.Process.Kill() should also have finished the pipereader | 			// most of the time the cmd.Process.Kill() should also have finished the pipereader | ||||||
| @@ -160,7 +157,8 @@ func run(opt CommandRunner) (CommandResult, error) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	case outobj := <-outputChan: | 	case outobj := <-outputChan: | ||||||
| 		if exiterr, ok := outobj.err.(*exec.ExitError); ok { | 		var exiterr *exec.ExitError | ||||||
|  | 		if errors.As(outobj.err, &exiterr) { | ||||||
| 			excode := exiterr.ExitCode() | 			excode := exiterr.ExitCode() | ||||||
| 			for _, lstr := range opt.listener { | 			for _, lstr := range opt.listener { | ||||||
| 				lstr.Finished(excode) | 				lstr.Finished(excode) | ||||||
|   | |||||||
| @@ -32,8 +32,8 @@ func (pr *pipeReader) Read(listener []CommandListener) (string, string, string, | |||||||
| 	stdout := "" | 	stdout := "" | ||||||
| 	go func() { | 	go func() { | ||||||
| 		buf := make([]byte, 128) | 		buf := make([]byte, 128) | ||||||
| 		for true { | 		for { | ||||||
| 			n, out := pr.stdout.Read(buf) | 			n, err := pr.stdout.Read(buf) | ||||||
| 			if n > 0 { | 			if n > 0 { | ||||||
| 				txt := string(buf[:n]) | 				txt := string(buf[:n]) | ||||||
| 				stdout += txt | 				stdout += txt | ||||||
| @@ -42,11 +42,11 @@ func (pr *pipeReader) Read(listener []CommandListener) (string, string, string, | |||||||
| 					lstr.ReadRawStdout(buf[:n]) | 					lstr.ReadRawStdout(buf[:n]) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			if out == io.EOF { | 			if err == io.EOF { | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 			if out != nil { | 			if err != nil { | ||||||
| 				errch <- out | 				errch <- err | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -61,7 +61,7 @@ func (pr *pipeReader) Read(listener []CommandListener) (string, string, string, | |||||||
| 	stderr := "" | 	stderr := "" | ||||||
| 	go func() { | 	go func() { | ||||||
| 		buf := make([]byte, 128) | 		buf := make([]byte, 128) | ||||||
| 		for true { | 		for { | ||||||
| 			n, err := pr.stderr.Read(buf) | 			n, err := pr.stderr.Read(buf) | ||||||
|  |  | ||||||
| 			if n > 0 { | 			if n > 0 { | ||||||
|   | |||||||
| @@ -55,6 +55,8 @@ var ( | |||||||
| 	TypeBindFailFormData = NewType("BINDFAIL_FORMDATA", langext.Ptr(400)) | 	TypeBindFailFormData = NewType("BINDFAIL_FORMDATA", langext.Ptr(400)) | ||||||
| 	TypeBindFailHeader   = NewType("BINDFAIL_HEADER", langext.Ptr(400)) | 	TypeBindFailHeader   = NewType("BINDFAIL_HEADER", langext.Ptr(400)) | ||||||
|  |  | ||||||
|  | 	TypeMarshalEntityID = NewType("MARSHAL_ENTITY_ID", langext.Ptr(400)) | ||||||
|  |  | ||||||
| 	TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401)) | 	TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401)) | ||||||
| 	TypeAuthFailed   = NewType("AUTH_FAILED", langext.Ptr(401)) | 	TypeAuthFailed   = NewType("AUTH_FAILED", langext.Ptr(401)) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							| @@ -6,7 +6,7 @@ require ( | |||||||
| 	github.com/gin-gonic/gin v1.9.1 | 	github.com/gin-gonic/gin v1.9.1 | ||||||
| 	github.com/jmoiron/sqlx v1.3.5 | 	github.com/jmoiron/sqlx v1.3.5 | ||||||
| 	github.com/rs/xid v1.5.0 | 	github.com/rs/xid v1.5.0 | ||||||
| 	github.com/rs/zerolog v1.30.0 | 	github.com/rs/zerolog v1.31.0 | ||||||
| 	go.mongodb.org/mongo-driver v1.12.1 | 	go.mongodb.org/mongo-driver v1.12.1 | ||||||
| 	golang.org/x/crypto v0.13.0 | 	golang.org/x/crypto v0.13.0 | ||||||
| 	golang.org/x/sys v0.12.0 | 	golang.org/x/sys v0.12.0 | ||||||
| @@ -14,18 +14,18 @@ require ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/bytedance/sonic v1.10.0 // indirect | 	github.com/bytedance/sonic v1.10.1 // indirect | ||||||
| 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect | 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect | ||||||
| 	github.com/chenzhuoyu/iasm v0.9.0 // indirect | 	github.com/chenzhuoyu/iasm v0.9.0 // indirect | ||||||
| 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect | 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect | ||||||
| 	github.com/gin-contrib/sse v0.1.0 // indirect | 	github.com/gin-contrib/sse v0.1.0 // indirect | ||||||
| 	github.com/go-playground/locales v0.14.1 // indirect | 	github.com/go-playground/locales v0.14.1 // indirect | ||||||
| 	github.com/go-playground/universal-translator v0.18.1 // indirect | 	github.com/go-playground/universal-translator v0.18.1 // indirect | ||||||
| 	github.com/go-playground/validator/v10 v10.15.3 // indirect | 	github.com/go-playground/validator/v10 v10.15.4 // indirect | ||||||
| 	github.com/goccy/go-json v0.10.2 // indirect | 	github.com/goccy/go-json v0.10.2 // indirect | ||||||
| 	github.com/golang/snappy v0.0.4 // indirect | 	github.com/golang/snappy v0.0.4 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/klauspost/compress v1.16.7 // indirect | 	github.com/klauspost/compress v1.17.0 // indirect | ||||||
| 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect | 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect | ||||||
| 	github.com/leodido/go-urn v1.2.4 // indirect | 	github.com/leodido/go-urn v1.2.4 // indirect | ||||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							| @@ -6,6 +6,8 @@ github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9Mwe | |||||||
| github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
| github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= | github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= | ||||||
| github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
|  | github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= | ||||||
|  | github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
| github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= | ||||||
| github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= | ||||||
| github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= | ||||||
| @@ -34,6 +36,8 @@ github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2r | |||||||
| github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | ||||||
| github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= | github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= | ||||||
| github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | ||||||
|  | github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs= | ||||||
|  | github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | ||||||
| github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||||
| github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= | ||||||
| github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | ||||||
| @@ -54,6 +58,8 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ | |||||||
| github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | ||||||
| github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= | ||||||
| github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | ||||||
|  | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= | ||||||
|  | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | ||||||
| github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||||
| github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= | ||||||
| github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= | ||||||
| @@ -99,6 +105,8 @@ github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= | |||||||
| github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= | github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= | ||||||
| github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= | github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= | ||||||
| github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= | github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= | ||||||
|  | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= | ||||||
|  | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| package goext | package goext | ||||||
|  |  | ||||||
| const GoextVersion = "0.0.258" | const GoextVersion = "0.0.272" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2023-09-11T11:28:34+0200" | const GoextVersionTimestamp = "2023-09-26T14:41:15+0200" | ||||||
|   | |||||||
| @@ -27,10 +27,12 @@ func (a *AtomicBool) Get() bool { | |||||||
| 	return a.v | 	return a.v | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AtomicBool) Set(value bool) { | func (a *AtomicBool) Set(value bool) bool { | ||||||
| 	a.lock.Lock() | 	a.lock.Lock() | ||||||
| 	defer a.lock.Unlock() | 	defer a.lock.Unlock() | ||||||
|  |  | ||||||
|  | 	oldValue := a.v | ||||||
|  |  | ||||||
| 	a.v = value | 	a.v = value | ||||||
|  |  | ||||||
| 	for k, v := range a.listener { | 	for k, v := range a.listener { | ||||||
| @@ -42,6 +44,8 @@ func (a *AtomicBool) Set(value bool) { | |||||||
| 			delete(a.listener, k) | 			delete(a.listener, k) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return oldValue | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AtomicBool) Wait(waitFor bool) { | func (a *AtomicBool) Wait(waitFor bool) { | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								timeext/calendarweek.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								timeext/calendarweek.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | package timeext | ||||||
|  |  | ||||||
|  | import "time" | ||||||
|  |  | ||||||
|  | func WeekStart(year, week int) time.Time { | ||||||
|  |  | ||||||
|  | 	// https://stackoverflow.com/a/52303730/1761622 | ||||||
|  |  | ||||||
|  | 	// Start from the middle of the year: | ||||||
|  | 	t := time.Date(year, 7, 1, 0, 0, 0, 0, time.UTC) | ||||||
|  |  | ||||||
|  | 	// Roll back to Monday: | ||||||
|  | 	if wd := t.Weekday(); wd == time.Sunday { | ||||||
|  | 		t = t.AddDate(0, 0, -6) | ||||||
|  | 	} else { | ||||||
|  | 		t = t.AddDate(0, 0, -int(wd)+1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Difference in weeks: | ||||||
|  | 	_, w := t.ISOWeek() | ||||||
|  | 	t = t.AddDate(0, 0, (week-w)*7) | ||||||
|  |  | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func WeekEnd(year, week int) time.Time { | ||||||
|  | 	return WeekStart(year, week).AddDate(0, 0, 7).Add(time.Duration(-1)) | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								timeext/calendarweek_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								timeext/calendarweek_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | package timeext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestWeekStart(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	tst.AssertEqual(t, WeekStart(2018, 1).Format(time.RFC3339Nano), "2018-01-01T00:00:00Z") | ||||||
|  | 	tst.AssertEqual(t, WeekStart(2018, 2).Format(time.RFC3339Nano), "2018-01-08T00:00:00Z") | ||||||
|  | 	tst.AssertEqual(t, WeekStart(2019, 1).Format(time.RFC3339Nano), "2018-12-31T00:00:00Z") | ||||||
|  | 	tst.AssertEqual(t, WeekStart(2019, 2).Format(time.RFC3339Nano), "2019-01-07T00:00:00Z") | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestWeekEnd(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	tst.AssertEqual(t, WeekEnd(2018, 1).Format(time.RFC3339Nano), "2018-01-07T23:59:59.999999999Z") | ||||||
|  | 	tst.AssertEqual(t, WeekEnd(2018, 2).Format(time.RFC3339Nano), "2018-01-14T23:59:59.999999999Z") | ||||||
|  | 	tst.AssertEqual(t, WeekEnd(2019, 1).Format(time.RFC3339Nano), "2019-01-06T23:59:59.999999999Z") | ||||||
|  | 	tst.AssertEqual(t, WeekEnd(2019, 2).Format(time.RFC3339Nano), "2019-01-13T23:59:59.999999999Z") | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -2,23 +2,59 @@ package tst | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
|  | 	"reflect" | ||||||
| 	"runtime/debug" | 	"runtime/debug" | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func AssertEqual[T comparable](t *testing.T, actual T, expected T) { | func AssertEqual[T comparable](t *testing.T, actual T, expected T) { | ||||||
|  | 	t.Helper() | ||||||
| 	if actual != expected { | 	if actual != expected { | ||||||
| 		t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) | 		t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func AssertNotEqual[T comparable](t *testing.T, actual T, expected T) { | func AssertNotEqual[T comparable](t *testing.T, actual T, expected T) { | ||||||
|  | 	t.Helper() | ||||||
| 	if actual == expected { | 	if actual == expected { | ||||||
| 		t.Errorf("values do not differ: Actual: '%v', Expected: '%v'", actual, expected) | 		t.Errorf("values do not differ: Actual: '%v', Expected: '%v'", actual, expected) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func AssertDeepEqual[T any](t *testing.T, actual T, expected T) { | ||||||
|  | 	t.Helper() | ||||||
|  | 	if !reflect.DeepEqual(actual, expected) { | ||||||
|  | 		t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func AssertSetDeepEqual[T any](t *testing.T, actual []T, expected []T) { | ||||||
|  | 	t.Helper() | ||||||
|  | 	if len(actual) != len(expected) { | ||||||
|  | 		t.Errorf("values differ in length: Actual (n=%d): '%v', Expected (n=%d): '%v'", len(actual), actual, len(expected), expected) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, a := range expected { | ||||||
|  | 		found := false | ||||||
|  | 		for _, b := range actual { | ||||||
|  | 			found = found || reflect.DeepEqual(a, b) | ||||||
|  | 		} | ||||||
|  | 		if !found { | ||||||
|  | 			t.Errorf("values differ: Element '%v' not found. Actual: '%v', Expected: '%v'", a, actual, expected) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func AssertNotDeepEqual[T any](t *testing.T, actual T, expected T) { | ||||||
|  | 	t.Helper() | ||||||
|  | 	if reflect.DeepEqual(actual, expected) { | ||||||
|  | 		t.Errorf("values do not differ: Actual: '%v', Expected: '%v'", actual, expected) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func AssertDeRefEqual[T comparable](t *testing.T, actual *T, expected T) { | func AssertDeRefEqual[T comparable](t *testing.T, actual *T, expected T) { | ||||||
|  | 	t.Helper() | ||||||
| 	if actual == nil { | 	if actual == nil { | ||||||
| 		t.Errorf("values differ: Actual: NIL, Expected: '%v'", expected) | 		t.Errorf("values differ: Actual: NIL, Expected: '%v'", expected) | ||||||
| 	} | 	} | ||||||
| @@ -28,6 +64,7 @@ func AssertDeRefEqual[T comparable](t *testing.T, actual *T, expected T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func AssertPtrEqual[T comparable](t *testing.T, actual *T, expected *T) { | func AssertPtrEqual[T comparable](t *testing.T, actual *T, expected *T) { | ||||||
|  | 	t.Helper() | ||||||
| 	if actual == nil && expected == nil { | 	if actual == nil && expected == nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -47,6 +84,7 @@ func AssertPtrEqual[T comparable](t *testing.T, actual *T, expected *T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func AssertHexEqual(t *testing.T, expected string, actual []byte) { | func AssertHexEqual(t *testing.T, expected string, actual []byte) { | ||||||
|  | 	t.Helper() | ||||||
| 	actualStr := hex.EncodeToString(actual) | 	actualStr := hex.EncodeToString(actual) | ||||||
| 	if actualStr != expected { | 	if actualStr != expected { | ||||||
| 		t.Errorf("values differ: Actual: '%v', Expected: '%v'", actualStr, expected) | 		t.Errorf("values differ: Actual: '%v', Expected: '%v'", actualStr, expected) | ||||||
| @@ -54,18 +92,21 @@ func AssertHexEqual(t *testing.T, expected string, actual []byte) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func AssertTrue(t *testing.T, value bool) { | func AssertTrue(t *testing.T, value bool) { | ||||||
|  | 	t.Helper() | ||||||
| 	if !value { | 	if !value { | ||||||
| 		t.Error("value should be true\n" + string(debug.Stack())) | 		t.Error("value should be true\n" + string(debug.Stack())) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func AssertFalse(t *testing.T, value bool) { | func AssertFalse(t *testing.T, value bool) { | ||||||
|  | 	t.Helper() | ||||||
| 	if value { | 	if value { | ||||||
| 		t.Error("value should be false\n" + string(debug.Stack())) | 		t.Error("value should be false\n" + string(debug.Stack())) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func AssertNoErr(t *testing.T, anerr error) { | func AssertNoErr(t *testing.T, anerr error) { | ||||||
|  | 	t.Helper() | ||||||
| 	if anerr != nil { | 	if anerr != nil { | ||||||
| 		t.Error("Function returned an error: " + anerr.Error() + "\n" + string(debug.Stack())) | 		t.Error("Function returned an error: " + anerr.Error() + "\n" + string(debug.Stack())) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -116,3 +116,17 @@ func (c *Coll[TData]) Count(ctx context.Context, filter ct.Filter) (int64, error | |||||||
|  |  | ||||||
| 	return 0, nil | 	return 0, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) ListWithCount(ctx context.Context, filter ct.Filter, pageSize *int, inTok ct.CursorToken) ([]TData, ct.CursorToken, int64, error) { | ||||||
|  | 	// NOTE: Possible optimization: Cache count in CursorToken, then fetch count only on first page. | ||||||
|  | 	count, err := c.Count(ctx, filter) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, ct.CursorToken{}, 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data, token, err := c.List(ctx, filter, pageSize, inTok) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, ct.CursorToken{}, 0, err | ||||||
|  | 	} | ||||||
|  | 	return data, token, count, nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -73,7 +73,7 @@ func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) { | func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) { | ||||||
| 	mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, bson.M{"$set": value}, options.FindOneAndUpdate().SetReturnDocument(options.After)) | 	mongoRes := c.coll.FindOneAndReplace(ctx, filterQuery, value, options.FindOneAndReplace().SetReturnDocument(options.After)) | ||||||
| 	if err := mongoRes.Err(); err != nil { | 	if err := mongoRes.Err(); err != nil { | ||||||
| 		return *new(TData), exerr.Wrap(err, "mongo-query[find-one-and-update] failed"). | 		return *new(TData), exerr.Wrap(err, "mongo-query[find-one-and-update] failed"). | ||||||
| 			Str("collection", c.Name()). | 			Str("collection", c.Name()). | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ func (c *Coll[TData]) EnsureInitializedReflection(v TData) { | |||||||
|  |  | ||||||
| 	m := make(map[string]fullTypeRef) | 	m := make(map[string]fullTypeRef) | ||||||
|  |  | ||||||
| 	c.initFields("", rval, m, make([]int, 0)) | 	c.initFields("", rval.Type(), m, make([]int, 0), make([]reflect.Type, 0)) | ||||||
|  |  | ||||||
| 	c.implDataTypeMap[rval.Type()] = m | 	c.implDataTypeMap[rval.Type()] = m | ||||||
| } | } | ||||||
| @@ -50,20 +50,16 @@ func (c *Coll[TData]) init() { | |||||||
| 		c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef) | 		c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef) | ||||||
|  |  | ||||||
| 		v := reflect.ValueOf(example) | 		v := reflect.ValueOf(example) | ||||||
| 		c.initFields("", v, c.dataTypeMap, make([]int, 0)) | 		c.initFields("", v.Type(), c.dataTypeMap, make([]int, 0), make([]reflect.Type, 0)) | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, m map[string]fullTypeRef, idxarr []int) { | func (c *Coll[TData]) initFields(prefix string, rtyp reflect.Type, m map[string]fullTypeRef, idxarr []int, typesInPath []reflect.Type) { | ||||||
|  |  | ||||||
| 	rtyp := rval.Type() |  | ||||||
|  |  | ||||||
| 	for i := 0; i < rtyp.NumField(); i++ { | 	for i := 0; i < rtyp.NumField(); i++ { | ||||||
|  |  | ||||||
| 		rsfield := rtyp.Field(i) | 		rsfield := rtyp.Field(i) | ||||||
| 		rvfield := rval.Field(i) |  | ||||||
|  |  | ||||||
| 		if !rsfield.IsExported() { | 		if !rsfield.IsExported() { | ||||||
| 			continue | 			continue | ||||||
| @@ -91,21 +87,21 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, m map[string | |||||||
| 		newIdxArr := langext.ArrCopy(idxarr) | 		newIdxArr := langext.ArrCopy(idxarr) | ||||||
| 		newIdxArr = append(newIdxArr, i) | 		newIdxArr = append(newIdxArr, i) | ||||||
|  |  | ||||||
| 		if langext.InArray("inline", bsontags) && rvfield.Kind() == reflect.Struct { | 		if langext.InArray("inline", bsontags) && rsfield.Type.Kind() == reflect.Struct { | ||||||
|  |  | ||||||
| 			// pass-through field | 			// pass-through field | ||||||
| 			c.initFields(prefix, rvfield, m, newIdxArr) | 			c.initFields(prefix, rsfield.Type, m, newIdxArr, typesInPath) | ||||||
|  |  | ||||||
| 		} else { | 		} else { | ||||||
|  |  | ||||||
| 			if rvfield.Type().Kind() == reflect.Pointer { | 			if rsfield.Type.Kind() == reflect.Pointer { | ||||||
|  |  | ||||||
| 				m[fullKey] = fullTypeRef{ | 				m[fullKey] = fullTypeRef{ | ||||||
| 					IsPointer:      true, | 					IsPointer:      true, | ||||||
| 					RealType:       rvfield.Type(), | 					RealType:       rsfield.Type, | ||||||
| 					Kind:           rvfield.Type().Elem().Kind(), | 					Kind:           rsfield.Type.Elem().Kind(), | ||||||
| 					Type:           rvfield.Type().Elem(), | 					Type:           rsfield.Type.Elem(), | ||||||
| 					UnderlyingType: reflectext.Underlying(rvfield.Type().Elem()), | 					UnderlyingType: reflectext.Underlying(rsfield.Type.Elem()), | ||||||
| 					Name:           rsfield.Name, | 					Name:           rsfield.Name, | ||||||
| 					Index:          newIdxArr, | 					Index:          newIdxArr, | ||||||
| 				} | 				} | ||||||
| @@ -114,20 +110,37 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, m map[string | |||||||
|  |  | ||||||
| 				m[fullKey] = fullTypeRef{ | 				m[fullKey] = fullTypeRef{ | ||||||
| 					IsPointer:      false, | 					IsPointer:      false, | ||||||
| 					RealType:       rvfield.Type(), | 					RealType:       rsfield.Type, | ||||||
| 					Kind:           rvfield.Type().Kind(), | 					Kind:           rsfield.Type.Kind(), | ||||||
| 					Type:           rvfield.Type(), | 					Type:           rsfield.Type, | ||||||
| 					UnderlyingType: reflectext.Underlying(rvfield.Type()), | 					UnderlyingType: reflectext.Underlying(rsfield.Type), | ||||||
| 					Name:           rsfield.Name, | 					Name:           rsfield.Name, | ||||||
| 					Index:          newIdxArr, | 					Index:          newIdxArr, | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if rvfield.Kind() == reflect.Struct { | 			if rsfield.Type.Kind() == reflect.Struct { | ||||||
| 				c.initFields(fullKey+".", rvfield, m, newIdxArr) | 				c.initFields(fullKey+".", rsfield.Type, m, newIdxArr, typesInPath) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			if rsfield.Type.Kind() == reflect.Pointer && rsfield.Type.Elem().Kind() == reflect.Struct { | ||||||
|  | 				innerType := rsfield.Type.Elem() | ||||||
|  |  | ||||||
|  | 				// check if there is recursion | ||||||
|  | 				recursion := false | ||||||
|  | 				for _, typ := range typesInPath { | ||||||
|  | 					recursion = recursion || (typ == innerType) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if !recursion { | ||||||
|  | 					// Store all seen types before that deref a pointer to prevent endless recursion | ||||||
|  | 					newTypesInPath := make([]reflect.Type, len(typesInPath)) | ||||||
|  | 					copy(newTypesInPath, typesInPath) | ||||||
|  | 					newTypesInPath = append(newTypesInPath, rtyp) | ||||||
|  | 					c.initFields(fullKey+".", innerType, m, newIdxArr, newTypesInPath) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -23,6 +23,9 @@ func TestReflectionGetFieldType(t *testing.T) { | |||||||
| 		Sub   struct { | 		Sub   struct { | ||||||
| 			A string `bson:"a"` | 			A string `bson:"a"` | ||||||
| 		} `bson:"sub"` | 		} `bson:"sub"` | ||||||
|  | 		SubPtr *struct { | ||||||
|  | 			A string `bson:"a"` | ||||||
|  | 		} `bson:"subPtr"` | ||||||
| 		Str   string                  `bson:"str"` | 		Str   string                  `bson:"str"` | ||||||
| 		Ptr   *int                    `bson:"ptr"` | 		Ptr   *int                    `bson:"ptr"` | ||||||
| 		MDate rfctime.RFC3339NanoTime `bson:"mdate"` | 		MDate rfctime.RFC3339NanoTime `bson:"mdate"` | ||||||
| @@ -43,6 +46,11 @@ func TestReflectionGetFieldType(t *testing.T) { | |||||||
| 		}{ | 		}{ | ||||||
| 			A: "2", | 			A: "2", | ||||||
| 		}, | 		}, | ||||||
|  | 		SubPtr: &struct { | ||||||
|  | 			A string `bson:"a"` | ||||||
|  | 		}{ | ||||||
|  | 			A: "4", | ||||||
|  | 		}, | ||||||
| 		Str:   "3", | 		Str:   "3", | ||||||
| 		Ptr:   langext.Ptr(4), | 		Ptr:   langext.Ptr(4), | ||||||
| 		MDate: t1, | 		MDate: t1, | ||||||
| @@ -82,6 +90,12 @@ func TestReflectionGetFieldType(t *testing.T) { | |||||||
| 	tst.AssertEqual(t, gft("sub.a").IsPointer, false) | 	tst.AssertEqual(t, gft("sub.a").IsPointer, false) | ||||||
| 	tst.AssertEqual(t, gfv("sub.a").(string), "2") | 	tst.AssertEqual(t, gfv("sub.a").(string), "2") | ||||||
|  |  | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").Kind.String(), "string") | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").Type.String(), "string") | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").Name, "A") | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").IsPointer, false) | ||||||
|  | 	tst.AssertEqual(t, gfv("subPtr.a").(string), "4") | ||||||
|  |  | ||||||
| 	tst.AssertEqual(t, gft("str").Kind.String(), "string") | 	tst.AssertEqual(t, gft("str").Kind.String(), "string") | ||||||
| 	tst.AssertEqual(t, gft("str").Type.String(), "string") | 	tst.AssertEqual(t, gft("str").Type.String(), "string") | ||||||
| 	tst.AssertEqual(t, gft("str").Name, "Str") | 	tst.AssertEqual(t, gft("str").Name, "Str") | ||||||
| @@ -99,16 +113,25 @@ func TestReflectionGetTokenValueAsMongoType(t *testing.T) { | |||||||
|  |  | ||||||
| 	type IDType string | 	type IDType string | ||||||
|  |  | ||||||
|  | 	type RecurseiveType struct { | ||||||
|  | 		Other int             `bson:"other"` | ||||||
|  | 		Inner *RecurseiveType `bson:"inner"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	type TestData struct { | 	type TestData struct { | ||||||
| 		ID    IDType    `bson:"_id"` | 		ID    IDType    `bson:"_id"` | ||||||
| 		CDate time.Time `bson:"cdate"` | 		CDate time.Time `bson:"cdate"` | ||||||
| 		Sub   struct { | 		Sub   struct { | ||||||
| 			A string `bson:"a"` | 			A string `bson:"a"` | ||||||
| 		} `bson:"sub"` | 		} `bson:"sub"` | ||||||
|  | 		SubPtr *struct { | ||||||
|  | 			A string `bson:"a"` | ||||||
|  | 		} `bson:"subPtr"` | ||||||
| 		Str   string                  `bson:"str"` | 		Str   string                  `bson:"str"` | ||||||
| 		Ptr   *int                    `bson:"ptr"` | 		Ptr   *int                    `bson:"ptr"` | ||||||
| 		Num   int                     `bson:"num"` | 		Num   int                     `bson:"num"` | ||||||
| 		MDate rfctime.RFC3339NanoTime `bson:"mdate"` | 		MDate rfctime.RFC3339NanoTime `bson:"mdate"` | ||||||
|  | 		Rec   RecurseiveType          `bson:"rec"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	coll := W[TestData](&mongo.Collection{}) | 	coll := W[TestData](&mongo.Collection{}) | ||||||
| @@ -130,6 +153,9 @@ func TestReflectionGetTokenValueAsMongoType(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tst.AssertEqual(t, gtvasmt("hello", "str").(string), "hello") | 	tst.AssertEqual(t, gtvasmt("hello", "str").(string), "hello") | ||||||
|  | 	tst.AssertEqual(t, gtvasmt("hello", "sub.a").(string), "hello") | ||||||
|  | 	tst.AssertEqual(t, gtvasmt("hello", "subPtr.a").(string), "hello") | ||||||
|  | 	tst.AssertEqual(t, gtvasmt("4", "rec.other").(int), 4) | ||||||
| 	tst.AssertEqual(t, gtvasmt("4", "num").(int), 4) | 	tst.AssertEqual(t, gtvasmt("4", "num").(int), 4) | ||||||
| 	tst.AssertEqual(t, gtvasmt("asdf", "_id").(IDType), "asdf") | 	tst.AssertEqual(t, gtvasmt("asdf", "_id").(IDType), "asdf") | ||||||
| 	tst.AssertEqual(t, gtvasmt("", "ptr").(*int), nil) | 	tst.AssertEqual(t, gtvasmt("", "ptr").(*int), nil) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user