2018-10-20 05:44:13 +09:00
package main
import (
2018-11-29 05:40:11 +09:00
"encoding/json"
"flag"
2018-10-20 05:44:13 +09:00
"fmt"
2019-01-29 05:51:12 +09:00
"github.com/go-fed/activity/astool/convert"
"github.com/go-fed/activity/astool/gen"
"github.com/go-fed/activity/astool/rdf"
"github.com/go-fed/activity/astool/rdf/owl"
"github.com/go-fed/activity/astool/rdf/rdfs"
"github.com/go-fed/activity/astool/rdf/rfc"
"github.com/go-fed/activity/astool/rdf/schema"
"github.com/go-fed/activity/astool/rdf/xsd"
2018-11-29 05:40:11 +09:00
"io/ioutil"
2018-12-10 05:23:32 +09:00
"os"
"strings"
2018-11-29 05:40:11 +09:00
)
2019-01-27 23:12:49 +09:00
const (
pathFlag = "path"
specFlag = "spec"
2019-01-28 00:20:45 +09:00
helpText = `
2019-01-29 05:51:12 +09:00
The ActivityStreams tool ( astool ) is used to generate ActivityStreams types ,
properties , and values from an OWL2 RDF specification . The tool generates the
code necessary to create interfaces and functions that solve the problems of
serialization & deserialization of functional and nonfunctional properties ,
serialization & deserialization of types , navigating the extends / disjoint
hierarchy , and resolving an arbitrary ActivityStreams into a concrete Go type .
2019-01-28 00:20:45 +09:00
The tool generates files in the current working directory , and creates
subpackages as needed . To generate the code for a specification , pass the OWL
ontology defined as JSON - LD to the tool :
2019-01-29 05:51:12 +09:00
astool - spec specification . jsonld
2019-01-28 00:20:45 +09:00
The @ context provided in the ActivityStreams specification may be insufficient
for this tool to use to generate code . However , if this tool is able to use the
JSON - LD specification to generate the code , then it should also be compatible
with the @ context .
This tool will automatically detect the correct Go prefix path to use if used
in a subdirectory under GOPATH . If used outside of GOPATH , the prefix to the
current working directory must be provided :
2019-01-29 05:51:12 +09:00
astool - spec specification . jsonld - path path / to / my / module / cwd
2019-01-28 00:20:45 +09:00
If a specification builds off of a previous specification , they must be provided
in the order of root to dependency , with the ActivityStreams Core & Extended
Types specification as the root :
2019-01-29 05:51:12 +09:00
astool - spec activitystreams . jsonld - spec derived_extension . jsonld
2019-01-28 00:20:45 +09:00
The following directories are generated in the current working directory ( cwd )
given a particular specification for a < vocabulary > :
cwd /
gen_doc . go
- Package level documentation .
gen_init . go
- Init function definitions .
gen_manager . go
- Definition of Manager , which is responsible for dependency
injection of concrete values at runtime for deserialization .
gen_pkg_ < vocabulary > _disjoint . go
- Functions determining the "disjointedness" of ActivityStreams
types in the specified vocabulary .
gen_pkg_ < vocabulary > _extendedby . go
- Functions determining the parent - to - child "extends" of
ActivityStreams types in the specified vocabulary .
gen_pkg_ < vocabulary > _extends . go
- Functions determining the child - to - parent "extends" of
ActivityStreams types in the specified vocabulary .
gen_pkg_ < vocabulary > _property_constructors . go
- Constructors of properties in the specified vocabulary .
gen_pkg_ < vocabulary > _type_constructors . go
- Constructors of types in the specified vocabulary .
resolver /
gen_type_resolver . go
- Resolves arbitrary ActivityStream objects by type .
gen_interface_resolver . go
- Resolves arbitrary ActivityStream objects by their assertable
interfaces .
gen_type_predicated_resolver . go
- Conditionally resolves based on the ActivityStream object ' s
type .
gen_interface_predicated_resolver . go
- Conditionally resolves based on the ACtivityStream ' s
assertable interfaces .
gen_resolver_utils . go
- Functions aiding in handling resolver errors .
vocab /
gen_doc . go
- Package level documentation .
gen_pkg . go
- Generic interface definition .
gen_property_ < property > _interface . go
- Interface definition of a property .
- NOTE : Application developers should prefer using these
interfaces over the concrete types defined in "impl" .
gen_type_ < type > _interface . go
- Interface definition of a type .
- NOTE : Application developers should prefer using these
interfaces over the concrete types defined in "impl" .
values /
< value > /
- Contains RDF values and their serialization , deserialization ,
and comparison methods .
impl /
< vocabulary > /
- Implementation of the vocabulary .
- NOTE : Application developers should strongly prefer using the
interfaces in "vocab" over these .
This tool is geared for three kinds of developers :
1 ) Application developers can use the tool to generate the native Go types
needed to build an application .
2 ) Developers wishing to extend ActivityStreams may use the tool to evaluate
their OWL definition of their new ActivityStreams types and properties to
rapidly prototype in Go code .
3 ) Finally , developers wishing to provide an alternate implementation to go - fed
can target the same interfaces generated by this tool , and create a fork that
allows the generated Manager and constructors to inject their concrete type
into any existing application using go - fed .
The tool relies on built - in knowledge of several ontologies : RDF , RDFS , OWL ,
Schema . org , XML , and a few RFCs . However , this tool doesn ' t have complete
knowledge of all of these ontologies . It may error out because a provided
specification uses a definition that the tool doesn ' t currently know . In such a
case , please file an issue at https : //github.com/go-fed/activity in order to
include the missing definition .
2019-01-29 05:51:12 +09:00
Experimental support for generating the code as a module is provided by settting
the ' path ' flag , which will prefix all generated code with the ' path ' :
astool - spec specification . jsonld - path mymodule
2019-01-28 00:20:45 +09:00
`
2019-01-27 23:12:49 +09:00
)
2019-01-27 20:15:57 +09:00
// Global registry of "known" RDF ontologies. This manages the built-in
// knowledge of how to parse specific linked data documents. It may be cloned
// in the course of processing a JSON-LD document, due to "@context" dictating
// certain ontologies being aliased in some specifications and not others.
2018-11-29 05:40:11 +09:00
var registry * rdf . RDFRegistry
2019-01-27 20:15:57 +09:00
// mustAddOntology ensures that the registry global variable is not nil, and
// then adds the specific ontology or panics if it cannot.
2018-11-29 05:40:11 +09:00
func mustAddOntology ( o rdf . Ontology ) {
if registry == nil {
registry = rdf . NewRDFRegistry ( )
}
if err := registry . AddOntology ( o ) ; err != nil {
panic ( err )
}
}
2019-01-27 20:15:57 +09:00
// At init time, get our built-in knowledge of OWL and other RDF ontologies
// into the registry, before main executes.
2018-11-29 05:40:11 +09:00
func init ( ) {
2019-01-28 00:20:45 +09:00
flag . Usage = func ( ) {
fmt . Fprintf (
flag . CommandLine . Output ( ) ,
helpText )
flag . PrintDefaults ( )
}
2018-12-10 05:23:32 +09:00
mustAddOntology ( & xsd . XMLOntology { Package : "xml" } )
2018-11-29 05:40:11 +09:00
mustAddOntology ( & owl . OWLOntology { } )
2018-12-10 05:23:32 +09:00
mustAddOntology ( & rdf . RDFOntology { Package : "rdf" } )
2018-11-29 05:40:11 +09:00
mustAddOntology ( & rdfs . RDFSchemaOntology { } )
mustAddOntology ( & schema . SchemaOntology { } )
2019-01-06 20:27:18 +09:00
mustAddOntology ( & rfc . RFCOntology { Package : "rfc" } )
2018-11-29 05:40:11 +09:00
}
2019-01-27 20:15:57 +09:00
// list is a flag-friendly comma-separated list of strings. Also allows multiple
// definitions of the flag to not overwrite each other and instead result in a
// list of strings.
//
// The values of the flag cannot contain commas within them because the value
// will be split into two.
2018-12-10 05:23:32 +09:00
type list [ ] string
2019-01-27 20:15:57 +09:00
// String turns this list into a single comma-separated string.
2018-12-10 05:23:32 +09:00
func ( l * list ) String ( ) string {
return strings . Join ( * l , "," )
}
2019-01-27 20:15:57 +09:00
// Set adds a string value to the list, after splitting on the comma separator.
2018-12-10 05:23:32 +09:00
func ( l * list ) Set ( v string ) error {
vals := strings . Split ( v , "," )
* l = append ( * l , vals ... )
return nil
}
2019-01-27 23:12:49 +09:00
// settableString is a flag-friendly string that distinguishes an empty string
// due to not being set and explicitly being set as empty at the command line.
type settableString struct {
set bool
str string
}
// String simply returns the string value of this settableString.
func ( s * settableString ) String ( ) string {
return s . str
}
// Set will mark this settableString's set as true and store the value.
func ( s * settableString ) Set ( v string ) error {
s . set = true
s . str = v
return nil
}
// IsSet returns true if this value was explicitly set as a flag value.
func ( s settableString ) IsSet ( ) bool {
return s . set
}
2019-01-27 20:15:57 +09:00
// CommandLineFlags manages the flags defined by this tool.
2019-01-17 05:43:36 +09:00
type CommandLineFlags struct {
2019-01-27 23:12:49 +09:00
// Flags
specs list
path settableString
// Additional data
pathAutoDetected bool
2019-01-17 05:43:36 +09:00
}
2018-11-29 05:40:11 +09:00
2019-01-27 20:15:57 +09:00
// NewCommandLineFlags defines the flags expected to be used by this tool. Calls
// flag.Parse on behalf of the main program, and validates the flags. Returns an
// error if validation fails.
func NewCommandLineFlags ( ) ( * CommandLineFlags , error ) {
c := & CommandLineFlags { }
2019-01-27 23:12:49 +09:00
flag . Var (
& c . path ,
pathFlag ,
"Package path to use for all generated package paths. If using GOPATH, this is automatically detected as $GOPATH/<path>/ when generating in a subdirectory. Cannot be explicitly set to be empty." )
flag . Var ( & ( c . specs ) , specFlag , "Input JSON-LD specification used to generate Go code." )
2019-01-17 05:43:36 +09:00
flag . Parse ( )
2019-01-27 20:15:57 +09:00
return c , c . Validate ( )
2019-01-17 05:43:36 +09:00
}
2019-01-27 23:12:49 +09:00
// detectPath attempts to detect the path to use when generating the code. The
// path is only detected if the tool is running in a subdirectory of GOPATH,
// and will be set to $GOPATH/<path>/. After this method runs without errors,
// c.path.IsSet will always return true.
//
// When auto-detecting, if GOPATH is not set then will return an error.
//
// If the path has already been set at the command line, does nothing.
func ( c * CommandLineFlags ) detectPath ( ) error {
if c . path . IsSet ( ) {
return nil
}
gopath , isSet := os . LookupEnv ( "GOPATH" )
if ! isSet {
return fmt . Errorf ( "cannot detect %q because GOPATH environmental variable is not set and %q flag was not explicitly set" , pathFlag , pathFlag )
}
pwd , err := os . Getwd ( )
if err != nil {
return err
}
if ! strings . HasPrefix ( pwd , gopath ) {
return fmt . Errorf ( "cannot detect %q because current working directory is not under GOPATH and %q flag was not explicitly set" , pathFlag , pathFlag )
}
c . pathAutoDetected = true
gopath = strings . Join ( [ ] string { gopath , "src" , "" } , "/" )
c . path . Set ( strings . TrimPrefix ( pwd , gopath ) )
return nil
}
2019-01-27 20:15:57 +09:00
// Validate applies custom validation logic to flags and returns an error if any
// flags violate these rules.
func ( c * CommandLineFlags ) Validate ( ) error {
2019-01-17 05:43:36 +09:00
if len ( c . specs ) == 0 {
2019-01-27 23:12:49 +09:00
return fmt . Errorf ( "%q flag must not be empty" , specFlag )
}
if err := c . detectPath ( ) ; err != nil {
return err
}
if len ( c . path . String ( ) ) == 0 {
return fmt . Errorf ( "%q flag must not be empty" , pathFlag )
2019-01-17 05:43:36 +09:00
}
return nil
}
2019-01-27 20:15:57 +09:00
// ReadSpecs returns the JSONLD contents of files specified in the 'spec' flag.
func ( c * CommandLineFlags ) ReadSpecs ( ) ( j [ ] rdf . JSONLD , err error ) {
j = make ( [ ] rdf . JSONLD , 0 , len ( c . specs ) )
for _ , spec := range c . specs {
var b [ ] byte
b , err = ioutil . ReadFile ( spec )
2019-01-17 05:43:36 +09:00
if err != nil {
2019-01-27 20:15:57 +09:00
return
2019-01-17 05:43:36 +09:00
}
var inputJSON map [ string ] interface { }
err = json . Unmarshal ( b , & inputJSON )
if err != nil {
2019-01-27 20:15:57 +09:00
return
2019-01-17 05:43:36 +09:00
}
2019-01-27 20:15:57 +09:00
j = append ( j , inputJSON )
2019-01-17 05:43:36 +09:00
}
2019-01-27 20:15:57 +09:00
return
}
2019-01-27 23:12:49 +09:00
// AutoDetectedPath returns true if the path flag was auto-detected.
func ( c * CommandLineFlags ) AutoDetectedPath ( ) bool {
return c . pathAutoDetected
}
// Path returns the path flag.
func ( c * CommandLineFlags ) Path ( ) string {
return c . path . String ( )
}
2019-01-27 20:15:57 +09:00
func main ( ) {
// Read, Parse, and Validate command line flags
cmd , err := NewCommandLineFlags ( )
if err != nil {
fmt . Println ( err )
return
}
2019-01-27 23:12:49 +09:00
// Print auto-determined values
if cmd . AutoDetectedPath ( ) {
fmt . Printf ( "Auto-detected path: %s\n" , cmd . Path ( ) )
}
2019-01-27 20:15:57 +09:00
// Read input specification files
fmt . Printf ( "Reading input specifications...\n" )
inputJSONs , err := cmd . ReadSpecs ( )
if err != nil {
fmt . Println ( err )
return
}
// Parse specifications
fmt . Printf ( "Parsing %d vocabularies...\n" , len ( inputJSONs ) )
2019-01-17 05:43:36 +09:00
p , err := rdf . ParseVocabularies ( registry , inputJSONs )
2018-11-04 00:56:09 +09:00
if err != nil {
panic ( err )
}
2019-01-27 20:15:57 +09:00
// Convert to generated code
fmt . Printf ( "Converting %d types, properties, and values...\n" , p . Size ( ) )
2018-12-10 05:23:32 +09:00
c := & convert . Converter {
2019-01-27 23:12:49 +09:00
GenRoot : gen . NewPackageManager ( cmd . Path ( ) , "" ) ,
2019-01-27 20:15:57 +09:00
PackagePolicy : convert . IndividualUnderRoot ,
2018-12-10 05:23:32 +09:00
}
f , err := c . Convert ( p )
if err != nil {
panic ( err )
}
2019-01-27 20:15:57 +09:00
// Write generated code
fmt . Printf ( "Writing %d files...\n" , len ( f ) )
2018-12-10 05:23:32 +09:00
for _ , file := range f {
if e := os . MkdirAll ( "./" + file . Directory , 0777 ) ; e != nil {
panic ( e )
}
2018-12-31 00:54:16 +09:00
if e := file . F . Save ( "./" + file . Directory + "/" + file . FileName ) ; e != nil {
2018-12-10 05:23:32 +09:00
panic ( e )
}
}
2019-01-27 20:15:57 +09:00
fmt . Printf ( "Done!\n" )
2018-10-20 05:44:13 +09:00
}