def element_header(self, obj: Element, name: str, curie: str, uri: str) -> None: """ Write the header for an element. Parameters ---------- obj: linkml_runtime.linkml_model.meta.Element An element name: str The name of the element curie: str The CURIE of the element uri: str The URI of the element """ if curie.startswith('http'): if curie.startswith( 'https://w3id.org/biolink/vocab/linkml:types/'): simple_name = curie.split('/')[-1] uri = f"https://linkml.github.io/linkml-model/docs/types/{simple_name}" simple_name = f"metatype:{simple_name}" else: simple_name = curie else: simple_name = curie.split(':', 1)[1] if isinstance(obj, TypeDefinition): obj_type = 'Type' elif isinstance(obj, ClassDefinition): obj_type = 'Class' elif isinstance(obj, SlotDefinition): obj_type = 'Slot' else: obj_type = 'Class' self.header( 1, f"{obj_type}: {simple_name}" + (f" _(deprecated)_" if obj.deprecated else "")) self.para(be(obj.description)) print(f'URI: [{curie}]({uri})') print()
def element_header(self, obj: Element, name: str, curie: str, uri: str) -> None: simple_name = curie.split(':', 1)[1] if isinstance(obj, TypeDefinition): obj_type = 'Type' elif isinstance(obj, ClassDefinition): obj_type = 'Class' elif isinstance(obj, SlotDefinition): obj_type = 'Slot' elif isinstance(obj, EnumDefinition): obj_type = 'Enum' elif isinstance(obj, SubsetDefinition): obj_type = 'Subset' else: obj_type = 'Class' header_label = f"{obj_type}: ~~{name}~~ _(deprecated)_" if obj.deprecated else f"{obj_type}: {name}" self.header(1, header_label) self.para(be(obj.description)) # Display CodeableConcept as per https://github.com/cancerDHC/ccdhmodel/issues/114 if hasattr(obj, 'range') and obj.range == 'CodeableConcept': if hasattr(obj, 'values_from'): for values_from in obj.values_from: enum_name = re.sub(r'^crdch:', '', values_from) self.para( f'**CodeableConcept Binding:** This property has a Range of CodeableConcept. One of the ' + f'Codings in this CodeableConcept instance should be populated with values from ' + self.class_type_link(enum_name) + '.') # print(f'URI: [{curie}]({uri})') print( f'URI: `{curie}`' ) # Replaced with CURIEs since CRDCH URIs are currently non-resolvable. print()
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> None: """ Visit a given slot definition and write the following properties in Markdown, - Frontmatter - Mappings - Description - Domain and Range constraints - Parents - Children - Used by Parameters ---------- cls: linkml_runtime.linkml_model.meta.SlotDefinition A SlotDefinition """ if not slot.alias: with open(self.dir_path(slot), 'w') as slotfile: with redirect_stdout(slotfile): slot_curie = self.namespaces.uri_or_curie_for( self.namespaces._base, underscore(slot.name)) slot_uri = self.namespaces.uri_for(slot_curie) ancs = self.ancestors(slot) if 'related to' in ancs: if slot.mixin: parent = 'Slot Mixins' else: parent = 'Predicates' grand_parent = 'Slots' slot_type = 'Relation' elif 'node property' in ancs: if slot.mixin: parent = 'Slot Mixins' else: parent = 'Node Properties' grand_parent = 'Slots' slot_type = 'Slot' elif 'association slot' in ancs: if slot.mixin: parent = 'Slot Mixins' else: parent = 'Edge Properties' grand_parent = 'Slots' slot_type = 'Slot' else: if slot.mixin: parent = 'Slot Mixins' else: parent = 'Other Slots' grand_parent = 'Slots' slot_type = 'Slot' self.frontmatter( **{ 'parent': parent, 'title': slot_curie, 'grand_parent': grand_parent, 'layout': 'default' }) simple_name = slot_curie.split(':', 1)[1] self.header( 1, f"{slot_type}: {simple_name}" + (f" _(deprecated)_" if slot.deprecated else "")) for s in slot.in_subset: self.badges(s, f'{s}-subset-label') self.para(be(slot.description)) print(f'URI: [{slot_curie}]({slot_uri})') self.header(2, 'Domain and Range') print( f'{self.class_link(slot.domain)} ->{self.predicate_cardinality(slot)} ' f'{self.class_type_link(slot.range)}') self.header(2, 'Parents') if slot.is_a: self.bullet(f' is_a: {self.slot_link(slot.is_a)}') self.header(2, 'Children') if slot.name in sorted(self.synopsis.isarefs): for child in sorted( self.synopsis.isarefs[slot.name].slotrefs): child_slot = self.schema.slots[child] if not child_slot.alias: self.bullet(f' {self.slot_link(child)}') self.header(2, 'Used by') if slot.name in sorted(self.synopsis.slotrefs): for rc in sorted( self.synopsis.slotrefs[slot.name].classrefs): self.bullet(f'{self.class_link(rc)}') if aliased_slot_name == 'relation': if slot.subproperty_of: self.bullet( f' reifies: {self.slot_link(slot.subproperty_of) if slot.subproperty_of in self.schema.slots else slot.subproperty_of}' ) self.element_properties(slot)
def visit_schema(self, directory: str = None, classes: Set[ClassDefinitionName] = None, image_dir: bool = False, noimages: bool = False, **_) -> None: """ Visit the schema and generate Markdown for each ClassDefinition, SlotDefinition, and TypeDefinition. Parameters ---------- directory: str The directory to write to classes: Set[ClassDefinitionName] A set of classes to subset image_dir: str The directory to write static images noimages: bool Whether or not to generate static images """ self.gen_classes = classes if classes else [] for cls in self.gen_classes: if cls not in self.schema.classes: raise ValueError("Unknown class name: {cls}") if self.gen_classes: self.gen_classes_neighborhood = self.neighborhood( list(self.gen_classes)) self.directory = directory if directory: os.makedirs(directory, exist_ok=True) elif image_dir: raise ValueError( f"Image directory can only be used with '-d' option") if image_dir: self.image_directory = os.path.join(directory, 'images') if not noimages: os.makedirs(self.image_directory, exist_ok=True) self.noimages = noimages self.types_directory = os.path.join(directory, 'types') os.makedirs(self.types_directory, exist_ok=True) self.doc_root_title = f'Browse {self.schema.name.title().replace("-", " ")}' self.seen_elements = set() with open(os.path.join(directory, 'index.md'), 'w') as ixfile: # Create the data model index with redirect_stdout(ixfile): self.frontmatter( **{ 'title': self.doc_root_title, 'has_children': 'true', 'nav_order': 2, 'layout': 'default', 'has_toc': 'false' }) self.para(be(self.schema.description)) self.header(2, 'Classes') self.header(3, "Entity") cls = self.schemaview.get_element('entity') self.seen_elements.add(cls.name) self.bullet(self.class_link(cls, use_desc=True)) self.header(3, 'Named Things') cls = self.schemaview.get_element('named thing') self.seen_elements.add(cls.name) self.class_hier(cls) self.header(3, 'Associations') cls = self.schemaview.get_element('association') self.seen_elements.add(cls.name) self.class_hier(cls) self.header(3, 'Class Mixins') for cls in sorted(self.schema.classes.values(), key=lambda c: c.name.lower()): if cls.mixin and self.is_secondary_ref(cls.name): if cls.name not in self.seen_elements: self.seen_elements.add(cls.name) self.class_hier(cls) self.header(3, 'Other Classes') for cls in sorted(self.schema.classes.values(), key=lambda c: c.name.lower()): if cls.name not in self.seen_elements: self.seen_elements.add(cls.name) self.class_hier(cls) self.header(2, 'Slots') self.header(3, 'Predicates') for slot in sorted(self.schema.slots.values(), key=lambda c: c.name.lower()): if not slot.alias: if 'related to' in self.ancestors( slot) and not slot.mixin: self.seen_elements.add(slot.name) self.pred_hier(slot) self.header(3, 'Node Properties') for slot in sorted(self.schema.slots.values(), key=lambda s: s.name.lower()): ancs = self.ancestors(slot) if not slot.alias: if 'node property' in ancs and not slot.mixin: self.seen_elements.add(slot.name) self.pred_hier(slot) self.header(3, 'Edge Properties') for slot in sorted(self.schema.slots.values(), key=lambda s: s.name.lower()): ancs = self.ancestors(slot) if not slot.alias: if 'association slot' in ancs and not slot.mixin: self.seen_elements.add(slot.name) self.pred_hier(slot) self.header(3, 'Slot Mixins') for slot in sorted(self.schema.slots.values(), key=lambda s: s.name): if not slot.alias: if slot.mixin: self.seen_elements.add(slot.name) self.pred_hier(slot) self.header(3, 'Other Slots') for slot in sorted(self.schema.slots.values(), key=lambda s: s.name): if not slot.alias: if slot.name not in self.seen_elements: ancs = self.ancestors(slot) if len(ancs) <= 1: self.seen_elements.add(slot.name) self.pred_hier(slot) self.header(2, 'Subsets') for subset in sorted(self.schema.subsets.values(), key=lambda s: s.name): self.seen_elements.add(subset.name) self.bullet(self.subset_link(subset, use_desc=True)) self.header(2, 'Types') self.header(3, 'Built in') for builtin_name in sorted(self.synopsis.typebases.keys()): self.bullet(f'**{builtin_name}**') self.header(3, 'Defined') for typ in sorted(self.schema.types.values(), key=lambda t: t.name): if self.is_secondary_ref(typ.name): if typ.typeof: typ_typ = self.type_link(typ.typeof) else: typ_typ = f'**{typ.base}**' self.bullet( self.type_link(typ, after_link=f' ({typ_typ})', use_desc=True)) self.header(2, "Enums") for enum in sorted(self.schema.enums.values(), key=lambda s: s.name.lower()): self.bullet(self.enum_link(enum, use_desc=True), 0) # create parent for organizing markdown based on Class types with open(os.path.join(directory, 'classes.md'), 'w') as file: file.write( f'---\nparent: {self.doc_root_title}\ntitle: Classes\nhas_children: true\nnav_order: 1\nlayout: default\n---' ) with open(os.path.join(directory, 'named_things.md'), 'w') as file: file.write( f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Named Things\nhas_children: true\nnav_order: 1\nlayout: default\n---' ) with open(os.path.join(directory, 'associations.md'), 'w') as file: file.write( f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Associations\nhas_children: true\nnav_order: 2\nlayout: default\n---' ) with open(os.path.join(directory, 'class_mixins.md'), 'w') as file: file.write( f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Class Mixins\nhas_children: true\nnav_order: 3\nlayout: default\n---' ) with open(os.path.join(directory, 'other_classes.md'), 'w') as file: file.write( f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Other Classes\nhas_children: true\nnav_order: 4\nlayout: default\n---' ) # create parent for organizing markdown based on Slot types with open(os.path.join(directory, 'slots.md'), 'w') as file: file.write( f'---\nparent: {self.doc_root_title}\ntitle: Slots\nhas_children: true\nnav_order: 2\nlayout: default\n---' ) with open(os.path.join(directory, 'predicates.md'), 'w') as file: file.write( f'---\nparent: Slots\n\ngrand_parent: {self.doc_root_title}\ntitle: Predicates\nhas_children: true\nnav_order: 1\nlayout: default\n---' ) with open(os.path.join(directory, 'node_properties.md'), 'w') as file: file.write( f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Node Properties\nhas_children: true\nnav_order: 2\nlayout: default\n---' ) with open(os.path.join(directory, 'edge_properties.md'), 'w') as file: file.write( f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Edge Properties\nhas_children: true\nnav_order: 3\nlayout: default\n---' ) with open(os.path.join(directory, 'slot_mixins.md'), 'w') as file: file.write( f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Slot Mixins\nhas_children: true\nnav_order: 4\nlayout: default\n---' ) with open(os.path.join(directory, 'other_slots.md'), 'w') as file: file.write( f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Other Slots\nhas_children: true\nnav_order: 5\nlayout: default\n---' ) # create parent for organizing markdown based on Subset types with open(os.path.join(directory, 'subsets.md'), 'w') as file: file.write( f'---\nparent: {self.doc_root_title}\ntitle: Subsets\nhas_children: true\nnav_order: 3\nlayout: default\n---' ) # create parent for organizing markdown based on Type types os.makedirs(os.path.join(directory, 'types'), exist_ok=True) with open(os.path.join(directory, 'types', 'index.md'), 'w') as file: file.write( f'---\nparent: {self.doc_root_title}\ntitle: Types\nhas_children: true\nnav_order: 4\nlayout: default\n---' ) with open(os.path.join(directory, 'types', 'built_in_types.md'), 'w') as file: file.write( f'---\nparent: Types\ngrand_parent: {self.doc_root_title}\ntitle: Built-in Types\nhas_children: true\nnav_order: 1\nlayout: default\n---' ) with open(os.path.join(directory, 'types', 'defined_types.md'), 'w') as file: file.write( f'---\nparent: Types\ngrand_parent: {self.doc_root_title}\ntitle: Defined Types\nhas_children: true\nnav_order: 2\nlayout: default\n---' ) # create parent for organizing markdown based on Enum types with open(os.path.join(directory, 'enums.md'), 'w') as file: file.write( f'---\nparent: {self.doc_root_title}\ntitle: Enums\nhas_children: true\nnav_order: 4\nlayout: default\n---' )
def visit_schema(self, directory: str = None, classes: Set[ClassDefinitionName] = None, image_dir: bool = False, index_file: str = 'index.md', noimages: bool = False, **_) -> None: self.gen_classes = classes if classes else [] for cls in self.gen_classes: if cls not in self.schema.classes: raise ValueError("Unknown class name: {cls}") if self.gen_classes: self.gen_classes_neighborhood = self.neighborhood( list(self.gen_classes)) self.directory = directory if directory: os.makedirs(directory, exist_ok=True) elif image_dir: raise ValueError( f"Image directory can only be used with '-d' option") if image_dir: self.image_directory = os.path.join(directory, 'images') if not noimages: os.makedirs(self.image_directory, exist_ok=True) self.noimages = noimages if not self.no_types_dir: os.makedirs(os.path.join(directory, 'types'), exist_ok=True) with open(self.exist_warning(directory, index_file), 'w') as ixfile: with redirect_stdout(ixfile): self.frontmatter(f"{self.schema.name} schema") self.para(be(self.schema.description)) self.header(3, 'Classes') for cls in sorted(self.schema.classes.values(), key=lambda c: c.name): if not cls.is_a and not cls.mixin and self.is_secondary_ref( cls.name): self.class_hier(cls) self.header(3, 'Mixins') for cls in sorted(self.schema.classes.values(), key=lambda c: c.name): if cls.mixin and self.is_secondary_ref(cls.name): self.class_hier(cls) self.header(3, 'Slots') for slot in sorted(self.schema.slots.values(), key=lambda s: s.name): if not slot.is_a and self.is_secondary_ref(slot.name): self.pred_hier(slot) self.header(3, 'Enums') for enu in sorted(self.schema.enums.values(), key=lambda e: e.name): self.enum_hier(enu) self.header(3, 'Subsets') for subset in sorted(self.schema.subsets.values(), key=lambda s: s.name): self.bullet(self.subset_link(subset, use_desc=True), 0) self.header(3, 'Types') self.header(4, 'Built in') for builtin_name in sorted(self.synopsis.typebases.keys()): self.bullet(f'**{builtin_name}**') self.header(4, 'Defined') for typ in sorted(self.schema.types.values(), key=lambda t: t.name): if self.is_secondary_ref(typ.name): if typ.typeof: typ_typ = self.type_link(typ.typeof) else: typ_typ = f'**{typ.base}**' self.bullet( self.type_link(typ, after_link=f' ({typ_typ})', use_desc=True)) with open(self.exist_warning(directory, 'mappings.md'), 'w') as ixfile: with redirect_stdout(ixfile): self.frontmatter(f"{self.schema.name} schema mappings") self.header(3, 'Classes') for cls in sorted(self.schema.classes.values(), key=lambda c: c.name): # if not cls.is_a and not cls.mixin and self.is_secondary_ref(cls.name): # Display information on this class. self.header(4, self.class_link(cls, use_desc=False)) #desc = be(cls.description).strip() #if desc != '': # self.para(f'Description: {desc}') # Looks like we don't need {self.to_uri(mapping)} mappings = [] for mapping in cls.exact_mappings: mappings.append( (self.class_link(cls, use_desc=False), "Direct", f"[{mapping}]({self.to_uri(mapping)})")) for mapping in cls.close_mappings: mappings.append( (self.class_link(cls, use_desc=False), "Indirect", f"[{mapping}]({self.to_uri(mapping)})")) # Lets go through the slots. for slot_name in cls.slots: slot = self.slot_for(slot_name) for mapping in slot.exact_mappings: mappings.append( (self.slot_link(slot, use_desc=False), "Direct", f"[{mapping}]({self.to_uri(mapping)})")) for mapping in slot.close_mappings: mappings.append( (self.slot_link(slot, use_desc=False), "Indirect", f"[{mapping}]({self.to_uri(mapping)})")) # Display only if there are any mappings left over. if len(mappings) == 0: print("No mappings included in schema.") else: print("| Source | Mapping type | Destination |") print("| --- | --- | --- |") for (src, typ, dest) in mappings: print(f"| {src} | {typ} | {dest} |")