1431 行
41 KiB
Go
1431 行
41 KiB
Go
package convert
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/dave/jennifer/jen"
|
|
"github.com/go-fed/activity/astool/codegen"
|
|
"github.com/go-fed/activity/astool/gen"
|
|
"github.com/go-fed/activity/astool/rdf"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
interfacePkg = "vocab"
|
|
resolverPkg = "resolver"
|
|
typePropertyName = "type"
|
|
)
|
|
|
|
// 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),
|
|
}
|
|
}
|
|
|
|
// allTypeArray converts all Types, including referenced Types, to an array.
|
|
func (v vocabulary) allTypeArray() []*gen.TypeGenerator {
|
|
var typeArray []*gen.TypeGenerator
|
|
for _, ref := range v.References {
|
|
typeArray = append(typeArray, ref.typeArray()...)
|
|
}
|
|
typeArray = append(typeArray, v.typeArray()...)
|
|
sort.Sort(sortableTypeGenerator(typeArray))
|
|
return typeArray
|
|
}
|
|
|
|
// allPropArray converts all Properties, including referenced Properties, to an
|
|
// array.
|
|
func (v vocabulary) allPropArray() []*gen.PropertyGenerator {
|
|
var propArray []*gen.PropertyGenerator
|
|
for _, ref := range v.References {
|
|
propArray = append(propArray, ref.propArray()...)
|
|
}
|
|
propArray = append(propArray, v.propArray()...)
|
|
sort.Sort(sortablePropertyGenerator(propArray))
|
|
return propArray
|
|
}
|
|
|
|
// allFuncPropArray converts all FProps, including referenced Properties, to an
|
|
// array.
|
|
func (v vocabulary) allFuncPropArray() []*gen.FunctionalPropertyGenerator {
|
|
var funcPropArray []*gen.FunctionalPropertyGenerator
|
|
for _, ref := range v.References {
|
|
funcPropArray = append(funcPropArray, ref.funcPropArray()...)
|
|
}
|
|
funcPropArray = append(funcPropArray, v.funcPropArray()...)
|
|
sort.Sort(sortableFuncPropertyGenerator(funcPropArray))
|
|
return funcPropArray
|
|
}
|
|
|
|
// allNonFuncPropArray converts all NFProps, including referenced Properties, to
|
|
// an array.
|
|
func (v vocabulary) allNonFuncPropArray() []*gen.NonFunctionalPropertyGenerator {
|
|
var nonFuncPropArray []*gen.NonFunctionalPropertyGenerator
|
|
for _, ref := range v.References {
|
|
nonFuncPropArray = append(nonFuncPropArray, ref.nonFuncPropArray()...)
|
|
}
|
|
nonFuncPropArray = append(nonFuncPropArray, v.nonFuncPropArray()...)
|
|
sort.Sort(sortableNonFuncPropertyGenerator(nonFuncPropArray))
|
|
return nonFuncPropArray
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
sort.Sort(sortableTypeGenerator(tg))
|
|
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)
|
|
}
|
|
sort.Sort(sortablePropertyGenerator(fp))
|
|
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)
|
|
}
|
|
sort.Sort(sortableFuncPropertyGenerator(fp))
|
|
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)
|
|
}
|
|
sort.Sort(sortableNonFuncPropertyGenerator(nfp))
|
|
return nfp
|
|
}
|
|
|
|
// rdfReferences properly accounts for HTTP and HTTPS lookups of specification
|
|
// URIs.
|
|
type mapReferences map[string]*vocabulary
|
|
|
|
// Get attempts to fetch a reference, returning an error if it cannot.
|
|
func (r mapReferences) Get(uri string) (*vocabulary, error) {
|
|
http, https, err := rdf.ToHttpAndHttps(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if v, ok := r[http]; ok {
|
|
return v, nil
|
|
} else if v, ok := r[https]; ok {
|
|
return v, nil
|
|
} else {
|
|
return nil, fmt.Errorf("mapReferences does not have reference %s", uri)
|
|
}
|
|
}
|
|
|
|
// rdfReferences properly accounts for HTTP and HTTPS lookups of specification
|
|
// URIs.
|
|
type rdfReferences map[string]*rdf.Vocabulary
|
|
|
|
// Get attempts to fetch a reference, returning an error if it cannot.
|
|
func (r rdfReferences) Get(uri string) (*rdf.Vocabulary, error) {
|
|
http, https, err := rdf.ToHttpAndHttps(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if v, ok := r[http]; ok {
|
|
return v, nil
|
|
} else if v, ok := r[https]; ok {
|
|
return v, nil
|
|
} else {
|
|
return nil, fmt.Errorf("rdfReferences does not have reference %s", uri)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
GenRoot *gen.PackageManager
|
|
PackagePolicy PackagePolicy
|
|
typeProperty *gen.PropertyGenerator
|
|
typePropertyVocabName string
|
|
}
|
|
|
|
// Convert turns a ParsedVocabulary into a set of code-generated files.
|
|
func (c *Converter) Convert(p *rdf.ParsedVocabulary) (f []*File, e error) {
|
|
v := newVocabulary()
|
|
done := make(map[string]bool)
|
|
// Step 1: Convert referenced specifications
|
|
for i, vocabURI := range p.Order {
|
|
refP := p.Clone()
|
|
if i < len(p.Order)-1 {
|
|
refP.Vocab = *refP.References[vocabURI]
|
|
delete(refP.References, vocabURI)
|
|
}
|
|
|
|
var refV vocabulary
|
|
refV, e = c.convertVocabulary(refP, v.References)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Order is full of references except the last, which is the
|
|
// Vocab on ParsedVocabulary.
|
|
if i < len(p.Order)-1 {
|
|
v.References[vocabURI] = &refV
|
|
} else {
|
|
refV.References = v.References
|
|
v = refV
|
|
}
|
|
done[vocabURI] = true
|
|
}
|
|
// Step 2: Intrinsically-known vocabularies won't appear in p.Order
|
|
recurV, err := c.convertReferenceVocabularyRecursively(done, p, v.References)
|
|
if err != nil {
|
|
e = err
|
|
return
|
|
}
|
|
for k, rv := range recurV {
|
|
v.References[k] = rv
|
|
}
|
|
// Step 3: Create code-wide generators
|
|
e = c.convertGenRoot(&v)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Step 4: Use the code generators to build the resulting code-generated
|
|
// files.
|
|
f, e = c.convertToFiles(v)
|
|
return
|
|
}
|
|
|
|
// convertReferenceVocabularyRecursively will convert all references nested in
|
|
// all vocabularies and results in a flattened converted map.
|
|
func (c *Converter) convertReferenceVocabularyRecursively(skip map[string]bool, p *rdf.ParsedVocabulary, refs map[string]*vocabulary) (v map[string]*vocabulary, e error) {
|
|
v = make(map[string]*vocabulary)
|
|
for k, _ := range p.References {
|
|
if skip[k] {
|
|
continue
|
|
}
|
|
refP := p.Clone()
|
|
refP.Vocab = *refP.References[k]
|
|
delete(refP.References, k)
|
|
var refV vocabulary
|
|
refV, e = c.convertVocabulary(refP, refs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
v[k] = &refV
|
|
// Recur
|
|
var recurVocab map[string]*vocabulary
|
|
recurVocab, e = c.convertReferenceVocabularyRecursively(skip, refP, refs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
// Flatten
|
|
for k, refVocab := range recurVocab {
|
|
v[k] = refVocab
|
|
}
|
|
}
|
|
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) {
|
|
pub := c.GenRoot.PublicPackage()
|
|
// References
|
|
for _, ref := range v.References {
|
|
for _, v := range ref.Values {
|
|
pkg := c.valuePackage(v)
|
|
f = append(f, convertValue(pkg, v))
|
|
}
|
|
var files []*File
|
|
files, e = c.toFiles(*ref)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, files...)
|
|
files, e = c.rootFiles(pub, ref.Name, *ref, v.Manager)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, files...)
|
|
pkgFiles, err := c.packageFiles(*ref, v.Manager)
|
|
if err != nil {
|
|
e = err
|
|
return
|
|
}
|
|
f = append(f, pkgFiles...)
|
|
}
|
|
// This vocabulary
|
|
for _, v := range v.Values {
|
|
pkg := c.valuePackage(v)
|
|
f = append(f, convertValue(pkg, v))
|
|
}
|
|
var files []*File
|
|
files, e = c.toFiles(v)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, files...)
|
|
files, e = c.rootFiles(pub, v.Name, v, v.Manager)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, files...)
|
|
pkgFiles, err := c.packageFiles(v, v.Manager)
|
|
if err != nil {
|
|
e = err
|
|
return
|
|
}
|
|
f = append(f, pkgFiles...)
|
|
// Init file
|
|
var file *File
|
|
file, e = c.initFile(pub, v, v.Manager)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, file)
|
|
// Manager
|
|
jenFile := jen.NewFilePath(pub.Path())
|
|
jenFile.Add(v.Manager.Definition().Definition())
|
|
f = append(f, &File{
|
|
F: jenFile,
|
|
FileName: "gen_manager.go",
|
|
Directory: pub.WriteDir(),
|
|
})
|
|
// 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(),
|
|
})
|
|
// Constants
|
|
files, e = c.constFiles(c.GenRoot.PublicPackage(), v.allTypeArray(), v.allPropArray())
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, files...)
|
|
// Resolvers
|
|
files, e = c.resolverFiles(c.GenRoot.PublicPackage(), v.Manager, v)
|
|
if e != nil {
|
|
return
|
|
}
|
|
f = append(f, files...)
|
|
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, refs map[string]*vocabulary) (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(p.Vocab.Name, val)
|
|
}
|
|
for k, prop := range p.Vocab.Properties {
|
|
if prop.Functional {
|
|
v.FProps[k], e = c.convertFunctionalProperty(prop, v.Values, p.Vocab, p.References, refs)
|
|
if c.typeProperty == nil && prop.Name == typePropertyName {
|
|
c.typeProperty = &(v.FProps[k].PropertyGenerator)
|
|
c.typePropertyVocabName = v.Name
|
|
}
|
|
} else {
|
|
v.NFProps[k], e = c.convertNonFunctionalProperty(prop, v.Values, p.Vocab, p.References, refs)
|
|
if c.typeProperty == nil && prop.Name == typePropertyName {
|
|
c.typeProperty = &(v.NFProps[k].PropertyGenerator)
|
|
c.typePropertyVocabName = v.Name
|
|
}
|
|
}
|
|
if e != nil {
|
|
return
|
|
}
|
|
}
|
|
if c.typeProperty == nil {
|
|
e = fmt.Errorf("convertVocabulary: could not find \"type\" property and its constructor")
|
|
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 c.allExtendsAreIn(p.Vocab.Registry, t, v.Types, refs) {
|
|
var tg *gen.TypeGenerator
|
|
tg, e = c.convertType(t, p.Vocab, v.FProps, v.NFProps, v.Types, refs)
|
|
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
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// convertGenRoot creates code-wide code generators.
|
|
func (c *Converter) convertGenRoot(v *vocabulary) (e error) {
|
|
v.Manager, e = gen.NewManagerGenerator(
|
|
c.GenRoot.PublicPackage(),
|
|
v.allTypeArray(),
|
|
v.allFuncPropArray(),
|
|
v.allNonFuncPropArray())
|
|
return
|
|
}
|
|
|
|
// existingProperty attempts to find an existing Property in a referred
|
|
// vocabulary.
|
|
//
|
|
// Returns all nils if the property has been defined in a later vocabulary.
|
|
func (c *Converter) existingProperty(registry *rdf.RDFRegistry, r rdf.VocabularyReference, genRefs map[string]*vocabulary) (g gen.Property, e error) {
|
|
var url string
|
|
url, e = registry.ResolveAlias(r.Vocab)
|
|
if e != nil {
|
|
return
|
|
}
|
|
mapRef := mapReferences(genRefs)
|
|
var refVocab *vocabulary
|
|
refVocab, e = mapRef.Get(url)
|
|
if e != nil {
|
|
// Forwarded property -- a later specification added this
|
|
// property to a type defined in an earlier specification.
|
|
e = nil
|
|
return
|
|
}
|
|
// Ugly.
|
|
if p, ok := refVocab.FProps[r.Name]; !ok {
|
|
if p, ok := refVocab.NFProps[r.Name]; !ok {
|
|
e = fmt.Errorf("refVocab %s cannot find %s", r.Vocab, r.Name)
|
|
return
|
|
} else {
|
|
g = p
|
|
return
|
|
}
|
|
} else {
|
|
g = p
|
|
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,
|
|
genRefs map[string]*vocabulary) (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 {
|
|
var py gen.Property
|
|
py, e = c.existingProperty(v.Registry, prop, genRefs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
if py != nil {
|
|
p = append(p, py)
|
|
}
|
|
} 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 {
|
|
var py gen.Property
|
|
py, e = c.existingProperty(v.Registry, prop, genRefs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
if py != nil {
|
|
wop = append(wop, py)
|
|
}
|
|
} 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 {
|
|
var t *gen.TypeGenerator
|
|
t, e = existingType(v.Registry, ex, genRefs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
ext = append(ext, t)
|
|
} 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 {
|
|
var t *gen.TypeGenerator
|
|
t, e = existingType(v.Registry, disj, genRefs)
|
|
if e != nil {
|
|
return
|
|
}
|
|
disjoint = append(disjoint, t)
|
|
} 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.
|
|
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(),
|
|
v.URI,
|
|
vocabNameToAlias(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,
|
|
genRefs map[string]*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, e = gen.NewFunctionalPropertyGenerator(
|
|
v.GetName(),
|
|
v.URI,
|
|
vocabNameToAlias(v.GetName()),
|
|
pm,
|
|
toIdentifier(p),
|
|
comment,
|
|
k,
|
|
p.NaturalLanguageMap)
|
|
if e != nil {
|
|
return
|
|
}
|
|
e = backPopulateProperty(v.Registry, p, genRefs, fp)
|
|
if e != nil {
|
|
return
|
|
}
|
|
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,
|
|
genRefs map[string]*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, e = gen.NewNonFunctionalPropertyGenerator(
|
|
v.GetName(),
|
|
v.URI,
|
|
vocabNameToAlias(v.GetName()),
|
|
pm,
|
|
toIdentifier(p),
|
|
comment,
|
|
k,
|
|
p.NaturalLanguageMap)
|
|
if e != nil {
|
|
return
|
|
}
|
|
e = backPopulateProperty(v.Registry, p, genRefs, nfp)
|
|
if e != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// convertValue turns a rdf.VocabularyValue into a Kind.
|
|
func (c *Converter) convertValue(vocabName string, v rdf.VocabularyValue) *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())
|
|
// Name must use toIdentifier for vocabValuePackage and valuePackage to
|
|
// be the same.
|
|
id := toIdentifier(v)
|
|
return gen.NewKindForValue(id.LowerName,
|
|
id.CamelName,
|
|
vocabName,
|
|
v.DefinitionType,
|
|
v.IsNilable,
|
|
v.IsURI,
|
|
s,
|
|
d,
|
|
l)
|
|
}
|
|
|
|
// 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 {
|
|
id := toIdentifier(t)
|
|
kt := gen.NewKindForType(id.LowerName, id.CamelName, vocab.Name)
|
|
k = append(k, *kt)
|
|
}
|
|
} else {
|
|
// It is a Value of the vocabulary
|
|
k = append(k, *kind)
|
|
}
|
|
} else {
|
|
var url string
|
|
url, e = vocab.Registry.ResolveAlias(r.Vocab)
|
|
if e != nil {
|
|
return
|
|
}
|
|
rdfRef := rdfReferences(refs)
|
|
var refVocab *rdf.Vocabulary
|
|
refVocab, e = rdfRef.Get(url)
|
|
if e != nil {
|
|
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 {
|
|
id := toIdentifier(t)
|
|
kt := gen.NewKindForType(id.LowerName, id.CamelName, refVocab.Name)
|
|
k = append(k, *kt)
|
|
}
|
|
} else {
|
|
// It is a Value of the vocabulary
|
|
k = append(k, *c.convertValue(refVocab.Name, 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.SubPublic(interfacePkg).SubPrivate(strings.ToLower(vocabName))
|
|
case IndividualUnderRoot:
|
|
pkg = c.GenRoot.SubPublic(interfacePkg).SubPrivate(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, m *gen.ManagerGenerator) (f []*File, e error) {
|
|
pg := gen.NewPackageGenerator(c.typePropertyVocabName, m, c.typeProperty)
|
|
typeCtors, propCtors, ext, disj, extBy := pg.RootDefinitions(vocabName, v.typeArray(), v.propArray())
|
|
lowerVocabName := strings.ToLower(vocabName)
|
|
if file := funcsToFile(pkg, typeCtors, fmt.Sprintf("gen_pkg_%s_type_constructors.go", lowerVocabName)); file != nil {
|
|
f = append(f, file)
|
|
}
|
|
if file := funcsToFile(pkg, propCtors, fmt.Sprintf("gen_pkg_%s_property_constructors.go", lowerVocabName)); file != nil {
|
|
f = append(f, file)
|
|
}
|
|
if file := funcsToFile(pkg, ext, fmt.Sprintf("gen_pkg_%s_extends.go", lowerVocabName)); file != nil {
|
|
f = append(f, file)
|
|
}
|
|
if file := funcsToFile(pkg, disj, fmt.Sprintf("gen_pkg_%s_disjoint.go", lowerVocabName)); file != nil {
|
|
f = append(f, file)
|
|
}
|
|
if file := funcsToFile(pkg, extBy, fmt.Sprintf("gen_pkg_%s_extendedby.go", lowerVocabName)); file != nil {
|
|
f = append(f, file)
|
|
}
|
|
return
|
|
}
|
|
|
|
// initFile creates the file with the init function that hooks together the
|
|
// runtime Manager.
|
|
func (c *Converter) initFile(pkg gen.Package, root vocabulary, m *gen.ManagerGenerator) (f *File, e error) {
|
|
pg := gen.NewPackageGenerator(c.typePropertyVocabName, m, c.typeProperty)
|
|
globalVar, initFn := pg.InitDefinitions(pkg, root.allTypeArray(), root.allPropArray())
|
|
initFile := jen.NewFilePath(pkg.Path())
|
|
initFile.Add(globalVar).Line().Add(initFn.Definition()).Line()
|
|
f = &File{
|
|
F: initFile,
|
|
FileName: "gen_init.go",
|
|
Directory: pkg.WriteDir(),
|
|
}
|
|
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, m *gen.ManagerGenerator) (f []*File, e error) {
|
|
switch c.PackagePolicy {
|
|
case FlatUnderRoot:
|
|
pg := gen.NewPackageGenerator(c.typePropertyVocabName, m, c.typeProperty)
|
|
if tArr := v.typeArray(); len(tArr) > 0 {
|
|
// Only need one for all types.
|
|
pubI := pg.PublicDefinitions(tArr)
|
|
// 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
|
|
if tArr, pArr := v.typeArray(), v.propArray(); len(tArr) > 0 || len(pArr) > 0 {
|
|
s, i, fn := pg.PrivateDefinitions(tArr, pArr)
|
|
var priv gen.Package
|
|
if len(tArr) > 0 {
|
|
priv = tArr[0].PrivatePackage()
|
|
} else {
|
|
priv = pArr[0].GetPrivatePackage()
|
|
}
|
|
file := jen.NewFilePath(priv.Path())
|
|
for _, elem := range s {
|
|
file.Add(elem).Line()
|
|
}
|
|
for _, elem := range i {
|
|
file.Add(elem.Definition()).Line()
|
|
}
|
|
for _, elem := range fn {
|
|
file.Add(elem.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, m)
|
|
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, m *gen.ManagerGenerator) (f []*File, e error) {
|
|
// Only need one for all types.
|
|
tpg := gen.NewTypePackageGenerator(c.typePropertyVocabName, m, c.typeProperty)
|
|
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())
|
|
for _, elem := range s {
|
|
file.Add(elem).Line()
|
|
}
|
|
for _, elem := range i {
|
|
file.Add(elem.Definition()).Line()
|
|
}
|
|
for _, elem := range fn {
|
|
file.Add(elem.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
|
|
}
|
|
|
|
// resolverFiles creates the files necessary for the resolvers.
|
|
func (c *Converter) resolverFiles(pkg gen.Package, manGen *gen.ManagerGenerator, root vocabulary) (files []*File, e error) {
|
|
rg := gen.NewResolverGenerator(root.allTypeArray(), manGen, pkg)
|
|
jsonRes, typeRes, typePredRes, errDefs, fns, iFaces := rg.Definition()
|
|
// Utils
|
|
file := jen.NewFilePath(pkg.Path())
|
|
for _, errDef := range errDefs {
|
|
file.Add(errDef).Line()
|
|
}
|
|
for _, iFace := range iFaces {
|
|
file.Add(iFace.Definition()).Line()
|
|
}
|
|
for _, fn := range fns {
|
|
file.Add(fn.Definition()).Line()
|
|
}
|
|
files = append(files, &File{
|
|
F: file,
|
|
FileName: "gen_resolver_utils.go",
|
|
Directory: pkg.WriteDir(),
|
|
})
|
|
// JSON resolver
|
|
file = jen.NewFilePath(pkg.Path())
|
|
file.Add(jsonRes.Definition())
|
|
files = append(files, &File{
|
|
F: file,
|
|
FileName: "gen_json_resolver.go",
|
|
Directory: pkg.WriteDir(),
|
|
})
|
|
// Type, not predicated
|
|
file = jen.NewFilePath(pkg.Path())
|
|
file.Add(typeRes.Definition())
|
|
files = append(files, &File{
|
|
F: file,
|
|
FileName: "gen_type_resolver.go",
|
|
Directory: pkg.WriteDir(),
|
|
})
|
|
// Type, Predicated
|
|
file = jen.NewFilePath(pkg.Path())
|
|
file.Add(typePredRes.Definition())
|
|
files = append(files, &File{
|
|
F: file,
|
|
FileName: "gen_type_predicated_resolver.go",
|
|
Directory: pkg.WriteDir(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// constFiles creates the files for constants.
|
|
func (c *Converter) constFiles(pkg gen.Package, types []*gen.TypeGenerator, props []*gen.PropertyGenerator) (files []*File, e error) {
|
|
consts := gen.GenerateConstants(types, props)
|
|
file := jen.NewFilePath(pkg.Path())
|
|
for _, elem := range consts {
|
|
file.Add(elem).Line()
|
|
}
|
|
files = append(files, &File{
|
|
F: file,
|
|
FileName: "gen_consts.go",
|
|
Directory: pkg.WriteDir(),
|
|
})
|
|
return files, e
|
|
}
|
|
|
|
// allExtendsAreIn determines if a VocabularyType's parents are all already
|
|
// converted to a TypeGenerator.
|
|
func (c *Converter) allExtendsAreIn(registry *rdf.RDFRegistry, t rdf.VocabularyType, v map[string]*gen.TypeGenerator, genRefs map[string]*vocabulary) bool {
|
|
for _, e := range t.Extends {
|
|
if len(e.Vocab) != 0 {
|
|
_, err := existingType(registry, e, genRefs)
|
|
return err == nil
|
|
} else if _, ok := v[e.Name]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// toFiles converts a vocabulary's types and properties to files.
|
|
func (c *Converter) toFiles(v vocabulary) (f []*File, e error) {
|
|
vName := strings.ToLower(v.Name)
|
|
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_%s.go", vName, 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_%s_interface.go", vName, 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_%s.go", vName, 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_%s_interface.go", vName, 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_%s.go", vName, 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_%s_interface.go", vName, strings.ToLower(i.TypeName())),
|
|
Directory: pub.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()),
|
|
}
|
|
}
|
|
|
|
// 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(),
|
|
}
|
|
}
|
|
|
|
// existingType attempts to find an existing Property in a referred vocabulary.
|
|
func existingType(registry *rdf.RDFRegistry, r rdf.VocabularyReference, genRefs map[string]*vocabulary) (g *gen.TypeGenerator, e error) {
|
|
var url string
|
|
url, e = registry.ResolveAlias(r.Vocab)
|
|
if e != nil {
|
|
return
|
|
}
|
|
mapRef := mapReferences(genRefs)
|
|
var refVocab *vocabulary
|
|
refVocab, e = mapRef.Get(url)
|
|
if e != nil {
|
|
return
|
|
}
|
|
if t, ok := refVocab.Types[r.Name]; !ok {
|
|
e = fmt.Errorf("refVocab %s cannot find %s", r.Vocab, r.Name)
|
|
} else {
|
|
g = t
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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 {
|
|
if len(fns) == 0 {
|
|
return nil
|
|
}
|
|
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)
|
|
}
|
|
|
|
// backPopulateProperty sets a new property generator onto existing types.
|
|
func backPopulateProperty(r *rdf.RDFRegistry, p rdf.VocabularyProperty, genRefs map[string]*vocabulary, fp gen.Property) (e error) {
|
|
for _, dom := range p.Domain {
|
|
// Within our own vocabulary -- skip
|
|
if len(dom.Vocab) == 0 {
|
|
continue
|
|
}
|
|
var url string
|
|
url, e = r.ResolveAlias(dom.Vocab)
|
|
if e != nil {
|
|
return
|
|
}
|
|
mapRef := mapReferences(genRefs)
|
|
var refVocab *vocabulary
|
|
refVocab, e = mapRef.Get(url)
|
|
if e != nil {
|
|
// Continue -- this property should be added when the
|
|
// type is being created for this extension.
|
|
continue
|
|
}
|
|
if t, ok := refVocab.Types[dom.Name]; !ok {
|
|
e = fmt.Errorf("back populate property with vocab %s cannot find %s", dom.Vocab, dom.Name)
|
|
return
|
|
} else {
|
|
e = t.AddPropertyGenerator(fp)
|
|
if e != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
for _, ran := range p.Range {
|
|
t, e := existingType(r, ran, genRefs)
|
|
if e != nil {
|
|
// Ignore -- it is either a part of this vocabulary, or
|
|
// not a type (is a value instead).
|
|
continue
|
|
}
|
|
t.AddRangeProperty(fp)
|
|
}
|
|
return
|
|
}
|
|
|
|
// vocabName turns a vocabulary name into an alias based on the capitalization
|
|
// of the name. Note that "ActivityStreams" will not be aliased, it will return
|
|
// an empty string.
|
|
func vocabNameToAlias(name string) string {
|
|
if strings.ToLower(name) == "activitystreams" {
|
|
return ""
|
|
}
|
|
s := ""
|
|
for _, r := range name {
|
|
if unicode.IsUpper(r) {
|
|
s += string(r)
|
|
}
|
|
}
|
|
return strings.ToLower(s)
|
|
}
|