Skip to content

Commit

Permalink
protogen: support multiple struct type definitions in a sheet (#175)
Browse files Browse the repository at this point in the history
* feat: add field prop to Type column when define a struct type in sheet

* protogen: support multiple struct type definitions in a sheet
  • Loading branch information
wenchy authored Feb 14, 2025
1 parent 1ea50e2 commit 6039b69
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 127 deletions.
6 changes: 4 additions & 2 deletions internal/importer/book/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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]
Expand Down
9 changes: 8 additions & 1 deletion internal/protogen/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
99 changes: 66 additions & 33 deletions internal/protogen/protogen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down
122 changes: 122 additions & 0 deletions internal/protogen/sheet_mode.go
Original file line number Diff line number Diff line change
@@ -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
}
55 changes: 0 additions & 55 deletions internal/protogen/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 6039b69

Please sign in to comment.