diff --git a/internal/importer/book/table.go b/internal/importer/book/table.go index 6a1db5c..6c1abb7 100644 --- a/internal/importer/book/table.go +++ b/internal/importer/book/table.go @@ -61,7 +61,8 @@ func (t *Table) IsRowEmpty(row int) bool { // it will just return the start row. Otherwise, it will return the last // none-empty row. // -// NOTE: A block is a series of contiguous none-empty rows. +// NOTE: A block is a series of contiguous none-empty rows. So different blocks +// are seperated by one or more empty rows. func (t *Table) FindBlockEndRow(startRow int) int { for row := startRow; row <= len(t.Rows); row++ { if t.IsRowEmpty(row) { @@ -76,7 +77,8 @@ func (t *Table) FindBlockEndRow(startRow int) int { // ExtractBlock extracts a block of rows. // -// NOTE: A block is a series of contiguous none-empty rows. +// NOTE: A block is a series of contiguous none-empty rows. So different blocks +// are seperated by one or more empty rows. func (t *Table) ExtractBlock(startRow int) (rows [][]string, endRow int) { endRow = t.FindBlockEndRow(startRow) rows = t.Rows[startRow : endRow+1] diff --git a/internal/protogen/exporter.go b/internal/protogen/exporter.go index 24f00b9..fc22395 100644 --- a/internal/protogen/exporter.go +++ b/internal/protogen/exporter.go @@ -151,7 +151,7 @@ func (x *sheetExporter) export() error { return x.exportMessager() case tableaupb.Mode_MODE_ENUM_TYPE, tableaupb.Mode_MODE_ENUM_TYPE_MULTI: return x.exportEnum() - case tableaupb.Mode_MODE_STRUCT_TYPE: + case tableaupb.Mode_MODE_STRUCT_TYPE, tableaupb.Mode_MODE_STRUCT_TYPE_MULTI: return x.exportStruct() case tableaupb.Mode_MODE_UNION_TYPE: return x.exportUnion() @@ -186,6 +186,13 @@ func (x *sheetExporter) exportEnum() error { func (x *sheetExporter) exportStruct() error { x.g.P("// Generated from sheet: ", x.ws.GetOptions().GetName(), ".") x.g.P("message ", x.ws.Name, " {") + // TODO: support worksheet options, but should not be treated as a + // standard config message, as this is a predefined message. + // if x.ws.Alias != "" { + // opts := &tableaupb.WorkbookOptions{Name: x.ws.Alias} + // x.g.P(" option (tableau.worksheet) = {", marshalToText(opts), "};") + // x.g.P("") + // } // generate the fields depth := 1 for i, field := range x.ws.Fields { diff --git a/internal/protogen/protogen.go b/internal/protogen/protogen.go index b3ee79a..bde5f06 100644 --- a/internal/protogen/protogen.go +++ b/internal/protogen/protogen.go @@ -519,14 +519,14 @@ func (gen *Generator) extractTypeInfoFromSpecialSheetMode(mode tableaupb.Mode, s case tableaupb.Mode_MODE_ENUM_TYPE_MULTI: for row := 0; row <= sheet.Table.MaxRow; row++ { cols := sheet.Table.GetRow(row) - if isEnumTypeDefinitionBlockHeader(cols) { + if isEnumTypeBlockHeader(cols) { if row < 1 { continue } typeRow := sheet.Table.GetRow(row - 1) typeName, _, _, err := extractEnumTypeRow(typeRow) if err != nil { - return xerrors.Wrapf(err, "failed to parse enum type block at row: %d, sheet: %s", row, sheet.Name) + return xerrors.Wrapf(err, "failed to parse enum type block, sheet: %s, row: %d", sheet.Name, row) } // add type info info := &xproto.TypeInfo{ @@ -554,7 +554,40 @@ func (gen *Generator) extractTypeInfoFromSpecialSheetMode(mode tableaupb.Mode, s FirstFieldOptionName: firstFieldOptionName, } gen.typeInfos.Put(info) - + case tableaupb.Mode_MODE_STRUCT_TYPE_MULTI: + for row := 0; row <= sheet.Table.MaxRow; row++ { + cols := sheet.Table.GetRow(row) + if isStructTypeBlockHeader(cols) { + if row < 1 { + continue + } + typeRow := sheet.Table.GetRow(row - 1) + typeName, _, _, err := extractStructTypeRow(typeRow) + if err != nil { + return xerrors.Wrapf(err, "failed to parse struct type block at row: %d, sheet: %s", row, sheet.Name) + } + blockBeginRow := row + block, blockEndRow := sheet.Table.ExtractBlock(blockBeginRow) + row = blockEndRow // skip row to next block + subSheet := book.NewTableSheet(sheet.Name, block) + desc := &internalpb.StructDescriptor{} + if err := parser.Parse(desc, subSheet); err != nil { + return xerrors.Wrapf(err, "failed to parse struct type block, sheet: %s, row: %d", sheet.Name, row) + } + firstFieldOptionName := "" + if len(desc.Fields) != 0 { + firstFieldOptionName = desc.Fields[0].Name + } + // add type info + info := &xproto.TypeInfo{ + FullName: protoreflect.FullName(gen.ProtoPackage + "." + typeName), + ParentFilename: parentFilename, + Kind: types.MessageKind, + FirstFieldOptionName: firstFieldOptionName, + } + gen.typeInfos.Put(info) + } + } case tableaupb.Mode_MODE_UNION_TYPE: // add union self type info info := &xproto.TypeInfo{ @@ -618,7 +651,7 @@ func (gen *Generator) parseSpecialSheetMode(mode tableaupb.Mode, ws *internalpb. var worksheets []*internalpb.Worksheet for row := 0; row <= sheet.Table.MaxRow; row++ { cols := sheet.Table.GetRow(row) - if isEnumTypeDefinitionBlockHeader(cols) { + if isEnumTypeBlockHeader(cols) { if row < 1 { continue } @@ -628,11 +661,11 @@ func (gen *Generator) parseSpecialSheetMode(mode tableaupb.Mode, ws *internalpb. subWs := proto.Clone(ws).(*internalpb.Worksheet) subWs.Name, subWs.Alias, subWs.Note, err = extractEnumTypeRow(typeRow) if err != nil { - return nil, xerrors.Wrapf(err, "failed to parse enum type block at row: %d, sheet: %s", row, sheet.Name) + return nil, xerrors.Wrapf(err, "failed to extract enum type block at row: %d, sheet: %s", row, sheet.Name) } block, blockEndRow := sheet.Table.ExtractBlock(blockBeginRow) row = blockEndRow // skip row to next block - subSheet := book.NewTableSheet(sheet.Name, block) + subSheet := book.NewTableSheet(subWs.Name, block) if err := parseEnumTypeValues(subWs, subSheet, parser); err != nil { return nil, err } @@ -641,36 +674,36 @@ func (gen *Generator) parseSpecialSheetMode(mode tableaupb.Mode, ws *internalpb. } return worksheets, nil case tableaupb.Mode_MODE_STRUCT_TYPE: - desc := &internalpb.StructDescriptor{} - if err := parser.Parse(desc, sheet); err != nil { - return nil, xerrors.Wrapf(err, "failed to parse struct type sheet: %s", sheet.Name) - } - bp := newBookParser("struct", "", "", gen) - shHeader := &tableHeader{ - meta: &tableaupb.WorksheetOptions{ - Namerow: 1, - Typerow: 2, - }, - validNames: map[string]int{}, - } - for _, field := range desc.Fields { - shHeader.namerow = append(shHeader.namerow, field.Name) - shHeader.typerow = append(shHeader.typerow, field.Type) - shHeader.noterow = append(shHeader.noterow, "") + if err := parseStructTypeValues(ws, sheet, parser, gen, debugBookName, debugSheetName); err != nil { + return nil, err } - var parsed bool - var err error - for cursor := 0; cursor < len(shHeader.namerow); cursor++ { - subField := &internalpb.Field{} - cursor, parsed, err = bp.parseField(subField, shHeader, cursor, "") - if err != nil { - return nil, wrapDebugErr(err, debugBookName, debugSheetName, shHeader, cursor) - } - if parsed { - ws.Fields = append(ws.Fields, subField) + return []*internalpb.Worksheet{ws}, nil + case tableaupb.Mode_MODE_STRUCT_TYPE_MULTI: + var worksheets []*internalpb.Worksheet + for row := 0; row <= sheet.Table.MaxRow; row++ { + cols := sheet.Table.GetRow(row) + if isStructTypeBlockHeader(cols) { + if row < 1 { + continue + } + blockBeginRow := row + typeRow := sheet.Table.GetRow(row - 1) + var err error + subWs := proto.Clone(ws).(*internalpb.Worksheet) + subWs.Name, subWs.Alias, subWs.Note, err = extractStructTypeRow(typeRow) + if err != nil { + return nil, xerrors.Wrapf(err, "failed to extract struct type block at row: %d, sheet: %s", row, sheet.Name) + } + block, blockEndRow := sheet.Table.ExtractBlock(blockBeginRow) + row = blockEndRow // skip row to next block + subSheet := book.NewTableSheet(subWs.Name, block) + if err := parseStructTypeValues(subWs, subSheet, parser, gen, debugBookName, debugSheetName); err != nil { + return nil, err + } + worksheets = append(worksheets, subWs) } } - return []*internalpb.Worksheet{ws}, nil + return worksheets, nil case tableaupb.Mode_MODE_UNION_TYPE: desc := &internalpb.UnionDescriptor{} if err := parser.Parse(desc, sheet); err != nil { diff --git a/internal/protogen/sheet_mode.go b/internal/protogen/sheet_mode.go new file mode 100644 index 0000000..9ec6547 --- /dev/null +++ b/internal/protogen/sheet_mode.go @@ -0,0 +1,122 @@ +package protogen + +import ( + "fmt" + + "github.com/tableauio/tableau/internal/importer/book" + "github.com/tableauio/tableau/proto/tableaupb" + "github.com/tableauio/tableau/proto/tableaupb/internalpb" + "github.com/tableauio/tableau/xerrors" +) + +const ( + colNumber = "Number" // name of column "Number" + colName = "Name" // name of column "Name" + colType = "Type" // name of column "Type" + colAlias = "Alias" // name of column "Alias" +) + +func isEnumTypeBlockHeader(cols []string) bool { + if len(cols) < 3 { + return false + } + return cols[0] == colNumber && cols[1] == colName && cols[2] == colAlias +} + +// extractEnumTypeRow find the first none-empty column as "name", and then +// the two subsequent columns as "alias" and "note" if provided. +func extractEnumTypeRow(cols []string) (name, alias, note string, err error) { + for i, cell := range cols { + if cell != "" { + name = cell + if i+1 < len(cols) { + alias = cols[i+1] + } + if i+2 < len(cols) { + note = cols[i+2] + } + break + } + } + if name == "" { + err = fmt.Errorf("name cell not found in enum type name row") + } + return +} + +func parseEnumTypeValues(ws *internalpb.Worksheet, sheet *book.Sheet, parser book.SheetParser) error { + desc := &internalpb.EnumDescriptor{} + if err := parser.Parse(desc, sheet); err != nil { + return xerrors.Wrapf(err, "failed to parse enum type sheet (block): %s", sheet.Name) + } + for i, value := range desc.Values { + number := int32(i + 1) + if value.Number != nil { + number = *value.Number + } + field := &internalpb.Field{ + Number: number, + Name: value.Name, + Alias: value.Alias, + } + ws.Fields = append(ws.Fields, field) + } + return nil +} + +func isStructTypeBlockHeader(cols []string) bool { + if len(cols) < 2 { + return false + } + return cols[0] == colName && cols[1] == colType +} + +// extractStructTypeRow find the first none-empty column as "name", and then +// the two subsequent columns as "alias" and "note" if provided. +func extractStructTypeRow(cols []string) (name, alias, note string, err error) { + if len(cols) == 0 || cols[0] == "" { + err = fmt.Errorf("name cell not found in struct type name row") + return + } + name = cols[0] + if len(cols) >= 2 { + alias = cols[1] + } + if len(cols) >= 3 { + note = cols[2] + } + return +} + +func parseStructTypeValues(ws *internalpb.Worksheet, sheet *book.Sheet, parser book.SheetParser, gen *Generator, debugBookName, debugSheetName string) error { + desc := &internalpb.StructDescriptor{} + if err := parser.Parse(desc, sheet); err != nil { + return xerrors.Wrapf(err, "failed to parse struct type sheet (block): %s", sheet.Name) + } + bp := newBookParser("struct", "", "", gen) + shHeader := &tableHeader{ + meta: &tableaupb.WorksheetOptions{ + Namerow: 1, + Typerow: 2, + }, + validNames: map[string]int{}, + } + for _, field := range desc.Fields { + shHeader.namerow = append(shHeader.namerow, field.Name) + shHeader.typerow = append(shHeader.typerow, field.Type) + shHeader.noterow = append(shHeader.noterow, "") + } + var parsed bool + var err error + for cursor := 0; cursor < len(shHeader.namerow); cursor++ { + subField := &internalpb.Field{} + cursor, parsed, err = bp.parseField(subField, shHeader, cursor, "") + if err != nil { + return wrapDebugErr(err, debugBookName, debugSheetName, shHeader, cursor) + } + if parsed { + ws.Fields = append(ws.Fields, subField) + } + } + return nil +} diff --git a/internal/protogen/util.go b/internal/protogen/util.go index b3e5bf7..04e2b72 100644 --- a/internal/protogen/util.go +++ b/internal/protogen/util.go @@ -13,7 +13,6 @@ import ( "github.com/tableauio/tableau/internal/x/xfs" "github.com/tableauio/tableau/options" "github.com/tableauio/tableau/proto/tableaupb" - "github.com/tableauio/tableau/proto/tableaupb/internalpb" "github.com/tableauio/tableau/xerrors" ) @@ -24,60 +23,6 @@ const ( secondPass // generate config messagers from sheets ) -const ( - colNumber = "Number" // name of column "Number" - colName = "Name" // name of column "Name" - colAlias = "Alias" // name of column "Alias" -) - -func isEnumTypeDefinitionBlockHeader(cols []string) bool { - if len(cols) < 3 { - return false - } - return cols[0] == colNumber && cols[1] == colName && cols[2] == colAlias -} - -// extractEnumTypeRow find the first none-empty column as "name", and then -// the two subsequent columns as "alias" and "note" if provided. -func extractEnumTypeRow(cols []string) (name, alias, note string, err error) { - for i, cell := range cols { - if cell != "" { - name = cell - if i+1 < len(cols) { - alias = cols[i+1] - } - if i+2 < len(cols) { - note = cols[i+2] - } - break - } - } - if name == "" { - err = fmt.Errorf("name cell not found in enum type row") - } - return -} - -func parseEnumTypeValues(ws *internalpb.Worksheet, sheet *book.Sheet, parser book.SheetParser) error { - desc := &internalpb.EnumDescriptor{} - if err := parser.Parse(desc, sheet); err != nil { - return xerrors.Wrapf(err, "failed to parse enum type sheet: %s", sheet.Name) - } - for i, value := range desc.Values { - number := int32(i + 1) - if value.Number != nil { - number = *value.Number - } - field := &internalpb.Field{ - Number: number, - Name: value.Name, - Alias: value.Alias, - } - ws.Fields = append(ws.Fields, field) - } - return nil -} - func prepareOutdir(outdir string, importFiles []string, delExisted bool) error { existed, err := xfs.Exists(outdir) if err != nil { diff --git a/test/functest/conf/StructTypeReuseInBook.json b/test/functest/conf/StructTypeReuseInBook.json index 3935edb..ae84678 100644 --- a/test/functest/conf/StructTypeReuseInBook.json +++ b/test/functest/conf/StructTypeReuseInBook.json @@ -1,42 +1,69 @@ { - "taskRewardMap": { + "rewardMap": { "1": { "id": 1, - "num": 100, - "fruitType": "FRUIT_TYPE_APPLE", - "featureList": [ - 1, - 2 - ], - "propMap": { - "1": "Power", - "2": "Skill" + "pet": { + "kind": 1, + "tipList": [ + "lovely", + "small" + ] }, - "detail": { - "type": "ITEM_TYPE_EQUIP", - "name": "Sword", - "desc": "Sword's Desc" + "taskReward": { + "id": 1, + "num": 100, + "fruitType": "FRUIT_TYPE_APPLE", + "featureList": [ + 1, + 2 + ], + "propMap": { + "1": "Power", + "2": "Skill" + }, + "detail": { + "type": "ITEM_TYPE_EQUIP", + "name": "Sword", + "desc": "Sword's Desc" + } } }, "2": { "id": 2, - "num": 200, - "fruitType": "FRUIT_TYPE_BANANA", - "featureList": [ - 1 - ], - "propMap": { - "1": "Power" + "pet": { + "kind": 2, + "tipList": [ + "beautiful", + "big" + ] }, - "detail": null + "taskReward": { + "id": 2, + "num": 200, + "fruitType": "FRUIT_TYPE_BANANA", + "featureList": [ + 1 + ], + "propMap": { + "1": "Power" + }, + "detail": null + } }, "3": { "id": 3, - "num": 300, - "fruitType": "FRUIT_TYPE_UNKNOWN", - "featureList": [], - "propMap": {}, - "detail": null + "pet": { + "kind": 3, + "tipList": [] + }, + "taskReward": { + "id": 3, + "num": 300, + "fruitType": "FRUIT_TYPE_UNKNOWN", + "featureList": [], + "propMap": {}, + "detail": null + } } } } \ No newline at end of file diff --git a/test/functest/proto/excel__metasheet__sheet_mode.proto b/test/functest/proto/excel__metasheet__sheet_mode.proto index 9bf2d00..cd0a20d 100644 --- a/test/functest/proto/excel__metasheet__sheet_mode.proto +++ b/test/functest/proto/excel__metasheet__sheet_mode.proto @@ -65,7 +65,7 @@ enum ItemType { // Generated from sheet: StructTaskReward. message TaskReward { - uint32 id = 1 [(tableau.field) = {name:"ID"}]; + uint32 id = 1 [(tableau.field) = {name:"ID" prop:{range:"1,10"}}]; int32 num = 2 [(tableau.field) = {name:"Num"}]; protoconf.FruitType fruit_type = 3 [(tableau.field) = {name:"FruitType"}]; repeated int32 feature_list = 4 [(tableau.field) = {name:"Feature" layout:LAYOUT_INCELL}]; @@ -78,10 +78,33 @@ message TaskReward { } } +// Generated from sheet: StructType. +message Tree { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + int32 num = 2 [(tableau.field) = {name:"Num"}]; +} + +// Generated from sheet: StructType. +message Pet { + int32 kind = 1 [(tableau.field) = {name:"Kind"}]; + repeated string tip_list = 2 [(tableau.field) = {name:"Tip" layout:LAYOUT_INCELL}]; +} + +// Generated from sheet: StructType. +message FruitShop { + protoconf.FruitType fruit_type = 1 [(tableau.field) = {name:"FruitType"}]; + map prop_map = 2 [(tableau.field) = {name:"Prop" layout:LAYOUT_INCELL}]; +} + message StructTypeReuseInBook { option (tableau.worksheet) = {name:"StructTypeReuseInBook" namerow:1 typerow:2 noterow:3 datarow:4}; - map task_reward_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + map reward_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}]; + message Reward { + uint32 id = 1 [(tableau.field) = {name:"ID"}]; + protoconf.Pet pet = 2 [(tableau.field) = {name:"Pet"}]; + protoconf.TaskReward task_reward = 3 [(tableau.field) = {name:"TaskReward"}]; + } } // Generated from sheet: UnionSimpleTarget. diff --git a/test/functest/testdata/excel/metasheet/SheetMode#@TABLEAU.csv b/test/functest/testdata/excel/metasheet/SheetMode#@TABLEAU.csv index 1968ad0..a340dbd 100644 --- a/test/functest/testdata/excel/metasheet/SheetMode#@TABLEAU.csv +++ b/test/functest/testdata/excel/metasheet/SheetMode#@TABLEAU.csv @@ -7,3 +7,4 @@ UnionSimpleTarget,SimpleTarget,MODE_UNION_TYPE EnumTypeReuseInBook,, StructTypeReuseInBook,, UnionTypeReuseInBook,, +StructType,,MODE_STRUCT_TYPE_MULTI diff --git a/test/functest/testdata/excel/metasheet/SheetMode#StructTaskReward.csv b/test/functest/testdata/excel/metasheet/SheetMode#StructTaskReward.csv index 863cb47..0e2bb41 100644 --- a/test/functest/testdata/excel/metasheet/SheetMode#StructTaskReward.csv +++ b/test/functest/testdata/excel/metasheet/SheetMode#StructTaskReward.csv @@ -1,5 +1,5 @@ Name,Type -ID,uint32 +ID,"uint32|{range: ""1,10""}" Num,int32 FruitType,enum<.FruitType> Feature,[]int32 diff --git a/test/functest/testdata/excel/metasheet/SheetMode#StructType.csv b/test/functest/testdata/excel/metasheet/SheetMode#StructType.csv new file mode 100644 index 0000000..6a99e30 --- /dev/null +++ b/test/functest/testdata/excel/metasheet/SheetMode#StructType.csv @@ -0,0 +1,16 @@ +Tree,TreeAlias +Name,Type +ID,uint32 +Num,int32 +, +Pet,PetAlias +Name,Type +Kind,int32 +Tip,[]string +, +, +, +FruitShop,FruitShopAlias +Name,Type +FruitType,enum<.FruitType> +Prop,"map" diff --git a/test/functest/testdata/excel/metasheet/SheetMode#StructTypeReuseInBook.csv b/test/functest/testdata/excel/metasheet/SheetMode#StructTypeReuseInBook.csv index 5060a1c..984d6ed 100644 --- a/test/functest/testdata/excel/metasheet/SheetMode#StructTypeReuseInBook.csv +++ b/test/functest/testdata/excel/metasheet/SheetMode#StructTypeReuseInBook.csv @@ -1,6 +1,6 @@ -ID,Num,FruitType,Feature,Prop,Detail -"map",int32,enum<.FruitType>,[]int32,"map","{enum<.ItemType> Type, string Name, string Desc}Detail" -Reward’s ID,Reward’s num,Reward’s fruit type,Reward’s feature,Reward’s Prop,Reward’s Detail -1,100,Apple,"1,2","1:Power,2:Skill","Equip,Sword,Sword's Desc" -2,200,Banana,1,1:Power, -3,300,,,, +ID,PetKind,PetTip,TaskRewardID,TaskRewardNum,TaskRewardFruitType,TaskRewardFeature,TaskRewardProp,TaskRewardDetail +"map",{.Pet}int32,[]string,{.TaskReward}uint32,int32,enum<.FruitType>,[]int32,"map","{enum<.ItemType> Type, string Name, string Desc}Detail" +Reward’s ID,Pet’s kind,Pet's tip,Reward’s ID,Reward’s num,Reward’s fruit type,Reward’s feature,Reward’s Prop,Reward’s Detail +1,1,"lovely,small",1,100,Apple,"1,2","1:Power,2:Skill","Equip,Sword,Sword's Desc" +2,2,"beautiful,big",2,200,Banana,1,1:Power, +3,3,,3,300,,,,