@eriknaslund/skiss 0.2.1 → 0.3.0

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.
package/dist/index.d.ts CHANGED
@@ -63,8 +63,12 @@ interface ClassNode {
63
63
  line: number;
64
64
  }
65
65
  type Severity = 'error' | 'warning';
66
- /** Codes from the table in docs/ARCHITECTURE.md. `parse` adds the `E_` codes, `resolve` the `W_` codes. */
67
- type DiagnosticCode = 'E_UNPARSABLE' | 'E_FIELD_WITHOUT_CLASS' | 'E_MISSING_TYPE' | 'E_UNCLOSED_MANY' | 'E_BAD_NAME' | 'W_UNKNOWN_TYPE' | 'W_UNDECLARED_CLASS' | 'W_UNDECLARED_FIELD' | 'W_DUPLICATE_CLASS' | 'W_DUPLICATE_FIELD' | 'W_MULTIPLE_IDENTIFIERS';
66
+ /**
67
+ * Codes from the tables in docs/ARCHITECTURE.md. `parse` adds the `E_` codes,
68
+ * `resolve` the `W_` codes, and `importLinkML` the one code of its own,
69
+ * `E_NOT_YAML`, for a text no schema can be read out of.
70
+ */
71
+ type DiagnosticCode = 'E_UNPARSABLE' | 'E_FIELD_WITHOUT_CLASS' | 'E_MISSING_TYPE' | 'E_UNCLOSED_MANY' | 'E_BAD_NAME' | 'W_UNKNOWN_TYPE' | 'W_UNDECLARED_CLASS' | 'W_UNDECLARED_FIELD' | 'W_DUPLICATE_CLASS' | 'W_DUPLICATE_FIELD' | 'W_MULTIPLE_IDENTIFIERS' | 'E_NOT_YAML';
68
72
  interface Diagnostic {
69
73
  severity: Severity;
70
74
  code: DiagnosticCode;
@@ -168,6 +172,41 @@ declare function compile(source: string, opts: CompileOptions): CompileResult;
168
172
  */
169
173
  declare function formatDiagnostic(d: Diagnostic, file?: string): string;
170
174
 
175
+ /**
176
+ * One thing the projection could not carry. `kind` is the LinkML key, or one
177
+ * of the projection's own kinds: `renamed`, `narrowed`, `inlined`,
178
+ * `enum_detail`, `annotation`, `schema`. `element` names what carried it as
179
+ * the sketch names it — `Class`, `Class.field` — or, for an enum, its LinkML
180
+ * name, which the sketch does not keep.
181
+ */
182
+ interface Dropped {
183
+ kind: string;
184
+ element: string;
185
+ detail?: string;
186
+ }
187
+ interface FromLinkMLResult {
188
+ /** The projected sketch, parsed from `source`, so every node has a position. */
189
+ document: Document;
190
+ /** The sketch as canonical Skiss text. */
191
+ source: string;
192
+ /** SPEC §8, in schema order: class by class, then enums, then the schema itself. */
193
+ dropped: Dropped[];
194
+ }
195
+ declare function fromLinkML(schema: unknown): FromLinkMLResult;
196
+ declare function formatDropped(dropped: Dropped[]): string;
197
+
198
+ declare function toSkiss(doc: Document): string;
199
+
200
+ interface ImportResult {
201
+ /** The projected sketch as canonical Skiss text, `\n`-terminated. */
202
+ output: string;
203
+ /** SPEC §8, in schema order: what the projection could not carry. */
204
+ dropped: Dropped[];
205
+ /** The diagnostics of the projected sketch. Their lines are lines of `output`. */
206
+ diagnostics: Diagnostic[];
207
+ }
208
+ declare function importLinkML(text: string): ImportResult;
209
+
171
210
  declare function parse(source: string): Document;
172
211
 
173
212
  /** What `resolve` returns: a `Document` whose `undeclared` list is always present. */
@@ -176,6 +215,6 @@ interface ResolvedDocument extends Document {
176
215
  }
177
216
  declare function resolve(doc: Document): ResolvedDocument;
178
217
 
179
- declare const VERSION = "0.2.1";
218
+ declare const VERSION = "0.3.0";
180
219
 
181
- export { type ClassNode, type CompileOptions, type CompileResult, type Diagnostic, type DiagnosticCode, type Document, type FieldNode, type JoinRef, type LinkMLAnnotations, type LinkMLAttribute, type LinkMLClass, type LinkMLCompileOptions, type LinkMLEnum, type LinkMLOptions, type LinkMLSchema, type MermaidCompileOptions, type MermaidOptions, type Name, type Primitive, type ResolvedDocument, type SerializeFormat, type Severity, type TypeRef, VERSION, compile, formatDiagnostic, parse, resolve, serialize, toLinkML, toMermaid };
220
+ export { type ClassNode, type CompileOptions, type CompileResult, type Diagnostic, type DiagnosticCode, type Document, type Dropped, type FieldNode, type FromLinkMLResult, type ImportResult, type JoinRef, type LinkMLAnnotations, type LinkMLAttribute, type LinkMLClass, type LinkMLCompileOptions, type LinkMLEnum, type LinkMLOptions, type LinkMLSchema, type MermaidCompileOptions, type MermaidOptions, type Name, type Primitive, type ResolvedDocument, type SerializeFormat, type Severity, type TypeRef, VERSION, compile, formatDiagnostic, formatDropped, fromLinkML, importLinkML, parse, resolve, serialize, toLinkML, toMermaid, toSkiss };
package/dist/index.js CHANGED
@@ -520,9 +520,9 @@ function resolve(doc) {
520
520
  }
521
521
  }
