@eriknaslund/skiss 0.2.1 → 0.4.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.js CHANGED
@@ -3,7 +3,8 @@ var isWs = (c) => c === " " || c === " ";
3
3
  var trimWs = (s) => s.replace(/^[ \t]+/, "").replace(/[ \t]+$/, "");
4
4
  var CLASS_NAME = /^[A-Z][A-Za-z0-9]*$/;
5
5
  var FIELD_NAME = /^[a-z][A-Za-z0-9]*$/;
6
- var SYSTEM_OR_VALUE = /^[A-Za-z][A-Za-z0-9_-]*$/;
6
+ var SYSTEM_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/;
7
+ var ENUM_VALUE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
7
8
  var PRIMITIVES = {
8
9
  string: "string",
9
10
  int: "int",
@@ -308,7 +309,7 @@ function parseSystem(c, at) {
308
309
  system ?? afterSpan(at)
309
310
  );
310
311
  }
311
- if (!SYSTEM_OR_VALUE.test(system.text)) {
312
+ if (!SYSTEM_NAME.test(system.text)) {
312
313
  return fail(
313
314
  "E_UNPARSABLE",
314
315
  `A system name is letters, digits, \`-\` and \`_\`, starting with a letter: \`${system.text}\``,
@@ -366,10 +367,10 @@ function parseType(c, colon) {
366
367
  }
367
368
  if (words.length > 1) {
368
369
  for (const w of words) {
369
- if (!SYSTEM_OR_VALUE.test(w.text)) {
370
+ if (!ENUM_VALUE.test(w.text)) {
370
371
  return fail(
371
372
  "E_UNPARSABLE",
372
- `An enum value is letters, digits, \`-\` and \`_\`, starting with a letter: \`${w.text}\``,
373
+ `An enum value is letters, digits, \`-\` and \`_\`, and does not start with \`-\` or \`_\`: \`${w.text}\``,
373
374
  c.line,
374
375
  w
375
376
  );
@@ -506,6 +507,18 @@ function resolve(doc) {
506
507
  );
507
508
  } else if (field.type?.kind === "class") {
508
509
  reference(field.type.name);
510
+ } else if (field.type?.kind === "enum") {
511
+ const seen = /* @__PURE__ */ new Set();
512
+ for (const value of field.type.values) {
513
+ if (seen.has(value.text)) {
514
+ warn(
515
+ "W_DUPLICATE_ENUM_VALUE",
516
+ `value \`${value.text}\` is already in this enum; it is carried once`,
517
+ value
518
+ );
519
+ }
520
+ seen.add(value.text);
521
+ }
509
522
  }
510
523
  if (field.joinsTo !== void 0) {
511
524
  const { className, fieldName } = field.joinsTo;
@@ -520,9 +533,9 @@ function resolve(doc) {
520
533
  }
521
534
  }
522
535
  }
523
- const present = new Set(out.diagnostics.map(key));
536
+ const present2 = new Set(out.diagnostics.map(key));
524
537
  for (const w of warnings) {
525
- if (!present.has(key(w))) out.diagnostics.push(w);
538
+ if (!present2.has(key(w))) out.diagnostics.push(w);
526
539
  }
527
540
  out.diagnostics.sort((a, b) => a.line - b.line);
528
541
  out.undeclared = [...undeclared.values()].map((n) => ({ ...n }));
@@ -619,7 +632,8 @@ function toLinkML(doc, opts) {
619
632
  const classNames = new Set(classes.map((cls) => cls.node.name.text));
620
633
  for (const name2 of resolved.undeclared) classNames.add(name2.text);
621
634
  const enums = nameEnums(classes, classNames);
622
- const prefixes = { linkml: "https://w3id.org/linkml/" };
635
+ const prefixes = nameKeyed();
636
+ prefixes.linkml = "https://w3id.org/linkml/";
623
637
  if (!Object.hasOwn(prefixes, schemaName)) prefixes[schemaName] = `${BASE_URI}/${schemaName}/`;
624
638
  const systemPrefix = /* @__PURE__ */ new Map();
625
639
  const addSystem = (system) => {
@@ -650,12 +664,12 @@ function toLinkML(doc, opts) {
650
664
  prefixes,
651
665
  imports: ["linkml:types"]
652
666
  };
653
- const out = {};
667
+ const out = nameKeyed();
654
668
  for (const cls of classes) {
655
669
  out[cls.node.name.text] = buildClass(cls, mappingPrefix, enums);
656
670
  }
657
671
  for (const name2 of resolved.undeclared) {
658
- if (!(name2.text in out)) out[name2.text] = { annotations: { undeclared: true } };
672
+ if (!Object.hasOwn(out, name2.text)) out[name2.text] = { annotations: { undeclared: true } };
659
673
  }
660
674
  if (Object.keys(out).length > 0) schema.classes = out;
661
675
  const enumBlock = buildEnums(classes, enums);
@@ -713,13 +727,13 @@ function nameEnums(classes, classNames) {
713
727
  return names;
714
728
  }
715
729
  function buildEnums(classes, enums) {
716
- const out = {};
730
+ const out = nameKeyed();
717
731
  for (const { node, fields } of classes) {
718
732
  for (const field of fields) {
719
733
  if (field.type?.kind !== "enum") continue;
720
734
  const name2 = enums.get(useKey(node.name.text, field.name.text));
721
- if (name2 === void 0 || name2 in out) continue;
722
- const values = {};
735
+ if (name2 === void 0 || Object.hasOwn(out, name2)) continue;
736
+ const values = nameKeyed();
723
737
  for (const value of field.type.values) values[value.text] = null;
724
738
  out[name2] = { permissible_values: values };
725
739
  }
@@ -738,7 +752,7 @@ function buildClass(cls, mappingPrefix, enums) {
738
752
  const target = node.similarTo.text;
739
753
  out.close_mappings = [`${mappingPrefix(target)}:${target}`];
740
754
  }
741
- const attributes = {};
755
+ const attributes = nameKeyed();
742
756
  for (const field of fields) {
743
757
  attributes[field.name.text] = buildAttribute(field, node.name.text, enums);
744
758
  }
@@ -776,10 +790,12 @@ function rangeOf(field, className, enums) {
776
790
  }
777
791
  }
778
792
  var RESERVED_PREFIXES = /* @__PURE__ */ new Set(["linkml", "xsd", "shex", "schema"]);
793
+ var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
794
+ var nameKeyed = () => /* @__PURE__ */ Object.create(null);
779
795
  var linkmlName = (text) => {
780
796
  const name2 = text.toLowerCase().replace(/[^a-z0-9]/g, "_");
781
797
  if (!/[a-z0-9]/.test(name2)) return "sketch";
782
- if (RESERVED_PREFIXES.has(name2)) return `${name2}_`;
798
+ if (RESERVED_PREFIXES.has(name2) || PROTOTYPE_KEYS.has(name2)) return `${name2}_`;
783
799
  return /^[0-9]/.test(name2) ? `_${name2}` : name2;
784
800
  };
785
801
  var capitalise = (text) => text.charAt(0).toUpperCase() + text.slice(1);
@@ -908,16 +924,562 @@ function formatDiagnostic(d, file) {
908
924
  return `${where}: ${d.severity} ${d.code} ${d.message}`;
909
925
  }
910
926
 
927
+ // src/generators/skiss.ts
928
+ var TRAILER_COLUMN = 40;
929
+ function toSkiss(doc) {
930
+ if (doc.classes.length === 0) return "";
931
+ return `${doc.classes.map(classBlock).join("\n\n")}
932
+ `;
933
+ }
934
+ function classBlock(cls) {
935
+ const head = cls.name.text + (cls.system === void 0 ? "" : ` @${cls.system.text}`) + (cls.similarTo === void 0 ? "" : ` ~ ${cls.similarTo.text}`);
936
+ return [withTrailer(head, cls), ...cls.fields.map(fieldLine)].join("\n");
937
+ }
938
+ function fieldLine(field) {
939
+ const type = typeText2(field.type);
940
+ 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}`);
941
+ return withTrailer(head, field);
942
+ }
943
+ function typeText2(type) {
944
+ if (type === void 0) return void 0;
945
+ let text;
946
+ switch (type.kind) {
947
+ case "primitive":
948
+ if (type.name === "string" && !type.many) return void 0;
949
+ text = type.name;
950
+ break;
951
+ case "class":
952
+ case "unknown":
953
+ text = type.name.text;
954
+ break;
955
+ case "enum":
956
+ text = type.values.map((v) => v.text).join("|");
957
+ break;
958
+ }
959
+ return type.many ? `${text}[]` : text;
960
+ }
961
+ function writeDescription(text) {
962
+ const attached = text.replace(/[ \t]+\?(?=[ \t]|$)/g, "?");
963
+ return /^\?([ \t]|$)/.test(attached) ? `#${attached}` : `# ${attached}`;
964
+ }
965
+ var rewordsDescription = (text) => writeDescription(text) !== `# ${text}`;
966
+ function withTrailer(head, node) {
967
+ const parts2 = [];
968
+ if (node.description !== void 0 && node.description !== "")
969
+ parts2.push(writeDescription(node.description));
970
+ if (node.note !== void 0 && node.note !== "") parts2.push(`? ${node.note}`);
971
+ if (parts2.length === 0) return head;
972
+ const gap = head.length < TRAILER_COLUMN - 1 ? " ".repeat(TRAILER_COLUMN - head.length) : " ";
973
+ return head + gap + parts2.join(" ");
974
+ }
975
+
976
+ // src/generators/from-linkml.ts
977
+ var PRIMITIVE_RANGE = {
978
+ string: "string",
979
+ integer: "int",
980
+ float: "float",
981
+ double: "float",
982
+ decimal: "float",
983
+ boolean: "bool",
984
+ date: "date",
985
+ datetime: "datetime",
986
+ uri: "uri",
987
+ uriorcurie: "uri"
988
+ };
989
+ var CLASS_KEYS = /* @__PURE__ */ new Set(["description", "annotations", "close_mappings", "attributes", "slots"]);
990
+ var SLOT_KEYS = /* @__PURE__ */ new Set(["description", "annotations", "range", "multivalued", "identifier"]);
991
+ var CLASS_TAGS = /* @__PURE__ */ new Set(["system", "note", "undeclared"]);
992
+ var SLOT_TAGS = /* @__PURE__ */ new Set(["system", "note", "joins_to"]);
993
+ var QUIET_SCHEMA_KEYS = /* @__PURE__ */ new Set([
994
+ "id",
995
+ "name",
996
+ "prefixes",
997
+ "imports",
998
+ "default_prefix",
999
+ "default_range",
1000
+ "title",
1001
+ "license",
1002
+ "version",
1003
+ "classes",
1004
+ "slots",
1005
+ "enums"
1006
+ ]);
1007
+ var REWORDED = "a standalone `?` is attached to the word before it";
1008
+ var UNKNOWN_TYPE = /^[a-z][a-z0-9_-]*$/;
1009
+ function fromLinkML(schema) {
1010
+ const root = asRecord(schema);
1011
+ if (root === void 0) return nothing("schema", "the schema is not an object");
1012
+ const schemaName = schemaElement(root);
1013
+ const classBlock2 = asRecord(get(root, "classes"));
1014
+ if (classBlock2 === void 0) return nothing(schemaName, "the schema has no `classes`");
1015
+ const classDefs = classBlock2;
1016
+ const dropped = [];
1017
+ const report = (kind, element, detail) => {
1018
+ dropped.push(detail === void 0 ? { kind, element } : { kind, element, detail });
1019
+ };
1020
+ const slotDefs = asRecord(get(root, "slots")) ?? {};
1021
+ const enumDefs = asRecord(get(root, "enums")) ?? {};
1022
+ const defaultRange = stringOf(get(root, "default_range")) ?? "string";
1023
+ const classNames = /* @__PURE__ */ new Map();
1024
+ const takenClasses = /* @__PURE__ */ new Set();
1025
+ for (const key2 of Object.keys(classDefs)) {
1026
+ classNames.set(key2, unique(toClassName(key2), takenClasses));
1027
+ }
1028
+ const fieldNames = /* @__PURE__ */ new Map();
1029
+ for (const key2 of Object.keys(classDefs)) {
1030
+ const names = /* @__PURE__ */ new Map();
1031
+ const taken = /* @__PURE__ */ new Set();
1032
+ for (const slot of slotKeysOf(asRecord(get(classDefs, key2)) ?? {})) {
1033
+ names.set(slot, unique(toFieldName(slot), taken));
1034
+ }
1035
+ fieldNames.set(key2, names);
1036
+ }
1037
+ const referenced = /* @__PURE__ */ new Set();
1038
+ const reference = (element, from, to) => {
1039
+ if (from === to || referenced.has(`${element}\0${to}`)) return;
1040
+ referenced.add(`${element}\0${to}`);
1041
+ report("renamed", element, `\`${to}\` from \`${from}\``);
1042
+ };
1043
+ const classNameOf = (key2) => classNames.get(key2) ?? toClassName(key2);
1044
+ const fieldNameOf = (classKey, slot) => fieldNames.get(classKey)?.get(slot) ?? toFieldName(slot);
1045
+ const enumValues = /* @__PURE__ */ new Map();
1046
+ const unwritable = /* @__PURE__ */ new Map();
1047
+ for (const key2 of Object.keys(enumDefs)) {
1048
+ const rawValues = get(asRecord(get(enumDefs, key2)) ?? {}, "permissible_values");
1049
+ const values = asRecord(rawValues);
1050
+ const keys = Object.keys(values ?? {});
1051
+ const bad = keys.find((value) => !ENUM_VALUE.test(value));
1052
+ if (values === void 0 && present(rawValues)) {
1053
+ unwritable.set(key2, `${shown(rawValues)} is not a map of permissible values`);
1054
+ } else if (keys.length < 2) {
1055
+ const count = `${keys.length} permissible value${keys.length === 1 ? "" : "s"}`;
1056
+ unwritable.set(key2, `${count} cannot be an inline enum (\xA73.4)`);
1057
+ } else if (bad !== void 0) {
1058
+ unwritable.set(key2, `\`${bad}\` is not a value Skiss can write (\xA74)`);
1059
+ } else {
1060
+ enumValues.set(key2, keys);
1061
+ }
1062
+ }
1063
+ const enumUses = [];
1064
+ const classes = [];
1065
+ for (const key2 of Object.keys(classDefs)) {
1066
+ const raw = get(classDefs, key2);
1067
+ const body = asRecord(raw);
1068
+ const name2 = classNameOf(key2);
1069
+ if (body === void 0 && raw !== null && raw !== void 0) {
1070
+ report("class", name2, "is not a class definition");
1071
+ continue;
1072
+ }
1073
+ const def = body ?? {};
1074
+ if (name2 !== key2) report("renamed", name2, `from \`${key2}\``);
1075
+ const annotations = annotationsOf(def);
1076
+ if (annotations.get("undeclared") === true) {
1077
+ for (const other of Object.keys(def)) {
1078
+ if (other !== "annotations") report(other, name2);
1079
+ }
1080
+ continue;
1081
+ }
1082
+ const undeclared = annotations.get("undeclared");
1083
+ if (undeclared !== void 0 && typeof undeclared !== "boolean") {
1084
+ report("annotation", name2, notText("undeclared", undeclared));
1085
+ }
1086
+ const node = { name: at0(name2), fields: [], line: 0 };
1087
+ const description = oneLine(stringOf(get(def, "description")));
1088
+ if (description !== void 0) {
1089
+ node.description = description;
1090
+ if (rewordsDescription(description)) report("reworded", name2, REWORDED);
1091
+ }
1092
+ const system = annotations.get("system");
1093
+ if (typeof system === "string") {
1094
+ if (SYSTEM_NAME.test(system)) node.system = at0(system);
1095
+ else report("annotation", name2, "`system` is not a system name");
1096
+ } else if (system !== void 0) report("annotation", name2, notText("system", system));
1097
+ const rawNote = annotations.get("note");
1098
+ if (typeof rawNote === "string") {
1099
+ const note = oneLine(rawNote);
1100
+ if (note !== void 0) node.note = note;
1101
+ } else if (rawNote !== void 0) report("annotation", name2, notText("note", rawNote));
1102
+ const mappings = stringList(get(def, "close_mappings"));
1103
+ const first = mappings[0];
1104
+ const target = first === void 0 ? "" : localName(first);
1105
+ if (target !== "") {
1106
+ const similar = Object.hasOwn(classDefs, target) ? classNameOf(target) : toClassName(target);
1107
+ node.similarTo = at0(similar);
1108
+ reference(name2, target, similar);
1109
+ }
1110
+ for (const other of Object.keys(def)) {
1111
+ if (CLASS_KEYS.has(other)) {
1112
+ if (other === "annotations") {
1113
+ for (const tag of annotations.keys()) {
1114
+ if (!CLASS_TAGS.has(tag)) report("annotation", name2, `\`${tag}\``);
1115
+ }
1116
+ }
1117
+ if (other === "close_mappings") {
1118
+ if (target === "") report("close_mappings", name2, "no mapping this can read");
1119
+ else if (mappings.length > 1) {
1120
+ report("close_mappings", name2, `${mappings.length - 1} beyond the first`);
1121
+ }
1122
+ }
1123
+ const block = get(def, other);
1124
+ if (other === "attributes" && present(block) && asRecord(block) === void 0) {
1125
+ report("attributes", name2, `${shown(block)} is not a map`);
1126
+ }
1127
+ if (other === "slots" && present(block) && !Array.isArray(block)) {
1128
+ report("slots", name2, `${shown(block)} is not a list`);
1129
+ }
1130
+ continue;
1131
+ }
1132
+ if (other === "slot_usage") {
1133
+ const usage = asRecord(get(def, other));
1134
+ if (usage === void 0) {
1135
+ report("slot_usage", name2);
1136
+ continue;
1137
+ }
1138
+ for (const slot of Object.keys(usage)) {
1139
+ if (fieldNames.get(key2)?.has(slot) === true) {
1140
+ report("slot_usage", `${name2}.${fieldNameOf(key2, slot)}`);
1141
+ } else {
1142
+ report("slot_usage", name2, `\`${slot}\` is inherited and not in the sketch`);
1143
+ }
1144
+ }
1145
+ continue;
1146
+ }
1147
+ report(other, name2);
1148
+ }
1149
+ for (const slot of slotKeysOf(def)) {
1150
+ const attributes = asRecord(get(def, "attributes")) ?? {};
1151
+ const rawSlot = Object.hasOwn(attributes, slot) ? get(attributes, slot) : get(slotDefs, slot);
1152
+ const slotDef = asRecord(rawSlot);
1153
+ if (slotDef === void 0 && present(rawSlot)) {
1154
+ report("slot", `${name2}.${fieldNameOf(key2, slot)}`, "is not a slot definition");
1155
+ continue;
1156
+ }
1157
+ node.fields.push(buildField(key2, name2, slot, slotDef ?? {}));
1158
+ }
1159
+ classes.push(node);
1160
+ }
1161
+ for (const key2 of Object.keys(enumDefs)) {
1162
+ const def = asRecord(get(enumDefs, key2)) ?? {};
1163
+ const uses = enumUses.filter((use) => use.enumName === key2);
1164
+ const why = unwritable.get(key2);
1165
+ if (why !== void 0) report("enum", key2, `${why}; those attributes keep no type`);
1166
+ else if (uses.length === 0) report("unused_enum", key2, "no attribute has it as its range");
1167
+ else if (uses.length > 1 && sharingLost(uses, enumUses)) {
1168
+ report("inlined", key2, `inlined at ${uses.length} attributes`);
1169
+ }
1170
+ const detail = [];
1171
+ const values = asRecord(get(def, "permissible_values")) ?? {};
1172
+ const detailed = Object.keys(values).filter((value) => {
1173
+ const valueBody = asRecord(get(values, value));
1174
+ return valueBody !== void 0 && Object.keys(valueBody).length > 0;
1175
+ });
1176
+ if (detailed.length > 0) {
1177
+ const bodies = detailed.length === 1 ? "carries a body of its own" : "carry bodies of their own";
1178
+ detail.push(`${list(detailed)} ${bodies}`);
1179
+ }
1180
+ const others = Object.keys(def).filter((other) => other !== "permissible_values");
1181
+ if (others.length > 0) detail.push(`the enum carries ${list(others)}`);
1182
+ if (detail.length > 0) report("enum_detail", key2, detail.join("; "));
1183
+ }
1184
+ for (const slot of Object.keys(slotDefs)) {
1185
+ if (!listedSomewhere(slot)) report("slot", slot, "no class lists it");
1186
+ }
1187
+ for (const key2 of Object.keys(root)) {
1188
+ if (!QUIET_SCHEMA_KEYS.has(key2)) report("schema", schemaName, `\`${key2}\``);
1189
+ }
1190
+ const source = toSkiss({ classes, diagnostics: [] });
1191
+ return { document: parse(source), source, dropped };
1192
+ function buildField(classKey, className, slot, def) {
1193
+ const name2 = fieldNameOf(classKey, slot);
1194
+ const element = `${className}.${name2}`;
1195
+ if (name2 !== slot) report("renamed", element, `from \`${slot}\``);
1196
+ const identifier = get(def, "identifier");
1197
+ if (present(identifier) && typeof identifier !== "boolean") {
1198
+ report("identifier", element, `${shown(identifier)} is not a boolean`);
1199
+ }
1200
+ const field = {
1201
+ name: at0(name2),
1202
+ identifier: identifier === true,
1203
+ line: 0
1204
+ };
1205
+ const description = oneLine(stringOf(get(def, "description")));
1206
+ if (description !== void 0) {
1207
+ field.description = description;
1208
+ if (rewordsDescription(description)) report("reworded", element, REWORDED);
1209
+ }
1210
+ const type = typeFor(element, name2, def);
1211
+ if (type !== void 0) field.type = type;
1212
+ const annotations = annotationsOf(def);
1213
+ const system = annotations.get("system");
1214
+ if (typeof system === "string") {
1215
+ if (SYSTEM_NAME.test(system)) field.system = at0(system);
1216
+ else report("annotation", element, "`system` is not a system name");
1217
+ } else if (system !== void 0) report("annotation", element, notText("system", system));
1218
+ const joins = annotations.get("joins_to");
1219
+ if (typeof joins === "string") {
1220
+ const dot = joins.indexOf(".");
1221
+ const target = joins.slice(0, dot);
1222
+ const targetField = joins.slice(dot + 1);
1223
+ if (dot > 0 && targetField !== "") {
1224
+ const targetClass = classNameOf(target);
1225
+ const targetName = fieldNameOf(target, targetField);
1226
+ field.joinsTo = { className: at0(targetClass), fieldName: at0(targetName) };
1227
+ reference(element, target, targetClass);
1228
+ reference(element, targetField, targetName);
1229
+ } else report("annotation", element, "`joins_to` is not `Class.field`");
1230
+ } else if (joins !== void 0) report("annotation", element, notText("joins_to", joins));
1231
+ const rawNote = annotations.get("note");
1232
+ if (typeof rawNote === "string") {
1233
+ const note = oneLine(rawNote);
1234
+ if (note !== void 0) field.note = note;
1235
+ } else if (rawNote !== void 0) report("annotation", element, notText("note", rawNote));
1236
+ for (const other of Object.keys(def)) {
1237
+ if (!SLOT_KEYS.has(other)) {
1238
+ report(other, element);
1239
+ continue;
1240
+ }
1241
+ if (other !== "annotations") continue;
1242
+ for (const tag of annotations.keys()) {
1243
+ if (!SLOT_TAGS.has(tag)) report("annotation", element, `\`${tag}\``);
1244
+ }
1245
+ }
1246
+ return field;
1247
+ }
1248
+ function typeFor(element, field, def) {
1249
+ const rawMany = get(def, "multivalued");
1250
+ if (present(rawMany) && typeof rawMany !== "boolean") {
1251
+ report("multivalued", element, `${shown(rawMany)} is not a boolean`);
1252
+ }
1253
+ const many = rawMany === true;
1254
+ const rawRange = get(def, "range");
1255
+ if (present(rawRange) && typeof rawRange !== "string") {
1256
+ report("range", element, `${shown(rawRange)} is not a string`);
1257
+ } else if (rawRange === "") report("range", element, "the range is empty");
1258
+ const range = stringOf(rawRange) ?? defaultRange;
1259
+ if (Object.hasOwn(classDefs, range)) {
1260
+ const target = classNameOf(range);
1261
+ reference(element, range, target);
1262
+ return { kind: "class", name: at0(target), many };
1263
+ }
1264
+ if (Object.hasOwn(enumDefs, range)) {
1265
+ const values = enumValues.get(range);
1266
+ if (values === void 0) return many ? stringType(true) : void 0;
1267
+ enumUses.push({ enumName: range, field, values: values.join("|") });
1268
+ return { kind: "enum", values: values.map(at0), many };
1269
+ }
1270
+ if (Object.hasOwn(PRIMITIVE_RANGE, range)) {
1271
+ const primitive = PRIMITIVE_RANGE[range] ?? "string";
1272
+ if (primitive === "string") return many ? stringType(true) : void 0;
1273
+ return { kind: "primitive", name: primitive, written: at0(primitive), many };
1274
+ }
1275
+ if (/^[A-Z]/.test(range)) {
1276
+ const target = toClassName(range);
1277
+ reference(element, range, target);
1278
+ return { kind: "class", name: at0(target), many };
1279
+ }
1280
+ const word = range.toLowerCase();
1281
+ if (!UNKNOWN_TYPE.test(word)) {
1282
+ report("narrowed", element, `\`${range}\` is not a type Skiss can write; it reads as string`);
1283
+ return many ? stringType(true) : void 0;
1284
+ }
1285
+ report("narrowed", element, `\`${range}\` is not a Skiss type; it falls back to string`);
1286
+ return { kind: "unknown", name: at0(word), many };
1287
+ }
1288
+ function listedSomewhere(slot) {
1289
+ for (const key2 of Object.keys(classDefs)) {
1290
+ const def = asRecord(get(classDefs, key2)) ?? {};
1291
+ if (stringList(get(def, "slots")).includes(slot)) return true;
1292
+ }
1293
+ return false;
1294
+ }
1295
+ }
1296
+ function sharingLost(uses, all) {
1297
+ const first = uses[0];
1298
+ if (first === void 0) return false;
1299
+ if (uses.some((use) => use.field !== first.field)) return true;
1300
+ return all.some((use) => use.field === first.field && use.values !== first.values);
1301
+ }
1302
+ var COUNTED = {
1303
+ renamed: ["name", "names"],
1304
+ inlined: ["enum", "enums"],
1305
+ enum: ["unwritable enum", "unwritable enums"],
1306
+ unused_enum: ["unused enum", "unused enums"],
1307
+ class: ["class", "classes"],
1308
+ slot: ["slot", "slots"],
1309
+ schema: ["schema key", "schema keys"],
1310
+ narrowed: ["narrowed range", "narrowed ranges"],
1311
+ enum_detail: ["enum detail", "enum details"],
1312
+ annotation: ["unreadable annotation", "unreadable annotations"],
1313
+ reworded: ["description reworded", "descriptions reworded"],
1314
+ // `mixin: true` marks the class; `mixins: [M]` points at another one.
1315
+ mixin: ["mixin class", "mixin classes"],
1316
+ mixins: ["mixin", "mixins"],
1317
+ pattern: ["pattern", "patterns"],
1318
+ unit: ["unit", "units"]
1319
+ };
1320
+ var UNREADABLE = "unreadable";
1321
+ function formatDropped(dropped) {
1322
+ const groups = /* @__PURE__ */ new Map();
1323
+ for (const entry of dropped) {
1324
+ const group = groups.get(entry.kind);
1325
+ if (group === void 0) groups.set(entry.kind, [entry]);
1326
+ else group.push(entry);
1327
+ }
1328
+ const drops = [];
1329
+ const notRead = [];
1330
+ let renamed = "";
1331
+ let inlined = "";
1332
+ for (const [kind, entries] of groups) {
1333
+ if (kind === UNREADABLE) {
1334
+ for (const entry of entries) notRead.push(entry.detail ?? "the schema could not be read");
1335
+ continue;
1336
+ }
1337
+ const phrase = phraseFor(kind, entries);
1338
+ if (kind === "renamed") renamed = phrase;
1339
+ else if (kind === "inlined") inlined = phrase;
1340
+ else drops.push(phrase);
1341
+ }
1342
+ const sentences = [];
1343
+ if (notRead.length > 0) sentences.push(`Not read: ${notRead.join("; ")}.`);
1344
+ if (drops.length > 0) sentences.push(`Dropped: ${drops.join(", ")}.`);
1345
+ if (renamed !== "") sentences.push(`Renamed: ${renamed}.`);
1346
+ if (inlined !== "") sentences.push(`Inlined: ${inlined}.`);
1347
+ return sentences.join(" ");
1348
+ }
1349
+ function phraseFor(kind, entries) {
1350
+ const n = entries.length;
1351
+ const counted = Object.hasOwn(COUNTED, kind) ? COUNTED[kind] : void 0;
1352
+ if (counted !== void 0) return `${n} ${n === 1 ? counted[0] : counted[1]}`;
1353
+ return `${kind} on ${n} ${nounFor(entries)}`;
1354
+ }
1355
+ function nounFor(entries) {
1356
+ const one = entries.length === 1;
1357
+ const slots = entries.filter((entry) => entry.element.includes(".")).length;
1358
+ if (slots === entries.length) return one ? "slot" : "slots";
1359
+ if (slots === 0) return one ? "class" : "classes";
1360
+ return one ? "element" : "elements";
1361
+ }
1362
+ var capitalise2 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1363
+ var decapitalise = (text) => text.charAt(0).toLowerCase() + text.slice(1);
1364
+ var parts = (text) => text.split(/[^A-Za-z0-9]+/).filter((part) => part !== "");
1365
+ function toClassName(text) {
1366
+ if (CLASS_NAME.test(text)) return text;
1367
+ const name2 = parts(text).map(capitalise2).join("");
1368
+ if (name2 === "") return "Class";
1369
+ return CLASS_NAME.test(name2) ? name2 : `X${name2}`;
1370
+ }
1371
+ function toFieldName(text) {
1372
+ if (FIELD_NAME.test(text)) return text;
1373
+ const name2 = parts(text).map((part, i) => i === 0 ? decapitalise(part) : capitalise2(part)).join("");
1374
+ if (name2 === "") return "field";
1375
+ return FIELD_NAME.test(name2) ? name2 : `x${capitalise2(name2)}`;
1376
+ }
1377
+ function unique(base, taken) {
1378
+ let name2 = base;
1379
+ for (let n = 2; taken.has(name2); n++) name2 = `${base}${n}`;
1380
+ taken.add(name2);
1381
+ return name2;
1382
+ }
1383
+ var get = (record, key2) => Object.hasOwn(record, key2) ? record[key2] : void 0;
1384
+ function asRecord(value) {
1385
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1386
+ return value;
1387
+ }
1388
+ var present = (value) => value !== void 0 && value !== null;
1389
+ function shown(value) {
1390
+ if (Array.isArray(value)) return "a list";
1391
+ if (typeof value === "object" && value !== null) return "a map";
1392
+ return `\`${String(value)}\``;
1393
+ }
1394
+ function localName(mapping) {
1395
+ let cut = -1;
1396
+ for (const mark of [":", "/", "#"]) cut = Math.max(cut, mapping.lastIndexOf(mark));
1397
+ return mapping.slice(cut + 1);
1398
+ }
1399
+ var stringOf = (value) => typeof value === "string" && value !== "" ? value : void 0;
1400
+ var stringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1401
+ function annotationsOf(def) {
1402
+ const out = /* @__PURE__ */ new Map();
1403
+ const annotations = asRecord(get(def, "annotations"));
1404
+ if (annotations === void 0) return out;
1405
+ for (const tag of Object.keys(annotations)) {
1406
+ const raw = get(annotations, tag);
1407
+ const long = asRecord(raw);
1408
+ const value = long === void 0 ? raw : get(long, "value");
1409
+ out.set(tag, value ?? null);
1410
+ }
1411
+ return out;
1412
+ }
1413
+ function notText(tag, value) {
1414
+ if (value === null) return `\`${tag}\` was written with no value`;
1415
+ if (Array.isArray(value)) return `\`${tag}\` is a list, not text`;
1416
+ if (typeof value === "object") return `\`${tag}\` is a map, not text`;
1417
+ return `\`${tag}\` is a ${typeof value}, not text`;
1418
+ }
1419
+ function slotKeysOf(def) {
1420
+ const keys = Object.keys(asRecord(get(def, "attributes")) ?? {});
1421
+ for (const slot of stringList(get(def, "slots"))) {
1422
+ if (!keys.includes(slot)) keys.push(slot);
1423
+ }
1424
+ return keys;
1425
+ }
1426
+ function oneLine(text) {
1427
+ if (text === void 0) return void 0;
1428
+ const line = text.replace(/\s*[\r\n]+\s*/g, " ").trim();
1429
+ return line === "" ? void 0 : line;
1430
+ }
1431
+ var list = (items) => items.map((item) => `\`${item}\``).join(", ");
1432
+ var at0 = (text) => ({ text, line: 0, col: 0, end: 0 });
1433
+ var stringType = (many) => ({
1434
+ kind: "primitive",
1435
+ name: "string",
1436
+ written: at0("string"),
1437
+ many
1438
+ });
1439
+ var schemaElement = (root) => stringOf(get(root, "name")) ?? "schema";
1440
+ var nothing = (element, detail) => ({
1441
+ document: parse(""),
1442
+ source: "",
1443
+ dropped: [{ kind: UNREADABLE, element, detail }]
1444
+ });
1445
+
1446
+ // src/import.ts
1447
+ import { parse as parseYAML } from "yaml";
1448
+ function importLinkML(text) {
1449
+ let schema;
1450
+ try {
1451
+ schema = parseYAML(text);
1452
+ } catch (error) {
1453
+ return { output: "", dropped: [], diagnostics: [notYAML(error)] };
1454
+ }
1455
+ const { document, source, dropped } = fromLinkML(schema);
1456
+ return { output: source, dropped, diagnostics: resolve(document).diagnostics };
1457
+ }
1458
+ function notYAML(error) {
1459
+ const first = error instanceof Error ? error.message.split("\n")[0] ?? "" : String(error);
1460
+ const detail = first.replace(/:$/, "");
1461
+ return {
1462
+ severity: "error",
1463
+ code: "E_NOT_YAML",
1464
+ message: `the input is not YAML: ${detail}`,
1465
+ line: 1
1466
+ };
1467
+ }
1468
+
911
1469
  // src/index.ts
912
- var VERSION = "0.2.1";
1470
+ var VERSION = "0.4.0";
913
1471
  export {
914
1472
  VERSION,
915
1473
  compile,
916
1474
  formatDiagnostic,
1475
+ formatDropped,
1476
+ fromLinkML,
1477
+ importLinkML,
917
1478
  parse,
918
1479
  resolve,
919
1480
  serialize,
920
1481
  toLinkML,
921
- toMermaid
1482
+ toMermaid,
1483
+ toSkiss
922
1484
  };
923
1485
  //# sourceMappingURL=index.js.map