linkml 1.7.8__py3-none-any.whl → 1.7.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- linkml/generators/csvgen.py +15 -5
- linkml/generators/docgen/class_diagram.md.jinja2 +19 -4
- linkml/generators/docgen.py +59 -14
- linkml/generators/graphqlgen.py +14 -16
- linkml/generators/jsonldcontextgen.py +3 -3
- linkml/generators/jsonldgen.py +3 -2
- linkml/generators/jsonschemagen.py +9 -0
- linkml/generators/markdowngen.py +341 -301
- linkml/generators/owlgen.py +87 -20
- linkml/generators/plantumlgen.py +9 -8
- linkml/generators/prefixmapgen.py +15 -23
- linkml/generators/protogen.py +23 -18
- linkml/generators/pydanticgen/pydanticgen.py +11 -2
- linkml/generators/pythongen.py +2 -5
- linkml/generators/rdfgen.py +5 -4
- linkml/generators/shaclgen.py +8 -6
- linkml/generators/shexgen.py +9 -7
- linkml/generators/summarygen.py +8 -2
- linkml/generators/terminusdbgen.py +2 -2
- linkml/generators/yumlgen.py +2 -2
- linkml/utils/__init__.py +3 -0
- linkml/utils/deprecation.py +255 -0
- linkml/utils/generator.py +78 -56
- linkml/validator/cli.py +11 -0
- linkml/validator/plugins/jsonschema_validation_plugin.py +2 -0
- linkml/validator/report.py +1 -0
- linkml/workspaces/example_runner.py +2 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/METADATA +1 -1
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/RECORD +32 -31
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/LICENSE +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/WHEEL +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/entry_points.txt +0 -0
linkml/generators/markdowngen.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import os
|
2
|
-
|
2
|
+
import re
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from io import StringIO
|
5
4
|
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
6
5
|
|
7
6
|
import click
|
@@ -63,7 +62,7 @@ class MarkdownGenerator(Generator):
|
|
63
62
|
index_file: str = "index.md",
|
64
63
|
noimages: bool = False,
|
65
64
|
**_,
|
66
|
-
) ->
|
65
|
+
) -> str:
|
67
66
|
self.gen_classes = classes if classes else []
|
68
67
|
for cls in self.gen_classes:
|
69
68
|
if cls not in self.schema.classes:
|
@@ -85,248 +84,270 @@ class MarkdownGenerator(Generator):
|
|
85
84
|
os.makedirs(os.path.join(directory, "types"), exist_ok=True)
|
86
85
|
|
87
86
|
with open(self.exist_warning(directory, index_file), "w", encoding="UTF-8") as ixfile:
|
88
|
-
|
89
|
-
|
87
|
+
items = []
|
88
|
+
items.append(self.frontmatter(f"{self.schema.name}"))
|
89
|
+
items.append(
|
90
90
|
self.para(
|
91
91
|
f"**metamodel version:** {self.schema.metamodel_version}\n\n**version:** {self.schema.version}"
|
92
92
|
)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
self.
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
93
|
+
)
|
94
|
+
items.append(self.para(be(self.schema.description)))
|
95
|
+
|
96
|
+
items.append(self.header(3, "Classes"))
|
97
|
+
for cls in sorted(self.schema.classes.values(), key=lambda c: c.name):
|
98
|
+
if not cls.is_a and not cls.mixin and self.is_secondary_ref(cls.name):
|
99
|
+
items.append(self.class_hier(cls))
|
100
|
+
|
101
|
+
items.append(self.header(3, "Mixins"))
|
102
|
+
for cls in sorted(self.schema.classes.values(), key=lambda c: c.name):
|
103
|
+
if cls.mixin and self.is_secondary_ref(cls.name):
|
104
|
+
items.append(self.class_hier(cls))
|
105
|
+
|
106
|
+
items.append(self.header(3, "Slots"))
|
107
|
+
for slot in sorted(self.schema.slots.values(), key=lambda s: s.name):
|
108
|
+
if not slot.is_a and self.is_secondary_ref(slot.name):
|
109
|
+
items.append(self.pred_hier(slot))
|
110
|
+
|
111
|
+
items.append(self.header(3, "Enums"))
|
112
|
+
for enu in sorted(self.schema.enums.values(), key=lambda e: e.name):
|
113
|
+
items.append(self.enum_hier(enu))
|
114
|
+
|
115
|
+
items.append(self.header(3, "Subsets"))
|
116
|
+
for subset in sorted(self.schema.subsets.values(), key=lambda s: s.name):
|
117
|
+
items.append(self.bullet(self.subset_link(subset, use_desc=True), 0))
|
118
|
+
|
119
|
+
items.append(self.header(3, "Types"))
|
120
|
+
items.append(self.header(4, "Built in"))
|
121
|
+
for builtin_name in sorted(self.synopsis.typebases.keys()):
|
122
|
+
items.append(self.bullet(f"**{builtin_name}**"))
|
123
|
+
items.append(self.header(4, "Defined"))
|
124
|
+
for typ in sorted(self.schema.types.values(), key=lambda t: t.name):
|
125
|
+
if self.is_secondary_ref(typ.name):
|
126
|
+
if typ.typeof:
|
127
|
+
typ_typ = self.type_link(typ.typeof)
|
128
|
+
else:
|
129
|
+
typ_typ = f"**{typ.base}**"
|
129
130
|
|
130
|
-
|
131
|
+
items.append(self.bullet(self.type_link(typ, after_link=f" ({typ_typ})", use_desc=True)))
|
132
|
+
items = [i for i in items if i is not None]
|
133
|
+
out = "\n".join(items) + "\n"
|
134
|
+
out = pad_heading(out)
|
135
|
+
ixfile.write(out)
|
136
|
+
return out
|
131
137
|
|
132
|
-
def visit_class(self, cls: ClassDefinition) ->
|
138
|
+
def visit_class(self, cls: ClassDefinition) -> str:
|
133
139
|
# allow client to relabel metamodel
|
134
140
|
mixin_local_name = self.get_metamodel_slot_name("Mixin")
|
135
141
|
class_local_name = self.get_metamodel_slot_name("Class")
|
136
142
|
|
137
143
|
if self.gen_classes and cls.name not in self.gen_classes:
|
138
|
-
return
|
144
|
+
return ""
|
139
145
|
|
140
146
|
with open(self.exist_warning(self.dir_path(cls)), "w", encoding="UTF-8") as clsfile:
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
self.
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
self.header(2, f"Referenced by {class_local_name}")
|
193
|
-
for sn in sorted(self.synopsis.classrefs[cls.name].slotrefs):
|
194
|
-
slot = self.schema.slots[sn]
|
195
|
-
if slot.range == cls.name:
|
147
|
+
items = []
|
148
|
+
class_curi = self.namespaces.uri_or_curie_for(str(self.namespaces._base), camelcase(cls.name))
|
149
|
+
class_uri = self.namespaces.uri_for(class_curi)
|
150
|
+
items.append(self.element_header(cls, cls.name, class_curi, class_uri))
|
151
|
+
items.append("")
|
152
|
+
if not self.noyuml:
|
153
|
+
if self.image_directory:
|
154
|
+
yg = YumlGenerator(self)
|
155
|
+
yg.serialize(
|
156
|
+
classes=[cls.name],
|
157
|
+
directory=self.image_directory,
|
158
|
+
load_image=not self.noimages,
|
159
|
+
)
|
160
|
+
img_url = os.path.join("images", os.path.basename(yg.output_file_name))
|
161
|
+
else:
|
162
|
+
yg = YumlGenerator(self)
|
163
|
+
img_url = (
|
164
|
+
yg.serialize(classes=[cls.name]).replace("?", "%3F").replace(" ", "%20").replace("|", "|")
|
165
|
+
)
|
166
|
+
|
167
|
+
items.append(f"[]({img_url})")
|
168
|
+
|
169
|
+
if cls.id_prefixes:
|
170
|
+
items.append(self.header(2, "Identifier prefixes"))
|
171
|
+
for p in cls.id_prefixes:
|
172
|
+
items.append(self.bullet(f"{p}"))
|
173
|
+
|
174
|
+
if cls.is_a is not None:
|
175
|
+
items.append(self.header(2, "Parents"))
|
176
|
+
items.append(self.bullet(f" is_a: {self.class_link(cls.is_a, use_desc=True)}"))
|
177
|
+
if cls.mixins:
|
178
|
+
items.append(self.header(2, f"Uses {mixin_local_name}"))
|
179
|
+
for mixin in cls.mixins:
|
180
|
+
items.append(self.bullet(f" mixin: {self.class_link(mixin, use_desc=True)}"))
|
181
|
+
|
182
|
+
if cls.name in self.synopsis.isarefs:
|
183
|
+
items.append(self.header(2, "Children"))
|
184
|
+
for child in sorted(self.synopsis.isarefs[cls.name].classrefs):
|
185
|
+
items.append(self.bullet(f"{self.class_link(child, use_desc=True)}"))
|
186
|
+
|
187
|
+
if cls.name in self.synopsis.mixinrefs:
|
188
|
+
items.append(self.header(2, f"{mixin_local_name} for"))
|
189
|
+
for mixin in sorted(self.synopsis.mixinrefs[cls.name].classrefs):
|
190
|
+
items.append(self.bullet(f'{self.class_link(mixin, use_desc=True, after_link="(mixin)")}'))
|
191
|
+
|
192
|
+
if cls.name in self.synopsis.classrefs:
|
193
|
+
items.append(self.header(2, f"Referenced by {class_local_name}"))
|
194
|
+
for sn in sorted(self.synopsis.classrefs[cls.name].slotrefs):
|
195
|
+
slot = self.schema.slots[sn]
|
196
|
+
if slot.range == cls.name:
|
197
|
+
items.append(
|
196
198
|
self.bullet(
|
197
199
|
f" **{self.class_link(slot.domain)}** "
|
198
200
|
f"*{self.slot_link(slot, add_subset=False)}*{self.predicate_cardinality(slot)} "
|
199
201
|
f"**{self.class_type_link(slot.range)}**"
|
200
202
|
)
|
203
|
+
)
|
201
204
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
205
|
+
items.append(self.header(2, "Attributes"))
|
206
|
+
|
207
|
+
# List all of the slots that directly belong to the class
|
208
|
+
slot_list = [slot for slot in [self.schema.slots[sn] for sn in cls.slots]]
|
209
|
+
own_slots = [slot for slot in slot_list if cls.name in slot.domain_of]
|
210
|
+
if own_slots:
|
211
|
+
items.append(self.header(3, "Own"))
|
212
|
+
for slot in own_slots:
|
213
|
+
items.append(self.slot_field(cls, slot))
|
214
|
+
slot_list.remove(slot)
|
215
|
+
|
216
|
+
# List all of the inherited slots
|
217
|
+
ancestors = set(self.ancestors(cls))
|
218
|
+
inherited_slots = [slot for slot in slot_list if set(slot.domain_of).intersection(ancestors)]
|
219
|
+
if inherited_slots:
|
220
|
+
items.append(self.header(3, "Inherited from " + cls.is_a + ":"))
|
221
|
+
for inherited_slot in inherited_slots:
|
222
|
+
items.append(self.slot_field(cls, inherited_slot))
|
223
|
+
slot_list.remove(inherited_slot)
|
224
|
+
|
225
|
+
# List all of the slots acquired through mixing
|
226
|
+
mixed_in_classes = set()
|
227
|
+
for mixin in cls.mixins:
|
228
|
+
mixed_in_classes.add(mixin)
|
229
|
+
mixed_in_classes.update(set(self.ancestors(self.schema.classes[mixin])))
|
230
|
+
for slot in slot_list:
|
231
|
+
mixers = set(slot.domain_of).intersection(mixed_in_classes)
|
232
|
+
for mixer in mixers:
|
233
|
+
items.append(self.header(3, "Mixed in from " + mixer + ":"))
|
234
|
+
items.append(self.slot_field(cls, slot))
|
235
|
+
|
236
|
+
items.append(self.element_properties(cls))
|
237
|
+
out = "\n".join(items)
|
238
|
+
out = pad_heading(out)
|
239
|
+
clsfile.write(out)
|
240
|
+
return out
|
241
|
+
|
242
|
+
def visit_type(self, typ: TypeDefinition) -> str:
|
238
243
|
with open(self.exist_warning(self.dir_path(typ)), "w", encoding="UTF-8") as typefile:
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
244
|
+
type_uri = typ.definition_uri
|
245
|
+
type_curie = self.namespaces.curie_for(type_uri)
|
246
|
+
out = self.element_header(typ, typ.name, type_curie, type_uri)
|
247
|
+
|
248
|
+
out = "\n".join([out, "| | | |"])
|
249
|
+
out = "\n".join([out, "| --- | --- | --- |"])
|
250
|
+
if typ.typeof:
|
251
|
+
out = "\n".join([out, f"| Parent type | | {self.class_type_link(typ.typeof)} |"])
|
252
|
+
out = "\n".join([out, f"| Root (builtin) type | | **{typ.base}** |"])
|
253
|
+
if typ.repr:
|
254
|
+
out = "\n".join([out, f"| Representation | | {typ.repr} |"])
|
255
|
+
out += self.element_properties(typ)
|
256
|
+
out += "\n"
|
257
|
+
out = pad_heading(out)
|
258
|
+
typefile.write(out)
|
259
|
+
return out
|
260
|
+
|
261
|
+
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> str:
|
254
262
|
with open(self.exist_warning(self.dir_path(slot)), "w", encoding="UTF-8") as slotfile:
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
+
items = []
|
264
|
+
slot_curie = self.namespaces.uri_or_curie_for(str(self.namespaces._base), underscore(slot.name))
|
265
|
+
slot_uri = self.namespaces.uri_for(slot_curie)
|
266
|
+
items.append(self.element_header(slot, aliased_slot_name, slot_curie, slot_uri))
|
267
|
+
|
268
|
+
items.append(self.header(2, "Domain and Range"))
|
269
|
+
items.append(
|
270
|
+
(
|
263
271
|
f"{self.class_link(slot.domain)} →{self.predicate_cardinality(slot)} "
|
264
272
|
f"{self.class_type_link(slot.range)}"
|
265
273
|
)
|
274
|
+
)
|
266
275
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
276
|
+
items.append(self.header(2, "Parents"))
|
277
|
+
if slot.is_a:
|
278
|
+
items.append(self.bullet(f" is_a: {self.slot_link(slot.is_a)}"))
|
279
|
+
|
280
|
+
items.append(self.header(2, "Children"))
|
281
|
+
if slot.name in sorted(self.synopsis.isarefs):
|
282
|
+
for child in sorted(self.synopsis.isarefs[slot.name].slotrefs):
|
283
|
+
items.append(self.bullet(f" {self.slot_link(child)}"))
|
284
|
+
|
285
|
+
items.append(self.header(2, "Used by"))
|
286
|
+
if slot.name in sorted(self.synopsis.slotrefs):
|
287
|
+
for rc in sorted(self.synopsis.slotrefs[slot.name].classrefs):
|
288
|
+
items.append(self.bullet(f"{self.class_link(rc)}"))
|
289
|
+
if aliased_slot_name == "relation":
|
290
|
+
if slot.subproperty_of:
|
291
|
+
reifies = (
|
292
|
+
self.slot_link(slot.subproperty_of)
|
293
|
+
if slot.subproperty_of in self.schema.slots
|
294
|
+
else slot.subproperty_of
|
295
|
+
)
|
296
|
+
items.append(self.bullet(f" reifies: {reifies}"))
|
297
|
+
items.append(self.element_properties(slot))
|
298
|
+
out = "\n".join(items)
|
299
|
+
out = pad_heading(out)
|
300
|
+
slotfile.write(out)
|
301
|
+
return out
|
302
|
+
|
303
|
+
def visit_enum(self, enum: EnumDefinition) -> str:
|
291
304
|
with open(self.exist_warning(self.dir_path(enum)), "w", encoding="UTF-8") as enumfile:
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
305
|
+
items = []
|
306
|
+
enum_curie = self.namespaces.uri_or_curie_for(str(self.namespaces._base), underscore(enum.name))
|
307
|
+
enum_uri = self.namespaces.uri_for(enum_curie)
|
308
|
+
items.append(self.element_header(obj=enum, name=enum.name, curie=enum_curie, uri=enum_uri))
|
309
|
+
items.append(self.element_properties(enum))
|
310
|
+
out = "\n".join(items)
|
311
|
+
out = pad_heading(out)
|
312
|
+
enumfile.write(out)
|
313
|
+
return out
|
314
|
+
|
315
|
+
def visit_subset(self, subset: SubsetDefinition) -> str:
|
299
316
|
with open(self.exist_warning(self.dir_path(subset)), "w", encoding="UTF-8") as subsetfile:
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
317
|
+
items = []
|
318
|
+
curie = self.namespaces.uri_or_curie_for(str(self.namespaces._base), underscore(subset.name))
|
319
|
+
uri = self.namespaces.uri_for(curie)
|
320
|
+
items.append(self.element_header(obj=subset, name=subset.name, curie=curie, uri=uri))
|
321
|
+
# TODO: consider showing hierarchy within a subset
|
322
|
+
items.append(self.header(3, "Classes"))
|
323
|
+
for cls in sorted(self.schema.classes.values(), key=lambda c: c.name):
|
324
|
+
if not cls.mixin:
|
325
|
+
if cls.in_subset and subset.name in cls.in_subset:
|
326
|
+
items.append(self.bullet(self.class_link(cls, use_desc=True), 0))
|
327
|
+
items.append(self.header(3, "Mixins"))
|
328
|
+
for cls in sorted(self.schema.classes.values(), key=lambda c: c.name):
|
329
|
+
if cls.mixin:
|
330
|
+
if cls.in_subset and subset.name in cls.in_subset:
|
331
|
+
items.append(self.bullet(self.class_link(cls, use_desc=True), 0))
|
332
|
+
items.append(self.header(3, "Slots"))
|
333
|
+
for slot in sorted(self.schema.slots.values(), key=lambda s: s.name):
|
334
|
+
if slot.in_subset and subset.name in slot.in_subset:
|
335
|
+
items.append(self.bullet(self.slot_link(slot, use_desc=True), 0))
|
336
|
+
items.append(self.header(3, "Types"))
|
337
|
+
for type in sorted(self.schema.types.values(), key=lambda s: s.name):
|
338
|
+
if type.in_subset and subset.name in type.in_subset:
|
339
|
+
items.append(self.bullet(self.type_link(type, use_desc=True), 0))
|
340
|
+
items.append(self.header(3, "Enums"))
|
341
|
+
for enum in sorted(self.schema.enums.values(), key=lambda s: s.name):
|
342
|
+
if enum.in_subset and subset.name in enum.in_subset:
|
343
|
+
items.append(self.bullet(self.enum_link(enum, use_desc=True), 0))
|
344
|
+
items.append(self.element_properties(subset))
|
345
|
+
out = "\n".join(items)
|
346
|
+
out = pad_heading(out)
|
347
|
+
subsetfile.write(out)
|
348
|
+
return out
|
349
|
+
|
350
|
+
def element_header(self, obj: Element, name: str, curie: str, uri: str) -> str:
|
330
351
|
if isinstance(obj, TypeDefinition):
|
331
352
|
obj_type = "Type"
|
332
353
|
elif isinstance(obj, ClassDefinition):
|
@@ -341,13 +362,13 @@ class MarkdownGenerator(Generator):
|
|
341
362
|
obj_type = "Class"
|
342
363
|
|
343
364
|
header_label = f"{obj_type}: ~~{name}~~ _(deprecated)_" if obj.deprecated else f"{obj_type}: {name}"
|
344
|
-
self.header(1, header_label)
|
365
|
+
out = self.header(1, header_label)
|
345
366
|
|
346
|
-
self.para(be(obj.description))
|
347
|
-
|
348
|
-
|
367
|
+
out += self.para(be(obj.description))
|
368
|
+
out = "\n".join([out, f"URI: [{curie}]({uri})", ""])
|
369
|
+
return out
|
349
370
|
|
350
|
-
def element_properties(self, obj: Element) ->
|
371
|
+
def element_properties(self, obj: Element) -> str:
|
351
372
|
def identity(e: Any) -> Any:
|
352
373
|
return e
|
353
374
|
|
@@ -355,21 +376,24 @@ class MarkdownGenerator(Generator):
|
|
355
376
|
title: str,
|
356
377
|
entries: Union[List, Dict],
|
357
378
|
formatter: Optional[Callable[[Element], str]] = None,
|
358
|
-
) ->
|
379
|
+
) -> Optional[str]:
|
359
380
|
if formatter is None:
|
360
381
|
formatter = identity
|
361
382
|
if isinstance(entries, (dict, JsonObj)):
|
362
383
|
entries = list(values(entries))
|
363
384
|
if entries:
|
364
|
-
|
385
|
+
items = []
|
386
|
+
items.append(f"| **{title}:** | | {formatter(entries[0])} |")
|
365
387
|
for entry in entries[1:]:
|
366
|
-
|
388
|
+
items.append(f"| | | {formatter(entry)} |")
|
389
|
+
return "\n".join(items)
|
367
390
|
|
368
|
-
def enum_list(title: str, obj: EnumDefinition) ->
|
391
|
+
def enum_list(title: str, obj: EnumDefinition) -> str:
|
369
392
|
# This data is from the enum provided in the YAML
|
370
|
-
|
371
|
-
|
372
|
-
|
393
|
+
items = []
|
394
|
+
items.append(self.header(2, title))
|
395
|
+
items.append("| Text | Description | Meaning | Other Information |")
|
396
|
+
items.append("| :--- | :---: | :---: | ---: |")
|
373
397
|
|
374
398
|
for item, item_info in obj.permissible_values.items():
|
375
399
|
text = ""
|
@@ -388,66 +412,81 @@ class MarkdownGenerator(Generator):
|
|
388
412
|
other[k] = item_info[k]
|
389
413
|
if not other:
|
390
414
|
other = ""
|
391
|
-
|
415
|
+
items.append(f"| {text} | {desc} | {meaning} | {other} |")
|
416
|
+
return "\n".join(items)
|
417
|
+
|
418
|
+
items = []
|
392
419
|
|
393
|
-
|
394
|
-
|
395
|
-
prop_list("Aliases", obj.aliases)
|
420
|
+
items.append(prop_list("Aliases", obj.aliases))
|
421
|
+
items.append(
|
396
422
|
prop_list(
|
397
423
|
"Local names",
|
398
424
|
obj.local_names,
|
399
425
|
lambda e: f"{e.local_name_value} ({e.local_name_source})",
|
400
426
|
)
|
401
|
-
|
427
|
+
)
|
428
|
+
items.append(prop_list("Mappings", obj.mappings))
|
429
|
+
items.append(
|
402
430
|
prop_list(
|
403
431
|
"Alt Descriptions",
|
404
432
|
obj.alt_descriptions,
|
405
433
|
lambda e: f"{e.description} ({e.source})",
|
406
434
|
)
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
435
|
+
)
|
436
|
+
# todos
|
437
|
+
# notes
|
438
|
+
items.append(prop_list("Comments", obj.comments))
|
439
|
+
items.append(prop_list("Examples", obj.examples))
|
440
|
+
items.append(prop_list("In Subsets", obj.in_subset))
|
441
|
+
# from_schema
|
442
|
+
# imported_from
|
443
|
+
items.append(prop_list("See also", [f"[{v}]({v})" for v in obj.see_also]))
|
444
|
+
items.append(prop_list("Exact Mappings", obj.exact_mappings))
|
445
|
+
items.append(prop_list("Close Mappings", obj.close_mappings))
|
446
|
+
items.append(prop_list("Narrow Mappings", obj.narrow_mappings))
|
447
|
+
items.append(prop_list("Broad Mappings", obj.broad_mappings))
|
448
|
+
items.append(prop_list("Related Mappings", obj.related_mappings))
|
449
|
+
|
450
|
+
items = [i for i in items if i is not None]
|
451
|
+
if len(items) > 0:
|
452
|
+
header = "\n".join([self.header(2, "Other properties"), "| | | |", "| --- | --- | --- |"])
|
453
|
+
items.insert(0, header)
|
454
|
+
|
455
|
+
# - exact mappings
|
456
|
+
# - close mappings
|
457
|
+
# - related mappings
|
458
|
+
# - deprecated element has exact replacement
|
459
|
+
# - deprecated element has possible replacement
|
460
|
+
if type(obj) == EnumDefinition:
|
461
|
+
items.insert(0, enum_list("Permissible Values", obj))
|
462
|
+
items.insert(1, "\n")
|
463
|
+
|
464
|
+
out = "\n".join(items)
|
465
|
+
return out
|
466
|
+
|
467
|
+
def class_hier(self, cls: ClassDefinition, level=0) -> str:
|
468
|
+
items = []
|
469
|
+
items.append(self.bullet(self.class_link(cls, use_desc=True), level))
|
436
470
|
if cls.name in sorted(self.synopsis.isarefs):
|
437
471
|
for child in sorted(self.synopsis.isarefs[cls.name].classrefs):
|
438
|
-
self.class_hier(self.schema.classes[child], level + 1)
|
472
|
+
items.append(self.class_hier(self.schema.classes[child], level + 1))
|
473
|
+
return "\n".join(items) if items else None
|
439
474
|
|
440
|
-
def pred_hier(self, slot: SlotDefinition, level=0) ->
|
441
|
-
|
475
|
+
def pred_hier(self, slot: SlotDefinition, level=0) -> str:
|
476
|
+
items = []
|
477
|
+
items.append(self.bullet(self.slot_link(slot, use_desc=True), level))
|
442
478
|
if slot.name in sorted(self.synopsis.isarefs):
|
443
479
|
for child in sorted(self.synopsis.isarefs[slot.name].slotrefs):
|
444
|
-
self.pred_hier(self.schema.slots[child], level + 1)
|
480
|
+
items.append(self.pred_hier(self.schema.slots[child], level + 1))
|
481
|
+
return "\n".join(items) if items else None
|
445
482
|
|
446
|
-
def enum_hier(self, enum: EnumDefinition, level=0) ->
|
447
|
-
|
483
|
+
def enum_hier(self, enum: EnumDefinition, level=0) -> str:
|
484
|
+
items = []
|
485
|
+
items.append(self.bullet(self.enum_link(enum, use_desc=True), level))
|
448
486
|
if enum.name in sorted(self.synopsis.isarefs):
|
449
487
|
for child in sorted(self.synopsis.isarefs[enum.name].classrefs):
|
450
|
-
self.enum_hier(self.schema.enums[child], level + 1)
|
488
|
+
items.append(self.enum_hier(self.schema.enums[child], level + 1))
|
489
|
+
return "\n".join(items) if items else None
|
451
490
|
|
452
491
|
def dir_path(
|
453
492
|
self,
|
@@ -465,15 +504,6 @@ class MarkdownGenerator(Generator):
|
|
465
504
|
subdir = "/types" if isinstance(obj, TypeDefinition) and not self.no_types_dir else ""
|
466
505
|
return f"{self.directory}{subdir}/{filename}.md"
|
467
506
|
|
468
|
-
def mappings(self, obj: Union[SlotDefinition, ClassDefinition]) -> None:
|
469
|
-
# TODO: get rid of this?
|
470
|
-
# self.header(2, 'Mappings')
|
471
|
-
# for mapping in obj.mappings:
|
472
|
-
# self.bullet(f"{self.xlink(mapping)} {self.to_uri(mapping)}")
|
473
|
-
# if obj.subclass_of:
|
474
|
-
# self.bullet(self.xlink(obj.subclass_of))
|
475
|
-
pass
|
476
|
-
|
477
507
|
def is_secondary_ref(self, en: str) -> bool:
|
478
508
|
"""Determine whether 'en' is the name of something in the neighborhood of the requested classes
|
479
509
|
|
@@ -492,23 +522,27 @@ class MarkdownGenerator(Generator):
|
|
492
522
|
else:
|
493
523
|
return True
|
494
524
|
|
495
|
-
def slot_field(self, cls: ClassDefinition, slot: SlotDefinition) ->
|
496
|
-
|
525
|
+
def slot_field(self, cls: ClassDefinition, slot: SlotDefinition) -> str:
|
526
|
+
items = []
|
527
|
+
items.append(self.bullet(f"{self.slot_link(slot)}{self.predicate_cardinality(slot)}"))
|
497
528
|
if slot.description:
|
498
|
-
self.bullet(f"Description: {slot.description}", level=1)
|
499
|
-
self.bullet(f"Range: {self.class_type_link(slot.range)}", level=1)
|
529
|
+
items.append(self.bullet(f"Description: {slot.description}", level=1))
|
530
|
+
items.append(self.bullet(f"Range: {self.class_type_link(slot.range)}", level=1))
|
500
531
|
# if slot.subproperty_of:
|
501
532
|
# self.bullet(f'edge label: {self.slot_link(slot.subproperty_of)}', level=1)
|
502
533
|
for example in slot.examples:
|
503
|
-
|
504
|
-
|
505
|
-
|
534
|
+
items.append(
|
535
|
+
self.bullet(
|
536
|
+
f'Example: {getattr(example, "value", " ")} {getattr(example, "description", " ")}',
|
537
|
+
level=1,
|
538
|
+
)
|
506
539
|
)
|
507
540
|
# if slot.name not in self.own_slot_names(cls):
|
508
541
|
# self.bullet(f'inherited from: {self.class_link(slot.domain)}', level=1)
|
509
542
|
if slot.in_subset:
|
510
543
|
ssl = ",".join(slot.in_subset)
|
511
|
-
self.bullet(f"in subsets: ({ssl})", level=1)
|
544
|
+
items.append(self.bullet(f"in subsets: ({ssl})", level=1))
|
545
|
+
return "\n".join(items)
|
512
546
|
|
513
547
|
def to_uri(self, uri_or_curie: str) -> str:
|
514
548
|
"""Return the URI for the slot if known"""
|
@@ -541,27 +575,28 @@ class MarkdownGenerator(Generator):
|
|
541
575
|
return f" <sub><b>{card_str}</b></sub>"
|
542
576
|
|
543
577
|
@staticmethod
|
544
|
-
def anchor(id_: str) ->
|
545
|
-
|
578
|
+
def anchor(id_: str) -> str:
|
579
|
+
return f'<a name="{id_}">'
|
546
580
|
|
547
581
|
@staticmethod
|
548
|
-
def anchorend() ->
|
549
|
-
|
582
|
+
def anchorend() -> str:
|
583
|
+
return "</a>"
|
550
584
|
|
551
|
-
def header(self, level: int, txt: str) ->
|
585
|
+
def header(self, level: int, txt: str) -> str:
|
552
586
|
txt = self.get_metamodel_slot_name(txt)
|
553
|
-
|
587
|
+
out = f'\n{"#" * level} {txt}\n'
|
588
|
+
return out
|
554
589
|
|
555
590
|
@staticmethod
|
556
|
-
def para(txt: str) ->
|
557
|
-
|
591
|
+
def para(txt: str) -> str:
|
592
|
+
return f"\n{txt}\n"
|
558
593
|
|
559
594
|
@staticmethod
|
560
|
-
def bullet(txt: str, level=0) ->
|
561
|
-
|
595
|
+
def bullet(txt: str, level=0) -> str:
|
596
|
+
return f'{" " * level} * {txt}'
|
562
597
|
|
563
|
-
def frontmatter(self, thingtype: str, layout="default") ->
|
564
|
-
self.header(1, thingtype)
|
598
|
+
def frontmatter(self, thingtype: str, layout="default") -> str:
|
599
|
+
return self.header(1, thingtype)
|
565
600
|
# print(f'---\nlayout: {layout}\n---\n')
|
566
601
|
|
567
602
|
def bbin(self, obj: Element) -> str:
|
@@ -748,6 +783,11 @@ class MarkdownGenerator(Generator):
|
|
748
783
|
return uri
|
749
784
|
|
750
785
|
|
786
|
+
def pad_heading(text: str) -> str:
|
787
|
+
"""Add an extra newline to a non-top-level header that doesn't have one preceding it"""
|
788
|
+
return re.sub(r"(?<!\n)\n##", "\n\n##", text)
|
789
|
+
|
790
|
+
|
751
791
|
@shared_arguments(MarkdownGenerator)
|
752
792
|
@click.command()
|
753
793
|
@click.option("--dir", "-d", required=True, help="Output directory")
|