Skip to content

Commit

Permalink
fix: clean up fitgen + fix mesgdef (#211)
Browse files Browse the repository at this point in the history
* chore: clean up fitgen code

* fix: mesgdef on dynamic field accessor

* fitgen generate code
  • Loading branch information
muktihari authored Apr 23, 2024
1 parent f4be214 commit 71da3b1
Show file tree
Hide file tree
Showing 132 changed files with 8,883 additions and 8,501 deletions.
302 changes: 151 additions & 151 deletions factory/factory_gen.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions internal/cmd/fitgen/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FIT SDK for Go Generator

The FIT SDK Generator in Go, also known as "fitgen", is a program designed to create several \*.go files using `Profile.xlsx`, file retrieved from the Official FIT SDK release. The generated files enable this FIT SDK for Go to carry out the decoding and encoding process of FIT files.
The FIT SDK for Go Generator, also known as "fitgen", is a program designed to create several \*.go files using `Profile.xlsx`, file retrieved from the Official FIT SDK release. The generated files enable this FIT SDK for Go to carry out the decoding and encoding process of FIT files.

The files are organized into distinct packages:

Expand All @@ -14,4 +14,4 @@ Example:
- "./fitgen --profile-file Profile-copy.xlsx --path ../../../../ --builders all --profile-version 21.115 -v -y"
- "./fitgen -f Profile-copy.xlsx -p ../../ -b all --profile-version 21.115 -v -y"

Note: The existing Garmin SDK specifications must not be altered, since it might result in data that does not align with the terms and conditions of the FIT Protocol.
Note: The existing Garmin SDK specifications must not be altered, as such modifications could lead to data that does not align with the terms and conditions of the FIT Protocol.
124 changes: 36 additions & 88 deletions internal/cmd/fitgen/factory/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,17 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"text/template"

"github.com/muktihari/fit/internal/cmd/fitgen/builder"
"github.com/muktihari/fit/internal/cmd/fitgen/lookup"
"github.com/muktihari/fit/internal/cmd/fitgen/parser"
"github.com/muktihari/fit/internal/cmd/fitgen/pkg/strutil"
"github.com/muktihari/fit/profile/basetype"
)

type ( // type aliasing for better code reading.
// types
ValueName = string
BaseType = string

// messages
MessageName = string
FieldName = string
FieldNum = byte

// profile
ProfileType = string
)

