diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index 0986c68..c7994cd 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/cjslep/activity/tools/exp/props" "github.com/cjslep/activity/tools/exp/rdf" - "github.com/cjslep/activity/tools/exp/types" "github.com/dave/jennifer/jen" "strings" ) @@ -19,7 +18,7 @@ type vocabulary struct { Kinds map[string]*props.Kind FProps map[string]*props.FunctionalPropertyGenerator NFProps map[string]*props.NonFunctionalPropertyGenerator - Types map[string]*types.TypeGenerator + Types map[string]*props.TypeGenerator } func newVocabulary() vocabulary { @@ -27,7 +26,7 @@ func newVocabulary() vocabulary { Kinds: make(map[string]*props.Kind, 0), FProps: make(map[string]*props.FunctionalPropertyGenerator, 0), NFProps: make(map[string]*props.NonFunctionalPropertyGenerator, 0), - Types: make(map[string]*types.TypeGenerator, 0), + Types: make(map[string]*props.TypeGenerator, 0), } } @@ -68,7 +67,7 @@ func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*File, e error) { func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { for _, _ = range v.Kinds { - // TODO + // TODO: Implement } for _, i := range v.FProps { var pkg string @@ -131,6 +130,16 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { return } +// convertVocabulary works in a two-pass system: first converting all known +// properties, and then the types. +// +// Due to the fact that properties rely on the Kind abstraction, and both +// properties and types can be Kinds, this introduces tight coupling between +// the two so that callbacks can fill in missing links in data that isn't known +// beforehand (ex: how to serialize, deserialize, and compare types). +// +// This feels very hacky and could be decoupled using standard design patterns, +// but since there is no need, it isn't addressed now. func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e error) { v = newVocabulary() for k, val := range p.Vocab.Values { @@ -159,7 +168,7 @@ func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e e stuck := true for i, t := range allTypes { if allExtendsAreIn(t, v.Types) { - var tg *types.TypeGenerator + var tg *props.TypeGenerator tg, e = c.convertType(t, p.Vocab, v.FProps, v.NFProps, v.Types) if e != nil { return @@ -173,7 +182,7 @@ func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e e } } if stuck { - e = fmt.Errorf("converting types got stuck in dependency cycle") + e = fmt.Errorf("converting props got stuck in dependency cycle") return } } @@ -184,46 +193,70 @@ func (c Converter) convertType(t rdf.VocabularyType, v rdf.Vocabulary, existingFProps map[string]*props.FunctionalPropertyGenerator, existingNFProps map[string]*props.NonFunctionalPropertyGenerator, - existingTypes map[string]*types.TypeGenerator) (tg *types.TypeGenerator, e error) { - // Determine the types package name + existingTypes map[string]*props.TypeGenerator) (tg *props.TypeGenerator, e error) { + // Determine the props package name var pkg string pkg, e = c.typePackageName(t) if e != nil { return } // Determine the properties for this type - var p []types.Property - for _, prop := range v.Properties { - for _, ref := range prop.Domain { - if len(ref.Vocab) != 0 { - e = fmt.Errorf("unhandled use case: property domain outside its vocabulary") - return - } else if ref.Name == t.Name { - if prop.Functional { - p = append(p, existingFProps[prop.Name]) - } else { - p = append(p, existingNFProps[prop.Name]) + var p []props.Property + for _, prop := range t.Properties { + if len(prop.Vocab) != 0 { + e = fmt.Errorf("unhandled use case: property domain outside its vocabulary") + return + } else { + var property props.Property + var ok bool + property, ok = existingFProps[prop.Name] + if !ok { + property, ok = existingNFProps[prop.Name] + if !ok { + e = fmt.Errorf("cannot find property with name: %s", prop.Name) + return } - break } + p = append(p, property) + } + } + // Determine WithoutProperties for this type + var wop []props.Property + for _, prop := range t.WithoutProperties { + if len(prop.Vocab) != 0 { + e = fmt.Errorf("unhandled use case: withoutproperty domain outside its vocabulary") + return + } else { + var property props.Property + var ok bool + property, ok = existingFProps[prop.Name] + if !ok { + property, ok = existingNFProps[prop.Name] + if !ok { + e = fmt.Errorf("cannot find property with name: %s", prop.Name) + return + } + } + wop = append(wop, property) } } // Determine what this type extends - var ext []*types.TypeGenerator + var ext []*props.TypeGenerator for _, ex := range t.Extends { if len(ex.Vocab) != 0 { - // TODO: This should be fixed + // TODO: This should be fixed to handle references e = fmt.Errorf("unhandled use case: type extends another type outside its vocabulary") return } else { ext = append(ext, existingTypes[ex.Name]) } } - tg, e = types.NewTypeGenerator( + tg, e = props.NewTypeGenerator( pkg, c.convertTypeToName(t), t.Notes, p, + wop, ext, nil) if e != nil { @@ -232,7 +265,7 @@ func (c Converter) convertType(t rdf.VocabularyType, // Apply disjoint if both sides are available. for _, disj := range t.DisjointWith { if len(disj.Vocab) != 0 { - // TODO: This should be fixed + // TODO: This should be fixed to handle references e = fmt.Errorf("unhandled use case: type is disjoint with another type outside its vocabulary") return } else if disjointType, ok := existingTypes[disj.Name]; ok { @@ -300,19 +333,18 @@ func (c Converter) convertValue(v rdf.VocabularyValue) (k *props.Kind) { } func (c Converter) convertTypeToKind(v rdf.VocabularyType) (k *props.Kind, e error) { - var pkg string - pkg, e = c.typePackageName(v) - if e != nil { - return - } - s, d, l := types.KindSerializationFuncs(pkg, c.convertTypeToName(v)) k = &props.Kind{ - Name: c.toIdentifier(v), - ConcreteKind: c.convertTypeToConcreteKind(v), - Nilable: true, - SerializeFn: s, - DeserializeFn: d, - LessFn: l, + Name: c.toIdentifier(v), + ConcreteKind: c.convertTypeToConcreteKind(v), + Nilable: true, + // Instead of populating: + // - SerializeFn + // - DeserializeFn + // - LessFn + // + // The TypeGenerator is responsible for calling setKindFns on + // the properties, to property wire a Property's Kind back to + // the Type's implementation. } return } @@ -409,7 +441,7 @@ func (c Converter) propertyPackageName(v rdf.VocabularyProperty) (pkg string, e return } -func (c Converter) typePackageDirectory(v *types.TypeGenerator) (dir string, e error) { +func (c Converter) typePackageDirectory(v *props.TypeGenerator) (dir string, e error) { switch c.TypePackagePolicy { case TypeFlatUnderRoot: dir = fmt.Sprintf("%s/%s/", c.VocabularyRoot, c.TypePackageRoot) @@ -423,7 +455,7 @@ func (c Converter) typePackageDirectory(v *types.TypeGenerator) (dir string, e e return } -func (c Converter) typePackageFile(v *types.TypeGenerator) (pkg string, e error) { +func (c Converter) typePackageFile(v *props.TypeGenerator) (pkg string, e error) { switch c.TypePackagePolicy { case TypeFlatUnderRoot: pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.TypePackageRoot) @@ -485,10 +517,10 @@ func (c Converter) isNilable(goType string) bool { return goType[0] == '*' } -func allExtendsAreIn(t rdf.VocabularyType, v map[string]*types.TypeGenerator) bool { +func allExtendsAreIn(t rdf.VocabularyType, v map[string]*props.TypeGenerator) bool { for _, e := range t.Extends { if len(e.Vocab) != 0 { - // TODO: Handle references + // TODO: This should be fixed to handle references return false } else if _, ok := v[e.Name]; !ok { return false diff --git a/tools/exp/props/funcprop.go b/tools/exp/props/funcprop.go index fe19446..7b49bdc 100644 --- a/tools/exp/props/funcprop.go +++ b/tools/exp/props/funcprop.go @@ -320,7 +320,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ deserialize = append(deserialize, codegen.NewCommentedFunction( p.packageName(), - p.deserializeFnName(), + p.DeserializeFnName(), []jen.Code{jen.Id("i").Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ @@ -330,13 +330,13 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ jen.Nil(), ), }, - jen.Commentf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.deserializeFnName()), + jen.Commentf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.DeserializeFnName()), )) } else { deserialize = append(deserialize, codegen.NewCommentedFunction( p.packageName(), - p.deserializeFnName(), + p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ @@ -356,7 +356,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ jen.Nil(), ), }, - jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.deserializeFnName(), p.PropertyName()), + jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName()), )) } return serialize, deserialize diff --git a/tools/exp/props/nonfuncprop.go b/tools/exp/props/nonfuncprop.go index e55a523..0f00337 100644 --- a/tools/exp/props/nonfuncprop.go +++ b/tools/exp/props/nonfuncprop.go @@ -303,7 +303,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method jen.List( jen.Id("p"), jen.Err(), - ).Op(":=").Id(p.elementTypeGenerator().deserializeFnName()).Call( + ).Op(":=").Id(p.elementTypeGenerator().DeserializeFnName()).Call( jen.Id(variable), ), jen.Err().Op("!=").Nil(), @@ -324,7 +324,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method deserialize := []*codegen.Function{ codegen.NewCommentedFunction( p.packageName(), - p.deserializeFnName(), + p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Id(p.StructName()), jen.Error()}, []jen.Code{ @@ -364,7 +364,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method jen.Nil(), ), }, - jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.deserializeFnName(), p.PropertyName()), + jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName()), ), } return serialize, deserialize diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index 7aff71f..574ced8 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -89,6 +89,27 @@ func (p *PropertyGenerator) packageName() string { return p.Package } +// SetKindFns allows TypeGenerators to later notify this Property what functions +// to use when generating the serialization code. +// +// The name parameter must match the LowerName of an Identifier. +// +// This feels very hacky. +func (p *PropertyGenerator) SetKindFns(name string, ser, deser, less *codegen.Function) error { + for _, kind := range p.Kinds { + if kind.Name.LowerName == name { + if kind.SerializeFn != nil || kind.DeserializeFn != nil || kind.LessFn != nil { + return fmt.Errorf("property kind already has serialization functions set for %q", name) + } + kind.SerializeFn = ser + kind.DeserializeFn = deser + kind.LessFn = less + return nil + } + } + return fmt.Errorf("cannot find property kind %q", name) +} + // StructName returns the name of the type, which may or may not be a struct, // to generate. func (p *PropertyGenerator) StructName() string { @@ -105,9 +126,9 @@ func (p *PropertyGenerator) PropertyName() string { return p.Name.LowerName } -// deserializeFnName returns the identifier of the function that deserializes +// DeserializeFnName returns the identifier of the function that deserializes // raw JSON into the generated Go type. -func (p *PropertyGenerator) deserializeFnName() string { +func (p *PropertyGenerator) DeserializeFnName() string { if p.asIterator { return fmt.Sprintf("%s%s", deserializeIteratorMethod, p.Name.CamelName) } diff --git a/tools/exp/types/type.go b/tools/exp/props/type.go similarity index 75% rename from tools/exp/types/type.go rename to tools/exp/props/type.go index 82bfb56..6939a82 100644 --- a/tools/exp/types/type.go +++ b/tools/exp/props/type.go @@ -1,4 +1,4 @@ -package types +package props import ( "fmt" @@ -13,7 +13,7 @@ const ( extendedByMethod = "IsExtendedBy" extendsMethod = "Extends" disjointWithMethod = "IsDisjointWith" - nameMethod = "Name" + typeNameMethod = "Name" serializeMethodName = "Serialize" deserializeFnName = "Deserialize" lessFnName = "Less" @@ -26,73 +26,34 @@ func TypeInterface(pkg string) *codegen.Interface { comment := fmt.Sprintf("%s represents an ActivityStreams type.", typeInterfaceName) funcs := []codegen.FunctionSignature{ { - Name: nameMethod, + Name: typeNameMethod, Params: nil, Ret: []jen.Code{jen.String()}, - Comment: fmt.Sprintf("%s returns the ActivityStreams type name.", nameMethod), + Comment: fmt.Sprintf("%s returns the ActivityStreams type name.", typeNameMethod), }, } return codegen.NewInterface(pkg, typeInterfaceName, funcs, comment) } -// KindSerializationFuncs returns free function references that can be used to -// treat a TypeGenerator as another property's Kind. -func KindSerializationFuncs(pkg, typeName string) (ser, deser, less *codegen.Function) { - serName := fmt.Sprintf("%s%s", serializeMethodName, typeName) - ser = codegen.NewCommentedFunction( - pkg, - serName, - []jen.Code{jen.Id("s").Id(typeName)}, - []jen.Code{jen.Interface(), jen.Error()}, - []jen.Code{ - jen.Return( - jen.Id("s").Dot(serializeMethodName).Call(), - ), - }, - jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, typeName)) - deserName := fmt.Sprintf("%s%s", deserializeFnName, typeName) - deser = codegen.NewCommentedFunction( - pkg, - deserName, - []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, - []jen.Code{jen.Op("*").Id(typeName), jen.Error()}, - []jen.Code{ - // TODO - }, - jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, typeName)) - lessName := fmt.Sprintf("%s%s", lessFnName, typeName) - less = codegen.NewCommentedFunction( - pkg, - lessName, - []jen.Code{ - jen.Id("i"), - jen.Id("j").Op("*").Id(typeName), - }, - []jen.Code{jen.Bool()}, - []jen.Code{ - // TODO - }, - jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, typeName)) - return -} - // Property represents a property of an ActivityStreams type. type Property interface { PropertyName() string StructName() string + SetKindFns(name string, ser, deser, less *codegen.Function) error } // TypeGenerator represents an ActivityStream type definition to generate in Go. type TypeGenerator struct { - packageName string - typeName string - comment string - properties map[string]Property - extends []*TypeGenerator - disjoint []*TypeGenerator - extendedBy []*TypeGenerator - cacheOnce sync.Once - cachedStruct *codegen.Struct + packageName string + typeName string + comment string + properties map[string]Property + withoutProperties map[string]Property + extends []*TypeGenerator + disjoint []*TypeGenerator + extendedBy []*TypeGenerator + cacheOnce sync.Once + cachedStruct *codegen.Struct } // NewTypeGenerator creates a new generator for a specific ActivityStreams Core @@ -105,15 +66,16 @@ type TypeGenerator struct { // All TypeGenerators must be created before the Definition method is called, to // ensure that type extension, in the inheritence sense, is properly set up. func NewTypeGenerator(packageName, typeName, comment string, - properties []Property, + properties, withoutProperties []Property, extends, disjoint []*TypeGenerator) (*TypeGenerator, error) { t := &TypeGenerator{ - packageName: packageName, - typeName: typeName, - comment: comment, - properties: make(map[string]Property, len(properties)), - extends: extends, - disjoint: disjoint, + packageName: packageName, + typeName: typeName, + comment: comment, + properties: make(map[string]Property, len(properties)), + withoutProperties: make(map[string]Property, len(withoutProperties)), + extends: extends, + disjoint: disjoint, } for _, property := range properties { if _, has := t.properties[property.PropertyName()]; has { @@ -121,10 +83,24 @@ func NewTypeGenerator(packageName, typeName, comment string, } t.properties[property.PropertyName()] = property } + for _, wop := range withoutProperties { + if _, has := t.withoutProperties[wop.PropertyName()]; has { + return nil, fmt.Errorf("type already has withoutproperty with name %q", wop.PropertyName()) + } + t.withoutProperties[wop.PropertyName()] = wop + } // Complete doubly-linked extends/extendedBy lists. for _, ext := range extends { ext.extendedBy = append(ext.extendedBy, t) } + // TODO: Fix: Only notify properties whose Range is this type, not the + // properties this type has (which is wrong and currently borken) + for _, property := range t.properties { + ser, deser, less := t.kindSerializationFuncs() + if err := property.SetKindFns(t.TypeName(), ser, deser, less); err != nil { + return nil, err + } + } return t, nil } @@ -167,6 +143,12 @@ func (t *TypeGenerator) Properties() map[string]Property { return t.properties } +// WithoutProperties returns the properties that do not apply to this type, +// mapped by their property name. +func (t *TypeGenerator) WithoutProperties() map[string]Property { + return t.withoutProperties +} + // extendsFnName determines the name of the Extends function, which // determines if this ActivityStreams type extends another one. func (t *TypeGenerator) extendsFnName() string { @@ -190,7 +172,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct { t.cacheOnce.Do(func() { members := t.members() m := t.serializationMethod() - ser, deser, less := KindSerializationFuncs(t.packageName, t.TypeName()) + ser, deser, less := t.kindSerializationFuncs() t.cachedStruct = codegen.NewStruct( jen.Commentf(t.Comment()), t.TypeName(), @@ -211,7 +193,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct { return t.cachedStruct } -func (t *TypeGenerator) members() (members []jen.Code) { +func (t *TypeGenerator) allProperties() map[string]Property { p := t.properties // Properties of parents that are extended, minus DoesNotApplyTo var extends []*TypeGenerator @@ -220,8 +202,17 @@ func (t *TypeGenerator) members() (members []jen.Code) { for k, v := range ext.Properties() { p[k] = v } - // TODO: DoesNotApplyTo } + for _, ext := range t.extends { + for k, _ := range ext.WithoutProperties() { + delete(p, k) + } + } + return p +} + +func (t *TypeGenerator) members() (members []jen.Code) { + p := t.allProperties() members = make([]jen.Code, 0, len(p)) for name, property := range p { members = append(members, jen.Id(strings.Title(name)).Id(property.StructName())) @@ -234,14 +225,14 @@ func (t *TypeGenerator) members() (members []jen.Code) { func (t *TypeGenerator) nameDefinition() *codegen.Method { return codegen.NewCommentedValueMethod( t.packageName, - nameMethod, + typeNameMethod, t.TypeName(), /*params=*/ nil, []jen.Code{jen.String()}, []jen.Code{ jen.Return(jen.Lit(t.TypeName())), }, - jen.Commentf("%s returns the name of this type.", nameMethod)) + jen.Commentf("%s returns the name of this type.", typeNameMethod)) } // getAllParentExtends recursively determines all the parent types that this @@ -275,7 +266,7 @@ func (t *TypeGenerator) extendsDefinition() *codegen.Method { jen.Id("ext"), ).Op(":=").Range().Id("extensions")).Block( jen.If( - jen.Id("ext").Op("==").Id("other").Dot(nameMethod).Call(), + jen.Id("ext").Op("==").Id("other").Dot(typeNameMethod).Call(), ).Block( jen.Return(jen.True()), ), @@ -319,7 +310,7 @@ func (t *TypeGenerator) extendedByDefinition() *codegen.Function { jen.Id("ext"), ).Op(":=").Range().Id("extensions")).Block( jen.If( - jen.Id("ext").Op("==").Id("other").Dot(nameMethod).Call(), + jen.Id("ext").Op("==").Id("other").Dot(typeNameMethod).Call(), ).Block( jen.Return(jen.True()), ), @@ -363,7 +354,7 @@ func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { jen.Id("disjoint"), ).Op(":=").Range().Id("disjointWith")).Block( jen.If( - jen.Id("disjoint").Op("==").Id("other").Dot(nameMethod).Call(), + jen.Id("disjoint").Op("==").Id("other").Dot(typeNameMethod).Call(), ).Block( jen.Return(jen.True()), ), @@ -379,6 +370,8 @@ func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { jen.Commentf("%s returns true if the other provided type is disjoint with the %s type.", t.disjointWithFnName(), t.TypeName())) } +// serializationMethod returns the method needed to serialize a TypeGenerator as +// a property. func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { ser = codegen.NewCommentedValueMethod( t.packageName, @@ -388,7 +381,68 @@ func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { []jen.Code{jen.Interface(), jen.Error()}, []jen.Code{ // TODO + jen.Commentf("TODO: Serialization code for %s", t.TypeName()), }, jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", serializeMethodName)) return } + +// kindSerializationFuncs returns free function references that can be used to +// treat a TypeGenerator as another property's Kind. +func (t *TypeGenerator) kindSerializationFuncs() (ser, deser, less *codegen.Function) { + serName := fmt.Sprintf("%s%s", serializeMethodName, t.TypeName()) + ser = codegen.NewCommentedFunction( + t.packageName, + serName, + []jen.Code{jen.Id("s").Id(t.TypeName())}, + []jen.Code{jen.Interface(), jen.Error()}, + []jen.Code{ + jen.Return( + jen.Id("s").Dot(serializeMethodName).Call(), + ), + }, + jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, t.TypeName())) + deserName := fmt.Sprintf("%s%s", deserializeFnName, t.TypeName()) + deserCode := jen.Empty() + for name := range t.allProperties() { + deserCode = deserCode.Add( + jen.If( + jen.List( + jen.Id("p"), + jen.Err(), + // TODO: Qual + ).Op(":=").Qual("", deserializeFnName).Call(jen.Id("m")), + jen.Err().Op("!=").Nil(), + ).Block( + jen.Return(jen.Nil(), jen.Err()), + ).Else().Block( + jen.Id(codegen.This()).Dot(strings.Title(name)).Op(":=").Op("*").Id("p"), + )) + } + deser = codegen.NewCommentedFunction( + t.packageName, + deserName, + []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, + []jen.Code{jen.Op("*").Id(t.TypeName()), jen.Error()}, + []jen.Code{ + jen.Id(codegen.This()).Op(":=").Op("&").Id(t.TypeName()).Values(), + deserCode, + jen.Return(jen.Id(codegen.This()), jen.Nil()), + }, + jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, t.TypeName())) + lessName := fmt.Sprintf("%s%s", lessFnName, t.TypeName()) + less = codegen.NewCommentedFunction( + t.packageName, + lessName, + []jen.Code{ + jen.Id("i"), + jen.Id("j").Op("*").Id(t.TypeName()), + }, + []jen.Code{jen.Bool()}, + []jen.Code{ + // TODO + jen.Commentf("TODO: Less code for %s", t.TypeName()), + }, + jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, t.TypeName())) + return +}