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) diff --git a/tools/exp/convert/convert.go b/tools/exp/convert/convert.go new file mode 100644 index 0000000..8e868e9 --- /dev/null +++ b/tools/exp/convert/convert.go @@ -0,0 +1,562 @@ +package convert + +import ( + "fmt" + "github.com/cjslep/activity/tools/exp/props" + "github.com/cjslep/activity/tools/exp/rdf" + "github.com/dave/jennifer/jen" + "strings" +) + +type File struct { + F *jen.File + FileName string + Directory string +} + +type vocabulary struct { + Kinds map[string]*props.Kind + FProps map[string]*props.FunctionalPropertyGenerator + NFProps map[string]*props.NonFunctionalPropertyGenerator + Types map[string]*props.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]*props.TypeGenerator, 0), + } +} + +type PropertyPackagePolicy int + +const ( + PropertyFlatUnderRoot PropertyPackagePolicy = iota + PropertyIndividualUnderRoot + PropertyFlatUnderVocabularyRoot +) + +type TypePackagePolicy int + +const ( + TypeFlatUnderRoot TypePackagePolicy = iota + TypeIndividualUnderRoot + TypeFlatUnderVocabularyRoot +) + +type Converter struct { + Registry *rdf.RDFRegistry + VocabularyRoot string + PropertyPackagePolicy PropertyPackagePolicy + PropertyPackageRoot string + TypePackagePolicy TypePackagePolicy + TypePackageRoot string +} + +func (c Converter) Convert(p *rdf.ParsedVocabulary) (f []*File, e error) { + var v vocabulary + v, e = c.convertVocabulary(p) + if e != nil { + return + } + f, e = c.convertToFiles(v) + return +} + +func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { + for _, _ = range v.Kinds { + // TODO: Implement + } + 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()).Line().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 +} + +// 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 { + 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 + } + } + // Instead of building a dependency tree, naively keep iterating through + // 'allTypes' until it is empty (good) or we get stuck (return error). + allTypes := make([]rdf.VocabularyType, 0, len(p.Vocab.Types)) + for _, t := range p.Vocab.Types { + allTypes = append(allTypes, t) + } + for { + if len(allTypes) == 0 { + break + } + stuck := true + for i, t := range allTypes { + if allExtendsAreIn(t, v.Types) { + var tg *props.TypeGenerator + tg, e = c.convertType(t, p.Vocab, v.FProps, v.NFProps, v.Types) + if e != nil { + return + } + v.Types[t.Name] = tg + stuck = false + // Delete the one we just did. + allTypes[i] = allTypes[len(allTypes)-1] + allTypes = allTypes[:len(allTypes)-1] + break + } + } + if stuck { + e = fmt.Errorf("converting props 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]*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 []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 + } + } + 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 []*props.TypeGenerator + for _, ex := range t.Extends { + if len(ex.Vocab) != 0 { + // TODO: This should be fixed to handle references + e = fmt.Errorf("unhandled use case: type extends another type outside its vocabulary") + return + } else { + ext = append(ext, existingTypes[ex.Name]) + } + } + tg, e = props.NewTypeGenerator( + pkg, + c.convertTypeToName(t), + t.Notes, + p, + wop, + ext, + nil) + if e != nil { + return + } + // 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 + 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) + } + } + // 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 +} + +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, e error) { + k = &props.Kind{ + 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 +} + +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, + 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 { + 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 + k = append(k, *kind) + } + } else { + var url string + url, e = c.Registry.ResolveAlias(r.Vocab) + if e != nil { + return + } + refVocab, ok := refs[url] + if !ok { + e = fmt.Errorf("references do not contain %s", url) + return + } + if val, ok := refVocab.Values[r.Name]; !ok { + // It is a Type of the vocabulary instead + if t, ok := refVocab.Types[r.Name]; !ok { + e = fmt.Errorf("cannot find kind with name %q in %s", r.Name, url) + return + } else { + var kt *props.Kind + kt, e = c.convertTypeToKind(t) + if e != nil { + return + } + k = append(k, *kt) + } + } else { + // It is a Value of the vocabulary + k = append(k, *c.convertValue(val)) + } + } + } + return +} + +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 PropertyFlatUnderRoot: + pkg = c.PropertyPackageRoot + case PropertyIndividualUnderRoot: + pkg = v.Name + case PropertyFlatUnderVocabularyRoot: + pkg = c.VocabularyRoot + default: + e = fmt.Errorf("unrecognized PropertyPackagePolicy: %v", c.PropertyPackagePolicy) + } + return +} + +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) + 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 *props.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.TypeName()) + case TypeFlatUnderVocabularyRoot: + pkg = c.VocabularyRoot + default: + e = fmt.Errorf("unrecognized TypePackagePolicy: %v", c.TypePackagePolicy) + } + return +} + +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.PropertyName()) + case PropertyFlatUnderVocabularyRoot: + pkg = c.VocabularyRoot + 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] == '*' +} + +func allExtendsAreIn(t rdf.VocabularyType, v map[string]*props.TypeGenerator) bool { + for _, e := range t.Extends { + if len(e.Vocab) != 0 { + // TODO: This should be fixed to handle references + return false + } else if _, ok := v[e.Name]; !ok { + return false + } + } + return true +} 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 4b8ea54..860cc3e 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" ) @@ -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,24 +319,24 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ if p.asIterator { deserialize = append(deserialize, codegen.NewCommentedFunction( - p.packageName(), - p.deserializeFnName(), + p.PackageName(), + p.DeserializeFnName(), []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(), ), }, - 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.PackageName(), + p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ @@ -349,14 +349,14 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method, [ ), jen.Id("ok"), ).Block( - deserializeFns.Add(p.unknownDeserializeCode()), + p.addUnknownDeserializeCode(deserializeFns), ), jen.Return( jen.Nil(), 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 @@ -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, @@ -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/nonfuncprop.go b/tools/exp/props/nonfuncprop.go index a67ffde..9094abd 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" ) @@ -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, @@ -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(), @@ -323,8 +323,8 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() ([]*codegen.Method } deserialize := []*codegen.Function{ codegen.NewCommentedFunction( - p.packageName(), - p.deserializeFnName(), + p.PackageName(), + 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 994b6de..3145b87 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 @@ -74,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 @@ -82,11 +84,33 @@ 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 } +// 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 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) + } + kind.SerializeFn = ser + kind.DeserializeFn = deser + kind.LessFn = less + p.Kinds[i] = kind + 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 { @@ -103,9 +127,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) } @@ -177,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/types/type.go b/tools/exp/props/type.go similarity index 50% rename from tools/exp/types/type.go rename to tools/exp/props/type.go index 9f61385..682e58f 100644 --- a/tools/exp/types/type.go +++ b/tools/exp/props/type.go @@ -1,18 +1,27 @@ -package types +package props import ( "fmt" + "github.com/cjslep/activity/tools/exp/codegen" "github.com/dave/jennifer/jen" - "github.com/go-fed/activity/tools/exp/codegen" + "sort" + "strings" "sync" ) +// TODO: Prevent circular dependency by somehow abstracting the requisite +// functions between props and types. + const ( - typeInterfaceName = "Type" - extendedByMethod = "IsExtendedBy" - extendsMethod = "Extends" - disjointWithMethod = "IsDisjointWith" - nameMethod = "Name" + typeInterfaceName = "Type" + extendedByMethod = "IsExtendedBy" + extendingMethod = "IsExtending" + extendsMethod = "Extends" + disjointWithMethod = "IsDisjointWith" + typeNameMethod = "Name" + serializeMethodName = "Serialize" + deserializeFnName = "Deserialize" + lessFnName = "Less" ) // TypeInterface returns the Type Interface that is needed for ActivityStream @@ -22,10 +31,10 @@ 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) @@ -33,21 +42,25 @@ 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 + DeserializeFnName() string } // 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 @@ -59,16 +72,20 @@ 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 []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 { @@ -76,6 +93,12 @@ 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) @@ -83,6 +106,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 @@ -111,6 +139,18 @@ 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 +} + +// 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 { @@ -132,58 +172,117 @@ 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 { - members = append(members, jen.Id(name).Id(property.StructName())) - } + 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(), - t.extendsDefinition(), + extendsMethod, + m, }, []*codegen.Function{ t.extendedByDefinition(), + extendsFn, t.disjointWithDefinition(), + ser, + deser, + less, }, members) }) return t.cachedStruct } +func (t *TypeGenerator) allProperties() map[string]Property { + 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 + } + } + for _, ext := range t.extends { + for k, _ := range ext.WithoutProperties() { + delete(p, k) + } + } + 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 _, property := range sortedMembers { + members = append(members, jen.Id(strings.Title(property.PropertyName())).Qual(property.PackageName(), property.StructName())) + } + return +} + // nameDefinition generates the golang method for returning the ActivityStreams // type name. 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 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 +// 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 { - var extendNames []string - t.getAllParentExtends(extendNames, t) +func (t *TypeGenerator) extendsDefinition() (*codegen.Function, *codegen.Method) { + var extends []*TypeGenerator + extends = t.getAllParentExtends(extends, t) + extendNames := make(map[string]struct{}, len(extends)) + for _, ext := range extends { + 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 { @@ -193,21 +292,33 @@ 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()), ), ), jen.Return(jen.False())} } - return codegen.NewCommentedValueMethod( + f := codegen.NewCommentedFunction( t.packageName, t.extendsFnName(), - t.TypeName(), []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 @@ -237,7 +348,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()), ), @@ -267,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)) @@ -281,7 +394,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()), ), @@ -296,3 +409,79 @@ 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())) } + +// serializationMethod returns the method needed to serialize a TypeGenerator as +// a property. +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("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, prop := range t.allProperties() { + deserCode = deserCode.Add( + jen.If( + jen.List( + jen.Id("p"), + jen.Err(), + ).Op(":=").Qual(prop.PackageName(), prop.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"), + ).Line()) + } + 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 +} diff --git a/tools/exp/rdf/data.go b/tools/exp/rdf/data.go index 86fad9e..ecc9046 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" ) @@ -16,7 +17,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 { @@ -89,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 { @@ -99,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 } @@ -111,7 +125,7 @@ func (v *VocabularyValue) SetURI(s string) error { var ( _ NameSetter = &VocabularyValue{} - _ nameGetter = &VocabularyValue{} + _ NameGetter = &VocabularyValue{} _ URISetter = &VocabularyValue{} ) @@ -121,21 +135,21 @@ type VocabularyType struct { 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 + Extends []VocabularyReference Examples []VocabularyExample + Properties []VocabularyReference + WithoutProperties []VocabularyReference } 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) { v.Name = s } -func (v *VocabularyType) GetName() string { +func (v VocabularyType) GetName() string { return v.Name } @@ -155,7 +169,7 @@ func (v *VocabularyType) AddExample(e *VocabularyExample) { var ( _ NameSetter = &VocabularyType{} - _ nameGetter = &VocabularyType{} + _ NameGetter = &VocabularyType{} _ URISetter = &VocabularyType{} _ NotesSetter = &VocabularyType{} _ ExampleAdder = &VocabularyType{} @@ -164,12 +178,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,14 +192,14 @@ 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) { v.Name = s } -func (v *VocabularyProperty) GetName() string { +func (v VocabularyProperty) GetName() string { return v.Name } @@ -204,7 +219,7 @@ func (v *VocabularyProperty) AddExample(e *VocabularyExample) { var ( _ NameSetter = &VocabularyProperty{} - _ nameGetter = &VocabularyProperty{} + _ NameGetter = &VocabularyProperty{} _ URISetter = &VocabularyProperty{} _ NotesSetter = &VocabularyProperty{} _ ExampleAdder = &VocabularyProperty{} @@ -218,11 +233,15 @@ 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 } -func (v *VocabularyExample) GetName() string { +func (v VocabularyExample) GetName() string { return v.Name } @@ -234,7 +253,7 @@ func (v *VocabularyExample) SetURI(s string) error { var ( _ NameSetter = &VocabularyExample{} - _ nameGetter = &VocabularyExample{} + _ NameGetter = &VocabularyExample{} _ URISetter = &VocabularyExample{} ) @@ -247,11 +266,15 @@ 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 } -func (v *VocabularyReference) GetName() string { +func (v VocabularyReference) GetName() string { return v.Name } @@ -263,6 +286,6 @@ func (v *VocabularyReference) SetURI(s string) error { var ( _ NameSetter = &VocabularyReference{} - _ nameGetter = &VocabularyReference{} + _ NameGetter = &VocabularyReference{} _ URISetter = &VocabularyReference{} ) 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/ontology.go b/tools/exp/rdf/ontology.go index 2aa7994..c2eb11a 100644 --- a/tools/exp/rdf/ontology.go +++ b/tools/exp/rdf/ontology.go @@ -2,17 +2,61 @@ package rdf import ( "fmt" + "github.com/cjslep/activity/tools/exp/codegen" + "github.com/dave/jennifer/jen" + "net/url" "strings" ) const ( rdfSpec = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" - langstringSpec = "langstring" + langstringSpec = "langString" propertySpec = "Property" - valueSpec = "value" ) -type RDFOntology struct{} +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 { + Package string + alias string +} func (o *RDFOntology) SpecURI() string { return rdfSpec @@ -23,12 +67,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{pkg: o.Package, alias: o.alias}, }, &AliasedDelegate{ Spec: rdfSpec, @@ -36,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 } @@ -53,7 +92,7 @@ func (o *RDFOntology) LoadSpecificAsAlias(alias, name string) ([]RDFNode, error) Spec: "", Alias: "", Name: alias, - Delegate: &langstring{}, + Delegate: &langstring{pkg: o.Package, alias: o.alias}, }, }, nil case propertySpec: @@ -65,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) } @@ -86,18 +116,19 @@ func (o *RDFOntology) GetByName(name string) (RDFNode, error) { name = strings.TrimPrefix(name, o.SpecURI()) switch name { case langstringSpec: - return &langstring{}, 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) } var _ RDFNode = &langstring{} -type langstring struct{} +type langstring struct { + alias string + pkg string +} func (l *langstring) Enter(key string, ctx *ParsingContext) (bool, error) { return true, fmt.Errorf("rdf langstring cannot be entered") @@ -108,8 +139,47 @@ 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 + } + } + } + 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{} @@ -135,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/parse.go b/tools/exp/rdf/parse.go index caf4c26..dd9d30a 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 } @@ -133,12 +133,182 @@ 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. +// +// 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 { + 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. +// +// 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. @@ -148,7 +318,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..0d42074 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,12 @@ func (r *RDFRegistry) getNode(s string) (n RDFNode, e error) { return } } + +// resolveAlias turns an alias into its full qualifier for the ontology. +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..d1e43ee 100644 --- a/tools/exp/rdf/xsd/ontology.go +++ b/tools/exp/rdf/xsd/ontology.go @@ -3,25 +3,148 @@ package xsd import ( "fmt" "github.com/cjslep/activity/tools/exp/rdf" + "github.com/dave/jennifer/jen" + "net/url" "strings" ) -type XMLOntology struct{} +const ( + xmlSpec = "http://www.w3.org/2001/XMLSchema#" + anyURISpec = "anyURI" + dateTimeSpec = "dateTime" + floatSpec = "float" + stringSpec = "string" + booleanSpec = "boolean" + nonNegativeIntegerSpec = "nonNegativeInteger" + durationSpec = "duration" +) + +type XMLOntology struct { + Package string +} 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{pkg: o.Package}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: dateTimeSpec, + Delegate: &dateTime{pkg: o.Package}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: floatSpec, + Delegate: &float{pkg: o.Package}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: stringSpec, + Delegate: &xmlString{pkg: o.Package}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: booleanSpec, + Delegate: &boolean{pkg: o.Package}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: nonNegativeIntegerSpec, + Delegate: &nonNegativeInteger{pkg: o.Package}, + }, + &rdf.AliasedDelegate{ + Spec: xmlSpec, + Alias: s, + Name: durationSpec, + Delegate: &duration{pkg: o.Package}, + }, + }, 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{pkg: o.Package}, + }, + }, nil + case dateTimeSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &dateTime{pkg: o.Package}, + }, + }, nil + case floatSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &float{pkg: o.Package}, + }, + }, nil + case stringSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &xmlString{pkg: o.Package}, + }, + }, nil + case booleanSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &boolean{pkg: o.Package}, + }, + }, nil + case nonNegativeIntegerSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &nonNegativeInteger{pkg: o.Package}, + }, + }, nil + case durationSpec: + return []rdf.RDFNode{ + &rdf.AliasedDelegate{ + Spec: "", + Alias: "", + Name: alias, + Delegate: &duration{pkg: o.Package}, + }, + }, 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 +154,405 @@ 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{pkg: o.Package}, nil + case dateTimeSpec: + return &dateTime{pkg: o.Package}, nil + case floatSpec: + return &float{pkg: o.Package}, nil + case stringSpec: + return &xmlString{pkg: o.Package}, nil + case booleanSpec: + return &boolean{pkg: o.Package}, nil + case nonNegativeIntegerSpec: + return &nonNegativeInteger{pkg: o.Package}, nil + case durationSpec: + 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 { + pkg string +} + +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: anyURISpec, + 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 + } + } + return true, nil +} + +var _ rdf.RDFNode = &dateTime{} + +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") +} + +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{}", + 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 + } + } + return true, nil +} + +var _ rdf.RDFNode = &float{} + +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") +} + +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", + 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 + } + } + return true, nil +} + +var _ rdf.RDFNode = &xmlString{} + +type xmlString struct { + pkg string +} + +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 (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) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: stringSpec, + 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 + } + } + return true, nil +} + +var _ rdf.RDFNode = &boolean{} + +type boolean struct { + pkg string +} + +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 (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) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: booleanSpec, + URI: u, + 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 + } + } + return true, nil +} + +var _ rdf.RDFNode = &nonNegativeInteger{} + +type nonNegativeInteger struct { + pkg string +} + +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 (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) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: nonNegativeIntegerSpec, + 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 + } + } + return true, nil +} + +var _ rdf.RDFNode = &duration{} + +type duration struct { + pkg string +} + +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 (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) + if err != nil { + return true, err + } + val := &rdf.VocabularyValue{ + Name: durationSpec, + 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 + } + } + return true, nil +} diff --git a/tools/exp/spec.json b/tools/exp/spec.json index 0a9b349..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": { @@ -1134,11 +1133,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 +1157,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 +1353,7 @@ "subClassOf": { "type": "owl:Class", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection", - "name": "OrderedCollectionCollectionPage" + "name": "CollectionPage" }, "disjointWith": [], "name": "OrderedCollectionPage", @@ -1774,7 +1763,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 +1802,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" @@ -2698,11 +2687,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" @@ -2804,11 +2800,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" @@ -3022,7 +3025,7 @@ "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", "name": "Link" }, - "xsd:datetime", + "xsd:dateTime", "xsd:boolean" ] }, @@ -3202,16 +3205,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 +3685,7 @@ "range": { "type": "owl:Class", "unionOf": [ - "xsd:anyuri", + "xsd:anyURI", { "type": "owl:Class", "url": "https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link", @@ -3820,7 +3825,7 @@ "type": "owl:Class", "unionOf": [ "xsd:string", - "rdf:langstring" + "rdf:langString" ] }, "name": "content", @@ -3877,7 +3882,7 @@ "type": "owl:Class", "unionOf": [ "xsd:string", - "rdf:langstring" + "rdf:langString" ] }, "name": "name", @@ -3947,7 +3952,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" @@ -3981,7 +3986,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" @@ -4221,7 +4226,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" @@ -4256,7 +4261,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" @@ -4291,7 +4296,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" @@ -4406,7 +4411,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" @@ -4457,7 +4462,7 @@ "type": "owl:Class", "unionOf": [ "xsd:string", - "rdf:langstring" + "rdf:langString" ] }, "name": "summary", @@ -4501,7 +4506,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" @@ -4538,13 +4543,8 @@ "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:anyuri" + "xsd:string", + "xsd:anyURI" ] }, "name": "units", @@ -4580,7 +4580,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" @@ -4614,7 +4614,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" @@ -4817,7 +4817,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"