diff --git a/bfcodegen/_test_example.tgz b/bfcodegen/_test_example_1.tgz similarity index 100% rename from bfcodegen/_test_example.tgz rename to bfcodegen/_test_example_1.tgz diff --git a/bfcodegen/_test_example_2.tgz b/bfcodegen/_test_example_2.tgz new file mode 100644 index 0000000..6db88e8 Binary files /dev/null and b/bfcodegen/_test_example_2.tgz differ diff --git a/bfcodegen/csid-generate_test.go b/bfcodegen/csid-generate_test.go index 8f7c665..64db2d6 100644 --- a/bfcodegen/csid-generate_test.go +++ b/bfcodegen/csid-generate_test.go @@ -12,8 +12,8 @@ import ( "time" ) -//go:embed _test_example.tgz -var CSIDExampleModels []byte +//go:embed _test_example_1.tgz +var CSIDExampleModels1 []byte func TestGenerateCSIDSpecs(t *testing.T) { @@ -21,7 +21,7 @@ func TestGenerateCSIDSpecs(t *testing.T) { tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) - err := os.WriteFile(tmpFile, CSIDExampleModels, 0o777) + err := os.WriteFile(tmpFile, CSIDExampleModels1, 0o777) tst.AssertNoErr(t, err) t.Cleanup(func() { _ = os.Remove(tmpFile) }) diff --git a/bfcodegen/enum-generate.go b/bfcodegen/enum-generate.go index b122cb8..24221c3 100644 --- a/bfcodegen/enum-generate.go +++ b/bfcodegen/enum-generate.go @@ -3,6 +3,7 @@ package bfcodegen import ( "bytes" _ "embed" + "encoding/json" "errors" "fmt" "go/format" @@ -14,6 +15,7 @@ import ( "os" "path" "path/filepath" + "reflect" "regexp" "strings" "text/template" @@ -23,6 +25,8 @@ type EnumDefVal struct { VarName string Value string Description *string + Data *map[string]any + RawComment *string } type EnumDef struct { @@ -37,7 +41,7 @@ var rexEnumPackage = rext.W(regexp.MustCompile(`^package\s+(?P[A-Za-z0-9_] var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P[A-Za-z0-9_]+)\s+(?P[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`)) -var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P[A-Za-z0-9_]+)\s+(?P[A-Za-z0-9_]+)\s*=\s*(?P("[A-Za-z0-9_:\s\-.]+"|[0-9]+))\s*(//(?P.*))?.*$`)) +var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P[A-Za-z0-9_]+)\s+(?P[A-Za-z0-9_]+)\s*=\s*(?P("[A-Za-z0-9_:\s\-.]+"|[0-9]+))\s*(//(?P.*))?.*$`)) var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P[A-Za-z0-9_]*)"`)) @@ -46,11 +50,6 @@ var templateEnumGenerateText string func GenerateEnumSpecs(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) @@ -62,6 +61,26 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { } } + gocode, _, err := _generateEnumSpecs(sourceDir, oldChecksum, destFile, true) + if err != nil { + return err + } + + err = os.WriteFile(destFile, []byte(gocode), 0o755) + if err != nil { + return err + } + + return nil +} + +func _generateEnumSpecs(sourceDir string, destFile string, oldChecksum string, gofmt bool) (string, string, error) { + + files, err := os.ReadDir(sourceDir) + if err != nil { + return "", "", err + } + 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") }) @@ -71,7 +90,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { for _, f := range files { content, err := os.ReadFile(path.Join(sourceDir, f.Name())) if err != nil { - return err + return "", "", err } newChecksumStr += "\n" + f.Name() + "\t" + cryptext.BytesSha256(content) } @@ -82,7 +101,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { fmt.Printf("[EnumGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum) } else { fmt.Printf("[EnumGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum) - return nil + return "", oldChecksum, nil } allEnums := make([]EnumDef, 0) @@ -93,7 +112,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { fmt.Printf("========= %s =========\n\n", f.Name()) fileEnums, pn, err := processEnumFile(sourceDir, path.Join(sourceDir, f.Name())) if err != nil { - return err + return "", "", err } fmt.Printf("\n") @@ -106,20 +125,21 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { } if pkgname == "" { - return errors.New("no package name found in any file") + return "", "", errors.New("no package name found in any file") } - fdata, err := format.Source([]byte(fmtEnumOutput(newChecksum, allEnums, pkgname))) + rdata := fmtEnumOutput(newChecksum, allEnums, pkgname) + + if !gofmt { + return rdata, newChecksum, nil + } + + fdata, err := format.Source([]byte(rdata)) if err != nil { - return err + return "", "", err } - err = os.WriteFile(destFile, fdata, 0o755) - if err != nil { - return err - } - - return nil + return string(fdata), newChecksum, nil } func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) { @@ -171,10 +191,34 @@ func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) { if match, ok := rexEnumValueDef.MatchFirst(line); ok { typename := match.GroupByName("type").Value() + + comment := match.GroupByNameOrEmpty("comm").ValueOrNil() + var descr *string = nil + var data *map[string]any = nil + if comment != nil { + comment = langext.Ptr(strings.TrimSpace(*comment)) + if strings.HasPrefix(*comment, "{") { + if v, ok := tryParseDataComment(*comment); ok { + data = &v + if anyDataDescr, ok := v["description"]; ok { + if dataDescr, ok := anyDataDescr.(string); ok { + descr = &dataDescr + } + } + } else { + descr = comment + } + } else { + descr = comment + } + } + def := EnumDefVal{ VarName: match.GroupByName("name").Value(), Value: match.GroupByName("value").Value(), - Description: match.GroupByNameOrEmpty("descr").ValueOrNil(), + RawComment: comment, + Description: descr, + Data: data, } found := false @@ -199,6 +243,41 @@ func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) { return enums, pkgname, nil } +func tryParseDataComment(s string) (map[string]any, bool) { + + r := make(map[string]any) + + err := json.Unmarshal([]byte(s), &r) + if err != nil { + return nil, false + } + + for _, v := range r { + + rv := reflect.ValueOf(v) + + if rv.Kind() == reflect.Ptr && rv.IsNil() { + continue + } + if rv.Kind() == reflect.Bool { + continue + } + if rv.Kind() == reflect.String { + continue + } + if rv.Kind() == reflect.Int64 { + continue + } + if rv.Kind() == reflect.Float64 { + continue + } + + return nil, false + } + + return r, true +} + func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string { templ := template.New("enum-generate") @@ -211,6 +290,47 @@ func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string { "hasDescr": func(v EnumDef) bool { return langext.ArrAll(v.Values, func(val EnumDefVal) bool { return val.Description != nil }) }, + "hasData": func(v EnumDef) bool { + return len(v.Values) > 0 && langext.ArrAll(v.Values, func(val EnumDefVal) bool { return val.Data != nil }) + }, + "gostr": func(v any) string { + return fmt.Sprintf("%#+v", v) + }, + "goobj": func(name string, v any) string { + return fmt.Sprintf("%#+v", v) + }, + "godatakey": func(v string) string { + return strings.ToUpper(v[0:1]) + v[1:] + }, + "godatavalue": func(v any) string { + return fmt.Sprintf("%#+v", v) + }, + "godatatype": func(v any) string { + return fmt.Sprintf("%T", v) + }, + "mapindex": func(v map[string]any, k string) any { + return v[k] + }, + "generalDataKeys": func(v EnumDef) map[string]string { + r0 := make(map[string]int) + + for _, eval := range v.Values { + for k := range *eval.Data { + if ctr, ok := r0[k]; ok { + r0[k] = ctr + 1 + } else { + r0[k] = 1 + } + } + } + + r1 := langext.MapToArr(r0) + r2 := langext.ArrFilter(r1, func(p langext.MapEntry[string, int]) bool { return p.Value == len(v.Values) }) + r3 := langext.ArrMap(r2, func(p langext.MapEntry[string, int]) string { return p.Key }) + r4 := langext.ArrToKVMap(r3, func(p string) string { return p }, func(p string) string { return fmt.Sprintf("%T", (*v.Values[0].Data)[p]) }) + + return r4 + }, }) templ = template.Must(templ.Parse(templateEnumGenerateText)) diff --git a/bfcodegen/enum-generate.template b/bfcodegen/enum-generate.template index 220910c..43a30be 100644 --- a/bfcodegen/enum-generate.template +++ b/bfcodegen/enum-generate.template @@ -11,21 +11,38 @@ const ChecksumEnumGenerator = "{{.Checksum}}" // GoExtVersion: {{.GoextVersion}} {{ $hasStr := ( . | hasStr ) }} {{ $hasDescr := ( . | hasDescr ) }} +{{ $hasData := ( . | hasData ) }} // ================================ {{.EnumTypeName}} ================================ // // File: {{.FileRelative}} // StringEnum: {{$hasStr | boolToStr}} // DescrEnum: {{$hasDescr | boolToStr}} +// DataEnum: {{$hasData | boolToStr}} // +{{ $typename := .EnumTypeName }} +{{ $enumdef := . }} + var __{{.EnumTypeName}}Values = []{{.EnumTypeName}}{ {{range .Values}} {{.VarName}}, {{end}} } {{if $hasDescr}} var __{{.EnumTypeName}}Descriptions = map[{{.EnumTypeName}}]string{ {{range .Values}} - {{.VarName}}: "{{.Description | deref | trimSpace}}", {{end}} + {{.VarName}}: {{.Description | deref | trimSpace | gostr}}, {{end}} +} +{{end}} + +{{if $hasData}} +type {{ .EnumTypeName }}Data struct { {{ range $datakey, $datatype := ($enumdef | generalDataKeys) }} + {{ $datakey | godatakey }} {{ $datatype }} `json:"{{ $datakey }}"` {{ end }} +} + +var __{{.EnumTypeName}}Data = map[{{.EnumTypeName}}]{{.EnumTypeName}}Data{ {{range .Values}} {{ $enumvalue := . }} + {{.VarName}}: {{ $typename }}Data{ {{ range $datakey, $datatype := $enumdef | generalDataKeys }} + {{ $datakey | godatakey }}: {{ (mapindex $enumvalue.Data $datakey) | godatavalue }}, {{ end }} + }, {{end}} } {{end}} @@ -64,6 +81,15 @@ func (e {{.EnumTypeName}}) Description() string { } {{end}} +{{if $hasData}} +func (e {{.EnumTypeName}}) Data() {{.EnumTypeName}}Data { + if d, ok := __{{.EnumTypeName}}Data[e]; ok { + return d + } + return {{.EnumTypeName}}Data{} +} +{{end}} + func (e {{.EnumTypeName}}) VarName() string { if d, ok := __{{.EnumTypeName}}Varnames[e]; ok { return d diff --git a/bfcodegen/enum-generate_test.go b/bfcodegen/enum-generate_test.go index 72dcce7..896fd52 100644 --- a/bfcodegen/enum-generate_test.go +++ b/bfcodegen/enum-generate_test.go @@ -12,8 +12,11 @@ import ( "time" ) -//go:embed _test_example.tgz -var EnumExampleModels []byte +//go:embed _test_example_1.tgz +var EnumExampleModels1 []byte + +//go:embed _test_example_2.tgz +var EnumExampleModels2 []byte func TestGenerateEnumSpecs(t *testing.T) { @@ -21,7 +24,7 @@ func TestGenerateEnumSpecs(t *testing.T) { tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) - err := os.WriteFile(tmpFile, EnumExampleModels, 0o777) + err := os.WriteFile(tmpFile, EnumExampleModels1, 0o777) tst.AssertNoErr(t, err) t.Cleanup(func() { _ = os.Remove(tmpFile) }) @@ -34,17 +37,53 @@ func TestGenerateEnumSpecs(t *testing.T) { _, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run() tst.AssertNoErr(t, err) - err = GenerateEnumSpecs(tmpDir, tmpDir+"/enums_gen.go") + s1, cs1, err := _generateEnumSpecs(tmpDir, "", "N/A", true) tst.AssertNoErr(t, err) - err = GenerateEnumSpecs(tmpDir, tmpDir+"/enums_gen.go") + s2, cs2, err := _generateEnumSpecs(tmpDir, "", "N/A", true) tst.AssertNoErr(t, err) + tst.AssertEqual(t, cs1, cs2) + tst.AssertEqual(t, s1, s2) + fmt.Println() fmt.Println() fmt.Println() fmt.Println("=====================================================================================================") - fmt.Println(string(tst.Must(os.ReadFile(tmpDir + "/enums_gen.go"))(t))) + fmt.Println(s1) + fmt.Println("=====================================================================================================") + fmt.Println() + fmt.Println() + fmt.Println() +} + +func TestGenerateEnumSpecsData(t *testing.T) { + + tmpFile := filepath.Join(t.TempDir(), langext.MustHexUUID()+".tgz") + + tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) + + err := os.WriteFile(tmpFile, EnumExampleModels2, 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) + + s1, _, err := _generateEnumSpecs(tmpDir, "", "", true) + tst.AssertNoErr(t, err) + + fmt.Println() + fmt.Println() + fmt.Println() + fmt.Println("=====================================================================================================") + fmt.Println(s1) fmt.Println("=====================================================================================================") fmt.Println() fmt.Println() diff --git a/bfcodegen/id-generate_test.go b/bfcodegen/id-generate_test.go index f37b163..3eec16a 100644 --- a/bfcodegen/id-generate_test.go +++ b/bfcodegen/id-generate_test.go @@ -12,8 +12,8 @@ import ( "time" ) -//go:embed _test_example.tgz -var IDExampleModels []byte +//go:embed _test_example_1.tgz +var IDExampleModels1 []byte func TestGenerateIDSpecs(t *testing.T) { @@ -21,7 +21,7 @@ func TestGenerateIDSpecs(t *testing.T) { tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) - err := os.WriteFile(tmpFile, IDExampleModels, 0o777) + err := os.WriteFile(tmpFile, IDExampleModels1, 0o777) tst.AssertNoErr(t, err) t.Cleanup(func() { _ = os.Remove(tmpFile) }) diff --git a/goextVersion.go b/goextVersion.go index e8d5864..f22581f 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.341" +const GoextVersion = "0.0.342" -const GoextVersionTimestamp = "2023-12-07T14:43:12+0100" +const GoextVersionTimestamp = "2023-12-07T17:57:06+0100" diff --git a/langext/maps.go b/langext/maps.go index 2b2307b..50da0a1 100644 --- a/langext/maps.go +++ b/langext/maps.go @@ -29,6 +29,14 @@ func ArrToMap[T comparable, V any](a []V, keyfunc func(V) T) map[T]V { return result } +func ArrToKVMap[T any, K comparable, V any](a []T, keyfunc func(T) K, valfunc func(T) V) map[K]V { + result := make(map[K]V, len(a)) + for _, v := range a { + result[keyfunc(v)] = valfunc(v) + } + return result +} + func ArrToSet[T comparable](a []T) map[T]bool { result := make(map[T]bool, len(a)) for _, v := range a {