From 8589b0f14f752122c3e97270b9910482f375b598 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 8 Dec 2018 17:50:02 +0100 Subject: [PATCH 01/14] Cleaned up the spec definition file --- tools/exp/spec.json | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/tools/exp/spec.json b/tools/exp/spec.json index 0a9b349..32b7cbc 100644 --- a/tools/exp/spec.json +++ b/tools/exp/spec.json @@ -1134,11 +1134,6 @@ "name": "Example 1" }, "notes": "Describes an object of any kind. The Object type serves as the base type for most of the other kinds of objects defined in the Activity Vocabulary, including other Core types such as Activity, IntransitiveActivity, Collection and OrderedCollection.", - "subClassOf": { - "type": "owl:Class", - "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", - "name": "Link" - }, "disjointWith": { "type": "owl:Class", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", @@ -1163,11 +1158,6 @@ "name": "Example 2" }, "notes": "A Link is an indirect, qualified reference to a resource identified by a URL. The fundamental model for links is established by [ RFC5988]. Many of the properties defined by the Activity Vocabulary allow values that are either instances of Object or Link. When a Link is used, it establishes a qualified relation connecting the subject (the containing object) to the resource identified by the href. Properties of the Link are properties of the reference as opposed to properties of the resource.", - "subClassOf": { - "type": "owl:Class", - "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object", - "name": "Object" - }, "disjointWith": { "type": "owl:Class", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object", @@ -1364,7 +1354,7 @@ "subClassOf": { "type": "owl:Class", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection", - "name": "OrderedCollectionCollectionPage" + "name": "CollectionPage" }, "disjointWith": [], "name": "OrderedCollectionPage", @@ -1774,7 +1764,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-id", "range": { "type": "owl:Class", - "unionOf": "https://www.w3.org/TR/activitystreams-vocabulary/anyURI" + "unionOf": "xsd:anyURI" }, "name": "id", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-id" @@ -1813,7 +1803,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-type", "range": { "type": "owl:Class", - "unionOf": "https://www.w3.org/TR/activitystreams-vocabulary/anyURI" + "unionOf": "xsd:anyURI" }, "name": "type", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-type" @@ -3202,16 +3192,18 @@ "unionOf": [ { "type": "owl:Class", - "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity", - "name": "Activity" - }, - { - "type": "owl:Class", - "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship", - "name": "Relationship" + "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object", + "name": "Object" } ] }, + "@wtf_without_property": [ + { + "type": "owl:Class", + "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-intransitiveactivity", + "name": "IntransitiveActivity" + } + ], "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object-term", "range": { "type": "owl:Class", @@ -3680,7 +3672,7 @@ "range": { "type": "owl:Class", "unionOf": [ - "xsd:anyuri", + "xsd:anyURI", { "type": "owl:Class", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", @@ -3981,7 +3973,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-href", "range": { "type": "owl:Class", - "unionOf": "xsd:anyuri" + "unionOf": "xsd:anyURI" }, "name": "href", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-href" @@ -4544,7 +4536,7 @@ "https://www.w3.org/TR/activitystreams-vocabulary/km", "https://www.w3.org/TR/activitystreams-vocabulary/m", "https://www.w3.org/TR/activitystreams-vocabulary/miles", - "xsd:anyuri" + "xsd:anyURI" ] }, "name": "units", From 139dc3c5ea630b3de72f5b73a1513b130736a7f8 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 8 Dec 2018 17:50:26 +0100 Subject: [PATCH 02/14] Hack for "without property", begin resolving references. Also begin populating values in the intermediate definition. TODO: Replace the hack in the spec definition with something applicable RDF-wise (is there anything that permits this RDF wise?). --- tools/exp/rdf/data.go | 51 ++++++++++++++------- tools/exp/rdf/jsonld.go | 42 ++++++++++++++++++ tools/exp/rdf/parse.go | 84 ++++++++++++++++++++++++++++++++++- tools/exp/rdf/rdf.go | 16 +++++++ tools/exp/rdf/xsd/ontology.go | 67 +++++++++++++++++++++++++--- 5 files changed, 237 insertions(+), 23 deletions(-) diff --git a/tools/exp/rdf/data.go b/tools/exp/rdf/data.go index 86fad9e..b830d48 100644 --- a/tools/exp/rdf/data.go +++ b/tools/exp/rdf/data.go @@ -16,7 +16,17 @@ import ( // details. type ParsedVocabulary struct { Vocab Vocabulary - References map[string]Vocabulary + References map[string]*Vocabulary +} + +func (p *ParsedVocabulary) GetReference(uri string) *Vocabulary { + if p.References == nil { + p.References = make(map[string]*Vocabulary, 0) + } + if _, ok := p.References[uri]; !ok { + p.References[uri] = &Vocabulary{} + } + return p.References[uri] } func (p ParsedVocabulary) String() string { @@ -117,18 +127,16 @@ var ( // VocabularyType represents a single ActivityStream type in a vocabulary. type VocabularyType struct { - Name string - URI *url.URL - Notes string - DisjointWith []VocabularyReference - Extends []VocabularyReference // TODO: Object improperly extends Link - Properties []VocabularyReference // TODO: Check for duplication - WithoutProperties []VocabularyReference // TODO: Missing for IntransitiveActivity - Examples []VocabularyExample + Name string + URI *url.URL + Notes string + DisjointWith []VocabularyReference + Extends []VocabularyReference + Examples []VocabularyExample } func (v VocabularyType) String() string { - return fmt.Sprintf("Type=%s,%s,%s", v.Name, v.URI, v.Notes) + return fmt.Sprintf("Type=%s,%s,%s\n\tDJW=%s\n\tExt=%s\n\tEx=%s", v.Name, v.URI, v.Notes, v.DisjointWith, v.Extends, v.Examples) } func (v *VocabularyType) SetName(s string) { @@ -164,12 +172,13 @@ var ( // VocabularyProperty represents a single ActivityStream property type in a // vocabulary. type VocabularyProperty struct { - Name string - URI *url.URL - Notes string - Domain []VocabularyReference - Range []VocabularyReference - Examples []VocabularyExample + Name string + URI *url.URL + Notes string + Domain []VocabularyReference + Range []VocabularyReference + DoesNotApplyTo []VocabularyReference + Examples []VocabularyExample // SubpropertyOf is ignorable as long as data is set up correctly TODO: Is this still correct? SubpropertyOf VocabularyReference // Must be a VocabularyProperty Functional bool @@ -177,7 +186,7 @@ type VocabularyProperty struct { } func (v VocabularyProperty) String() string { - return fmt.Sprintf("Property=%s,%s,%s,%s,%s", v.Name, v.URI, v.Notes, v.Functional, v.NaturalLanguageMap) + return fmt.Sprintf("Property=%s,%s,%s\n\tD=%s\n\tR=%s\n\tEx=%s\n\tSub=%s\n\tDNApply=%s\n\tfunc=%t,natLangMap=%t", v.Name, v.URI, v.Notes, v.Domain, v.Range, v.Examples, v.SubpropertyOf, v.DoesNotApplyTo, v.Functional, v.NaturalLanguageMap) } func (v *VocabularyProperty) SetName(s string) { @@ -218,6 +227,10 @@ type VocabularyExample struct { Example interface{} } +func (v VocabularyExample) String() string { + return fmt.Sprintf("VocabularyExample: %s,%s,%s", v.Name, v.URI, v.Example) +} + func (v *VocabularyExample) SetName(s string) { v.Name = s } @@ -247,6 +260,10 @@ type VocabularyReference struct { Vocab string // If present, must match key in ParsedVocabulary.References } +func (v VocabularyReference) String() string { + return fmt.Sprintf("VocabularyReference: %s,%s,%s", v.Name, v.URI, v.Vocab) +} + func (v *VocabularyReference) SetName(s string) { v.Name = s } diff --git a/tools/exp/rdf/jsonld.go b/tools/exp/rdf/jsonld.go index bed5c01..42bcaa9 100644 --- a/tools/exp/rdf/jsonld.go +++ b/tools/exp/rdf/jsonld.go @@ -11,6 +11,13 @@ const ( IdActivityStreamsSpec = "id" ContainerSpec = "@container" IndexSpec = "@index" + // ActivityStreams specifically disallows the 'object' property on + // certain IntransitiveActivity and subtypes. There is no RDF mechanism + // to describe this. So this is a stupid hack, based on the assumption + // that no one -- W3C or otherwise -- will name a reserved word with a + // "@wtf_" prefix due to the reserved '@', the use of the unprofessional + // 'wtf', and a style-breaking underscore. + withoutPropertySpec = "@wtf_without_property" ) // jsonLDNodes contains the well-known set of nodes as defined by the JSON-LD @@ -55,6 +62,12 @@ func jsonLDNodes(r *RDFRegistry) []RDFNode { Name: IndexSpec, Delegate: &IndexLD{}, }, + &AliasedDelegate{ + Spec: "", + Alias: "", + Name: withoutPropertySpec, + Delegate: &withoutProperty{}, + }, } } @@ -149,3 +162,32 @@ func (i *IndexLD) Exit(key string, ctx *ParsingContext) (bool, error) { func (i *IndexLD) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) { return true, nil } + +var _ RDFNode = &withoutProperty{} + +type withoutProperty struct{} + +func (w *withoutProperty) Enter(key string, ctx *ParsingContext) (bool, error) { + ctx.Push() + ctx.Current = &VocabularyReference{} + return true, nil +} + +func (w *withoutProperty) Exit(key string, ctx *ParsingContext) (bool, error) { + i := ctx.Current + ctx.Pop() + vr, ok := i.(*VocabularyReference) + if !ok { + return true, fmt.Errorf("hacky withoutProperty exit did not get *rdf.VocabularyReference") + } + vp, ok := ctx.Current.(*VocabularyProperty) + if !ok { + return true, fmt.Errorf("hacky withoutProperty exit Current is not *rdf.VocabularyProperty") + } + vp.DoesNotApplyTo = append(vp.DoesNotApplyTo, *vr) + return true, nil +} + +func (w *withoutProperty) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) { + return true, fmt.Errorf("hacky withoutProperty cannot be applied") +} diff --git a/tools/exp/rdf/parse.go b/tools/exp/rdf/parse.go index caf4c26..06645ad 100644 --- a/tools/exp/rdf/parse.go +++ b/tools/exp/rdf/parse.go @@ -134,9 +134,92 @@ func ParseVocabulary(registry *RDFRegistry, input JSONLD) (vocabulary *ParsedVoc // hijack processing. nodes = append(jsonLDNodes(registry), nodes...) err = apply(nodes, input, ctx) + if err != nil { + return + } + err = resolveReferences(registry, ctx) return } +// resolveReferences ensures that all references mentioned have been +// successfully parsed, and if not attempts to search the ontologies for any +// values, types, and properties that need to be referenced. +// +// Currently, this is the only way that values are added to the +// ParsedVocabulary. +func resolveReferences(registry *RDFRegistry, ctx *ParsingContext) error { + vocabulary := ctx.Result + for _, t := range vocabulary.Vocab.Types { + for _, ref := range t.DisjointWith { + if err := resolveReference(ref, registry, ctx); err != nil { + return err + } + } + for _, ref := range t.Extends { + if err := resolveReference(ref, registry, ctx); err != nil { + return err + } + } + } + for _, p := range vocabulary.Vocab.Properties { + for _, ref := range p.Domain { + if err := resolveReference(ref, registry, ctx); err != nil { + return err + } + } + for _, ref := range p.Range { + if err := resolveReference(ref, registry, ctx); err != nil { + return err + } + } + for _, ref := range p.DoesNotApplyTo { + if err := resolveReference(ref, registry, ctx); err != nil { + return err + } + } + if len(p.SubpropertyOf.Name) > 0 { + if err := resolveReference(p.SubpropertyOf, registry, ctx); err != nil { + return err + } + } + } + return nil +} + +// resolveReference will attempt to resolve the reference by either finding it +// in the known References of the vocabulary, or load it from the registry. Will +// fail if a reference is not found. +func resolveReference(reference VocabularyReference, registry *RDFRegistry, ctx *ParsingContext) error { + fmt.Println(reference) + name := reference.Name + vocab := &ctx.Result.Vocab + if len(reference.Vocab) > 0 { + name = joinAlias(reference.Vocab, reference.Name) + url, e := registry.resolveAlias(reference.Vocab) + if e != nil { + return e + } + vocab = ctx.Result.GetReference(url) + } + if _, ok := vocab.Types[reference.Name]; ok { + return nil + } else if _, ok := vocab.Properties[reference.Name]; ok { + return nil + } else if _, ok := vocab.Values[reference.Name]; ok { + return nil + } else if n, e := registry.getNode(name); e != nil { + return e + } else { + applicable, e := n.Apply("", nil, ctx) + if !applicable { + return fmt.Errorf("cannot resolve reference with unapplicable node for %s", reference) + } else if e != nil { + return e + } + return nil + } +} + // apply takes a specification input to populate the ParsingContext, based on // the capabilities of the RDFNodes created from ontologies. func apply(nodes []RDFNode, input JSONLD, ctx *ParsingContext) error { @@ -148,7 +231,6 @@ func apply(nodes []RDFNode, input JSONLD, ctx *ParsingContext) error { } else { return err } - return nil } // Special processing: '@type' or 'type' if they are present if v, ok := input[JSON_LD_TYPE]; ok { diff --git a/tools/exp/rdf/rdf.go b/tools/exp/rdf/rdf.go index 8fe8d26..59c7f1f 100644 --- a/tools/exp/rdf/rdf.go +++ b/tools/exp/rdf/rdf.go @@ -41,6 +41,11 @@ func SplitAlias(s string) []string { } } +// joinAlias combines a string and prepends an RDF alias to it. +func joinAlias(alias, s string) string { + return fmt.Sprintf("%s%s%s", alias, ALIAS_DELIMITER, s) +} + // Ontology returns different RDF "actions" or "handlers" that are able to // interpret the schema definitions as actions upon a set of data, specific // for this ontology. @@ -255,3 +260,14 @@ func (r *RDFRegistry) getNode(s string) (n RDFNode, e error) { return } } + +// resolveAlias turns an alias into its full qualifier for the ontology. +// +// Package public. +func (r *RDFRegistry) resolveAlias(alias string) (url string, e error) { + var ok bool + if url, ok = r.aliases[alias]; !ok { + e = fmt.Errorf("registry cannot resolve alias %q", alias) + } + return +} diff --git a/tools/exp/rdf/xsd/ontology.go b/tools/exp/rdf/xsd/ontology.go index 8899c9a..5f86b32 100644 --- a/tools/exp/rdf/xsd/ontology.go +++ b/tools/exp/rdf/xsd/ontology.go @@ -3,25 +3,49 @@ package xsd import ( "fmt" "github.com/cjslep/activity/tools/exp/rdf" + "net/url" "strings" ) +const ( + xmlSpec = "http://www.w3.org/2001/XMLSchema#" + anyURISpec = "anyURI" +) + type XMLOntology struct{} func (o *XMLOntology) SpecURI() string { - return "http://www.w3.org/2001/XMLSchema#" + return xmlSpec } func (o *XMLOntology) Load() ([]rdf.RDFNode, error) { - return nil, nil + return o.LoadAsAlias("") } func (o *XMLOntology) LoadAsAlias(s string) ([]rdf.RDFNode, error) { - return nil, nil + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: anyURISpec, + Delegate: &anyURI{}, + }, + }, nil } func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, error) { - return nil, nil + switch name { + case anyURISpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &anyURI{}, + }, + }, nil + } + return nil, fmt.Errorf("xml ontology cannot find %q to alias to %q", name, alias) } func (o *XMLOntology) LoadElement(name string, payload map[string]interface{}) ([]rdf.RDFNode, error) { @@ -31,7 +55,40 @@ func (o *XMLOntology) LoadElement(name string, payload map[string]interface{}) ( func (o *XMLOntology) GetByName(name string) (rdf.RDFNode, error) { name = strings.TrimPrefix(name, o.SpecURI()) switch name { - // TODO + case anyURISpec: + return &anyURI{}, nil } return nil, fmt.Errorf("xsd ontology could not find node for name %s", name) } + +var _ rdf.RDFNode = &anyURI{} + +type anyURI struct{} + +func (a *anyURI) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd anyURI cannot be entered") +} + +func (a *anyURI) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd anyURI cannot be exited") +} + +func (a *anyURI) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { + v := ctx.Result.GetReference(xmlSpec) + if len(v.Values[anyURISpec].Name) == 0 { + u, err := url.Parse(xmlSpec + anyURISpec) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: "anyURI", + URI: u, + DefinitionType: "*url.URL", + Zero: "&url.URL{}", + } + if err = v.SetValue(anyURISpec, val); err != nil { + return true, err + } + } + return true, nil +} From beb44b1bde64fde970925461baf33b97d5a4a90d Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 8 Dec 2018 18:57:20 +0100 Subject: [PATCH 03/14] Fix more items in the specification --- tools/exp/spec.json | 67 +++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/tools/exp/spec.json b/tools/exp/spec.json index 32b7cbc..de20239 100644 --- a/tools/exp/spec.json +++ b/tools/exp/spec.json @@ -2688,11 +2688,18 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-instrument", "range": { "type": "owl:Class", - "unionOf": { - "type": "owl:Class", - "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object", - "name": "Object | Link" - } + "unionOf": [ + { + "type": "owl:Class", + "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object", + "name": "Object" + }, + { + "type": "owl:Class", + "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", + "name": "Link" + } + ] }, "name": "instrument", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-instrument" @@ -2794,11 +2801,18 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-location", "range": { "type": "owl:Class", - "unionOf": { - "type": "owl:Class", - "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object", - "name": "Object | Link" - } + "unionOf": [ + { + "type": "owl:Class", + "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object", + "name": "Object" + }, + { + "type": "owl:Class", + "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", + "name": "Link" + } + ] }, "name": "location", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-location" @@ -3012,7 +3026,7 @@ "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", "name": "Link" }, - "xsd:datetime", + "xsd:dateTime", "xsd:boolean" ] }, @@ -3812,7 +3826,7 @@ "type": "owl:Class", "unionOf": [ "xsd:string", - "rdf:langstring" + "rdf:langString" ] }, "name": "content", @@ -3869,7 +3883,7 @@ "type": "owl:Class", "unionOf": [ "xsd:string", - "rdf:langstring" + "rdf:langString" ] }, "name": "name", @@ -3939,7 +3953,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-height", "range": { "type": "owl:Class", - "unionOf": "xsd:nonnegativeinteger" + "unionOf": "xsd:nonNegativeInteger" }, "name": "height", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-height" @@ -4213,7 +4227,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-endtime", "range": { "type": "owl:Class", - "unionOf": "xsd:datetime" + "unionOf": "xsd:dateTime" }, "name": "endTime", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-endtime" @@ -4248,7 +4262,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published", "range": { "type": "owl:Class", - "unionOf": "xsd:datetime" + "unionOf": "xsd:dateTime" }, "name": "published", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published" @@ -4283,7 +4297,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-starttime", "range": { "type": "owl:Class", - "unionOf": "xsd:datetime" + "unionOf": "xsd:dateTime" }, "name": "startTime", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-starttime" @@ -4398,7 +4412,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-startindex", "range": { "type": "owl:Class", - "unionOf": "xsd:nonnegativeinteger" + "unionOf": "xsd:nonNegativeInteger" }, "name": "startIndex", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-startindex" @@ -4449,7 +4463,7 @@ "type": "owl:Class", "unionOf": [ "xsd:string", - "rdf:langstring" + "rdf:langString" ] }, "name": "summary", @@ -4493,7 +4507,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-totalitems", "range": { "type": "owl:Class", - "unionOf": "xsd:nonnegativeinteger" + "unionOf": "xsd:nonNegativeInteger" }, "name": "totalItems", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-totalitems" @@ -4530,12 +4544,7 @@ "range": { "type": "owl:Class", "unionOf": [ - "https://www.w3.org/TR/activitystreams-vocabulary/cm", - "https://www.w3.org/TR/activitystreams-vocabulary/feet", - "https://www.w3.org/TR/activitystreams-vocabulary/inches", - "https://www.w3.org/TR/activitystreams-vocabulary/km", - "https://www.w3.org/TR/activitystreams-vocabulary/m", - "https://www.w3.org/TR/activitystreams-vocabulary/miles", + "xsd:string", "xsd:anyURI" ] }, @@ -4572,7 +4581,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-updated", "range": { "type": "owl:Class", - "unionOf": "xsd:datetime" + "unionOf": "xsd:dateTime" }, "name": "updated", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-updated" @@ -4606,7 +4615,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-width", "range": { "type": "owl:Class", - "unionOf": "xsd:nonnegativeinteger" + "unionOf": "xsd:nonNegativeInteger" }, "name": "width", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-width" @@ -4809,7 +4818,7 @@ "isDefinedBy": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-deleted", "range": { "type": "owl:Class", - "unionOf": "xsd:datetime" + "unionOf": "xsd:dateTime" }, "name": "deleted", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-deleted" From dc2ce18fd780d5c1b0beea3289a3b6f97b6bff97 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 8 Dec 2018 18:57:40 +0100 Subject: [PATCH 04/14] Finish adding values and nat lang maps. --- tools/exp/rdf/ontology.go | 29 +++- tools/exp/rdf/parse.go | 1 - tools/exp/rdf/xsd/ontology.go | 306 +++++++++++++++++++++++++++++++++- 3 files changed, 324 insertions(+), 12 deletions(-) diff --git a/tools/exp/rdf/ontology.go b/tools/exp/rdf/ontology.go index 2aa7994..7c05481 100644 --- a/tools/exp/rdf/ontology.go +++ b/tools/exp/rdf/ontology.go @@ -7,12 +7,14 @@ import ( const ( rdfSpec = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" - langstringSpec = "langstring" + langstringSpec = "langString" propertySpec = "Property" valueSpec = "value" ) -type RDFOntology struct{} +type RDFOntology struct { + alias string +} func (o *RDFOntology) SpecURI() string { return rdfSpec @@ -23,12 +25,13 @@ func (o *RDFOntology) Load() ([]RDFNode, error) { } func (o *RDFOntology) LoadAsAlias(s string) ([]RDFNode, error) { + o.alias = s return []RDFNode{ &AliasedDelegate{ Spec: rdfSpec, Alias: s, Name: langstringSpec, - Delegate: &langstring{}, + Delegate: &langstring{alias: o.alias}, }, &AliasedDelegate{ Spec: rdfSpec, @@ -53,7 +56,7 @@ func (o *RDFOntology) LoadSpecificAsAlias(alias, name string) ([]RDFNode, error) Spec: "", Alias: "", Name: alias, - Delegate: &langstring{}, + Delegate: &langstring{alias: o.alias}, }, }, nil case propertySpec: @@ -86,7 +89,7 @@ func (o *RDFOntology) GetByName(name string) (RDFNode, error) { name = strings.TrimPrefix(name, o.SpecURI()) switch name { case langstringSpec: - return &langstring{}, nil + return &langstring{alias: o.alias}, nil case propertySpec: return &property{}, nil case valueSpec: @@ -97,7 +100,9 @@ func (o *RDFOntology) GetByName(name string) (RDFNode, error) { var _ RDFNode = &langstring{} -type langstring struct{} +type langstring struct { + alias string +} func (l *langstring) Enter(key string, ctx *ParsingContext) (bool, error) { return true, fmt.Errorf("rdf langstring cannot be entered") @@ -108,8 +113,16 @@ func (l *langstring) Exit(key string, ctx *ParsingContext) (bool, error) { } func (l *langstring) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) { - // TODO: Act as value - return true, fmt.Errorf("rdf langstring cannot be applied") + for k, p := range ctx.Result.Vocab.Properties { + for _, ref := range p.Range { + if ref.Name == langstringSpec && ref.Vocab == l.alias { + p.NaturalLanguageMap = true + ctx.Result.Vocab.Properties[k] = p + break + } + } + } + return true, nil } var _ RDFNode = &property{} diff --git a/tools/exp/rdf/parse.go b/tools/exp/rdf/parse.go index 06645ad..3147306 100644 --- a/tools/exp/rdf/parse.go +++ b/tools/exp/rdf/parse.go @@ -190,7 +190,6 @@ func resolveReferences(registry *RDFRegistry, ctx *ParsingContext) error { // in the known References of the vocabulary, or load it from the registry. Will // fail if a reference is not found. func resolveReference(reference VocabularyReference, registry *RDFRegistry, ctx *ParsingContext) error { - fmt.Println(reference) name := reference.Name vocab := &ctx.Result.Vocab if len(reference.Vocab) > 0 { diff --git a/tools/exp/rdf/xsd/ontology.go b/tools/exp/rdf/xsd/ontology.go index 5f86b32..98447cf 100644 --- a/tools/exp/rdf/xsd/ontology.go +++ b/tools/exp/rdf/xsd/ontology.go @@ -8,8 +8,14 @@ import ( ) const ( - xmlSpec = "http://www.w3.org/2001/XMLSchema#" - anyURISpec = "anyURI" + xmlSpec = "http://www.w3.org/2001/XMLSchema#" + anyURISpec = "anyURI" + dateTimeSpec = "dateTime" + floatSpec = "float" + stringSpec = "string" + booleanSpec = "boolean" + nonNegativeIntegerSpec = "nonNegativeInteger" + durationSpec = "duration" ) type XMLOntology struct{} @@ -30,6 +36,42 @@ func (o *XMLOntology) LoadAsAlias(s string) ([]rdf.RDFNode, error) { Name: anyURISpec, Delegate: &anyURI{}, }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: dateTimeSpec, + Delegate: &dateTime{}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: floatSpec, + Delegate: &float{}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: stringSpec, + Delegate: &xmlString{}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: booleanSpec, + Delegate: &boolean{}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: nonNegativeIntegerSpec, + Delegate: &nonNegativeInteger{}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: durationSpec, + Delegate: &duration{}, + }, }, nil } @@ -44,6 +86,60 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Delegate: &anyURI{}, }, }, nil + case dateTimeSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &dateTime{}, + }, + }, nil + case floatSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &float{}, + }, + }, nil + case stringSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &xmlString{}, + }, + }, nil + case booleanSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &boolean{}, + }, + }, nil + case nonNegativeIntegerSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &nonNegativeInteger{}, + }, + }, nil + case durationSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &duration{}, + }, + }, nil } return nil, fmt.Errorf("xml ontology cannot find %q to alias to %q", name, alias) } @@ -57,6 +153,18 @@ func (o *XMLOntology) GetByName(name string) (rdf.RDFNode, error) { switch name { case anyURISpec: return &anyURI{}, nil + case dateTimeSpec: + return &dateTime{}, nil + case floatSpec: + return &float{}, nil + case stringSpec: + return &xmlString{}, nil + case booleanSpec: + return &boolean{}, nil + case nonNegativeIntegerSpec: + return &nonNegativeInteger{}, nil + case durationSpec: + return &duration{}, nil } return nil, fmt.Errorf("xsd ontology could not find node for name %s", name) } @@ -81,7 +189,7 @@ func (a *anyURI) Apply(key string, value interface{}, ctx *rdf.ParsingContext) ( return true, err } val := &rdf.VocabularyValue{ - Name: "anyURI", + Name: anyURISpec, URI: u, DefinitionType: "*url.URL", Zero: "&url.URL{}", @@ -92,3 +200,195 @@ func (a *anyURI) Apply(key string, value interface{}, ctx *rdf.ParsingContext) ( } return true, nil } + +var _ rdf.RDFNode = &dateTime{} + +type dateTime struct{} + +func (d *dateTime) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd dateTime cannot be entered") +} + +func (d *dateTime) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd dateTime cannot be exited") +} + +func (d *dateTime) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { + v := ctx.Result.GetReference(xmlSpec) + if len(v.Values[dateTimeSpec].Name) == 0 { + u, err := url.Parse(xmlSpec + dateTimeSpec) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: dateTimeSpec, + URI: u, + DefinitionType: "time.Time", + Zero: "&time.Time{}", + } + if err = v.SetValue(dateTimeSpec, val); err != nil { + return true, err + } + } + return true, nil +} + +var _ rdf.RDFNode = &float{} + +type float struct{} + +func (f *float) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd float cannot be entered") +} + +func (f *float) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd float cannot be exited") +} + +func (f *float) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { + v := ctx.Result.GetReference(xmlSpec) + if len(v.Values[floatSpec].Name) == 0 { + u, err := url.Parse(xmlSpec + floatSpec) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: floatSpec, + URI: u, + DefinitionType: "float32", + Zero: "0.0", + } + if err = v.SetValue(floatSpec, val); err != nil { + return true, err + } + } + return true, nil +} + +var _ rdf.RDFNode = &xmlString{} + +type xmlString struct{} + +func (*xmlString) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd string cannot be entered") +} + +func (*xmlString) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd string cannot be exited") +} + +func (*xmlString) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { + v := ctx.Result.GetReference(xmlSpec) + if len(v.Values[stringSpec].Name) == 0 { + u, err := url.Parse(xmlSpec + stringSpec) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: stringSpec, + URI: u, + DefinitionType: "string", + Zero: "\"\"", + } + if err = v.SetValue(stringSpec, val); err != nil { + return true, err + } + } + return true, nil +} + +var _ rdf.RDFNode = &boolean{} + +type boolean struct{} + +func (*boolean) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd boolean cannot be entered") +} + +func (*boolean) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd boolean cannot be exited") +} + +func (*boolean) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { + v := ctx.Result.GetReference(xmlSpec) + if len(v.Values[booleanSpec].Name) == 0 { + u, err := url.Parse(xmlSpec + booleanSpec) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: booleanSpec, + URI: u, + DefinitionType: "string", + Zero: "\"\"", + } + if err = v.SetValue(booleanSpec, val); err != nil { + return true, err + } + } + return true, nil +} + +var _ rdf.RDFNode = &nonNegativeInteger{} + +type nonNegativeInteger struct{} + +func (*nonNegativeInteger) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd nonNegativeInteger cannot be entered") +} + +func (*nonNegativeInteger) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd nonNegativeInteger cannot be exited") +} + +func (*nonNegativeInteger) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { + v := ctx.Result.GetReference(xmlSpec) + if len(v.Values[nonNegativeIntegerSpec].Name) == 0 { + u, err := url.Parse(xmlSpec + nonNegativeIntegerSpec) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: nonNegativeIntegerSpec, + URI: u, + DefinitionType: "int", + Zero: "0", + } + if err = v.SetValue(nonNegativeIntegerSpec, val); err != nil { + return true, err + } + } + return true, nil +} + +var _ rdf.RDFNode = &duration{} + +type duration struct{} + +func (*duration) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd duration cannot be entered") +} + +func (*duration) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { + return true, fmt.Errorf("xsd duration cannot be exited") +} + +func (*duration) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { + v := ctx.Result.GetReference(xmlSpec) + if len(v.Values[durationSpec].Name) == 0 { + u, err := url.Parse(xmlSpec + durationSpec) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: durationSpec, + URI: u, + DefinitionType: "time.Duration", + Zero: "time.Duration(0)", + } + if err = v.SetValue(durationSpec, val); err != nil { + return true, err + } + } + return true, nil +} From 069b8de8209ed052ce2cffc77468628886c41200 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 8 Dec 2018 23:05:02 +0100 Subject: [PATCH 05/14] Add initial convert (exp is broken at this commit) Still need to flesh out the types for conversion. Also still need to add the serialize and deserialize calls for individual types. Finally, will need to put the finishing touches on writing the output files in the desired directories. Then the experimental tool will be ready for end to end testing. --- tools/exp/convert/convert.go | 209 +++++++++++++++++++++++++++++++++ tools/exp/props/funcprop.go | 2 +- tools/exp/props/nonfuncprop.go | 2 +- tools/exp/props/property.go | 15 ++- tools/exp/rdf/data.go | 24 ++-- tools/exp/rdf/parse.go | 6 +- tools/exp/rdf/rdf.go | 4 +- tools/exp/types/type.go | 2 +- 8 files changed, 237 insertions(+), 27 deletions(-) create mode 100644 tools/exp/convert/convert.go diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go new file mode 100644 index 0000000..2268f09 --- /dev/null +++ b/tools/exp/convert/convert.go @@ -0,0 +1,209 @@ +package convert + +import ( + "fmt" + "github.com/cjslep/activity/tools/exp/props" + "github.com/cjslep/activity/tools/exp/rdf" + "github.com/cjslep/activity/tools/exp/types" + "strings" +) + +type vocabulary struct { + Kinds map[string]*props.Kind + FProps map[string]*props.FunctionalPropertyGenerator + NFProps map[string]*props.NonFunctionalPropertyGenerator + Types map[string]*types.TypeGenerator +} + +func newVocabulary() vocabulary { + return vocabulary{ + Kinds: make(map[string]*props.Kind, 0), + FProps: make(map[string]*props.FunctionalPropertyGenerator, 0), + NFProps: make(map[string]*props.NonFunctionalPropertyGenerator, 0), + Types: make(map[string]*types.TypeGenerator, 0), + } +} + +type PropertyPackagePolicy int + +const ( + FlatUnderRoot PropertyPackagePolicy = iota + IndividualUnderRoot +) + +type Converter struct { + Registry *rdf.RDFRegistry + VocabularyRoot string + PropertyPackagePolicy PropertyPackagePolicy + PropertyPackageRoot string +} + +func (c Converter) convert(p *rdf.ParsedVocabulary) (v vocabulary, e error) { + v = newVocabulary() + for k, val := range p.Vocab.Values { + v.Kinds[k] = c.convertValue(val) + } + for k, prop := range p.Vocab.Properties { + if prop.Functional { + v.FProps[k], e = c.convertFunctionalProperty(prop, v.Kinds, p.Vocab, p.References) + } else { + v.NFProps[k], e = c.convertNonFunctionalProperty(prop, v.Kinds, p.Vocab, p.References) + } + if e != nil { + return + } + } + // TODO: Types in dependency order. + return +} + +func (c Converter) convertFunctionalProperty(p rdf.VocabularyProperty, + kinds map[string]*props.Kind, + v rdf.Vocabulary, + refs map[string]*rdf.Vocabulary) (fp *props.FunctionalPropertyGenerator, e error) { + var k []props.Kind + k, e = c.propertyKinds(p, kinds, v, refs) + if e != nil { + return + } + var pkg string + pkg, e = c.propertyPackageName(p) + if e != nil { + return + } + fp = props.NewFunctionalPropertyGenerator( + pkg, + c.toIdentifier(p), + k, + p.NaturalLanguageMap) + return +} + +func (c Converter) convertNonFunctionalProperty(p rdf.VocabularyProperty, + kinds map[string]*props.Kind, + v rdf.Vocabulary, + refs map[string]*rdf.Vocabulary) (nfp *props.NonFunctionalPropertyGenerator, e error) { + var k []props.Kind + k, e = c.propertyKinds(p, kinds, v, refs) + if e != nil { + return + } + var pkg string + pkg, e = c.propertyPackageName(p) + if e != nil { + return + } + nfp = props.NewNonFunctionalPropertyGenerator( + pkg, + c.toIdentifier(p), + k, + p.NaturalLanguageMap) + return +} + +func (c Converter) convertValue(v rdf.VocabularyValue) (k *props.Kind) { + k = &props.Kind{ + Name: c.toIdentifier(v), + ConcreteKind: v.DefinitionType, + Nilable: c.isNilable(v.DefinitionType), + SerializeFn: v.SerializeFn, + DeserializeFn: v.DeserializeFn, + LessFn: v.LessFn, + } + return +} + +func (c Converter) convertTypeToKind(v rdf.VocabularyType) (k *props.Kind) { + k = &props.Kind{ + Name: c.toIdentifier(v), + ConcreteKind: strings.Title(v.Name), + Nilable: true, + // TODO + SerializeFn: types.SerializeFn, + DeserializeFn: types.DeserializeFn, + LessFn: types.LessFn, + } + return +} + +func (c Converter) propertyKinds(v rdf.VocabularyProperty, + kinds map[string]*props.Kind, + vocab rdf.Vocabulary, + refs map[string]*rdf.Vocabulary) (k []props.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 { + k = append(k, *c.convertTypeToKind(t)) + } + } 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 { + k = append(k, *c.convertTypeToKind(t)) + } + } else { + // It is a Value of the vocabulary + k = append(k, *c.convertValue(val)) + } + } + } + return +} + +func (c Converter) propertyPackageName(v rdf.VocabularyProperty) (pkg string, e error) { + switch c.PropertyPackagePolicy { + case FlatUnderRoot: + pkg = c.PropertyPackageRoot + case IndividualUnderRoot: + pkg = v.Name + default: + e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) + } + return +} + +// TODO: Use this? +func (c Converter) propertyPackageFile(v rdf.VocabularyProperty) (pkg string, e error) { + switch c.PropertyPackagePolicy { + case FlatUnderRoot: + pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.PropertyPackageRoot) + case IndividualUnderRoot: + pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.PropertyPackageRoot, v.Name) + default: + e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) + } + return +} + +func (c Converter) toIdentifier(n rdf.NameGetter) props.Identifier { + return props.Identifier{ + LowerName: n.GetName(), + CamelName: strings.Title(n.GetName()), + } +} + +func (c Converter) isNilable(goType string) bool { + return goType[0] == '*' +} diff --git a/tools/exp/props/funcprop.go b/tools/exp/props/funcprop.go index 4b8ea54..3eb890a 100644 --- a/tools/exp/props/funcprop.go +++ b/tools/exp/props/funcprop.go @@ -2,8 +2,8 @@ package props import ( "fmt" + "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" - "github.com/go-fed/activity/tools/exp/codegen" "sync" ) diff --git a/tools/exp/props/nonfuncprop.go b/tools/exp/props/nonfuncprop.go index a67ffde..e55a523 100644 --- a/tools/exp/props/nonfuncprop.go +++ b/tools/exp/props/nonfuncprop.go @@ -2,8 +2,8 @@ package props import ( "fmt" + "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" - "github.com/go-fed/activity/tools/exp/codegen" "sync" ) diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index 994b6de..60b2bdb 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -2,8 +2,8 @@ package props import ( "fmt" + "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" - "github.com/go-fed/activity/tools/exp/codegen" ) const ( @@ -59,13 +59,12 @@ type Identifier struct { // deserialize such types, compare the types, and other meta-information to use // during Go code generation. type Kind struct { - Name Identifier - ConcreteKind string - Nilable bool - HasNaturalLanguageMap bool - SerializeFn codegen.Function - DeserializeFn codegen.Function - LessFn codegen.Function + Name Identifier + ConcreteKind string + Nilable bool + SerializeFn codegen.Function + DeserializeFn codegen.Function + LessFn codegen.Function } // PropertyGenerator is a common base struct used in both Functional and diff --git a/tools/exp/rdf/data.go b/tools/exp/rdf/data.go index b830d48..8b90c20 100644 --- a/tools/exp/rdf/data.go +++ b/tools/exp/rdf/data.go @@ -3,6 +3,7 @@ package rdf import ( "bytes" "fmt" + "github.com/cjslep/activity/tools/exp/codegen" "net/url" ) @@ -99,6 +100,9 @@ type VocabularyValue struct { URI *url.URL DefinitionType string Zero string + SerializeFn codegen.Function + DeserializeFn codegen.Function + LessFn codegen.Function } func (v VocabularyValue) String() string { @@ -109,7 +113,7 @@ func (v *VocabularyValue) SetName(s string) { v.Name = s } -func (v *VocabularyValue) GetName() string { +func (v VocabularyValue) GetName() string { return v.Name } @@ -121,7 +125,7 @@ func (v *VocabularyValue) SetURI(s string) error { var ( _ NameSetter = &VocabularyValue{} - _ nameGetter = &VocabularyValue{} + _ NameGetter = &VocabularyValue{} _ URISetter = &VocabularyValue{} ) @@ -143,7 +147,7 @@ func (v *VocabularyType) SetName(s string) { v.Name = s } -func (v *VocabularyType) GetName() string { +func (v VocabularyType) GetName() string { return v.Name } @@ -163,7 +167,7 @@ func (v *VocabularyType) AddExample(e *VocabularyExample) { var ( _ NameSetter = &VocabularyType{} - _ nameGetter = &VocabularyType{} + _ NameGetter = &VocabularyType{} _ URISetter = &VocabularyType{} _ NotesSetter = &VocabularyType{} _ ExampleAdder = &VocabularyType{} @@ -193,7 +197,7 @@ func (v *VocabularyProperty) SetName(s string) { v.Name = s } -func (v *VocabularyProperty) GetName() string { +func (v VocabularyProperty) GetName() string { return v.Name } @@ -213,7 +217,7 @@ func (v *VocabularyProperty) AddExample(e *VocabularyExample) { var ( _ NameSetter = &VocabularyProperty{} - _ nameGetter = &VocabularyProperty{} + _ NameGetter = &VocabularyProperty{} _ URISetter = &VocabularyProperty{} _ NotesSetter = &VocabularyProperty{} _ ExampleAdder = &VocabularyProperty{} @@ -235,7 +239,7 @@ func (v *VocabularyExample) SetName(s string) { v.Name = s } -func (v *VocabularyExample) GetName() string { +func (v VocabularyExample) GetName() string { return v.Name } @@ -247,7 +251,7 @@ func (v *VocabularyExample) SetURI(s string) error { var ( _ NameSetter = &VocabularyExample{} - _ nameGetter = &VocabularyExample{} + _ NameGetter = &VocabularyExample{} _ URISetter = &VocabularyExample{} ) @@ -268,7 +272,7 @@ func (v *VocabularyReference) SetName(s string) { v.Name = s } -func (v *VocabularyReference) GetName() string { +func (v VocabularyReference) GetName() string { return v.Name } @@ -280,6 +284,6 @@ func (v *VocabularyReference) SetURI(s string) error { var ( _ NameSetter = &VocabularyReference{} - _ nameGetter = &VocabularyReference{} + _ NameGetter = &VocabularyReference{} _ URISetter = &VocabularyReference{} ) diff --git a/tools/exp/rdf/parse.go b/tools/exp/rdf/parse.go index 3147306..78b188e 100644 --- a/tools/exp/rdf/parse.go +++ b/tools/exp/rdf/parse.go @@ -73,7 +73,7 @@ func (p *ParsingContext) Push() { func (p *ParsingContext) Pop() { p.Current = p.Stack[0] p.Stack = p.Stack[1:] - if ng, ok := p.Current.(nameGetter); ok { + if ng, ok := p.Current.(NameGetter); ok { p.Name = ng.GetName() } } @@ -92,7 +92,7 @@ type NameSetter interface { SetName(string) } -type nameGetter interface { +type NameGetter interface { GetName() string } @@ -194,7 +194,7 @@ func resolveReference(reference VocabularyReference, registry *RDFRegistry, ctx vocab := &ctx.Result.Vocab if len(reference.Vocab) > 0 { name = joinAlias(reference.Vocab, reference.Name) - url, e := registry.resolveAlias(reference.Vocab) + url, e := registry.ResolveAlias(reference.Vocab) if e != nil { return e } diff --git a/tools/exp/rdf/rdf.go b/tools/exp/rdf/rdf.go index 59c7f1f..0d42074 100644 --- a/tools/exp/rdf/rdf.go +++ b/tools/exp/rdf/rdf.go @@ -262,9 +262,7 @@ func (r *RDFRegistry) getNode(s string) (n RDFNode, e error) { } // resolveAlias turns an alias into its full qualifier for the ontology. -// -// Package public. -func (r *RDFRegistry) resolveAlias(alias string) (url string, e error) { +func (r *RDFRegistry) ResolveAlias(alias string) (url string, e error) { var ok bool if url, ok = r.aliases[alias]; !ok { e = fmt.Errorf("registry cannot resolve alias %q", alias) diff --git a/tools/exp/types/type.go b/tools/exp/types/type.go index 9f61385..3f861a6 100644 --- a/tools/exp/types/type.go +++ b/tools/exp/types/type.go @@ -2,8 +2,8 @@ package types import ( "fmt" + "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" - "github.com/go-fed/activity/tools/exp/codegen" "sync" ) From fe8b4e72616c63c50a467e89ecae4ff245904845 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sun, 9 Dec 2018 11:40:59 +0100 Subject: [PATCH 06/14] Add type conversion (still broken at this commit) --- tools/exp/convert/convert.go | 159 +++++++++++++++++++++++++++++++++-- tools/exp/types/type.go | 6 ++ 2 files changed, 156 insertions(+), 9 deletions(-) diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index 2268f09..558766b 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -5,6 +5,7 @@ import ( "github.com/cjslep/activity/tools/exp/props" "github.com/cjslep/activity/tools/exp/rdf" "github.com/cjslep/activity/tools/exp/types" + "github.com/dave/jennifer/jen" "strings" ) @@ -27,8 +28,17 @@ func newVocabulary() vocabulary { type PropertyPackagePolicy int const ( - FlatUnderRoot PropertyPackagePolicy = iota - IndividualUnderRoot + PropertyFlatUnderRoot PropertyPackagePolicy = iota + PropertyIndividualUnderRoot + PropertyFlatUnderVocabularyRoot +) + +type TypePackagePolicy int + +const ( + TypeFlatUnderRoot TypePackagePolicy = iota + TypeIndividualUnderRoot + TypeFlatUnderVocabularyRoot ) type Converter struct { @@ -36,9 +46,21 @@ type Converter struct { VocabularyRoot string PropertyPackagePolicy PropertyPackagePolicy PropertyPackageRoot string + TypePackageRoot string + TypePackagePolicy TypePackagePolicy } -func (c Converter) convert(p *rdf.ParsedVocabulary) (v vocabulary, e error) { +func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*jen.File, e error) { + var v vocabulary + v, e = c.convertVocabulary(p) + if e != nil { + return + } + // TODO + return +} + +func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e error) { v = newVocabulary() for k, val := range p.Vocab.Values { v.Kinds[k] = c.convertValue(val) @@ -53,7 +75,93 @@ func (c Converter) convert(p *rdf.ParsedVocabulary) (v vocabulary, e error) { return } } - // TODO: Types in dependency order. + // 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 _, t := range allTypes { + var tg *types.TypeGenerator + tg, e = c.convertType(t, p.Vocab, v.FProps, v.NFProps, v.Types) + if e != nil { + return + } + v.Types[t.Name] = tg + } + if stuck { + e = fmt.Errorf("converting types got stuck in dependency cycle") + return + } + } + return +} + +func (c Converter) convertType(t rdf.VocabularyType, + v rdf.Vocabulary, + existingFProps map[string]*props.FunctionalPropertyGenerator, + existingNFProps map[string]*props.NonFunctionalPropertyGenerator, + existingTypes map[string]*types.TypeGenerator) (tg *types.TypeGenerator, e error) { + // Determine the types package name + var pkg string + pkg, e = c.typePackageName(t) + if e != nil { + return + } + // Determine the properties for this type + var p []types.Property + for k, prop := range v.Properties { + for _, ref := range prop.Domain { + if len(ref.Vocab) != 0 { + e = fmt.Errorf("unhandled use case: property domain outside its vocabulary") + return + } else if ref.Name == t.Name { + if prop.Functional { + p = append(p, existingFProps[prop.Name]) + } else { + p = append(p, existingNFProps[prop.Name]) + } + break + } + } + } + // Determine what this type extends + var ext []*types.TypeGenerator + for _, ex := range t.Extends { + if len(ex.Vocab) != 0 { + // TODO: This should be fixed + e = fmt.Errorf("unhandled use case: type extends another type outside its vocabulary") + return + } else { + ext = append(ext, existingTypes[ex.Name]) + } + } + tg, e = types.NewTypeGenerator( + pkg, + strings.Title(t.Name), // Must be same in convertTypeToKind + t.Notes, + p, + ext, + nil) + if e != nil { + return + } + // Apply disjoint if both sides are available. + for _, disj := range t.DisjointWith { + if len(disj.Vocab) != 0 { + // TODO: This should be fixed + e = fmt.Errorf("unhandled use case: type is disjoint with another type outside its vocabulary") + return + } else if disjointType, ok := existingTypes[disj.Name]; ok { + disjointType.AddDisjoint(tg) + tg.AddDisjoint(disjointType) + } + } return } @@ -116,7 +224,7 @@ func (c Converter) convertValue(v rdf.VocabularyValue) (k *props.Kind) { func (c Converter) convertTypeToKind(v rdf.VocabularyType) (k *props.Kind) { k = &props.Kind{ Name: c.toIdentifier(v), - ConcreteKind: strings.Title(v.Name), + ConcreteKind: strings.Title(v.Name), // Must be same in convertType Nilable: true, // TODO SerializeFn: types.SerializeFn, @@ -172,25 +280,58 @@ func (c Converter) propertyKinds(v rdf.VocabularyProperty, return } +func (c Converter) typePackageName(v rdf.VocabularyType) (pkg string, e error) { + switch c.TypePackagePolicy { + case TypeFlatUnderRoot: + pkg = c.TypePackageRoot + case TypeIndividualUnderRoot: + pkg = v.Name + case TypeFlatUnderVocabularyRoot: + pkg = c.VocabularyRoot + default: + e = fmt.Errorf("unrecognized TypePackagePolicy: %v", c.TypePackagePolicy) + } + return +} + func (c Converter) propertyPackageName(v rdf.VocabularyProperty) (pkg string, e error) { switch c.PropertyPackagePolicy { - case FlatUnderRoot: + case PropertyFlatUnderRoot: pkg = c.PropertyPackageRoot - case IndividualUnderRoot: + case PropertyIndividualUnderRoot: pkg = v.Name + case PropertyFlatUnderVocabularyRoot: + pkg = c.VocabularyRoot default: e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) } return } +// TODO: Use this? +func (c Converter) typePackageFile(v rdf.VocabularyType) (pkg string, e error) { + switch c.TypePackagePolicy { + case TypeFlatUnderRoot: + pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.TypePackageRoot) + case TypeIndividualUnderRoot: + pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.TypePackageRoot, v.Name) + case TypeFlatUnderVocabularyRoot: + pkg = c.VocabularyRoot + default: + e = fmt.Errorf("unrecognized TypePackagePolicy: %v", c.TypePackagePolicy) + } + return +} + // TODO: Use this? func (c Converter) propertyPackageFile(v rdf.VocabularyProperty) (pkg string, e error) { switch c.PropertyPackagePolicy { - case FlatUnderRoot: + case PropertyFlatUnderRoot: pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.PropertyPackageRoot) - case IndividualUnderRoot: + case PropertyIndividualUnderRoot: pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.PropertyPackageRoot, v.Name) + case PropertyFlatUnderVocabularyRoot: + pkg = c.VocabularyRoot default: e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) } diff --git a/tools/exp/types/type.go b/tools/exp/types/type.go index 3f861a6..ee19ec5 100644 --- a/tools/exp/types/type.go +++ b/tools/exp/types/type.go @@ -83,6 +83,11 @@ func NewTypeGenerator(packageName, typeName, comment string, return t, nil } +// AddDisjoint adds another TypeGenerator that is disjoint to this one. +func (t *TypeGenerator) AddDisjoint(o *TypeGenerator) { + t.disjoint = append(t.disjoint, o) +} + // Comment returns the comment for this type. func (t *TypeGenerator) Comment() string { return t.comment @@ -134,6 +139,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct { t.cacheOnce.Do(func() { members := make([]jen.Code, 0, len(t.properties)) for name, property := range t.properties { + // TODO: Properties of parents that are extended, minus DoesNotApplyTo members = append(members, jen.Id(name).Id(property.StructName())) } t.cachedStruct = codegen.NewStruct( From 9a0f688c63a809afe86b33b43c584d372d60a508 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sun, 9 Dec 2018 12:45:08 +0100 Subject: [PATCH 07/14] Fix build, hook up type serialization. - Prepared Types to be Kinds. - Need to handle DoesNotApplyTo (to remove Intransitive properties from parent) - Need to handle crafting files in the appropriate structure. --- tools/exp/convert/convert.go | 51 ++++++++++---- tools/exp/props/property.go | 3 + tools/exp/types/type.go | 129 ++++++++++++++++++++++++++++++----- 3 files changed, 153 insertions(+), 30 deletions(-) diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index 558766b..de55097 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -51,8 +51,8 @@ type Converter struct { } func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*jen.File, e error) { - var v vocabulary - v, e = c.convertVocabulary(p) + // var v vocabulary + _, e = c.convertVocabulary(p) if e != nil { return } @@ -115,7 +115,7 @@ func (c Converter) convertType(t rdf.VocabularyType, } // Determine the properties for this type var p []types.Property - for k, prop := range v.Properties { + for _, prop := range v.Properties { for _, ref := range prop.Domain { if len(ref.Vocab) != 0 { e = fmt.Errorf("unhandled use case: property domain outside its vocabulary") @@ -143,7 +143,7 @@ func (c Converter) convertType(t rdf.VocabularyType, } tg, e = types.NewTypeGenerator( pkg, - strings.Title(t.Name), // Must be same in convertTypeToKind + c.convertTypeToName(t), t.Notes, p, ext, @@ -221,19 +221,32 @@ func (c Converter) convertValue(v rdf.VocabularyValue) (k *props.Kind) { return } -func (c Converter) convertTypeToKind(v rdf.VocabularyType) (k *props.Kind) { +func (c Converter) convertTypeToKind(v rdf.VocabularyType) (k *props.Kind, e error) { + var pkg string + pkg, e = c.typePackageName(v) + if e != nil { + return + } + s, d, l := types.KindSerializationFuncs(pkg, c.convertTypeToName(v)) k = &props.Kind{ - Name: c.toIdentifier(v), - ConcreteKind: strings.Title(v.Name), // Must be same in convertType - Nilable: true, - // TODO - SerializeFn: types.SerializeFn, - DeserializeFn: types.DeserializeFn, - LessFn: types.LessFn, + Name: c.toIdentifier(v), + ConcreteKind: c.convertTypeToConcreteKind(v), + Nilable: true, + SerializeFn: s, + DeserializeFn: d, + LessFn: l, } return } +func (c Converter) convertTypeToName(v rdf.VocabularyType) string { + return strings.Title(v.Name) +} + +func (c Converter) convertTypeToConcreteKind(v rdf.VocabularyType) string { + return "*" + c.convertTypeToName(v) +} + func (c Converter) propertyKinds(v rdf.VocabularyProperty, kinds map[string]*props.Kind, vocab rdf.Vocabulary, @@ -246,7 +259,12 @@ func (c Converter) propertyKinds(v rdf.VocabularyProperty, e = fmt.Errorf("cannot find own kind with name %q", r.Name) return } else { - k = append(k, *c.convertTypeToKind(t)) + var kt *props.Kind + kt, e = c.convertTypeToKind(t) + if e != nil { + return + } + k = append(k, *kt) } } else { // It is a Value of the vocabulary @@ -269,7 +287,12 @@ func (c Converter) propertyKinds(v rdf.VocabularyProperty, e = fmt.Errorf("cannot find kind with name %q in %s", r.Name, url) return } else { - k = append(k, *c.convertTypeToKind(t)) + var kt *props.Kind + kt, e = c.convertTypeToKind(t) + if e != nil { + return + } + k = append(k, *kt) } } else { // It is a Value of the vocabulary diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index 60b2bdb..034ff3f 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -73,7 +73,10 @@ type Kind struct { // // It also properly handles the concept of generating Go code for property // iterators, which are needed for NonFunctional properties. +// +// TODO: Make this type private type PropertyGenerator struct { + // TODO: Make these private Package string Name Identifier Kinds []Kind diff --git a/tools/exp/types/type.go b/tools/exp/types/type.go index ee19ec5..ed357de 100644 --- a/tools/exp/types/type.go +++ b/tools/exp/types/type.go @@ -8,11 +8,14 @@ import ( ) const ( - typeInterfaceName = "Type" - extendedByMethod = "IsExtendedBy" - extendsMethod = "Extends" - disjointWithMethod = "IsDisjointWith" - nameMethod = "Name" + typeInterfaceName = "Type" + extendedByMethod = "IsExtendedBy" + extendsMethod = "Extends" + disjointWithMethod = "IsDisjointWith" + nameMethod = "Name" + serializeMethodName = "Serialize" + deserializeFnName = "Deserialize" + lessFnName = "Less" ) // TypeInterface returns the Type Interface that is needed for ActivityStream @@ -31,6 +34,16 @@ func TypeInterface(pkg string) *codegen.Interface { return codegen.NewInterface(pkg, typeInterfaceName, funcs, comment) } +// KindSerializationFuncs returns free function references that can be used to +// treat a TypeGenerator as another property's Kind. +func KindSerializationFuncs(pkg, typeName string) (ser, deser, less codegen.Function) { + s, d, l := kindSerializationFuncs(pkg, typeName) + ser = *s + deser = *d + less = *l + return +} + // Property represents a property of an ActivityStreams type. type Property interface { PropertyName() string @@ -116,6 +129,12 @@ func (t *TypeGenerator) Disjoint() []*TypeGenerator { return t.disjoint } +// Properties returns the Properties of this type, mapped by their property +// name. +func (t *TypeGenerator) Properties() map[string]Property { + return t.properties +} + // extendsFnName determines the name of the Extends function, which // determines if this ActivityStreams type extends another one. func (t *TypeGenerator) extendsFnName() string { @@ -137,27 +156,47 @@ func (t *TypeGenerator) disjointWithFnName() string { // Definition generates the golang code for this ActivityStreams type. func (t *TypeGenerator) Definition() *codegen.Struct { t.cacheOnce.Do(func() { - members := make([]jen.Code, 0, len(t.properties)) - for name, property := range t.properties { - // TODO: Properties of parents that are extended, minus DoesNotApplyTo - members = append(members, jen.Id(name).Id(property.StructName())) - } + members := t.members() + m := t.serializationMethod() + ser, deser, less := kindSerializationFuncs(t.packageName, t.TypeName()) t.cachedStruct = codegen.NewStruct( jen.Commentf(t.Comment()), t.TypeName(), []*codegen.Method{ t.nameDefinition(), t.extendsDefinition(), + m, }, []*codegen.Function{ t.extendedByDefinition(), t.disjointWithDefinition(), + ser, + deser, + less, }, members) }) return t.cachedStruct } +func (t *TypeGenerator) members() (members []jen.Code) { + p := t.properties + // Properties of parents that are extended, minus DoesNotApplyTo + var extends []*TypeGenerator + extends = t.getAllParentExtends(extends, t) + for _, ext := range t.extends { + for k, v := range ext.Properties() { + p[k] = v + } + // TODO: DoesNotApplyTo + } + members = make([]jen.Code, 0, len(p)) + for name, property := range p { + members = append(members, jen.Id(name).Id(property.StructName())) + } + return +} + // nameDefinition generates the golang method for returning the ActivityStreams // type name. func (t *TypeGenerator) nameDefinition() *codegen.Method { @@ -173,20 +212,25 @@ func (t *TypeGenerator) nameDefinition() *codegen.Method { jen.Commentf("%s returns the name of this type.", nameMethod)) } -// getAllParentExtends recursivley determines all the parent types that this +// getAllParentExtends recursively determines all the parent types that this // type extends from. -func (t *TypeGenerator) getAllParentExtends(s []string, tg *TypeGenerator) { +func (t *TypeGenerator) getAllParentExtends(s []*TypeGenerator, tg *TypeGenerator) []*TypeGenerator { for _, e := range tg.Extends() { - s = append(s, e.TypeName()) - t.getAllParentExtends(s, e) + s = append(s, e) + s = append(s, t.getAllParentExtends(s, e)...) } + return s } // extendsDefinition generates the golang method for determining if this // ActivityStreams type extends another type. It requires the Type interface. func (t *TypeGenerator) extendsDefinition() *codegen.Method { - var extendNames []string - t.getAllParentExtends(extendNames, t) + var extends []*TypeGenerator + extends = t.getAllParentExtends(extends, t) + extendNames := make([]string, 0, len(extends)) + for _, ext := range extends { + extendNames = append(extendNames, ext.TypeName()) + } extensions := make([]jen.Code, len(extendNames)) for i, e := range extendNames { extensions[i] = jen.Lit(e) @@ -302,3 +346,56 @@ func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { impl, jen.Commentf("%s returns true if the other provided type is disjoint with the %s type.", t.disjointWithFnName(), t.TypeName())) } + +func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { + ser = codegen.NewCommentedValueMethod( + t.packageName, + serializeMethodName, + t.TypeName(), + /*params=*/ nil, + []jen.Code{jen.Interface(), jen.Error()}, + []jen.Code{ + // TODO + }, + jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", serializeMethodName)) + return +} + +func kindSerializationFuncs(pkg, typeName string) (ser, deser, less *codegen.Function) { + serName := fmt.Sprintf("%s%s", serializeMethodName, typeName) + ser = codegen.NewCommentedFunction( + pkg, + serName, + []jen.Code{jen.Id("s").Id(typeName)}, + []jen.Code{jen.Interface(), jen.Error()}, + []jen.Code{ + jen.Return( + jen.Id("s").Dot(serializeMethodName).Call(), + ), + }, + jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, typeName)) + deserName := fmt.Sprintf("%s%s", deserializeFnName, typeName) + deser = codegen.NewCommentedFunction( + pkg, + deserName, + []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, + []jen.Code{jen.Op("*").Id(typeName), jen.Error()}, + []jen.Code{ + // TODO + }, + jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, typeName)) + lessName := fmt.Sprintf("%s%s", lessFnName, typeName) + less = codegen.NewCommentedFunction( + pkg, + lessName, + []jen.Code{ + jen.Id("i"), + jen.Id("j").Op("*").Id(typeName), + }, + []jen.Code{jen.Bool()}, + []jen.Code{ + // TODO + }, + jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, typeName)) + return +} From 73f7e3cf364fc6ea568bd9005dcaa226619991df Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sun, 9 Dec 2018 21:23:32 +0100 Subject: [PATCH 08/14] Experimental codegen works end to end. Still plenty of missing features, and missing implementations in the generated code. Also missing some functionality and flags for generating references and/or well-known references (ex: XML, RDF values). --- tools/exp/convert/convert.go | 159 ++++++++++++++++++++--- tools/exp/main.go | 57 ++++++++- tools/exp/props/funcprop.go | 45 ++++--- tools/exp/props/property.go | 6 +- tools/exp/rdf/data.go | 6 +- tools/exp/rdf/ontology.go | 120 +++++++++++------ tools/exp/rdf/xsd/ontology.go | 234 +++++++++++++++++++++++++++++----- tools/exp/spec.json | 3 +- tools/exp/types/type.go | 85 ++++++------ 9 files changed, 546 insertions(+), 169 deletions(-) diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index de55097..0986c68 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -9,6 +9,12 @@ import ( "strings" ) +type File struct { + F *jen.File + FileName string + Directory string +} + type vocabulary struct { Kinds map[string]*props.Kind FProps map[string]*props.FunctionalPropertyGenerator @@ -46,17 +52,82 @@ type Converter struct { VocabularyRoot string PropertyPackagePolicy PropertyPackagePolicy PropertyPackageRoot string - TypePackageRoot string TypePackagePolicy TypePackagePolicy + TypePackageRoot string } -func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*jen.File, e error) { - // var v vocabulary - _, e = c.convertVocabulary(p) +func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*File, e error) { + var v vocabulary + v, e = c.convertVocabulary(p) if e != nil { return } - // TODO + f, e = c.convertToFiles(v) + return +} + +func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { + for _, _ = range v.Kinds { + // TODO + } + for _, i := range v.FProps { + var pkg string + pkg, e = c.propertyPackageFile(i) + if e != nil { + return + } + var dir string + dir, e = c.propertyPackageDirectory(i) + if e != nil { + return + } + file := jen.NewFilePath(pkg) + file.Add(i.Definition().Definition()) + f = append(f, &File{ + F: file, + FileName: fmt.Sprintf("gen_%s.go", i.PropertyName()), + Directory: dir, + }) + } + for _, i := range v.NFProps { + var pkg string + pkg, e = c.propertyPackageFile(i) + if e != nil { + return + } + var dir string + dir, e = c.propertyPackageDirectory(i) + if e != nil { + return + } + file := jen.NewFilePath(pkg) + s, t := i.Definitions() + file.Add(s.Definition()).Add(t.Definition()) + f = append(f, &File{ + F: file, + FileName: fmt.Sprintf("gen_%s.go", i.PropertyName()), + Directory: dir, + }) + } + for _, i := range v.Types { + var pkg string + pkg, e = c.typePackageFile(i) + if e != nil { + return + } + var dir string + dir, e = c.typePackageDirectory(i) + if e != nil { + return + } + file := jen.NewFilePath(pkg) + file.Add(i.Definition().Definition()) + f = append(f, &File{ + F: file, + FileName: fmt.Sprintf("gen_%s.go", i.TypeName()), + Directory: dir, + }) + } return } @@ -86,13 +157,20 @@ func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e e break } stuck := true - for _, t := range allTypes { - var tg *types.TypeGenerator - tg, e = c.convertType(t, p.Vocab, v.FProps, v.NFProps, v.Types) - if e != nil { - return + for i, t := range allTypes { + if allExtendsAreIn(t, v.Types) { + var tg *types.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 } - v.Types[t.Name] = tg } if stuck { e = fmt.Errorf("converting types got stuck in dependency cycle") @@ -331,13 +409,26 @@ func (c Converter) propertyPackageName(v rdf.VocabularyProperty) (pkg string, e return } -// TODO: Use this? -func (c Converter) typePackageFile(v rdf.VocabularyType) (pkg string, e error) { +func (c Converter) typePackageDirectory(v *types.TypeGenerator) (dir string, e error) { + switch c.TypePackagePolicy { + case TypeFlatUnderRoot: + dir = fmt.Sprintf("%s/%s/", c.VocabularyRoot, c.TypePackageRoot) + case TypeIndividualUnderRoot: + dir = fmt.Sprintf("%s/%s/%s/", c.VocabularyRoot, c.TypePackageRoot, v.TypeName()) + case TypeFlatUnderVocabularyRoot: + dir = c.VocabularyRoot + "/" + default: + e = fmt.Errorf("unrecognized TypePackagePolicy: %v", c.TypePackagePolicy) + } + return +} + +func (c Converter) typePackageFile(v *types.TypeGenerator) (pkg string, e error) { switch c.TypePackagePolicy { case TypeFlatUnderRoot: pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.TypePackageRoot) case TypeIndividualUnderRoot: - pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.TypePackageRoot, v.Name) + pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.TypePackageRoot, v.TypeName()) case TypeFlatUnderVocabularyRoot: pkg = c.VocabularyRoot default: @@ -346,13 +437,35 @@ func (c Converter) typePackageFile(v rdf.VocabularyType) (pkg string, e error) { return } -// TODO: Use this? -func (c Converter) propertyPackageFile(v rdf.VocabularyProperty) (pkg string, e error) { +type propertyNamer interface { + PropertyName() string +} + +var ( + _ propertyNamer = &props.FunctionalPropertyGenerator{} + _ propertyNamer = &props.NonFunctionalPropertyGenerator{} +) + +func (c Converter) propertyPackageDirectory(v propertyNamer) (dir string, e error) { + switch c.PropertyPackagePolicy { + case PropertyFlatUnderRoot: + dir = fmt.Sprintf("%s/%s/", c.VocabularyRoot, c.PropertyPackageRoot) + case PropertyIndividualUnderRoot: + dir = fmt.Sprintf("%s/%s/%s/", c.VocabularyRoot, c.PropertyPackageRoot, v.PropertyName()) + case PropertyFlatUnderVocabularyRoot: + dir = c.VocabularyRoot + "/" + default: + e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) + } + return +} + +func (c Converter) propertyPackageFile(v propertyNamer) (pkg string, e error) { switch c.PropertyPackagePolicy { case PropertyFlatUnderRoot: pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.PropertyPackageRoot) case PropertyIndividualUnderRoot: - pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.PropertyPackageRoot, v.Name) + pkg = fmt.Sprintf("%s/%s/%s", c.VocabularyRoot, c.PropertyPackageRoot, v.PropertyName()) case PropertyFlatUnderVocabularyRoot: pkg = c.VocabularyRoot default: @@ -371,3 +484,15 @@ func (c Converter) toIdentifier(n rdf.NameGetter) props.Identifier { func (c Converter) isNilable(goType string) bool { return goType[0] == '*' } + +func allExtendsAreIn(t rdf.VocabularyType, v map[string]*types.TypeGenerator) bool { + for _, e := range t.Extends { + if len(e.Vocab) != 0 { + // TODO: Handle references + return false + } else if _, ok := v[e.Name]; !ok { + return false + } + } + return true +} diff --git a/tools/exp/main.go b/tools/exp/main.go index a67befb..37b5730 100644 --- a/tools/exp/main.go +++ b/tools/exp/main.go @@ -4,12 +4,15 @@ import ( "encoding/json" "flag" "fmt" + "github.com/cjslep/activity/tools/exp/convert" "github.com/cjslep/activity/tools/exp/rdf" "github.com/cjslep/activity/tools/exp/rdf/owl" "github.com/cjslep/activity/tools/exp/rdf/rdfs" "github.com/cjslep/activity/tools/exp/rdf/schema" "github.com/cjslep/activity/tools/exp/rdf/xsd" "io/ioutil" + "os" + "strings" ) var registry *rdf.RDFRegistry @@ -24,19 +27,44 @@ func mustAddOntology(o rdf.Ontology) { } func init() { - mustAddOntology(&xsd.XMLOntology{}) + mustAddOntology(&xsd.XMLOntology{Package: "xml"}) mustAddOntology(&owl.OWLOntology{}) - mustAddOntology(&rdf.RDFOntology{}) + mustAddOntology(&rdf.RDFOntology{Package: "rdf"}) mustAddOntology(&rdfs.RDFSchemaOntology{}) mustAddOntology(&schema.SchemaOntology{}) } var ( input = flag.String("input", "spec.json", "Input JSON-LD specification used to generate Go code.") + // TODO: Use this flag + root = flag.String("root", "github.com/go-fed/activity/", "Go import path prefix for generated packages") + xmlpkg = flag.String("xmlpkg", "github.com/go-fed/activity/tools/exp/ref/xml", "Go package location for known XML references") + rdfpkg = flag.String("rdfpkg", "github.com/go-fed/activity/tools/exp/ref/rdf", "Go package location for known RDF references") + // TODO: Use this flag + writeWellKnown = flag.Bool("write-well-known", false, "When true, also outputs well-known specifications to './ref' subdirectories (ex: XML, RDF)") ) +type list []string + +func (l *list) String() string { + return strings.Join(*l, ",") +} + +func (l *list) Set(v string) error { + vals := strings.Split(v, ",") + *l = append(*l, vals...) + return nil +} + func main() { + var ref list + var refspec list + var refpkg list + flag.Var(&ref, "ref", "Input JSON-LD specification of other referenced specifications. Must be the same size and in the same order as the 'refspec' and 'refpkg' flags") + flag.Var(&refspec, "refspec", "Base URL for other referenced specifications. Must be the same size and in the same order as the 'ref' and 'refpkg' flags") + flag.Var(&refpkg, "refpkg", "Golang package location for other referenced specifications. Must be the same size and in the same order as the 'ref' and 'refspec' flags") flag.Parse() + // TODO: Flag validation b, err := ioutil.ReadFile(*input) if err != nil { @@ -51,5 +79,28 @@ func main() { if err != nil { panic(err) } - fmt.Printf("done\n%s\n", p) + c := &convert.Converter{ + Registry: registry, + VocabularyRoot: "as", + PropertyPackagePolicy: convert.PropertyFlatUnderRoot, + PropertyPackageRoot: "props", + TypePackagePolicy: convert.TypeFlatUnderRoot, + TypePackageRoot: "types", + } + f, err := c.Convert(p) + if err != nil { + panic(err) + } + for _, file := range f { + file.F.ImportName(*xmlpkg, "xml") + file.F.ImportName(*rdfpkg, "rdf") + if e := os.MkdirAll("./"+file.Directory, 0777); e != nil { + panic(e) + } + if e := file.F.Save("./" + file.Directory + file.FileName); e != nil { + panic(e) + } + } + fmt.Printf("done") + // fmt.Printf("done\n%s\n", p) } diff --git a/tools/exp/props/funcprop.go b/tools/exp/props/funcprop.go index 3eb890a..fe19446 100644 --- a/tools/exp/props/funcprop.go +++ b/tools/exp/props/funcprop.go @@ -324,7 +324,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ []jen.Code{jen.Id("i").Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ - deserializeFns.Add(p.unknownDeserializeCode()), + p.addUnknownDeserializeCode(deserializeFns), jen.Return( jen.Nil(), jen.Nil(), @@ -349,7 +349,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ ), jen.Id("ok"), ).Block( - deserializeFns.Add(p.unknownDeserializeCode()), + p.addUnknownDeserializeCode(deserializeFns), ), jen.Return( jen.Nil(), @@ -733,26 +733,31 @@ func (p *FunctionalPropertyGenerator) unknownMemberDef() jen.Code { return jen.Id(unknownMemberName).Index().Byte() } -// unknownDeserializeCode generates the "else if it's a []byte" code used for +// addUnknownDeserializeCode generates the "else if it's a []byte" code used for // deserializing unknown values. -func (p *FunctionalPropertyGenerator) unknownDeserializeCode() jen.Code { - return jen.Else().If( - jen.List( - jen.Id("v"), +func (p *FunctionalPropertyGenerator) addUnknownDeserializeCode(existing jen.Code) jen.Code { + if len(p.Kinds) > 0 { + existing = jen.Add(existing, jen.Else()) + } + return jen.Add(existing, + jen.If( + jen.List( + jen.Id("v"), + jen.Id("ok"), + ).Op(":=").Id("i").Assert( + jen.Index().Byte(), + ), jen.Id("ok"), - ).Op(":=").Id("i").Assert( - jen.Index().Byte(), - ), - jen.Id("ok"), - ).Block( - jen.Id(codegen.This()).Op(":=").Op("&").Id(p.StructName()).Values( - jen.Dict{ - jen.Id(unknownMemberName): jen.Id("v"), - }, - ), - jen.Return( - jen.Id(codegen.This()), - jen.Err(), + ).Block( + jen.Id(codegen.This()).Op(":=").Op("&").Id(p.StructName()).Values( + jen.Dict{ + jen.Id(unknownMemberName): jen.Id("v"), + }, + ), + jen.Return( + jen.Id(codegen.This()), + jen.Err(), + ), ), ) } diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index 034ff3f..7aff71f 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -62,9 +62,9 @@ type Kind struct { Name Identifier ConcreteKind string Nilable bool - SerializeFn codegen.Function - DeserializeFn codegen.Function - LessFn codegen.Function + SerializeFn *codegen.Function + DeserializeFn *codegen.Function + LessFn *codegen.Function } // PropertyGenerator is a common base struct used in both Functional and diff --git a/tools/exp/rdf/data.go b/tools/exp/rdf/data.go index 8b90c20..c6cb6ae 100644 --- a/tools/exp/rdf/data.go +++ b/tools/exp/rdf/data.go @@ -100,9 +100,9 @@ type VocabularyValue struct { URI *url.URL DefinitionType string Zero string - SerializeFn codegen.Function - DeserializeFn codegen.Function - LessFn codegen.Function + SerializeFn *codegen.Function + DeserializeFn *codegen.Function + LessFn *codegen.Function } func (v VocabularyValue) String() string { diff --git a/tools/exp/rdf/ontology.go b/tools/exp/rdf/ontology.go index 7c05481..c2eb11a 100644 --- a/tools/exp/rdf/ontology.go +++ b/tools/exp/rdf/ontology.go @@ -2,6 +2,9 @@ package rdf import ( "fmt" + "github.com/cjslep/activity/tools/exp/codegen" + "github.com/dave/jennifer/jen" + "net/url" "strings" ) @@ -9,11 +12,50 @@ const ( rdfSpec = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" langstringSpec = "langString" propertySpec = "Property" - valueSpec = "value" ) +func SerializeValueFunction(pkg, valueName string, + concreteType jen.Code, + impl []jen.Code) *codegen.Function { + name := fmt.Sprintf("Serialize%s", strings.Title(valueName)) + return codegen.NewCommentedFunction( + pkg, + name, + []jen.Code{jen.Id(codegen.This()).Add(concreteType)}, + []jen.Code{jen.Interface(), jen.Error()}, + impl, + jen.Commentf("%s converts a %s value to an interface representation suitable for marshalling into a text or binary format.", name, valueName)) +} + +func DeserializeValueFunction(pkg, valueName string, + concreteType jen.Code, + impl []jen.Code) *codegen.Function { + name := fmt.Sprintf("Deserialize%s", strings.Title(valueName)) + return codegen.NewCommentedFunction( + pkg, + name, + []jen.Code{jen.Id(codegen.This()).Interface()}, + []jen.Code{concreteType, jen.Error()}, + impl, + jen.Commentf("%s creates %s value from an interface representation that has been unmarshalled from a text or binary format.", name, valueName)) +} + +func LessFunction(pkg, valueName string, + concreteType jen.Code, + impl []jen.Code) *codegen.Function { + name := fmt.Sprintf("Less%s", strings.Title(valueName)) + return codegen.NewCommentedFunction( + pkg, + name, + []jen.Code{jen.List(jen.Id("lhs"), jen.Id("rhs")).Add(concreteType)}, + []jen.Code{jen.Bool()}, + impl, + jen.Commentf("%s returns true if the left %s value is less than the right value.", name, valueName)) +} + type RDFOntology struct { - alias string + Package string + alias string } func (o *RDFOntology) SpecURI() string { @@ -31,7 +73,7 @@ func (o *RDFOntology) LoadAsAlias(s string) ([]RDFNode, error) { Spec: rdfSpec, Alias: s, Name: langstringSpec, - Delegate: &langstring{alias: o.alias}, + Delegate: &langstring{pkg: o.Package, alias: o.alias}, }, &AliasedDelegate{ Spec: rdfSpec, @@ -39,12 +81,6 @@ func (o *RDFOntology) LoadAsAlias(s string) ([]RDFNode, error) { Name: propertySpec, Delegate: &property{}, }, - &AliasedDelegate{ - Spec: rdfSpec, - Alias: s, - Name: valueSpec, - Delegate: &value{}, - }, }, nil } @@ -56,7 +92,7 @@ func (o *RDFOntology) LoadSpecificAsAlias(alias, name string) ([]RDFNode, error) Spec: "", Alias: "", Name: alias, - Delegate: &langstring{alias: o.alias}, + Delegate: &langstring{pkg: o.Package, alias: o.alias}, }, }, nil case propertySpec: @@ -68,15 +104,6 @@ func (o *RDFOntology) LoadSpecificAsAlias(alias, name string) ([]RDFNode, error) Delegate: &property{}, }, }, nil - case valueSpec: - return []RDFNode{ - &AliasedDelegate{ - Spec: "", - Alias: "", - Name: alias, - Delegate: &value{}, - }, - }, nil } return nil, fmt.Errorf("rdf ontology cannot find %q to make alias %q", name, alias) } @@ -89,11 +116,9 @@ func (o *RDFOntology) GetByName(name string) (RDFNode, error) { name = strings.TrimPrefix(name, o.SpecURI()) switch name { case langstringSpec: - return &langstring{alias: o.alias}, nil + return &langstring{pkg: o.Package, alias: o.alias}, nil case propertySpec: return &property{}, nil - case valueSpec: - return &value{}, nil } return nil, fmt.Errorf("rdf ontology could not find node for name %s", name) } @@ -102,6 +127,7 @@ var _ RDFNode = &langstring{} type langstring struct { alias string + pkg string } func (l *langstring) Enter(key string, ctx *ParsingContext) (bool, error) { @@ -122,7 +148,38 @@ func (l *langstring) Apply(key string, value interface{}, ctx *ParsingContext) ( } } } - return true, nil + u, e := url.Parse(rdfSpec + langstringSpec) + if e != nil { + return true, e + } + e = ctx.Result.GetReference(rdfSpec).SetValue(langstringSpec, &VocabularyValue{ + Name: langstringSpec, + URI: u, + DefinitionType: "map[string]string", + Zero: "nil", + SerializeFn: SerializeValueFunction( + l.pkg, + langstringSpec, + jen.Map(jen.String()).String(), + []jen.Code{ + // TODO + }), + DeserializeFn: DeserializeValueFunction( + l.pkg, + langstringSpec, + jen.Map(jen.String()).String(), + []jen.Code{ + // TODO + }), + LessFn: LessFunction( + l.pkg, + langstringSpec, + jen.Map(jen.String()).String(), + []jen.Code{ + // TODO + }), + }) + return true, e } var _ RDFNode = &property{} @@ -148,20 +205,3 @@ func (p *property) Apply(key string, value interface{}, ctx *ParsingContext) (bo ctx.Current = &VocabularyProperty{} return true, nil } - -var _ RDFNode = &value{} - -type value struct{} - -func (v *value) Enter(key string, ctx *ParsingContext) (bool, error) { - return true, fmt.Errorf("rdf value cannot be entered") -} - -func (v *value) Exit(key string, ctx *ParsingContext) (bool, error) { - return true, fmt.Errorf("rdf value cannot be exited") -} - -func (v *value) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) { - // TODO: Act as value - return true, fmt.Errorf("rdf value cannot be applied") -} diff --git a/tools/exp/rdf/xsd/ontology.go b/tools/exp/rdf/xsd/ontology.go index 98447cf..d1e43ee 100644 --- a/tools/exp/rdf/xsd/ontology.go +++ b/tools/exp/rdf/xsd/ontology.go @@ -3,6 +3,7 @@ package xsd import ( "fmt" "github.com/cjslep/activity/tools/exp/rdf" + "github.com/dave/jennifer/jen" "net/url" "strings" ) @@ -18,7 +19,9 @@ const ( durationSpec = "duration" ) -type XMLOntology struct{} +type XMLOntology struct { + Package string +} func (o *XMLOntology) SpecURI() string { return xmlSpec @@ -34,43 +37,43 @@ func (o *XMLOntology) LoadAsAlias(s string) ([]rdf.RDFNode, error) { Spec: xmlSpec, Alias: s, Name: anyURISpec, - Delegate: &anyURI{}, + Delegate: &anyURI{pkg: o.Package}, }, &rdf.AliasedDelegate{ Spec: xmlSpec, Alias: s, Name: dateTimeSpec, - Delegate: &dateTime{}, + Delegate: &dateTime{pkg: o.Package}, }, &rdf.AliasedDelegate{ Spec: xmlSpec, Alias: s, Name: floatSpec, - Delegate: &float{}, + Delegate: &float{pkg: o.Package}, }, &rdf.AliasedDelegate{ Spec: xmlSpec, Alias: s, Name: stringSpec, - Delegate: &xmlString{}, + Delegate: &xmlString{pkg: o.Package}, }, &rdf.AliasedDelegate{ Spec: xmlSpec, Alias: s, Name: booleanSpec, - Delegate: &boolean{}, + Delegate: &boolean{pkg: o.Package}, }, &rdf.AliasedDelegate{ Spec: xmlSpec, Alias: s, Name: nonNegativeIntegerSpec, - Delegate: &nonNegativeInteger{}, + Delegate: &nonNegativeInteger{pkg: o.Package}, }, &rdf.AliasedDelegate{ Spec: xmlSpec, Alias: s, Name: durationSpec, - Delegate: &duration{}, + Delegate: &duration{pkg: o.Package}, }, }, nil } @@ -83,7 +86,7 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Spec: "", Alias: "", Name: alias, - Delegate: &anyURI{}, + Delegate: &anyURI{pkg: o.Package}, }, }, nil case dateTimeSpec: @@ -92,7 +95,7 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Spec: "", Alias: "", Name: alias, - Delegate: &dateTime{}, + Delegate: &dateTime{pkg: o.Package}, }, }, nil case floatSpec: @@ -101,7 +104,7 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Spec: "", Alias: "", Name: alias, - Delegate: &float{}, + Delegate: &float{pkg: o.Package}, }, }, nil case stringSpec: @@ -110,7 +113,7 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Spec: "", Alias: "", Name: alias, - Delegate: &xmlString{}, + Delegate: &xmlString{pkg: o.Package}, }, }, nil case booleanSpec: @@ -119,7 +122,7 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Spec: "", Alias: "", Name: alias, - Delegate: &boolean{}, + Delegate: &boolean{pkg: o.Package}, }, }, nil case nonNegativeIntegerSpec: @@ -128,7 +131,7 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Spec: "", Alias: "", Name: alias, - Delegate: &nonNegativeInteger{}, + Delegate: &nonNegativeInteger{pkg: o.Package}, }, }, nil case durationSpec: @@ -137,7 +140,7 @@ func (o *XMLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, er Spec: "", Alias: "", Name: alias, - Delegate: &duration{}, + Delegate: &duration{pkg: o.Package}, }, }, nil } @@ -152,26 +155,28 @@ func (o *XMLOntology) GetByName(name string) (rdf.RDFNode, error) { name = strings.TrimPrefix(name, o.SpecURI()) switch name { case anyURISpec: - return &anyURI{}, nil + return &anyURI{pkg: o.Package}, nil case dateTimeSpec: - return &dateTime{}, nil + return &dateTime{pkg: o.Package}, nil case floatSpec: - return &float{}, nil + return &float{pkg: o.Package}, nil case stringSpec: - return &xmlString{}, nil + return &xmlString{pkg: o.Package}, nil case booleanSpec: - return &boolean{}, nil + return &boolean{pkg: o.Package}, nil case nonNegativeIntegerSpec: - return &nonNegativeInteger{}, nil + return &nonNegativeInteger{pkg: o.Package}, nil case durationSpec: - return &duration{}, nil + return &duration{pkg: o.Package}, nil } return nil, fmt.Errorf("xsd ontology could not find node for name %s", name) } var _ rdf.RDFNode = &anyURI{} -type anyURI struct{} +type anyURI struct { + pkg string +} func (a *anyURI) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd anyURI cannot be entered") @@ -193,6 +198,27 @@ func (a *anyURI) Apply(key string, value interface{}, ctx *rdf.ParsingContext) ( URI: u, DefinitionType: "*url.URL", Zero: "&url.URL{}", + SerializeFn: rdf.SerializeValueFunction( + a.pkg, + anyURISpec, + jen.Op("*").Qual("url", "URL"), + []jen.Code{ + // TODO + }), + DeserializeFn: rdf.DeserializeValueFunction( + a.pkg, + anyURISpec, + jen.Op("*").Qual("url", "URL"), + []jen.Code{ + // TODO + }), + LessFn: rdf.LessFunction( + a.pkg, + anyURISpec, + jen.Op("*").Qual("url", "URL"), + []jen.Code{ + // TODO + }), } if err = v.SetValue(anyURISpec, val); err != nil { return true, err @@ -203,7 +229,9 @@ func (a *anyURI) Apply(key string, value interface{}, ctx *rdf.ParsingContext) ( var _ rdf.RDFNode = &dateTime{} -type dateTime struct{} +type dateTime struct { + pkg string +} func (d *dateTime) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd dateTime cannot be entered") @@ -225,6 +253,27 @@ func (d *dateTime) Apply(key string, value interface{}, ctx *rdf.ParsingContext) URI: u, DefinitionType: "time.Time", Zero: "&time.Time{}", + SerializeFn: rdf.SerializeValueFunction( + d.pkg, + dateTimeSpec, + jen.Qual("time", "Time"), + []jen.Code{ + // TODO + }), + DeserializeFn: rdf.DeserializeValueFunction( + d.pkg, + dateTimeSpec, + jen.Qual("time", "Time"), + []jen.Code{ + // TODO + }), + LessFn: rdf.LessFunction( + d.pkg, + dateTimeSpec, + jen.Qual("time", "Time"), + []jen.Code{ + // TODO + }), } if err = v.SetValue(dateTimeSpec, val); err != nil { return true, err @@ -235,7 +284,9 @@ func (d *dateTime) Apply(key string, value interface{}, ctx *rdf.ParsingContext) var _ rdf.RDFNode = &float{} -type float struct{} +type float struct { + pkg string +} func (f *float) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd float cannot be entered") @@ -257,6 +308,27 @@ func (f *float) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (b URI: u, DefinitionType: "float32", Zero: "0.0", + SerializeFn: rdf.SerializeValueFunction( + f.pkg, + floatSpec, + jen.Id("float32"), + []jen.Code{ + // TODO + }), + DeserializeFn: rdf.DeserializeValueFunction( + f.pkg, + floatSpec, + jen.Id("float32"), + []jen.Code{ + // TODO + }), + LessFn: rdf.LessFunction( + f.pkg, + floatSpec, + jen.Id("float32"), + []jen.Code{ + // TODO + }), } if err = v.SetValue(floatSpec, val); err != nil { return true, err @@ -267,7 +339,9 @@ func (f *float) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (b var _ rdf.RDFNode = &xmlString{} -type xmlString struct{} +type xmlString struct { + pkg string +} func (*xmlString) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd string cannot be entered") @@ -277,7 +351,7 @@ func (*xmlString) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd string cannot be exited") } -func (*xmlString) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { +func (s *xmlString) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { v := ctx.Result.GetReference(xmlSpec) if len(v.Values[stringSpec].Name) == 0 { u, err := url.Parse(xmlSpec + stringSpec) @@ -289,6 +363,27 @@ func (*xmlString) Apply(key string, value interface{}, ctx *rdf.ParsingContext) URI: u, DefinitionType: "string", Zero: "\"\"", + SerializeFn: rdf.SerializeValueFunction( + s.pkg, + stringSpec, + jen.Id("string"), + []jen.Code{ + // TODO + }), + DeserializeFn: rdf.DeserializeValueFunction( + s.pkg, + stringSpec, + jen.Id("string"), + []jen.Code{ + // TODO + }), + LessFn: rdf.LessFunction( + s.pkg, + stringSpec, + jen.Id("string"), + []jen.Code{ + // TODO + }), } if err = v.SetValue(stringSpec, val); err != nil { return true, err @@ -299,7 +394,9 @@ func (*xmlString) Apply(key string, value interface{}, ctx *rdf.ParsingContext) var _ rdf.RDFNode = &boolean{} -type boolean struct{} +type boolean struct { + pkg string +} func (*boolean) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd boolean cannot be entered") @@ -309,7 +406,7 @@ func (*boolean) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd boolean cannot be exited") } -func (*boolean) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { +func (b *boolean) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { v := ctx.Result.GetReference(xmlSpec) if len(v.Values[booleanSpec].Name) == 0 { u, err := url.Parse(xmlSpec + booleanSpec) @@ -319,8 +416,29 @@ func (*boolean) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (b val := &rdf.VocabularyValue{ Name: booleanSpec, URI: u, - DefinitionType: "string", - Zero: "\"\"", + DefinitionType: "bool", + Zero: "false", + SerializeFn: rdf.SerializeValueFunction( + b.pkg, + booleanSpec, + jen.Id("bool"), + []jen.Code{ + // TODO + }), + DeserializeFn: rdf.DeserializeValueFunction( + b.pkg, + booleanSpec, + jen.Id("bool"), + []jen.Code{ + // TODO + }), + LessFn: rdf.LessFunction( + b.pkg, + booleanSpec, + jen.Id("bool"), + []jen.Code{ + // TODO + }), } if err = v.SetValue(booleanSpec, val); err != nil { return true, err @@ -331,7 +449,9 @@ func (*boolean) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (b var _ rdf.RDFNode = &nonNegativeInteger{} -type nonNegativeInteger struct{} +type nonNegativeInteger struct { + pkg string +} func (*nonNegativeInteger) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd nonNegativeInteger cannot be entered") @@ -341,7 +461,7 @@ func (*nonNegativeInteger) Exit(key string, ctx *rdf.ParsingContext) (bool, erro return true, fmt.Errorf("xsd nonNegativeInteger cannot be exited") } -func (*nonNegativeInteger) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { +func (n *nonNegativeInteger) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { v := ctx.Result.GetReference(xmlSpec) if len(v.Values[nonNegativeIntegerSpec].Name) == 0 { u, err := url.Parse(xmlSpec + nonNegativeIntegerSpec) @@ -353,6 +473,27 @@ func (*nonNegativeInteger) Apply(key string, value interface{}, ctx *rdf.Parsing URI: u, DefinitionType: "int", Zero: "0", + SerializeFn: rdf.SerializeValueFunction( + n.pkg, + nonNegativeIntegerSpec, + jen.Id("int"), + []jen.Code{ + // TODO + }), + DeserializeFn: rdf.DeserializeValueFunction( + n.pkg, + nonNegativeIntegerSpec, + jen.Id("int"), + []jen.Code{ + // TODO + }), + LessFn: rdf.LessFunction( + n.pkg, + nonNegativeIntegerSpec, + jen.Id("int"), + []jen.Code{ + // TODO + }), } if err = v.SetValue(nonNegativeIntegerSpec, val); err != nil { return true, err @@ -363,7 +504,9 @@ func (*nonNegativeInteger) Apply(key string, value interface{}, ctx *rdf.Parsing var _ rdf.RDFNode = &duration{} -type duration struct{} +type duration struct { + pkg string +} func (*duration) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd duration cannot be entered") @@ -373,7 +516,7 @@ func (*duration) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("xsd duration cannot be exited") } -func (*duration) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { +func (d *duration) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { v := ctx.Result.GetReference(xmlSpec) if len(v.Values[durationSpec].Name) == 0 { u, err := url.Parse(xmlSpec + durationSpec) @@ -385,6 +528,27 @@ func (*duration) Apply(key string, value interface{}, ctx *rdf.ParsingContext) ( URI: u, DefinitionType: "time.Duration", Zero: "time.Duration(0)", + SerializeFn: rdf.SerializeValueFunction( + d.pkg, + durationSpec, + jen.Qual("time", "Duration"), + []jen.Code{ + // TODO + }), + DeserializeFn: rdf.DeserializeValueFunction( + d.pkg, + durationSpec, + jen.Qual("time", "Duration"), + []jen.Code{ + // TODO + }), + LessFn: rdf.LessFunction( + d.pkg, + durationSpec, + jen.Qual("time", "Duration"), + []jen.Code{ + // TODO + }), } if err = v.SetValue(durationSpec, val); err != nil { return true, err diff --git a/tools/exp/spec.json b/tools/exp/spec.json index de20239..d4e2d5f 100644 --- a/tools/exp/spec.json +++ b/tools/exp/spec.json @@ -20,8 +20,7 @@ "disjointWith": "owl:disjointWith", "subPropertyOf": "rdfs:subPropertyOf", "unionOf": "owl:unionOf", - "url": "schema:URL", - "value": "rdf:value" + "url": "schema:URL" }, { "sections": { diff --git a/tools/exp/types/type.go b/tools/exp/types/type.go index ed357de..82bfb56 100644 --- a/tools/exp/types/type.go +++ b/tools/exp/types/type.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" + "strings" "sync" ) @@ -36,11 +37,42 @@ func TypeInterface(pkg string) *codegen.Interface { // KindSerializationFuncs returns free function references that can be used to // treat a TypeGenerator as another property's Kind. -func KindSerializationFuncs(pkg, typeName string) (ser, deser, less codegen.Function) { - s, d, l := kindSerializationFuncs(pkg, typeName) - ser = *s - deser = *d - less = *l +func KindSerializationFuncs(pkg, typeName string) (ser, deser, less *codegen.Function) { + serName := fmt.Sprintf("%s%s", serializeMethodName, typeName) + ser = codegen.NewCommentedFunction( + pkg, + serName, + []jen.Code{jen.Id("s").Id(typeName)}, + []jen.Code{jen.Interface(), jen.Error()}, + []jen.Code{ + jen.Return( + jen.Id("s").Dot(serializeMethodName).Call(), + ), + }, + jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, typeName)) + deserName := fmt.Sprintf("%s%s", deserializeFnName, typeName) + deser = codegen.NewCommentedFunction( + pkg, + deserName, + []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, + []jen.Code{jen.Op("*").Id(typeName), jen.Error()}, + []jen.Code{ + // TODO + }, + jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, typeName)) + lessName := fmt.Sprintf("%s%s", lessFnName, typeName) + less = codegen.NewCommentedFunction( + pkg, + lessName, + []jen.Code{ + jen.Id("i"), + jen.Id("j").Op("*").Id(typeName), + }, + []jen.Code{jen.Bool()}, + []jen.Code{ + // TODO + }, + jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, typeName)) return } @@ -158,7 +190,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct { t.cacheOnce.Do(func() { members := t.members() m := t.serializationMethod() - ser, deser, less := kindSerializationFuncs(t.packageName, t.TypeName()) + ser, deser, less := KindSerializationFuncs(t.packageName, t.TypeName()) t.cachedStruct = codegen.NewStruct( jen.Commentf(t.Comment()), t.TypeName(), @@ -192,7 +224,7 @@ func (t *TypeGenerator) members() (members []jen.Code) { } members = make([]jen.Code, 0, len(p)) for name, property := range p { - members = append(members, jen.Id(name).Id(property.StructName())) + members = append(members, jen.Id(strings.Title(name)).Id(property.StructName())) } return } @@ -360,42 +392,3 @@ func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", serializeMethodName)) return } - -func kindSerializationFuncs(pkg, typeName string) (ser, deser, less *codegen.Function) { - serName := fmt.Sprintf("%s%s", serializeMethodName, typeName) - ser = codegen.NewCommentedFunction( - pkg, - serName, - []jen.Code{jen.Id("s").Id(typeName)}, - []jen.Code{jen.Interface(), jen.Error()}, - []jen.Code{ - jen.Return( - jen.Id("s").Dot(serializeMethodName).Call(), - ), - }, - jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, typeName)) - deserName := fmt.Sprintf("%s%s", deserializeFnName, typeName) - deser = codegen.NewCommentedFunction( - pkg, - deserName, - []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, - []jen.Code{jen.Op("*").Id(typeName), jen.Error()}, - []jen.Code{ - // TODO - }, - jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, typeName)) - lessName := fmt.Sprintf("%s%s", lessFnName, typeName) - less = codegen.NewCommentedFunction( - pkg, - lessName, - []jen.Code{ - jen.Id("i"), - jen.Id("j").Op("*").Id(typeName), - }, - []jen.Code{jen.Bool()}, - []jen.Code{ - // TODO - }, - jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, typeName)) - return -} From 222074b503733e55398d6db583dfd989c39dc151 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Mon, 17 Dec 2018 21:43:33 +0100 Subject: [PATCH 09/14] Put properties on the vocabulary types. --- tools/exp/rdf/data.go | 14 ++++--- tools/exp/rdf/parse.go | 88 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/tools/exp/rdf/data.go b/tools/exp/rdf/data.go index c6cb6ae..ecc9046 100644 --- a/tools/exp/rdf/data.go +++ b/tools/exp/rdf/data.go @@ -131,12 +131,14 @@ var ( // VocabularyType represents a single ActivityStream type in a vocabulary. type VocabularyType struct { - Name string - URI *url.URL - Notes string - DisjointWith []VocabularyReference - Extends []VocabularyReference - Examples []VocabularyExample + Name string + URI *url.URL + Notes string + DisjointWith []VocabularyReference + Extends []VocabularyReference + Examples []VocabularyExample + Properties []VocabularyReference + WithoutProperties []VocabularyReference } func (v VocabularyType) String() string { diff --git a/tools/exp/rdf/parse.go b/tools/exp/rdf/parse.go index 78b188e..dd9d30a 100644 --- a/tools/exp/rdf/parse.go +++ b/tools/exp/rdf/parse.go @@ -133,14 +133,98 @@ func ParseVocabulary(registry *RDFRegistry, input JSONLD) (vocabulary *ParsedVoc // parser can understand things like types so that other nodes do not // hijack processing. nodes = append(jsonLDNodes(registry), nodes...) + // Step 1: Parse all core data, excluding: + // - Value types + // - Referenced types + // - VocabularyType's 'Properties' and 'WithoutProperties' fields + // + // This is all horrible code but it works, so.... err = apply(nodes, input, ctx) if err != nil { return } + // Step 2: Populate value and referenced types. err = resolveReferences(registry, ctx) + if err != nil { + return + } + // Step 3: Populate VocabularyType's 'Properties' and + // 'WithoutProperties' fields + err = populatePropertiesOnTypes(ctx) return } +// populatePropertiesOnTypes populates the 'Properties' and 'WithoutProperties' +// entries on a VocabularyType. +func populatePropertiesOnTypes(ctx *ParsingContext) error { + for _, p := range ctx.Result.Vocab.Properties { + if err := populatePropertyOnTypes(p, "", ctx); err != nil { + return err + } + } + for vName, ref := range ctx.Result.References { + for _, p := range ref.Properties { + if err := populatePropertyOnTypes(p, vName, ctx); err != nil { + return err + } + } + } + return nil +} + +// populatePropertyOnTypes populates the VocabularyType's 'Properties' and +// 'WithoutProperties' fields based on the 'Domain' and 'DoesNotApplyTo'. +func populatePropertyOnTypes(p VocabularyProperty, vocabName string, ctx *ParsingContext) error { + ref := VocabularyReference{ + Name: p.Name, + URI: p.URI, + Vocab: vocabName, + } + for _, d := range p.Domain { + if len(d.Vocab) == 0 { + t, ok := ctx.Result.Vocab.Types[d.Name] + if !ok { + return fmt.Errorf("cannot populate property on type %q for desired vocab", d.Name) + } + t.Properties = append(t.Properties, ref) + ctx.Result.Vocab.Types[d.Name] = t + } else { + v, ok := ctx.Result.References[d.Vocab] + if !ok { + return fmt.Errorf("cannot populate property on type for vocab %q", d.Vocab) + } + t, ok := v.Types[d.Name] + if !ok { + return fmt.Errorf("cannot populate property on type %q for vocab %q", d.Name, d.Vocab) + } + t.Properties = append(t.Properties, ref) + v.Types[d.Name] = t + } + } + for _, dna := range p.DoesNotApplyTo { + if len(dna.Vocab) == 0 { + t, ok := ctx.Result.Vocab.Types[dna.Name] + if !ok { + return fmt.Errorf("cannot populate withoutproperty on type %q for desired vocab", dna.Name) + } + t.WithoutProperties = append(t.WithoutProperties, ref) + ctx.Result.Vocab.Types[dna.Name] = t + } else { + v, ok := ctx.Result.References[dna.Vocab] + if !ok { + return fmt.Errorf("cannot populate withoutproperty on type for vocab %q", dna.Vocab) + } + t, ok := v.Types[dna.Name] + if !ok { + return fmt.Errorf("cannot populate withoutproperty on type %q for vocab %q", dna.Name, dna.Vocab) + } + t.WithoutProperties = append(t.WithoutProperties, ref) + v.Types[dna.Name] = t + } + } + return nil +} + // resolveReferences ensures that all references mentioned have been // successfully parsed, and if not attempts to search the ontologies for any // values, types, and properties that need to be referenced. @@ -221,6 +305,10 @@ func resolveReference(reference VocabularyReference, registry *RDFRegistry, ctx // apply takes a specification input to populate the ParsingContext, based on // the capabilities of the RDFNodes created from ontologies. +// +// This function will populate all non-value data in the Vocabulary. It does not +// populate the 'Properties' nor the 'WithoutProperties' fields on any +// VocabularyType. func apply(nodes []RDFNode, input JSONLD, ctx *ParsingContext) error { // Hijacked processing: Process the rest of the data in this single // node. From a3c3a7b5fc1993a11aacdfc34f26b6792e344c38 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Mon, 17 Dec 2018 23:11:55 +0100 Subject: [PATCH 10/14] Prepare TypeGenerator being a Kind for Properties. Right now the two-pass tooling system has issues with establishing doubly-linked data between Functiona/NonFunctional Properties (which have Kinds abstractions) and Types (which have Property abstraction). While the experimental tool compiles, it panics at runtime currently because the TypeGenerator needs to look at properties with Range of itself, but it is applying itself to properties with Domain of its type. Which is wrong. Will need to stew on this and think of how to avoid making even more shortcuts and hacky solutions in the name of progress. --- tools/exp/convert/convert.go | 112 +++++++++++------ tools/exp/props/funcprop.go | 8 +- tools/exp/props/nonfuncprop.go | 6 +- tools/exp/props/property.go | 25 +++- tools/exp/{types => props}/type.go | 192 ++++++++++++++++++----------- 5 files changed, 225 insertions(+), 118 deletions(-) rename tools/exp/{types => props}/type.go (75%) diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index 0986c68..c7994cd 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/cjslep/activity/tools/exp/props" "github.com/cjslep/activity/tools/exp/rdf" - "github.com/cjslep/activity/tools/exp/types" "github.com/dave/jennifer/jen" "strings" ) @@ -19,7 +18,7 @@ type vocabulary struct { Kinds map[string]*props.Kind FProps map[string]*props.FunctionalPropertyGenerator NFProps map[string]*props.NonFunctionalPropertyGenerator - Types map[string]*types.TypeGenerator + Types map[string]*props.TypeGenerator } func newVocabulary() vocabulary { @@ -27,7 +26,7 @@ func newVocabulary() vocabulary { Kinds: make(map[string]*props.Kind, 0), FProps: make(map[string]*props.FunctionalPropertyGenerator, 0), NFProps: make(map[string]*props.NonFunctionalPropertyGenerator, 0), - Types: make(map[string]*types.TypeGenerator, 0), + Types: make(map[string]*props.TypeGenerator, 0), } } @@ -68,7 +67,7 @@ func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*File, e error) { func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { for _, _ = range v.Kinds { - // TODO + // TODO: Implement } for _, i := range v.FProps { var pkg string @@ -131,6 +130,16 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { return } +// convertVocabulary works in a two-pass system: first converting all known +// properties, and then the types. +// +// Due to the fact that properties rely on the Kind abstraction, and both +// properties and types can be Kinds, this introduces tight coupling between +// the two so that callbacks can fill in missing links in data that isn't known +// beforehand (ex: how to serialize, deserialize, and compare types). +// +// This feels very hacky and could be decoupled using standard design patterns, +// but since there is no need, it isn't addressed now. func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e error) { v = newVocabulary() for k, val := range p.Vocab.Values { @@ -159,7 +168,7 @@ func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e e stuck := true for i, t := range allTypes { if allExtendsAreIn(t, v.Types) { - var tg *types.TypeGenerator + var tg *props.TypeGenerator tg, e = c.convertType(t, p.Vocab, v.FProps, v.NFProps, v.Types) if e != nil { return @@ -173,7 +182,7 @@ func (c Converter) convertVocabulary(p *rdf.ParsedVocabulary) (v vocabulary, e e } } if stuck { - e = fmt.Errorf("converting types got stuck in dependency cycle") + e = fmt.Errorf("converting props got stuck in dependency cycle") return } } @@ -184,46 +193,70 @@ func (c Converter) convertType(t rdf.VocabularyType, v rdf.Vocabulary, existingFProps map[string]*props.FunctionalPropertyGenerator, existingNFProps map[string]*props.NonFunctionalPropertyGenerator, - existingTypes map[string]*types.TypeGenerator) (tg *types.TypeGenerator, e error) { - // Determine the types package name + existingTypes map[string]*props.TypeGenerator) (tg *props.TypeGenerator, e error) { + // Determine the props package name var pkg string pkg, e = c.typePackageName(t) if e != nil { return } // Determine the properties for this type - var p []types.Property - for _, prop := range v.Properties { - for _, ref := range prop.Domain { - if len(ref.Vocab) != 0 { - e = fmt.Errorf("unhandled use case: property domain outside its vocabulary") - return - } else if ref.Name == t.Name { - if prop.Functional { - p = append(p, existingFProps[prop.Name]) - } else { - p = append(p, existingNFProps[prop.Name]) + var p []props.Property + for _, prop := range t.Properties { + if len(prop.Vocab) != 0 { + e = fmt.Errorf("unhandled use case: property domain outside its vocabulary") + return + } else { + var property props.Property + var ok bool + property, ok = existingFProps[prop.Name] + if !ok { + property, ok = existingNFProps[prop.Name] + if !ok { + e = fmt.Errorf("cannot find property with name: %s", prop.Name) + return } - break } + p = append(p, property) + } + } + // Determine WithoutProperties for this type + var wop []props.Property + for _, prop := range t.WithoutProperties { + if len(prop.Vocab) != 0 { + e = fmt.Errorf("unhandled use case: withoutproperty domain outside its vocabulary") + return + } else { + var property props.Property + var ok bool + property, ok = existingFProps[prop.Name] + if !ok { + property, ok = existingNFProps[prop.Name] + if !ok { + e = fmt.Errorf("cannot find property with name: %s", prop.Name) + return + } + } + wop = append(wop, property) } } // Determine what this type extends - var ext []*types.TypeGenerator + var ext []*props.TypeGenerator for _, ex := range t.Extends { if len(ex.Vocab) != 0 { - // TODO: This should be fixed + // TODO: This should be fixed to handle references e = fmt.Errorf("unhandled use case: type extends another type outside its vocabulary") return } else { ext = append(ext, existingTypes[ex.Name]) } } - tg, e = types.NewTypeGenerator( + tg, e = props.NewTypeGenerator( pkg, c.convertTypeToName(t), t.Notes, p, + wop, ext, nil) if e != nil { @@ -232,7 +265,7 @@ func (c Converter) convertType(t rdf.VocabularyType, // Apply disjoint if both sides are available. for _, disj := range t.DisjointWith { if len(disj.Vocab) != 0 { - // TODO: This should be fixed + // TODO: This should be fixed to handle references e = fmt.Errorf("unhandled use case: type is disjoint with another type outside its vocabulary") return } else if disjointType, ok := existingTypes[disj.Name]; ok { @@ -300,19 +333,18 @@ func (c Converter) convertValue(v rdf.VocabularyValue) (k *props.Kind) { } func (c Converter) convertTypeToKind(v rdf.VocabularyType) (k *props.Kind, e error) { - var pkg string - pkg, e = c.typePackageName(v) - if e != nil { - return - } - s, d, l := types.KindSerializationFuncs(pkg, c.convertTypeToName(v)) k = &props.Kind{ - Name: c.toIdentifier(v), - ConcreteKind: c.convertTypeToConcreteKind(v), - Nilable: true, - SerializeFn: s, - DeserializeFn: d, - LessFn: l, + Name: c.toIdentifier(v), + ConcreteKind: c.convertTypeToConcreteKind(v), + Nilable: true, + // Instead of populating: + // - SerializeFn + // - DeserializeFn + // - LessFn + // + // The TypeGenerator is responsible for calling setKindFns on + // the properties, to property wire a Property's Kind back to + // the Type's implementation. } return } @@ -409,7 +441,7 @@ func (c Converter) propertyPackageName(v rdf.VocabularyProperty) (pkg string, e return } -func (c Converter) typePackageDirectory(v *types.TypeGenerator) (dir string, e error) { +func (c Converter) typePackageDirectory(v *props.TypeGenerator) (dir string, e error) { switch c.TypePackagePolicy { case TypeFlatUnderRoot: dir = fmt.Sprintf("%s/%s/", c.VocabularyRoot, c.TypePackageRoot) @@ -423,7 +455,7 @@ func (c Converter) typePackageDirectory(v *types.TypeGenerator) (dir string, e e return } -func (c Converter) typePackageFile(v *types.TypeGenerator) (pkg string, e error) { +func (c Converter) typePackageFile(v *props.TypeGenerator) (pkg string, e error) { switch c.TypePackagePolicy { case TypeFlatUnderRoot: pkg = fmt.Sprintf("%s/%s", c.VocabularyRoot, c.TypePackageRoot) @@ -485,10 +517,10 @@ func (c Converter) isNilable(goType string) bool { return goType[0] == '*' } -func allExtendsAreIn(t rdf.VocabularyType, v map[string]*types.TypeGenerator) bool { +func allExtendsAreIn(t rdf.VocabularyType, v map[string]*props.TypeGenerator) bool { for _, e := range t.Extends { if len(e.Vocab) != 0 { - // TODO: Handle references + // TODO: This should be fixed to handle references return false } else if _, ok := v[e.Name]; !ok { return false diff --git a/tools/exp/props/funcprop.go b/tools/exp/props/funcprop.go index fe19446..7b49bdc 100644 --- a/tools/exp/props/funcprop.go +++ b/tools/exp/props/funcprop.go @@ -320,7 +320,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ deserialize = append(deserialize, codegen.NewCommentedFunction( p.packageName(), - p.deserializeFnName(), + p.DeserializeFnName(), []jen.Code{jen.Id("i").Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ @@ -330,13 +330,13 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ jen.Nil(), ), }, - jen.Commentf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.deserializeFnName()), + jen.Commentf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.DeserializeFnName()), )) } else { deserialize = append(deserialize, codegen.NewCommentedFunction( p.packageName(), - p.deserializeFnName(), + p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ @@ -356,7 +356,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ jen.Nil(), ), }, - jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.deserializeFnName(), p.PropertyName()), + jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName()), )) } return serialize, deserialize diff --git a/tools/exp/props/nonfuncprop.go b/tools/exp/props/nonfuncprop.go index e55a523..0f00337 100644 --- a/tools/exp/props/nonfuncprop.go +++ b/tools/exp/props/nonfuncprop.go @@ -303,7 +303,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method jen.List( jen.Id("p"), jen.Err(), - ).Op(":=").Id(p.elementTypeGenerator().deserializeFnName()).Call( + ).Op(":=").Id(p.elementTypeGenerator().DeserializeFnName()).Call( jen.Id(variable), ), jen.Err().Op("!=").Nil(), @@ -324,7 +324,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method deserialize := []*codegen.Function{ codegen.NewCommentedFunction( p.packageName(), - p.deserializeFnName(), + p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Id(p.StructName()), jen.Error()}, []jen.Code{ @@ -364,7 +364,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method jen.Nil(), ), }, - jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.deserializeFnName(), p.PropertyName()), + jen.Commentf("%s creates a %q property from an interface representation that has been unmarshalled from a text or binary format.", p.DeserializeFnName(), p.PropertyName()), ), } return serialize, deserialize diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index 7aff71f..574ced8 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -89,6 +89,27 @@ func (p *PropertyGenerator) packageName() string { return p.Package } +// SetKindFns allows TypeGenerators to later notify this Property what functions +// to use when generating the serialization code. +// +// The name parameter must match the LowerName of an Identifier. +// +// This feels very hacky. +func (p *PropertyGenerator) SetKindFns(name string, ser, deser, less *codegen.Function) error { + for _, kind := range p.Kinds { + if kind.Name.LowerName == name { + if kind.SerializeFn != nil || kind.DeserializeFn != nil || kind.LessFn != nil { + return fmt.Errorf("property kind already has serialization functions set for %q", name) + } + kind.SerializeFn = ser + kind.DeserializeFn = deser + kind.LessFn = less + return nil + } + } + return fmt.Errorf("cannot find property kind %q", name) +} + // StructName returns the name of the type, which may or may not be a struct, // to generate. func (p *PropertyGenerator) StructName() string { @@ -105,9 +126,9 @@ func (p *PropertyGenerator) PropertyName() string { return p.Name.LowerName } -// deserializeFnName returns the identifier of the function that deserializes +// DeserializeFnName returns the identifier of the function that deserializes // raw JSON into the generated Go type. -func (p *PropertyGenerator) deserializeFnName() string { +func (p *PropertyGenerator) DeserializeFnName() string { if p.asIterator { return fmt.Sprintf("%s%s", deserializeIteratorMethod, p.Name.CamelName) } diff --git a/tools/exp/types/type.go b/tools/exp/props/type.go similarity index 75% rename from tools/exp/types/type.go rename to tools/exp/props/type.go index 82bfb56..6939a82 100644 --- a/tools/exp/types/type.go +++ b/tools/exp/props/type.go @@ -1,4 +1,4 @@ -package types +package props import ( "fmt" @@ -13,7 +13,7 @@ const ( extendedByMethod = "IsExtendedBy" extendsMethod = "Extends" disjointWithMethod = "IsDisjointWith" - nameMethod = "Name" + typeNameMethod = "Name" serializeMethodName = "Serialize" deserializeFnName = "Deserialize" lessFnName = "Less" @@ -26,73 +26,34 @@ func TypeInterface(pkg string) *codegen.Interface { comment := fmt.Sprintf("%s represents an ActivityStreams type.", typeInterfaceName) funcs := []codegen.FunctionSignature{ { - Name: nameMethod, + Name: typeNameMethod, Params: nil, Ret: []jen.Code{jen.String()}, - Comment: fmt.Sprintf("%s returns the ActivityStreams type name.", nameMethod), + Comment: fmt.Sprintf("%s returns the ActivityStreams type name.", typeNameMethod), }, } return codegen.NewInterface(pkg, typeInterfaceName, funcs, comment) } -// KindSerializationFuncs returns free function references that can be used to -// treat a TypeGenerator as another property's Kind. -func KindSerializationFuncs(pkg, typeName string) (ser, deser, less *codegen.Function) { - serName := fmt.Sprintf("%s%s", serializeMethodName, typeName) - ser = codegen.NewCommentedFunction( - pkg, - serName, - []jen.Code{jen.Id("s").Id(typeName)}, - []jen.Code{jen.Interface(), jen.Error()}, - []jen.Code{ - jen.Return( - jen.Id("s").Dot(serializeMethodName).Call(), - ), - }, - jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, typeName)) - deserName := fmt.Sprintf("%s%s", deserializeFnName, typeName) - deser = codegen.NewCommentedFunction( - pkg, - deserName, - []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, - []jen.Code{jen.Op("*").Id(typeName), jen.Error()}, - []jen.Code{ - // TODO - }, - jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, typeName)) - lessName := fmt.Sprintf("%s%s", lessFnName, typeName) - less = codegen.NewCommentedFunction( - pkg, - lessName, - []jen.Code{ - jen.Id("i"), - jen.Id("j").Op("*").Id(typeName), - }, - []jen.Code{jen.Bool()}, - []jen.Code{ - // TODO - }, - jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, typeName)) - return -} - // Property represents a property of an ActivityStreams type. type Property interface { PropertyName() string StructName() string + SetKindFns(name string, ser, deser, less *codegen.Function) error } // TypeGenerator represents an ActivityStream type definition to generate in Go. type TypeGenerator struct { - packageName string - typeName string - comment string - properties map[string]Property - extends []*TypeGenerator - disjoint []*TypeGenerator - extendedBy []*TypeGenerator - cacheOnce sync.Once - cachedStruct *codegen.Struct + packageName string + typeName string + comment string + properties map[string]Property + withoutProperties map[string]Property + extends []*TypeGenerator + disjoint []*TypeGenerator + extendedBy []*TypeGenerator + cacheOnce sync.Once + cachedStruct *codegen.Struct } // NewTypeGenerator creates a new generator for a specific ActivityStreams Core @@ -105,15 +66,16 @@ type TypeGenerator struct { // All TypeGenerators must be created before the Definition method is called, to // ensure that type extension, in the inheritence sense, is properly set up. func NewTypeGenerator(packageName, typeName, comment string, - properties []Property, + properties, withoutProperties []Property, extends, disjoint []*TypeGenerator) (*TypeGenerator, error) { t := &TypeGenerator{ - packageName: packageName, - typeName: typeName, - comment: comment, - properties: make(map[string]Property, len(properties)), - extends: extends, - disjoint: disjoint, + packageName: packageName, + typeName: typeName, + comment: comment, + properties: make(map[string]Property, len(properties)), + withoutProperties: make(map[string]Property, len(withoutProperties)), + extends: extends, + disjoint: disjoint, } for _, property := range properties { if _, has := t.properties[property.PropertyName()]; has { @@ -121,10 +83,24 @@ func NewTypeGenerator(packageName, typeName, comment string, } t.properties[property.PropertyName()] = property } + for _, wop := range withoutProperties { + if _, has := t.withoutProperties[wop.PropertyName()]; has { + return nil, fmt.Errorf("type already has withoutproperty with name %q", wop.PropertyName()) + } + t.withoutProperties[wop.PropertyName()] = wop + } // Complete doubly-linked extends/extendedBy lists. for _, ext := range extends { ext.extendedBy = append(ext.extendedBy, t) } + // TODO: Fix: Only notify properties whose Range is this type, not the + // properties this type has (which is wrong and currently borken) + for _, property := range t.properties { + ser, deser, less := t.kindSerializationFuncs() + if err := property.SetKindFns(t.TypeName(), ser, deser, less); err != nil { + return nil, err + } + } return t, nil } @@ -167,6 +143,12 @@ func (t *TypeGenerator) Properties() map[string]Property { return t.properties } +// WithoutProperties returns the properties that do not apply to this type, +// mapped by their property name. +func (t *TypeGenerator) WithoutProperties() map[string]Property { + return t.withoutProperties +} + // extendsFnName determines the name of the Extends function, which // determines if this ActivityStreams type extends another one. func (t *TypeGenerator) extendsFnName() string { @@ -190,7 +172,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct { t.cacheOnce.Do(func() { members := t.members() m := t.serializationMethod() - ser, deser, less := KindSerializationFuncs(t.packageName, t.TypeName()) + ser, deser, less := t.kindSerializationFuncs() t.cachedStruct = codegen.NewStruct( jen.Commentf(t.Comment()), t.TypeName(), @@ -211,7 +193,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct { return t.cachedStruct } -func (t *TypeGenerator) members() (members []jen.Code) { +func (t *TypeGenerator) allProperties() map[string]Property { p := t.properties // Properties of parents that are extended, minus DoesNotApplyTo var extends []*TypeGenerator @@ -220,8 +202,17 @@ func (t *TypeGenerator) members() (members []jen.Code) { for k, v := range ext.Properties() { p[k] = v } - // TODO: DoesNotApplyTo } + for _, ext := range t.extends { + for k, _ := range ext.WithoutProperties() { + delete(p, k) + } + } + return p +} + +func (t *TypeGenerator) members() (members []jen.Code) { + p := t.allProperties() members = make([]jen.Code, 0, len(p)) for name, property := range p { members = append(members, jen.Id(strings.Title(name)).Id(property.StructName())) @@ -234,14 +225,14 @@ func (t *TypeGenerator) members() (members []jen.Code) { func (t *TypeGenerator) nameDefinition() *codegen.Method { return codegen.NewCommentedValueMethod( t.packageName, - nameMethod, + typeNameMethod, t.TypeName(), /*params=*/ nil, []jen.Code{jen.String()}, []jen.Code{ jen.Return(jen.Lit(t.TypeName())), }, - jen.Commentf("%s returns the name of this type.", nameMethod)) + jen.Commentf("%s returns the name of this type.", typeNameMethod)) } // getAllParentExtends recursively determines all the parent types that this @@ -275,7 +266,7 @@ func (t *TypeGenerator) extendsDefinition() *codegen.Method { jen.Id("ext"), ).Op(":=").Range().Id("extensions")).Block( jen.If( - jen.Id("ext").Op("==").Id("other").Dot(nameMethod).Call(), + jen.Id("ext").Op("==").Id("other").Dot(typeNameMethod).Call(), ).Block( jen.Return(jen.True()), ), @@ -319,7 +310,7 @@ func (t *TypeGenerator) extendedByDefinition() *codegen.Function { jen.Id("ext"), ).Op(":=").Range().Id("extensions")).Block( jen.If( - jen.Id("ext").Op("==").Id("other").Dot(nameMethod).Call(), + jen.Id("ext").Op("==").Id("other").Dot(typeNameMethod).Call(), ).Block( jen.Return(jen.True()), ), @@ -363,7 +354,7 @@ func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { jen.Id("disjoint"), ).Op(":=").Range().Id("disjointWith")).Block( jen.If( - jen.Id("disjoint").Op("==").Id("other").Dot(nameMethod).Call(), + jen.Id("disjoint").Op("==").Id("other").Dot(typeNameMethod).Call(), ).Block( jen.Return(jen.True()), ), @@ -379,6 +370,8 @@ func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { jen.Commentf("%s returns true if the other provided type is disjoint with the %s type.", t.disjointWithFnName(), t.TypeName())) } +// serializationMethod returns the method needed to serialize a TypeGenerator as +// a property. func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { ser = codegen.NewCommentedValueMethod( t.packageName, @@ -388,7 +381,68 @@ func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { []jen.Code{jen.Interface(), jen.Error()}, []jen.Code{ // TODO + jen.Commentf("TODO: Serialization code for %s", t.TypeName()), }, jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", serializeMethodName)) return } + +// kindSerializationFuncs returns free function references that can be used to +// treat a TypeGenerator as another property's Kind. +func (t *TypeGenerator) kindSerializationFuncs() (ser, deser, less *codegen.Function) { + serName := fmt.Sprintf("%s%s", serializeMethodName, t.TypeName()) + ser = codegen.NewCommentedFunction( + t.packageName, + serName, + []jen.Code{jen.Id("s").Id(t.TypeName())}, + []jen.Code{jen.Interface(), jen.Error()}, + []jen.Code{ + jen.Return( + jen.Id("s").Dot(serializeMethodName).Call(), + ), + }, + jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, t.TypeName())) + deserName := fmt.Sprintf("%s%s", deserializeFnName, t.TypeName()) + deserCode := jen.Empty() + for name := range t.allProperties() { + deserCode = deserCode.Add( + jen.If( + jen.List( + jen.Id("p"), + jen.Err(), + // TODO: Qual + ).Op(":=").Qual("", deserializeFnName).Call(jen.Id("m")), + jen.Err().Op("!=").Nil(), + ).Block( + jen.Return(jen.Nil(), jen.Err()), + ).Else().Block( + jen.Id(codegen.This()).Dot(strings.Title(name)).Op(":=").Op("*").Id("p"), + )) + } + deser = codegen.NewCommentedFunction( + t.packageName, + deserName, + []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, + []jen.Code{jen.Op("*").Id(t.TypeName()), jen.Error()}, + []jen.Code{ + jen.Id(codegen.This()).Op(":=").Op("&").Id(t.TypeName()).Values(), + deserCode, + jen.Return(jen.Id(codegen.This()), jen.Nil()), + }, + jen.Commentf("%s creates a %s from a map representation that has been unmarshalled from a text or binary format.", deserName, t.TypeName())) + lessName := fmt.Sprintf("%s%s", lessFnName, t.TypeName()) + less = codegen.NewCommentedFunction( + t.packageName, + lessName, + []jen.Code{ + jen.Id("i"), + jen.Id("j").Op("*").Id(t.TypeName()), + }, + []jen.Code{jen.Bool()}, + []jen.Code{ + // TODO + jen.Commentf("TODO: Less code for %s", t.TypeName()), + }, + jen.Commentf("%s computes which %s is lesser, with an arbitrary but stable determination", lessName, t.TypeName())) + return +} From b5d927c49f05dabbd1a3782decf297467d6d5f64 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Wed, 19 Dec 2018 09:44:57 +0100 Subject: [PATCH 11/14] Properties now have serialization references to Types. This setup allows properties to recur deserializing into types as necessary, and sets the groundwork for successfully handling all kinds of JSON-LD input. --- tools/exp/convert/convert.go | 34 +++++++++++++++++++++++++- tools/exp/props/funcprop.go | 44 +++++++++++++++++----------------- tools/exp/props/nonfuncprop.go | 18 +++++++------- tools/exp/props/property.go | 9 +++---- tools/exp/props/type.go | 27 +++++++++------------ 5 files changed, 80 insertions(+), 52 deletions(-) diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index c7994cd..900f386 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -262,7 +262,12 @@ func (c Converter) convertType(t rdf.VocabularyType, if e != nil { return } - // Apply disjoint if both sides are available. + // 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. + // + // TODO: Pass in the disjoint and have the TypeGenerator complete the + // doubly-linked connection for us. for _, disj := range t.DisjointWith { if len(disj.Vocab) != 0 { // TODO: This should be fixed to handle references @@ -273,6 +278,33 @@ func (c Converter) convertType(t rdf.VocabularyType, tg.AddDisjoint(disjointType) } } + // Apply the type's KindSerializationFuncs to the property because there + // is no way for the TypeGenerator to know all properties who have a + // range of this type. + // + // TODO: Pass in these properties to the TypeGenerator constructor so it + // can build these double-links properly. Note this would also need to + // apply to referenced properties, possibly. + for _, prop := range existingFProps { + for _, kind := range prop.Kinds { + if kind.Name.LowerName == tg.TypeName() { + ser, deser, less := tg.KindSerializationFuncs() + if e = prop.SetKindFns(tg.TypeName(), ser, deser, less); e != nil { + return + } + } + } + } + for _, prop := range existingNFProps { + for _, kind := range prop.Kinds { + if kind.Name.LowerName == tg.TypeName() { + ser, deser, less := tg.KindSerializationFuncs() + if e = prop.SetKindFns(tg.TypeName(), ser, deser, less); e != nil { + return + } + } + } + } return } diff --git a/tools/exp/props/funcprop.go b/tools/exp/props/funcprop.go index 7b49bdc..860cc3e 100644 --- a/tools/exp/props/funcprop.go +++ b/tools/exp/props/funcprop.go @@ -113,7 +113,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { } methods := []*codegen.Method{ codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), kindIndexMethod, p.StructName(), /*params=*/ nil, @@ -129,7 +129,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // IsLanguageMap Method methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), isLanguageMapMethod, p.StructName(), /*params=*/ nil, @@ -154,7 +154,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // HasLanguage Method methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), hasLanguageMethod, p.StructName(), []jen.Code{jen.Id("bcp47").String()}, @@ -182,7 +182,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // GetLanguage Method methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), getLanguageMethod, p.StructName(), []jen.Code{jen.Id("bcp47").String()}, @@ -214,7 +214,7 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { // SetLanguage Method methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), setLanguageMethod, p.StructName(), []jen.Code{ @@ -273,7 +273,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ } serialize := []*codegen.Method{ codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), p.serializeFnName(), p.StructName(), /*params=*/ nil, @@ -319,7 +319,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ if p.asIterator { deserialize = append(deserialize, codegen.NewCommentedFunction( - p.packageName(), + p.PackageName(), p.DeserializeFnName(), []jen.Code{jen.Id("i").Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, @@ -335,7 +335,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ } else { deserialize = append(deserialize, codegen.NewCommentedFunction( - p.packageName(), + p.PackageName(), p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, @@ -421,7 +421,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { } if p.Kinds[0].Nilable { methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), hasMethod, p.StructName(), /*params=*/ nil, @@ -431,7 +431,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), hasMethod, p.StructName(), /*params=*/ nil, @@ -443,7 +443,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { // Get Method getComment := jen.Commentf("%s returns the value of this property. When %s returns false, %s will return any arbitrary value.", getMethod, hasMethod, getMethod) methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), p.getFnName(0), p.StructName(), /*params=*/ nil, @@ -465,7 +465,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { } if p.Kinds[0].Nilable { methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), p.setFnName(0), p.StructName(), []jen.Code{jen.Id("v").Id(p.Kinds[0].ConcreteKind)}, @@ -478,7 +478,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), p.setFnName(0), p.StructName(), []jen.Code{jen.Id("v").Id(p.Kinds[0].ConcreteKind)}, @@ -507,7 +507,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { } if p.Kinds[0].Nilable { methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), p.clearMethodName(), p.StructName(), /*params=*/ nil, @@ -517,7 +517,7 @@ func (p *FunctionalPropertyGenerator) singleTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), p.clearMethodName(), p.StructName(), /*params=*/ nil, @@ -602,7 +602,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { ) } methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), hasAnyMethodName, p.StructName(), /*params=*/ nil, @@ -626,7 +626,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { clearLine = append(clearLine, jen.Id(codegen.This()).Dot(langMapMember).Op("=").Nil()) } methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), p.clearMethodName(), p.StructName(), /*params=*/ nil, @@ -649,7 +649,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { } if kind.Nilable { methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), p.isMethodName(i), p.StructName(), /*params=*/ nil, @@ -659,7 +659,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), p.isMethodName(i), p.StructName(), /*params=*/ nil, @@ -684,7 +684,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { } if kind.Nilable { methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), p.setFnName(i), p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -697,7 +697,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { )) } else { methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), p.setFnName(i), p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -715,7 +715,7 @@ func (p *FunctionalPropertyGenerator) multiTypeFuncs() []*codegen.Method { for i, kind := range p.Kinds { getComment := jen.Commentf("%s returns the value of this property. When %s returns false, %s will return an arbitrary value.", p.getFnName(i), p.isMethodName(i), p.getFnName(i)) methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), p.getFnName(i), p.StructName(), /*params=*/ nil, diff --git a/tools/exp/props/nonfuncprop.go b/tools/exp/props/nonfuncprop.go index 0f00337..9094abd 100644 --- a/tools/exp/props/nonfuncprop.go +++ b/tools/exp/props/nonfuncprop.go @@ -90,7 +90,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { prependMethodName := fmt.Sprintf("%s%s", prependMethod, p.kindCamelName(i)) methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), prependMethodName, p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -108,7 +108,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { appendMethodName := fmt.Sprintf("%s%s", appendMethod, p.kindCamelName(i)) methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), appendMethodName, p.StructName(), []jen.Code{jen.Id("v").Id(kind.ConcreteKind)}, @@ -140,7 +140,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Remove Method methods = append(methods, codegen.NewCommentedPointerMethod( - p.packageName(), + p.PackageName(), removeMethod, p.StructName(), []jen.Code{jen.Id("idx").Int()}, @@ -176,7 +176,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Len Method methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), lenMethod, p.StructName(), /*params=*/ nil, @@ -192,7 +192,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Swap Method methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), swapMethod, p.StructName(), []jen.Code{ @@ -213,7 +213,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Less Method methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), lessMethod, p.StructName(), []jen.Code{ @@ -235,7 +235,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { // Kind Method methods = append(methods, codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), kindIndexMethod, p.StructName(), []jen.Code{jen.Id("idx").Int()}, @@ -255,7 +255,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, []*codegen.Function) { serialize := []*codegen.Method{ codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), p.serializeFnName(), p.StructName(), /*params=*/ nil, @@ -323,7 +323,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method } deserialize := []*codegen.Function{ codegen.NewCommentedFunction( - p.packageName(), + p.PackageName(), p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Id(p.StructName()), jen.Error()}, diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index 574ced8..3145b87 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -84,8 +84,8 @@ type PropertyGenerator struct { asIterator bool } -// packageName returns the name of the package for the property to be generated. -func (p *PropertyGenerator) packageName() string { +// PackageName returns the name of the package for the property to be generated. +func (p *PropertyGenerator) PackageName() string { return p.Package } @@ -96,7 +96,7 @@ func (p *PropertyGenerator) packageName() string { // // This feels very hacky. func (p *PropertyGenerator) SetKindFns(name string, ser, deser, less *codegen.Function) error { - for _, kind := range p.Kinds { + for i, kind := range p.Kinds { if kind.Name.LowerName == name { if kind.SerializeFn != nil || kind.DeserializeFn != nil || kind.LessFn != nil { return fmt.Errorf("property kind already has serialization functions set for %q", name) @@ -104,6 +104,7 @@ func (p *PropertyGenerator) SetKindFns(name string, ser, deser, less *codegen.Fu kind.SerializeFn = ser kind.DeserializeFn = deser kind.LessFn = less + p.Kinds[i] = kind return nil } } @@ -200,7 +201,7 @@ func (p *PropertyGenerator) clearMethodName() string { func (p *PropertyGenerator) commonMethods() []*codegen.Method { return []*codegen.Method{ codegen.NewCommentedValueMethod( - p.packageName(), + p.PackageName(), nameMethod, p.StructName(), /*params=*/ nil, diff --git a/tools/exp/props/type.go b/tools/exp/props/type.go index 6939a82..66437ec 100644 --- a/tools/exp/props/type.go +++ b/tools/exp/props/type.go @@ -37,6 +37,7 @@ func TypeInterface(pkg string) *codegen.Interface { // Property represents a property of an ActivityStreams type. type Property interface { + PackageName() string PropertyName() string StructName() string SetKindFns(name string, ser, deser, less *codegen.Function) error @@ -65,6 +66,9 @@ type TypeGenerator struct { // // All TypeGenerators must be created before the Definition method is called, to // ensure that type extension, in the inheritence sense, is properly set up. +// Additionally, all properties whose range is this type should have their +// SetKindFns method called with this TypeGenerator's KindSerializationFuncs for +// all code generation to correctly reference each other. func NewTypeGenerator(packageName, typeName, comment string, properties, withoutProperties []Property, extends, disjoint []*TypeGenerator) (*TypeGenerator, error) { @@ -93,14 +97,6 @@ func NewTypeGenerator(packageName, typeName, comment string, for _, ext := range extends { ext.extendedBy = append(ext.extendedBy, t) } - // TODO: Fix: Only notify properties whose Range is this type, not the - // properties this type has (which is wrong and currently borken) - for _, property := range t.properties { - ser, deser, less := t.kindSerializationFuncs() - if err := property.SetKindFns(t.TypeName(), ser, deser, less); err != nil { - return nil, err - } - } return t, nil } @@ -172,7 +168,7 @@ func (t *TypeGenerator) Definition() *codegen.Struct { t.cacheOnce.Do(func() { members := t.members() m := t.serializationMethod() - ser, deser, less := t.kindSerializationFuncs() + ser, deser, less := t.KindSerializationFuncs() t.cachedStruct = codegen.NewStruct( jen.Commentf(t.Comment()), t.TypeName(), @@ -387,9 +383,9 @@ func (t *TypeGenerator) serializationMethod() (ser *codegen.Method) { return } -// kindSerializationFuncs returns free function references that can be used to +// KindSerializationFuncs returns free function references that can be used to // treat a TypeGenerator as another property's Kind. -func (t *TypeGenerator) kindSerializationFuncs() (ser, deser, less *codegen.Function) { +func (t *TypeGenerator) KindSerializationFuncs() (ser, deser, less *codegen.Function) { serName := fmt.Sprintf("%s%s", serializeMethodName, t.TypeName()) ser = codegen.NewCommentedFunction( t.packageName, @@ -404,20 +400,19 @@ func (t *TypeGenerator) kindSerializationFuncs() (ser, deser, less *codegen.Func jen.Commentf("%s calls %s on the %s type.", serName, serializeMethodName, t.TypeName())) deserName := fmt.Sprintf("%s%s", deserializeFnName, t.TypeName()) deserCode := jen.Empty() - for name := range t.allProperties() { + for name, prop := range t.allProperties() { deserCode = deserCode.Add( jen.If( jen.List( jen.Id("p"), jen.Err(), - // TODO: Qual - ).Op(":=").Qual("", deserializeFnName).Call(jen.Id("m")), + ).Op(":=").Qual(prop.PackageName(), deserializeMethod).Call(jen.Id("m")), jen.Err().Op("!=").Nil(), ).Block( jen.Return(jen.Nil(), jen.Err()), ).Else().Block( - jen.Id(codegen.This()).Dot(strings.Title(name)).Op(":=").Op("*").Id("p"), - )) + jen.Id(codegen.This()).Dot(strings.Title(name)).Op("=").Op("*").Id("p"), + ).Line()) } deser = codegen.NewCommentedFunction( t.packageName, From 43ab7d319c2857b9327d15d9a496da99b889fdea Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Thu, 20 Dec 2018 23:03:13 +0100 Subject: [PATCH 12/14] Alphabetical order of type members and better extends API. --- tools/exp/props/type.go | 47 ++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/tools/exp/props/type.go b/tools/exp/props/type.go index 66437ec..a0ca697 100644 --- a/tools/exp/props/type.go +++ b/tools/exp/props/type.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" + "sort" "strings" "sync" ) @@ -41,6 +42,7 @@ type Property interface { PropertyName() string StructName() string SetKindFns(name string, ser, deser, less *codegen.Function) error + DeserializeFnName() string } // TypeGenerator represents an ActivityStream type definition to generate in Go. @@ -174,10 +176,10 @@ func (t *TypeGenerator) Definition() *codegen.Struct { t.TypeName(), []*codegen.Method{ t.nameDefinition(), - t.extendsDefinition(), m, }, []*codegen.Function{ + t.extendsDefinition(), t.extendedByDefinition(), t.disjointWithDefinition(), ser, @@ -207,11 +209,33 @@ func (t *TypeGenerator) allProperties() map[string]Property { return p } +// sortedProperty is a slice of Properties that implements the Sort interface. +type sortedProperty []Property + +func (s sortedProperty) Less(i, j int) bool { + return s[i].PropertyName() < s[j].PropertyName() +} + +func (s sortedProperty) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s sortedProperty) Len() int { + return len(s) +} + func (t *TypeGenerator) members() (members []jen.Code) { p := t.allProperties() + // Sort the properties for readability + sortedMembers := make(sortedProperty, 0, len(p)) + for _, property := range p { + sortedMembers = append(sortedMembers, property) + } + sort.Sort(sortedMembers) + // Convert to jen.Code members = make([]jen.Code, 0, len(p)) - for name, property := range p { - members = append(members, jen.Id(strings.Title(name)).Id(property.StructName())) + for _, property := range sortedMembers { + members = append(members, jen.Id(strings.Title(property.PropertyName())).Qual(property.PackageName(), property.StructName())) } return } @@ -241,18 +265,18 @@ func (t *TypeGenerator) getAllParentExtends(s []*TypeGenerator, tg *TypeGenerato return s } -// extendsDefinition generates the golang method for determining if this +// extendsDefinition generates the golang function for determining if this // ActivityStreams type extends another type. It requires the Type interface. -func (t *TypeGenerator) extendsDefinition() *codegen.Method { +func (t *TypeGenerator) extendsDefinition() *codegen.Function { var extends []*TypeGenerator extends = t.getAllParentExtends(extends, t) - extendNames := make([]string, 0, len(extends)) + extendNames := make(map[string]struct{}, len(extends)) for _, ext := range extends { - extendNames = append(extendNames, ext.TypeName()) + extendNames[ext.TypeName()] = struct{}{} } extensions := make([]jen.Code, len(extendNames)) - for i, e := range extendNames { - extensions[i] = jen.Lit(e) + for e := range extendNames { + extensions = append(extensions, jen.Lit(e)) } impl := []jen.Code{jen.Comment("Shortcut implementation: this does not extend anything."), jen.Return(jen.False())} if len(extensions) > 0 { @@ -269,10 +293,9 @@ func (t *TypeGenerator) extendsDefinition() *codegen.Method { ), jen.Return(jen.False())} } - return codegen.NewCommentedValueMethod( + return codegen.NewCommentedFunction( t.packageName, t.extendsFnName(), - t.TypeName(), []jen.Code{jen.Id("other").Id(typeInterfaceName)}, []jen.Code{jen.Bool()}, impl, @@ -406,7 +429,7 @@ func (t *TypeGenerator) KindSerializationFuncs() (ser, deser, less *codegen.Func jen.List( jen.Id("p"), jen.Err(), - ).Op(":=").Qual(prop.PackageName(), deserializeMethod).Call(jen.Id("m")), + ).Op(":=").Qual(prop.PackageName(), prop.DeserializeFnName()).Call(jen.Id("m")), jen.Err().Op("!=").Nil(), ).Block( jen.Return(jen.Nil(), jen.Err()), From b0937b7dec1d1e351b272662ad375541042e19d2 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Thu, 20 Dec 2018 23:19:51 +0100 Subject: [PATCH 13/14] Add TODOs for more type improvements, add IsExtending convenience. --- tools/exp/convert/convert.go | 2 +- tools/exp/props/type.go | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go index 900f386..8e868e9 100644 --- a/tools/exp/convert/convert.go +++ b/tools/exp/convert/convert.go @@ -101,7 +101,7 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { } file := jen.NewFilePath(pkg) s, t := i.Definitions() - file.Add(s.Definition()).Add(t.Definition()) + file.Add(s.Definition()).Line().Add(t.Definition()) f = append(f, &File{ F: file, FileName: fmt.Sprintf("gen_%s.go", i.PropertyName()), diff --git a/tools/exp/props/type.go b/tools/exp/props/type.go index a0ca697..682e58f 100644 --- a/tools/exp/props/type.go +++ b/tools/exp/props/type.go @@ -9,9 +9,13 @@ import ( "sync" ) +// TODO: Prevent circular dependency by somehow abstracting the requisite +// functions between props and types. + const ( typeInterfaceName = "Type" extendedByMethod = "IsExtendedBy" + extendingMethod = "IsExtending" extendsMethod = "Extends" disjointWithMethod = "IsDisjointWith" typeNameMethod = "Name" @@ -171,16 +175,18 @@ func (t *TypeGenerator) Definition() *codegen.Struct { members := t.members() m := t.serializationMethod() ser, deser, less := t.KindSerializationFuncs() + extendsFn, extendsMethod := t.extendsDefinition() t.cachedStruct = codegen.NewStruct( jen.Commentf(t.Comment()), t.TypeName(), []*codegen.Method{ t.nameDefinition(), + extendsMethod, m, }, []*codegen.Function{ - t.extendsDefinition(), t.extendedByDefinition(), + extendsFn, t.disjointWithDefinition(), ser, deser, @@ -267,7 +273,7 @@ func (t *TypeGenerator) getAllParentExtends(s []*TypeGenerator, tg *TypeGenerato // extendsDefinition generates the golang function for determining if this // ActivityStreams type extends another type. It requires the Type interface. -func (t *TypeGenerator) extendsDefinition() *codegen.Function { +func (t *TypeGenerator) extendsDefinition() (*codegen.Function, *codegen.Method) { var extends []*TypeGenerator extends = t.getAllParentExtends(extends, t) extendNames := make(map[string]struct{}, len(extends)) @@ -293,13 +299,26 @@ func (t *TypeGenerator) extendsDefinition() *codegen.Function { ), jen.Return(jen.False())} } - return codegen.NewCommentedFunction( + f := codegen.NewCommentedFunction( t.packageName, t.extendsFnName(), []jen.Code{jen.Id("other").Id(typeInterfaceName)}, []jen.Code{jen.Bool()}, impl, jen.Commentf("%s returns true if the %s type extends from the other type.", t.extendsFnName(), t.TypeName())) + m := codegen.NewCommentedValueMethod( + t.packageName, + extendingMethod, + t.TypeName(), + []jen.Code{jen.Id("other").Id(typeInterfaceName)}, + []jen.Code{jen.Bool()}, + []jen.Code{ + jen.Return( + jen.Id(t.extendsFnName()).Call(jen.Id("other")), + ), + }, + jen.Commentf("%s returns true if the %s type extends from the other type.", extendingMethod, t.TypeName())) + return f, m } // getAllChildrenExtendBy recursivley determines all the child types that this @@ -359,6 +378,8 @@ func (t *TypeGenerator) getAllDisjointWith(s []string) { // another ActivityStreams type is disjoint with this type. It requires the Type // interface. func (t *TypeGenerator) disjointWithDefinition() *codegen.Function { + // TODO: Inherit disjoint from parent and the other extended types of + // the other. var disjointNames []string t.getAllDisjointWith(disjointNames) disjointWith := make([]jen.Code, len(disjointNames)) From 0a7539cdf70e09b097095384e06ef5a52d9fdfc9 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Mon, 24 Dec 2018 07:51:51 +0100 Subject: [PATCH 14/14] Add CODE_OF_CONDUCT.md. It stinks that I'm the only team member, so complaints about me go directly to me. Which doesn't seem fair for victims. --- CODE_OF_CONDUCT.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ab8abbc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,72 @@ +# Contributor Covenant Code Of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and +expression, level of experience, education, socio-economic status, nationality, +personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others’ private information, such as a physical or electronic +address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at cjslep@gmail.com. All complaints will +be reviewed and investigated and will result in a response that is deemed +necessary and appropriate to the circumstances. The project team is obligated to +maintain confidentiality with regard to the reporter of an incident. Further +details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project’s leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +[https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)