type factoryBuilder struct {
Expand All @@ -38,74 +28,35 @@ type factoryBuilder struct {
mesgnumPackageName string
profilePackageName string

path string // path to generate the file
path string // path to generate the file

lookup *lookup.Lookup
messages []parser.Message // messages parsed from profile.xlsx
types []parser.Type

once sync.Once

// types lookup
goTypesByProfileTypes map[ProfileType]string // (k -> v) typedef.DateTime -> uint32
baseTypeMapByProfileType map[ProfileType]BaseType // e.g. mesg_num -> uint16 , data_time -> uint32
valueMapByProfileTypeByValueName map[ProfileType]map[ValueName]parser.Value // e.g. map[mesg_num][file_id] -> Value{}

// message-field lookup
fieldMapByMessageNameByFieldNum map[MessageName]map[FieldNum]parser.Field // e.g. map[file_id][0] -> Field{}
fieldMapByMessageNameByFieldName map[MessageName]map[FieldName]parser.Field // e.g. map[file_createor][software_version] -> Field{}
}

func NewBuilder(path string, types []parser.Type, messages []parser.Message) builder.Builder {
func NewBuilder(path string, lookup *lookup.Lookup, types []parser.Type, messages []parser.Message) builder.Builder {
_, filename, _, _ := runtime.Caller(0)
cd := filepath.Dir(filename)
return &factoryBuilder{
template: template.Must(template.New("main").ParseFiles(filepath.Join(cd, "factory.tmpl"))),
path: filepath.Join(path, "factory"),
mesgnumPackageName: "typedef",
profilePackageName: "profile",
types: types,
messages: messages,
goTypesByProfileTypes: make(map[ProfileType]string),
baseTypeMapByProfileType: make(map[ProfileType]BaseType),
valueMapByProfileTypeByValueName: make(map[ProfileType]map[ValueName]parser.Value),
f := &factoryBuilder{
template: template.Must(template.New("main").ParseFiles(filepath.Join(cd, "factory.tmpl"))),
path: filepath.Join(path, "factory"),
mesgnumPackageName: "typedef",
profilePackageName: "profile",
types: types,
messages: messages,
lookup: lookup,
}
f.preproccessMessageField()
return f
}

func (b *factoryBuilder) populateLookupData() {
goTypesByBaseTypes := map[BaseType]string{
"bool": "bool",
"fit_base_type": "basetype.BaseType",
}

for _, v := range basetype.List() { // map to itself
goTypesByBaseTypes[v.String()] = v.GoType()
b.goTypesByProfileTypes[v.String()] = v.GoType()
b.baseTypeMapByProfileType[v.String()] = v.String()
}

// additional profile type which is not defined in basetype.
b.types = append(b.types, parser.Type{Name: "bool", BaseType: "enum"})

for _, _type := range b.types {
b.goTypesByProfileTypes[_type.Name] = goTypesByBaseTypes[_type.BaseType]
b.baseTypeMapByProfileType[_type.Name] = _type.BaseType
b.valueMapByProfileTypeByValueName[_type.Name] = make(map[ValueName]parser.Value)
for _, value := range _type.Values {
b.valueMapByProfileTypeByValueName[_type.Name][value.Name] = value
}
}

b.fieldMapByMessageNameByFieldNum = make(map[MessageName]map[FieldNum]parser.Field)
b.fieldMapByMessageNameByFieldName = make(map[MessageName]map[FieldName]parser.Field)
func (b *factoryBuilder) preproccessMessageField() {
// Prepare lookup table for field indexes
fieldIndexMapByMessageNameByFieldName := make(map[MessageName]map[FieldName]int)

for _, message := range b.messages {
b.fieldMapByMessageNameByFieldNum[message.Name] = make(map[FieldNum]parser.Field)
b.fieldMapByMessageNameByFieldName[message.Name] = make(map[FieldName]parser.Field)

fieldIndexMapByMessageNameByFieldName[message.Name] = make(map[FieldName]int)
for i, field := range message.Fields {
b.fieldMapByMessageNameByFieldNum[message.Name][field.Num] = field
b.fieldMapByMessageNameByFieldName[message.Name][field.Name] = field
fieldIndexMapByMessageNameByFieldName[message.Name][field.Name] = i
}
}
Expand All @@ -128,8 +79,6 @@ func (b *factoryBuilder) populateLookupData() {
}

func (b *factoryBuilder) Build() ([]builder.Data, error) {
b.once.Do(func() { b.populateLookupData() })

// Create structure of []proto.Message as string using strings.Builder{},
// This way, we don't depend on generated value such as types and profile package to be able to generate factory.
// And also we don't need to process the data in the template which is a bit painful for complex data structure.
Expand Down Expand Up @@ -182,15 +131,14 @@ func (b *factoryBuilder) makeFields(message parser.Message) string {
strbuf := new(strings.Builder)
strbuf.WriteString("[256]proto.Field{\n")
for _, field := range message.Fields {
// strbuf.WriteString("{\n")
strbuf.WriteString(fmt.Sprintf("%d: {\n", field.Num))
strbuf.WriteString("FieldBase: &proto.FieldBase{\n")
strbuf.WriteString(fmt.Sprintf("Name: %q,\n", field.Name))
strbuf.WriteString(fmt.Sprintf("Num: %d,\n", field.Num))
strbuf.WriteString(fmt.Sprintf("Type: %s,\n", b.transformProfileType(field.Type)))
strbuf.WriteString(fmt.Sprintf("BaseType: %s, /* (size: %d) */\n",
b.transformBaseType(field.Type),
basetype.FromString(b.baseTypeMapByProfileType[field.Type]).Size(),
b.lookup.BaseType(field.Type).Size(),
))
strbuf.WriteString(fmt.Sprintf("Array: %t, %s\n", field.Array != "", makeArrayComment(field.Array)))
strbuf.WriteString(fmt.Sprintf("Components: %s,\n", b.makeComponents(field, message.Name)))
Expand All @@ -216,7 +164,7 @@ func (b *factoryBuilder) makeComponents(compField parser.ComponentField, message
strbuf := new(strings.Builder)
strbuf.WriteString("[]proto.Component{\n")
for i, fieldNameRef := range compField.GetComponents() {
fieldRef := b.fieldMapByMessageNameByFieldName[messageName][fieldNameRef]
fieldRef := b.lookup.FieldByName(messageName, fieldNameRef)
strbuf.WriteString("{")
strbuf.WriteString(fmt.Sprintf("FieldNum: %d, /* %s */", fieldRef.Num, fieldRef.Name))
strbuf.WriteString(fmt.Sprintf("Scale: %g,", scaleOrDefault(compField.GetScales(), i))) // component index or default
Expand Down Expand Up @@ -261,12 +209,12 @@ func (b *factoryBuilder) makeSubFieldMaps(subfield parser.SubField, messageName
strbuf := new(strings.Builder)
strbuf.WriteString("[]proto.SubFieldMap{\n")
for i, refValueName := range subfield.RefFieldNames {
fieldRef := b.fieldMapByMessageNameByFieldName[messageName][refValueName]
fieldRef := b.lookup.FieldByName(messageName, refValueName)
strbuf.WriteString("{")
strbuf.WriteString(fmt.Sprintf("RefFieldNum: %d /* %s */,", fieldRef.Num, fieldRef.Name))

typeRef := b.valueMapByProfileTypeByValueName[fieldRef.Type][subfield.RefFieldValue[i]]
strbuf.WriteString(fmt.Sprintf("RefFieldValue: %s /* %s */,", typeRef.Value, typeRef.Name))
typeValue := b.lookup.TypeValue(fieldRef.Type, subfield.RefFieldValue[i])
strbuf.WriteString(fmt.Sprintf("RefFieldValue: %s /* %s */,", typeValue, subfield.RefFieldValue[i]))
strbuf.WriteString("},\n")
}
strbuf.WriteString("}")
Expand All @@ -278,11 +226,8 @@ func (b *factoryBuilder) transformProfileType(fieldType string) string {
}

func (b *factoryBuilder) transformBaseType(fieldType string) string {
baseType := b.baseTypeMapByProfileType[fieldType]
if baseType == "bool" {
baseType = "enum"
}
return "basetype." + strutil.ToTitle(baseType) // basetype.Uint16z
baseType := b.lookup.BaseType(fieldType)
return "basetype." + strutil.ToTitle(baseType.String()) // basetype.Uint16z
}

func (b *factoryBuilder) transformMesgnum(s string) string {
Expand All @@ -296,31 +241,34 @@ var baseTypeReplacer = strings.NewReplacer(
)

func (b *factoryBuilder) invalidValueOf(fieldType, array string) string {
if fieldType == "bool" {
if fieldType == "bool" { // Special type, bool does not have basetype
if array != "" {
return "proto.SliceBool([]bool(nil))"
}
return "proto.Bool(false)"
}

baseType := strutil.ToTitle(b.baseTypeMapByProfileType[fieldType])
baseType = baseTypeReplacer.Replace(baseType)

protoFuncName := strings.TrimSuffix(baseType, "z")
var (
baseType = strutil.ToTitle(b.lookup.BaseType(fieldType).String())
protoFuncName = strings.TrimSuffix(baseTypeReplacer.Replace(baseType), "z")
goType = b.lookup.GoType(fieldType)
)

goType := b.goTypesByProfileTypes[fieldType]
if array != "" {
return fmt.Sprintf("proto.Slice%s([]%s(nil))", protoFuncName, goType)
}

// Float is a special case since NaN is not comparable, so for example, basetype.Float32Invalid, is not a float,
// but its representation in integer form. This way we can compare it in its integer form later.
if b.baseTypeMapByProfileType[fieldType] == "float32" {
if baseType == "Float32" {
return "proto.Float32(math.Float32frombits(basetype.Float32Invalid))" // same as `math.Float32frombits(basetype.Float32Invalid)`
}

if b.baseTypeMapByProfileType[fieldType] == "float64" {
if baseType == "Float64" {
return "proto.Float64(math.Float64frombits(basetype.Float64Invalid))" // same as `math.Float64frombits(basetype.Float64Invalid)`
}

return fmt.Sprintf("proto.%s(basetype.%sInvalid)", protoFuncName, strings.Replace(baseType, "Int", "Sint", 1))
return fmt.Sprintf("proto.%s(basetype.%sInvalid)", protoFuncName, baseType)

}

Expand Down
106 changes: 106 additions & 0 deletions internal/cmd/fitgen/lookup/lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package lookup

import (
"github.com/muktihari/fit/internal/cmd/fitgen/parser"
"github.com/muktihari/fit/profile/basetype"
)

type Lookup struct {
fieldsByMesgName map[string][]parser.Field // fields is small, slice should be sufficient.
profiles map[string]profile
}

type profile struct {
goType string
baseType basetype.BaseType
valuesByName map[string]string
}

func NewLookup(types []parser.Type, messages []parser.Message) *Lookup {
l := &Lookup{
fieldsByMesgName: make(map[string][]parser.Field),
profiles: make(map[string]profile),
}
l.populateLookupData(types, messages)
return l
}

func (l *Lookup) populateLookupData(types []parser.Type, messages []parser.Message) {
l.populateProfileLookup(types)
l.populateMessageLookup(messages)
}

func (l *Lookup) populateProfileLookup(types []parser.Type) {
// additional profile type, mark it as enum
l.profiles["bool"] = profile{
baseType: basetype.Enum,
goType: "bool",
}

// map fit_base_type to our defined type "basetype.BaseType"
l.profiles["fit_base_type"] = profile{
baseType: basetype.Uint8,
goType: "basetype.BaseType",
}

basetypes := make(map[string]basetype.BaseType)

// map basetype as profile type
for _, bt := range basetype.List() {
l.profiles[bt.String()] = profile{
goType: bt.GoType(),
baseType: bt,
}
basetypes[bt.String()] = bt
}

for _, typ := range types {
p := profile{
goType: basetypes[typ.BaseType].GoType(),
baseType: basetype.FromString(typ.BaseType),
valuesByName: make(map[string]string),
}

for _, val := range typ.Values {
p.valuesByName[val.Name] = val.Value
}

l.profiles[typ.Name] = p
}
}

func (l *Lookup) populateMessageLookup(messages []parser.Message) {
for _, mesg := range messages {
l.fieldsByMesgName[mesg.Name] = mesg.Fields
}
}

func (l *Lookup) BaseType(profileType string) basetype.BaseType {
return l.profiles[profileType].baseType
}

func (l *Lookup) GoType(profileType string) string {
return l.profiles[profileType].goType
}

func (l *Lookup) TypeValue(profileType string, valueName string) string {
return l.profiles[profileType].valuesByName[valueName]
}

func (l *Lookup) FieldByNum(mesgName string, fieldNum byte) parser.Field {
for _, field := range l.fieldsByMesgName[mesgName] {
if field.Num == fieldNum {
return field
}
}
return parser.Field{Num: 255, Name: "unknown"}
}

func (l *Lookup) FieldByName(mesgName string, fieldName string) parser.Field {
for _, field := range l.fieldsByMesgName[mesgName] {
if field.Name == fieldName {
return field
}
}
return parser.Field{Num: 255, Name: "unknown"}
}
14 changes: 8 additions & 6 deletions internal/cmd/fitgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/muktihari/fit/internal/cmd/fitgen/builder"
"github.com/muktihari/fit/internal/cmd/fitgen/factory"
"github.com/muktihari/fit/internal/cmd/fitgen/generator"
"github.com/muktihari/fit/internal/cmd/fitgen/lookup"
"github.com/muktihari/fit/internal/cmd/fitgen/parser"
"github.com/muktihari/fit/internal/cmd/fitgen/pkg/flagutil"
"github.com/muktihari/fit/internal/cmd/fitgen/pkg/strutil"
Expand All @@ -28,7 +29,7 @@ import (
)

var aboutFitgen = `
The FIT SDK Generator in Go, also known as "fitgen", is a program designed to create
The FIT SDK for Go Generator, also known as "fitgen", is a program designed to create
several *.go files using Garmin SDK specifications (Profile.xlsx). These generated files
enable this FIT SDK to carry out the decoding and encoding process of FIT files.
Expand All @@ -45,8 +46,8 @@ Example:
- "./fitgen --profile-file Profile-copy.xlsx --path ../../../ --builders all --profile-version 21.115 -v -y"
- "./fitgen -f Profile-copy.xlsx -p ../../../ -b all --profile-version 21.115 -v -y"
Note: The existing Garmin SDK specifications must not be altered, since it might
result in data that does not align with the terms and conditions of the FIT Protocol.
Note: The existing Garmin SDK specifications must not be altered, as such modifications
could lead to data that does not align with the terms and conditions of the FIT Protocol.
`

func main() {
Expand Down Expand Up @@ -127,13 +128,14 @@ func main() {
fatalf(fmt.Sprintf("could no parse message: %v\n", err))
}

lookup := lookup.NewLookup(parsedtypes, parsedmesgs)
var (
typedefb = typedef.NewBuilder(generatePath, parsedtypes)
profileb = profile.NewBuilder(generatePath, profileVersion, parsedtypes)
factoryb = factory.NewBuilder(generatePath, parsedtypes, parsedmesgs)
factoryb = factory.NewBuilder(generatePath, lookup, parsedtypes, parsedmesgs)
mesgnumb = mesgnum.NewBuilder(generatePath, parsedtypes)
fielnumb = fieldnum.NewBuilder(generatePath, parsedmesgs, parsedtypes)
mesgdefb = mesgdef.NewBuilder(generatePath, parsedmesgs, parsedtypes)
fielnumb = fieldnum.NewBuilder(generatePath, lookup, parsedmesgs, parsedtypes)
mesgdefb = mesgdef.NewBuilder(generatePath, lookup, parsedmesgs, parsedtypes)
)

var builders []builder.Builder
Expand Down
Loading

0 comments on commit 71da3b1

Please sign in to comment.