From 303d38d07990e82a6a4e16654312488f794a0bf0 Mon Sep 17 00:00:00 2001 From: wenchy Date: Mon, 7 Jul 2025 23:09:47 +0800 Subject: [PATCH 1/3] naming(Index): sorted columns --- cmd/protoc-gen-cpp-tableau-loader/index.go | 6 ++-- cmd/protoc-gen-go-tableau-loader/index.go | 6 ++-- internal/index/descriptor.go | 16 +++++------ internal/index/descriptor_test.go | 16 +++++------ internal/index/index.go | 30 ++++++++++---------- internal/index/index_test.go | 32 +++++++++++----------- 6 files changed, 53 insertions(+), 53 deletions(-) diff --git a/cmd/protoc-gen-cpp-tableau-loader/index.go b/cmd/protoc-gen-cpp-tableau-loader/index.go index aa8fd969..011838ee 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/index.go +++ b/cmd/protoc-gen-cpp-tableau-loader/index.go @@ -160,12 +160,12 @@ func genIndexSorter(g *protogen.GeneratedFile, descriptor *index.IndexDescriptor for levelMessage := descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { for _, index := range levelMessage.Indexes { indexContainerName := "index_" + strcase.ToSnake(index.Name()) + "_map_" - if len(index.KeyFields) != 0 { + if len(index.SortedColFields) != 0 { g.P(helper.Indent(1), "// Index(sort): ", index.Index) g.P(helper.Indent(1), "for (auto&& item : ", indexContainerName, ") {") g.P(helper.Indent(2), "std::sort(item.second.begin(), item.second.end(),") g.P(helper.Indent(7), "[](const ", helper.ParseCppClassType(index.MD), "* a, const ", helper.ParseCppClassType(index.MD), "* b) {") - for i, field := range index.KeyFields { + for i, field := range index.SortedColFields { fieldName := "" for i, leveledFd := range field.LeveledFDList { accessOperator := "." @@ -174,7 +174,7 @@ func genIndexSorter(g *protogen.GeneratedFile, descriptor *index.IndexDescriptor } fieldName += accessOperator + helper.ParseIndexFieldName(leveledFd) + "()" } - if i == len(index.KeyFields)-1 { + if i == len(index.SortedColFields)-1 { g.P(helper.Indent(8), "return a", fieldName, " < b", fieldName, ";") } else { g.P(helper.Indent(8), "if (a", fieldName, " != b", fieldName, ") {") diff --git a/cmd/protoc-gen-go-tableau-loader/index.go b/cmd/protoc-gen-go-tableau-loader/index.go index 1662eb78..d0f735f0 100644 --- a/cmd/protoc-gen-go-tableau-loader/index.go +++ b/cmd/protoc-gen-go-tableau-loader/index.go @@ -118,16 +118,16 @@ func genIndexSorter(gen *protogen.Plugin, g *protogen.GeneratedFile, descriptor for levelMessage := descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { for _, index := range levelMessage.Indexes { indexContainerName := "index" + strcase.ToCamel(index.Name()) + "Map" - if len(index.KeyFields) != 0 { + if len(index.SortedColFields) != 0 { g.P("// Index(sort): ", index.Index) g.P("for _, item := range x.", indexContainerName, " {") g.P(sortPackage.Ident("Slice"), "(item, func(i, j int) bool {") - for i, field := range index.KeyFields { + for i, field := range index.SortedColFields { fieldName := "" for _, leveledFd := range field.LeveledFDList { fieldName += ".Get" + helper.ParseIndexFieldName(gen, leveledFd) + "()" } - if i == len(index.KeyFields)-1 { + if i == len(index.SortedColFields)-1 { g.P("return item[i]", fieldName, " < item[j]", fieldName) } else { g.P("if item[i]", fieldName, " != item[j]", fieldName, " {") diff --git a/internal/index/descriptor.go b/internal/index/descriptor.go index c35fa1b9..408013f5 100644 --- a/internal/index/descriptor.go +++ b/internal/index/descriptor.go @@ -63,9 +63,9 @@ func (l *LevelMessage) NeedGen() bool { type LevelIndex struct { *Index - MD protoreflect.MessageDescriptor - ColFields []*LevelField - KeyFields []*LevelField + MD protoreflect.MessageDescriptor + ColFields []*LevelField + SortedColFields []*LevelField } func (l *LevelIndex) Name() string { @@ -96,14 +96,14 @@ func parseLevelMessage(md protoreflect.MessageDescriptor) *LevelMessage { // parseRecursively parses multi-column index related info. func parseRecursively(index *Index, prefix string, md protoreflect.MessageDescriptor, levelMessage *LevelMessage) { colFields := parseInSameLevel(index.Cols, prefix, md, nil) - keyFields := parseInSameLevel(index.Keys, prefix, md, nil) + sortedColFields := parseInSameLevel(index.SortedCols, prefix, md, nil) if len(colFields) != 0 { // index belongs to current level levelMessage.Indexes = append(levelMessage.Indexes, &LevelIndex{ - Index: index, - MD: md, - ColFields: colFields, - KeyFields: keyFields, + Index: index, + MD: md, + ColFields: colFields, + SortedColFields: sortedColFields, }) } else if levelMessage != nil && levelMessage.NextLevel != nil { // index invalid or belongs to deeper level diff --git a/internal/index/descriptor_test.go b/internal/index/descriptor_test.go index 37952b82..8b74d987 100644 --- a/internal/index/descriptor_test.go +++ b/internal/index/descriptor_test.go @@ -55,7 +55,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { { Index: &Index{ Cols: []string{"Param"}, - Keys: []string{"ID"}, + SortedCols: []string{"ID"}, Name: "ItemInfo", }, MD: md[*protoconf.ItemConf_Item](), @@ -67,7 +67,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, }, }, - KeyFields: []*LevelField{ + SortedColFields: []*LevelField{ { FD: fd[*protoconf.ItemConf_Item]("id"), LeveledFDList: []protoreflect.FieldDescriptor{ @@ -109,7 +109,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { { Index: &Index{ Cols: []string{"ID", "Name"}, - Keys: []string{"Type", "UseEffectType"}, + SortedCols: []string{"Type", "UseEffectType"}, Name: "AwardItem", }, MD: md[*protoconf.ItemConf_Item](), @@ -127,7 +127,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, }, }, - KeyFields: []*LevelField{ + SortedColFields: []*LevelField{ { FD: fd[*protoconf.ItemConf_Item]("type"), LeveledFDList: []protoreflect.FieldDescriptor{ @@ -327,7 +327,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { { Index: &Index{ Cols: []string{"ChapterName"}, - Keys: []string{"AwardID"}, + SortedCols: []string{"AwardID"}, Name: "NamedChapter", }, MD: md[*protoconf.ActivityConf_Activity_Chapter](), @@ -339,7 +339,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, }, }, - KeyFields: []*LevelField{ + SortedColFields: []*LevelField{ { FD: fd[*protoconf.ActivityConf_Activity_Chapter]("award_id"), LeveledFDList: []protoreflect.FieldDescriptor{ @@ -391,7 +391,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { { Index: &Index{ Cols: []string{"ActivityID"}, - Keys: []string{"Goal", "ID"}, + SortedCols: []string{"Goal", "ID"}, Name: "", }, MD: md[*protoconf.TaskConf_Task](), @@ -403,7 +403,7 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, }, }, - KeyFields: []*LevelField{ + SortedColFields: []*LevelField{ { FD: fd[*protoconf.TaskConf_Task]("goal"), LeveledFDList: []protoreflect.FieldDescriptor{ diff --git a/internal/index/index.go b/internal/index/index.go index b357c796..2454c25b 100644 --- a/internal/index/index.go +++ b/internal/index/index.go @@ -16,21 +16,21 @@ func init() { // Single-column index: // - ID // - ID@Item - // - ID@Item - // - ID@Item + // - ID@Item + // - ID@Item // // Multi-column index (composite index): // - (ID, Name) // - (ID, Name)@Item - // - (ID, Name)@Item - // - (ID, Name)@Item - indexRegexp = regexp.MustCompile(`^(?P\([^)]+\)|[^<@]+)?(<(?P[^>]+)>)?(@(?P.+))?$`) + // - (ID, Name)@Item + // - (ID, Name)@Item + indexRegexp = regexp.MustCompile(`^(?P\([^)]+\)|[^<@]+)?(<(?P[^>]+)>)?(@(?P.+))?$`) } type Index struct { - Cols []string // column names in CamelCase (single-column or multi-column) - Name string // index name in CamelCase - Keys []string // key names in CamelCase (single-column or multi-column) + Cols []string // column names in CamelCase (single-column or multi-column) + Name string // index name in CamelCase + SortedCols []string // sorted column names in CamelCase (single-column or multi-column) } func (index *Index) String() string { @@ -44,8 +44,8 @@ func (index *Index) String() string { if len(index.Cols) > 1 { syntax = "(" + syntax + ")" } - if len(index.Keys) != 0 { - syntax += "<" + strings.Join(index.Keys, ",") + ">" + if len(index.SortedCols) != 0 { + syntax += "<" + strings.Join(index.SortedCols, ",") + ">" } if index.Name != "" { syntax += "@" + index.Name @@ -92,11 +92,11 @@ func parseIndex(indexStr string) *Index { if len(index.Cols) == 0 { return nil } - // Extract keys - if keys := matches[indexRegexp.SubexpIndex("keys")]; keys != "" { - index.Keys = strings.Split(keys, ",") - for i, key := range index.Keys { - index.Keys[i] = strings.TrimSpace(key) + // Extract sortedCols + if sortedCols := matches[indexRegexp.SubexpIndex("sortedCols")]; sortedCols != "" { + index.SortedCols = strings.Split(sortedCols, ",") + for i, col := range index.SortedCols { + index.SortedCols[i] = strings.TrimSpace(col) } } // Extract name diff --git a/internal/index/index_test.go b/internal/index/index_test.go index a65c8172..f0f58a07 100644 --- a/internal/index/index_test.go +++ b/internal/index/index_test.go @@ -12,20 +12,20 @@ func Test_parseIndex(t *testing.T) { want *Index }{ { - name: "Single column with single key and name", - input: "Column1@IndexName", + name: "Single column with single sorted column and name", + input: "Column1@IndexName", want: &Index{ Cols: []string{"Column1"}, - Keys: []string{"Key1"}, + SortedCols: []string{"SortedCol1"}, Name: "IndexName", }, }, { - name: "Multi-column with multi-key and name", - input: "( Column1 , Column2 )< Key1 , Key2 >@IndexName", + name: "Multi-column with multi sorted column and name", + input: "( Column1 , Column2 )< SortedCol1 , SortedCol2 >@IndexName", want: &Index{ Cols: []string{"Column1", "Column2"}, - Keys: []string{"Key1", "Key2"}, + SortedCols: []string{"SortedCol1", "SortedCol2"}, Name: "IndexName", }, }, @@ -38,18 +38,18 @@ func Test_parseIndex(t *testing.T) { }, }, { - name: "Multi-column without keys or name", + name: "Multi-column without sorted columns or name", input: "(Column4, Column5)", want: &Index{ Cols: []string{"Column4", "Column5"}, }, }, { - name: "Single column with single key only", - input: "Column6", + name: "Single column with single sorted column only", + input: "Column6", want: &Index{ Cols: []string{"Column6"}, - Keys: []string{"Key6"}, + SortedCols: []string{"SortedCol"}, }, }, { @@ -57,31 +57,31 @@ func Test_parseIndex(t *testing.T) { input: "ActivityID", want: &Index{ Cols: []string{"ActivityID"}, - Keys: []string{"Goal", "ID"}, + SortedCols: []string{"Goal", "ID"}, }, }, { name: "Multi-column with spaces around commas", - input: "(Column7, Column8, Column9)@IndexName", + input: "(Column7, Column8, Column9)@IndexName", want: &Index{ Cols: []string{"Column7", "Column8", "Column9"}, - Keys: []string{"Key7", "Key8", "Key9"}, + SortedCols: []string{"SortedCol7", "SortedCol8", "SortedCol9"}, Name: "IndexName", }, }, { name: "Invalid format (multi-column without parentheses)", - input: "Column10, Column11@IndexName", + input: "Column10, Column11@IndexName", want: nil, }, { name: "Invalid format (single column with parentheses)", - input: "(Column12)@IndexName", + input: "(Column12)@IndexName", want: nil, }, { name: "Invalid format (empty columns)", - input: "@IndexName", + input: "@IndexName", want: nil, }, } From 9f862034383554252da7f736293f8139132d891f Mon Sep 17 00:00:00 2001 From: wenchy Date: Mon, 7 Jul 2025 23:41:14 +0800 Subject: [PATCH 2/3] feat: add matchIndex for secure regex group matching --- internal/index/index.go | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/internal/index/index.go b/internal/index/index.go index 2454c25b..7917fba9 100644 --- a/internal/index/index.go +++ b/internal/index/index.go @@ -24,7 +24,29 @@ func init() { // - (ID, Name)@Item // - (ID, Name)@Item // - (ID, Name)@Item - indexRegexp = regexp.MustCompile(`^(?P\([^)]+\)|[^<@]+)?(<(?P[^>]+)>)?(@(?P.+))?$`) + indexRegexp = regexp.MustCompile(`^(?P\([^)]+\)|[^<@]+)?(<(?P[^>]+)>)?(@(?P.+))?$`) +} + +// matchIndex parses the index syntax and returns the columns, sorted columns and name. +func matchIndex(text string) (cols, sortedCols, name string) { + match := indexRegexp.FindStringSubmatch(text) + if match == nil { + return "", "", "" + } + for i, expName := range indexRegexp.SubexpNames() { + value := strings.TrimSpace(match[i]) + switch expName { + case "Cols": + cols = value + case "SortedCols": + sortedCols = value + case "Name": + name = value + default: + continue + } + } + return cols, sortedCols, name } type Index struct { @@ -61,10 +83,10 @@ func ParseWSOptionIndex(md protoreflect.MessageDescriptor) []*Index { } func parseIndex(indexStr string) *Index { + cols, sortedCols, name := matchIndex(indexStr) index := &Index{} - matches := indexRegexp.FindStringSubmatch(indexStr) // Extract columns - if cols := matches[indexRegexp.SubexpIndex("cols")]; cols != "" { + if cols != "" { if strings.HasPrefix(cols, "(") && strings.HasSuffix(cols, ")") { // Multi-column index cols = cols[1 : len(cols)-1] @@ -93,16 +115,14 @@ func parseIndex(indexStr string) *Index { return nil } // Extract sortedCols - if sortedCols := matches[indexRegexp.SubexpIndex("sortedCols")]; sortedCols != "" { + if sortedCols != "" { index.SortedCols = strings.Split(sortedCols, ",") for i, col := range index.SortedCols { index.SortedCols[i] = strings.TrimSpace(col) } } // Extract name - if name := matches[indexRegexp.SubexpIndex("name")]; name != "" { - index.Name = name - } + index.Name = name return index } From 8d40c2da0537966b79d70363cde017a011a38089 Mon Sep 17 00:00:00 2001 From: wenchy Date: Mon, 7 Jul 2025 23:45:25 +0800 Subject: [PATCH 3/3] feat: Version: add plugin cmd option to print version --- cmd/protoc-gen-cpp-tableau-loader/main.go | 8 ++++++++ cmd/protoc-gen-go-tableau-loader/main.go | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/cmd/protoc-gen-cpp-tableau-loader/main.go b/cmd/protoc-gen-cpp-tableau-loader/main.go index 856dbd9c..623f05f8 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/main.go +++ b/cmd/protoc-gen-cpp-tableau-loader/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "github.com/tableauio/loader/internal/options" "google.golang.org/protobuf/compiler/protogen" @@ -31,6 +32,13 @@ const ( ) func main() { + showVersion := flag.Bool("version", false, "print the version and exit") + flag.Parse() + if *showVersion { + fmt.Printf("protoc-gen-cpp-tableau-loader %v\n", version) + return + } + var flags flag.FlagSet namespace = flags.String("namespace", "tableau", "tableau namespace") messagerSuffix = flags.String("suffix", "Mgr", "tableau messager name suffix") diff --git a/cmd/protoc-gen-go-tableau-loader/main.go b/cmd/protoc-gen-go-tableau-loader/main.go index d6199ffc..b2943867 100644 --- a/cmd/protoc-gen-go-tableau-loader/main.go +++ b/cmd/protoc-gen-go-tableau-loader/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "github.com/tableauio/loader/internal/options" "google.golang.org/protobuf/compiler/protogen" @@ -13,6 +14,13 @@ const version = "0.8.0" var pkg *string func main() { + showVersion := flag.Bool("version", false, "print the version and exit") + flag.Parse() + if *showVersion { + fmt.Printf("protoc-gen-cpp-tableau-loader %v\n", version) + return + } + var flags flag.FlagSet pkg = flags.String("pkg", "tableau", "tableau package name")