예제 #1
0
    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()
예제 #2
0
    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()
예제 #3
0
    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)
예제 #4
0
    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---'
            )
예제 #5
0
    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} |")