"""
Generate JSON-LD contexts
"""
import logging
import os
import csv
from typing import Dict, Mapping, Union, TextIO, Set, Optional
import click
from jsonasobj2 import JsonObj, as_json
from rdflib import XSD
from linkml_runtime.linkml_model.meta import SchemaDefinition, ClassDefinition, SlotDefinition, Definition, Element
from linkml_runtime.utils.formatutils import camelcase, underscore, be
from linkml.utils.generator import Generator, shared_arguments
from linkml_runtime.linkml_model.types import SHEX
URI_RANGES = (XSD.anyURI, SHEX.nonliteral, SHEX.bnode, SHEX.iri)
[docs]class PrefixGenerator(Generator):
generatorname = os.path.basename(__file__)
generatorversion = "0.1.1"
valid_formats = ['json', 'tsv']
visit_all_class_slots = False
def __init__(self,
schema: Union[str, TextIO, SchemaDefinition],
**kwargs) -> None:
super().__init__(schema, **kwargs)
if self.namespaces is None:
raise TypeError("Schema text must be supplied to context generater. Preparsed schema will not work")
self.emit_prefixes: Set[str] = set()
self.default_ns = None
self.context_body = dict()
self.slot_class_maps = dict()
[docs] def visit_schema(self, base: Optional[str]=None, output: Optional[str]=None, **_):
# Add any explicitly declared prefixes
for prefix in self.schema.prefixes.values():
self.emit_prefixes.add(prefix.prefix_prefix)
# Add any prefixes explicitly declared
for pfx in self.schema.emit_prefixes:
self.add_prefix(pfx)
# Add the default prefix
if self.schema.default_prefix:
dflt = self.namespaces.prefix_for(self.schema.default_prefix)
if dflt:
self.default_ns = dflt
if self.default_ns:
self.emit_prefixes.add(self.default_ns)
[docs] def end_schema(self, base: Optional[str] = None, output: Optional[str] = None, **_) -> None:
context = JsonObj()
if base:
if '://' not in base:
self.context_body['@base'] = os.path.relpath(base, os.path.dirname(self.schema.source_file))
else:
self.context_body['@base'] = base
for prefix in sorted(self.emit_prefixes):
context[prefix] = self.namespaces[prefix]
for k, v in self.context_body.items():
context[k] = v
for k, v in self.slot_class_maps.items():
context[k] = v
if output:
output_ext = output.split(".")[-1]
if output_ext == "tsv":
mapping: Dict = {}
for prefix in sorted(self.emit_prefixes):
mapping[prefix] = self.namespaces[prefix]
with open(output, 'w', encoding='UTF-8') as outf:
writer = csv.writer(outf, delimiter='\t')
for key, value in mapping.items():
writer.writerow([key, value])
else:
with open(output, 'w', encoding='UTF-8') as outf:
outf.write(as_json(context))
else:
if self.format == "tsv":
mapping: Dict = {} # prefix to IRI mapping
for prefix in sorted(self.emit_prefixes):
mapping[prefix] = self.namespaces[prefix]
for key, value in mapping.items():
print(key, value, sep='\t')
else:
print(as_json(context))
[docs] def visit_class(self, cls: ClassDefinition) -> bool:
class_def = {}
cn = camelcase(cls.name)
self.add_mappings(cls)
cls_prefix = self.namespaces.prefix_for(cls.class_uri)
if not self.default_ns or not cls_prefix or cls_prefix != self.default_ns:
class_def['@id'] = cls.class_uri
if cls_prefix:
self.add_prefix(cls_prefix)
if class_def:
self.slot_class_maps[cn] = class_def
# We don't bother to visit class slots - just all slots
return False
[docs] def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> None:
self.add_mappings(slot)
@shared_arguments(PrefixGenerator)
@click.command()
@click.option("--base", help="Base URI for model")
@click.option("--output", "-o", help="Output file path")
def cli(yamlfile, **args):
""" Generate jsonld @context definition from LinkML model """
print(PrefixGenerator(yamlfile, **args).serialize(**args))