First pass at @context management.

Needs several improvements:
- private alias member for each type/property
- New constructor function sets this to be code-generated default
- Modify deserialize to also accept an @context alias map to override
  default if needed.
- Add utility function in root package to turn @context into an alias
  map, which will be needed by resolvers.
This commit is contained in:
Cory Slep 2019-01-19 15:17:39 +01:00
parent b349761fbc
commit a2a8775265
6 changed files with 241 additions and 44 deletions

View file

@ -600,6 +600,8 @@ func (c Converter) convertType(t rdf.VocabularyType,
}
tg, e = gen.NewTypeGenerator(
v.GetName(),
v.URI,
"", // TODO: Vocabulary alias
pm,
name,
comment,
@ -638,6 +640,8 @@ func (c Converter) convertFunctionalProperty(p rdf.VocabularyProperty,
}
fp = gen.NewFunctionalPropertyGenerator(
v.GetName(),
v.URI,
"", // TODO: Auto-generate aliases
pm,
toIdentifier(p),
comment,
@ -678,6 +682,8 @@ func (c Converter) convertNonFunctionalProperty(p rdf.VocabularyProperty,
}
nfp = gen.NewNonFunctionalPropertyGenerator(
v.GetName(),
v.URI,
"", // TODO: Auto-generate aliases
pm,
toIdentifier(p),
comment,
@ -930,13 +936,11 @@ func (c Converter) packageFiles(v vocabulary) (f []*File, e error) {
priv = pArr[0].GetPrivatePackage()
}
file := jen.NewFilePath(priv.Path())
file.Add(
s,
).Line().Add(
i.Definition(),
).Line().Add(
fn.Definition(),
).Line()
file.Add(s).Line()
for _, elem := range i {
file.Add(elem.Definition()).Line()
}
file.Add(fn.Definition()).Line()
f = append(f, &File{
F: file,
FileName: "gen_pkg.go",
@ -1010,13 +1014,11 @@ func (c Converter) typePackageFiles(tg *gen.TypeGenerator, vocabName string) (f
s, i, fn := tpg.PrivateDefinitions([]*gen.TypeGenerator{tg})
priv := tg.PrivatePackage()
file = jen.NewFilePath(priv.Path())
file.Add(
s,
).Line().Add(
i.Definition(),
).Line().Add(
fn.Definition(),
).Line()
file.Add(s).Line()
for _, elem := range i {
file.Add(elem.Definition()).Line()
}
file.Add(fn.Definition()).Line()
f = append(f, &File{
F: file,
FileName: "gen_pkg.go",

View file

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/cjslep/activity/tools/exp/codegen"
"github.com/dave/jennifer/jen"
"net/url"
"sync"
)
@ -27,6 +28,8 @@ type FunctionalPropertyGenerator struct {
// PropertyGenerators shoulf be in the first pass to construct, before types and
// other generators are constructed.
func NewFunctionalPropertyGenerator(vocabName string,
vocabURI *url.URL,
vocabAlias string,
pm *PackageManager,
name Identifier,
comment string,
@ -35,6 +38,8 @@ func NewFunctionalPropertyGenerator(vocabName string,
return &FunctionalPropertyGenerator{
PropertyGenerator: PropertyGenerator{
vocabName: vocabName,
vocabURI: vocabURI,
vocabAlias: vocabAlias,
packageManager: pm,
hasNaturalLanguageMap: hasNaturalLanguageMap,
name: name,
@ -129,6 +134,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method {
jen.Return(jen.Lit(iriKindIndex)),
))
methods := []*codegen.Method{
p.contextMethod(),
codegen.NewCommentedValueMethod(
p.GetPrivatePackage().Path(),
kindIndexMethod,
@ -997,6 +1003,54 @@ func (p *FunctionalPropertyGenerator) wrapDeserializeCode(valueExisting, typeExi
return iriCode
}
// contextMethod returns the Context method for this functional property.
func (p *FunctionalPropertyGenerator) contextMethod() *codegen.Method {
contextKind := jen.Var().Id("child").Map(jen.String()).String().Line()
hasIfStarted := false
for i, kind := range p.kinds {
// Skip raw values, as only types and any of their properties
// will need to have LD context strings added.
if kind.isValue() {
continue
}
if hasIfStarted {
contextKind = contextKind.Else()
} else {
hasIfStarted = true
}
contextKind.Add(
jen.If(
jen.Id(codegen.This()).Dot(p.isMethodName(i)).Call(),
).Block(
jen.Id("child").Op("=").Id(codegen.This()).Dot(p.getFnName(i)).Call().Dot(contextMethod).Call()))
}
return codegen.NewCommentedValueMethod(
p.GetPrivatePackage().Path(),
contextMethod,
p.StructName(),
/*params=*/ nil,
[]jen.Code{jen.Map(jen.String()).String()},
[]jen.Code{
jen.Id("m").Op(":=").Map(jen.String()).String().Values(
jen.Dict{
jen.Lit(p.vocabURI.String()): jen.Lit(p.vocabAlias),
},
),
contextKind,
jen.Commentf("Since the literal maps in this function are determined at\ncode-generation time, this loop should not overwrite an existing key with a\nnew value."),
jen.For(
jen.List(
jen.Id("k"),
jen.Id("v"),
).Op(":=").Range().Id("child"),
).Block(
jen.Id("m").Index(jen.Id("k")).Op("=").Id("v"),
),
jen.Return(jen.Id("m")),
},
fmt.Sprintf("%s returns the JSONLD URIs required in the context string for this property and the specific values that are set. The value in the map is the alias used to import the property's value or values.", contextMethod))
}
// hasURIKind returns true if this property already has a Kind that is a URI.
func (p *FunctionalPropertyGenerator) hasURIKind() bool {
for _, k := range p.kinds {

View file

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/cjslep/activity/tools/exp/codegen"
"github.com/dave/jennifer/jen"
"net/url"
"sync"
)
@ -24,6 +25,8 @@ type NonFunctionalPropertyGenerator struct {
// PropertyGenerators shoulf be in the first pass to construct, before types and
// other generators are constructed.
func NewNonFunctionalPropertyGenerator(vocabName string,
vocabURI *url.URL,
vocabAlias string,
pm *PackageManager,
name Identifier,
comment string,
@ -32,6 +35,8 @@ func NewNonFunctionalPropertyGenerator(vocabName string,
return &NonFunctionalPropertyGenerator{
PropertyGenerator: PropertyGenerator{
vocabName: vocabName,
vocabURI: vocabURI,
vocabAlias: vocabAlias,
packageManager: pm,
hasNaturalLanguageMap: hasNaturalLanguageMap,
name: name,
@ -83,6 +88,9 @@ func (p *NonFunctionalPropertyGenerator) iteratorInterfaceName() string {
func (p *NonFunctionalPropertyGenerator) elementTypeGenerator() *FunctionalPropertyGenerator {
return &FunctionalPropertyGenerator{
PropertyGenerator: PropertyGenerator{
vocabName: p.vocabName,
vocabURI: p.vocabURI,
vocabAlias: p.vocabAlias,
packageManager: p.PropertyGenerator.packageManager,
name: p.iteratorTypeName(),
kinds: p.kinds,
@ -482,6 +490,39 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method {
jen.Return(jen.Nil()),
},
fmt.Sprintf("%s returns beyond-the-last iterator, which is nil. Can be used with the iterator's %s method and this property's %s method to iterate from front to back through all values.", endMethod, nextMethod, beginMethod)))
// Context Method
methods = append(methods, codegen.NewCommentedValueMethod(
p.GetPrivatePackage().Path(),
contextMethod,
p.StructName(),
/*params=*/ nil,
[]jen.Code{jen.Map(jen.String()).String()},
[]jen.Code{
jen.Id("m").Op(":=").Map(jen.String()).String().Values(
jen.Dict{
jen.Lit(p.vocabURI.String()): jen.Lit(p.vocabAlias),
},
),
jen.For(
jen.List(
jen.Id("_"),
jen.Id("elem"),
).Op(":=").Range().Id(codegen.This()),
).Block(
jen.Id("child").Op(":=").Id("elem").Dot(contextMethod).Call(),
jen.Commentf("Since the literal maps in this function are determined at\ncode-generation time, this loop should not overwrite an existing key with a\nnew value."),
jen.For(
jen.List(
jen.Id("k"),
jen.Id("v"),
).Op(":=").Range().Id("child"),
).Block(
jen.Id("m").Index(jen.Id("k")).Op("=").Id("v"),
),
),
jen.Return(jen.Id("m")),
},
fmt.Sprintf("%s returns the JSONLD URIs required in the context string for this property and the specific values that are set. The value in the map is the alias used to import the property's value or values.", contextMethod)))
methods = append(methods, p.commonMethods()...)
return methods
}

View file

@ -162,9 +162,12 @@ func (t *TypePackageGenerator) PublicDefinitions(tgs []*TypeGenerator) (typeI *c
// per package.
//
// Precondition: The passed-in generators are the complete set of type
// generators within a package.
func (t *TypePackageGenerator) PrivateDefinitions(tgs []*TypeGenerator) (mgrVar *jen.Statement, mgrI *codegen.Interface, setMgrFn *codegen.Function) {
return privateManagerHookDefinitions(tgs, nil)
// generators within a package. len(tgs) > 0
func (t *TypePackageGenerator) PrivateDefinitions(tgs []*TypeGenerator) (mgrVar *jen.Statement, mgrI []*codegen.Interface, setMgrFn *codegen.Function) {
pkg := tgs[0].PrivatePackage()
s, i, f := privateManagerHookDefinitions(pkg, tgs, nil)
interfaces := []*codegen.Interface{i, ContextInterface(pkg)}
return s, interfaces, f
}
// PropertyPackageGenerator manages generating one-time files needed for
@ -180,9 +183,9 @@ func NewPropertyPackageGenerator() *PropertyPackageGenerator {
// per package.
//
// Precondition: The passed-in generators are the complete set of type
// generators within a package.
// generators within a package. len(pgs) > 0
func (p *PropertyPackageGenerator) PrivateDefinitions(pgs []*PropertyGenerator) (*jen.Statement, *codegen.Interface, *codegen.Function) {
return privateManagerHookDefinitions(nil, pgs)
return privateManagerHookDefinitions(pgs[0].GetPrivatePackage(), nil, pgs)
}
// PackageGenerator maanges generating one-time files needed for both type and
@ -216,14 +219,22 @@ func (t *PackageGenerator) PublicDefinitions(tgs []*TypeGenerator) *codegen.Inte
// per package.
//
// Precondition: The passed-in generators are the complete set of type
// generators within a package.
func (t *PackageGenerator) PrivateDefinitions(tgs []*TypeGenerator, pgs []*PropertyGenerator) (*jen.Statement, *codegen.Interface, *codegen.Function) {
return privateManagerHookDefinitions(tgs, pgs)
// generators within a package. One of tgs or pgs has at least one value.
func (t *PackageGenerator) PrivateDefinitions(tgs []*TypeGenerator, pgs []*PropertyGenerator) (*jen.Statement, []*codegen.Interface, *codegen.Function) {
var pkg Package
if len(tgs) > 0 {
pkg = tgs[0].PrivatePackage()
} else {
pkg = pgs[0].GetPrivatePackage()
}
s, i, f := privateManagerHookDefinitions(pkg, tgs, pgs)
interfaces := []*codegen.Interface{i, ContextInterface(pkg)}
return s, interfaces, f
}
// privateManagerHookDefinitions creates common code needed by types and
// properties to properly hook in the manager at initialization time.
func privateManagerHookDefinitions(tgs []*TypeGenerator, pgs []*PropertyGenerator) (mgrVar *jen.Statement, mgrI *codegen.Interface, setMgrFn *codegen.Function) {
func privateManagerHookDefinitions(pkg Package, tgs []*TypeGenerator, pgs []*PropertyGenerator) (mgrVar *jen.Statement, mgrI *codegen.Interface, setMgrFn *codegen.Function) {
fnsMap := make(map[string]codegen.FunctionSignature)
for _, tg := range tgs {
for _, m := range tg.getAllManagerMethods() {
@ -241,12 +252,7 @@ func privateManagerHookDefinitions(tgs []*TypeGenerator, pgs []*PropertyGenerato
for _, v := range fnsMap {
fns = append(fns, v)
}
var path string
if tgs != nil {
path = tgs[0].PrivatePackage().Path()
} else {
path = pgs[0].GetPrivatePackage().Path()
}
path := pkg.Path()
return jen.Var().Id(managerInitName()).Id(managerInterfaceName),
codegen.NewInterface(path,
managerInterfaceName,

View file

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/cjslep/activity/tools/exp/codegen"
"github.com/dave/jennifer/jen"
"net/url"
"strings"
)
@ -40,6 +41,8 @@ const (
beginMethod = "Begin"
endMethod = "End"
emptyMethod = "Empty"
// Context string management
contextMethod = "JSONLDContext"
// Member names for generated code
unknownMemberName = "unknown"
langMapMember = "langMap"
@ -143,6 +146,8 @@ func (k Kind) isValue() bool {
// iterators, which are needed for NonFunctional properties.
type PropertyGenerator struct {
vocabName string
vocabURI *url.URL
vocabAlias string
managerMethods []*codegen.Method
packageManager *PackageManager
name Identifier
@ -312,6 +317,7 @@ func (p *PropertyGenerator) clearMethodName() string {
// commonMethods returns methods common to every property.
func (p *PropertyGenerator) commonMethods() []*codegen.Method {
// Name method
m := []*codegen.Method{
codegen.NewCommentedValueMethod(
p.GetPrivatePackage().Path(),
@ -328,6 +334,7 @@ func (p *PropertyGenerator) commonMethods() []*codegen.Method {
),
}
if p.asIterator {
// Next & Prev methods
m = append(m, codegen.NewCommentedValueMethod(
p.GetPrivatePackage().Path(),
nextMethod,

View file

@ -4,25 +4,27 @@ import (
"fmt"
"github.com/cjslep/activity/tools/exp/codegen"
"github.com/dave/jennifer/jen"
"net/url"
"sort"
"strings"
"sync"
)
const (
typeInterfaceName = "Type"
extendedByMethod = "IsExtendedBy"
extendingMethod = "IsExtending"
extendsMethod = "Extends"
disjointWithMethod = "IsDisjointWith"
typeNameMethod = "GetName"
serializeMethodName = "Serialize"
deserializeFnName = "Deserialize"
compareLessMethod = "LessThan"
getUnknownMethod = "GetUnknownProperties"
unknownMember = "unknown"
getMethodFormat = "Get%s"
constructorName = "New"
typeInterfaceName = "Type"
jsonLDContextInterfaceName = "jsonldContexter"
extendedByMethod = "IsExtendedBy"
extendingMethod = "IsExtending"
extendsMethod = "Extends"
disjointWithMethod = "IsDisjointWith"
typeNameMethod = "GetName"
serializeMethodName = "Serialize"
deserializeFnName = "Deserialize"
compareLessMethod = "LessThan"
getUnknownMethod = "GetUnknownProperties"
unknownMember = "unknown"
getMethodFormat = "Get%s"
constructorName = "New"
)
// TypeInterface returns the Type Interface that is needed for ActivityStream
@ -41,6 +43,29 @@ func TypeInterface(pkg Package) *codegen.Interface {
return codegen.NewInterface(pkg.Path(), typeInterfaceName, funcs, comment)
}
// ContextInterface returns a jsonldContexter interface that is needed for
// ActivityStream types to recursively determine what context strings need to
// exist in a JSON-LD @context value for linked-data peers to parse.
//
// It is a private interface to make the implementation easier, not needed by
// anything outside the package this implementation is in.
func ContextInterface(pkg Package) *codegen.Interface {
comment := fmt.Sprintf("%s is a private interface to determine the JSON-LD contexts and aliases needed for functional and non-functional properties. It is a helper interface for this implementation.", jsonLDContextInterfaceName)
funcs := []codegen.FunctionSignature{
{
Name: contextMethod,
Params: nil,
Ret: []jen.Code{jen.Map(jen.String()).String()},
Comment: fmt.Sprintf("%s returns the JSONLD URIs required in the context string for this property and the specific values that are set. The value in the map is the alias used to import the property's value or values.", contextMethod),
},
}
return codegen.NewInterface(
pkg.Path(),
jsonLDContextInterfaceName,
funcs,
comment)
}
// Property represents a property of an ActivityStreams type.
type Property interface {
GetPublicPackage() Package
@ -53,6 +78,8 @@ type Property interface {
// TypeGenerator represents an ActivityStream type definition to generate in Go.
type TypeGenerator struct {
vocabName string
vocabURI *url.URL
vocabAlias string
pm *PackageManager
typeName string
comment string
@ -87,11 +114,17 @@ type TypeGenerator struct {
//
// A ManagerGenerator must be created with this type before Definition is
// called, to ensure that the serialization functions are properly set up.
func NewTypeGenerator(vocabName string, pm *PackageManager, typeName, comment string,
func NewTypeGenerator(vocabName string,
vocabURI *url.URL,
vocabAlias string,
pm *PackageManager,
typeName, comment string,
properties, withoutProperties, rangeProperties []Property,
extends, disjoint []*TypeGenerator) (*TypeGenerator, error) {
t := &TypeGenerator{
vocabName: vocabName,
vocabURI: vocabURI,
vocabAlias: vocabAlias,
pm: pm,
typeName: typeName,
comment: comment,
@ -261,10 +294,11 @@ func (t *TypeGenerator) Definition() *codegen.Struct {
getters := t.allGetters()
setters := t.allSetters()
constructor := t.constructorFn()
ctxMethods := t.contextMethods()
t.cachedStruct = codegen.NewStruct(
t.Comments(),
t.TypeName(),
append(append(
append(append(append(
[]*codegen.Method{
t.nameDefinition(),
extendsMethod,
@ -272,6 +306,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct {
less,
get,
},
ctxMethods...),
getters...),
setters...,
),
@ -778,3 +813,55 @@ func (t *TypeGenerator) constructorFn() *codegen.Function {
},
fmt.Sprintf("%s%s creates a new %s type", constructorName, t.TypeName(), t.TypeName()))
}
// contextMethod returns a map of the context's vocabulary
func (t *TypeGenerator) contextMethods() []*codegen.Method {
helperName := fmt.Sprintf("helper%s", contextMethod)
helper := codegen.NewCommentedValueMethod(
t.PrivatePackage().Path(),
helperName,
t.TypeName(),
[]jen.Code{jen.Id("i").Id(jsonLDContextInterfaceName), jen.Id("toMerge").Map(jen.String()).String()},
[]jen.Code{jen.Map(jen.String()).String()},
[]jen.Code{
jen.If(
jen.Id("i").Op("==").Nil(),
).Block(
jen.Return(jen.Id("toMerge")),
),
jen.For(
jen.List(
jen.Id("k"),
jen.Id("v"),
).Op(":=").Range().Id("i").Dot(contextMethod).Call(),
).Block(
jen.Commentf("Since the literal maps in this function are determined at\ncode-generation time, this loop should not overwrite an existing key with a\nnew value."),
jen.Id("toMerge").Index(jen.Id("k")).Op("=").Id("v"),
),
jen.Return(jen.Id("toMerge")),
},
fmt.Sprintf("%s obtains the context uris and their aliases from a property, if it is not nil.", helperName))
contextKind := jen.Id("m").Op(":=").Map(jen.String()).String().Values(
jen.Dict{
jen.Lit(t.vocabURI.String()): jen.Lit(t.vocabAlias),
},
).Line()
for _, property := range t.allProperties() {
contextKind.Add(
jen.Id("m").Op("=").Id(codegen.This()).Dot(helperName).Call(
jen.Id(codegen.This()).Dot(t.memberName(property)),
jen.Id("m")).Line())
}
ctxMethod := codegen.NewCommentedValueMethod(
t.PrivatePackage().Path(),
contextMethod,
t.TypeName(),
/*params=*/ nil,
[]jen.Code{jen.Map(jen.String()).String()},
[]jen.Code{
contextKind,
jen.Return(jen.Id("m")),
},
fmt.Sprintf("%s returns the JSONLD URIs required in the context string for this type and the specific properties that are set. The value in the map is the alias used to import the type and its properties.", contextMethod))
return []*codegen.Method{helper, ctxMethod}
}