920144aba6
However, the converter still cannot handle converting it to generators and then files.
1003 行
30 KiB
Go
1003 行
30 KiB
Go
package convert
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/cjslep/activity/tools/exp/codegen"
|
|
"github.com/cjslep/activity/tools/exp/gen"
|
|
"github.com/cjslep/activity/tools/exp/rdf"
|
|
"github.com/dave/jennifer/jen"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// File is a code-generated file.
|
|
type File struct {
|
|
// F is the code-generated contents of this file.
|
|
F *jen.File
|
|
// FileName is the name of this file to write.
|
|
FileName string
|
|
// Directory specifies the location to write this file.
|
|
Directory string
|
|
}
|
|
|
|
// vocabulary is a set of code generators for the vocabulary.
|
|
type vocabulary struct {
|
|
Name string
|
|
URI *url.URL
|
|
Values map[string]*gen.Kind
|
|
FProps map[string]*gen.FunctionalPropertyGenerator
|
|
NFProps map[string]*gen.NonFunctionalPropertyGenerator
|
|
Types map[string]*gen.TypeGenerator
|
|
Manager *gen.ManagerGenerator
|
|
References map[string]*vocabulary
|
|
}
|
|
|
|
// newVocabulary creates a vocabulary with maps already made.
|
|
func newVocabulary() vocabulary {
|
|
return vocabulary{
|
|
Values: make(map[string]*gen.Kind, 0),
|
|
FProps: make(map[string]*gen.FunctionalPropertyGenerator, 0),
|
|
NFProps: make(map[string]*gen.NonFunctionalPropertyGenerator, 0),
|
|
Types: make(map[string]*gen.TypeGenerator, 0),
|
|
References: make(map[string]*vocabulary, 0),
|
|
}
|
|
}
|
|
|
|
// typeArray converts Types to an array.
|
|
func (v vocabulary) typeArray() []*gen.TypeGenerator {
|
|
tg := make([]*gen.TypeGenerator, 0, len(v.Types))
|
|
for _, t := range v.Types {
|
|
tg = append(tg, t)
|
|
}
|
|
return tg
|
|
}
|
|
|
|
// propArray converts FProps and NFProps to a generic array.
|
|
func (v vocabulary) propArray() []*gen.PropertyGenerator {
|
|
fp := make([]*gen.PropertyGenerator, 0, len(v.FProps)+len(v.NFProps))
|
|
for _, f := range v.FProps {
|
|
fp = append(fp, &f.PropertyGenerator)
|
|
}
|
|
for _, f := range v.NFProps {
|
|
fp = append(fp, &f.PropertyGenerator)
|
|
}
|
|
return fp
|
|
}
|
|
|
|
// funcPropArray converts only FProps to an array.
|
|
func (v vocabulary) funcPropArray() []*gen.FunctionalPropertyGenerator {
|
|
fp := make([]*gen.FunctionalPropertyGenerator, 0, len(v.FProps))
|
|
for _, f := range v.FProps {
|
|
fp = append(fp, f)
|
|
}
|
|
return fp
|
|
}
|
|
|
|
// nonFuncPropArray converts NFProps to an array.
|
|
func (v vocabulary) nonFuncPropArray() []*gen.NonFunctionalPropertyGenerator {
|
|
nfp := make([]*gen.NonFunctionalPropertyGenerator, 0, len(v.NFProps))
|
|
for _, nf := range v.NFProps {
|
|
nfp = append(nfp, nf)
|
|
}
|
|
return nfp
|
|
}
|
|
|
|
// PackagePolicy governs what file directory structure to generate files in.
|
|
// Only affects types and properties in a vocabulary. Does not affect values.
|
|
type PackagePolicy int
|
|
|
|
const (
|
|
// FlatUnderRoot puts all types and properties together for each
|
|
// vocabulary.
|
|
FlatUnderRoot PackagePolicy = iota
|
|
// IndividualUnderRoot puts each type and each property into their own
|
|
// package within each vocabulary.
|
|
IndividualUnderRoot
|
|
)
|
|
|
|
// Converter is responsible for taking the intermediate RDF understanding of one
|
|
// or more ActivityStreams-based specifications and converting it into a series
|
|
// of code-generated files.
|
|
//
|
|
// It supports generating code in two different styles, which greatly affects
|
|
// the package imports that application developers will use in their
|
|
// applications. While both are available, this results in code that may not be
|
|
// able to be built on some systems due to the compilation memory requirements.
|
|
// Therefore, final tooling is ecouraged to pick one and only one to use as
|
|
// there is no need for another dimension of code fragmentation. These styles
|
|
// are dicatated by the Converter's PackagePolicy.
|
|
//
|
|
// The generated code is separated into three locations: a "values" series of
|
|
// subdirectories, the subdirectories for the ActivityStream specification, and
|
|
// the "root" generated package.
|
|
//
|
|
// The "root" package is indended to be the sole package that all application
|
|
// developers use for non-interface types. It contains free-functions that aid
|
|
// in navigating the ActivityStreams hierarchy (such as extends and disjoints).
|
|
// It also contains a Resolver for deserialization or type dispatching.
|
|
//
|
|
// The specifications' generated code contains both interfaces and
|
|
// implementations. Developers' applications should only rely on the interfaces,
|
|
// which are used internally anyway.
|
|
type Converter struct {
|
|
Registry *rdf.RDFRegistry
|
|
GenRoot *gen.PackageManager
|
|
PackagePolicy PackagePolicy
|
|
}
|
|
|
|
// Convert turns a ParsedVocabulary into a set of code-generated files.
|
|
func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*File, e error) {
|
|
var v vocabulary
|
|
v, e = c.convertVocabulary(p)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Step 1: Convert the rdf.ParsedVocabulary into the internal set of
|
|
// code generators.
|
|
for k, refVocab := range p.References {
|
|
// Create a copy, but with the Reference moved to Vocab.
|
|
refP := p
|
|
refP.Vocab = *refVocab
|
|
delete(refP.References, k)
|
|
|
|
var refV vocabulary
|
|
refV, e = c.convertVocabulary(refP)
|
|
if e != nil {
|
|
return
|
|
}
|
|
v.References[k] = &refV
|
|
}
|
|
// Step 2: Use the code generators to build the resulting code-generated
|
|
// files.
|
|
f, e = c.convertToFiles(v)
|
|
return
|
|
}
|
|
|
|
// convertToFiles takes the generators for a vocabulary and maps them into a
|
|
// file structure.
|
|
func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) {
|
|
// Values -- include all referenced values too.
|
|
for _, v := range v.Values {
|
|
pkg := c.valuePackage(v)
|
|
f = append(f, convertValue(pkg, v))
|
|
}
|
|
for _, ref := range v.References {
|
|
for _, v := range ref.Values {
|
|
pkg := c.valuePackage(v)
|
|
f = append(f, convertValue(pkg, v))
|
|
}
|
|
}
|
|
// Functional Properties
|
|
for _, i := range v.FProps {
|
|
var pm *gen.PackageManager
|
|
pm, e = c.propertyPackageManager(i, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Implementation
|
|
priv := pm.PrivatePackage()
|
|
file := jen.NewFilePath(priv.Path())
|
|
file.Add(i.Definition().Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: fmt.Sprintf("gen_property_%s.go", i.PropertyName()),
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
// Interface
|
|
pub := pm.PublicPackage()
|
|
file = jen.NewFilePath(pub.Path())
|
|
file.Add(i.InterfaceDefinition(pm.PublicPackage()).Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: fmt.Sprintf("gen_property_%s_interface.go", i.PropertyName()),
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
}
|
|
// Non-Functional Properties
|
|
for _, i := range v.NFProps {
|
|
var pm *gen.PackageManager
|
|
pm, e = c.propertyPackageManager(i, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Implementation
|
|
priv := pm.PrivatePackage()
|
|
file := jen.NewFilePath(priv.Path())
|
|
s, t := i.Definitions()
|
|
file.Add(s.Definition()).Line().Add(t.Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: fmt.Sprintf("gen_property_%s.go", i.PropertyName()),
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
// Interface
|
|
pub := pm.PublicPackage()
|
|
file = jen.NewFilePath(pub.Path())
|
|
for _, intf := range i.InterfaceDefinitions(pm.PublicPackage()) {
|
|
file.Add(intf.Definition()).Line()
|
|
}
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: fmt.Sprintf("gen_property_%s_interface.go", i.PropertyName()),
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
}
|
|
// Types
|
|
for _, i := range v.Types {
|
|
var pm *gen.PackageManager
|
|
pm, e = c.typePackageManager(i, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Implementation
|
|
priv := pm.PrivatePackage()
|
|
file := jen.NewFilePath(priv.Path())
|
|
file.Add(i.Definition().Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: fmt.Sprintf("gen_type_%s.go", strings.ToLower(i.TypeName())),
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
// Interface
|
|
pub := pm.PublicPackage()
|
|
file = jen.NewFilePath(pub.Path())
|
|
file.Add(i.InterfaceDefinition(pm.PublicPackage()).Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: fmt.Sprintf("gen_type_%s_interface.go", strings.ToLower(i.TypeName())),
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
}
|
|
pkgFiles, err := c.packageFiles(v)
|
|
if err != nil {
|
|
e = err
|
|
return
|
|
}
|
|
f = append(f, pkgFiles...)
|
|
// Manager
|
|
pub := c.GenRoot.PublicPackage()
|
|
file := jen.NewFilePath(pub.Path())
|
|
file.Add(v.Manager.Definition().Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: "gen_manager.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
var files []*File
|
|
files, e = c.rootFiles(pub, v.Name, v)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, files...)
|
|
// Root Package Documentation
|
|
rootDocFile := jen.NewFilePath(pub.Path())
|
|
rootDocFile.PackageComment(gen.GenRootPackageComment(pub.Name()))
|
|
f = append(f, &File{
|
|
F: rootDocFile,
|
|
FileName: "gen_doc.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
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()
|
|
v.Name = p.Vocab.Name
|
|
v.URI = p.Vocab.URI
|
|
for k, val := range p.Vocab.Values {
|
|
v.Values[k] = c.convertValue(val)
|
|
}
|
|
for k, prop := range p.Vocab.Properties {
|
|
if prop.Functional {
|
|
v.FProps[k], e = c.convertFunctionalProperty(prop, v.Values, p.Vocab, p.References)
|
|
} else {
|
|
v.NFProps[k], e = c.convertNonFunctionalProperty(prop, v.Values, p.Vocab, p.References)
|
|
}
|
|
if e != nil {
|
|
return
|
|
}
|
|
}
|
|
// Instead of building a dependency tree, naively keep iterating through
|
|
// 'allTypes' until it is empty (good) or we get stuck (return error).
|
|
allTypes := make([]rdf.VocabularyType, 0, len(p.Vocab.Types))
|
|
for _, t := range p.Vocab.Types {
|
|
allTypes = append(allTypes, t)
|
|
}
|
|
for {
|
|
if len(allTypes) == 0 {
|
|
break
|
|
}
|
|
stuck := true
|
|
for i, t := range allTypes {
|
|
if allExtendsAreIn(t, v.Types) {
|
|
var tg *gen.TypeGenerator
|
|
tg, e = c.convertType(t, p.Vocab, v.FProps, v.NFProps, v.Types)
|
|
if e != nil {
|
|
return
|
|
}
|
|
v.Types[t.Name] = tg
|
|
stuck = false
|
|
// Delete the one we just did.
|
|
allTypes[i] = allTypes[len(allTypes)-1]
|
|
allTypes = allTypes[:len(allTypes)-1]
|
|
break
|
|
}
|
|
}
|
|
if stuck {
|
|
e = fmt.Errorf("converting gen got stuck in dependency cycle")
|
|
return
|
|
}
|
|
}
|
|
v.Manager, e = gen.NewManagerGenerator(
|
|
c.GenRoot.PublicPackage(),
|
|
v.typeArray(),
|
|
v.funcPropArray(),
|
|
v.nonFuncPropArray())
|
|
return
|
|
}
|
|
|
|
// convertType turns the rdf.VocabularyType into a TypeGenerator.
|
|
//
|
|
// Precondition: The types it extends from and the properties it references are
|
|
// already converted into their applicable generators.
|
|
func (c Converter) convertType(t rdf.VocabularyType,
|
|
v rdf.Vocabulary,
|
|
existingFProps map[string]*gen.FunctionalPropertyGenerator,
|
|
existingNFProps map[string]*gen.NonFunctionalPropertyGenerator,
|
|
existingTypes map[string]*gen.TypeGenerator) (tg *gen.TypeGenerator, e error) {
|
|
// Determine the gen package name
|
|
var pm *gen.PackageManager
|
|
pm, e = c.typePackageManager(t, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Determine the properties for this type
|
|
var p []gen.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 gen.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
|
|
}
|
|
}
|
|
p = append(p, property)
|
|
}
|
|
}
|
|
// Determine WithoutProperties for this type
|
|
var wop []gen.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 gen.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 []*gen.TypeGenerator
|
|
for _, ex := range t.Extends {
|
|
if len(ex.Vocab) != 0 {
|
|
// 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])
|
|
}
|
|
}
|
|
// Apply disjoint if both sides are available because the TypeGenerator
|
|
// does not know the entire vocabulary, so cannot do this lookup and
|
|
// create this connection for us.
|
|
var disjoint []*gen.TypeGenerator
|
|
for _, disj := range t.DisjointWith {
|
|
if len(disj.Vocab) != 0 {
|
|
// 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 {
|
|
disjoint = append(disjoint, disjointType)
|
|
}
|
|
}
|
|
// Pass in properties whose range is this type so it can build
|
|
// references properly.
|
|
//
|
|
// Note that the Kinds container on properties contains both types and
|
|
// values.
|
|
//
|
|
// TODO: Enable this for referenced properties.
|
|
name := c.convertTypeToName(t)
|
|
var rangeProps []gen.Property
|
|
for _, prop := range existingFProps {
|
|
for _, kind := range prop.GetKinds() {
|
|
// TODO: Rename "LowerName" since the type's name is
|
|
// actually title case.
|
|
if kind.Name.LowerName == name {
|
|
rangeProps = append(rangeProps, prop)
|
|
}
|
|
}
|
|
}
|
|
for _, prop := range existingNFProps {
|
|
for _, kind := range prop.GetKinds() {
|
|
if kind.Name.LowerName == name {
|
|
rangeProps = append(rangeProps, prop)
|
|
}
|
|
}
|
|
}
|
|
var examples []string
|
|
for _, ex := range t.Examples {
|
|
examples = append(examples, asComment(ex))
|
|
}
|
|
comment := t.Notes
|
|
if len(examples) > 0 {
|
|
comment = fmt.Sprintf("%s\n\n%s", comment, strings.Join(examples, "\n\n"))
|
|
}
|
|
tg, e = gen.NewTypeGenerator(
|
|
v.GetName(),
|
|
pm,
|
|
name,
|
|
comment,
|
|
p,
|
|
wop,
|
|
rangeProps,
|
|
ext,
|
|
disjoint)
|
|
return
|
|
}
|
|
|
|
// convertFunctionalProperty converts an rdf.VocabularyProperty that is
|
|
// functional (can only have one value) into a FunctionalPropertyGenerator.
|
|
func (c Converter) convertFunctionalProperty(p rdf.VocabularyProperty,
|
|
kinds map[string]*gen.Kind,
|
|
v rdf.Vocabulary,
|
|
refs map[string]*rdf.Vocabulary) (fp *gen.FunctionalPropertyGenerator, e error) {
|
|
var k []gen.Kind
|
|
k, e = c.propertyKinds(p, kinds, v, refs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
var pm *gen.PackageManager
|
|
pm, e = c.propertyPackageManager(p, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
var examples []string
|
|
for _, ex := range p.Examples {
|
|
examples = append(examples, asComment(ex))
|
|
}
|
|
comment := p.Notes
|
|
if len(examples) > 0 {
|
|
comment = fmt.Sprintf("%s\n\n%s", comment, strings.Join(examples, "\n\n"))
|
|
}
|
|
fp = gen.NewFunctionalPropertyGenerator(
|
|
v.GetName(),
|
|
pm,
|
|
toIdentifier(p),
|
|
comment,
|
|
k,
|
|
p.NaturalLanguageMap)
|
|
return
|
|
}
|
|
|
|
// convertNonFunctionalProperty converts an rdf.VocabularyProperty that is
|
|
// non-functional (can have multiple values) into a
|
|
// NonFunctionalPropertyGenerator.
|
|
func (c Converter) convertNonFunctionalProperty(p rdf.VocabularyProperty,
|
|
kinds map[string]*gen.Kind,
|
|
v rdf.Vocabulary,
|
|
refs map[string]*rdf.Vocabulary) (nfp *gen.NonFunctionalPropertyGenerator, e error) {
|
|
var k []gen.Kind
|
|
k, e = c.propertyKinds(p, kinds, v, refs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
var pm *gen.PackageManager
|
|
pm, e = c.propertyPackageManager(p, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
var examples []string
|
|
for _, ex := range p.Examples {
|
|
examples = append(examples, asComment(ex))
|
|
}
|
|
comment := p.Notes
|
|
if len(examples) > 0 {
|
|
comment = fmt.Sprintf("%s\n\n%s", comment, strings.Join(examples, "\n\n"))
|
|
}
|
|
nfp = gen.NewNonFunctionalPropertyGenerator(
|
|
v.GetName(),
|
|
pm,
|
|
toIdentifier(p),
|
|
comment,
|
|
k,
|
|
p.NaturalLanguageMap)
|
|
return
|
|
}
|
|
|
|
// convertValue turns a rdf.VocabularyValue into a Kind.
|
|
//
|
|
// TODO: Turn this into a Kind constructor in gen?
|
|
func (c Converter) convertValue(v rdf.VocabularyValue) (k *gen.Kind) {
|
|
s := v.SerializeFn.CloneToPackage(c.vocabValuePackage(v).Path())
|
|
d := v.DeserializeFn.CloneToPackage(c.vocabValuePackage(v).Path())
|
|
l := v.LessFn.CloneToPackage(c.vocabValuePackage(v).Path())
|
|
k = &gen.Kind{
|
|
// Name must use toIdentifier for vocabValuePackage and
|
|
// valuePackage to be the same.
|
|
Name: toIdentifier(v),
|
|
ConcreteKind: v.DefinitionType,
|
|
Nilable: v.IsNilable,
|
|
IsURI: v.IsURI,
|
|
SerializeFn: s.QualifiedName(),
|
|
DeserializeFn: d.QualifiedName(),
|
|
LessFn: l.QualifiedName(),
|
|
SerializeDef: s,
|
|
DeserializeDef: d,
|
|
LessDef: l,
|
|
}
|
|
return
|
|
}
|
|
|
|
// convertTypeToKind turns a rdf.VocabularyType into a Kind.
|
|
//
|
|
// TODO: Turn this into a Kind constructor in gen?
|
|
func (c Converter) convertTypeToKind(v rdf.VocabularyType) (k *gen.Kind, e error) {
|
|
k = &gen.Kind{
|
|
// Name must use toIdentifier for vocabValuePackage and
|
|
// valuePackage to be the same.
|
|
Name: toIdentifier(v),
|
|
Nilable: true,
|
|
IsURI: false,
|
|
// Instead of populating:
|
|
// - ConcreteKind
|
|
// - 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
|
|
}
|
|
|
|
// convertTypeToName makes a Titled version of the VocabularyType's name.
|
|
func (c Converter) convertTypeToName(v rdf.VocabularyType) string {
|
|
return strings.Title(v.Name)
|
|
}
|
|
|
|
// propertyKinds determines what Kind names are referenced by the
|
|
// rdf.VocabularyProperty, which may rely on the parsing registry for these
|
|
// particular files.
|
|
func (c Converter) propertyKinds(v rdf.VocabularyProperty,
|
|
kinds map[string]*gen.Kind,
|
|
vocab rdf.Vocabulary,
|
|
refs map[string]*rdf.Vocabulary) (k []gen.Kind, e error) {
|
|
for _, r := range v.Range {
|
|
if len(r.Vocab) == 0 {
|
|
if kind, ok := kinds[r.Name]; !ok {
|
|
// It is a Type of the vocabulary
|
|
if t, ok := vocab.Types[r.Name]; !ok {
|
|
e = fmt.Errorf("cannot find own kind with name %q", r.Name)
|
|
return
|
|
} else {
|
|
var kt *gen.Kind
|
|
kt, e = c.convertTypeToKind(t)
|
|
if e != nil {
|
|
return
|
|
}
|
|
k = append(k, *kt)
|
|
}
|
|
} else {
|
|
// It is a Value of the vocabulary
|
|
k = append(k, *kind)
|
|
}
|
|
} else {
|
|
var url string
|
|
url, e = c.Registry.ResolveAlias(r.Vocab)
|
|
if e != nil {
|
|
return
|
|
}
|
|
refVocab, ok := refs[url]
|
|
if !ok {
|
|
e = fmt.Errorf("references do not contain %s", url)
|
|
return
|
|
}
|
|
if val, ok := refVocab.Values[r.Name]; !ok {
|
|
// It is a Type of the vocabulary instead
|
|
if t, ok := refVocab.Types[r.Name]; !ok {
|
|
e = fmt.Errorf("cannot find kind with name %q in %s", r.Name, url)
|
|
return
|
|
} else {
|
|
var kt *gen.Kind
|
|
kt, e = c.convertTypeToKind(t)
|
|
if e != nil {
|
|
return
|
|
}
|
|
k = append(k, *kt)
|
|
}
|
|
} else {
|
|
// It is a Value of the vocabulary
|
|
k = append(k, *c.convertValue(val))
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// getValueRoot returns the subdirectory that contains value types.
|
|
func (c Converter) getValueRoot() *gen.PackageManager {
|
|
return c.GenRoot.Sub("values")
|
|
}
|
|
|
|
// valuePackage returns the subpackage for a value Kind.
|
|
//
|
|
// It must match the result of vocabValuePackage. Therefore, convertTypeToKind
|
|
// and convertValue must also use toIdentifier.
|
|
func (c Converter) valuePackage(v *gen.Kind) gen.Package {
|
|
return c.getValueRoot().Sub(v.Name.LowerName).PublicPackage()
|
|
}
|
|
|
|
// vocabValuePackage returns the subpackage for a value Kind.
|
|
//
|
|
// It must match the result of valuePackage. Therefore, convertTypeToKind and
|
|
// convertValue must also use toIdentifier.
|
|
func (c Converter) vocabValuePackage(v rdf.VocabularyValue) gen.Package {
|
|
return c.getValueRoot().Sub(toIdentifier(v).LowerName).PublicPackage()
|
|
}
|
|
|
|
// typePackageManager returns a package manager for an individual type. It may
|
|
// be the same as other types depending on the code generation policy.
|
|
func (c Converter) typePackageManager(v typeNamer, vocabName string) (pkg *gen.PackageManager, e error) {
|
|
return c.packageManager("type_"+v.TypeName(), vocabName)
|
|
}
|
|
|
|
// propertyPackageManager returns a package manager for an individual property.
|
|
// It may be the same as other types depending on the code generation policy.
|
|
func (c Converter) propertyPackageManager(v propertyNamer, vocabName string) (pkg *gen.PackageManager, e error) {
|
|
return c.packageManager("property_"+v.PropertyName(), vocabName)
|
|
}
|
|
|
|
// packageManager applies the code generation package policy and returns a
|
|
// PackageManager applicable for that policy.
|
|
//
|
|
// The FlatUnderRoot policy puts all properties and types together in a single
|
|
// public and single private package.
|
|
//
|
|
// The IndividualUnderRoot policy puts each property and each type in their own
|
|
// package, and each of those packages has their own public and private
|
|
// subpackages.
|
|
func (c Converter) packageManager(s, vocabName string) (pkg *gen.PackageManager, e error) {
|
|
s = strings.ToLower(s)
|
|
switch c.PackagePolicy {
|
|
case FlatUnderRoot:
|
|
pkg = c.GenRoot.Sub(strings.ToLower(vocabName))
|
|
case IndividualUnderRoot:
|
|
pkg = c.GenRoot.Sub(strings.ToLower(vocabName)).SubPrivate(s)
|
|
default:
|
|
e = fmt.Errorf("unrecognized PackagePolicy: %v", c.PackagePolicy)
|
|
}
|
|
return
|
|
}
|
|
|
|
// rootFiles creates files that are applied for all vocabularies. These files
|
|
// are the ones typically used by developers.
|
|
func (c Converter) rootFiles(pkg gen.Package, vocabName string, v vocabulary) (f []*File, e error) {
|
|
pg := gen.NewPackageGenerator()
|
|
ctors, ext, disj, extBy, globalVar, initFn := pg.RootDefinitions(vocabName, v.Manager, v.typeArray(), v.propArray())
|
|
initFile := jen.NewFilePath(pkg.Path())
|
|
initFile.Add(globalVar).Line().Add(initFn.Definition()).Line()
|
|
f = append(f, &File{
|
|
F: initFile,
|
|
FileName: "gen_init.go",
|
|
Directory: pkg.WriteDir(),
|
|
})
|
|
lowerVocabName := strings.ToLower(vocabName)
|
|
f = append(f, funcsToFile(pkg, ctors, fmt.Sprintf("gen_pkg_%s_constructors.go", lowerVocabName)))
|
|
f = append(f, funcsToFile(pkg, ext, fmt.Sprintf("gen_pkg_%s_extends.go", lowerVocabName)))
|
|
f = append(f, funcsToFile(pkg, disj, fmt.Sprintf("gen_pkg_%s_disjoint.go", lowerVocabName)))
|
|
f = append(f, funcsToFile(pkg, extBy, fmt.Sprintf("gen_pkg_%s_extendedby.go", lowerVocabName)))
|
|
return
|
|
}
|
|
|
|
// packageFiles generates package-level files necessary for types and properties
|
|
// within a single vocabulary.
|
|
//
|
|
// In the FlatUnderRoot policy, only one is needed in the public and private
|
|
// directories since all types and properties are co-located within the same
|
|
// package.
|
|
//
|
|
// In the IndividualUnderRoot policy, multiple are needed. Since each type and
|
|
// property are in their own package, one package file is needed in each of
|
|
// those types' and properties' subpackage.
|
|
func (c Converter) packageFiles(v vocabulary) (f []*File, e error) {
|
|
switch c.PackagePolicy {
|
|
case FlatUnderRoot:
|
|
// Only need one for all types.
|
|
pg := gen.NewPackageGenerator()
|
|
pubI := pg.PublicDefinitions(v.typeArray())
|
|
// Public
|
|
pub := v.typeArray()[0].PublicPackage()
|
|
file := jen.NewFilePath(pub.Path())
|
|
file.Add(pubI.Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: "gen_pkg.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
// Public Package Documentation
|
|
docFile := jen.NewFilePath(pub.Path())
|
|
docFile.PackageComment(gen.VocabPackageComment(pub.Name(), v.Name))
|
|
f = append(f, &File{
|
|
F: docFile,
|
|
FileName: "gen_doc.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
// Private
|
|
s, i, fn := pg.PrivateDefinitions(v.typeArray(), v.propArray())
|
|
priv := v.typeArray()[0].PrivatePackage()
|
|
file = jen.NewFilePath(priv.Path())
|
|
file.Add(
|
|
s,
|
|
).Line().Add(
|
|
i.Definition(),
|
|
).Line().Add(
|
|
fn.Definition(),
|
|
).Line()
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: "gen_pkg.go",
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
// Private Package Documentation
|
|
privDocFile := jen.NewFilePath(priv.Path())
|
|
privDocFile.PackageComment(gen.PrivateFlatPackageComment(priv.Name(), v.Name))
|
|
f = append(f, &File{
|
|
F: privDocFile,
|
|
FileName: "gen_doc.go",
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
case IndividualUnderRoot:
|
|
for _, tg := range v.Types {
|
|
var file []*File
|
|
file, e = c.typePackageFiles(tg, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, file...)
|
|
}
|
|
for _, pg := range v.FProps {
|
|
var file []*File
|
|
file, e = c.propertyPackageFiles(&pg.PropertyGenerator, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, file...)
|
|
}
|
|
for _, pg := range v.NFProps {
|
|
var file []*File
|
|
file, e = c.propertyPackageFiles(&pg.PropertyGenerator, v.Name)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, file...)
|
|
}
|
|
default:
|
|
e = fmt.Errorf("unrecognized PackagePolicy: %v", c.PackagePolicy)
|
|
}
|
|
return
|
|
}
|
|
|
|
// typePackageFile creates the package-level files necessary for a type if it
|
|
// is being generated in its own package.
|
|
func (c Converter) typePackageFiles(tg *gen.TypeGenerator, vocabName string) (f []*File, e error) {
|
|
// Only need one for all types.
|
|
tpg := gen.NewTypePackageGenerator()
|
|
pubI := tpg.PublicDefinitions([]*gen.TypeGenerator{tg})
|
|
// Public
|
|
pub := tg.PublicPackage()
|
|
file := jen.NewFilePath(pub.Path())
|
|
file.Add(pubI.Definition())
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: "gen_pkg.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
// Public Package Documentation -- this may collide, but it's all the
|
|
// same content.
|
|
docFile := jen.NewFilePath(pub.Path())
|
|
docFile.PackageComment(gen.VocabPackageComment(pub.Name(), vocabName))
|
|
f = append(f, &File{
|
|
F: docFile,
|
|
FileName: "gen_doc.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
// Private
|
|
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()
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: "gen_pkg.go",
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
// Private Package Documentation
|
|
privDocFile := jen.NewFilePath(priv.Path())
|
|
privDocFile.PackageComment(gen.PrivateIndividualTypePackageComment(priv.Name(), tg.TypeName()))
|
|
f = append(f, &File{
|
|
F: privDocFile,
|
|
FileName: "gen_doc.go",
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// propertyPackageFiles creates the package-level files necessary for a property
|
|
// if it is being generated in its own package.
|
|
func (c Converter) propertyPackageFiles(pg *gen.PropertyGenerator, vocabName string) (f []*File, e error) {
|
|
// Only need one for all types.
|
|
ppg := gen.NewPropertyPackageGenerator()
|
|
// Public Package Documentation -- this may collide, but it's all the
|
|
// same content.
|
|
pub := pg.GetPublicPackage()
|
|
docFile := jen.NewFilePath(pub.Path())
|
|
docFile.PackageComment(gen.VocabPackageComment(pub.Name(), vocabName))
|
|
f = append(f, &File{
|
|
F: docFile,
|
|
FileName: "gen_doc.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
// Private
|
|
s, i, fn := ppg.PrivateDefinitions([]*gen.PropertyGenerator{pg})
|
|
priv := pg.GetPrivatePackage()
|
|
file := jen.NewFilePath(priv.Path())
|
|
file.Add(
|
|
s,
|
|
).Line().Add(
|
|
i.Definition(),
|
|
).Line().Add(
|
|
fn.Definition(),
|
|
).Line()
|
|
f = append(f, &File{
|
|
F: file,
|
|
FileName: "gen_pkg.go",
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
// Private Package Documentation
|
|
privDocFile := jen.NewFilePath(priv.Path())
|
|
privDocFile.PackageComment(gen.PrivateIndividualPropertyPackageComment(priv.Name(), pg.PropertyName()))
|
|
f = append(f, &File{
|
|
F: privDocFile,
|
|
FileName: "gen_doc.go",
|
|
Directory: priv.WriteDir(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// typeNamer bridges rdf.VocabularyType and gen.TypeGenerator.
|
|
type typeNamer interface {
|
|
TypeName() string
|
|
}
|
|
|
|
var (
|
|
_ typeNamer = &gen.TypeGenerator{}
|
|
_ typeNamer = &rdf.VocabularyType{}
|
|
)
|
|
|
|
// propertyNamer bridges rdf.VocabularyProperty to the gen side of functional
|
|
// and non-functional property generators.
|
|
type propertyNamer interface {
|
|
PropertyName() string
|
|
}
|
|
|
|
var (
|
|
_ propertyNamer = &gen.FunctionalPropertyGenerator{}
|
|
_ propertyNamer = &gen.NonFunctionalPropertyGenerator{}
|
|
_ propertyNamer = &rdf.VocabularyProperty{}
|
|
)
|
|
|
|
// toIdentifier converts the name in the rdf package to a gen-compatible
|
|
// Identifier.
|
|
func toIdentifier(n rdf.NameGetter) gen.Identifier {
|
|
return gen.Identifier{
|
|
LowerName: n.GetName(),
|
|
CamelName: strings.Title(n.GetName()),
|
|
}
|
|
}
|
|
|
|
// allExtendsAreIn determines if a VocabularyType's parents are all already
|
|
// converted to a TypeGenerator.
|
|
func allExtendsAreIn(t rdf.VocabularyType, v map[string]*gen.TypeGenerator) bool {
|
|
for _, e := range t.Extends {
|
|
if len(e.Vocab) != 0 {
|
|
// TODO: This should be fixed to handle references
|
|
return false
|
|
} else if _, ok := v[e.Name]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// convertValue converts a Kind value into a code-generated File.
|
|
func convertValue(pkg gen.Package, v *gen.Kind) *File {
|
|
file := jen.NewFilePath(pkg.Path())
|
|
file.Add(
|
|
v.SerializeDef.Definition(),
|
|
).Line().Add(
|
|
v.DeserializeDef.Definition(),
|
|
).Line().Add(
|
|
v.LessDef.Definition())
|
|
return &File{
|
|
F: file,
|
|
FileName: fmt.Sprintf("gen_%s.go", v.Name.LowerName),
|
|
Directory: pkg.WriteDir(),
|
|
}
|
|
}
|
|
|
|
// funcsToFile is a helper converting an array of Functions into a single File
|
|
// in the specified Package.
|
|
func funcsToFile(pkg gen.Package, fns []*codegen.Function, filename string) *File {
|
|
file := jen.NewFilePath(pkg.Path())
|
|
for _, fn := range fns {
|
|
file.Add(fn.Definition()).Line()
|
|
}
|
|
return &File{
|
|
F: file,
|
|
FileName: filename,
|
|
Directory: pkg.WriteDir(),
|
|
}
|
|
}
|
|
|
|
// AsComment creates a Go-comment-compatible string out of an Example.
|
|
func asComment(v rdf.VocabularyExample) (s string) {
|
|
if len(v.Name) > 0 && v.URI != nil {
|
|
s = fmt.Sprintf("%s (%s):\n", v.Name, v.URI)
|
|
} else if len(v.Name) > 0 {
|
|
s = fmt.Sprintf("%s:\n", v.Name)
|
|
} else if v.URI != nil {
|
|
s = fmt.Sprintf("%s:\n", v.URI)
|
|
}
|
|
b, err := json.MarshalIndent(v.Example, "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ex := string(b)
|
|
ex = strings.Replace(ex, "\n", "\n ", -1)
|
|
return fmt.Sprintf("%s %s", s, ex)
|
|
}
|