From a2ab04d7be92a285d76533f56e77caa9f8eafbc2 Mon Sep 17 00:00:00 2001 From: Eric Torstenson Date: Fri, 28 Aug 2026 12:00:06 -0500 Subject: [PATCH 1/4] official document templates --- docs/templates-linkml/class.md.jinja2 | 290 +++++++++++ docs/templates-linkml/class_diagram.md.jinja2 | 76 +++ .../common_metadata.md.jinja2 | 142 +++++ docs/templates-linkml/enum.md.jinja2 | 203 ++++++++ docs/templates-linkml/index.md.jinja2 | 62 +++ docs/templates-linkml/index.tex.jinja2 | 26 + docs/templates-linkml/schema.md.jinja2 | 5 + docs/templates-linkml/slot.md.jinja2 | 490 ++++++++++++++++++ docs/templates-linkml/subset.md.jinja2 | 113 ++++ docs/templates-linkml/type.md.jinja2 | 170 ++++++ 10 files changed, 1577 insertions(+) create mode 100644 docs/templates-linkml/class.md.jinja2 create mode 100644 docs/templates-linkml/class_diagram.md.jinja2 create mode 100644 docs/templates-linkml/common_metadata.md.jinja2 create mode 100644 docs/templates-linkml/enum.md.jinja2 create mode 100644 docs/templates-linkml/index.md.jinja2 create mode 100644 docs/templates-linkml/index.tex.jinja2 create mode 100644 docs/templates-linkml/schema.md.jinja2 create mode 100644 docs/templates-linkml/slot.md.jinja2 create mode 100644 docs/templates-linkml/subset.md.jinja2 create mode 100644 docs/templates-linkml/type.md.jinja2 diff --git a/docs/templates-linkml/class.md.jinja2 b/docs/templates-linkml/class.md.jinja2 new file mode 100644 index 00000000..d68301bd --- /dev/null +++ b/docs/templates-linkml/class.md.jinja2 @@ -0,0 +1,290 @@ +--- +search: + boost: {% if element.deprecated %}0.5{% else %}10.0{% endif %} +--- + +{%- if element.title %} + {%- set title = element.title ~ ' (' ~ element.name ~ ')' -%} +{%- else %} + {%- if gen.use_class_uris -%} + {%- set title = element.name -%} + {%- else -%} + {%- set title = gen.name(element) -%} + {%- endif -%} +{%- endif -%} + +{% macro compute_range(slot) -%} + {%- if slot.any_of or slot.exactly_one_of -%} + {%- for subslot_range in schemaview.slot_range_as_union(slot) -%} + {{ gen.link(subslot_range) }} + {%- if not loop.last -%} +  or 
+ {%- endif -%} + {%- endfor -%} + {%- else -%} + {{ gen.link(slot.range) }} + {%- endif -%} +{% endmacro %} + +# Class: {{ title }} {% if element.deprecated %} (DEPRECATED) {% endif %} + +{%- if header -%} +{{ header }} +{%- endif -%} + +{% if element.description %} +{% set element_description_lines = element.description.split('\n') %} +{% for element_description_line in element_description_lines %} +_{{ element_description_line }}_ +{% endfor %} +{% endif %} + +
+ +{% if element.abstract %} +* __NOTE__: this is an abstract class and should not be instantiated directly +{% endif %} + +URI: {{ gen.uri_link(element) }} + +{% if diagram_type == "er_diagram" %} +```{{ gen.mermaid_directive() }} +{{ gen.mermaid_diagram([element.name]) }} +``` +{% elif diagram_type == "plantuml_class_diagram" %} +```puml +{{ gen.mermaid_diagram([element.name]) }} +``` +{% else %} +{% include "class_diagram.md.jinja2" %} +{% endif %} + +{% if schemaview.class_parents(element.name) or schemaview.class_children(element.name, mixins=False) %} + +## Inheritance +{{ gen.inheritance_tree(element, mixins=True) }} +{% else %} + +{% endif %} + +{%- set has_class_props = element.class_uri or element.tree_root or element.mixin + or element.subclass_of or element.union_of or element.disjoint_with + or element.slot_names_unique or element.represents_relationship + or element.children_are_mutually_disjoint %} + +{%- if has_class_props %} +## Class Properties + +| Property | Value | +| --- | --- | +{%- if element.class_uri %} +| Class URI | {{ gen.uri_link(element.class_uri) }} | +{%- endif %} +{%- if element.mixin %} +| Mixin | Yes | +{%- endif %} +{%- if element.tree_root %} +| Tree Root | Yes | +{%- endif %} +{%- if element.slot_names_unique %} +| Slot Names Unique | Yes | +{%- endif %} +{%- if element.represents_relationship %} +| Represents Relationship | Yes | +{%- endif %} +{%- if element.subclass_of %} +| Subclass Of | {{ gen.links(element.subclass_of) | join(', ') }} | +{%- endif %} +{%- if element.union_of %} +| Union Of | {{ gen.links(element.union_of) | join(', ') }} | +{%- endif %} +{%- if element.disjoint_with %} +| Disjoint With | {{ gen.links(element.disjoint_with) | join(', ') }} | +{%- endif %} +{%- if element.children_are_mutually_disjoint %} +| Children Are Mutually Disjoint | Yes | +{%- endif %} + +{% endif %} +## Slots + +| Name | Cardinality and Range | Description | Inheritance | +| --- | --- | --- | --- | +{% if gen.get_direct_slots(element)|length > 0 %} +{%- for slot in gen.get_direct_slots(element) -%} +| {{ gen.link(slot) }} | {{ gen.cardinality(slot) }}
{{ compute_range(slot) }} | {{ slot.description|enshorten }} | direct | +{% endfor -%} +{% endif -%} +{% if gen.get_indirect_slots(element)|length > 0 %} +{%- for slot in gen.get_indirect_slots(element) -%} +| {{ gen.link(slot) }} | {{ gen.cardinality(slot) }}
{{ compute_range(slot) }} | {{ slot.description|enshorten }} | {{ gen.links(gen.get_slot_inherited_from(element.name, slot.name))|join(', ') }} | +{% endfor -%} +{% endif %} + +{%- if element.unique_keys %} +## Unique Keys + +{% for uk in element.unique_keys.values() %} +### {{ uk.unique_key_name }} + +**Unique key slots:** {{ uk.unique_key_slots | join(', ') }} +{%- if uk.consider_nulls_inequal %} + +Considers null values as inequal +{%- endif %} +{% endfor %} +{% endif %} + +{%- if element.defining_slots %} +## Defining Slots + +This class is defined by the following slots: + +{% for slot_name in element.defining_slots %} +* {{ gen.link(slot_name) }} +{%- endfor %} +{% endif %} + +{%- set has_expressions = element.any_of or element.all_of or element.exactly_one_of + or element.none_of or element.slot_conditions %} + +{%- if has_expressions %} +
+Expressions & Logic + +{%- if element.any_of %} +#### Any Of + +The class must satisfy at least one of: + +{%- for expr in element.any_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.all_of %} +#### All Of + +The class must satisfy all of: + +{%- for expr in element.all_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.exactly_one_of %} +#### Exactly One Of + +The class must satisfy exactly one of: + +{%- for expr in element.exactly_one_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.none_of %} +#### None Of + +The class must not satisfy any of: + +{%- for expr in element.none_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.slot_conditions %} +#### Slot Conditions + +{%- for slot_name, conditions in element.slot_conditions.items() %} +- **{{ gen.link(slot_name) }}**: {{ conditions }} +{%- endfor %} +{%- endif %} + +
+{% endif %} + +{% if schemaview.is_mixin(element.name) %} +## Mixin Usage + +| mixed into | description | +| --- | --- | +{% for c in schemaview.class_children(element.name, is_a=False) -%} +| {{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} | +{% endfor %} +{% endif %} + +{% if schemaview.usage_index().get(element.name) %} +## Usages + +| used by | used in | type | used | +| --- | --- | --- | --- | +{% for usage in schemaview.usage_index().get(element.name) -%} +| {{ gen.link(usage.used_by) }} | {{ gen.link(usage.slot) }} | {{ usage.metaslot }} | {{ gen.link(usage.used) }} | +{% endfor %} +{% endif %} + +{% if element.rules %} +## Rules + +{% for rule in gen.classrule_to_dict_view(element) %} +### {{ rule.title }} + +| Rule Applied | Preconditions | Postconditions | Elseconditions | +|--------------|---------------|----------------|----------------| +{% for key in rule.preconditions|default({}) -%} +| {{ key }} | +{%- if rule.preconditions[key] is defined -%} +```{{ rule.preconditions[key] }}``` +{%- else -%} +{% endif %} | +{%- if rule.postconditions and rule.postconditions[key] is defined -%} +```{{ rule.postconditions[key] }}``` +{%- else -%} +{% endif %} | +{%- if rule.elseconditions and rule.elseconditions[key] is defined -%} +```{{ rule.elseconditions[key] }}``` +{%- else -%} +{% endif %} | +{% endfor %} + +{% endfor %} +{% endif %} + +{% include "common_metadata.md.jinja2" %} + +{% if gen.example_object_blobs(element.name) -%} +## Examples +{% for name, blob in gen.example_object_blobs(element.name) -%} +### Example: {{ name }} + +```yaml +{{ blob }} +``` +{% endfor %} +{% endif %} + +## LinkML Source + + + +### Direct + +
+```yaml +{{ gen.yaml(element) }} +``` +
+ +### Induced + +
+```yaml +{{ gen.yaml(element, inferred=True) }} +``` +
+ +{%- if footer -%} +{{ footer }} +{%- endif -%} + +
diff --git a/docs/templates-linkml/class_diagram.md.jinja2 b/docs/templates-linkml/class_diagram.md.jinja2 new file mode 100644 index 00000000..95c2e980 --- /dev/null +++ b/docs/templates-linkml/class_diagram.md.jinja2 @@ -0,0 +1,76 @@ +{% macro slot_relationship(element, slot) %} + {% if slot.range is not none %} + {% set range_element = gen.name(schemaview.get_element(slot.range)) %} + {% set relation_label = gen.name(slot) %} + {{ gen.name(element) }} --> "{{ gen.cardinality(slot) }}" {{ range_element }} : {{ relation_label }} + click {{ range_element }} href "{{ gen.link_mermaid(schemaview.get_element(slot.range)) }}" + {% endif %} +{% endmacro %} + +{% if schemaview.class_parents(element.name) and schemaview.class_children(element.name) %} +```{{ gen.mermaid_directive() }} + classDiagram + class {{ gen.name(element) }} + click {{ gen.name(element) }} href "{{ gen.link_mermaid(element) }}" + {% for s in schemaview.class_parents(element.name)|sort(attribute='name') -%} + {{ gen.name(schemaview.get_element(s)) }} <|-- {{ gen.name(element) }} + click {{ gen.name(schemaview.get_element(s)) }} href "{{ gen.link_mermaid(schemaview.get_element(s)) }}" + {% endfor %} + + {% for s in schemaview.class_children(element.name)|sort(attribute='name') -%} + {{ gen.name(element) }} <|-- {{ gen.name(schemaview.get_element(s)) }} + click {{ gen.name(schemaview.get_element(s)) }} href "{{ gen.link_mermaid(schemaview.get_element(s)) }}" + {% endfor %} + + {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%} + {{ gen.name(element) }} : {{ gen.name(s) }} + {% if s.range is not none and s.range not in gen.all_type_object_names() %} + {{ slot_relationship(element, s) }} + {% endif %} + {% endfor %} +``` +{% elif schemaview.class_parents(element.name) %} +```{{ gen.mermaid_directive() }} + classDiagram + class {{ gen.name(element) }} + click {{ gen.name(element) }} href "{{ gen.link_mermaid(element) }}" + {% for s in schemaview.class_parents(element.name)|sort(attribute='name') -%} + {{ gen.name(schemaview.get_element(s)) }} <|-- {{ gen.name(element) }} + click {{ gen.name(schemaview.get_element(s)) }} href "{{ gen.link_mermaid(schemaview.get_element(s)) }}" + {% endfor %} + {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%} + {{ gen.name(element) }} : {{ gen.name(s) }} + {% if s.range is not none and s.range not in gen.all_type_object_names() %} + {{ slot_relationship(element, s) }} + {% endif %} + {% endfor %} +``` +{% elif schemaview.class_children(element.name) %} +```{{ gen.mermaid_directive() }} + classDiagram + class {{ gen.name(element) }} + click {{ gen.name(element) }} href "{{ gen.link_mermaid(element) }}" + {% for s in schemaview.class_children(element.name)|sort(attribute='name') -%} + {{ gen.name(element) }} <|-- {{ gen.name(schemaview.get_element(s)) }} + click {{ gen.name(schemaview.get_element(s)) }} href "{{ gen.link_mermaid(schemaview.get_element(s)) }}" + {% endfor %} + {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%} + {{ gen.name(element) }} : {{ gen.name(s) }} + {% if s.range is not none and s.range not in gen.all_type_object_names() %} + {{ slot_relationship(element, s) }} + {% endif %} + {% endfor %} +``` +{% else %} +```{{ gen.mermaid_directive() }} + classDiagram + class {{ gen.name(element) }} + click {{ gen.name(element) }} href "{{ gen.link_mermaid(element) }}" + {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%} + {{ gen.name(element) }} : {{ gen.name(s) }} + {% if s.range is not none and s.range not in gen.all_type_object_names() %} + {{ slot_relationship(element, s) }} + {% endif %} + {% endfor %} +``` +{% endif %} diff --git a/docs/templates-linkml/common_metadata.md.jinja2 b/docs/templates-linkml/common_metadata.md.jinja2 new file mode 100644 index 00000000..cdc0598f --- /dev/null +++ b/docs/templates-linkml/common_metadata.md.jinja2 @@ -0,0 +1,142 @@ +{% if element.categories %} +## Categories + +{% for cat in element.categories %} +* {{ cat }} +{%- endfor %} + +{% endif %} +{% if element.keywords %} +## Keywords + +{% for kw in element.keywords %} +* {{ kw }} +{%- endfor %} + +{% endif %} +{% if element.in_subset %} +## In Subsets + +{% for subset in element.in_subset %} +* {{ gen.link(subset) }} +{%- endfor %} + +{% endif %} +{% if element.aliases %} +## Aliases + +{% for alias in element.aliases %} +* {{ alias }} +{%- endfor %} +{% endif %} + +{% if element.examples %} +## Examples + +| Value | +| --- | +{% for x in element.examples -%} +| {{ x.value }} | +{% endfor %} +{% endif -%} + +{% if element.comments -%} +## Comments + +{% for x in element.comments -%} +* {{ x }} +{% endfor %} +{% endif -%} + +{% if element.todos -%} +## TODOs + +{% for x in element.todos -%} +* {{ x }} +{% endfor %} +{% endif -%} + +{% if element.see_also -%} +## See Also + +{% for x in element.see_also -%} +* {{ gen.uri_link(x) }} +{% endfor %} +{% endif -%} + +{% if element.notes -%} +## Notes + +{% for note in element.notes -%} +* {{ note }} +{% endfor %} +{% endif -%} + +{% if element.alt_descriptions %} +## Alternative Descriptions + +{% for source, alt_desc in element.alt_descriptions.items() %} +* **{{ source }}**: {{ alt_desc.description }} +{%- endfor %} +{% endif %} + +## Identifier and Mapping Information + +{%- set has_admin_metadata = element.status or (element.rank is not none and element.rank != 1000) %} + +{%- if has_admin_metadata %} +### Administrative Metadata + +{% if element.status -%} +**Status:** {{ element.status }} +{% endif -%} +{% if element.rank is not none and element.rank != 1000 -%} +**Rank:** {{ element.rank }} +{% endif -%} + +{% endif %} +{% if element.id_prefixes %} +### Valid ID Prefixes + +Instances of this class *should* have identifiers with one of the following prefixes: +{% for p in element.id_prefixes %} +* {{ p }} +{% endfor %} + +{% endif %} + +{% if element.annotations %} +### Annotations + +| property | value | +| --- | --- | +{% for a in element.annotations -%} +{%- if a|string|first != '_' -%} +| {{ a }} | {{ element.annotations[a].value }} | +{% endif -%} +{% endfor %} +{% endif %} + +{% if element.from_schema or element.imported_from %} +### Schema Source + +{% if element.from_schema %} +* from schema: {{ element.from_schema }} +{% endif %} +{% if element.imported_from %} +* imported from: {{ element.imported_from }} +{% endif %} +{% endif %} + +{% if schemaview.get_mappings(element.name).items() -%} +## Mappings + +| Mapping Type | Mapped Value | +| --- | --- | +{% for m, mt in schemaview.get_mappings(element.name).items() -%} +{% if mt|length > 0 -%} +| {{ m }} | {{ mt|join(', ') }} | +{% endif -%} +{% endfor %} + +{% endif -%} diff --git a/docs/templates-linkml/enum.md.jinja2 b/docs/templates-linkml/enum.md.jinja2 new file mode 100644 index 00000000..07ccd5b5 --- /dev/null +++ b/docs/templates-linkml/enum.md.jinja2 @@ -0,0 +1,203 @@ +--- +search: + boost: {% if element.deprecated %}0.5{% else %}2.0{% endif %} +--- + +{%- if element.title and element.title != element.name %} + {%- set title = element.title ~ ' (' ~ element.name ~ ')' -%} +{%- else %} + {%- set title = gen.name(element) -%} +{%- endif -%} + +{# Render an EnumExpression / AnonymousEnumExpression as a Markdown sub-list. + Walks the selector slots defined on `enum_expression` in the LinkML metamodel + (reachable_from, concepts, code_set, matches, pv_formula, inherits) and + formats each as a top-level bullet with any modifier fields nested below. #} +{% macro render_enum_expression(expr) -%} +{%- if expr.reachable_from %} +- **Reachable from**{% if expr.reachable_from.source_ontology %} {{ gen.uri_link(expr.reachable_from.source_ontology) }}{% endif %} +{%- if expr.reachable_from.source_nodes %} + - Source nodes: {{ expr.reachable_from.source_nodes | join(', ') }} +{%- endif %} +{%- if expr.reachable_from.relationship_types %} + - Via relationships: {{ expr.reachable_from.relationship_types | join(', ') }} +{%- endif %} +{%- if expr.reachable_from.is_direct %} + - Direct descendants only +{%- endif %} +{%- if expr.reachable_from.include_self %} + - Includes the source node itself +{%- endif %} +{%- if expr.reachable_from.traverse_up %} + - Traverses upward (ancestors) +{%- endif %} +{%- endif %} +{%- if expr.concepts %} +- **Concepts:** {{ expr.concepts | join(', ') }} +{%- endif %} +{%- if expr.code_set %} +- **Code set:** {{ gen.uri_link(expr.code_set) }} +{%- if expr.code_set_tag %} + - Tag: {{ expr.code_set_tag }} +{%- endif %} +{%- if expr.code_set_version %} + - Version: {{ expr.code_set_version }} +{%- endif %} +{%- endif %} +{%- if expr.matches %} +- **Matches identifier pattern** `{{ expr.matches.identifier_pattern }}` +{%- if expr.matches.source_ontology %} + - Source ontology: {{ gen.uri_link(expr.matches.source_ontology) }} +{%- endif %} +{%- endif %} +{%- if expr.pv_formula %} +- **Permissible value formula:** {{ expr.pv_formula }} +{%- endif %} +{%- if expr.inherits %} +- **Inherits from:** {{ gen.links(expr.inherits) | join(', ') }} +{%- endif %} +{% endmacro %} + +# Enum: {{ title }} {% if element.deprecated %} (DEPRECATED) {% endif %} + +{% if element.description %} +{% set element_description_lines = element.description.split('\n') %} +{% for element_description_line in element_description_lines %} +_{{ element_description_line }}_ +{% endfor %} +{% endif %} + +
+ +URI: {{ gen.uri_link(element) }} + +{%- if element.enum_uri %} + +**Enum URI:** {{ gen.uri_link(element.enum_uri) }} +{% endif %} + +{%- set has_enum_source = element.code_set or element.pv_formula or element.reachable_from + or element.matches or element.concepts %} + +{%- if has_enum_source %} +## Enumeration Source + +{%- if element.code_set %} +**Code Set:** {{ gen.uri_link(element.code_set) }} + +{%- if element.code_set_tag %} +- **Tag:** {{ element.code_set_tag }} +{%- endif %} +{%- if element.code_set_version %} +- **Version:** {{ element.code_set_version }} +{%- endif %} +{%- endif %} + +{%- if element.pv_formula %} +**Permissible Value Formula:** {{ element.pv_formula }} +{%- endif %} + +{%- if element.reachable_from %} +**Reachable From:** + +{%- if element.reachable_from.source_ontology %} +- **Source:** {{ gen.link(element.reachable_from.source_ontology) }} +{%- endif %} +{%- if element.reachable_from.source_nodes %} +- **Nodes:** {{ element.reachable_from.source_nodes | join(', ') }} +{%- endif %} +{%- if element.reachable_from.relationship_types %} +- **Via:** {{ element.reachable_from.relationship_types | join(', ') }} +{%- endif %} +{%- endif %} + +{%- if element.matches %} +**Matches:** + +- **Expression:** `{{ element.matches.string_expression }}` +{%- endif %} + +{%- if element.concepts %} +**Concepts:** {{ gen.uri_links(element.concepts) | join(', ') }} +{%- endif %} + +{% endif %} + +{% if element.permissible_values -%} +{%- set has_pv_extras = namespace(found=false) -%} +{%- for pv in element.permissible_values.values() -%} + {%- if pv.title or pv.is_a or pv.mixins or pv.deprecated -%} + {%- set has_pv_extras.found = true -%} + {%- endif -%} +{%- endfor -%} +## Permissible Values + +{%- if has_pv_extras.found %} +| Value | Meaning | Description | Additional Info | +| --- | --- | --- | --- | +{% for pv in element.permissible_values.values() -%} +| {{ pv.text }} | {{ pv.meaning }} | {{ pv.description|enshorten }} | +{%- if pv.title %} Title: {{ pv.title }}
{% endif -%} +{%- if pv.is_a %} Is-A: {{ gen.link(pv.is_a) }}
{% endif -%} +{%- if pv.mixins %} Mixins: {{ gen.links(pv.mixins) | join(', ') }}
{% endif -%} +{%- if pv.deprecated %} **DEPRECATED**{% if pv.deprecated_element_has_exact_replacement %} (use {{ gen.link(pv.deprecated_element_has_exact_replacement) }}){% endif %}{% endif -%} + | +{% endfor %} +{%- else %} +| Value | Meaning | Description | +| --- | --- | --- | +{% for pv in element.permissible_values.values() -%} +| {{ pv.text }} | {{ pv.meaning }} | {{ pv.description|enshorten }} | +{% endfor %} +{%- endif %} +{% else %} +_This is a dynamic enum_ +{% endif %} + +{%- set has_enum_ops = element.inherits or element.include or element.minus %} + +{%- if has_enum_ops %} +## Enumeration Operations + +{%- if element.inherits %} +**Inherits From:** {{ gen.links(element.inherits) | join(', ') }} +{%- endif %} + +{%- if element.include %} +**Includes:** +{% for expr in element.include -%} +{{ render_enum_expression(expr) }} +{%- endfor %} +{%- endif %} + +{%- if element.minus %} +**Excludes:** +{% for expr in element.minus -%} +{{ render_enum_expression(expr) }} +{%- endfor %} +{%- endif %} + +{% endif %} + +{% set slots_for_enum = schemaview.get_slots_by_enum(element.name) %} +{% if slots_for_enum is defined and slots_for_enum|length > 0 -%} +## Slots + +| Name | Description | +| --- | --- | +{% for s in schemaview.get_slots_by_enum(element.name) -%} +| {{ gen.link(s) }} | {{ s.description|enshorten }} | +{% endfor %} +{% endif %} + +{% include "common_metadata.md.jinja2" %} + +## LinkML Source + +
+```yaml +{{ gen.yaml(element) }} +``` +
+ +
diff --git a/docs/templates-linkml/index.md.jinja2 b/docs/templates-linkml/index.md.jinja2 new file mode 100644 index 00000000..fe14498e --- /dev/null +++ b/docs/templates-linkml/index.md.jinja2 @@ -0,0 +1,62 @@ +# {% if schema.title %}{{ schema.title }}{% else %}{{ schema.name }}{% endif %} + +{% if schema.description %}{{ schema.description }}{% endif %} + +URI: {{ schema.id }} + +Name: {{ schema.name }} + +{% if include_top_level_diagram %} + +## Schema Diagram + +```{{ gen.mermaid_directive() }} +{{ gen.mermaid_diagram() }} +``` +{% endif %} + +## Classes + +| Class | Description | +| --- | --- | +{% if gen.hierarchical_class_view -%} +{% for u, v in gen.class_hierarchy_as_tuples() -%} +| {{ " "|safe*u*8 }}{{ gen.link(schemaview.get_class(v), True) }} | {{ schemaview.get_class(v).description|enshorten }} | +{% endfor %} +{% else -%} +{% for c in gen.all_class_objects()|sort(attribute=sort_by) -%} +| {{ gen.link(c, True) }} | {{ c.description|enshorten }} | +{% endfor %} +{% endif %} + +## Slots + +| Slot | Description | +| --- | --- | +{% for s in gen.all_slot_objects()|sort(attribute=sort_by) -%} +| {{ gen.link(s, True) }} | {{ s.description|enshorten }} | +{% endfor %} + +## Enumerations + +| Enumeration | Description | +| --- | --- | +{% for e in gen.all_enum_objects()|sort(attribute=sort_by) -%} +| {{ gen.link(e, True) }} | {{ e.description|enshorten }} | +{% endfor %} + +## Types + +| Type | Description | +| --- | --- | +{% for t in gen.all_type_objects()|sort(attribute=sort_by) -%} +| {{ gen.link(t, True) }} | {{ t.description|enshorten }} | +{% endfor %} + +## Subsets + +| Subset | Description | +| --- | --- | +{% for ss in schemaview.all_subsets().values()|sort(attribute='name') -%} +| {{ gen.link(ss, True) }} | {{ ss.description|enshorten }} | +{% endfor %} diff --git a/docs/templates-linkml/index.tex.jinja2 b/docs/templates-linkml/index.tex.jinja2 new file mode 100644 index 00000000..782b712b --- /dev/null +++ b/docs/templates-linkml/index.tex.jinja2 @@ -0,0 +1,26 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} + +\title{ {{ gen.latex(gen.schema_title()) }} } + +\begin{document} + +{{ schema.description }} + +URI: {{ gen.latex(schema.id) }} +Name: {{ gen.latex(schema.name) }} + +\subsection{Classes}\label{classes} + +\begin{tabular}{ |c|c| } + \hline + Class & Description \\\\ + \hline + +{% for c in schemaview.all_classes().values()|sort(attribute='name') -%} +{{ gen.latex(c.name) }} & {{ gen.latex(c.description) }} \\\\ +{% endfor %} + \hline +\end{tabular} + +\end{document} diff --git a/docs/templates-linkml/schema.md.jinja2 b/docs/templates-linkml/schema.md.jinja2 new file mode 100644 index 00000000..847c58b0 --- /dev/null +++ b/docs/templates-linkml/schema.md.jinja2 @@ -0,0 +1,5 @@ +# {{ schema.name }} {% if schema.deprecated %} (DEPRECATED) {% endif %} + +{{ schema.description }} + +URI: {{ schema.id }} diff --git a/docs/templates-linkml/slot.md.jinja2 b/docs/templates-linkml/slot.md.jinja2 new file mode 100644 index 00000000..5066e41b --- /dev/null +++ b/docs/templates-linkml/slot.md.jinja2 @@ -0,0 +1,490 @@ +--- +search: + boost: {% if element.deprecated %}0.5{% else %}5.0{% endif %} +--- + +{%- if element.title %} + {%- set title = element.title ~ ' (' ~ element.name ~ ')' -%} +{%- else %} + {%- if gen.use_slot_uris -%} + {%- set title = element.name -%} + {%- else -%} + {%- set title = gen.name(element) -%} + {%- endif -%} +{%- endif -%} + +{% macro compute_range(slot) -%} + {%- if slot.any_of or slot.exactly_one_of -%} + {%- for subslot_range in schemaview.slot_range_as_union(slot) -%} + {{ gen.link(subslot_range) }} + {%- if not loop.last -%} +  or 
+ {%- endif -%} + {%- endfor -%} + {%- else -%} + {{ gen.link(slot.range) }} + {%- endif -%} +{% endmacro %} + +# Slot: {{ title }} {% if element.deprecated %} (DEPRECATED) {% endif %} + +{%- if header -%} +{{ header }} +{%- endif -%} + +{% if element.description %} +{% set element_description_lines = element.description.split('\n') %} +{% for element_description_line in element_description_lines %} +_{{ element_description_line }}_ +{% endfor %} +{% endif %} + +
+ +{% if element.abstract %} +* __NOTE__: this is an abstract slot and should not be populated directly +{% endif %} + +URI: {{ gen.uri_link(element) }} + +{%- if element.alias %} +Alias: {{ element.alias }} +{% endif -%} + +{% if schemaview.slot_parents(element.name) or schemaview.slot_children(element.name, mixins=False) %} + +## Inheritance + +{{ gen.inheritance_tree(element, mixins=True) }} +{% else %} + +{% endif %} + +{% set classes_by_slot = schemaview.get_classes_by_slot(element, include_induced=True) %} +{% if classes_by_slot %} + +## Applicable Classes + +| Name | Description | Modifies Slot | +| --- | --- | --- | +{% for c in classes_by_slot -%} +| {{ gen.link(c) }} | {{ schemaview.get_class(c).description|enshorten }} | {% if c in schemaview.get_classes_modifying_slot(element) %} yes {% else %} no {% endif %} | +{% endfor %} + +{% endif %} + +{% if schemaview.is_mixin(element.name) %} +## Mixin Usage + +| mixed into | description | range | domain | +| --- | --- | --- | --- | +{% for s in schemaview.slot_children(element.name, is_a=False) -%} +| {{ gen.link(s) }} | {{ schemaview.get_slot(s).description|enshorten }} | {{ schemaview.get_slot(s).range }} | {{ schemaview.get_classes_by_slot(schemaview.get_slot(s))|join(', ') }} | +{% endfor %} +{% endif %} + +## Properties + +### Type and Range + +| Property | Value | +| --- | --- | +| Range | {{ compute_range(element) }} | +{%- if element.domain %} +| Domain | {{ gen.link(element.domain) }} | +{%- endif %} +{%- if element.domain_of %} +| Domain Of | {{ gen.links(element.domain_of) | join(', ') }} | +{%- endif %} +{%- if element.slot_uri %} +| Slot URI | {{ gen.uri_link(element.slot_uri) }} | +{%- endif %} +{%- if element.slot_group %} +| Slot Group | {{ gen.link(element.slot_group) }} | +{%- endif %} +{%- if element.is_grouping_slot %} +| Is Grouping Slot | Yes | +{%- endif %} + +### Cardinality and Requirements + +| Property | Value | +| --- | --- | +{%- if element.required %} +| Required | Yes | +{%- elif element.recommended %} +| Recommended | Yes | +{%- endif %} +{%- if element.multivalued %} +| Multivalued | Yes | +{%- endif %} +{%- if element.minimum_cardinality is not none %} +| Minimum Cardinality | {{ element.minimum_cardinality }} | +{%- endif %} +{%- if element.maximum_cardinality is not none %} +| Maximum Cardinality | {{ element.maximum_cardinality }} | +{%- endif %} +{%- if element.exact_cardinality is not none %} +| Exact Cardinality | {{ element.exact_cardinality }} | +{%- endif %} + +{%- if element.multivalued and (element.list_elements_unique is not none or element.list_elements_ordered is not none) %} +### List/Collection Properties + +| Property | Value | +| --- | --- | +{%- if element.list_elements_unique is not none %} +| Elements Must Be Unique | {% if element.list_elements_unique %}Yes{% else %}No{% endif %} | +{%- endif %} +{%- if element.list_elements_ordered is not none %} +| Elements Are Ordered | {% if element.list_elements_ordered %}Yes{% else %}No{% endif %} | +{%- endif %} + +{% endif %} +{%- set has_slot_chars = element.key or element.identifier or element.designates_type + or element.inherited or element.readonly or element.ifabsent or element.owner + or element.shared or element.is_class_field or element.is_usage_slot + or element.usage_slot_name or element.singular_name or schemaview.is_mixin(element.name) %} + +{%- if has_slot_chars %} +### Slot Characteristics + +| Property | Value | +| --- | --- | +{%- if element.singular_name %} +| Singular Name | {{ element.singular_name }} | +{%- endif %} +{%- if element.key %} +| Key | Yes | +{%- endif %} +{%- if element.identifier %} +| Identifier | Yes | +{%- endif %} +{%- if element.designates_type %} +| Designates Type | Yes | +{%- endif %} +{%- if element.inherited %} +| Inherited | Yes | +{%- endif %} +{%- if element.readonly %} +| Readonly | Yes | +{%- endif %} +{%- if element.ifabsent %} +| If Absent | `{{ element.ifabsent }}` | +{%- endif %} +{%- if element.owner %} +| Owner | {{ gen.link(element.owner) }} | +{%- endif %} +{%- if element.shared %} +| Shared | Yes | +{%- endif %} +{%- if element.is_class_field %} +| Is Class Field | Yes | +{%- endif %} +{%- if element.is_usage_slot %} +| Is Usage Slot | Yes | +{%- endif %} +{%- if element.usage_slot_name %} +| Usage Slot Name | {{ element.usage_slot_name }} | +{%- endif %} +{%- if schemaview.is_mixin(element.name) %} +| Mixin | Yes | +{%- endif %} + +{% endif %} +{%- set has_basic_constraints = element.minimum_value is not none or element.maximum_value is not none or element.pattern %} + +{%- if has_basic_constraints %} +### Value Constraints + +| Property | Value | +| --- | --- | +{%- if element.minimum_value is not none %} +| Minimum Value | {{ element.minimum_value|int }} | +{%- endif %} +{%- if element.maximum_value is not none %} +| Maximum Value | {{ element.maximum_value|int }} | +{%- endif %} +{%- if element.pattern %} +| Regex Pattern | `{{ element.pattern }}` | +{%- endif %} + +{% endif %} +{%- set has_advanced_constraints = element.structured_pattern or element.equals_string + or element.equals_string_in or element.equals_number or element.enum_range + or element.unit or element.implicit_prefix %} + +{%- if has_advanced_constraints %} +
+Additional Constraints + +{%- if element.structured_pattern %} +**Structured Pattern:** + +- **Syntax:** `{{ element.structured_pattern.syntax }}` +- **Interpolated:** {{ element.structured_pattern.interpolated }} +{%- if element.structured_pattern.partial_match %} +- **Partial Match:** Yes +{%- endif %} +{%- endif %} + +{%- if element.equals_string %} +**Must Equal:** `{{ element.equals_string }}` +{%- endif %} + +{%- if element.equals_string_in %} +**Must Be One Of:** {{ element.equals_string_in | join(', ') }} +{%- endif %} + +{%- if element.equals_number %} +**Must Equal:** {{ element.equals_number }} +{%- endif %} + +{%- if element.enum_range %} +**Enumeration Range:** {{ gen.link(element.enum_range) }} +{%- endif %} + +{%- if element.unit %} +**Unit:** + +| Property | Value | +| --- | --- | +{%- if element.unit.symbol %} +| symbol | {{ element.unit.symbol }} | +{%- endif %} +{%- if element.unit.abbreviation %} +| abbreviation | {{ element.unit.abbreviation }} | +{%- endif %} +{%- if element.unit.descriptive_name %} +| descriptive_name | {{ element.unit.descriptive_name }} | +{%- endif %} +{%- if element.unit.ucum_code %} +| ucum_code | {{ element.unit.ucum_code }} | +{%- endif %} +{%- if element.unit.derivation %} +| derivation | {{ element.unit.derivation }} | +{%- endif %} +{%- if element.unit.iec61360code %} +| iec61360code | {{ element.unit.iec61360code }} | +{%- endif %} +{%- if element.unit.has_quantity_kind %} +| has_quantity_kind | {{ gen.uri_link(element.unit.has_quantity_kind) }} | +{%- endif %} +{%- if element.unit.exact_mappings %} +| exact_mappings | {% for m in element.unit.exact_mappings %}{{ gen.uri_link(m) }}{% if not loop.last %}, {% endif %}{% endfor %} | +{%- endif %} +{%- endif %} + +{%- if element.implicit_prefix %} +**Implicit Prefix:** {{ element.implicit_prefix }} +{%- endif %} + +
+{% endif %} + +{%- set has_rel_props = element.symmetric or element.asymmetric or element.reflexive + or element.locally_reflexive or element.irreflexive or element.transitive + or element.inverse or element.transitive_form_of or element.reflexive_transitive_form_of + or element.role or element.relational_role %} + +{%- if has_rel_props %} +
+Relationship Properties + +| Property | Value | +| --- | --- | +{%- if element.symmetric %} +| Symmetric | Yes | +{%- endif %} +{%- if element.asymmetric %} +| Asymmetric | Yes | +{%- endif %} +{%- if element.reflexive %} +| Reflexive | Yes | +{%- endif %} +{%- if element.locally_reflexive %} +| Locally Reflexive | Yes | +{%- endif %} +{%- if element.irreflexive %} +| Irreflexive | Yes | +{%- endif %} +{%- if element.transitive %} +| Transitive | Yes | +{%- endif %} +{%- if element.inverse %} +| Inverse | {{ gen.link(element.inverse) }} | +{%- endif %} +{%- if element.transitive_form_of %} +| Transitive Form Of | {{ gen.link(element.transitive_form_of) }} | +{%- endif %} +{%- if element.reflexive_transitive_form_of %} +| Reflexive Transitive Form Of | {{ gen.link(element.reflexive_transitive_form_of) }} | +{%- endif %} +{%- if element.role %} +| Role | {{ element.role }} | +{%- endif %} +{%- if element.relational_role %} +| Relational Role | {{ element.relational_role }} | +{%- endif %} + +
+{% endif %} + +{%- set has_advanced = element.path_rule or element.disjoint_with + or element.children_are_mutually_disjoint or element.subproperty_of + or element.array or element.bindings or element.type_mappings + or element.value_presence or element.range_expression %} + +{%- if has_advanced %} +
+Advanced Properties + +{%- if element.subproperty_of %} +**Subproperty Of:** {{ gen.link(element.subproperty_of) }} +{%- endif %} + +{%- if element.path_rule %} +**Path Rule:** + +``` +{{ element.path_rule }} +``` +{%- endif %} + +{%- if element.disjoint_with %} +**Disjoint With:** {{ gen.links(element.disjoint_with) | join(', ') }} +{%- endif %} + +{%- if element.children_are_mutually_disjoint %} +**Children Are Mutually Disjoint:** Yes +{%- endif %} + +{%- if element.array %} +**Array Configuration:** + +- **Dimensions:** {{ element.array.dimensions | join(' x ') }} +{%- if element.array.exact_number_dimensions %} +- **Exact Dimensions Required:** Yes +{%- endif %} +{%- endif %} + +{%- if element.range_expression %} +**Range Expression:** {{ element.range_expression }} +{%- endif %} + +{%- if element.value_presence %} +**Value Presence:** {{ element.value_presence }} +{%- endif %} + +{%- if element.bindings %} +**Term Bindings:** + +{%- for binding in element.bindings %} +- {{ binding }} +{%- endfor %} +{%- endif %} + +{%- if element.type_mappings %} +**Type Mappings:** + +{%- for tm in element.type_mappings %} +- **Framework:** {{ tm.framework }}, **Mapping:** {{ tm.mapping }} +{%- endfor %} +{%- endif %} + +
+{% endif %} + +{%- set has_expressions = element.any_of or element.all_of or element.exactly_one_of + or element.none_of or element.equals_expression or element.has_member or element.all_members %} + +{%- if has_expressions %} +
+Expressions & Logic + +{%- if element.any_of %} +#### Any Of + +Value must satisfy at least one of: + +{%- for expr in element.any_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.all_of %} +#### All Of + +Value must satisfy all of: + +{%- for expr in element.all_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.exactly_one_of %} +#### Exactly One Of + +Value must satisfy exactly one of: + +{%- for expr in element.exactly_one_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.none_of %} +#### None Of + +Value must not satisfy any of: + +{%- for expr in element.none_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.equals_expression %} +#### Equals Expression + +`{{ element.equals_expression }}` +{%- endif %} + +{%- if element.has_member %} +#### Has Member + +{{ element.has_member }} +{%- endif %} + +{%- if element.all_members %} +#### All Members + +{{ element.all_members }} +{%- endif %} + +
+{% endif %} + +{% if schemaview.usage_index().get(element.name) %} +## Usages + +| used by | used in | type | used | +| --- | --- | --- | --- | +{% for usage in schemaview.usage_index().get(element.name) -%} +| {{ gen.link(usage.used_by) }} | {{ gen.link(usage.slot) }} | {{ usage.metaslot }} | {{ gen.link(usage.used) }} | +{% endfor %} +{% endif %} + +{% include "common_metadata.md.jinja2" %} + +## LinkML Source + +
+```yaml +{{ gen.yaml(element) }} +``` +
+ +{%- if footer -%} +{{ footer }} +{%- endif -%} + +
diff --git a/docs/templates-linkml/subset.md.jinja2 b/docs/templates-linkml/subset.md.jinja2 new file mode 100644 index 00000000..0a05161d --- /dev/null +++ b/docs/templates-linkml/subset.md.jinja2 @@ -0,0 +1,113 @@ +--- +search: + boost: {% if element.deprecated %}0.5{% else %}1.0{% endif %} +--- + + +# Subset: {{ gen.name(element) }} {% if element.deprecated %} (DEPRECATED) {% endif %} + +{%- if header -%} +{{ header }} +{%- endif -%} + +{% if element.description %} +{% set element_description_lines = element.description.split('\n') %} +{% for element_description_line in element_description_lines %} +_{{ element_description_line }}_ +{% endfor %} +{% endif %} + +
+ +URI: {{ gen.link(element) }} + +{% include "common_metadata.md.jinja2" %} + +{% set classes_in_subset = [] %} +{% set slots_in_subset = [] %} +{% set enums_in_subset = [] %} + +{# Collect classes, slots, and enumerations in subset #} +{% for c in gen.all_class_objects()|sort(attribute=sort_by) %} + {%- if element.name in c.in_subset %} + {% set _ = classes_in_subset.append(c) %} + {%- endif %} +{% endfor %} + +{% for s in gen.all_slot_objects()|sort(attribute=sort_by) %} + {%- if element.name in s.in_subset %} + {% set _ = slots_in_subset.append(s) %} + {%- endif %} +{% endfor %} + +{% for e in schemaview.all_enums().values() %} + {%- if element.name in e.in_subset %} + {% set _ = enums_in_subset.append(e) %} + {%- endif %} +{% endfor %} + +{% if classes_in_subset %} +## Classes in subset + +| Class | Description | +| --- | --- | +{% for c in classes_in_subset -%} +{%- if element.name in c.in_subset -%} +| {{ gen.link(c) }} | {{ c.description|enshorten }} | +{% endif -%} +{% endfor %} + +{% for c in classes_in_subset -%} +{%- if element.name in c.in_subset -%} + +{% set induced_slots = gen.class_induced_slots(c.name)|sort(attribute=sort_by) %} + +{%- set filtered_slots = [] -%} +{%- for s in induced_slots|sort(attribute=sort_by) -%} + {%- if element.name in s.in_subset or element.name in schemaview.get_slot(s.name).in_subset -%} + {% set _ = filtered_slots.append(s) %} + {%- endif -%} +{%- endfor %} + +{%- if filtered_slots|length > 0 -%} +### Slots from {{ gen.link(c) }} also in _{{ element.name }}_ + +| Name | Cardinality and Range | Description | +| --- | --- | --- | +{% for s in filtered_slots -%} +| {{ gen.link(s) }} | {{ gen.cardinality(s) }}
{{ gen.link(s.range) }} | {{ s.description|enshorten }} {% if s.identifier %}**identifier**{% endif %} | +{% endfor %} +{%- endif %} + +{%- endif %} +{% endfor %} + +{%- endif %} + +{% if slots_in_subset %} +## Slots in subset + +| Slot | Description | +| --- | --- | +{% for s in slots_in_subset|sort(attribute=sort_by) -%} +{%- if element.name in s.in_subset -%} +| {{ gen.link(s) }} | {{ s.description|enshorten }} | +{%- endif %} +{% endfor %} + +{%- endif %} + +{% if enums_in_subset %} +## Enumerations in subset + +| Enumeration | Description | +| --- | --- | +{% for e in enums_in_subset|sort(attribute='name') -%} +{% if element.name in e.in_subset -%} +| {{ gen.link(e) }} | {{ e.description|enshorten }} | +{%- endif %} +{% endfor %} + +{%- endif %} + +
diff --git a/docs/templates-linkml/type.md.jinja2 b/docs/templates-linkml/type.md.jinja2 new file mode 100644 index 00000000..642a6ba8 --- /dev/null +++ b/docs/templates-linkml/type.md.jinja2 @@ -0,0 +1,170 @@ +--- +search: + boost: {% if element.deprecated %}0.5{% else %}1.0{% endif %} +--- + +{%- if element.title and element.title != element.name %} + {%- set title = element.title ~ ' (' ~ element.name ~ ')' -%} +{%- else %} + {%- set title = gen.name(element) -%} +{%- endif -%} + +# Type: {{ title }} {% if element.deprecated %} (DEPRECATED) {% endif %} + +{% if element.description %} +{% set element_description_lines = element.description.split('\n') %} +{% for element_description_line in element_description_lines %} +_{{ element_description_line }}_ +{% endfor %} +{% endif %} + +
+ +URI: {{ gen.uri_link(element) }} + +## Type Properties + +| Property | Value | +| --- | --- | +{%- if element.typeof %} +| Type Of | {{ gen.link(element.typeof) }} | +{%- endif %} +{%- if element.base %} +| Base | `{{ element.base }}` | +{%- endif %} +{%- if element.uri %} +| Type URI | {{ gen.uri_link(element.uri) }} | +{%- endif %} +{%- if element.repr %} +| Representation | `{{ element.repr }}` | +{%- endif %} +{%- if element.union_of %} +| Union Of | {{ gen.links(element.union_of) | join(', ') }} | +{%- endif %} + +{%- set has_basic_constraints = element.minimum_value is not none or element.maximum_value is not none or element.pattern %} + +{%- if has_basic_constraints %} +## Value Constraints + +| Property | Value | +| --- | --- | +{%- if element.minimum_value is not none or element.maximum_value is not none %} +| Numeric Range | {{ gen.number_value_range(element) }} | +{%- endif %} +{%- if element.pattern %} +| Regex Pattern | `{{ element.pattern }}` | +{%- endif %} + +{% endif %} +{%- set has_advanced_constraints = element.structured_pattern or element.equals_string + or element.equals_string_in or element.equals_number or element.unit or element.implicit_prefix %} + +{%- if has_advanced_constraints %} +
+Additional Constraints + +{%- if element.structured_pattern %} +**Structured Pattern:** + +- **Syntax:** `{{ element.structured_pattern.syntax }}` +- **Interpolated:** {{ element.structured_pattern.interpolated }} +{%- if element.structured_pattern.partial_match %} +- **Partial Match:** Yes +{%- endif %} +{%- endif %} + +{%- if element.equals_string %} +**Must Equal:** `{{ element.equals_string }}` +{%- endif %} + +{%- if element.equals_string_in %} +**Must Be One Of:** {{ element.equals_string_in | join(', ') }} +{%- endif %} + +{%- if element.equals_number %} +**Must Equal:** {{ element.equals_number }} +{%- endif %} + +{%- if element.unit %} +**Unit:** + +| Property | Value | +| --- | --- | +{%- if element.unit.symbol %} +| symbol | {{ element.unit.symbol }} | +{%- endif %} +{%- if element.unit.abbreviation %} +| abbreviation | {{ element.unit.abbreviation }} | +{%- endif %} +{%- if element.unit.descriptive_name %} +| descriptive_name | {{ element.unit.descriptive_name }} | +{%- endif %} +{%- if element.unit.ucum_code %} +| ucum_code | {{ element.unit.ucum_code }} | +{%- endif %} +{%- if element.unit.derivation %} +| derivation | {{ element.unit.derivation }} | +{%- endif %} +{%- if element.unit.iec61360code %} +| iec61360code | {{ element.unit.iec61360code }} | +{%- endif %} +{%- if element.unit.has_quantity_kind %} +| has_quantity_kind | {{ gen.uri_link(element.unit.has_quantity_kind) }} | +{%- endif %} +{%- if element.unit.exact_mappings %} +| exact_mappings | {% for m in element.unit.exact_mappings %}{{ gen.uri_link(m) }}{% if not loop.last %}, {% endif %}{% endfor %} | +{%- endif %} +{%- endif %} + +{%- if element.implicit_prefix %} +**Implicit Prefix:** {{ element.implicit_prefix }} +{%- endif %} + +
+{% endif %} + +{%- set has_expressions = element.any_of or element.all_of or element.exactly_one_of or element.none_of %} + +{%- if has_expressions %} +
+Type Expressions + +{%- if element.any_of %} +**Any Of:** Value must satisfy at least one of these expressions + +{%- for expr in element.any_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.all_of %} +**All Of:** Value must satisfy all of these expressions + +{%- for expr in element.all_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.exactly_one_of %} +**Exactly One Of:** Value must satisfy exactly one of these expressions + +{%- for expr in element.exactly_one_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +{%- if element.none_of %} +**None Of:** Value must not satisfy any of these expressions + +{%- for expr in element.none_of %} +- {{ expr }} +{%- endfor %} +{%- endif %} + +
+{% endif %} + +{% include "common_metadata.md.jinja2" %} + +
From 525d8689aaafb71e37464cb48b758e76d216d1bc Mon Sep 17 00:00:00 2001 From: Eric Torstenson Date: Fri, 28 Aug 2026 13:38:16 -0500 Subject: [PATCH 2/4] First pass at cleaningup up the docs --- config.public.mk | 2 +- docs/templates-linkml/class.md.jinja2 | 17 +++++++++++------ docs/templates-linkml/slot.md.jinja2 | 20 ++++++++++++-------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/config.public.mk b/config.public.mk index ff16ec86..dcf10297 100644 --- a/config.public.mk +++ b/config.public.mk @@ -17,7 +17,7 @@ LINKML_SCHEMA_SOURCE_DIR="src/common_access_model/schema" LINKML_GENERATORS_CONFIG_YAML=config.yaml ## pass args if gendoc ignores config.yaml (i.e. --no-mergeimports) -LINKML_GENERATORS_DOC_ARGS= +LINKML_GENERATORS_DOC_ARGS="--template-directory docs/templates-linkml" ## pass args to workaround genowl rdfs config bug (linkml#1453) ## (i.e. --no-type-objects --no-metaclasses --metadata-profile rdfs) diff --git a/docs/templates-linkml/class.md.jinja2 b/docs/templates-linkml/class.md.jinja2 index d68301bd..4d4e350c 100644 --- a/docs/templates-linkml/class.md.jinja2 +++ b/docs/templates-linkml/class.md.jinja2 @@ -15,12 +15,17 @@ search: {% macro compute_range(slot) -%} {%- if slot.any_of or slot.exactly_one_of -%} - {%- for subslot_range in schemaview.slot_range_as_union(slot) -%} - {{ gen.link(subslot_range) }} - {%- if not loop.last -%} -  or 
- {%- endif -%} - {%- endfor -%} + {%- set expr_ranges = (slot.exactly_one_of + slot.any_of) | selectattr('range') | map(attribute='range') | unique | list -%} + {%- if expr_ranges -%} + {%- for subslot_range in expr_ranges -%} + {{ gen.link(subslot_range) }} + {%- if not loop.last -%} +  or 
+ {%- endif -%} + {%- endfor -%} + {%- else -%} + {{ gen.link(slot.range) }} + {%- endif -%} {%- else -%} {{ gen.link(slot.range) }} {%- endif -%} diff --git a/docs/templates-linkml/slot.md.jinja2 b/docs/templates-linkml/slot.md.jinja2 index 5066e41b..8f31f48b 100644 --- a/docs/templates-linkml/slot.md.jinja2 +++ b/docs/templates-linkml/slot.md.jinja2 @@ -15,17 +15,21 @@ search: {% macro compute_range(slot) -%} {%- if slot.any_of or slot.exactly_one_of -%} - {%- for subslot_range in schemaview.slot_range_as_union(slot) -%} - {{ gen.link(subslot_range) }} - {%- if not loop.last -%} -  or 
- {%- endif -%} - {%- endfor -%} + {%- set expr_ranges = (slot.exactly_one_of + slot.any_of) | selectattr('range') | map(attribute='range') | unique | list -%} + {%- if expr_ranges -%} + {%- for subslot_range in expr_ranges -%} + {{ gen.link(subslot_range) }} + {%- if not loop.last -%} +  or 
+ {%- endif -%} + {%- endfor -%} + {%- else -%} + {{ gen.link(slot.range) }} + {%- endif -%} {%- else -%} {{ gen.link(slot.range) }} {%- endif -%} {% endmacro %} - # Slot: {{ title }} {% if element.deprecated %} (DEPRECATED) {% endif %} {%- if header -%} @@ -408,7 +412,7 @@ Alias: {{ element.alias }} Value must satisfy at least one of: {%- for expr in element.any_of %} -- {{ expr }} +- {% if expr.range %}{{ gen.link(expr.range) }}{% endif %}{% if expr.pattern %} matching pattern `{{ expr.pattern }}`{% endif %}{% if expr.minimum_value is not none %}, minimum {{ expr.minimum_value }}{% endif %}{% if expr.maximum_value is not none %}, maximum {{ expr.maximum_value }}{% endif %} {%- endfor %} {%- endif %} From 26fbc0cc7bd550ee5b23fad373f3e41c7cda23f1 Mon Sep 17 00:00:00 2001 From: Eric Torstenson Date: Fri, 28 Aug 2026 13:41:44 -0500 Subject: [PATCH 3/4] add in support for other types of optional slot content --- docs/templates-linkml/slot.md.jinja2 | 60 +++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/docs/templates-linkml/slot.md.jinja2 b/docs/templates-linkml/slot.md.jinja2 index 8f31f48b..039a6102 100644 --- a/docs/templates-linkml/slot.md.jinja2 +++ b/docs/templates-linkml/slot.md.jinja2 @@ -30,6 +30,17 @@ search: {{ gen.link(slot.range) }} {%- endif -%} {% endmacro %} + +{% macro expr_line(expr) -%} +{%- if expr.range %}{{ gen.link(expr.range) }}{% endif -%} +{%- if expr.pattern %} matching pattern `{{ expr.pattern }}`{% endif -%} +{%- if expr.minimum_value is not none %}, minimum {{ expr.minimum_value }}{% endif -%} +{%- if expr.maximum_value is not none %}, maximum {{ expr.maximum_value }}{% endif -%} +{%- if expr.equals_string %}, must equal `{{ expr.equals_string }}`{% endif -%} +{%- if expr.equals_string_in %}, must be one of {{ expr.equals_string_in | join(', ') }}{% endif -%} +{%- if expr.equals_number %}, must equal {{ expr.equals_number }}{% endif -%} +{% endmacro %} + # Slot: {{ title }} {% if element.deprecated %} (DEPRECATED) {% endif %} {%- if header -%} @@ -399,50 +410,79 @@ Alias: {{ element.alias }} {% endif %} -{%- set has_expressions = element.any_of or element.all_of or element.exactly_one_of - or element.none_of or element.equals_expression or element.has_member or element.all_members %} +{%- set any_of_has_extra = element.any_of | selectattr('pattern') | list + or element.any_of | selectattr('minimum_value') | list + or element.any_of | selectattr('maximum_value') | list + or element.any_of | selectattr('equals_string') | list + or element.any_of | selectattr('equals_string_in') | list + or element.any_of | selectattr('equals_number') | list %} + +{%- set all_of_has_extra = element.all_of | selectattr('pattern') | list + or element.all_of | selectattr('minimum_value') | list + or element.all_of | selectattr('maximum_value') | list + or element.all_of | selectattr('equals_string') | list + or element.all_of | selectattr('equals_string_in') | list + or element.all_of | selectattr('equals_number') | list %} + +{%- set exactly_one_of_has_extra = element.exactly_one_of | selectattr('pattern') | list + or element.exactly_one_of | selectattr('minimum_value') | list + or element.exactly_one_of | selectattr('maximum_value') | list + or element.exactly_one_of | selectattr('equals_string') | list + or element.exactly_one_of | selectattr('equals_string_in') | list + or element.exactly_one_of | selectattr('equals_number') | list %} + +{%- set none_of_has_extra = element.none_of | selectattr('pattern') | list + or element.none_of | selectattr('minimum_value') | list + or element.none_of | selectattr('maximum_value') | list + or element.none_of | selectattr('equals_string') | list + or element.none_of | selectattr('equals_string_in') | list + or element.none_of | selectattr('equals_number') | list %} + +{%- set has_expressions = (element.any_of and any_of_has_extra) or (element.all_of and all_of_has_extra) + or (element.exactly_one_of and exactly_one_of_has_extra) or (element.none_of and none_of_has_extra) + or element.equals_expression or element.has_member or element.all_members %} {%- if has_expressions %}
Expressions & Logic -{%- if element.any_of %} +{%- if element.any_of and any_of_has_extra %} #### Any Of Value must satisfy at least one of: {%- for expr in element.any_of %} -- {% if expr.range %}{{ gen.link(expr.range) }}{% endif %}{% if expr.pattern %} matching pattern `{{ expr.pattern }}`{% endif %}{% if expr.minimum_value is not none %}, minimum {{ expr.minimum_value }}{% endif %}{% if expr.maximum_value is not none %}, maximum {{ expr.maximum_value }}{% endif %} +- {{ expr_line(expr) }} {%- endfor %} {%- endif %} -{%- if element.all_of %} +{%- if element.all_of and all_of_has_extra %} #### All Of Value must satisfy all of: {%- for expr in element.all_of %} -- {{ expr }} +- {{ expr_line(expr) }} {%- endfor %} {%- endif %} -{%- if element.exactly_one_of %} +{%- if element.exactly_one_of and exactly_one_of_has_extra %} #### Exactly One Of Value must satisfy exactly one of: {%- for expr in element.exactly_one_of %} -- {{ expr }} +- {{ expr_line(expr) }} {%- endfor %} {%- endif %} -{%- if element.none_of %} +{%- if element.none_of and none_of_has_extra %} #### None Of Value must not satisfy any of: {%- for expr in element.none_of %} -- {{ expr }} +- {{ expr_line(expr) }} {%- endfor %} {%- endif %} From 9dde6c13b438ed7c12b23d1dbe2cc81c00d9ac78 Mon Sep 17 00:00:00 2001 From: Eric Torstenson Date: Fri, 28 Aug 2026 16:28:47 -0500 Subject: [PATCH 4/4] Fix the weird CSS behavior due to instant --- mkdocs.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index be98403f..18062730 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,8 +12,10 @@ theme: - content.tabs.link - header.autohide # maximum screen space during scrolling - navigation.expand # optimize user experience - - navigation.instant # optimize user experience - - navigation.instant.progress + # The navigation.instant is causing glitchy behavior with the styling + # due to a bug in mkdocs.material related to mermaid. + #- navigation.instant # optimize user experience + #- navigation.instant.progress - navigation.tracking # update address bar - search.suggest # suggest good completions - content.code.copy # enable copy to clipboard