522
522
  }
523
- const present = new Set(out.diagnostics.map(key));
523
+ const present2 = new Set(out.diagnostics.map(key));
524
524
  for (const w of warnings) {
525
- if (!present.has(key(w))) out.diagnostics.push(w);
525
+ if (!present2.has(key(w))) out.diagnostics.push(w);
526
526
  }
527
527
  out.diagnostics.sort((a, b) => a.line - b.line);
528
528
  out.undeclared = [...undeclared.values()].map((n) => ({ ...n }));
@@ -908,16 +908,521 @@ function formatDiagnostic(d, file) {
908
908
  return `${where}: ${d.severity} ${d.code} ${d.message}`;
909
909
  }
910
910
 
911
+ // src/generators/skiss.ts
912
+ var TRAILER_COLUMN = 40;
913
+ function toSkiss(doc) {
914
+ if (doc.classes.length === 0) return "";
915
+ return `${doc.classes.map(classBlock).join("\n\n")}
916
+ `;
917
+ }
918
+ function classBlock(cls) {
919
+ const head = cls.name.text + (cls.system === void 0 ? "" : ` @${cls.system.text}`) + (cls.similarTo === void 0 ? "" : ` ~ ${cls.similarTo.text}`);
920
+ return [withTrailer(head, cls), ...cls.fields.map(fieldLine)].join("\n");
921
+ }
922
+ function fieldLine(field) {
923
+ const type = typeText2(field.type);
924
+ const head = ` ${field.name.text}` + (field.identifier ? "*" : "") + (type === void 0 ? "" : `: ${type}`) + (field.system === void 0 ? "" : ` @${field.system.text}`) + (field.joinsTo === void 0 ? "" : ` = ${field.joinsTo.className.text}.${field.joinsTo.fieldName.text}`);
925
+ return withTrailer(head, field);
926
+ }
927
+ function typeText2(type) {
928
+ if (type === void 0) return void 0;
929
+ let text;
930
+ switch (type.kind) {
931
+ case "primitive":
932
+ if (type.name === "string" && !type.many) return void 0;
933
+ text = type.name;
934
+ break;
935
+ case "class":
936
+ case "unknown":
937
+ text = type.name.text;
938
+ break;
939
+ case "enum":
940
+ text = type.values.map((v) => v.text).join("|");
941
+ break;
942
+ }
943
+ return type.many ? `${text}[]` : text;
944
+ }
945
+ function withTrailer(head, node) {
946
+ const parts2 = [];
947
+ if (node.description !== void 0 && node.description !== "")
948
+ parts2.push(`# ${node.description}`);
949
+ if (node.note !== void 0 && node.note !== "") parts2.push(`? ${node.note}`);
950
+ if (parts2.length === 0) return head;
951
+ const gap = head.length < TRAILER_COLUMN - 1 ? " ".repeat(TRAILER_COLUMN - head.length) : " ";
952
+ return head + gap + parts2.join(" ");
953
+ }
954
+
955
+ // src/generators/from-linkml.ts
956
+ var PRIMITIVE_RANGE = {
957
+ string: "string",
958
+ integer: "int",
959
+ float: "float",
960
+ double: "float",
961
+ decimal: "float",
962
+ boolean: "bool",
963
+ date: "date",
964
+ datetime: "datetime",
965
+ uri: "uri",
966
+ uriorcurie: "uri"
967
+ };
968
+ var CLASS_KEYS = /* @__PURE__ */ new Set(["description", "annotations", "close_mappings", "attributes", "slots"]);
969
+ var SLOT_KEYS = /* @__PURE__ */ new Set(["description", "annotations", "range", "multivalued", "identifier"]);
970
+ var CLASS_TAGS = /* @__PURE__ */ new Set(["system", "note", "undeclared"]);
971
+ var SLOT_TAGS = /* @__PURE__ */ new Set(["system", "note", "joins_to"]);
972
+ var QUIET_SCHEMA_KEYS = /* @__PURE__ */ new Set([
973
+ "id",
974
+ "name",
975
+ "prefixes",
976
+ "imports",
977
+ "default_prefix",
978
+ "default_range",
979
+ "title",
980
+ "license",
981
+ "version",
982
+ "classes",
983
+ "slots",
984
+ "enums"
985
+ ]);
986
+ var UNKNOWN_TYPE = /^[a-z][a-z0-9_-]*$/;
987
+ function fromLinkML(schema) {
988
+ const root = asRecord(schema);
989
+ if (root === void 0) return nothing("schema", "the schema is not an object");
990
+ const schemaName = schemaElement(root);
991
+ const classBlock2 = asRecord(get(root, "classes"));
992
+ if (classBlock2 === void 0) return nothing(schemaName, "the schema has no `classes`");
993
+ const classDefs = classBlock2;
994
+ const dropped = [];
995
+ const report = (kind, element, detail) => {
996
+ dropped.push(detail === void 0 ? { kind, element } : { kind, element, detail });
997
+ };
998
+ const slotDefs = asRecord(get(root, "slots")) ?? {};
999
+ const enumDefs = asRecord(get(root, "enums")) ?? {};
1000
+ const defaultRange = stringOf(get(root, "default_range")) ?? "string";
1001
+ const classNames = /* @__PURE__ */ new Map();
1002
+ const takenClasses = /* @__PURE__ */ new Set();
1003
+ for (const key2 of Object.keys(classDefs)) {
1004
+ classNames.set(key2, unique(toClassName(key2), takenClasses));
1005
+ }
1006
+ const fieldNames = /* @__PURE__ */ new Map();
1007
+ for (const key2 of Object.keys(classDefs)) {
1008
+ const names = /* @__PURE__ */ new Map();
1009
+ const taken = /* @__PURE__ */ new Set();
1010
+ for (const slot of slotKeysOf(asRecord(get(classDefs, key2)) ?? {})) {
1011
+ names.set(slot, unique(toFieldName(slot), taken));
1012
+ }
1013
+ fieldNames.set(key2, names);
1014
+ }
1015
+ const referenced = /* @__PURE__ */ new Set();
1016
+ const reference = (element, from, to) => {
1017
+ if (from === to || referenced.has(`${element}\0${to}`)) return;
1018
+ referenced.add(`${element}\0${to}`);
1019
+ report("renamed", element, `\`${to}\` from \`${from}\``);
1020
+ };
1021
+ const classNameOf = (key2) => classNames.get(key2) ?? toClassName(key2);
1022
+ const fieldNameOf = (classKey, slot) => fieldNames.get(classKey)?.get(slot) ?? toFieldName(slot);
1023
+ const enumValues = /* @__PURE__ */ new Map();
1024
+ const unwritable = /* @__PURE__ */ new Map();
1025
+ for (const key2 of Object.keys(enumDefs)) {
1026
+ const rawValues = get(asRecord(get(enumDefs, key2)) ?? {}, "permissible_values");
1027
+ const values = asRecord(rawValues);
1028
+ const keys = Object.keys(values ?? {});
1029
+ const bad = keys.find((value) => !SYSTEM_OR_VALUE.test(value));
1030
+ if (values === void 0 && present(rawValues)) {
1031
+ unwritable.set(key2, `${shown(rawValues)} is not a map of permissible values`);
1032
+ } else if (keys.length < 2) {
1033
+ const count = `${keys.length} permissible value${keys.length === 1 ? "" : "s"}`;
1034
+ unwritable.set(key2, `${count} cannot be an inline enum (\xA73.4)`);
1035
+ } else if (bad !== void 0) {
1036
+ unwritable.set(key2, `\`${bad}\` is not a value Skiss can write (\xA74)`);
1037
+ } else {
1038
+ enumValues.set(key2, keys);
1039
+ }
1040
+ }
1041
+ const enumUses = [];
1042
+ const classes = [];
1043
+ for (const key2 of Object.keys(classDefs)) {
1044
+ const raw = get(classDefs, key2);
1045
+ const body = asRecord(raw);
1046
+ const name2 = classNameOf(key2);
1047
+ if (body === void 0 && raw !== null && raw !== void 0) {
1048
+ report("class", name2, "is not a class definition");
1049
+ continue;
1050
+ }
1051
+ const def = body ?? {};
1052
+ if (name2 !== key2) report("renamed", name2, `from \`${key2}\``);
1053
+ const annotations = annotationsOf(def);
1054
+ if (annotations.get("undeclared") === true) {
1055
+ for (const other of Object.keys(def)) {
1056
+ if (other !== "annotations") report(other, name2);
1057
+ }
1058
+ continue;
1059
+ }
1060
+ const node = { name: at0(name2), fields: [], line: 0 };
1061
+ const description = oneLine(stringOf(get(def, "description")));
1062
+ if (description !== void 0) node.description = description;
1063
+ const system = annotations.get("system");
1064
+ if (typeof system === "string" && SYSTEM_OR_VALUE.test(system)) node.system = at0(system);
1065
+ else if (system !== void 0) report("annotation", name2, "`system` is not a system name");
1066
+ const note = oneLine(stringOf(annotations.get("note")));
1067
+ if (note !== void 0) node.note = note;
1068
+ const mappings = stringList(get(def, "close_mappings"));
1069
+ const first = mappings[0];
1070
+ const target = first === void 0 ? "" : localName(first);
1071
+ if (target !== "") {
1072
+ const similar = Object.hasOwn(classDefs, target) ? classNameOf(target) : toClassName(target);
1073
+ node.similarTo = at0(similar);
1074
+ reference(name2, target, similar);
1075
+ }
1076
+ for (const other of Object.keys(def)) {
1077
+ if (CLASS_KEYS.has(other)) {
1078
+ if (other === "annotations") {
1079
+ for (const tag of annotations.keys()) {
1080
+ if (!CLASS_TAGS.has(tag)) report("annotation", name2, `\`${tag}\``);
1081
+ }
1082
+ }
1083
+ if (other === "close_mappings") {
1084
+ if (target === "") report("close_mappings", name2, "no mapping this can read");
1085
+ else if (mappings.length > 1) {
1086
+ report("close_mappings", name2, `${mappings.length - 1} beyond the first`);
1087
+ }
1088
+ }
1089
+ const block = get(def, other);
1090
+ if (other === "attributes" && present(block) && asRecord(block) === void 0) {
1091
+ report("attributes", name2, `${shown(block)} is not a map`);
1092
+ }
1093
+ if (other === "slots" && present(block) && !Array.isArray(block)) {
1094
+ report("slots", name2, `${shown(block)} is not a list`);
1095
+ }
1096
+ continue;
1097
+ }
1098
+ if (other === "slot_usage") {
1099
+ const usage = asRecord(get(def, other));
1100
+ if (usage === void 0) {
1101
+ report("slot_usage", name2);
1102
+ continue;
1103
+ }
1104
+ for (const slot of Object.keys(usage)) {
1105
+ if (fieldNames.get(key2)?.has(slot) === true) {
1106
+ report("slot_usage", `${name2}.${fieldNameOf(key2, slot)}`);
1107
+ } else {
1108
+ report("slot_usage", name2, `\`${slot}\` is inherited and not in the sketch`);
1109
+ }
1110
+ }
1111
+ continue;
1112
+ }
1113
+ report(other, name2);
1114
+ }
1115
+ for (const slot of slotKeysOf(def)) {
1116
+ const attributes = asRecord(get(def, "attributes")) ?? {};
1117
+ const rawSlot = Object.hasOwn(attributes, slot) ? get(attributes, slot) : get(slotDefs, slot);
1118
+ const slotDef = asRecord(rawSlot);
1119
+ if (slotDef === void 0 && present(rawSlot)) {
1120
+ report("slot", `${name2}.${fieldNameOf(key2, slot)}`, "is not a slot definition");
1121
+ continue;
1122
+ }
1123
+ node.fields.push(buildField(key2, name2, slot, slotDef ?? {}));
1124
+ }
1125
+ classes.push(node);
1126
+ }
1127
+ for (const key2 of Object.keys(enumDefs)) {
1128
+ const def = asRecord(get(enumDefs, key2)) ?? {};
1129
+ const uses = enumUses.filter((use) => use.enumName === key2);
1130
+ const why = unwritable.get(key2);
1131
+ if (why !== void 0) report("enum", key2, `${why}; those attributes keep no type`);
1132
+ else if (uses.length === 0) report("unused_enum", key2, "no attribute has it as its range");
1133
+ else if (uses.length > 1 && sharingLost(uses, enumUses)) {
1134
+ report("inlined", key2, `inlined at ${uses.length} attributes`);
1135
+ }
1136
+ const detail = [];
1137
+ const values = asRecord(get(def, "permissible_values")) ?? {};
1138
+ const detailed = Object.keys(values).filter((value) => {
1139
+ const valueBody = asRecord(get(values, value));
1140
+ return valueBody !== void 0 && Object.keys(valueBody).length > 0;
1141
+ });
1142
+ if (detailed.length > 0) {
1143
+ const bodies = detailed.length === 1 ? "carries a body of its own" : "carry bodies of their own";
1144
+ detail.push(`${list(detailed)} ${bodies}`);
1145
+ }
1146
+ const others = Object.keys(def).filter((other) => other !== "permissible_values");
1147
+ if (others.length > 0) detail.push(`the enum carries ${list(others)}`);
1148
+ if (detail.length > 0) report("enum_detail", key2, detail.join("; "));
1149
+ }
1150
+ for (const slot of Object.keys(slotDefs)) {
1151
+ if (!listedSomewhere(slot)) report("slot", slot, "no class lists it");
1152
+ }
1153
+ for (const key2 of Object.keys(root)) {
1154
+ if (!QUIET_SCHEMA_KEYS.has(key2)) report("schema", schemaName, `\`${key2}\``);
1155
+ }
1156
+ const source = toSkiss({ classes, diagnostics: [] });
1157
+ return { document: parse(source), source, dropped };
1158
+ function buildField(classKey, className, slot, def) {
1159
+ const name2 = fieldNameOf(classKey, slot);
1160
+ const element = `${className}.${name2}`;
1161
+ if (name2 !== slot) report("renamed", element, `from \`${slot}\``);
1162
+ const identifier = get(def, "identifier");
1163
+ if (present(identifier) && typeof identifier !== "boolean") {
1164
+ report("identifier", element, `${shown(identifier)} is not a boolean`);
1165
+ }
1166
+ const field = {
1167
+ name: at0(name2),
1168
+ identifier: identifier === true,
1169
+ line: 0
1170
+ };
1171
+ const description = oneLine(stringOf(get(def, "description")));
1172
+ if (description !== void 0) field.description = description;
1173
+ const type = typeFor(element, name2, def);
1174
+ if (type !== void 0) field.type = type;
1175
+ const annotations = annotationsOf(def);
1176
+ const system = annotations.get("system");
1177
+ if (typeof system === "string" && SYSTEM_OR_VALUE.test(system)) field.system = at0(system);
1178
+ else if (system !== void 0) report("annotation", element, "`system` is not a system name");
1179
+ const joins = annotations.get("joins_to");
1180
+ if (typeof joins === "string") {
1181
+ const dot = joins.indexOf(".");
1182
+ const target = joins.slice(0, dot);
1183
+ const targetField = joins.slice(dot + 1);
1184
+ if (dot > 0 && targetField !== "") {
1185
+ const targetClass = classNameOf(target);
1186
+ const targetName = fieldNameOf(target, targetField);
1187
+ field.joinsTo = { className: at0(targetClass), fieldName: at0(targetName) };
1188
+ reference(element, target, targetClass);
1189
+ reference(element, targetField, targetName);
1190
+ } else report("annotation", element, "`joins_to` is not `Class.field`");
1191
+ } else if (joins !== void 0) report("annotation", element, "`joins_to` is not text");
1192
+ const note = oneLine(stringOf(annotations.get("note")));
1193
+ if (note !== void 0) field.note = note;
1194
+ for (const other of Object.keys(def)) {
1195
+ if (!SLOT_KEYS.has(other)) {
1196
+ report(other, element);
1197
+ continue;
1198
+ }
1199
+ if (other !== "annotations") continue;
1200
+ for (const tag of annotations.keys()) {
1201
+ if (!SLOT_TAGS.has(tag)) report("annotation", element, `\`${tag}\``);
1202
+ }
1203
+ }
1204
+ return field;
1205
+ }
1206
+ function typeFor(element, field, def) {
1207
+ const rawMany = get(def, "multivalued");
1208
+ if (present(rawMany) && typeof rawMany !== "boolean") {
1209
+ report("multivalued", element, `${shown(rawMany)} is not a boolean`);
1210
+ }
1211
+ const many = rawMany === true;
1212
+ const rawRange = get(def, "range");
1213
+ if (present(rawRange) && typeof rawRange !== "string") {
1214
+ report("range", element, `${shown(rawRange)} is not a string`);
1215
+ } else if (rawRange === "") report("range", element, "the range is empty");
1216
+ const range = stringOf(rawRange) ?? defaultRange;
1217
+ if (Object.hasOwn(classDefs, range)) {
1218
+ const target = classNameOf(range);
1219
+ reference(element, range, target);
1220
+ return { kind: "class", name: at0(target), many };
1221
+ }
1222
+ if (Object.hasOwn(enumDefs, range)) {
1223
+ const values = enumValues.get(range);
1224
+ if (values === void 0) return many ? stringType(true) : void 0;
1225
+ enumUses.push({ enumName: range, field, values: values.join("|") });
1226
+ return { kind: "enum", values: values.map(at0), many };
1227
+ }
1228
+ if (Object.hasOwn(PRIMITIVE_RANGE, range)) {
1229
+ const primitive = PRIMITIVE_RANGE[range] ?? "string";
1230
+ if (primitive === "string") return many ? stringType(true) : void 0;
1231
+ return { kind: "primitive", name: primitive, written: at0(primitive), many };
1232
+ }
1233
+ if (/^[A-Z]/.test(range)) {
1234
+ const target = toClassName(range);
1235
+ reference(element, range, target);
1236
+ return { kind: "class", name: at0(target), many };
1237
+ }
1238
+ const word = range.toLowerCase();
1239
+ if (!UNKNOWN_TYPE.test(word)) {
1240
+ report("narrowed", element, `\`${range}\` is not a type Skiss can write; it reads as string`);
1241
+ return many ? stringType(true) : void 0;
1242
+ }
1243
+ report("narrowed", element, `\`${range}\` is not a Skiss type; it falls back to string`);
1244
+ return { kind: "unknown", name: at0(word), many };
1245
+ }
1246
+ function listedSomewhere(slot) {
1247
+ for (const key2 of Object.keys(classDefs)) {
1248
+ const def = asRecord(get(classDefs, key2)) ?? {};
1249
+ if (stringList(get(def, "slots")).includes(slot)) return true;
1250
+ }
1251
+ return false;
1252
+ }
1253
+ }
1254
+ function sharingLost(uses, all) {
1255
+ const first = uses[0];
1256
+ if (first === void 0) return false;
1257
+ if (uses.some((use) => use.field !== first.field)) return true;
1258
+ return all.some((use) => use.field === first.field && use.values !== first.values);
1259
+ }
1260
+ var COUNTED = {
1261
+ renamed: ["name", "names"],
1262
+ inlined: ["enum", "enums"],
1263
+ enum: ["unwritable enum", "unwritable enums"],
1264
+ unused_enum: ["unused enum", "unused enums"],
1265
+ class: ["class", "classes"],
1266
+ slot: ["slot", "slots"],
1267
+ schema: ["schema key", "schema keys"],
1268
+ // `mixin: true` marks the class; `mixins: [M]` points at another one.
1269
+ mixin: ["mixin class", "mixin classes"],
1270
+ mixins: ["mixin", "mixins"],
1271
+ pattern: ["pattern", "patterns"],
1272
+ unit: ["unit", "units"]
1273
+ };
1274
+ var ENUM_KINDS = /* @__PURE__ */ new Set(["enum_detail"]);
1275
+ function formatDropped(dropped) {
1276
+ const groups = /* @__PURE__ */ new Map();
1277
+ for (const entry of dropped) {
1278
+ const group = groups.get(entry.kind);
1279
+ if (group === void 0) groups.set(entry.kind, [entry]);
1280
+ else group.push(entry);
1281
+ }
1282
+ const drops = [];
1283
+ let renamed = "";
1284
+ let inlined = "";
1285
+ for (const [kind, entries] of groups) {
1286
+ const phrase = phraseFor(kind, entries);
1287
+ if (kind === "renamed") renamed = phrase;
1288
+ else if (kind === "inlined") inlined = phrase;
1289
+ else drops.push(phrase);
1290
+ }
1291
+ const sentences = [];
1292
+ if (drops.length > 0) sentences.push(`Dropped: ${drops.join(", ")}.`);
1293
+ if (renamed !== "") sentences.push(`Renamed: ${renamed}.`);
1294
+ if (inlined !== "") sentences.push(`Inlined: ${inlined}.`);
1295
+ return sentences.join(" ");
1296
+ }
1297
+ function phraseFor(kind, entries) {
1298
+ const n = entries.length;
1299
+ const counted = Object.hasOwn(COUNTED, kind) ? COUNTED[kind] : void 0;
1300
+ if (counted !== void 0) return `${n} ${n === 1 ? counted[0] : counted[1]}`;
1301
+ return `${kind} on ${n} ${nounFor(kind, entries)}`;
1302
+ }
1303
+ function nounFor(kind, entries) {
1304
+ const one = entries.length === 1;
1305
+ if (ENUM_KINDS.has(kind)) return one ? "enum" : "enums";
1306
+ const slots = entries.filter((entry) => entry.element.includes(".")).length;
1307
+ if (slots === entries.length) return one ? "slot" : "slots";
1308
+ if (slots === 0) return one ? "class" : "classes";
1309
+ return one ? "element" : "elements";
1310
+ }
1311
+ var capitalise2 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1312
+ var decapitalise = (text) => text.charAt(0).toLowerCase() + text.slice(1);
1313
+ var parts = (text) => text.split(/[^A-Za-z0-9]+/).filter((part) => part !== "");
1314
+ function toClassName(text) {
1315
+ if (CLASS_NAME.test(text)) return text;
1316
+ const name2 = parts(text).map(capitalise2).join("");
1317
+ if (name2 === "") return "Class";
1318
+ return CLASS_NAME.test(name2) ? name2 : `X${name2}`;
1319
+ }
1320
+ function toFieldName(text) {
1321
+ if (FIELD_NAME.test(text)) return text;
1322
+ const name2 = parts(text).map((part, i) => i === 0 ? decapitalise(part) : capitalise2(part)).join("");
1323
+ if (name2 === "") return "field";
1324
+ return FIELD_NAME.test(name2) ? name2 : `x${capitalise2(name2)}`;
1325
+ }
1326
+ function unique(base, taken) {
1327
+ let name2 = base;
1328
+ for (let n = 2; taken.has(name2); n++) name2 = `${base}${n}`;
1329
+ taken.add(name2);
1330
+ return name2;
1331
+ }
1332
+ var get = (record, key2) => Object.hasOwn(record, key2) ? record[key2] : void 0;
1333
+ function asRecord(value) {
1334
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1335
+ return value;
1336
+ }
1337
+ var present = (value) => value !== void 0 && value !== null;
1338
+ function shown(value) {
1339
+ if (Array.isArray(value)) return "a list";
1340
+ if (typeof value === "object" && value !== null) return "a map";
1341
+ return `\`${String(value)}\``;
1342
+ }
1343
+ function localName(mapping) {
1344
+ let cut = -1;
1345
+ for (const mark of [":", "/", "#"]) cut = Math.max(cut, mapping.lastIndexOf(mark));
1346
+ return mapping.slice(cut + 1);
1347
+ }
1348
+ var stringOf = (value) => typeof value === "string" && value !== "" ? value : void 0;
1349
+ var stringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1350
+ function annotationsOf(def) {
1351
+ const out = /* @__PURE__ */ new Map();
1352
+ const annotations = asRecord(get(def, "annotations"));
1353
+ if (annotations === void 0) return out;
1354
+ for (const tag of Object.keys(annotations)) {
1355
+ const raw = get(annotations, tag);
1356
+ const value = asRecord(raw) === void 0 ? raw : get(asRecord(raw) ?? {}, "value");
1357
+ if (typeof value === "string" || typeof value === "boolean") out.set(tag, value);
1358
+ else out.set(tag, String(value));
1359
+ }
1360
+ return out;
1361
+ }
1362
+ function slotKeysOf(def) {
1363
+ const keys = Object.keys(asRecord(get(def, "attributes")) ?? {});
1364
+ for (const slot of stringList(get(def, "slots"))) {
1365
+ if (!keys.includes(slot)) keys.push(slot);
1366
+ }
1367
+ return keys;
1368
+ }
1369
+ function oneLine(text) {
1370
+ if (text === void 0) return void 0;
1371
+ const line = text.replace(/\s*[\r\n]+\s*/g, " ").trim();
1372
+ return line === "" ? void 0 : line;
1373
+ }
1374
+ var list = (items) => items.map((item) => `\`${item}\``).join(", ");
1375
+ var at0 = (text) => ({ text, line: 0, col: 0, end: 0 });
1376
+ var stringType = (many) => ({
1377
+ kind: "primitive",
1378
+ name: "string",
1379
+ written: at0("string"),
1380
+ many
1381
+ });
1382
+ var schemaElement = (root) => stringOf(get(root, "name")) ?? "schema";
1383
+ var nothing = (element, detail) => ({
1384
+ document: parse(""),
1385
+ source: "",
1386
+ dropped: [{ kind: "schema", element, detail }]
1387
+ });
1388
+
1389
+ // src/import.ts
1390
+ import { parse as parseYAML } from "yaml";
1391
+ function importLinkML(text) {
1392
+ let schema;
1393
+ try {
1394
+ schema = parseYAML(text);
1395
+ } catch (error) {
1396
+ return { output: "", dropped: [], diagnostics: [notYAML(error)] };
1397
+ }
1398
+ const { document, source, dropped } = fromLinkML(schema);
1399
+ return { output: source, dropped, diagnostics: resolve(document).diagnostics };
1400
+ }
1401
+ function notYAML(error) {
1402
+ const first = error instanceof Error ? error.message.split("\n")[0] ?? "" : String(error);
1403
+ const detail = first.replace(/:$/, "");
1404
+ return {
1405
+ severity: "error",
1406
+ code: "E_NOT_YAML",
1407
+ message: `the input is not YAML: ${detail}`,
1408
+ line: 1
1409
+ };
1410
+ }
1411
+
911
1412
  // src/index.ts
912
- var VERSION = "0.2.1";
1413
+ var VERSION = "0.3.0";
913
1414
  export {
914
1415
  VERSION,
915
1416
  compile,
916
1417
  formatDiagnostic,
1418
+ formatDropped,
1419
+ fromLinkML,
1420
+ importLinkML,
917
1421
  parse,
918
1422
  resolve,
919
1423
  serialize,
920
1424
  toLinkML,
921
- toMermaid
1425
+ toMermaid,
1426
+ toSkiss
922
1427
  };
923
1428
  //# sourceMappingURL=index.js.map