@eriknaslund/skiss 0.3.0 → 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/LICENSE +1 -1
- package/README.md +5 -2
- package/dist/cli.js +105 -35
- package/dist/index.d.ts +5 -5
- package/dist/index.js +91 -34
- package/package.json +4 -2
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
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
|
|
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 (!
|
|
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 (!
|
|
370
|
+
if (!ENUM_VALUE.test(w.text)) {
|
|
370
371
|
return fail(
|
|
371
372
|
"E_UNPARSABLE",
|
|
372
|
-
`An enum value is letters, digits, \`-\` and \`_\`,
|
|
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;
|
|
@@ -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 =
|
|
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
|
|
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
|
|
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);
|
|
@@ -942,10 +958,15 @@ function typeText2(type) {
|
|
|
942
958
|
}
|
|
943
959
|
return type.many ? `${text}[]` : text;
|
|
944
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}`;
|
|
945
966
|
function withTrailer(head, node) {
|
|
946
967
|
const parts2 = [];
|
|
947
968
|
if (node.description !== void 0 && node.description !== "")
|
|
948
|
-
parts2.push(
|
|
969
|
+
parts2.push(writeDescription(node.description));
|
|
949
970
|
if (node.note !== void 0 && node.note !== "") parts2.push(`? ${node.note}`);
|
|
950
971
|
if (parts2.length === 0) return head;
|
|
951
972
|
const gap = head.length < TRAILER_COLUMN - 1 ? " ".repeat(TRAILER_COLUMN - head.length) : " ";
|
|
@@ -983,6 +1004,7 @@ var QUIET_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
|
983
1004
|
"slots",
|
|
984
1005
|
"enums"
|
|
985
1006
|
]);
|
|
1007
|
+
var REWORDED = "a standalone `?` is attached to the word before it";
|
|
986
1008
|
var UNKNOWN_TYPE = /^[a-z][a-z0-9_-]*$/;
|
|
987
1009
|
function fromLinkML(schema) {
|
|
988
1010
|
const root = asRecord(schema);
|
|
@@ -1026,7 +1048,7 @@ function fromLinkML(schema) {
|
|
|
1026
1048
|
const rawValues = get(asRecord(get(enumDefs, key2)) ?? {}, "permissible_values");
|
|
1027
1049
|
const values = asRecord(rawValues);
|
|
1028
1050
|
const keys = Object.keys(values ?? {});
|
|
1029
|
-
const bad = keys.find((value) => !
|
|
1051
|
+
const bad = keys.find((value) => !ENUM_VALUE.test(value));
|
|
1030
1052
|
if (values === void 0 && present(rawValues)) {
|
|
1031
1053
|
unwritable.set(key2, `${shown(rawValues)} is not a map of permissible values`);
|
|
1032
1054
|
} else if (keys.length < 2) {
|
|
@@ -1057,14 +1079,26 @@ function fromLinkML(schema) {
|
|
|
1057
1079
|
}
|
|
1058
1080
|
continue;
|
|
1059
1081
|
}
|
|
1082
|
+
const undeclared = annotations.get("undeclared");
|
|
1083
|
+
if (undeclared !== void 0 && typeof undeclared !== "boolean") {
|
|
1084
|
+
report("annotation", name2, notText("undeclared", undeclared));
|
|
1085
|
+
}
|
|
1060
1086
|
const node = { name: at0(name2), fields: [], line: 0 };
|
|
1061
1087
|
const description = oneLine(stringOf(get(def, "description")));
|
|
1062
|
-
if (description !== void 0)
|
|
1088
|
+
if (description !== void 0) {
|
|
1089
|
+
node.description = description;
|
|
1090
|
+
if (rewordsDescription(description)) report("reworded", name2, REWORDED);
|
|
1091
|
+
}
|
|
1063
1092
|
const system = annotations.get("system");
|
|
1064
|
-
if (typeof system === "string"
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
if (
|
|
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));
|
|
1068
1102
|
const mappings = stringList(get(def, "close_mappings"));
|
|
1069
1103
|
const first = mappings[0];
|
|
1070
1104
|
const target = first === void 0 ? "" : localName(first);
|
|
@@ -1169,13 +1203,18 @@ function fromLinkML(schema) {
|
|
|
1169
1203
|
line: 0
|
|
1170
1204
|
};
|
|
1171
1205
|
const description = oneLine(stringOf(get(def, "description")));
|
|
1172
|
-
if (description !== void 0)
|
|
1206
|
+
if (description !== void 0) {
|
|
1207
|
+
field.description = description;
|
|
1208
|
+
if (rewordsDescription(description)) report("reworded", element, REWORDED);
|
|
1209
|
+
}
|
|
1173
1210
|
const type = typeFor(element, name2, def);
|
|
1174
1211
|
if (type !== void 0) field.type = type;
|
|
1175
1212
|
const annotations = annotationsOf(def);
|
|
1176
1213
|
const system = annotations.get("system");
|
|
1177
|
-
if (typeof system === "string"
|
|
1178
|
-
|
|
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));
|
|
1179
1218
|
const joins = annotations.get("joins_to");
|
|
1180
1219
|
if (typeof joins === "string") {
|
|
1181
1220
|
const dot = joins.indexOf(".");
|
|
@@ -1188,9 +1227,12 @@ function fromLinkML(schema) {
|
|
|
1188
1227
|
reference(element, target, targetClass);
|
|
1189
1228
|
reference(element, targetField, targetName);
|
|
1190
1229
|
} else report("annotation", element, "`joins_to` is not `Class.field`");
|
|
1191
|
-
} else if (joins !== void 0) report("annotation", element, "
|
|
1192
|
-
const
|
|
1193
|
-
if (
|
|
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));
|
|
1194
1236
|
for (const other of Object.keys(def)) {
|
|
1195
1237
|
if (!SLOT_KEYS.has(other)) {
|
|
1196
1238
|
report(other, element);
|
|
@@ -1265,13 +1307,17 @@ var COUNTED = {
|
|
|
1265
1307
|
class: ["class", "classes"],
|
|
1266
1308
|
slot: ["slot", "slots"],
|
|
1267
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"],
|
|
1268
1314
|
// `mixin: true` marks the class; `mixins: [M]` points at another one.
|
|
1269
1315
|
mixin: ["mixin class", "mixin classes"],
|
|
1270
1316
|
mixins: ["mixin", "mixins"],
|
|
1271
1317
|
pattern: ["pattern", "patterns"],
|
|
1272
1318
|
unit: ["unit", "units"]
|
|
1273
1319
|
};
|
|
1274
|
-
var
|
|
1320
|
+
var UNREADABLE = "unreadable";
|
|
1275
1321
|
function formatDropped(dropped) {
|
|
1276
1322
|
const groups = /* @__PURE__ */ new Map();
|
|
1277
1323
|
for (const entry of dropped) {
|
|
@@ -1280,15 +1326,21 @@ function formatDropped(dropped) {
|
|
|
1280
1326
|
else group.push(entry);
|
|
1281
1327
|
}
|
|
1282
1328
|
const drops = [];
|
|
1329
|
+
const notRead = [];
|
|
1283
1330
|
let renamed = "";
|
|
1284
1331
|
let inlined = "";
|
|
1285
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
|
+
}
|
|
1286
1337
|
const phrase = phraseFor(kind, entries);
|
|
1287
1338
|
if (kind === "renamed") renamed = phrase;
|
|
1288
1339
|
else if (kind === "inlined") inlined = phrase;
|
|
1289
1340
|
else drops.push(phrase);
|
|
1290
1341
|
}
|
|
1291
1342
|
const sentences = [];
|
|
1343
|
+
if (notRead.length > 0) sentences.push(`Not read: ${notRead.join("; ")}.`);
|
|
1292
1344
|
if (drops.length > 0) sentences.push(`Dropped: ${drops.join(", ")}.`);
|
|
1293
1345
|
if (renamed !== "") sentences.push(`Renamed: ${renamed}.`);
|
|
1294
1346
|
if (inlined !== "") sentences.push(`Inlined: ${inlined}.`);
|
|
@@ -1298,11 +1350,10 @@ function phraseFor(kind, entries) {
|
|
|
1298
1350
|
const n = entries.length;
|
|
1299
1351
|
const counted = Object.hasOwn(COUNTED, kind) ? COUNTED[kind] : void 0;
|
|
1300
1352
|
if (counted !== void 0) return `${n} ${n === 1 ? counted[0] : counted[1]}`;
|
|
1301
|
-
return `${kind} on ${n} ${nounFor(
|
|
1353
|
+
return `${kind} on ${n} ${nounFor(entries)}`;
|
|
1302
1354
|
}
|
|
1303
|
-
function nounFor(
|
|
1355
|
+
function nounFor(entries) {
|
|
1304
1356
|
const one = entries.length === 1;
|
|
1305
|
-
if (ENUM_KINDS.has(kind)) return one ? "enum" : "enums";
|
|
1306
1357
|
const slots = entries.filter((entry) => entry.element.includes(".")).length;
|
|
1307
1358
|
if (slots === entries.length) return one ? "slot" : "slots";
|
|
1308
1359
|
if (slots === 0) return one ? "class" : "classes";
|
|
@@ -1353,12 +1404,18 @@ function annotationsOf(def) {
|
|
|
1353
1404
|
if (annotations === void 0) return out;
|
|
1354
1405
|
for (const tag of Object.keys(annotations)) {
|
|
1355
1406
|
const raw = get(annotations, tag);
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1407
|
+
const long = asRecord(raw);
|
|
1408
|
+
const value = long === void 0 ? raw : get(long, "value");
|
|
1409
|
+
out.set(tag, value ?? null);
|
|
1359
1410
|
}
|
|
1360
1411
|
return out;
|
|
1361
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
|
+
}
|
|
1362
1419
|
function slotKeysOf(def) {
|
|
1363
1420
|
const keys = Object.keys(asRecord(get(def, "attributes")) ?? {});
|
|
1364
1421
|
for (const slot of stringList(get(def, "slots"))) {
|
|
@@ -1383,7 +1440,7 @@ var schemaElement = (root) => stringOf(get(root, "name")) ?? "schema";
|
|
|
1383
1440
|
var nothing = (element, detail) => ({
|
|
1384
1441
|
document: parse(""),
|
|
1385
1442
|
source: "",
|
|
1386
|
-
dropped: [{ kind:
|
|
1443
|
+
dropped: [{ kind: UNREADABLE, element, detail }]
|
|
1387
1444
|
});
|
|
1388
1445
|
|
|
1389
1446
|
// src/import.ts
|
|
@@ -1410,7 +1467,7 @@ function notYAML(error) {
|
|
|
1410
1467
|
}
|
|
1411
1468
|
|
|
1412
1469
|
// src/index.ts
|
|
1413
|
-
var VERSION = "0.
|
|
1470
|
+
var VERSION = "0.4.0";
|
|
1414
1471
|
export {
|
|
1415
1472
|
VERSION,
|
|
1416
1473
|
compile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eriknaslund/skiss",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A text notation for sketching data models, compiled to LinkML",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
"node": ">=20"
|
|
13
13
|
},
|
|
14
14
|
"packageManager": "pnpm@9.15.9",
|
|
15
|
+
"sideEffects": false,
|
|
15
16
|
"files": [
|
|
16
|
-
"dist"
|
|
17
|
+
"dist",
|
|
18
|
+
"!dist/**/*.map"
|
|
17
19
|
],
|
|
18
20
|
"exports": {
|
|
19
21
|
".": {
|